Refactor monorepo structure and replace hardcoded colors

Add TypeScript project references, update ESLint configs to support
ESM modules, and introduce brand semantic color tokens across the web
app. Add comprehensive documentation for architecture, API reference,
development setup, and user guide.
This commit is contained in:
2026-05-12 21:22:40 +02:00
parent 98e74ee48d
commit 3c6df95a86
20 changed files with 577 additions and 76 deletions
+5
View File
@@ -1,6 +1,10 @@
import js from "@eslint/js";
import ts from "typescript-eslint";
import reactPlugin from "eslint-plugin-react";
import { fileURLToPath } from "url";
import path from "path";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// We have no .eslintrc, so we must define everything here.
@@ -40,6 +44,7 @@ export default ts.config(
ecmaFeatures: {
jsx: true,
},
tsconfigRootDir: path.resolve(__dirname),
},
},
plugins: {
+9
View File
@@ -1,6 +1,10 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
import { fileURLToPath } from "url";
import path from "path";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const eslintConfig = defineConfig([
...nextVitals,
@@ -14,6 +18,11 @@ const eslintConfig = defineConfig([
"next-env.d.ts",
]),
{
languageOptions: {
parserOptions: {
tsconfigRootDir: path.resolve(__dirname),
},
},
rules: {
// Allow _-prefixed parameters and variables to signal intentionally unused.
"@typescript-eslint/no-unused-vars": [
+2 -2
View File
@@ -15,9 +15,9 @@ export default function CalendarPage() {
return (
<div className="mx-auto max-w-6xl p-4 sm:p-6 lg:p-8">
<div className="mb-6 rounded-2xl border border-white/10 bg-[#17112A]/55 p-5 shadow-[0_22px_70px_rgba(0,0,0,0.24)] backdrop-blur-xl">
<p className="mb-2 text-xs font-semibold uppercase tracking-[0.28em] text-[#D946EF]">Calendar sync</p>
<p className="mb-2 text-xs font-semibold uppercase tracking-[0.28em] text-brand-fuchsia">Calendar sync</p>
<h1 className="text-3xl font-extrabold text-white sm:text-4xl">Import, inspect, and time your day.</h1>
<p className="mt-2 text-[#F4F1EA]/66">View and manage every appointment from a single departure-focused calendar.</p>
<p className="mt-2 text-brand-light/66">View and manage every appointment from a single departure-focused calendar.</p>
</div>
<div className="mb-6">
+8 -8
View File
@@ -32,7 +32,7 @@ const BikeSection: React.FC<BikeSectionProps> = ({
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold text-white">Bicycle Route</h3>
<p className="text-sm text-[#F4F1EA]/60">Door-to-door route to the event</p>
<p className="text-sm text-brand-light/60">Door-to-door route to the event</p>
</div>
{onRefresh && (
<Button variant="secondary" size="sm" onClick={onRefresh}>
@@ -47,29 +47,29 @@ const BikeSection: React.FC<BikeSectionProps> = ({
<LoadingSpinner size="md" />
</div>
) : bikeError ? (
<div className="text-center py-4 text-[#FF2D8D]">Error: {bikeError}</div>
<div className="text-center py-4 text-brand-pink">Error: {bikeError}</div>
) : bikeRoute ? (
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-[#F4F1EA]/66">Distance</span>
<span className="text-brand-light/66">Distance</span>
<span className="font-medium text-white">
{Math.round(bikeRoute.distance / 1000)} km ({Math.round(bikeRoute.distance)} m)
</span>
</div>
<div className="flex justify-between">
<span className="text-[#F4F1EA]/66">Duration</span>
<span className="text-brand-light/66">Duration</span>
<span className="font-medium text-white">
{Math.floor(bikeRoute.duration / 60)} min {bikeRoute.duration % 60} s
</span>
</div>
{bikeRoute.steps && bikeRoute.steps.length > 0 && (
<div className="mt-4">
<h4 className="mb-2 font-medium text-[#F4F1EA]">Steps</h4>
<h4 className="mb-2 font-medium text-brand-light">Steps</h4>
<ul className="space-y-2">
{bikeRoute.steps.map((step, index) => (
<li key={index} className="text-sm">
<span className="font-medium text-[#D946EF]">{step.name}</span>
<span className="ml-2 text-[#F4F1EA]/70">{step.instruction}</span>
<span className="font-medium text-brand-fuchsia">{step.name}</span>
<span className="ml-2 text-brand-light/70">{step.instruction}</span>
</li>
))}
</ul>
@@ -77,7 +77,7 @@ const BikeSection: React.FC<BikeSectionProps> = ({
)}
</div>
) : (
<div className="py-4 text-center text-sm text-[#F4F1EA]/58">
<div className="py-4 text-center text-sm text-brand-light/58">
Add origin and destination coordinates to calculate a bike route.
</div>
)}
+12 -12
View File
@@ -86,7 +86,7 @@ export default function EventCard({ event, originStation }: EventCardProps) {
<div className="brand-panel overflow-hidden rounded-2xl p-4 sm:p-5">
<div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<p className="mb-1 text-xs font-semibold uppercase tracking-[0.24em] text-[#D946EF]">Next stop</p>
<p className="mb-1 text-xs font-semibold uppercase tracking-[0.24em] text-brand-fuchsia">Next stop</p>
<h3 className="text-2xl font-bold text-white">{event.title}</h3>
</div>
<CountdownBadge countdown={countdown} status={status} />
@@ -94,12 +94,12 @@ export default function EventCard({ event, originStation }: EventCardProps) {
<div className="mb-5 grid gap-3 sm:grid-cols-2">
<div className="brand-panel-soft rounded-xl p-3">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[#F4F1EA]/42">Destination</p>
<p className="mt-1 text-sm font-medium text-[#F4F1EA]">{event.destination}</p>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-brand-light/42">Destination</p>
<p className="mt-1 text-sm font-medium text-brand-light">{event.destination}</p>
</div>
<div className="brand-panel-soft rounded-xl p-3">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[#F4F1EA]/42">Appointment</p>
<p className="mt-1 text-sm font-medium text-[#F4F1EA]">{format(event.eventTime, "EEE dd MMM yyyy HH:mm")}</p>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-brand-light/42">Appointment</p>
<p className="mt-1 text-sm font-medium text-brand-light">{format(event.eventTime, "EEE dd MMM yyyy HH:mm")}</p>
</div>
</div>
@@ -109,8 +109,8 @@ export default function EventCard({ event, originStation }: EventCardProps) {
key={option.id}
className={`rounded-xl border p-3 text-left transition-all ${
activeMode === option.id
? "border-[#D946EF]/70 bg-[#B23CFF]/18 shadow-[0_14px_34px_rgba(178,60,255,0.2)]"
: "border-white/10 bg-white/[0.045] hover:border-[#D946EF]/40 hover:bg-white/[0.07]"
? "border-brand-fuchsia/70 bg-brand-purple/18 shadow-[0_14px_34px_rgba(178,60,255,0.2)]"
: "border-white/10 bg-white/[0.045] hover:border-brand-fuchsia/40 hover:bg-white/[0.07]"
} ${option.disabled ? "cursor-not-allowed opacity-45 hover:border-white/10 hover:bg-white/[0.045]" : ""}`}
onClick={() => {
if (!option.disabled) {
@@ -122,31 +122,31 @@ export default function EventCard({ event, originStation }: EventCardProps) {
<span className="flex items-center justify-between gap-3">
<span className="font-semibold text-white">{option.label}</span>
{activeMode === option.id && (
<span className="rounded-full bg-[#FF2D8D]/20 px-2 py-0.5 text-xs font-semibold text-pink-100">
<span className="rounded-full bg-brand-pink/20 px-2 py-0.5 text-xs font-semibold text-pink-100">
Active
</span>
)}
</span>
<span className="mt-1 block text-xs text-[#F4F1EA]/58">{option.meta}</span>
<span className="mt-1 block text-xs text-brand-light/58">{option.meta}</span>
</button>
))}
</div>
<div className="mb-4 grid gap-3 rounded-xl border border-white/10 bg-black/16 p-3 text-sm sm:grid-cols-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-[#F4F1EA]/42">Leave by</p>
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-brand-light/42">Leave by</p>
<p className="mt-1 font-semibold text-white">
{departureTime ? format(departureTime, "HH:mm") : "Pending"}
</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-[#F4F1EA]/42">Arrive by</p>
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-brand-light/42">Arrive by</p>
<p className="mt-1 font-semibold text-white">
{arrivalTime ? format(arrivalTime, "HH:mm") : format(new Date(event.eventTime.getTime() - arrivalBufferMinutes * 60_000), "HH:mm")}
</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-[#F4F1EA]/42">Buffer</p>
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-brand-light/42">Buffer</p>
<p className="mt-1 font-semibold text-white">
{arrivalBufferMinutes} min {calculatedMode ? `via ${calculatedMode}` : ""}
</p>
+6 -6
View File
@@ -22,7 +22,7 @@ const JourneyList: React.FC<JourneyListProps> = ({
const targetArrivalTime = new Date(eventTime.getTime() - arrivalBufferMinutes * 60000);
if (journeys.length === 0) {
return <div className={`py-8 text-center text-[#F4F1EA]/60 ${className}`}>No journeys found</div>;
return <div className={`py-8 text-center text-brand-light/60 ${className}`}>No journeys found</div>;
}
return (
@@ -37,20 +37,20 @@ const JourneyList: React.FC<JourneyListProps> = ({
key={journey.id}
className={`rounded-xl border p-3 ${
arrivesTooLate || journey.cancelled
? "border-[#FF2D8D]/18 bg-[#FF2D8D]/7 opacity-40 line-through"
? "border-brand-pink/18 bg-brand-pink/7 opacity-40 line-through"
: "border-white/10 bg-white/[0.04]"
}`}
>
<div className="mb-2 flex items-center justify-between gap-3">
<div className="flex-1">
<span className="font-semibold text-white">{formatTime(departure)}</span>
<span className="ml-2 text-sm text-[#F4F1EA]/48">{journey.platform}</span>
<span className="ml-2 text-sm text-brand-light/48">{journey.platform}</span>
{journey.delay > 0 && (
<span className="ml-2 text-sm font-medium text-orange-200">{`+${journey.delay}'`}</span>
)}
</div>
<div className="flex-1 text-right">
<span className={`font-medium ${journey.cancelled ? "text-[#FF2D8D]" : "text-[#F4F1EA]"}`}>
<span className={`font-medium ${journey.cancelled ? "text-brand-pink" : "text-brand-light"}`}>
{journey.cancelled ? "Cancelled" : journey.trains.join(" -> ")}
</span>
</div>
@@ -58,11 +58,11 @@ const JourneyList: React.FC<JourneyListProps> = ({
<LeaveByBadge countdown={calculateCountdown(departure)} />
</div>
</div>
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-[#F4F1EA]/64">
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-brand-light/64">
<span>{journey.changes > 0 ? `Change(s): ${journey.changes}` : "Direct"}</span>
<span>Arrives {formatTime(arrival)}</span>
{arrivesTooLate && (
<span className="font-medium text-[#FF2D8D]">
<span className="font-medium text-brand-pink">
misses {arrivalBufferMinutes} min buffer
</span>
)}
+3 -3
View File
@@ -43,11 +43,11 @@ const TrainSection: React.FC<TrainSectionProps> = ({
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold text-white">Trains</h3>
<p className="text-sm text-[#F4F1EA]/66">
<p className="text-sm text-brand-light/66">
To {destName} <span className="font-mono">{formatDateTime(eventTime)}</span>
</p>
{(arrivalBufferMinutes ?? 0) > 0 && (
<p className="mt-1 text-xs text-[#D946EF]">
<p className="mt-1 text-xs text-brand-fuchsia">
Target arrival: {arrivalBufferMinutes} min early
</p>
)}
@@ -65,7 +65,7 @@ const TrainSection: React.FC<TrainSectionProps> = ({
<LoadingSpinner size="lg" />
</div>
) : error ? (
<div className="p-8 text-center text-sm text-[#FF2D8D]">{error}</div>
<div className="p-8 text-center text-sm text-brand-pink">{error}</div>
) : (
<>
<JourneyList journeys={journeys} eventTime={eventTime} arrivalBufferMinutes={arrivalBufferMinutes ?? 0} />
+7 -7
View File
@@ -24,7 +24,7 @@ const WalkingOption: React.FC<WalkingOptionProps> = ({ walkRoute, walkLoading, w
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold text-white">Final Walk</h3>
<p className="text-sm text-[#F4F1EA]/60">From arrival station to destination</p>
<p className="text-sm text-brand-light/60">From arrival station to destination</p>
</div>
{onRefresh && (
<Button variant="secondary" size="sm" onClick={onRefresh}>
@@ -39,29 +39,29 @@ const WalkingOption: React.FC<WalkingOptionProps> = ({ walkRoute, walkLoading, w
<LoadingSpinner size="md" />
</div>
) : walkError ? (
<div className="py-4 text-center text-[#FF2D8D]">Error: {walkError}</div>
<div className="py-4 text-center text-brand-pink">Error: {walkError}</div>
) : walkRoute ? (
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-[#F4F1EA]/66">Distance</span>
<span className="text-brand-light/66">Distance</span>
<span className="font-medium text-white">
{Math.round(walkRoute.distance / 1000)} km ({Math.round(walkRoute.distance)} m)
</span>
</div>
<div className="flex justify-between">
<span className="text-[#F4F1EA]/66">Duration</span>
<span className="text-brand-light/66">Duration</span>
<span className="font-medium text-white">
{Math.floor(walkRoute.duration / 60)} min {walkRoute.duration % 60} s
</span>
</div>
{walkRoute.steps && walkRoute.steps.length > 0 && (
<div className="mt-4">
<h4 className="mb-2 font-medium text-[#F4F1EA]">Steps</h4>
<h4 className="mb-2 font-medium text-brand-light">Steps</h4>
<ul className="space-y-2">
{walkRoute.steps.map((step: { name: string; instruction: string }, index: number) => (
<li key={index} className="text-sm">
<span className="font-medium text-[#D946EF]">{step.name}</span>
<span className="ml-2 text-[#F4F1EA]/70">{step.instruction}</span>
<span className="font-medium text-brand-fuchsia">{step.name}</span>
<span className="ml-2 text-brand-light/70">{step.instruction}</span>
</li>
))}
</ul>
+1 -1
View File
@@ -31,7 +31,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en" className={`${inter.variable} h-full antialiased`}>
<body className="brand-shell min-h-full flex flex-col bg-[#090816] text-[#F4F1EA]">
<body className="brand-shell min-h-full flex flex-col bg-[#090816] text-brand-light">
<ReminderSettingsProvider>
<EventsProvider>
<ReminderEngine />
+8 -8
View File
@@ -28,14 +28,14 @@ const Header: React.FC<HeaderProps> = ({ className = "" }) => {
<div className="flex items-center">
<Link
href="/"
className="flex h-12 items-center rounded-lg text-[#F4F1EA] drop-shadow-[0_0_18px_rgba(178,60,255,0.28)] focus:outline-none focus:ring-2 focus:ring-[#D946EF]/70"
className="flex h-12 items-center rounded-lg text-brand-light drop-shadow-[0_0_18px_rgba(178,60,255,0.28)] focus:outline-none focus:ring-2 focus:ring-brand-fuchsia/70"
aria-label="TimeToLeave home"
>
<LogoHorizontal height={42} className="block h-[42px] w-auto" />
</Link>
</div>
<div className="flex items-center gap-2 sm:gap-3">
<div className="hidden md:flex items-center rounded-full border border-white/10 bg-white/[0.06] px-3 py-1.5 text-sm text-[#F4F1EA]/76">
<div className="hidden md:flex items-center rounded-full border border-white/10 bg-white/[0.06] px-3 py-1.5 text-sm text-brand-light/76">
<span className="font-semibold text-white">
{events.length}
</span>
@@ -46,8 +46,8 @@ const Header: React.FC<HeaderProps> = ({ className = "" }) => {
Online
</span>
) : status === false ? (
<span className="ml-3 inline-flex items-center gap-1 text-[#FF2D8D]">
<span className="h-1.5 w-1.5 rounded-full bg-[#FF2D8D]" />
<span className="ml-3 inline-flex items-center gap-1 text-brand-pink">
<span className="h-1.5 w-1.5 rounded-full bg-brand-pink" />
Offline
</span>
) : null}
@@ -58,7 +58,7 @@ const Header: React.FC<HeaderProps> = ({ className = "" }) => {
<button
onClick={toggle}
aria-label="Toggle dark mode"
className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-white/[0.06] text-sm text-[#F4F1EA]/80 transition-colors hover:border-[#D946EF]/45 hover:bg-[#D946EF]/12 hover:text-white focus:outline-none focus:ring-2 focus:ring-[#D946EF]/60"
className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-white/[0.06] text-sm text-brand-light/80 transition-colors hover:border-brand-fuchsia/45 hover:bg-brand-fuchsia/12 hover:text-white focus:outline-none focus:ring-2 focus:ring-brand-fuchsia/60"
>
{mounted && dark ? (
<svg aria-hidden="true" viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2">
@@ -74,7 +74,7 @@ const Header: React.FC<HeaderProps> = ({ className = "" }) => {
<button
onClick={() => setShowSettingsModal(true)}
aria-label="Settings"
className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-white/[0.06] text-sm text-[#F4F1EA]/80 transition-colors hover:border-[#D946EF]/45 hover:bg-[#D946EF]/12 hover:text-white focus:outline-none focus:ring-2 focus:ring-[#D946EF]/60"
className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-white/[0.06] text-sm text-brand-light/80 transition-colors hover:border-brand-fuchsia/45 hover:bg-brand-fuchsia/12 hover:text-white focus:outline-none focus:ring-2 focus:ring-brand-fuchsia/60"
>
<svg aria-hidden="true" viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M12 15.5A3.5 3.5 0 1 0 12 8a3.5 3.5 0 0 0 0 7.5z" />
@@ -90,12 +90,12 @@ const Header: React.FC<HeaderProps> = ({ className = "" }) => {
<div className="brand-panel relative w-full max-w-sm rounded-2xl p-6">
<button
onClick={() => setShowSettingsModal(false)}
className="absolute right-3 top-3 inline-flex h-8 w-8 items-center justify-center rounded-lg text-[#F4F1EA]/50 transition-colors hover:bg-white/10 hover:text-white"
className="absolute right-3 top-3 inline-flex h-8 w-8 items-center justify-center rounded-lg text-brand-light/50 transition-colors hover:bg-white/10 hover:text-white"
aria-label="Close settings"
>
x
</button>
<h2 className="mb-4 text-lg font-semibold text-[#F4F1EA]">Settings</h2>
<h2 className="mb-4 text-lg font-semibold text-brand-light">Settings</h2>
<ReminderSettingsPanel />
</div>
</div>
+5 -5
View File
@@ -17,7 +17,7 @@ export default function Home() {
<section className="mb-7 overflow-hidden rounded-2xl border border-white/10 bg-[#17112A]/55 p-5 shadow-[0_22px_70px_rgba(0,0,0,0.28)] backdrop-blur-xl sm:p-7">
<div className="flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between">
<div>
<p className="mb-2 text-xs font-semibold uppercase tracking-[0.28em] text-[#D946EF]">Departure desk</p>
<p className="mb-2 text-xs font-semibold uppercase tracking-[0.28em] text-brand-fuchsia">Departure desk</p>
<h1 className="max-w-2xl text-3xl font-extrabold leading-tight text-white sm:text-5xl">
Know when to leave before the clock turns hostile.
</h1>
@@ -25,22 +25,22 @@ export default function Home() {
<div className="grid grid-cols-2 gap-3 sm:min-w-60">
<div className="brand-panel-soft rounded-xl p-3">
<p className="text-2xl font-bold text-white">{upcoming.length}</p>
<p className="text-xs text-[#F4F1EA]/60">upcoming</p>
<p className="text-xs text-brand-light/60">upcoming</p>
</div>
<div className="brand-panel-soft rounded-xl p-3">
<p className="text-2xl font-bold text-white">{events.length}</p>
<p className="text-xs text-[#F4F1EA]/60">total events</p>
<p className="text-xs text-brand-light/60">total events</p>
</div>
</div>
</div>
</section>
{upcoming.length === 0 ? (
<div className="brand-panel mx-auto max-w-2xl rounded-2xl px-6 py-14 text-center">
<div className="mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-[#8B5CF6] to-[#FF2D8D] text-2xl font-black text-white shadow-[0_16px_44px_rgba(178,60,255,0.4)]">
<div className="mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-[#8B5CF6] to-brand-pink text-2xl font-black text-white shadow-[0_16px_44px_rgba(178,60,255,0.4)]">
T
</div>
<p className="mb-2 text-2xl font-bold text-white">No upcoming events</p>
<p className="mx-auto max-w-sm text-sm leading-6 text-[#F4F1EA]/66">
<p className="mx-auto max-w-sm text-sm leading-6 text-brand-light/66">
Add an event or import your calendar to turn this into a live departure board.
</p>
</div>
+1 -1
View File
@@ -20,7 +20,7 @@ const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
return (
<div
className={
`inline-block animate-spin rounded-full border-4 border-solid border-[#B23CFF] border-t-transparent ${sizeClasses[size]} ${className}`
`inline-block animate-spin rounded-full border-4 border-solid border-brand-purple border-t-transparent ${sizeClasses[size]} ${className}`
}
data-testid="loading-spinner"
>
+16 -16
View File
@@ -30,7 +30,7 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
<div className={`space-y-4 ${className}`}>
{/* Toggle */}
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-[#F4F1EA]/80">
<span className="text-sm font-medium text-brand-light/80">
Leave reminders
</span>
<button
@@ -38,7 +38,7 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
aria-checked={enabled}
onClick={() => setEnabled(!enabled)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
enabled ? "bg-gradient-to-r from-[#8B5CF6] to-[#FF2D8D]" : "bg-white/16"
enabled ? "bg-gradient-to-r from-[#8B5CF6] to-brand-pink" : "bg-white/16"
}`}
>
<span
@@ -54,10 +54,10 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
<div className="space-y-2">
<label
htmlFor="buffer-minutes"
className="text-sm font-medium text-[#F4F1EA]/80"
className="text-sm font-medium text-brand-light/80"
>
Remind me{" "}
<span className="text-[#F4F1EA]/48">
<span className="text-brand-light/48">
(minutes before event)
</span>
</label>
@@ -70,11 +70,11 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
step={1}
value={bufferMinutes}
onChange={(e) => setBufferMinutes(Number(e.target.value))}
className="flex-1 accent-[#B23CFF]"
className="flex-1 accent-brand-purple"
/>
<output
htmlFor="buffer-minutes"
className="min-w-[3ch] text-center text-sm font-semibold tabular-nums text-[#F4F1EA]/80"
className="min-w-[3ch] text-center text-sm font-semibold tabular-nums text-brand-light/80"
>
{bufferMinutes}
</output>
@@ -86,10 +86,10 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
<div className="space-y-2">
<label
htmlFor="arrival-buffer"
className="text-sm font-medium text-[#F4F1EA]/80"
className="text-sm font-medium text-brand-light/80"
>
Arrive early
<span className="text-[#F4F1EA]/48">
<span className="text-brand-light/48">
(minutes before event)
</span>
</label>
@@ -102,11 +102,11 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
step={1}
value={arrivalBufferMinutes}
onChange={(e) => setArrivalBufferMinutes(Number(e.target.value))}
className="flex-1 accent-[#B23CFF]"
className="flex-1 accent-brand-purple"
/>
<output
htmlFor="arrival-buffer"
className="min-w-[3ch] text-center text-sm font-semibold tabular-nums text-[#F4F1EA]/80"
className="min-w-[3ch] text-center text-sm font-semibold tabular-nums text-brand-light/80"
>
{arrivalBufferMinutes}
</output>
@@ -115,7 +115,7 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
{/* Show walking option toggle */}
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-[#F4F1EA]/80">
<span className="text-sm font-medium text-brand-light/80">
Show walking option
</span>
<button
@@ -123,7 +123,7 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
aria-checked={showWalkingOption}
onClick={() => setShowWalkingOption(!showWalkingOption)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
showWalkingOption ? "bg-gradient-to-r from-[#8B5CF6] to-[#FF2D8D]" : "bg-white/16"
showWalkingOption ? "bg-gradient-to-r from-[#8B5CF6] to-brand-pink" : "bg-white/16"
}`}
>
<span
@@ -136,7 +136,7 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
{/* Show bike option toggle */}
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-[#F4F1EA]/80">
<span className="text-sm font-medium text-brand-light/80">
Show bike route
</span>
<button
@@ -144,7 +144,7 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
aria-checked={showBikeOption}
onClick={() => setShowBikeOption(!showBikeOption)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
showBikeOption ? "bg-gradient-to-r from-[#8B5CF6] to-[#FF2D8D]" : "bg-white/16"
showBikeOption ? "bg-gradient-to-r from-[#8B5CF6] to-brand-pink" : "bg-white/16"
}`}
>
<span
@@ -157,11 +157,11 @@ export default function ReminderSettingsPanel({ className = "" }: ReminderSettin
{/* Permission status */}
<div className="border-t border-white/10 pt-2">
<p className="text-xs text-[#F4F1EA]/50">{statusLabel}</p>
<p className="text-xs text-brand-light/50">{statusLabel}</p>
{permission !== "granted" && permission !== "denied" && (
<button
onClick={() => Notification.requestPermission()}
className="mt-2 text-xs text-[#B23CFF] hover:underline"
className="mt-2 text-xs text-brand-purple hover:underline"
>
Request permission now
</button>