diff options
Diffstat (limited to 'src/frontend')
24 files changed, 212 insertions, 892 deletions
diff --git a/src/frontend/app/components/PullToRefresh.css b/src/frontend/app/components/PullToRefresh.css index 3e8f802..910d201 100644 --- a/src/frontend/app/components/PullToRefresh.css +++ b/src/frontend/app/components/PullToRefresh.css @@ -20,21 +20,21 @@ width: 40px; height: 40px; border-radius: 50%; - background: var(--surface-color, #f8f9fa); - border: 2px solid var(--border-color, #e9ecef); + background: var(--message-background-color); + border: 2px solid var(--border-color); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: all 0.2s ease; } .refresh-icon-container.active { - background: var(--primary-color, #007bff); - border-color: var(--primary-color, #007bff); + background: var(--primary-color); + border-color: var(--primary-color); } .refresh-icon { width: 20px; height: 20px; - color: var(--text-secondary, #6c757d); + color: var(--text-secondary); transition: color 0.2s ease; } diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css deleted file mode 100644 index d9ed38f..0000000 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css +++ /dev/null @@ -1,212 +0,0 @@ -@import "../../tailwind.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 { - 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 { - justify-content: flex-start; - flex-wrap: wrap; - 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 shrink-0 bg-gray-200/30 dark:bg-gray-600/30; -} - -.meta-chip.delay-ok { - @apply bg-green-600/80 dark:bg-green-600/30 border-green-500 dark:border-green-700 text-white dark:text-green-200; -} - -.meta-chip.delay-warn { - @apply bg-amber-600/80 dark:bg-yellow-600/30 border-yellow-500 dark:border-yellow-700 text-white dark:text-yellow-200; -} - -.meta-chip.delay-critical { - @apply bg-red-400/80 dark:bg-red-600/30 border-red-500 dark:border-red-700 text-white; -} - -.meta-chip.delay-early { - @apply bg-blue-400/80 dark:bg-blue-600/30 border-blue-500 dark:border-blue-700 text-white 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 deleted file mode 100644 index 679345f..0000000 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx +++ /dev/null @@ -1,467 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import Marquee from "react-fast-marquee"; -import { useTranslation } from "react-i18next"; -import LineIcon from "~components/LineIcon"; -import { type ConsolidatedCirculation } from "~routes/stops-$id"; - -import { AlertTriangle, LocateIcon } from "lucide-react"; -import "./ConsolidatedCirculationCard.css"; - -interface ConsolidatedCirculationCardProps { - estimate: ConsolidatedCirculation; - onMapClick?: () => void; - readonly?: boolean; - reduced?: boolean; - driver?: string; -} - -// Utility function to parse service ID and get the turn number -const parseServiceId = (serviceId: string): string => { - const parts = serviceId.split("_"); - if (parts.length === 0) return ""; - - const lastPart = parts[parts.length - 1]; - if (lastPart.length < 6) return ""; - - const last6 = lastPart.slice(-6); - const lineCode = last6.slice(0, 3); - const turnCode = last6.slice(-3); - - // Remove leading zeros from turn - const turnNumber = parseInt(turnCode, 10).toString(); - - // Parse line number with special cases - const lineNumber = parseInt(lineCode, 10); - let displayLine: string; - - switch (lineNumber) { - case 1: - displayLine = "C1"; - break; - case 3: - displayLine = "C3"; - break; - case 30: - displayLine = "N1"; - break; - case 33: - displayLine = "N4"; - break; - case 8: - displayLine = "A"; - break; - case 101: - displayLine = "H"; - break; - case 150: - displayLine = "REF"; - break; - case 500: - displayLine = "TUR"; - break; - case 201: - displayLine = "U1"; - break; - case 202: - displayLine = "U2"; - break; - default: - displayLine = `L${lineNumber}`; - } - - return `${displayLine}-${turnNumber}`; -}; - -const AutoMarquee = ({ text }: { text: string }) => { - const containerRef = useRef<HTMLDivElement>(null); - const [shouldScroll, setShouldScroll] = useState(false); - - useEffect(() => { - const el = containerRef.current; - if (!el) return; - - const checkScroll = () => { - // 9px per char for text-sm font-mono is a safe upper bound estimate - // (14px * 0.6 = 8.4px) - const charWidth = 9; - const availableWidth = el.offsetWidth; - const textWidth = text.length * charWidth; - - setShouldScroll(textWidth > availableWidth); - }; - - checkScroll(); - - const observer = new ResizeObserver(checkScroll); - observer.observe(el); - - return () => observer.disconnect(); - }, [text]); - - if (shouldScroll) { - return ( - <div ref={containerRef} className="w-full overflow-hidden"> - <Marquee speed={60} gradient={false}> - <div className="mr-64 text-sm font-mono">{text}</div> - </Marquee> - </div> - ); - } - - return ( - <div - ref={containerRef} - className="w-full overflow-hidden text-sm font-mono truncate" - > - {text} - </div> - ); -}; - -export const ConsolidatedCirculationCard: React.FC< - ConsolidatedCirculationCardProps -> = ({ estimate, onMapClick, readonly, reduced, driver }) => { - const { t } = useTranslation(); - - const formatDistance = (meters: number) => { - if (meters > 1024) { - return `${(meters / 1000).toFixed(1)} km`; - } - return `${meters} ${t("estimates.meters", "m")}`; - }; - - const getTripIdDisplay = (tripId: string): string => { - const parts = tripId.split("_"); - return parts.length > 1 ? parts[1] : tripId; - }; - - const etaMinutes = - estimate.realTime?.minutes ?? estimate.schedule?.minutes ?? null; - - let etaValue: string; - let etaUnit: string; - - if (etaMinutes === null) { - etaValue = "--"; - etaUnit = t("estimates.minutes", "min"); - } else { - const isRenfe = driver === "renfe"; - const isLongWait = etaMinutes > 60; - - if (isRenfe || isLongWait) { - const now = new Date(); - const arrivalTime = new Date(now.getTime() + etaMinutes * 60 * 1000); - etaValue = arrivalTime.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); - etaUnit = ""; - } else { - etaValue = Math.max(0, Math.round(etaMinutes)).toString(); - etaUnit = t("estimates.minutes", "min"); - } - } - - const timeClass = useMemo(() => { - if (estimate.realTime && estimate.schedule?.running) { - return "time-running"; - } - if (estimate.realTime && !estimate.schedule) { - return "time-running"; - } - if (estimate.realTime && !estimate.schedule?.running) { - return "time-delayed"; - } - return "time-scheduled"; - }, [estimate.realTime, estimate.schedule]); - - const delayChip = useMemo(() => { - if (!estimate.schedule || !estimate.realTime) { - return null; - } - - const delta = Math.round( - estimate.realTime.minutes - estimate.schedule.minutes - ); - const absDelta = Math.abs(delta); - - // On time - if (delta === 0) { - return { - label: reduced ? "OK" : t("estimates.delay_on_time"), - tone: "delay-ok", - kind: "delay", - } as const; - } - - // Delayed - if (delta > 0) { - const tone = - delta <= 2 ? "delay-ok" : delta <= 10 ? "delay-warn" : "delay-critical"; - return { - label: reduced - ? `R${delta}` - : t("estimates.delay_positive", { - minutes: delta, - }), - tone, - kind: "delay", - } as const; - } - - // Early - const tone = absDelta <= 2 ? "delay-ok" : "delay-early"; - return { - label: reduced - ? `A${absDelta}` - : t("estimates.delay_negative", { - minutes: absDelta, - }), - tone, - kind: "delay", - } as const; - }, [estimate.schedule, estimate.realTime, t, reduced]); - - const metaChips = useMemo(() => { - const chips: Array<{ - label: string; - tone?: string; - kind?: "regular" | "gps" | "delay" | "warning"; - }> = []; - - if (delayChip) { - chips.push(delayChip); - } - - if (estimate.schedule && driver !== "renfe") { - chips.push({ - label: `${parseServiceId(estimate.schedule.serviceId)} · ${getTripIdDisplay( - estimate.schedule.tripId - )}`, - kind: "regular", - }); - } - - if (estimate.realTime && estimate.realTime.distance >= 0) { - chips.push({ - label: formatDistance(estimate.realTime.distance), - kind: "regular", - }); - } - - if (!reduced) { - if (estimate.currentPosition) { - if (estimate.isPreviousTrip) { - chips.push({ label: t("estimates.previous_trip"), kind: "gps" }); - } else { - chips.push({ label: t("estimates.bus_gps_position"), kind: "gps" }); - } - } - - if (driver !== "renfe") { - if (timeClass === "time-delayed") { - chips.push({ - label: reduced ? "!" : t("estimates.low_accuracy"), - tone: "warning", - kind: "warning", - }); - } - - if (timeClass === "time-scheduled") { - chips.push({ - label: reduced ? "⧗" : t("estimates.no_realtime"), - tone: "warning", - kind: "warning", - }); - } - } - } - - return chips; - }, [delayChip, estimate.schedule, estimate.realTime, timeClass, t, reduced]); - - // Check if bus has GPS position (live tracking) - const hasGpsPosition = !!estimate.currentPosition; - const isRenfe = driver === "renfe"; - const isClickable = hasGpsPosition; - const looksDisabled = !isClickable && !isRenfe; - - const Tag = readonly ? "div" : "button"; - const interactiveProps = readonly - ? {} - : { - onClick: isClickable ? onMapClick : undefined, - type: "button" as const, - disabled: !isClickable, - }; - - if (reduced) { - return ( - <Tag - className={` - flex-none flex items-center gap-2.5 min-h-12 - bg-(--message-background-color) border border-(--border-color) - rounded-xl px-3 py-2.5 transition-all - ${ - readonly - ? looksDisabled - ? "opacity-70 cursor-not-allowed" - : "" - : isClickable - ? "cursor-pointer hover:shadow-[0_4px_14px_rgba(0,0,0,0.08)] hover:border-(--button-background-color) hover:bg-[color-mix(in_oklab,var(--button-background-color)_5%,var(--message-background-color))] active:scale-[0.98]" - : looksDisabled - ? "opacity-70 cursor-not-allowed" - : "" - } - `.trim()} - {...interactiveProps} - > - <div className="shrink-0 min-w-[7ch]"> - <LineIcon line={estimate.line} mode="pill" /> - </div> - <div className="flex-1 min-w-0 flex flex-col gap-1"> - <strong className="text-base text-(--text-color) overflow-hidden text-ellipsis line-clamp-2 leading-tight"> - {driver === "renfe" && estimate.schedule?.tripId && ( - <span className="font-mono text-slate-500 mr-1.5 text-sm"> - {estimate.schedule.tripId} - </span> - )} - {driver === "renfe" ? estimate.route.toUpperCase() : estimate.route} - </strong> - {metaChips.length > 0 && ( - <div className="flex items-center gap-1.5 flex-wrap"> - {metaChips.map((chip, idx) => { - let chipColourClasses = ""; - switch (chip.tone) { - case "delay-ok": - chipColourClasses = - "bg-green-600/20 dark:bg-green-600/30 text-green-700 dark:text-green-300"; - break; - case "delay-warn": - chipColourClasses = - "bg-amber-600/20 dark:bg-yellow-600/30 text-amber-700 dark:text-yellow-300"; - break; - case "delay-critical": - chipColourClasses = - "bg-red-400/20 dark:bg-red-600/30 text-red-600 dark:text-red-300"; - break; - case "delay-early": - chipColourClasses = - "bg-blue-400/20 dark:bg-blue-600/30 text-blue-700 dark:text-blue-300"; - break; - case "warning": - chipColourClasses = - "bg-orange-400/20 dark:bg-orange-600/30 text-orange-700 dark:text-orange-300"; - break; - default: - chipColourClasses = - "bg-black/[0.06] dark:bg-white/[0.12] text-[var(--text-color)]"; - } - - return ( - <span - key={`${chip.label}-${idx}`} - className={`text-xs px-2 py-0.5 rounded-full flex items-center justify-center gap-1 shrink-0 ${chipColourClasses}`} - > - {chip.kind === "gps" && ( - <LocateIcon className="w-3 h-3 inline-block" /> - )} - {chip.kind === "warning" && ( - <AlertTriangle className="w-3 h-3 inline-block" /> - )} - {chip.label} - </span> - ); - })} - </div> - )} - </div> - <div - className={` - inline-flex items-center justify-center px-2 py-1.5 rounded-xl shrink-0 - ${ - timeClass === "time-running" - ? "bg-green-600/20 dark:bg-green-600/25 text-[#1a9e56] dark:text-[#22c55e]" - : timeClass === "time-delayed" - ? "bg-orange-600/20 dark:bg-orange-600/25 text-[#d06100] dark:text-[#fb923c]" - : "bg-blue-900/20 dark:bg-blue-600/25 text-[#0b3d91] dark:text-[#93c5fd]" - } - `.trim()} - > - <div className="flex flex-col items-center leading-none"> - <span className="text-lg font-bold leading-none">{etaValue}</span> - <span className="text-[0.65rem] uppercase tracking-wider mt-0.5 opacity-90"> - {etaUnit} - </span> - </div> - </div> - </Tag> - ); - } - - return ( - <Tag - className={`consolidated-circulation-card ${ - readonly - ? looksDisabled - ? "no-gps" - : "" - : isClickable - ? "has-gps" - : looksDisabled - ? "no-gps" - : "" - }`} - {...interactiveProps} - > - <> - <div className="card-row main"> - <div className="line-info"> - <LineIcon line={estimate.line} mode="pill" /> - </div> - <div className="route-info"> - <strong> - {driver === "renfe" && estimate.schedule?.tripId && ( - <span className="font-mono text-slate-500 mr-2 text-[0.9em]"> - {estimate.schedule.tripId} - </span> - )} - {driver === "renfe" - ? estimate.route.toUpperCase() - : estimate.route} - </strong> - {estimate.nextStreets && estimate.nextStreets.length > 0 && ( - <AutoMarquee text={estimate.nextStreets.join(" — ")} /> - )} - </div> - <div className={`eta-badge ${timeClass}`}> - <div className="eta-text"> - <span className="eta-value">{etaValue}</span> - <span className="eta-unit">{etaUnit}</span> - </div> - </div> - </div> - - {metaChips.length > 0 && ( - <div className="card-row meta"> - {metaChips.map((chip, idx) => ( - <span - key={`${chip.label}-${idx}`} - className={`meta-chip ${chip.tone ?? ""}`.trim()} - > - {chip.kind === "gps" && ( - <LocateIcon className="w-3 h-3 inline-block" /> - )} - {chip.kind === "warning" && ( - <AlertTriangle className="w-3 h-3 inline-block" /> - )} - {chip.label} - </span> - ))} - </div> - )} - </> - </Tag> - ); -}; diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css deleted file mode 100644 index 044b4a3..0000000 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css +++ /dev/null @@ -1,13 +0,0 @@ -.consolidated-circulation-caption { - font-size: 0.9rem; - color: var(--subtitle-color); - text-align: center; - padding: 0.5rem; -} - -.consolidated-circulation-no-data { - text-align: center; - padding: 2rem 1rem; - color: var(--subtitle-color); - font-size: 0.95rem; -} diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx deleted file mode 100644 index eea4582..0000000 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { type ConsolidatedCirculation } from "~routes/stops-$id"; -import { ConsolidatedCirculationCard } from "./ConsolidatedCirculationCard"; - -import { useCallback } from "react"; -import "./ConsolidatedCirculationList.css"; - -interface ConsolidatedCirculationListProps { - data: ConsolidatedCirculation[]; - onCirculationClick?: ( - estimate: ConsolidatedCirculation, - index: number - ) => void; - reduced?: boolean; - driver?: string; -} - -export const ConsolidatedCirculationList: React.FC< - ConsolidatedCirculationListProps -> = ({ data, onCirculationClick, reduced, driver }) => { - const { t } = useTranslation(); - - const sortedData = [...data].sort( - (a, b) => - (a.realTime?.minutes ?? a.schedule?.minutes ?? 999) - - (b.realTime?.minutes ?? b.schedule?.minutes ?? 999) - ); - - const generateKey = useCallback((estimate: ConsolidatedCirculation) => { - if (estimate.realTime && estimate.schedule) { - return `rt-${estimate.schedule.tripId}`; - } - - return `sch-${estimate.schedule ? estimate.schedule.tripId : estimate.line + "-" + estimate.route}`; - }, []); - - return ( - <> - {sortedData.length === 0 ? ( - <div className="consolidated-circulation-no-data"> - {t("estimates.none", "No hay estimaciones disponibles")} - </div> - ) : ( - <div className="flex flex-col gap-3"> - {sortedData.map((estimate, idx) => ( - <ConsolidatedCirculationCard - reduced={reduced} - driver={driver} - key={generateKey(estimate)} - estimate={estimate} - onMapClick={() => onCirculationClick?.(estimate, idx)} - /> - ))} - </div> - )} - </> - ); -}; diff --git a/src/frontend/app/components/ThemeColorManager.tsx b/src/frontend/app/components/ThemeColorManager.tsx index eba0471..30a5f7a 100644 --- a/src/frontend/app/components/ThemeColorManager.tsx +++ b/src/frontend/app/components/ThemeColorManager.tsx @@ -5,7 +5,7 @@ export const ThemeColorManager = () => { const { resolvedTheme } = useSettings(); useEffect(() => { - const color = resolvedTheme === "dark" ? "#121212" : "#ffffff"; + const color = resolvedTheme === "dark" ? "#1A1B26" : "#F7F7FF"; let meta = document.querySelector('meta[name="theme-color"]'); if (!meta) { diff --git a/src/frontend/app/components/arrivals/ArrivalCard.css b/src/frontend/app/components/arrivals/ArrivalCard.css index 5835352..0e5af25 100644 --- a/src/frontend/app/components/arrivals/ArrivalCard.css +++ b/src/frontend/app/components/arrivals/ArrivalCard.css @@ -13,5 +13,5 @@ } .time-scheduled { - @apply bg-blue-900/20 dark:bg-blue-600/25 text-[#0b3d91] dark:text-[#93c5fd]; + @apply bg-primary/20 dark:bg-primary/25 text-primary dark:text-primary; } diff --git a/src/frontend/app/components/layout/NavBar.module.css b/src/frontend/app/components/layout/NavBar.module.css index 19d0a93..32827c6 100644 --- a/src/frontend/app/components/layout/NavBar.module.css +++ b/src/frontend/app/components/layout/NavBar.module.css @@ -28,6 +28,16 @@ color: var(--text-color); padding: 0.25rem 0; border-radius: 0.5rem; + gap: 4px; +} + +.iconWrapper { + display: flex; + align-items: center; + justify-content: center; + padding: 4px 20px; + border-radius: 20px; + transition: background-color 0.2s ease; } .link svg { @@ -38,11 +48,27 @@ } .link span { - font-size: 13px; + font-size: 11px; line-height: 1; - font-family: system-ui; + font-family: var(--font-ui); } .active { color: var(--button-background-color); } + +.active .iconWrapper { + background-color: color-mix( + in oklab, + var(--button-background-color) 15%, + transparent + ); +} + +[data-theme="dark"] .active .iconWrapper { + background-color: color-mix( + in oklab, + var(--button-background-color) 20%, + transparent + ); +} diff --git a/src/frontend/app/components/layout/NavBar.tsx b/src/frontend/app/components/layout/NavBar.tsx index 9c42987..58228c7 100644 --- a/src/frontend/app/components/layout/NavBar.tsx +++ b/src/frontend/app/components/layout/NavBar.tsx @@ -112,7 +112,9 @@ export default function NavBar({ orientation = "horizontal" }: NavBarProps) { title={item.name} aria-label={item.name} > - <Icon size={24} /> + <div className={styles.iconWrapper}> + <Icon size={24} /> + </div> <span>{item.name}</span> </Link> ); diff --git a/src/frontend/app/components/map/StopSummarySheet.css b/src/frontend/app/components/map/StopSummarySheet.css index 5869d41..e39ac07 100644 --- a/src/frontend/app/components/map/StopSummarySheet.css +++ b/src/frontend/app/components/map/StopSummarySheet.css @@ -1,3 +1,5 @@ +@import "../../tailwind.css"; + /* Stop Sheet Styles */ .react-modal-sheet-container { background-color: var(--background-color) !important; @@ -29,6 +31,7 @@ } .stop-sheet-title { + font-family: var(--font-display); font-size: 1.5rem; font-weight: 600; color: var(--text-color); @@ -36,6 +39,7 @@ } .stop-sheet-id { + font-family: var(--font-display); font-size: 1rem; color: var(--subtitle-color); } @@ -85,6 +89,7 @@ } .stop-sheet-subtitle { + font-family: var(--font-display); font-size: 1.1rem; font-weight: 500; color: var(--text-color); @@ -92,6 +97,7 @@ } .stop-sheet-no-estimates { + font-family: var(--font-display); text-align: center; padding: 32px 16px; color: var(--subtitle-color); @@ -183,6 +189,7 @@ } .stop-sheet-timestamp { + font-family: var(--font-ui); font-size: 0.8rem; color: var(--subtitle-color); text-align: center; @@ -194,35 +201,19 @@ } .stop-sheet-reload { - display: inline-flex; - align-items: center; - gap: 0.4rem; - background: transparent; - border: 1px solid var(--border-color); - color: var(--text-color); - padding: 0.5rem 0.75rem; - border-radius: 6px; - font-size: 0.85rem; - cursor: pointer; - transition: all 0.2s ease; - flex: 1; - justify-content: center; + @apply inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl border border-border bg-transparent text-text text-sm font-sans cursor-pointer transition-all flex-1; } .stop-sheet-reload:hover:not(:disabled) { - background: var(--message-background-color); - border-color: var(--button-background-color); + @apply bg-surface border-primary; } .stop-sheet-reload:disabled { - opacity: 0.6; - cursor: not-allowed; + @apply opacity-60 cursor-not-allowed; } .reload-icon { - width: 14px; - height: 14px; - transition: transform 0.5s ease; + @apply w-4 h-4 transition-transform duration-500; } .reload-icon.spinning { @@ -239,22 +230,11 @@ } .stop-sheet-view-all { - display: block; - padding: 0.5rem 0.75rem; - background-color: var(--button-background-color); - color: white; - text-decoration: none; - text-align: center; - border-radius: 6px; - font-weight: 500; - font-size: 0.85rem; - transition: background-color 0.2s ease; - flex: 2; + @apply block px-4 py-2.5 bg-primary text-white no-underline text-center rounded-xl font-semibold text-sm font-sans transition-colors flex-[1.5]; } .stop-sheet-view-all:hover { - background-color: var(--button-hover-background-color); - text-decoration: none; + @apply bg-primary/90 no-underline; } /* Error display adjustments for sheet */ diff --git a/src/frontend/app/components/map/StopSummarySheet.tsx b/src/frontend/app/components/map/StopSummarySheet.tsx index 56e80a4..7024f41 100644 --- a/src/frontend/app/components/map/StopSummarySheet.tsx +++ b/src/frontend/app/components/map/StopSummarySheet.tsx @@ -26,7 +26,7 @@ export interface StopSheetProps { }; } -export const StopSheet: React.FC<StopSheetProps> = ({ +export const StopSummarySheet: React.FC<StopSheetProps> = ({ isOpen, onClose, stop, diff --git a/src/frontend/app/root.css b/src/frontend/app/root.css index c6d9058..8ac6bf1 100644 --- a/src/frontend/app/root.css +++ b/src/frontend/app/root.css @@ -1,35 +1,36 @@ :root { --colour-scheme: light; - --background-color: #ffffff; - --text-color: #333333; - --subtitle-color: #444444; - --border-color: #eeeeee; - --button-background-color: #007bff; - --button-hover-background-color: #0069d9; + --background-color: #f7f7ff; + --text-color: #1a1b26; + --subtitle-color: #4a4b56; + --border-color: #e1e1ef; + --button-background-color: #27187e; + --primary-color: var(--button-background-color); + --button-hover-background-color: #1e1263; --button-disabled-background-color: #cccccc; --star-color: #ffcc00; - --message-background-color: #f8f9fa; + --message-background-color: #ffffff; /* Skeletons */ - --skeleton-base: #f0f0f0; - --skeleton-highlight: #e0e0e0; + --skeleton-base: #eef0f7; + --skeleton-highlight: #e1e4f0; /* Timetable component variables */ --text-primary: var(--text-color); - --text-secondary: #666666; + --text-secondary: #6a6b76; --surface-future: var(--background-color); - --surface-next: #eef6ff; /* slightly accented surface for next card */ - --surface-past: hsl(0 0% 90% / 1); - --accent-next: #1e88e5; /* accent color for next card left border */ + --surface-next: #eef0ff; /* slightly accented surface for next card */ + --surface-past: hsl(240 20% 90% / 1); + --accent-next: #27187e; /* accent color for next card left border */ --card-border: var(--border-color); - --card-background: #f8f9fa; - --service-background: #f0f0f0; - --service-background-past: #e8e8e8; + --card-background: #ffffff; + --service-background: #f0f1f9; + --service-background-past: #e8e9f0; /* Alert color variables */ - --alert-info-bg: rgba(59, 130, 246, 0.1); - --alert-info-border: rgba(59, 130, 246, 0.5); - --alert-info-text: #1e40af; + --alert-info-bg: rgba(39, 24, 126, 0.05); + --alert-info-border: rgba(39, 24, 126, 0.2); + --alert-info-text: #27187e; --alert-warning-bg: rgba(255, 152, 0, 0.1); --alert-warning-border: rgba(255, 152, 0, 0.5); @@ -41,46 +42,49 @@ /* Error display colors */ --error-icon-color: #e74c3c; - --error-title-color: #2c3e50; - --error-message-color: #7f8c8d; + --error-title-color: #1a1b26; + --error-message-color: #4a4b56; color-scheme: light; - font-family: - ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", - "Segoe UI Symbol", "Noto Color Emoji"; + --font-display: "Outfit Variable", ui-sans-serif, system-ui, sans-serif; + --font-ui: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + + font-family: var(--font-ui); } [data-theme="dark"] { --colour-scheme: dark; - --background-color: #121212; - --text-color: #ffffff; - --subtitle-color: #bbbbbb; - --border-color: #444444; - --button-background-color: #1f93f2; - --button-hover-background-color: #1872d9; - --button-disabled-background-color: #555555; + --background-color: #1a1b26; + --text-color: #f7f7ff; + --subtitle-color: #a1a1b0; + --border-color: #2d2e3d; + --button-background-color: #8b7fff; + --primary-color: var(--button-background-color); + --button-hover-background-color: #7a6eff; + --button-disabled-background-color: #444444; --star-color: #ffcc00; - --message-background-color: #333333; + --message-background-color: #242533; /* Skeletons (dark) */ - --skeleton-base: #2a2a2a; - --skeleton-highlight: #3a3a3a; + --skeleton-base: #2d2e3d; + --skeleton-highlight: #3d3e4d; /* Timetable component dark overrides */ --text-primary: var(--text-color); - --text-secondary: #bbbbbb; - --surface-future: #1e1e1e; - --surface-next: #17212b; - --surface-past: #1a1a1a; - --accent-next: #64b5f6; + --text-secondary: #a1a1b0; + --surface-future: #242533; + --surface-next: #1e1f2e; + --surface-past: #1c1d29; + --accent-next: #8b7fff; --card-border: var(--border-color); - --card-background: #1e1e1e; - --service-background: #222222; - --service-background-past: #1f1f1f; + --card-background: #242533; + --service-background: #2a2b3a; + --service-background-past: #222331; /* Alert color variables (dark) */ - --alert-info-bg: rgba(59, 130, 246, 0.15); - --alert-info-border: rgba(59, 130, 246, 0.4); + --alert-info-bg: rgba(77, 59, 255, 0.15); + --alert-info-border: rgba(77, 59, 255, 0.4); --alert-info-text: #93c5fd; --alert-warning-bg: rgba(255, 152, 0, 0.15); @@ -93,12 +97,21 @@ /* Error display colors (dark) */ --error-icon-color: #e74c3c; - --error-title-color: #e0e0e0; - --error-message-color: #b0b0b0; + --error-title-color: #f7f7ff; + --error-message-color: #a1a1b0; color-scheme: dark; } +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-display); +} + body { color-scheme: var(--colour-scheme, light); @@ -118,6 +131,13 @@ body { overscroll-behavior-y: contain; } +button, +input, +select, +textarea { + font-family: var(--font-ui); +} + .main-content { flex: 1; overflow: auto; @@ -147,6 +167,7 @@ body { } .page-title { + font-family: var(--font-display); font-size: 1.25rem; font-weight: 600; color: var(--text-color); @@ -154,6 +175,7 @@ body { } .page-subtitle { + font-family: var(--font-display); font-size: 1rem; font-weight: 500; color: var(--subtitle-color); @@ -161,6 +183,7 @@ body { } .message { + font-family: var(--font-display); background-color: var(--message-background-color); padding: 1rem; border-radius: 0.5rem; diff --git a/src/frontend/app/root.tsx b/src/frontend/app/root.tsx index 1354660..656c75c 100644 --- a/src/frontend/app/root.tsx +++ b/src/frontend/app/root.tsx @@ -6,7 +6,7 @@ import { ScrollRestoration, } from "react-router"; -import "@fontsource-variable/roboto"; +import "@fontsource-variable/outfit"; import type { Route } from "./+types/root"; import "./root.css"; @@ -39,7 +39,7 @@ export function Layout({ children }: { children: React.ReactNode }) { <link rel="icon" type="image/jpg" href="/logo-512.jpg" /> <link rel="icon" href="/favicon.ico" sizes="64x64" /> <link rel="apple-touch-icon" href="/logo-512.jpg" sizes="512x512" /> - <meta name="theme-color" content="#ffffff" /> + <meta name="theme-color" content="#F7F7FF" /> <link rel="canonical" href="https://busurbano.costas.dev/" /> <meta diff --git a/src/frontend/app/routes/favourites.tsx b/src/frontend/app/routes/favourites.tsx index ff229b2..deb3629 100644 --- a/src/frontend/app/routes/favourites.tsx +++ b/src/frontend/app/routes/favourites.tsx @@ -183,19 +183,19 @@ function SpecialPlaceCard({ setLabel, }: SpecialPlaceCardProps) { return ( - <div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4"> + <div className="bg-surface border border-border rounded-lg p-4"> <div className="flex items-start justify-between gap-3"> <div className="flex items-start gap-3 flex-1 min-w-0"> <span className="text-2xl" aria-hidden="true"> {icon} </span> <div className="flex-1 min-w-0"> - <h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-1"> + <h3 className="font-semibold text-text mb-1"> {label} </h3> {place ? ( - <div className="text-sm text-gray-600 dark:text-gray-400"> - <p className="font-medium text-gray-900 dark:text-gray-100"> + <div className="text-sm text-muted"> + <p className="font-medium text-text"> {place.name} </p> {place.type === "stop" && place.stopId && ( @@ -272,21 +272,21 @@ function FavouriteStopItem({ }; return ( - <li className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg"> + <li className="bg-surface border border-border rounded-lg"> <div className="flex items-stretch justify-between gap-2"> <Link to={`/stops/${stop.stopId}`} - className="flex-1 min-w-0 p-3 no-underline hover:bg-gray-50 dark:hover:bg-gray-800/80 rounded-l-lg transition-colors" + className="flex-1 min-w-0 p-3 no-underline hover:bg-surface/80 rounded-l-lg transition-colors" > <div className="flex items-center gap-2 mb-1"> <span className="text-yellow-500 text-base" aria-label="Favourite"> ★ </span> - <span className="text-xs text-gray-600 dark:text-gray-400 font-medium"> + <span className="text-xs text-muted font-medium"> ({stop.stopId}) </span> </div> - <div className="font-semibold text-gray-900 dark:text-gray-100 mb-2"> + <div className="font-semibold text-text mb-2"> {StopDataProvider.getDisplayName(stop)} </div> <div className="flex flex-wrap gap-1 items-center"> diff --git a/src/frontend/app/routes/home.tsx b/src/frontend/app/routes/home.tsx index 36565bd..a20ba64 100644 --- a/src/frontend/app/routes/home.tsx +++ b/src/frontend/app/routes/home.tsx @@ -241,7 +241,7 @@ export default function StopList() { <div className="flex flex-col gap-4 py-4 pb-8"> {/* Search Section */} <div className="w-full px-4"> - <h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-gray-100"> + <h3 className="text-lg font-semibold mb-2 text-text"> {t("stoplist.search_label", "Buscar paradas")} </h3> <input @@ -250,11 +250,11 @@ export default function StopList() { onChange={handleStopSearch} className=" w-full px-4 py-3 text-base - border border-gray-300 dark:border-gray-700 rounded-xl - bg-white dark:bg-gray-800 - text-gray-900 dark:text-gray-100 - placeholder:text-gray-500 dark:placeholder:text-gray-400 placeholder:opacity-80 - focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent + border border-border rounded-xl + bg-surface + text-text + placeholder:text-muted placeholder:opacity-80 + focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-all duration-200 " /> @@ -263,7 +263,7 @@ export default function StopList() { {/* Search Results */} {searchResults && searchResults.length > 0 ? ( <div className="w-full px-4 flex flex-col gap-2"> - <h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100"> + <h2 className="text-lg font-semibold text-text"> {t("stoplist.search_results", "Resultados de la búsqueda")} </h2> <ul className="list-none p-0 m-0 flex flex-col gap-2 md:grid md:grid-cols-[repeat(auto-fill,minmax(300px,1fr))] lg:grid-cols-[repeat(auto-fill,minmax(320px,1fr))]"> diff --git a/src/frontend/app/routes/lines.tsx b/src/frontend/app/routes/lines.tsx index acf8a7f..900c543 100644 --- a/src/frontend/app/routes/lines.tsx +++ b/src/frontend/app/routes/lines.tsx @@ -24,11 +24,11 @@ export default function LinesPage() { href={line.scheduleUrl} target="_blank" rel="noopener noreferrer" - className="flex items-center gap-3 p-4 bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-lg transition-shadow border border-gray-200 dark:border-gray-700" + className="flex items-center gap-3 p-4 bg-surface rounded-lg shadow hover:shadow-lg transition-shadow border border-border" > <LineIcon line={line.lineNumber} mode="rounded" /> <div className="flex-1 min-w-0"> - <p className="text-sm md:text-md font-semibold text-gray-900 dark:text-gray-100"> + <p className="text-sm md:text-md font-semibold text-text"> {line.routeName} </p> </div> diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx index 1ce9942..517549b 100644 --- a/src/frontend/app/routes/map.tsx +++ b/src/frontend/app/routes/map.tsx @@ -16,7 +16,7 @@ import Map, { import { useNavigate } from "react-router"; import { PlannerOverlay } from "~/components/PlannerOverlay"; import { - StopSheet, + StopSummarySheet, type StopSheetProps, } from "~/components/map/StopSummarySheet"; import { APP_CONSTANTS } from "~/config/constants"; @@ -278,7 +278,7 @@ export default function StopMap() { /> {selectedStop && ( - <StopSheet + <StopSummarySheet isOpen={isSheetOpen} onClose={() => setIsSheetOpen(false)} stop={selectedStop} diff --git a/src/frontend/app/routes/planner.tsx b/src/frontend/app/routes/planner.tsx index 9e44425..3d0f703 100644 --- a/src/frontend/app/routes/planner.tsx +++ b/src/frontend/app/routes/planner.tsx @@ -125,14 +125,14 @@ const ItinerarySummary = ({ return ( <div - className="bg-white dark:bg-slate-800 p-4 rounded-lg shadow mb-3 cursor-pointer hover:bg-gray-50 dark:hover:bg-slate-700 border border-gray-200 dark:border-slate-700" + className="bg-surface p-4 rounded-lg shadow mb-3 cursor-pointer hover:bg-surface/80 border border-border" onClick={onClick} > <div className="flex justify-between items-center mb-2"> - <div className="font-bold text-lg text-slate-900 dark:text-slate-100"> + <div className="font-bold text-lg text-text"> {startTime} - {endTime} </div> - <div className="text-gray-600 dark:text-gray-400"> + <div className="text-muted"> {durationMinutes} min </div> </div> @@ -155,10 +155,10 @@ const ItinerarySummary = ({ return ( <React.Fragment key={idx}> - {idx > 0 && <span className="text-slate-400">›</span>} + {idx > 0 && <span className="text-muted/50">›</span>} {isWalk ? ( - <div className="flex items-center gap-2 rounded-full bg-slate-100 px-3 py-1.5 text-sm text-slate-800 whitespace-nowrap"> - <Footprints className="w-4 h-4 text-slate-600" /> + <div className="flex items-center gap-2 rounded-full bg-surface px-3 py-1.5 text-sm text-text whitespace-nowrap border border-border"> + <Footprints className="w-4 h-4 text-muted" /> <span className="font-semibold"> {legDurationMinutes} {t("estimates.minutes")} </span> @@ -176,21 +176,21 @@ const ItinerarySummary = ({ })} </div> - <div className="flex items-center justify-between text-sm text-slate-600 dark:text-slate-400 mt-1"> + <div className="flex items-center justify-between text-sm text-muted mt-1"> <span> {t("planner.walk")}: {formatDistance(walkTotals.meters)} {walkTotals.minutes - ? ` • ${walkTotals.minutes} ${t("estimates.minutes")}` + ? ` • ${walkTotals.minutes} {t("estimates.minutes")}` : ""} </span> <span className="flex items-center gap-3"> - <span className="flex items-center gap-1 font-semibold text-slate-700 dark:text-slate-300"> + <span className="flex items-center gap-1 font-semibold text-text"> <Coins className="w-4 h-4" /> {cashFare === "0.00" ? t("planner.free") : t("planner.fare", { amount: cashFare })} </span> - <span className="flex items-center gap-1 text-slate-600 dark:text-slate-400"> + <span className="flex items-center gap-1 text-muted"> <CreditCard className="w-4 h-4" /> {cardFare === "0.00" ? t("planner.free") diff --git a/src/frontend/app/routes/settings.tsx b/src/frontend/app/routes/settings.tsx index 56df777..c615844 100644 --- a/src/frontend/app/routes/settings.tsx +++ b/src/frontend/app/routes/settings.tsx @@ -31,7 +31,7 @@ export default function Settings() { <div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> {/* Theme Selection */} <section className="mb-8"> - <h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100"> + <h2 className="text-xl font-semibold mb-4 text-text"> {t("about.theme", "Tema")} </h2> <div className="grid grid-cols-3 gap-3 sm:gap-4"> @@ -42,12 +42,12 @@ export default function Settings() { className={` p-4 sm:p-6 flex flex-col items-center justify-center gap-2 rounded-lg border-2 transition-all duration-200 - hover:bg-gray-50 dark:hover:bg-gray-800 - focus:outline-none focus:ring focus:ring-blue-500 dark:focus:ring-offset-gray-900 + hover:bg-surface/50 + focus:outline-none focus:ring focus:ring-primary/50 ${ value === theme - ? "border-blue-600 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 font-semibold" - : "border-gray-300 dark:border-gray-700 text-gray-700 dark:text-gray-300" + ? "border-primary bg-primary/10 text-primary font-semibold" + : "border-border text-muted" } `} > @@ -62,17 +62,17 @@ export default function Settings() { <section className="mb-8"> <label htmlFor="mapPositionMode" - className="block text-lg font-medium text-gray-900 dark:text-gray-100 mb-3" + className="block text-lg font-medium text-text mb-3" > {t("about.map_position_mode")} </label> <select id="mapPositionMode" className=" - w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 - bg-white dark:bg-gray-800 - text-gray-900 dark:text-gray-100 - focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent + w-full px-4 py-3 rounded-lg border border-border + bg-surface + text-text + focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-colors duration-200 " value={mapPositionMode} @@ -87,17 +87,17 @@ export default function Settings() { <section> <label htmlFor="language" - className="block text-lg font-medium text-gray-900 dark:text-gray-100 mb-3" + className="block text-lg font-medium text-text mb-3" > {t("about.language", "Idioma")} </label> <select id="language" className=" - w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-700 - bg-white dark:bg-gray-800 - text-gray-900 dark:text-gray-100 - focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent + w-full px-4 py-3 rounded-lg border border-border + bg-surface + text-text + focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-transparent transition-colors duration-200 " value={i18n.language} diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx index 7adcef2..9147302 100644 --- a/src/frontend/app/routes/stops-$id.tsx +++ b/src/frontend/app/routes/stops-$id.tsx @@ -181,13 +181,13 @@ export default function Estimates() { className={`cursor-pointer transition-colors ${ favourited ? "fill-[var(--star-color)] text-[var(--star-color)]" - : "text-slate-500" + : "text-muted" }`} onClick={toggleFavourite} /> <CircleHelp - className="text-slate-500 cursor-pointer" + className="text-muted cursor-pointer" onClick={() => setIsHelpModalOpen(true)} /> </div> @@ -205,12 +205,12 @@ export default function Estimates() { <div> {isReducedView ? ( <EyeClosed - className="text-slate-500" + className="text-muted" onClick={() => setIsReducedView(false)} /> ) : ( <Eye - className="text-slate-500" + className="text-muted" onClick={() => setIsReducedView(true)} /> )} diff --git a/src/frontend/app/tailwind.css b/src/frontend/app/tailwind.css index de604f7..7438ac7 100644 --- a/src/frontend/app/tailwind.css +++ b/src/frontend/app/tailwind.css @@ -3,4 +3,32 @@ @import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/utilities.css" layer(utilities); +@theme { + --color-primary: var(--button-background-color); + --color-background: var(--background-color); + --color-text: var(--text-color); + --color-subtitle: var(--subtitle-color); + --color-border: var(--border-color); + --color-surface: var(--message-background-color); + + --font-display: var(--font-display); + --font-sans: var(--font-ui); + + /* Semantic colors for easier migration from slate/gray */ + --color-muted: var(--subtitle-color); + --color-accent: var(--button-background-color); + + /* Generated-like palette using color-mix for flexibility */ + --color-primary-50: color-mix(in oklab, var(--button-background-color) 5%, white); + --color-primary-100: color-mix(in oklab, var(--button-background-color) 10%, white); + --color-primary-200: color-mix(in oklab, var(--button-background-color) 20%, white); + --color-primary-300: color-mix(in oklab, var(--button-background-color) 40%, white); + --color-primary-400: color-mix(in oklab, var(--button-background-color) 60%, white); + --color-primary-500: var(--button-background-color); + --color-primary-600: color-mix(in oklab, var(--button-background-color) 80%, black); + --color-primary-700: color-mix(in oklab, var(--button-background-color) 60%, black); + --color-primary-800: color-mix(in oklab, var(--button-background-color) 40%, black); + --color-primary-900: color-mix(in oklab, var(--button-background-color) 20%, black); +} + @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 6c8284b..3413c58 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@fontsource-variable/outfit": "^5.2.8", "@fontsource-variable/roboto": "^5.2.8", "@react-router/node": "^7.9.6", "@react-router/serve": "^7.9.6", @@ -1105,6 +1106,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fontsource-variable/outfit": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/outfit/-/outfit-5.2.8.tgz", + "integrity": "sha512-4oUDCZx/Tcz6HZP423w/niqEH31Gks5IsqHV2ZZz1qKHaVIZdj2f0/S1IK2n8jl6Xo0o3N+3RjNHlV9R73ozQA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@fontsource-variable/roboto": { "version": "5.2.8", "resolved": "https://registry.npmjs.org/@fontsource-variable/roboto/-/roboto-5.2.8.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index bd55dff..4aaf83f 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -14,6 +14,7 @@ "checkformat": "prettier --check ." }, "dependencies": { + "@fontsource-variable/outfit": "^5.2.8", "@fontsource-variable/roboto": "^5.2.8", "@react-router/node": "^7.9.6", "@react-router/serve": "^7.9.6", diff --git a/src/frontend/public/manifest.webmanifest b/src/frontend/public/manifest.webmanifest index afdab3c..e849bd1 100644 --- a/src/frontend/public/manifest.webmanifest +++ b/src/frontend/public/manifest.webmanifest @@ -8,8 +8,8 @@ "display": "standalone", "orientation": "portrait-primary", "lang": "es", - "background_color": "#ffffff", - "theme_color": "#007bff", + "background_color": "#F7F7FF", + "theme_color": "#27187E", "categories": ["productivity"], "prefer_related_applications": false, "icons": [ |
