All checks were successful
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>
188 lines
7.2 KiB
TypeScript
188 lines
7.2 KiB
TypeScript
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, Sun–Sat
|
||
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",
|
||
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, flexShrink: 0 }}>
|
||
{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>
|
||
);
|
||
}
|