Commit Graph

129 Commits

Author SHA1 Message Date
josh f87462385c docs: sync README and docs/ with current codebase
Build and Deploy / Lint, typecheck, test (push) Successful in 34s
Build and Deploy / Build & Push (push) Successful in 1m6s
Surfaces features that landed after the last big docs pass: per-ride
history pages, Fast Lane wait times, outage shading on the today chart,
Tier-5 wait-time sampler, production-hardening pieces (rate limiter,
structured logger, env validation, graceful shutdown), and the new
rides + ride_wait_samples tables. Also corrects the weather-delay rule
to match the "open" vs "closing" gate now in rides.ts.
2026-06-02 15:31:50 -04:00
josh 2e9cec0b56 fix: make Max wait readable in chart tooltip and legend
Build and Deploy / Lint, typecheck, test (push) Successful in 31s
Build and Deploy / Build & Push (push) Successful in 1m6s
The Max bar uses a dark fill with bright stroke for the outlined look,
but Recharts pulled tooltip/legend text color from the dark fill — making
the Max row unreadable against the dark tooltip background. Render the
tooltip via a custom content fn and recolor the legend's Max label so
both pick up the bright stroke color instead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 09:38:20 -04:00
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 301ed797ea fix: suppress park-page weather-delay flag during wind-down too
Build and Deploy / Lint, typecheck, test (push) Successful in 30s
Build and Deploy / Build & Push (push) Successful in 1m15s
Same root cause as 52f7efd — the /rides endpoint also used
isWithinOperatingWindow, which includes the post-close buffer. Switch
to getOperatingStatus and gate the badge on status === "open" so the
park page matches the calendar.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:54:05 -04:00
josh 52f7efd21a fix: suppress weather-delay flag during post-close wind-down
Build and Deploy / Lint, typecheck, test (push) Successful in 28s
Build and Deploy / Build & Push (push) Successful in 1m15s
Parks naturally wind down rides over the 1-hour buffer after their
scheduled close, so all-rides-closed in that window isn't a weather
delay — it's just closing time. Both the calendar UI badge and the
sampler's telemetry counter were misclassifying this.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:49:14 -04:00
josh e1657f07d7 fix: surface silent scraper failures and stop falsely claiming weather delay
Build and Deploy / Lint, typecheck, test (push) Successful in 33s
Build and Deploy / Build & Push (push) Successful in 1m4s
The homepage was flagging every park as weather delay because calendar.ts
collapsed "fetchLiveRides returned null" into the same openRides=0 bucket as
"all rides actually closed." Meanwhile every scraper (queuetimes, sixflags
operating-hours, sixflags wait-times) was swallowing non-OK responses and
exceptions silently, so logs gave no signal which upstream was failing or how.

Add a small scraperWarn helper that emits in the same shape as backend/log.ts
(without importing it — lib/scrapers is shared with the Next frontend). Use it
in all three scrapers to record HTTP status and error name+message before each
return null. Add parksSkipped to the tier-5 summary log so we can tell when the
openParks filter is rejecting everyone vs the fetcher silently failing.

Convert calendar.ts ridesCache to a discriminated union { kind: "ok" | "unknown" }.
Weather delay only fires on { kind: "ok", openRides: 0 }; unknown entries get
a 30s TTL so we recover quickly when upstream comes back and don't thunder-herd
in the meantime.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:28:25 -04:00
josh d43d8eba86 fix: keep outage labels in-frame + show Fast Lane line based on data
Build and Deploy / Lint, typecheck, test (push) Successful in 34s
Build and Deploy / Build & Push (push) Successful in 57s
Two refinements after the previous label change:

1. Outage labels rendered at position="top" were clipping against the
   chart's 8px top margin. Bumped to 24px so #N · Hh Mm sits above the
   band fully visible.

2. Fast Lane line was only rendered when the ride's metadata flag
   has_fast_lane was true. Some rides report Fast Lane waits without
   getting flagged, so we now also render the line whenever today's
   samples carry any non-null fastLaneMinutes — catches rides that are
   walk-on all day with a flat line at 0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 19:36:08 -04:00
josh b401f28fef fix: show outage duration in the chart label
Build and Deploy / Lint, typecheck, test (push) Successful in 33s
Build and Deploy / Build & Push (push) Successful in 1m0s
The outage marker now reads "#N · 1h 28m" instead of just "#N" so the
duration is visible at a glance without hovering. Positioned above the
band ("position: top") rather than inside it — when the label string is
wider than the band, Recharts' insideTop placement silently drops the
ReferenceArea rect; placing the label above sidesteps that.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 19:16:24 -04:00
josh e888261ed9 feat: prefer Six Flags regular waits, show Fast Lane at 0, mark outages on chart
Build and Deploy / Lint, typecheck, test (push) Successful in 35s
Build and Deploy / Build & Push (push) Successful in 1m8s
Three usability fixes after a day of using the ride detail page.

