Commit Graph

41 Commits

Author SHA1 Message Date
josh 0dc84c7597 fix: bypass Data Cache on live park/ride pages so navigation shows fresh data
Build and Deploy / Lint, typecheck, test (push) Successful in 33s
Build and Deploy / Build & Push (push) Successful in 1m15s
The ride detail and park pages fetched with `next: { revalidate: 60 }`,
which is stale-while-revalidate. After hours of no traffic the Data Cache
held a morning snapshot; the first click served that stale value and only
the second request (e.g. a browser refresh) got the just-revalidated
payload. The endpoint also bundles live state with chart history, so one
stale fetch made the whole page wrong.

Switch the live-data fetches to `cache: "no-store"`. The calendar-month
fetch keeps its 5-min ISR since operating hours change slowly.
2026-06-01 21:06:58 -04:00
josh 5d9daee627 refactor: production-essentials hardening pass
Build and Deploy / Lint, typecheck, test (push) Successful in 30s
Build and Deploy / Build & Push (push) Successful in 1m39s
Backend: structured logger, env-validated config, graceful SIGTERM/SIGINT
shutdown, per-IP rate limiter, per-tier scheduler concurrency latch, error
context on previously-silent catches, compiled-JS Dockerfile stage.

Frontend: lib/api.ts consolidates BACKEND_URL with lazy production-required
check, root + per-segment error.tsx / not-found.tsx / loading.tsx,
generateMetadata on park and ride pages, graceful fallback when backend is
unreachable, Plausible script gated on env vars.

Infra: CI runs lint + typecheck + tests on both packages before docker build,
compose adds healthchecks, log rotation, and memory limits; .env.example
documents every variable.

Cleanup: removed empty app/api/parks/ dir and 0-byte root parks.db, moved
wait-times-urls.txt into docs/, dropped an `as any` cast.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 10:17:52 -04:00
josh 6447db3008 refactor: store selected week in a cookie, not the URL
Build and Deploy / Build & Push (push) Successful in 1m7s
The home page no longer reads ?week=YYYY-MM-DD from the URL. Selected week
lives in the tcWeek cookie, set via a server action that revalidates the
home page so the next render reflects it. The URL stays at "/" regardless
of which week the user is viewing.

WeekNav prev/next/today buttons (and the arrow-key bindings) call the
server action directly — no router.refresh dance, no client-side cookie
write. BackToCalendarLink drops its localStorage-based href reconstruction
and just links to "/" since the cookie already remembers the right week
across navigations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 08:39:20 -04:00
josh 44d079efb9 fix: render today's wait chart in viewer's local time + close stale live state
Build and Deploy / Build & Push (push) Successful in 1m8s
Two related polish fixes for the ride detail page:

1. Wait-time chart x-axis now uses Intl.DateTimeFormat with no timezone
   argument, so an Eastern-time user viewing a Pacific park sees ET on
   the axis. Backend now sends recorded_at (UTC) alongside local_time.

2. Ride-history endpoint now applies the same operating-window gate the
   /rides route uses. Queue-Times keeps reporting yesterday's last wait
   with isOpen=true overnight, which made the "Right now" pill show a
   live wait time when the park was actually closed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 08:23:00 -04:00
josh 4f838d99c1 feat: add per-ride history charts with wait time and uptime tracking
Build and Deploy / Build & Push (push) Successful in 3m7s
Adds a cron-driven sampler that snapshots Queue-Times waits and Six Flags
Fast Lane data every 5 minutes into a new ride_wait_samples table, and a
clickable per-ride detail page at /park/[id]/ride/[slug] with Today / 7d /
30d Recharts views plus a 30d uptime pill. Rides are keyed by Queue-Times'
stable qt_ride_id so renames don't fragment history. Samples store
pre-bucketed local_date and local_time in the park's IANA timezone so
aggregations are pure SQL and DST-safe.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:35:27 -04:00
josh bfe099322f feat: add Fast Lane wait times toggle on park pages
Build and Deploy / Build & Push (push) Successful in 1m3s
Join Fast Lane waits from the Six Flags /wait-times endpoint onto
Queue-Times rides by name. A new toggle on the live ride panel swaps
the shown wait to the Fast Lane number; regular waits and open status
still come from Queue-Times.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 22:51:52 -04:00
josh 06b911917d fix: use explicit Eastern timezone for day boundary instead of system TZ
Build and Deploy / Build & Push (push) Successful in 2m50s
getTodayLocal() relied on system clock hours, which broke in the web
container (TZ defaulting to UTC) — the day flipped at 11 PM EDT (3 AM
UTC) instead of 3 AM Eastern. Now uses Intl.DateTimeFormat with an
explicit America/New_York timezone. Also replaced all toISOString()
date formatting with local-component helpers to avoid UTC conversion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 23:11:33 -04:00
josh 3815da2d3f refactor: make frontend a pure presentation layer fetching from backend API
Server components now fetch composed data from the backend instead of
directly querying SQLite and external APIs. Removes better-sqlite3
dependency from the frontend entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:43:59 -04:00
josh 4652a92c29 refactor: hardcode API IDs and coaster lists, remove Playwright discovery
Embed Six Flags API IDs directly in the park registry and snapshot
coaster lists from park-meta.json into a TypeScript module. This
eliminates the Playwright-based discovery script, RCDB scraper, and
runtime dependency on park-meta.json — preparing for the backend
API transition.

