diff --git a/app/park/[id]/page.tsx b/app/park/[id]/page.tsx
index b16e35b..8432647 100644
--- a/app/park/[id]/page.tsx
+++ b/app/park/[id]/page.tsx
@@ -6,8 +6,9 @@ import { scrapeRidesForDay } from "@/lib/scrapers/sixflags";
import { fetchLiveRides } from "@/lib/scrapers/queuetimes";
import { QUEUE_TIMES_IDS } from "@/lib/queue-times-map";
import { ParkMonthCalendar } from "@/components/ParkMonthCalendar";
+import { LiveRidePanel } from "@/components/LiveRidePanel";
import type { RideStatus, RidesFetchResult } from "@/lib/scrapers/sixflags";
-import type { LiveRidesResult, LiveRide } from "@/lib/scrapers/queuetimes";
+import type { LiveRidesResult } from "@/lib/scrapers/queuetimes"; // used as prop type below
interface PageProps {
params: Promise<{ id: string }>;
@@ -126,7 +127,7 @@ export default async function ParkPage({ params, searchParams }: PageProps) {
{liveRides ? (
-
@@ -207,158 +208,6 @@ function LiveBadge() {
);
}
-// ── Live ride list (Queue-Times data) ──────────────────────────────────────
-
-function LiveRideList({
- liveRides,
- parkOpenToday,
-}: {
- liveRides: LiveRidesResult;
- parkOpenToday: boolean;
-}) {
- const { rides } = liveRides;
- const openRides = rides.filter((r) => r.isOpen);
- const closedRides = rides.filter((r) => !r.isOpen);
- const anyOpen = openRides.length > 0;
-
- return (
-
- {/* Summary badge row */}
-
- {anyOpen ? (
-
- {openRides.length} open
-
- ) : (
-
- {parkOpenToday ? "Not open yet — check back soon" : "No rides open"}
-
- )}
- {anyOpen && closedRides.length > 0 && (
-
- {closedRides.length} closed / down
-
- )}
-
-
- {/* Two-column grid */}
-
- {openRides.map((ride) => )}
- {closedRides.map((ride) => )}
-
-
- {/* Attribution — required by Queue-Times terms */}
-
-
- );
-}
-
-function LiveRideRow({ ride }: { ride: LiveRide }) {
- const showWait = ride.isOpen && ride.waitMinutes > 0;
-
- return (
-
-
-
-
- {ride.name}
-
-
- {showWait && (
-
- {ride.waitMinutes} min
-
- )}
- {ride.isOpen && !showWait && (
-
- walk-on
-
- )}
-
- );
-}
-
// ── Schedule ride list (Six Flags operating-hours API fallback) ────────────
function RideList({
diff --git a/components/LiveRidePanel.tsx b/components/LiveRidePanel.tsx
new file mode 100644
index 0000000..d488dc7
--- /dev/null
+++ b/components/LiveRidePanel.tsx
@@ -0,0 +1,200 @@
+"use client";
+
+import { useState } from "react";
+import type { LiveRidesResult, LiveRide } from "@/lib/scrapers/queuetimes";
+
+interface LiveRidePanelProps {
+ liveRides: LiveRidesResult;
+ parkOpenToday: boolean;
+}
+
+export function LiveRidePanel({ liveRides, parkOpenToday }: LiveRidePanelProps) {
+ const { rides } = liveRides;
+ const hasCoasters = rides.some((r) => r.isCoaster);
+ const [coastersOnly, setCoastersOnly] = useState(false);
+
+ const visible = coastersOnly ? rides.filter((r) => r.isCoaster) : rides;
+ const openRides = visible.filter((r) => r.isOpen);
+ const closedRides = visible.filter((r) => !r.isOpen);
+ const anyOpen = openRides.length > 0;
+
+ return (
+
+ {/* ── Toolbar: summary + coaster toggle ────────────────────────────── */}
+
+ {/* Open count badge */}
+ {anyOpen ? (
+
+ {openRides.length} open
+
+ ) : (
+
+ {parkOpenToday ? "Not open yet — check back soon" : "No rides open"}
+
+ )}
+
+ {/* Closed count badge */}
+ {anyOpen && closedRides.length > 0 && (
+
+ {closedRides.length} closed / down
+
+ )}
+
+ {/* Coaster toggle — only shown when the park has categorised coasters */}
+ {hasCoasters && (
+
+ )}
+
+
+ {/* ── Ride grid ────────────────────────────────────────────────────── */}
+
+ {openRides.map((ride) => )}
+ {closedRides.map((ride) => )}
+
+
+ {/* ── Attribution ──────────────────────────────────────────────────── */}
+
+
+ );
+}
+
+function RideRow({ ride }: { ride: LiveRide }) {
+ const showWait = ride.isOpen && ride.waitMinutes > 0;
+
+ return (
+
+
+
+
+ {ride.name}
+
+
+ {showWait && (
+
+ {ride.waitMinutes} min
+
+ )}
+ {ride.isOpen && !showWait && (
+
+ walk-on
+
+ )}
+
+ );
+}
diff --git a/lib/scrapers/queuetimes.ts b/lib/scrapers/queuetimes.ts
index c9c3201..d33053c 100644
--- a/lib/scrapers/queuetimes.ts
+++ b/lib/scrapers/queuetimes.ts
@@ -21,6 +21,8 @@ export interface LiveRide {
isOpen: boolean;
waitMinutes: number;
lastUpdated: string; // ISO 8601
+ /** True when Queue-Times placed this ride in a "Coasters" land category. */
+ isCoaster: boolean;
}
export interface LiveRidesResult {
@@ -76,8 +78,10 @@ export async function fetchLiveRides(
const rides: LiveRide[] = [];
- // Rides are nested inside lands
+ // Rides are nested inside lands. Queue-Times labels coaster sections
+ // with names like "Coasters", "Steel Coasters", "Wooden Coasters", etc.
for (const land of json.lands ?? []) {
+ const isCoaster = land.name.toLowerCase().includes("coaster");
for (const r of land.rides ?? []) {
if (!r.name) continue;
rides.push({
@@ -85,6 +89,7 @@ export async function fetchLiveRides(
isOpen: r.is_open,
waitMinutes: r.wait_time ?? 0,
lastUpdated: r.last_updated,
+ isCoaster,
});
}
}
@@ -97,6 +102,7 @@ export async function fetchLiveRides(
isOpen: r.is_open,
waitMinutes: r.wait_time ?? 0,
lastUpdated: r.last_updated,
+ isCoaster: false,
});
}