1. Six Flags is now the primary source for regular wait times. SF's
   /wait-times endpoint reports regular waits alongside Fast Lane, and it
   updates more promptly than Queue-Times around park-open. The sampler
   and the live /rides + ride-history routes all prefer SF's regularWaittime
   when its createdDateTime is non-empty; Queue-Times remains the fallback
   and the authoritative isOpen source.

2. The today chart's Fast Lane line now stays visible when its value is 0
   (walk-on). Y-axis bottom padding ensures the line sits clearly above the
   X-axis frame instead of being clipped against it. The tooltip shows
   "walk-on" instead of "0 min" for that case.

3. Outages are now explicit on the chart instead of just being gaps.
   computeOutages walks today's samples to find contiguous closed runs and
   numbers them chronologically. Each outage renders as a translucent pink
   ReferenceArea with a "#N" label. The custom tooltip detects when the
   cursor is over an outage span and shows "Outage #N — Hh Mm" (e.g.
   "Outage #2 — 1h 28m") in place of the wait/Fast Lane rows.

Includes a seed-test-samples.ts dev script for eyeballing the chart with
synthetic outage data.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 18:54:02 -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 7c88a3e568 fix: only sample wait times within each park's operating window
Build and Deploy / Build & Push (push) Successful in 1m5s
Queue-Times keeps reporting yesterday's last wait with isOpen=true overnight,
so the per-ride open check wasn't enough — the sampler was recording phantom
"open" samples between close and the next morning's first refresh, padding
both wait-time averages and uptime% with stale data.

Add isWithinOperatingWindow gate (same check the /rides route uses) so the
sampler only runs during the park's actual hours plus the 1-hour closing
buffer. Includes a one-off wipe script for the accumulated bad data.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 07:57:08 -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 aa46cc1b3d fix: run startup scrape only when database is empty
Build and Deploy / Build & Push (push) Successful in 3m37s
Restores the startup scrape removed in deb8e41, gated on
getParkDayCount() < 50 so warm restarts don't hammer the API.
Cold containers (e.g. after the volume mount fix) populate
immediately instead of waiting up to 24h for tier-4 cron.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 14:45:57 -04:00
josh 8027bfc5cf rename project from SixFlagsSuperCalendar to Thoosie Calendar
Build and Deploy / Build & Push (push) Successful in 1m39s
Update package names, Docker image tags, CI/CD workflow, and
documentation to reflect the public brand name. References to
the actual Six Flags theme park chain/API are intentionally kept.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 10:06:17 -04:00
josh f610883dea fix: allow Umami tracking script in Content Security Policy
Build and Deploy / Build & Push (push) Successful in 56s
The script was already in layout.tsx but CSP blocked both loading
and sending beacons to tracking.thewrightserver.net.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 09:55:20 -04:00
josh 6f893b909f docs: fix stale Docker paths and park region counts
Build and Deploy / Build & Push (push) Successful in 1m10s
Update /app/data → /app/backend/data across all docs to match the
volume mount fix from 3c91d9a. Add missing TZ env var to the web
container snippet in OPERATIONS.md. Correct Midwest (6→7) and
West & International (6→5) park counts in ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 09:37:02 -04:00
josh 3c91d9a453 fix: mount volume at correct path so database survives updates
Build and Deploy / Build & Push (push) Successful in 1m7s
WORKDIR is /app/backend so the DB lands at /app/backend/data/parks.db,
but the volume was mounted at /app/data — a different directory. The DB
lived in the container's ephemeral layer and was wiped on every pull.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 09:13:59 -04:00
josh deb8e4169b Revert "feat: run full scrape on backend container start"
Build and Deploy / Build & Push (push) Successful in 1m18s
This reverts commit db668c0787.
2026-04-23 23:14:22 -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 db668c0787 feat: run full scrape on backend container start
Build and Deploy / Build & Push (push) Successful in 1m44s
Fires scrapeToday() then scrapeFullYear() as a background task on
startup so fresh deploys have data immediately instead of waiting
for the first cron tick. Staleness check makes warm restarts a no-op.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 22:28:07 -04:00
josh a53e3ffa9f docs: add comprehensive project documentation
Build and Deploy / Build & Push (push) Successful in 1m31s
Add docs/ folder with architecture, operations, API reference, and
development guides covering system design, deployment, troubleshooting,
all backend endpoints, and contributor workflows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 22:15:02 -04:00
josh 4922dce8ac docs: update README for web + backend architecture
Build and Deploy / Build & Push (push) Successful in 34s
Remove references to Playwright discovery, RCDB scraping, scraper
container, and npm run scripts. Document the new two-container setup,
tiered scheduling, backend API endpoints, and local dev workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:54:45 -04:00
josh c5c9f750a3 chore: update Docker and CI for web + backend architecture
Build and Deploy / Build & Push (push) Successful in 2m10s
Replace scraper container with backend API container. Web image no
longer mounts a data volume or ships SQLite. Backend image runs Hono
server with node-cron scheduler, owns the database exclusively.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:49:17 -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 ccd35c4648 chore: remove old scraper scripts, replaced by backend scheduler
Delete scripts/scrape.ts and scripts/scrape-schedule.sh — their
functionality now lives in the backend's node-cron tiered scheduler
(backend/src/services/scheduler.ts + scraper.ts).

