Files
Josh Wright e405170c8b
All checks were successful
Build and Deploy / Build & Push (push) Successful in 1m1s
fix: allow ride count to wrap below park name on narrow mobile cards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:34:36 -04:00

189 lines
7.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import type { Park } from "@/lib/scrapers/types";
import type { DayData } from "@/lib/db";
import { getTimezoneAbbr } from "@/lib/env";
interface ParkCardProps {
park: Park;
weekDates: string[]; // 7 dates YYYY-MM-DD, SunSat
parkData: Record<string, DayData>;
today: string;
openRideCount?: number;
coastersOnly?: boolean;
isOpen?: boolean;
isClosing?: boolean;
isWeatherDelay?: boolean;
}
const DOW = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
export function ParkCard({ park, weekDates, parkData, today, openRideCount, coastersOnly, isOpen, isClosing, isWeatherDelay }: ParkCardProps) {
const openDays = weekDates.filter((d) => parkData[d]?.isOpen && parkData[d]?.hoursLabel);
const tzAbbr = getTimezoneAbbr(park.timezone);
const isOpenToday = openDays.includes(today);
return (
<Link
href={`/park/${park.id}`}
data-park={park.name.toLowerCase()}
style={{ textDecoration: "none", display: "block" }}
>
<div className="park-card" style={{
background: "var(--color-surface)",
border: "1px solid var(--color-border)",
borderLeft: isOpen
? `3px solid ${isWeatherDelay ? "var(--color-weather-border)" : isClosing ? "var(--color-closing-border)" : "var(--color-open-border)"}`
: "1px solid var(--color-border)",
borderRadius: 12,
overflow: "hidden",
}}>
{/* ── Card header ───────────────────────────────────────────────────── */}
<div style={{
padding: "14px 16px 12px",
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
flexWrap: "wrap",
gap: 12,
}}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{
fontSize: "0.95rem",
fontWeight: 600,
color: "var(--color-text)",
lineHeight: 1.2,
}}>
{park.name}
</div>
<div style={{
fontSize: "0.72rem",
color: "var(--color-text-muted)",
marginTop: 3,
}}>
{park.location.city}, {park.location.state}
</div>
</div>
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 5 }}>
{isOpenToday ? (
<div style={{
background: isWeatherDelay ? "var(--color-weather-bg)" : isClosing ? "var(--color-closing-bg)" : "var(--color-open-bg)",
border: `1px solid ${isWeatherDelay ? "var(--color-weather-border)" : isClosing ? "var(--color-closing-border)" : "var(--color-open-border)"}`,
borderRadius: 20,
padding: "4px 10px",
fontSize: "0.65rem",
fontWeight: 700,
color: isWeatherDelay ? "var(--color-weather-text)" : isClosing ? "var(--color-closing-text)" : "var(--color-open-text)",
whiteSpace: "nowrap",
letterSpacing: "0.03em",
}}>
{isWeatherDelay ? "⛈ Weather Delay" : isClosing ? "Closing" : "Open today"}
</div>
) : (
<div style={{
background: "transparent",
border: "1px solid var(--color-border)",
borderRadius: 20,
padding: "4px 10px",
fontSize: "0.65rem",
fontWeight: 500,
color: "var(--color-text-muted)",
whiteSpace: "nowrap",
}}>
Closed today
</div>
)}
{isOpenToday && isWeatherDelay && (
<div style={{
fontSize: "0.65rem",
color: "var(--color-weather-hours, #bfdbfe)",
fontWeight: 500,
textAlign: "right",
}}>
Weather Delay
</div>
)}
{isOpenToday && !isWeatherDelay && openRideCount !== undefined && (
<div style={{
fontSize: "0.65rem",
color: isClosing ? "var(--color-closing-hours)" : "var(--color-open-hours)",
fontWeight: 500,
textAlign: "right",
}}>
{openRideCount} {coastersOnly
? (openRideCount === 1 ? "coaster" : "coasters")
: (openRideCount === 1 ? "ride" : "rides")} operating
</div>
)}
</div>
</div>
{/* ── Open days list ────────────────────────────────────────────────── */}
{openDays.length > 0 && (
<div style={{ borderTop: "1px solid var(--color-border-subtle)" }}>
{openDays.map((date, i) => {
const dow = new Date(date + "T00:00:00").getDay();
const isToday = date === today;
const dayData = parkData[date];
const isPH = dayData.specialType === "passholder_preview";
const isLast = i === openDays.length - 1;
return (
<div
key={date}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "9px 16px",
background: isToday ? "var(--color-today-bg)" : "transparent",
borderBottom: isLast ? "none" : "1px solid var(--color-border-subtle)",
}}
>
<span style={{
fontSize: "0.82rem",
fontWeight: isToday ? 600 : 400,
color: isToday
? "var(--color-today-text)"
: "var(--color-text-secondary)",
}}>
{isToday ? "Today" : DOW[dow]}
</span>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
{isPH && (
<span style={{
fontSize: "0.65rem",
fontWeight: 700,
color: "var(--color-ph-label)",
letterSpacing: "0.04em",
textTransform: "uppercase",
}}>
Passholder
</span>
)}
<span style={{
fontSize: "0.82rem",
fontWeight: isToday ? 600 : 500,
color: isPH
? "var(--color-ph-hours)"
: isToday
? "var(--color-today-text)"
: "var(--color-open-hours)",
}}>
{dayData.hoursLabel}{" "}
<span style={{ fontSize: "0.68rem", fontWeight: 400, color: "var(--color-text-dim)" }}>
{tzAbbr}
</span>
</span>
</div>
</div>
);
})}
</div>
)}
</div>
</Link>
);
}