feat: coaster-only toggle on live ride status panel

Queue-Times groups rides into lands (e.g. "Coasters", "Family", "Kids").
Capture that categorisation in LiveRide.isCoaster and surface it as a
toggle in the new LiveRidePanel client component.

- lib/scrapers/queuetimes.ts: add isCoaster: boolean to LiveRide,
  derived from land.name.toLowerCase().includes("coaster")
- components/LiveRidePanel.tsx: client component replacing the old
  inline LiveRideList; adds a "🎢 Coasters only" pill toggle that
  filters the grid; toggle only appears when the park has coaster-
  categorised rides; amber when active, muted when inactive
- app/park/[id]/page.tsx: swap LiveRideList for LiveRidePanel,
  remove now-dead LiveRideList/LiveRideRow functions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 12:56:20 -04:00
parent da083c125c
commit 819e716197
3 changed files with 210 additions and 155 deletions

View File

@@ -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,
});
}