Remove scrape and scrape:force npm scripts from package.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:33:34 -04:00
josh 70b56158d4 feat: add Hono backend API server with tiered scheduler
Standalone Node.js backend that owns the SQLite database and serves
composed data via REST endpoints. Replaces the shell-scheduled scraper
with in-process node-cron tiered scheduling.

Backend structure:
- Hono HTTP server on port 3001 with CORS and request logging
- Singleton SQLite connection with WAL mode
- In-memory TTL cache for Queue-Times and fetchToday responses
- Comparison check on fetchToday (read-before-write, only upserts on change)

API endpoints:
- GET /api/calendar/week — week schedule + live ride counts for all parks
- GET /api/calendar/:parkId/month — month calendar for one park
- GET /api/parks — park list with metadata
- GET /api/parks/:id — single park detail
- GET /api/parks/:id/rides — live rides with Queue-Times/schedule fallback
- GET /api/status — health check, scrape stats
- POST /api/scrape/trigger — manual scrape (scope: today/month/upcoming/full)

Scheduler tiers:
- Tier 1: today — hourly (Mar-Dec)
- Tier 2: current month — every 6 hours
- Tier 3: upcoming — twice daily (3 AM + 3 PM)
- Tier 4: full year — daily at 3 AM

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:32:38 -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 757c2a8d4f chore: gitignore parks.db
Build and Deploy / Build & Push (push) Successful in 1m39s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:02:37 -04:00
josh c5dc01b6ff adds unami
Build and Deploy / Build & Push (push) Successful in 8m48s
2026-04-23 21:00:34 -04:00
josh 0009af751f Update README.md
Build and Deploy / Build & Push (push) Successful in 19s
2026-04-05 17:39:24 -04:00
josh 4063ded9ec Update README.md
Build and Deploy / Build & Push (push) Successful in 1m9s
2026-04-05 17:34:18 -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 08db97faa8 polish: center-align ride count and weather delay text in park column
Build and Deploy / Build & Push (push) Successful in 53s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 16:07:24 -04:00
Josh Wright 054c82529b polish: center-align weather delay text so it stacks neatly within its box
Build and Deploy / Build & Push (push) Successful in 1m8s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 16:02:23 -04:00
Josh Wright 8437cadee0 polish: weather delay text matches ride count style — blue, no emoji
Build and Deploy / Build & Push (push) Successful in 1m14s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:59:01 -04:00
Josh Wright b4af83b879 fix: weather delay text wraps within its box, no longer collides with park name
Build and Deploy / Build & Push (push) Successful in 54s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:56:12 -04:00
Josh Wright b1204c95cb fix: ride count stays right-aligned, wraps within its own box, never drops below park name
Build and Deploy / Build & Push (push) Successful in 52s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:52:48 -04:00
Josh Wright a5b98f93e6 fix: constrain ride count width so text wraps within its own box
Build and Deploy / Build & Push (push) Successful in 56s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:48:53 -04:00
Josh Wright b2ef342bf4 fix: ride count now wraps below park name instead of colliding
Removed flex:1 from left side so ride count has a real minimum width to
trigger wrapping. Added whiteSpace:nowrap so flexbox knows when to wrap it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:45:49 -04:00
Josh Wright e405170c8b fix: allow ride count to wrap below park name on narrow mobile cards
Build and Deploy / Build & Push (push) Successful in 1m1s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:34:36 -04:00
Josh Wright fd99f6f390 fix: allow ride count to wrap below park name on narrow columns
Build and Deploy / Build & Push (push) Successful in 58s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:30:56 -04:00
Josh Wright 4e6040a781 fix: add right padding to table scroll container to clear scrollbar
Build and Deploy / Build & Push (push) Successful in 57s
Padding on the parent main element doesn't work with overflow:auto — the
fix belongs on the scrollable div wrapping the table itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:27:18 -04:00
Josh Wright 7904475ddc polish: add right padding to main content to clear scrollbar
Build and Deploy / Build & Push (push) Successful in 55s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:23:45 -04:00
Josh Wright a84bbcac31 polish: taller week calendar cells with more padding around pills
Build and Deploy / Build & Push (push) Successful in 51s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:20:41 -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 c6c32a168b polish: more breathing room inside month calendar day pills
Build and Deploy / Build & Push (push) Successful in 1m4s
Increased pill vertical padding (3px→5px) and internal line gaps (1-2px→2-3px)
so the stacked hours/timezone text feels less cramped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:14:01 -04:00
Josh Wright cba8218fe8 feat: replace dot with left border line on park rows/cards
Build and Deploy / Build & Push (push) Successful in 51s
Open parks get a colored left border (green/amber/blue) instead of a dot.
Region headers lose their accent line; distinguished by "— REGION —" format
with higher-contrast text instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:07:42 -04:00