- Add apiId field to Park type and all 24 park entries
- Create lib/coaster-data.ts with hardcoded coaster lists
- Update page components to use park.apiId and new getCoasterSet()
- Remove scripts/discover.ts, lib/scrapers/rcdb.ts, lib/park-meta.ts
- Remove data/park-meta.json from shared volume
- Remove playwright devDependency and discover npm script
- Simplify scripts/scrape.ts (no RCDB, no discovery checks)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:25:53 -04:00
josh c5dc01b6ff adds unami
Build and Deploy / Build & Push (push) Successful in 8m48s
2026-04-23 21:00:34 -04:00
Josh Wright f0faff412c feat: use dateless Six Flags API endpoint for live today data
Build and Deploy / Build & Push (push) Successful in 17s
The API without a date param returns today's operating data directly,
invalidating the previous assumption that today's date was always missing.

- Add fetchToday(apiId, revalidate?) to sixflags.ts — calls the dateless
  endpoint with optional ISR cache
- Extract parseApiDay() helper shared by scrapeMonth and fetchToday
- Update upsertDay WHERE clause: >= date('now') so today can be updated
  (was > date('now'), which froze today after first write)
- scrape.ts: add a today-scrape pass after the monthly loop so each run
  always writes fresh today data to the DB
- app/page.tsx: fetch live today data for all parks (5-min ISR) and merge
  into the data map before computing open/closing/weatherDelay status
- app/park/[id]/page.tsx: prefer live today data from API for todayData
  so weather delays and hour changes surface within 5 minutes
- scrapeRidesForDay: update comment only — role unchanged (QT fallback)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 16:54:06 -04:00
Josh Wright 569d0a41e2 polish: more padding and line spacing in month calendar pills, taller min row
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:19:46 -04:00
Josh Wright f85cc084b7 feat: blue dot + Weather Delay notice for storm-closed parks
- Dot turns blue (instead of green) during weather delay on homepage
- Mobile card "Open today" badge becomes blue "⛈ Weather Delay"
- Park page LiveRidePanel shows a blue "⛈ Weather Delay — all rides currently closed" badge instead of "Not open yet — check back soon"
- Added --color-weather-* CSS variables (blue palette)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:01:37 -04:00
Josh Wright 32f0d05038 feat: show open dot based on hours, Weather Delay when queue-times shows 0 rides
Build and Deploy / Build & Push (push) Successful in 49s
Park open indicator now derives from scheduled hours, not ride counts.
Parks with queue-times coverage but 0 open rides (e.g. storm) show a
"⛈ Weather Delay" notice instead of a ride count on both desktop and mobile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 14:56:54 -04:00
Josh Wright 040c1e4d70 fix: responsive park month calendar — dot-only on mobile, full pill on desktop
Build and Deploy / Build & Push (push) Successful in 53s
Mobile cells (~55px wide) can't fit hours text legibly, so show only a
colored dot when open and a dash when no data. Desktop restores the full
pill (hours + tz abbreviation) via Tailwind responsive classes. Row height
is controlled by a new .park-calendar-grid CSS class: 72px fixed on mobile,
minmax(96px, auto) on sm+, keeping desktop cells from looking cramped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 09:31:48 -04:00
Josh Wright 53297a7cff feat: amber indicator during post-close wind-down buffer
Build and Deploy / Build & Push (push) Successful in 2m22s
Parks in the 1-hour buffer after scheduled close now show amber instead
of green: the dot on the desktop calendar turns yellow, and the mobile
card badge changes from "Open today" (green) to "Closing" (amber).

- getOperatingStatus() replaces isWithinOperatingWindow's inline logic,
  returning "open" | "closing" | "closed"; isWithinOperatingWindow now
  delegates to it so all callers are unchanged
