aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/ShapeTraversalService.cs30
-rw-r--r--src/frontend/app/components/LineIcon.css146
-rw-r--r--src/frontend/app/components/LineIcon.tsx16
-rw-r--r--src/frontend/app/components/StopSheet.tsx2
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationCard.css214
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx26
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationList.css226
-rw-r--r--src/frontend/app/root.css4
-rw-r--r--src/frontend/app/routes/estimates-$id.tsx2
-rw-r--r--src/frontend/app/routes/stops-$id.css2
-rw-r--r--src/frontend/app/routes/stops-$id.tsx2
-rw-r--r--src/frontend/package-lock.json643
-rw-r--r--src/frontend/package.json4
-rw-r--r--src/frontend/vite.config.ts7
14 files changed, 929 insertions, 395 deletions
diff --git a/src/Costasdev.Busurbano.Backend/Services/ShapeTraversalService.cs b/src/Costasdev.Busurbano.Backend/Services/ShapeTraversalService.cs
index c8fbbc8..928c173 100644
--- a/src/Costasdev.Busurbano.Backend/Services/ShapeTraversalService.cs
+++ b/src/Costasdev.Busurbano.Backend/Services/ShapeTraversalService.cs
@@ -112,6 +112,17 @@ public class ShapeTraversalService
// Find the closest point on the shape to the stop
int closestPointIndex = FindClosestPointIndex(shape.Points, stopLocation);
+ // Calculate the total distance from the start of the shape to the stop
+ double totalDistanceToStop = CalculateTotalDistance(shape.Points.ToArray(), closestPointIndex);
+
+ // If the reported distance exceeds the total distance to the stop, the bus is likely
+ // on a previous trip whose shape we don't have. Don't provide position information.
+ if (distanceMeters > totalDistanceToStop)
+ {
+ _logger.LogDebug("Distance {Distance}m exceeds total shape distance to stop {Total}m - bus likely on previous trip", distanceMeters, totalDistanceToStop);
+ return (null, closestPointIndex);
+ }
+
// Traverse backwards from the closest point to find the position at the given distance
var (busPoint, forwardIndex) = TraverseBackwards(shape.Points.ToArray(), closestPointIndex, distanceMeters);
@@ -218,6 +229,25 @@ public class ShapeTraversalService
}
/// <summary>
+ /// Calculates the total distance along the shape from the start to a given index
+ /// </summary>
+ private double CalculateTotalDistance(Epsg25829[] shapePoints, int endIndex)
+ {
+ if (endIndex <= 0 || shapePoints.Length == 0)
+ {
+ return 0;
+ }
+
+ double totalDistance = 0;
+ for (int i = 1; i <= endIndex && i < shapePoints.Length; i++)
+ {
+ totalDistance += CalculateDistance(shapePoints[i - 1], shapePoints[i]);
+ }
+
+ return totalDistance;
+ }
+
+ /// <summary>
/// Transforms a point from EPSG:25829 (meters) to EPSG:4326 (lat/lng)
/// </summary>
private Position TransformToLatLng(Epsg25829 point)
diff --git a/src/frontend/app/components/LineIcon.css b/src/frontend/app/components/LineIcon.css
index 721de76..50f2d5f 100644
--- a/src/frontend/app/components/LineIcon.css
+++ b/src/frontend/app/components/LineIcon.css
@@ -1,72 +1,76 @@
/* Vigo line colors */
:root {
- --line-vigo-c1: rgb(237, 71, 19);
- --line-vigo-c1-text: #ffffff;
- --line-vigo-c3d: rgb(255, 204, 0);
- --line-vigo-c3i: rgb(255, 204, 0);
- --line-vigo-l4a: rgb(0, 153, 0);
- --line-vigo-l4a-text: #ffffff;
- --line-vigo-l4c: rgb(0, 153, 0);
- --line-vigo-l4c-text: #ffffff;
- --line-vigo-l5a: rgb(0, 176, 240);
- --line-vigo-l5b: rgb(0, 176, 240);
- --line-vigo-l6: rgb(204, 51, 153);
- --line-vigo-l7: rgb(150, 220, 153);
- --line-vigo-l9b: rgb(244, 202, 140);
- --line-vigo-l10: rgb(153, 51, 0);
- --line-vigo-l10-text: #ffffff;
- --line-vigo-l11: rgb(226, 0, 38);
- --line-vigo-l11-text: #ffffff;
- --line-vigo-l12a: rgb(106, 150, 190);
- --line-vigo-l12b: rgb(106, 150, 190);
- --line-vigo-l13: rgb(0, 176, 240);
- --line-vigo-l14: rgb(129, 142, 126);
- --line-vigo-l14-text: #ffffff;
- --line-vigo-l15a: rgb(216, 168, 206);
- --line-vigo-l15b: rgb(216, 168, 206);
- --line-vigo-l15c: rgb(216, 168, 168);
- --line-vigo-l16: rgb(129, 142, 126);
- --line-vigo-l16-text: #ffffff;
- --line-vigo-l17: rgb(214, 245, 31);
- --line-vigo-l18a: rgb(212, 80, 168);
- --line-vigo-l18a-text: #ffffff;
- --line-vigo-l18b: rgb(212, 80, 168);
- --line-vigo-l18b-text: #ffffff;
- --line-vigo-l18h: rgb(212, 80, 168);
- --line-vigo-l18h-text: #ffffff;
- --line-vigo-l23: rgb(0, 70, 210);
- --line-vigo-l24: rgb(191, 191, 191);
- --line-vigo-l25: rgb(172, 100, 4);
- --line-vigo-l27: rgb(112, 74, 42);
- --line-vigo-l27-text: #ffffff;
- --line-vigo-l28: rgb(176, 189, 254);
- --line-vigo-l29: rgb(248, 184, 90);
- --line-vigo-l31: rgb(255, 255, 0);
- --line-vigo-a: rgb(119, 41, 143);
- --line-vigo-a-text: #ffffff;
- --line-vigo-h: rgb(0, 96, 168);
- --line-vigo-h-text: #ffffff;
- --line-vigo-h1: rgb(0, 96, 168);
- --line-vigo-h1-text: #ffffff;
- --line-vigo-h2: rgb(0, 96, 168);
- --line-vigo-h2-text: #ffffff;
- --line-vigo-h3: rgb(0, 96, 168);
- --line-vigo-h3-text: #ffffff;
- --line-vigo-lzd: rgb(61, 78, 167);
- --line-vigo-n1: rgb(191, 191, 191);
- --line-vigo-n4: rgb(102, 51, 102);
- --line-vigo-n4-text: #ffffff;
- --line-vigo-psa1: rgb(0, 153, 0);
- --line-vigo-psa4: rgb(0, 153, 0);
- --line-vigo-ptl: rgb(150, 220, 153);
- --line-vigo-turistico: rgb(102, 51, 102);
- --line-vigo-u1: rgb(172, 100, 4);
- --line-vigo-u1-text: #ffffff;
- --line-vigo-u2: rgb(172, 100, 4);
- --line-vigo-u2-text: #ffffff;
+ --line-c1: hsl(14, 86%, 50%);
+ --line-c1-text: hsl(0, 0%, 100%);
+ --line-c3d: hsl(48, 100%, 50%);
+ --line-c3i: hsl(48, 100%, 50%);
+ --line-l4a: hsl(120, 100%, 30%);
+ --line-l4a-text: hsl(0, 0%, 100%);
+ --line-l4c: hsl(120, 100%, 30%);
+ --line-l4c-text: hsl(0, 0%, 100%);
+ --line-l5a: hsl(204, 100%, 54%);
+ --line-l5a-text: hsl(0, 0%, 100%);
+ --line-l5b: hsl(204, 100%, 54%);
+ --line-l5b-text: hsl(0, 0%, 100%);
+ --line-l6: hsl(330, 60%, 50%);
+ --line-l7: hsl(120, 60%, 70%);
+ --line-l9b: hsl(36, 83%, 75%);
+ --line-l10: hsl(30, 80%, 20%);
+ --line-l10-text: hsl(0, 0%, 100%);
+ --line-l11: hsl(0, 100%, 44%);
+ --line-l11-text: hsl(0, 0%, 100%);
+ --line-l12a: hsl(210, 40%, 56%);
+ --line-l12b: hsl(209, 39%, 58%);
+ --line-l13: hsl(196, 100%, 47%);
+ --line-l14: hsl(120, 10%, 44%);
+ --line-l14-text: hsl(0, 0%, 100%);
+ --line-l15a: hsl(313, 38%, 75%);
+ --line-l15b: hsl(313, 38%, 75%);
+ --line-l15c: #d8a8a8;
+ --line-l16: hsl(120, 10%, 44%);
+ --line-l16-text: hsl(0, 0%, 100%);
+ --line-l17: hsl(69, 91%, 54%);
+ --line-l18a: hsl(320, 61%, 57%);
+ --line-l18a-text: hsl(0, 0%, 100%);
+ --line-l18b: hsl(320, 61%, 57%);
+ --line-l18b-text: hsl(0, 0%, 100%);
+ --line-l18h: hsl(320, 61%, 57%);
+ --line-l18h-text: hsl(0, 0%, 100%);
+ --line-l23: hsl(220, 100%, 41%);
+ --line-l24: hsl(0, 0%, 75%);
+ --line-l25: hsl(34, 95%, 35%);
+ --line-l27: hsl(30, 60%, 30%);
+ --line-l27-text: hsl(0, 0%, 100%);
+ --line-l28: hsl(230, 98%, 84%);
+ --line-l29: hsl(36, 92%, 66%);
+ --line-l31: hsl(60, 100%, 50%);
+ --line-a: hsl(300, 70%, 35%);
+ --line-a-text: hsl(0, 0%, 100%);
+ --line-a1: hsl(300, 70%, 35%);
+ --line-a1-text: hsl(0, 0%, 100%);
+ --line-h: hsl(210, 100%, 33%);
+ --line-h-text: hsl(0, 0%, 100%);
+ --line-h1: hsl(210, 100%, 33%);
+ --line-h1-text: hsl(0, 0%, 100%);
+ --line-h2: hsl(210, 100%, 33%);
+ --line-h2-text: hsl(0, 0%, 100%);
+ --line-h3: hsl(210, 100%, 33%);
+ --line-h3-text: hsl(0, 0%, 100%);
+ --line-lzd: hsl(220, 60%, 50%);
+ --line-n1: hsl(0, 51%, 53%);
+ --line-n4: hsl(300, 33%, 30%);
+ --line-n4-text: hsl(0, 0%, 100%);
+ --line-psa1: hsl(120, 100%, 30%);
+ --line-psa4: hsl(120, 100%, 30%);
+ --line-ptl: hsl(120, 60%, 70%);
+ --line-tur: hsl(300, 33%, 30%);
+ --line-u1: hsl(30, 80%, 20%);
+ --line-u1-text: hsl(0, 0%, 100%);
+ --line-u2: hsl(30, 80%, 20%);
+ --line-u2-text: hsl(0, 0%, 100%);
}
-.line-icon {
+.line-icon-default {
display: inline-block;
padding: 0.25rem 0.5rem;
margin-right: 0.5rem;
@@ -81,6 +85,18 @@
border-color: var(--line-colour);
}
+.line-icon-pill {
+ display: inline-block;
+ padding: 0.25rem 0.5rem;
+ margin-right: 0.5rem;
+ font-size: 0.9rem;
+ font-weight: 600;
+ border-radius: 0.25rem;
+
+ background-color: var(--line-colour);
+ color: var(--line-text-colour);
+}
+
.line-icon-rounded {
display: block;
width: 5ch;
diff --git a/src/frontend/app/components/LineIcon.tsx b/src/frontend/app/components/LineIcon.tsx
index 8c3dbeb..5ccf80a 100644
--- a/src/frontend/app/components/LineIcon.tsx
+++ b/src/frontend/app/components/LineIcon.tsx
@@ -4,24 +4,28 @@ import "./LineIcon.css";
interface LineIconProps {
line: string;
+
+ /**
+ * @deprecated Unused since region is only Vigo
+ */
region?: RegionId;
- rounded?: boolean;
+
+ mode?: "rounded"|"pill"|"default";
}
const LineIcon: React.FC<LineIconProps> = ({
line,
- region = "vigo",
- rounded = false,
+ mode = "default",
}) => {
const formattedLine = useMemo(() => {
return /^[a-zA-Z]/.test(line) ? line : `L${line}`;
}, [line]);
- const cssVarName = `--line-${region}-${formattedLine.toLowerCase()}`;
- const cssTextVarName = `--line-${region}-${formattedLine.toLowerCase()}-text`;
+ const cssVarName = `--line-${formattedLine.toLowerCase()}`;
+ const cssTextVarName = `--line-${formattedLine.toLowerCase()}-text`;
return (
<span
- className={rounded ? "line-icon-rounded" : "line-icon"}
+ className={`line-icon-${mode}`}
style={
{
"--line-colour": `var(${cssVarName})`,
diff --git a/src/frontend/app/components/StopSheet.tsx b/src/frontend/app/components/StopSheet.tsx
index cf070e2..6d2abf0 100644
--- a/src/frontend/app/components/StopSheet.tsx
+++ b/src/frontend/app/components/StopSheet.tsx
@@ -130,7 +130,7 @@ export const StopSheet: React.FC<StopSheetProps> = ({
>
{stop.lines.map((line) => (
<div key={line} className="stop-sheet-line-icon">
- <LineIcon line={line} region={region} rounded />
+ <LineIcon line={line} region={region} mode="rounded" />
</div>
))}
</div>
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css
new file mode 100644
index 0000000..8a442c3
--- /dev/null
+++ b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css
@@ -0,0 +1,214 @@
+@reference "../../root.css";
+
+.consolidated-circulation-card {
+ all: unset;
+ flex: 0 0 auto;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ background-color: var(--message-background-color);
+ border-radius: 12px;
+ border: 1px solid var(--border-color);
+ padding: 0.65rem 0.85rem;
+ transition: all 0.2s ease;
+}
+
+.consolidated-circulation-card.has-gps {
+ cursor: pointer;
+}
+
+.consolidated-circulation-card.no-gps {
+ cursor: not-allowed;
+ opacity: 0.7;
+}
+
+.consolidated-circulation-card.has-gps:hover {
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
+ border-color: var(--button-background-color);
+ background-color: color-mix(
+ in oklab,
+ var(--button-background-color) 5%,
+ var(--message-background-color)
+ );
+}
+
+.consolidated-circulation-card.has-gps:active {
+ transform: scale(0.98);
+}
+
+.consolidated-circulation-card:disabled {
+ pointer-events: none;
+}
+
+
+.consolidated-circulation-card .card-row {
+ display: flex;
+ align-items: center;
+ gap: 0.65rem;
+}
+
+.consolidated-circulation-card .card-row.main {
+ min-height: 48px;
+}
+
+.consolidated-circulation-card .line-info {
+ flex-shrink: 0;
+}
+
+.consolidated-circulation-card .route-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.consolidated-circulation-card .route-info strong {
+ font-size: 1rem;
+ color: var(--text-color);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ -webkit-box-orient: vertical;
+ line-height: 1.25;
+}
+
+.consolidated-circulation-card .eta-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.3rem 0.45rem;
+ border-radius: 12px;
+}
+
+.consolidated-circulation-card .eta-text {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ line-height: 1;
+}
+
+.consolidated-circulation-card .eta-value {
+ font-size: 1.15rem;
+ font-weight: 700;
+}
+
+.consolidated-circulation-card .eta-unit {
+ font-size: 0.65rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+.consolidated-circulation-card .eta-badge.time-running {
+ background: rgba(34, 197, 94, 0.12);
+ color: #1a9e56;
+}
+
+.consolidated-circulation-card .eta-badge.time-delayed {
+ background: rgba(255, 106, 0, 0.12);
+ color: #d06100;
+}
+
+.consolidated-circulation-card .eta-badge.time-scheduled {
+ background: rgba(11, 61, 145, 0.12);
+ color: #0b3d91;
+}
+
+[data-theme="dark"] .consolidated-circulation-card .eta-badge.time-scheduled {
+ color: #8fb4ff;
+}
+
+.consolidated-circulation-card .card-row.meta {
+ flex-wrap: wrap;
+ justify-content: flex-start;
+ gap: 0.4rem;
+}
+
+.meta-chip {
+ font-size: 0.75rem;
+ padding: 0.2rem 0.55rem;
+ border-radius: 999px;
+ background: rgba(0, 0, 0, 0.03);
+
+ @apply flex items-center justify-center gap-1 flex-shrink-0 bg-gray-200/30 dark:bg-gray-600/30;
+}
+
+.meta-chip.delay-ok {
+ @apply bg-green-400/30 dark:bg-green-600/30 border-green-500 dark:border-green-700 text-green-800 dark:text-green-200;
+}
+
+.meta-chip.delay-warn {
+ @apply bg-yellow-400/30 dark:bg-yellow-600/30 border-yellow-500 dark:border-yellow-700 text-yellow-800 dark:text-yellow-200;
+}
+
+.meta-chip.delay-critical {
+ @apply bg-red-400/30 dark:bg-red-600/30 border-red-500 dark:border-red-700 text-white;
+}
+
+.meta-chip.delay-early {
+ @apply bg-blue-400/30 dark:bg-blue-600/30 border-blue-500 dark:border-blue-700 text-blue-800 dark:text-blue-200;
+}
+
+/* GPS Indicator */
+.gps-indicator {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ flex-shrink: 0;
+ position: relative;
+}
+
+.gps-pulse {
+ position: absolute;
+ width: 8px;
+ height: 8px;
+ background: #22c55e;
+ border-radius: 50%;
+ box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
+ animation: gpsPulse 2s ease-in-out infinite;
+}
+
+.gps-pulse.previous-trip {
+ background: #ff9800;
+ box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
+ animation: gpsPulseOrange 2s ease-in-out infinite;
+}
+
+@keyframes gpsPulse {
+ 0% {
+ box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
+ }
+ 50% {
+ box-shadow: 0 0 0 6px rgba(34, 197, 94, 0.1);
+ }
+ 100% {
+ box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
+ }
+}
+
+@keyframes gpsPulseOrange {
+ 0% {
+ box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
+ }
+ 50% {
+ box-shadow: 0 0 0 6px rgba(255, 152, 0, 0.1);
+ }
+ 100% {
+ box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
+ }
+}
+
+@media (max-width: 480px) {
+ .consolidated-circulation-card {
+ padding: 0.65rem 0.75rem;
+ }
+
+ .consolidated-circulation-card .card-row {
+ gap: 0.5rem;
+ }
+
+ .consolidated-circulation-card .eta-badge {
+ padding: 0.25rem 0.4rem;
+ }
+}
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx
index 707ecce..0b97c11 100644
--- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx
+++ b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx
@@ -4,7 +4,7 @@ import { type RegionConfig } from "~/config/RegionConfig";
import LineIcon from "~components/LineIcon";
import { type ConsolidatedCirculation } from "~routes/stops-$id";
-import "./ConsolidatedCirculationList.css";
+import "./ConsolidatedCirculationCard.css";
interface ConsolidatedCirculationCardProps {
estimate: ConsolidatedCirculation;
@@ -75,18 +75,6 @@ export const ConsolidatedCirculationCard: React.FC<
> = ({ estimate, regionConfig, onMapClick, readonly }) => {
const { t } = useTranslation();
- const absoluteArrivalTime = (minutes: number) => {
- const now = new Date();
- const arrival = new Date(now.getTime() + minutes * 60000);
- return Intl.DateTimeFormat(
- typeof navigator !== "undefined" ? navigator.language : "en",
- {
- hour: "2-digit",
- minute: "2-digit",
- }
- ).format(arrival);
- };
-
const formatDistance = (meters: number) => {
if (meters > 1024) {
return `${(meters / 1000).toFixed(1)} km`;
@@ -123,9 +111,12 @@ export const ConsolidatedCirculationCard: React.FC<
return null;
}
- const delta = Math.round(estimate.realTime.minutes - estimate.schedule.minutes);
+ const delta = Math.round(
+ estimate.realTime.minutes - estimate.schedule.minutes
+ );
const absDelta = Math.abs(delta);
+ // On time
if (delta === 0) {
return {
label: t("estimates.delay_on_time", "En hora (0 min)"),
@@ -133,8 +124,10 @@ export const ConsolidatedCirculationCard: React.FC<
} as const;
}
+ // Delayed
if (delta > 0) {
- const tone = delta <= 2 ? "delay-ok" : delta <= 10 ? "delay-warn" : "delay-critical";
+ const tone =
+ delta <= 2 ? "delay-ok" : delta <= 10 ? "delay-warn" : "delay-critical";
return {
label: t("estimates.delay_positive", "Retraso de {{minutes}} min", {
minutes: delta,
@@ -143,6 +136,7 @@ export const ConsolidatedCirculationCard: React.FC<
} as const;
}
+ // Early
const tone = absDelta <= 2 ? "delay-ok" : "delay-early";
return {
label: t("estimates.delay_negative", "Adelanto de {{minutes}} min", {
@@ -200,7 +194,7 @@ export const ConsolidatedCirculationCard: React.FC<
>
<div className="card-row main">
<div className="line-info">
- <LineIcon line={estimate.line} region={regionConfig.id} rounded />
+ <LineIcon line={estimate.line} region={regionConfig.id} mode="pill" />
</div>
<div className="route-info">
<strong>{estimate.route}</strong>
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
index 5afdeaa..044b4a3 100644
--- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
+++ b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
@@ -11,229 +11,3 @@
color: var(--subtitle-color);
font-size: 0.95rem;
}
-
-.consolidated-circulation-card {
- all: unset;
- flex: 0 0 auto;
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- background-color: var(--message-background-color);
- border-radius: 12px;
- border: 1px solid var(--border-color);
- padding: 0.65rem 0.85rem;
- transition: all 0.2s ease;
-}
-
-.consolidated-circulation-card.has-gps {
- cursor: pointer;
-}
-
-.consolidated-circulation-card.no-gps {
- cursor: not-allowed;
- opacity: 0.7;
-}
-
-.consolidated-circulation-card.has-gps:hover {
- box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
- border-color: var(--button-background-color);
- background-color: color-mix(
- in oklab,
- var(--button-background-color) 5%,
- var(--message-background-color)
- );
-}
-
-.consolidated-circulation-card.has-gps:active {
- transform: scale(0.98);
-}
-
-.consolidated-circulation-card:disabled {
- pointer-events: none;
-}
-
-
-.consolidated-circulation-card .card-row {
- display: flex;
- align-items: center;
- gap: 0.65rem;
-}
-
-.consolidated-circulation-card .card-row.main {
- min-height: 48px;
-}
-
-.consolidated-circulation-card .line-info {
- flex-shrink: 0;
-}
-
-.consolidated-circulation-card .route-info {
- flex: 1;
- min-width: 0;
-}
-
-.consolidated-circulation-card .route-info strong {
- font-size: 1rem;
- color: var(--text-color);
- overflow: hidden;
- text-overflow: ellipsis;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- line-height: 1.25;
-}
-
-.consolidated-circulation-card .eta-badge {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 0.3rem 0.45rem;
- border-radius: 12px;
-}
-
-.consolidated-circulation-card .eta-text {
- display: flex;
- flex-direction: column;
- align-items: center;
- line-height: 1;
-}
-
-.consolidated-circulation-card .eta-value {
- font-size: 1.15rem;
- font-weight: 700;
-}
-
-.consolidated-circulation-card .eta-unit {
- font-size: 0.65rem;
- text-transform: uppercase;
- letter-spacing: 0.08em;
-}
-
-.consolidated-circulation-card .eta-badge.time-running {
- background: rgba(34, 197, 94, 0.12);
- color: #1a9e56;
-}
-
-.consolidated-circulation-card .eta-badge.time-delayed {
- background: rgba(255, 106, 0, 0.12);
- color: #d06100;
-}
-
-.consolidated-circulation-card .eta-badge.time-scheduled {
- background: rgba(11, 61, 145, 0.12);
- color: #0b3d91;
-}
-
-[data-theme="dark"] .consolidated-circulation-card .eta-badge.time-scheduled {
- color: #8fb4ff;
-}
-
-.consolidated-circulation-card .card-row.meta {
- flex-wrap: wrap;
- justify-content: flex-start;
- gap: 0.4rem;
-}
-
-.meta-chip {
- font-size: 0.75rem;
- padding: 0.2rem 0.55rem;
- border-radius: 999px;
- background: rgba(0, 0, 0, 0.03);
- color: var(--subtitle-color);
- border: 1px solid var(--border-color);
-}
-
-@media (prefers-color-scheme: dark) {
- .meta-chip {
- background: rgba(255, 255, 255, 0.05);
- }
-}
-
-.meta-chip.delay-ok {
- background: hsla(142, 71%, 45%, 0.15);
- border-color: hsla(142, 71%, 45%, 0.5);
- color: #1a9e56;
-}
-
-.meta-chip.delay-warn {
- background: hsla(43, 96%, 56%, 0.25);
- border-color: hsla(43, 96%, 56%, 0.6);
- color: hsl(43, 96%, 40%);
-}
-
-.meta-chip.delay-critical {
- background: hsla(0, 84%, 60%, 0.2);
- border-color: hsla(0, 84%, 60%, 0.35);
- color: hsl(0, 74%, 42%);
-}
-
-.meta-chip.delay-early {
- background: hsla(217, 91%, 60%, 0.17);
- border-color: hsla(217, 91%, 60%, 0.3);
- color: hsl(224, 90%, 50%);
-}
-
-/* GPS Indicator */
-.gps-indicator {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 20px;
- height: 20px;
- flex-shrink: 0;
- position: relative;
-}
-
-.gps-pulse {
- position: absolute;
- width: 8px;
- height: 8px;
- background: #22c55e;
- border-radius: 50%;
- box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
- animation: gpsPulse 2s ease-in-out infinite;
-}
-
-.gps-pulse.previous-trip {
- background: #ff9800;
- box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
- animation: gpsPulseOrange 2s ease-in-out infinite;
-}
-
-@keyframes gpsPulse {
- 0% {
- box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
- }
- 50% {
- box-shadow: 0 0 0 6px rgba(34, 197, 94, 0.1);
- }
- 100% {
- box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
- }
-}
-
-@keyframes gpsPulseOrange {
- 0% {
- box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
- }
- 50% {
- box-shadow: 0 0 0 6px rgba(255, 152, 0, 0.1);
- }
- 100% {
- box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
- }
-}
-
-@media (max-width: 480px) {
- .consolidated-circulation-card {
- padding: 0.65rem 0.75rem;
- }
-
- .consolidated-circulation-card .card-row {
- gap: 0.5rem;
- }
-
- .consolidated-circulation-card .eta-badge {
- padding: 0.25rem 0.4rem;
- }
-}
diff --git a/src/frontend/app/root.css b/src/frontend/app/root.css
index d718d92..c880763 100644
--- a/src/frontend/app/root.css
+++ b/src/frontend/app/root.css
@@ -1,3 +1,5 @@
+@import "tailwindcss";
+
:root {
--colour-scheme: light;
--background-color: #ffffff;
@@ -122,8 +124,6 @@ body {
overflow-x: hidden;
}
-
-
.theme-toggle {
background: none;
border: none;
diff --git a/src/frontend/app/routes/estimates-$id.tsx b/src/frontend/app/routes/estimates-$id.tsx
index b92e59d..afeb3d2 100644
--- a/src/frontend/app/routes/estimates-$id.tsx
+++ b/src/frontend/app/routes/estimates-$id.tsx
@@ -302,7 +302,7 @@ export default function Estimates() {
<div className={`estimates-lines-container`}>
{stopData.lines.map((line) => (
<div key={line} className="estimates-line-icon">
- <LineIcon line={line} region={region} rounded />
+ <LineIcon line={line} region={region} mode="rounded" />
</div>
))}
</div>
diff --git a/src/frontend/app/routes/stops-$id.css b/src/frontend/app/routes/stops-$id.css
index 782d9a1..4d204a7 100644
--- a/src/frontend/app/routes/stops-$id.css
+++ b/src/frontend/app/routes/stops-$id.css
@@ -7,7 +7,6 @@
}
.estimates-list-container {
- overflow-y: auto;
flex: 1;
min-height: 0;
@@ -47,7 +46,6 @@
display: flex;
flex-direction: column;
height: 100%;
- overflow: hidden;
padding-block: 1rem;
box-sizing: border-box;
diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx
index 4d96928..de552bd 100644
--- a/src/frontend/app/routes/stops-$id.tsx
+++ b/src/frontend/app/routes/stops-$id.tsx
@@ -238,7 +238,7 @@ export default function Estimates() {
<div className={`estimates-lines-container scrollable`}>
{stopData.lines.map((line) => (
<div key={line} className="estimates-line-icon">
- <LineIcon line={line} region={region} rounded />
+ <LineIcon line={line} region={region} mode="rounded" />
</div>
))}
</div>
diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json
index 55a3610..98bd7be 100644
--- a/src/frontend/package-lock.json
+++ b/src/frontend/package-lock.json
@@ -11,6 +11,7 @@
"@fontsource-variable/roboto": "^5.2.8",
"@react-router/node": "^7.9.6",
"@react-router/serve": "^7.9.6",
+ "@tailwindcss/vite": "^4.1.17",
"framer-motion": "^12.23.24",
"fuse.js": "^7.1.0",
"i18next-browser-languagedetector": "^8.2.0",
@@ -24,7 +25,8 @@
"react-leaflet-markercluster": "^5.0.0-rc.0",
"react-loading-skeleton": "^3.5.0",
"react-modal-sheet": "^5.2.1",
- "react-router": "^7.9.6"
+ "react-router": "^7.9.6",
+ "tailwindcss": "^4.1.17"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
@@ -533,7 +535,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -550,7 +551,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -567,7 +567,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -584,7 +583,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -601,7 +599,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -618,7 +615,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -635,7 +631,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -652,7 +647,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -669,7 +663,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -686,7 +679,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -703,7 +695,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -720,7 +711,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -737,7 +727,6 @@
"cpu": [
"mips64el"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -754,7 +743,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -771,7 +759,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -788,7 +775,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -805,7 +791,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -822,7 +807,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -839,7 +823,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -856,7 +839,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -873,7 +855,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -890,7 +871,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -907,7 +887,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -924,7 +903,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -941,7 +919,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -958,7 +935,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1208,7 +1184,6 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -1219,7 +1194,6 @@
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -1230,7 +1204,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1240,14 +1213,12 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -1647,7 +1618,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1661,7 +1631,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1675,7 +1644,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1689,7 +1657,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1703,7 +1670,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1717,7 +1683,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1731,7 +1696,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1745,7 +1709,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1759,7 +1722,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1773,7 +1735,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1787,7 +1748,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1801,7 +1761,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1815,7 +1774,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1829,7 +1787,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1843,7 +1800,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1870,7 +1826,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1884,7 +1839,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1898,7 +1852,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1912,7 +1865,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1926,7 +1878,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1940,18 +1891,273 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz",
+ "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.17"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz",
+ "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.17",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.17",
+ "@tailwindcss/oxide-darwin-x64": "4.1.17",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.17",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.17",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.17",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.17",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.17",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.17"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz",
+ "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz",
+ "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz",
+ "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz",
+ "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz",
+ "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz",
+ "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz",
+ "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz",
+ "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz",
+ "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz",
+ "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.6.0",
+ "@emnapi/runtime": "^1.6.0",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.0.7",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz",
+ "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz",
+ "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz",
+ "integrity": "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.1.17",
+ "@tailwindcss/oxide": "4.1.17",
+ "tailwindcss": "4.1.17"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "dev": true,
"license": "MIT"
},
"node_modules/@types/geojson": {
@@ -1992,7 +2198,7 @@
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
@@ -3004,6 +3210,15 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -3064,6 +3279,19 @@
"node": ">= 0.8"
}
},
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.3",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
@@ -3115,7 +3343,6 @@
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz",
"integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -3534,7 +3761,6 @@
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@@ -3723,7 +3949,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -3945,6 +4170,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -4290,9 +4521,7 @@
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
- "dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@@ -4428,6 +4657,255 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -4477,6 +4955,15 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/maplibre-gl": {
"version": "5.12.0",
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.12.0.tgz",
@@ -4786,7 +5273,6 @@
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -5100,14 +5586,12 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "dev": true,
"license": "MIT",
"peer": true,
"engines": {
@@ -5131,7 +5615,6 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -5569,7 +6052,6 @@
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -5614,7 +6096,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -5960,7 +6441,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -6203,11 +6683,29 @@
"node": ">=8"
}
},
+ "node_modules/tailwindcss": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
+ "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
@@ -6377,7 +6875,7 @@
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/union-value": {
@@ -6467,9 +6965,9 @@
}
},
"node_modules/valibot": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz",
- "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
+ "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -6516,7 +7014,6 @@
"version": "7.2.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz",
"integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
- "dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
diff --git a/src/frontend/package.json b/src/frontend/package.json
index cbf353a..53d23ef 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -17,6 +17,7 @@
"@fontsource-variable/roboto": "^5.2.8",
"@react-router/node": "^7.9.6",
"@react-router/serve": "^7.9.6",
+ "@tailwindcss/vite": "^4.1.17",
"framer-motion": "^12.23.24",
"fuse.js": "^7.1.0",
"i18next-browser-languagedetector": "^8.2.0",
@@ -30,7 +31,8 @@
"react-leaflet-markercluster": "^5.0.0-rc.0",
"react-loading-skeleton": "^3.5.0",
"react-modal-sheet": "^5.2.1",
- "react-router": "^7.9.6"
+ "react-router": "^7.9.6",
+ "tailwindcss": "^4.1.17"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
diff --git a/src/frontend/vite.config.ts b/src/frontend/vite.config.ts
index 89623c0..b827847 100644
--- a/src/frontend/vite.config.ts
+++ b/src/frontend/vite.config.ts
@@ -1,4 +1,5 @@
import { reactRouter } from "@react-router/dev/vite";
+import tailwindcss from "@tailwindcss/vite";
import { execSync } from "child_process";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
@@ -10,7 +11,11 @@ export default defineConfig({
define: {
__COMMIT_HASH__: JSON.stringify(commitHash),
},
- plugins: [reactRouter(), tsconfigPaths()],
+ plugins: [
+ reactRouter(),
+ tsconfigPaths(),
+ tailwindcss()
+ ],
server: {
proxy: {
"^/api": {