diff --git a/app/globals.css b/app/globals.css index c3e9ce1..1388dd2 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,49 +1,49 @@ @import "tailwindcss"; @theme { - /* ── Backgrounds ─────────────────────────────────────────────────────────── */ - --color-bg: #0c1220; - --color-surface: #141c2e; - --color-surface-2: #1c2640; - --color-surface-hover: #222e4a; - --color-border: #1f2d45; - --color-border-subtle: #172035; + /* ── Backgrounds — deep neutral dark, no purple tint ─────────────────────── */ + --color-bg: #111111; + --color-surface: #1c1c1c; + --color-surface-2: #242424; + --color-surface-hover: #2c2c2c; + --color-border: #333333; + --color-border-subtle: #272727; - /* ── Text ────────────────────────────────────────────────────────────────── */ - --color-text: #f1f5f9; - --color-text-secondary: #94a3b8; - --color-text-muted: #64748b; - --color-text-dim: #475569; + /* ── Text — clean white, no tint ─────────────────────────────────────────── */ + --color-text: #f5f5f5; + --color-text-secondary: #b0b0b0; + --color-text-muted: #737373; + --color-text-dim: #4a4a4a; - /* ── Warm accent (Today / active states) ─────────────────────────────────── */ - --color-accent: #f59e0b; - --color-accent-hover: #d97706; - --color-accent-text: #fef3c7; - --color-accent-muted: #78350f; + /* ── Hot pink accent — neon sign energy ──────────────────────────────────── */ + --color-accent: #ff4d8d; + --color-accent-hover: #e6006e; + --color-accent-text: #fff0f7; + --color-accent-muted: #3d0f22; - /* ── Open (green) ────────────────────────────────────────────────────────── */ - --color-open-bg: #052e16; - --color-open-border: #16a34a; + /* ── Open — electric lime green (go!) ────────────────────────────────────── */ + --color-open-bg: #0a1a0d; + --color-open-border: #22c55e; --color-open-text: #4ade80; - --color-open-hours: #dcfce7; + --color-open-hours: #bbf7d0; - /* ── Passholder preview (purple) ─────────────────────────────────────────── */ - --color-ph-bg: #1e0f2e; - --color-ph-border: #7e22ce; - --color-ph-hours: #e9d5ff; - --color-ph-label: #c084fc; + /* ── Passholder preview — vivid cyan ─────────────────────────────────────── */ + --color-ph-bg: #051518; + --color-ph-border: #22d3ee; + --color-ph-hours: #cffafe; + --color-ph-label: #67e8f9; - /* ── Today column (amber instead of cold blue) ───────────────────────────── */ - --color-today-bg: #1c1a0e; - --color-today-border: #f59e0b; - --color-today-text: #fde68a; + /* ── Today — vivid yellow, unmissable ────────────────────────────────────── */ + --color-today-bg: #1a1800; + --color-today-border: #facc15; + --color-today-text: #fef08a; - /* ── Weekend header ──────────────────────────────────────────────────────── */ - --color-weekend-header: #141f35; + /* ── Weekend — barely-there dark tint ───────────────────────────────────────*/ + --color-weekend-header: #181818; /* ── Region header ───────────────────────────────────────────────────────── */ - --color-region-bg: #0e1628; - --color-region-accent: #334155; + --color-region-bg: #161616; + --color-region-accent: #ff4d8d; } :root { @@ -64,14 +64,14 @@ height: 6px; } ::-webkit-scrollbar-track { - background: var(--color-bg); + background: var(--color-surface-2); } ::-webkit-scrollbar-thumb { background: var(--color-border); border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { - background: var(--color-text-muted); + background: var(--color-accent); } /* ── Sticky column shadow when scrolling ─────────────────────────────────── */ diff --git a/app/park/[id]/page.tsx b/app/park/[id]/page.tsx index af611ef..b4d0439 100644 --- a/app/park/[id]/page.tsx +++ b/app/park/[id]/page.tsx @@ -100,7 +100,7 @@ export default async function ParkPage({ params, searchParams }: PageProps) { -
+
{/* ── Month Calendar ───────────────────────────────────────────────── */}
diff --git a/components/ParkMonthCalendar.tsx b/components/ParkMonthCalendar.tsx index 975581a..2185f11 100644 --- a/components/ParkMonthCalendar.tsx +++ b/components/ParkMonthCalendar.tsx @@ -126,7 +126,7 @@ export function ParkMonthCalendar({ parkId, year, month, monthData, today }: Par if (!cell.day || !cell.iso) { return (
@@ -147,18 +147,18 @@ export function ParkMonthCalendar({ parkId, year, month, monthData, today }: Par return (
{/* Date number */} + ) : isPH && isOpen ? (
-
+
Passholder
-
+
{dayData.hoursLabel}
@@ -191,15 +191,15 @@ export function ParkMonthCalendar({ parkId, year, month, monthData, today }: Par
-
+
{dayData.hoursLabel}
) : ( - · + · )}
); diff --git a/lib/park-meta.ts b/lib/park-meta.ts index 0b71d7e..caf95fa 100644 --- a/lib/park-meta.ts +++ b/lib/park-meta.ts @@ -67,9 +67,9 @@ export function areCoastersStale(entry: ParkMeta): boolean { */ export function normalizeRideName(name: string): string { return name - .replace(/[™®©]/g, "") + .replace(/[\u2122\u00ae\u00a9™®©]/g, "") .replace(/^the\s+/i, "") - .replace(/[-:'".]/g, " ") + .replace(/[^\w\s]/g, " ") .replace(/\s+/g, " ") .toLowerCase() .trim(); diff --git a/lib/scrapers/queuetimes.ts b/lib/scrapers/queuetimes.ts index 71e4fcc..b54be43 100644 --- a/lib/scrapers/queuetimes.ts +++ b/lib/scrapers/queuetimes.ts @@ -11,18 +11,44 @@ const BASE = "https://queue-times.com/parks"; /** * Normalize a ride name for fuzzy matching between Queue-Times and RCDB. - * Strips trademark symbols, leading "THE ", and punctuation before comparing. + * + * - Strips trademark/copyright symbols (™ ® © and Unicode variants) + * - Strips leading "THE " prefix + * - Replaces ALL non-word, non-space characters with a space + * (handles !, -, :, ', ' U+2019, ", and any other punctuation) + * - Collapses whitespace, lowercases, trims */ function normalize(name: string): string { return name - .replace(/[™®©]/g, "") + .replace(/[\u2122\u00ae\u00a9™®©]/g, "") .replace(/^the\s+/i, "") - .replace(/[-:'".]/g, " ") + .replace(/[^\w\s]/g, " ") .replace(/\s+/g, " ") .toLowerCase() .trim(); } +/** + * Check if a Queue-Times ride name matches any coaster in the RCDB set. + * + * Exact normalized match covers most cases. Prefix matching handles cases + * where one source drops or adds a subtitle: + * "Apocalypse" (QT) vs "Apocalypse the Ride" (RCDB) + * "The New Revolution - Classic" (QT) vs "New Revolution" (RCDB) + * + * Minimum 5 chars on the shorter side prevents accidental short matches. + */ +function isCoaster(name: string, coasterSet: Set): boolean { + const norm = normalize(name); + if (coasterSet.has(norm)) return true; + for (const c of coasterSet) { + const shorter = norm.length <= c.length ? norm : c; + const longer = norm.length <= c.length ? c : norm; + if (shorter.length >= 5 && longer.startsWith(shorter)) return true; + } + return false; +} + const HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + @@ -105,7 +131,7 @@ export async function fetchLiveRides( isOpen: r.is_open, waitMinutes: r.wait_time ?? 0, lastUpdated: r.last_updated, - isCoaster: coasterNames ? coasterNames.has(normalize(r.name)) : false, + isCoaster: coasterNames ? isCoaster(r.name, coasterNames) : false, }); } } @@ -118,7 +144,7 @@ export async function fetchLiveRides( isOpen: r.is_open, waitMinutes: r.wait_time ?? 0, lastUpdated: r.last_updated, - isCoaster: coasterNames ? coasterNames.has(normalize(r.name)) : false, + isCoaster: coasterNames ? isCoaster(r.name, coasterNames) : false, }); }