- closingParkIds[] is computed server-side and threaded through
  HomePageClient → WeekCalendar/MobileCardList → ParkRow/ParkCard
- New --color-closing-* CSS variables mirror the green palette in amber

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 09:06:45 -04:00
Josh Wright 090f4d876d fix: "← Calendar" returns to the previously viewed week
Build and Deploy / Build & Push (push) Successful in 1m3s
HomePageClient writes the current weekStart to localStorage("lastWeek")
whenever it changes. BackToCalendarLink (new client component) reads
that value on mount and builds the href — falling back to "/" if nothing
is stored (e.g. direct link to a park page).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 08:59:14 -04:00
Josh Wright 5b575f962e feat: persistent Coaster Mode toggle in header top-right
Build and Deploy / Build & Push (push) Successful in 53s
- Moves the coaster toggle out of WeekNav and into the homepage header
  top-right as "🎢 Coaster Mode", alongside the parks open badge
- State is stored in localStorage ("coasterMode") so the preference
  persists across sessions and page refreshes
- Dropped the ?coasters=1 URL param entirely; the server always fetches
  both rideCounts and coasterCounts, and HomePageClient picks which to
  display client-side — no flash or server round-trip on toggle
- Individual park pages: LiveRidePanel reads localStorage on mount and
  pre-selects the Coasters Only filter when Coaster Mode is active
- Extracted HomePageClient (client component) to own the full homepage
  UI; page.tsx is now pure data-fetching

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 08:36:38 -04:00
Josh Wright 8c3841d9a5 polish: move queue-times attribution to Rides section heading
Build and Deploy / Build & Push (push) Successful in 51s
Attribution now sits on the right end of the "RIDES · Live" bar as a
subtle "via queue-times.com" link, making it clear the live ride data
(not the whole site) is sourced from Queue-Times. Removed the orphaned
attribution block from the bottom of LiveRidePanel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 08:27:45 -04:00
Josh Wright fd45309891 polish: clarify parks open badge; improve timezone display
Build and Deploy / Build & Push (push) Successful in 51s
- "parks open" → "parks open this week" for clarity
- Week calendar cells: stack hours above tz abbreviation (smaller,
  dimmer) instead of inline to avoid overflow in tight 130px columns
- Mobile park cards: tz abbreviation inline but smaller/dimmer (60% opacity)
- Month calendar: same two-line stacking in compact day cells

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 08:22:06 -04:00
Josh Wright c4c86a3796 fix: use park timezone for operating window check; show tz in hours
Build and Deploy / Build & Push (push) Successful in 4m22s
- isWithinOperatingWindow now accepts an IANA timezone and reads the
  current time in the park's local timezone via Intl.DateTimeFormat,
  fixing false positives when the server runs in UTC but parks store
  hours in local time (e.g. Pacific parks showing open at 6:50 AM EDT)
- Remove the 1-hour pre-open buffer so parks are not marked open before
  their doors actually open; retain the 1-hour post-close grace period
- Add getTimezoneAbbr() helper to derive the short tz label (EDT, PDT…)
- All hours labels now display with the local timezone abbreviation
  (e.g. "10am – 6pm PDT") in WeekCalendar, ParkCard, and ParkMonthCalendar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 08:12:19 -04:00
josh 7456ead430 feat: coaster filter toggle on homepage
Build and Deploy / Build & Push (push) Successful in 1m14s
- 🎢 Coasters button in nav bar (URL-driven: ?coasters=1)
- When active, swaps ride counts for coaster counts per park
- Label switches between "X rides operating" / "X coasters operating"
- Arrow key navigation preserves coaster filter state
- Only shown when coaster data exists in park-meta

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 21:03:00 -04:00
josh f1fec2355c polish: ride count copy, open indicator, and badge sizing
Build and Deploy / Build & Push (push) Successful in 1m5s
- "X rides open" → "X rides operating" (desktop + mobile)
- Green glowing dot next to park name when actively operating
- Hours text in calendar cells: larger (0.78rem) and bolder (600)
- Parks open badge: green tint when parks are open, larger text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:54:14 -04:00
josh fbf4337a83 feat: park page operating window check; always show ride total
Build and Deploy / Build & Push (push) Successful in 5m54s
- Extract isWithinOperatingWindow() to lib/env.ts (shared)
- Park detail page: always fetch Queue-Times, but force all rides
  closed when outside the ±1h operating window
- LiveRidePanel: always show closed ride count badge (not just when
  some rides are also open); label reads "X rides total" when none
  are open vs "X closed / down" when some are

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:43:33 -04:00
josh 8e969165b4 feat: show live open ride count in park name cell
- Fetch Queue-Times ride counts for parks open today (5min cache)
- Only shown within 1h before open to 1h after scheduled close
- Count displayed on the right of the park name/location cell (desktop)
  and below the open badge (mobile)
