diff --git a/app/page.tsx b/app/page.tsx
index d90edce..485d33b 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,9 +1,5 @@
-import { WeekCalendar } from "@/components/WeekCalendar";
-import { MobileCardList } from "@/components/MobileCardList";
-import { WeekNav } from "@/components/WeekNav";
-import { Legend } from "@/components/Legend";
-import { EmptyState } from "@/components/EmptyState";
-import { PARKS, groupByRegion } from "@/lib/parks";
+import { HomePageClient } from "@/components/HomePageClient";
+import { PARKS } from "@/lib/parks";
import { openDb, getDateRange } from "@/lib/db";
import { getTodayLocal, isWithinOperatingWindow } from "@/lib/env";
import { fetchLiveRides } from "@/lib/scrapers/queuetimes";
@@ -11,7 +7,7 @@ import { QUEUE_TIMES_IDS } from "@/lib/queue-times-map";
import { readParkMeta, getCoasterSet } from "@/lib/park-meta";
interface PageProps {
- searchParams: Promise<{ week?: string; coasters?: string }>;
+ searchParams: Promise<{ week?: string }>;
}
function getWeekStart(param: string | undefined): string {
@@ -46,7 +42,6 @@ function getCurrentWeekStart(): string {
export default async function HomePage({ searchParams }: PageProps) {
const params = await searchParams;
const weekStart = getWeekStart(params.week);
- const coastersOnly = params.coasters === "1";
const weekDates = getWeekDates(weekStart);
const endDate = weekDates[6];
const today = getTodayLocal();
@@ -61,8 +56,7 @@ export default async function HomePage({ searchParams }: PageProps) {
0
);
- // Fetch live ride counts for parks open today (cached 5 min via Queue-Times).
- // Only shown when the current time is within 1h before open to 1h after close.
+ // Always fetch both ride and coaster counts — the client decides which to display.
const parkMeta = readParkMeta();
const hasCoasterData = PARKS.some((p) => (parkMeta[p.id]?.coasters.length ?? 0) > 0);
@@ -91,108 +85,17 @@ export default async function HomePage({ searchParams }: PageProps) {
);
}
- const activeCounts = coastersOnly ? coasterCounts : rideCounts;
-
- const visibleParks = PARKS.filter((park) =>
- weekDates.some((date) => data[park.id]?.[date]?.isOpen)
- );
-
- const grouped = groupByRegion(visibleParks);
-
return (
-
- {/* ── Header ─────────────────────────────────────────────────────────── */}
-
- {/* Row 1: Title + park count */}
-
-
- Thoosie Calendar
-
-
- 0 ? "var(--color-open-bg)" : "var(--color-surface)",
- border: `1px solid ${visibleParks.length > 0 ? "var(--color-open-border)" : "var(--color-border)"}`,
- borderRadius: 20,
- padding: "4px 14px",
- fontSize: "0.78rem",
- color: visibleParks.length > 0 ? "var(--color-open-hours)" : "var(--color-text-muted)",
- fontWeight: 600,
- }}>
- {visibleParks.length} of {PARKS.length} parks open this week
-
-
-
- {/* Row 2: Week nav + legend (legend hidden on mobile) */}
-
-
-
- {/* ── Main content ───────────────────────────────────────────────────── */}
-
- {scrapedCount === 0 ? (
-
- ) : (
- <>
- {/* Mobile: card list (hidden on lg+) */}
-
-
-
-
- {/* Desktop: week table (hidden below lg) */}
-
-
-
- >
- )}
-
-
+
);
}
diff --git a/components/HomePageClient.tsx b/components/HomePageClient.tsx
new file mode 100644
index 0000000..848e960
--- /dev/null
+++ b/components/HomePageClient.tsx
@@ -0,0 +1,182 @@
+"use client";
+
+import { useState, useEffect } from "react";
+import { WeekCalendar } from "./WeekCalendar";
+import { MobileCardList } from "./MobileCardList";
+import { WeekNav } from "./WeekNav";
+import { Legend } from "./Legend";
+import { EmptyState } from "./EmptyState";
+import { PARKS, groupByRegion } from "@/lib/parks";
+import type { DayData } from "@/lib/db";
+
+const COASTER_MODE_KEY = "coasterMode";
+
+interface HomePageClientProps {
+ weekStart: string;
+ weekDates: string[];
+ today: string;
+ isCurrentWeek: boolean;
+ data: Record>;
+ rideCounts: Record;
+ coasterCounts: Record;
+ hasCoasterData: boolean;
+ scrapedCount: number;
+}
+
+export function HomePageClient({
+ weekStart,
+ weekDates,
+ today,
+ isCurrentWeek,
+ data,
+ rideCounts,
+ coasterCounts,
+ hasCoasterData,
+ scrapedCount,
+}: HomePageClientProps) {
+ const [coastersOnly, setCoastersOnly] = useState(false);
+
+ // Hydrate from localStorage after mount to avoid SSR mismatch.
+ useEffect(() => {
+ setCoastersOnly(localStorage.getItem(COASTER_MODE_KEY) === "true");
+ }, []);
+
+ const toggle = () => {
+ const next = !coastersOnly;
+ setCoastersOnly(next);
+ localStorage.setItem(COASTER_MODE_KEY, String(next));
+ };
+
+ const activeCounts = coastersOnly ? coasterCounts : rideCounts;
+
+ const visibleParks = PARKS.filter((park) =>
+ weekDates.some((date) => data[park.id]?.[date]?.isOpen)
+ );
+ const grouped = groupByRegion(visibleParks);
+
+ return (
+
+ {/* ── Header ───────────────────────────────────────────────────────────── */}
+
+ {/* Row 1: Title + controls */}
+
+
+ Thoosie Calendar
+
+
+
+ {hasCoasterData && (
+
+ )}
+
+ 0 ? "var(--color-open-bg)" : "var(--color-surface)",
+ border: `1px solid ${visibleParks.length > 0 ? "var(--color-open-border)" : "var(--color-border)"}`,
+ borderRadius: 20,
+ padding: "4px 14px",
+ fontSize: "0.78rem",
+ color: visibleParks.length > 0 ? "var(--color-open-hours)" : "var(--color-text-muted)",
+ fontWeight: 600,
+ }}>
+ {visibleParks.length} of {PARKS.length} parks open this week
+
+
+
+
+ {/* Row 2: Week nav + legend */}
+
+
+
+ {/* ── Main content ────────────────────────────────────────────────────── */}
+
+ {scrapedCount === 0 ? (
+
+ ) : (
+ <>
+ {/* Mobile: card list (hidden on lg+) */}
+
+
+
+
+ {/* Desktop: week table (hidden below lg) */}
+
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/components/LiveRidePanel.tsx b/components/LiveRidePanel.tsx
index 57f379b..cb641fa 100644
--- a/components/LiveRidePanel.tsx
+++ b/components/LiveRidePanel.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState } from "react";
+import { useState, useEffect } from "react";
import type { LiveRidesResult, LiveRide } from "@/lib/scrapers/queuetimes";
interface LiveRidePanelProps {
@@ -13,6 +13,13 @@ export function LiveRidePanel({ liveRides, parkOpenToday }: LiveRidePanelProps)
const hasCoasters = rides.some((r) => r.isCoaster);
const [coastersOnly, setCoastersOnly] = useState(false);
+ // Pre-select coaster filter if Coaster Mode is enabled on the homepage.
+ useEffect(() => {
+ if (hasCoasters && localStorage.getItem("coasterMode") === "true") {
+ setCoastersOnly(true);
+ }
+ }, [hasCoasters]);
+
const visible = coastersOnly ? rides.filter((r) => r.isCoaster) : rides;
const openRides = visible.filter((r) => r.isOpen);
const closedRides = visible.filter((r) => !r.isOpen);
diff --git a/components/WeekNav.tsx b/components/WeekNav.tsx
index 0548e3e..72c5118 100644
--- a/components/WeekNav.tsx
+++ b/components/WeekNav.tsx
@@ -7,8 +7,6 @@ interface WeekNavProps {
weekStart: string; // YYYY-MM-DD (Sunday)
weekDates: string[]; // 7 dates YYYY-MM-DD
isCurrentWeek: boolean;
- coastersOnly: boolean;
- hasCoasterData: boolean;
}
const MONTHS = [
@@ -33,15 +31,10 @@ function shiftWeek(weekStart: string, delta: number): string {
return d.toISOString().slice(0, 10);
}
-export function WeekNav({ weekStart, weekDates, isCurrentWeek, coastersOnly, hasCoasterData }: WeekNavProps) {
+export function WeekNav({ weekStart, weekDates, isCurrentWeek }: WeekNavProps) {
const router = useRouter();
- const weekParam = `week=${weekStart}`;
const nav = (delta: number) => {
- const base = `/?week=${shiftWeek(weekStart, delta)}`;
- router.push(coastersOnly ? `${base}&coasters=1` : base);
- };
- const toggleCoasters = () => {
- router.push(coastersOnly ? `/?${weekParam}` : `/?${weekParam}&coasters=1`);
+ router.push(`/?week=${shiftWeek(weekStart, delta)}`);
};
useEffect(() => {
@@ -99,30 +92,6 @@ export function WeekNav({ weekStart, weekDates, isCurrentWeek, coastersOnly, has
>
→
-
- {hasCoasterData && (
-
- )}
);
}