diff --git a/app/globals.css b/app/globals.css
index 5109702..55ac505 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -14,6 +14,11 @@
--color-open-text: #4ade80;
--color-open-hours: #bbf7d0;
+ --color-ph-bg: #1e0f2e;
+ --color-ph-border: #7e22ce;
+ --color-ph-hours: #e9d5ff;
+ --color-ph-label: #c084fc;
+
--color-today-bg: #0c1a3d;
--color-today-border: #2563eb;
--color-today-text: #93c5fd;
diff --git a/components/WeekCalendar.tsx b/components/WeekCalendar.tsx
index 0c0598a..52e45b1 100644
--- a/components/WeekCalendar.tsx
+++ b/components/WeekCalendar.tsx
@@ -177,6 +177,52 @@ export function WeekCalendar({ parks, weekDates, data }: WeekCalendarProps) {
);
}
+ // Passholder preview day
+ if (dayData.specialType === "passholder_preview") {
+ return (
+
+
+
+ Passholder
+
+
+ {dayData.hoursLabel}
+
+
+ |
+ );
+ }
+
// Open with confirmed hours
return (
> {
const rows = db
.prepare(
- `SELECT park_id, date, is_open, hours_label
+ `SELECT park_id, date, is_open, hours_label, special_type
FROM park_days
WHERE date >= ? AND date <= ?`
)
@@ -74,6 +84,7 @@ export function getDateRange(
date: string;
is_open: number;
hours_label: string | null;
+ special_type: string | null;
}[];
const result: Record> = {};
@@ -82,6 +93,7 @@ export function getDateRange(
result[row.park_id][row.date] = {
isOpen: row.is_open === 1,
hoursLabel: row.hours_label,
+ specialType: row.special_type,
};
}
return result;
diff --git a/lib/scrapers/sixflags.ts b/lib/scrapers/sixflags.ts
index 1922b02..a2bc1c4 100644
--- a/lib/scrapers/sixflags.ts
+++ b/lib/scrapers/sixflags.ts
@@ -34,6 +34,7 @@ export interface DayResult {
date: string; // YYYY-MM-DD
isOpen: boolean;
hoursLabel?: string;
+ specialType?: "passholder_preview";
}
function sleep(ms: number) {
@@ -49,6 +50,7 @@ function parseApiDate(d: string): string {
interface ApiOperatingItem {
timeFrom: string; // "10:30" 24h
timeTo: string; // "20:00" 24h
+ isBuyout?: boolean;
}
interface ApiOperating {
@@ -56,9 +58,14 @@ interface ApiOperating {
items: ApiOperatingItem[];
}
+interface ApiEvent {
+ extEventName: string;
+}
+
interface ApiDay {
date: string;
isParkClosed: boolean;
+ events?: ApiEvent[];
operatings?: ApiOperating[];
}
@@ -135,9 +142,18 @@ export async function scrapeMonth(
item?.timeFrom && item?.timeTo
? `${fmt24(item.timeFrom)} – ${fmt24(item.timeTo)}`
: undefined;
- // If the API says open but no hours are available, treat as closed
- const isOpen = !d.isParkClosed && hoursLabel !== undefined;
- return { date, isOpen, hoursLabel };
+
+ const isPassholderPreview = d.events?.some((e) =>
+ e.extEventName.toLowerCase().includes("passholder preview")
+ ) ?? false;
+
+ const isBuyout = item?.isBuyout ?? false;
+
+ // Buyout days are private events — treat as closed unless it's a passholder preview
+ const isOpen = !d.isParkClosed && hoursLabel !== undefined && (!isBuyout || isPassholderPreview);
+ const specialType: DayResult["specialType"] = isPassholderPreview ? "passholder_preview" : undefined;
+
+ return { date, isOpen, hoursLabel: isOpen ? hoursLabel : undefined, specialType };
});
}
diff --git a/scripts/debug.ts b/scripts/debug.ts
index 4e46445..5c45ed3 100644
--- a/scripts/debug.ts
+++ b/scripts/debug.ts
@@ -94,28 +94,37 @@ async function main() {
// ── Parsed result ──────────────────────────────────────────────────────────
const operating =
- dayData.operatings?.find((o) => o.operatingTypeName === "Park") ??
+ dayData.operatings?.find((o: { operatingTypeName: string }) => o.operatingTypeName === "Park") ??
dayData.operatings?.[0];
const item = operating?.items?.[0];
const hoursLabel =
item?.timeFrom && item?.timeTo
? `${fmt24(item.timeFrom)} – ${fmt24(item.timeTo)}`
: undefined;
- const isOpen = !dayData.isParkClosed && hoursLabel !== undefined;
+ const isBuyout = item?.isBuyout ?? false;
+ const isPassholderPreview = dayData.events?.some((e: { extEventName: string }) =>
+ e.extEventName.toLowerCase().includes("passholder preview")
+ ) ?? false;
+ const isOpen = !dayData.isParkClosed && hoursLabel !== undefined && (!isBuyout || isPassholderPreview);
+ const specialType = isPassholderPreview ? "passholder_preview" : null;
out("");
out("── Parsed result ────────────────────────────────────────────");
- out(` isParkClosed : ${dayData.isParkClosed}`);
- out(` operatings : ${dayData.operatings?.length ?? 0} entr${dayData.operatings?.length === 1 ? "y" : "ies"}`);
+ out(` isParkClosed : ${dayData.isParkClosed}`);
+ out(` events : ${dayData.events?.length ?? 0} (${dayData.events?.map((e: { extEventName: string }) => e.extEventName).join(", ") || "none"})`);
+ out(` operatings : ${dayData.operatings?.length ?? 0} entr${dayData.operatings?.length === 1 ? "y" : "ies"}`);
if (operating) {
- out(` selected : "${operating.operatingTypeName}" (${operating.items?.length ?? 0} item(s))`);
+ out(` selected : "${operating.operatingTypeName}" (${operating.items?.length ?? 0} item(s))`);
if (item) {
- out(` timeFrom : ${item.timeFrom} → ${fmt24(item.timeFrom)}`);
- out(` timeTo : ${item.timeTo} → ${fmt24(item.timeTo)}`);
+ out(` timeFrom : ${item.timeFrom} → ${fmt24(item.timeFrom)}`);
+ out(` timeTo : ${item.timeTo} → ${fmt24(item.timeTo)}`);
+ out(` isBuyout : ${isBuyout}`);
}
}
- out(` hoursLabel : ${hoursLabel ?? "(none)"}`);
- out(` isOpen : ${isOpen}`);
+ out(` isPassholderPreview : ${isPassholderPreview}`);
+ out(` hoursLabel : ${hoursLabel ?? "(none)"}`);
+ out(` isOpen : ${isOpen}`);
+ out(` specialType : ${specialType ?? "(none)"}`)
// ── Write to file ──────────────────────────────────────────────────────────
const debugDir = path.join(process.cwd(), "debug");
diff --git a/scripts/scrape.ts b/scripts/scrape.ts
index 571b849..604cee9 100644
--- a/scripts/scrape.ts
+++ b/scripts/scrape.ts
@@ -65,7 +65,7 @@ async function main() {
try {
const days = await scrapeMonth(apiId, YEAR, month);
db.transaction(() => {
- for (const d of days) upsertDay(db, park.id, d.date, d.isOpen, d.hoursLabel);
+ for (const d of days) upsertDay(db, park.id, d.date, d.isOpen, d.hoursLabel, d.specialType);
})();
openDays += days.filter((d) => d.isOpen).length;
fetched++;
|