- Whole park cell is now a clickable link
- Hover warms the park cell background; no row-wide highlight

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:38:12 -04:00
josh a87f97ef53 fix: use local time with 3am cutover for today's date
new Date().toISOString() returns UTC, causing the calendar to advance
to the next day at 8pm EDT / 7pm EST. getTodayLocal() reads local
wall-clock time and rolls back one day before 3am so the calendar
stays on the current day through the night.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 20:15:52 -04:00
josh c1e42d6aa1 fix: truncate long ride names with tooltip instead of wrapping
Restores fixed-height ride rows with ellipsis truncation.
Adds title attribute so hovering shows the full name in a native tooltip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 15:25:01 -04:00
josh e9da6f3120 fix: robust coaster matching + dark carnival color scheme
Matching fixes:
- normalize() now strips all non-word/non-space chars via [^\w\s] instead of
  a hand-rolled list, catching !, curly apostrophe (U+2019), and any future edge cases
- Add isCoaster() helper with prefix matching (min 5 chars) to handle subtitle
  mismatches in either direction (e.g. "Apocalypse" vs "Apocalypse the Ride",
  "The New Revolution - Classic" vs "New Revolution")
- Fix top-level rides loop which still used coasterNames.has(normalize()) instead
  of isCoaster() — this was the recurring bug causing top-level rides to miss

UI:
- Dark neutral base (#111) replacing cold navy and muddy purple
- Neon accent palette: hot pink, electric green, vivid yellow, cyan
- Park page max-width 960→1280px, calendar cells 72→96px tall
- Scrollbar accent matches theme

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 15:22:59 -04:00
josh 9700d0bd9a feat: RCDB-backed roller coaster filter with fuzzy name matching
Build and Deploy / Build & Push (push) Successful in 2m54s
- Add lib/park-meta.ts to manage data/park-meta.json (rcdb_id + coaster lists)
- Add lib/scrapers/rcdb.ts to scrape operating coaster names from RCDB park pages
- discover.ts now seeds park-meta.json with skeleton entries for all parks
- scrape.ts now refreshes RCDB coaster lists (30-day staleness) for parks with rcdb_id set
- fetchLiveRides() accepts a coasterNames Set; isCoaster uses normalize() on both sides
  to handle trademark symbols, 'THE ' prefixes, and punctuation differences between
  Queue-Times and RCDB names — applies correctly to both land rides and top-level rides
- Commit park-meta.json so it ships in the Docker image (fresh volumes get it automatically)
- Update .gitignore / .dockerignore to exclude only *.db files, not all of data/
- Dockerfile copies park-meta.json into image before VOLUME declaration
- README: document coaster filter setup and correct staleness window (72h not 7d)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 13:49:49 -04:00
josh 819e716197 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>
2026-04-04 12:56:20 -04:00
josh da083c125c feat: automated nightly scraper + housekeeping
Build and Deploy / Build & Push (push) Successful in 3m11s
Scraper automation (docker-compose):
- Add scraper service to docker-compose.yml using the same image and
  shared park_data volume; overrides CMD to run scrape-schedule.sh
- scripts/scrape-schedule.sh: runs an initial scrape on container start,
  then sleeps until 3:00 AM (respects TZ env var) and repeats nightly;
  logs timestamps and next-run countdown; non-fatal on scrape errors

Staleness window: 7 days → 72 hours in lib/db.ts so data refreshes
more frequently with the automated schedule in place

Remove favicon: delete app/icon.tsx and public/logo.svg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:47:14 -04:00
josh d4c8046515 improve: redesign mobile card layout for usability
Build and Deploy / Build & Push (push) Successful in 3m24s
Replace the cramped 7-column day grid with a clean open-days list.
Each card now shows:
- Park name + "Open today" / "Closed today" badge in the header
- One row per open day (Today, Monday, Friday...) with full hours
- Today row highlighted in amber; passholder days labeled inline
- Whole card is a tap target linking to the park detail page

Also:
- Hide the legend below sm breakpoint (not needed on phones)
- Reduce horizontal padding to 16px on mobile (was 24px)
- Tighten MobileCardList vertical spacing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:33:02 -04:00
josh b4183507a8 chore: remove logo from header and README
Build and Deploy / Build & Push (push) Successful in 3m3s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:28:39 -04:00
josh 81ff6ea659 feat: roller coaster logo and working favicon
- public/logo.svg: amber coaster silhouette (lift hill + vertical loop +
  camelback) on transparent background; used in header and README
- app/icon.tsx: PNG favicon via Next.js ImageResponse (works in all
  browsers including Safari); renders simplified hill + loop on dark
  rounded-square background
- app/page.tsx: logo img added next to "Thoosie Calendar" title in header
- README.md: logo displayed at top of document
- Remove app/icon.svg (replaced by icon.tsx → PNG)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:26:45 -04:00
josh 8b1c8dcb29 chore: rebrand to Thoosie Calendar, add favicon
- Rename app title and header from "Six Flags Calendar" to "Thoosie Calendar"
- Update layout metadata title and description
- Update README title
- Add app/icon.svg favicon: amber T on dark background, picked up
  automatically by Next.js App Router

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:18:22 -04:00
josh e7b72ff95b feat: add live ride status via Queue-Times.com API
Build and Deploy / Build & Push (push) Successful in 2m51s
Park detail pages now show real-time ride open/closed status and wait
times sourced from Queue-Times.com (updates every 5 min) when a park
is operating. Falls back to the Six Flags schedule API for off-hours
or parks without a Queue-Times mapping.

- lib/queue-times-map.ts: maps all 24 park IDs to Queue-Times park IDs
- lib/scrapers/queuetimes.ts: fetches and parses queue_times.json with
  5-minute ISR cache; returns LiveRidesResult with isOpen + waitMinutes
- app/park/[id]/page.tsx: tries Queue-Times first; renders LiveRideList
  with Live badge and per-ride wait times; falls back to RideList for
  schedule data when live data is unavailable
- README: documents two-tier ride status approach

Attribution: Queue-Times.com (displayed in UI per their API terms)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:15:36 -04:00
josh b0bbb4d465 fix: resolve ESLint errors blocking Docker build
Build and Deploy / Build & Push (push) Successful in 2m45s
- Remove unused formatDate() from park page (replaced by formatShortDate)
- Remove unused date prop from DayCell component
- Change let to const for cellBorderRadius in ParkCard
- Change let to const for bg in ParkMonthCalendar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:55:17 -04:00
josh e48038c399 feat: UI redesign with park detail pages and ride status
Build and Deploy / Build & Push (push) Failing after 22s
Visual overhaul:
- Warmer color system with amber accent for Today, better text hierarchy
- Row hover highlighting, sticky column shadow on horizontal scroll
- Closed cells replaced with dot (·) instead of "Closed" text
- Regional grouping (Northeast/Southeast/Midwest/Texas & South/West)
- Two-row header with park count badge and WeekNav on separate lines
- Amber "Today" button in WeekNav when off current week
- Mobile card layout (< 1024px) with 7-day grid per park; table on desktop
- Skeleton loading state via app/loading.tsx

Park detail pages (/park/[id]):
- Month calendar view with ← → navigation via ?month= param
- Live ride status fetched from Six Flags API (cached 1h)
- Ride hours only shown when they differ from park operating hours
- Fallback to nearest upcoming open day when today is dropped by API,
  including cross-month fallback for end-of-month edge case

Data layer:
- Park type gains region field; parks.ts exports groupByRegion()
- db.ts gains getParkMonthData() for single-park month queries
- sixflags.ts gains scrapeRidesForDay() returning RidesFetchResult
  with rides, dataDate, isExact, and parkHoursLabel

Removed: CalendarGrid.tsx, MonthNav.tsx (dead code)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:53:06 -04:00
josh e2d90b3c5b feat: hide parks with no open days in the current week
Build and Deploy / Build & Push (push) Has been cancelled
Header subtitle now shows "X of 24 parks open" for context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:00:53 -04:00
josh 91e09b0548 feat: detect passholder preview days and filter plain buyouts
Build and Deploy / Build & Push (push) Successful in 3m9s
- Buyout days are now treated as closed unless they carry a Passholder
  Preview event, in which case they surface as a distinct purple cell
  in the UI showing "Passholder" + hours
- DB gains a special_type column (auto-migrated on next startup)
- scrape.ts threads specialType through to upsertDay
- debug.ts now shows events, isBuyout, isPassholderPreview, and
  specialType in the parsed result section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 10:53:05 -04:00
josh 548c7ae09e feat: initial project scaffold with CI/CD and Docker deployment
Next.js 15 + Tailwind CSS v4 week calendar showing Six Flags park hours.
Scrapes the internal CloudFront API, stores results in SQLite.
Includes Dockerfile (Debian/Playwright-compatible), docker-compose, and
Gitea Actions pipeline that builds and pushes to the container registry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:48:09 -04:00