← back to Malden House
schedule.js
132 lines
// schedule.js — builds the Games calendar page from LA28_VENUES
// Uses window.LA28_VENUES exposed by signup.js (or reads directly if loaded first).
(function () {
// signup.js currently runs a bunch of side-effects when loaded in-browser.
// We only need LA28_VENUES, which is defined at module scope. To be safe,
// we hard-mirror the dataset here; keep in sync with signup.js.
const VENUES = [
{ name: "SoFi Stadium · Inglewood", short: "SoFi", headline: "Opening Ceremony", dates: "Jul 14, 2028" },
{ name: "Rose Bowl · Pasadena", short: "Rose Bowl", headline: "Soccer (Football)", dates: "Jul 11 – Jul 29, 2028" },
{ name: "Long Beach Convention Center", short: "Long Beach Conv.", headline: "Handball", dates: "Jul 15 – Jul 30, 2028" },
{ name: "Long Beach · Alamitos Beach", short: "Alamitos Beach", headline: "Beach Volleyball", dates: "Jul 15 – Jul 26, 2028" },
{ name: "Santa Monica Pier", short: "Santa Monica", headline: "Triathlon · Marathon Swim", dates: "Jul 15 – Jul 19, 2028" },
{ name: "Exposition Park Aquatic Center", short: "Expo Aquatic", headline: "Swimming · Diving", dates: "Jul 15 – Jul 21, 2028" },
{ name: "UCLA · Pauley Pavilion", short: "Pauley Pavilion", headline: "Gymnastics (Artistic)", dates: "Jul 15 – Jul 23, 2028" },
{ name: "Dodger Stadium", short: "Dodger Stadium", headline: "Baseball / Softball", dates: "Jul 17 – Jul 28, 2028" },
{ name: "Dignity Health Sports Park", short: "Dignity Health", headline: "Track Cycling · Tennis", dates: "Jul 17 – Jul 22, 2028" },
{ name: "Sepulveda Basin Valley Complex", short: "Valley Complex", headline: "3×3 Basketball · BMX · Skate · Pentathlon", dates: "Jul 18 – Jul 28, 2028" },
{ name: "LA Memorial Coliseum", short: "LA Coliseum", headline: "Track & Field", dates: "Jul 21 – Jul 30, 2028" },
{ name: "Crypto.com Arena · DTLA", short: "Crypto.com Arena", headline: "Gymnastics Finals", dates: "Jul 21 – Jul 23, 2028" },
{ name: "Peacock Theater · LA Live", short: "Peacock Theater", headline: "Weightlifting", dates: "Jul 23 – Jul 29, 2028" },
{ name: "Intuit Dome · Inglewood", short: "Intuit Dome", headline: "Basketball", dates: "Jul 24 – Jul 30, 2028" },
{ name: "Riviera Country Club", short: "Riviera CC", headline: "Golf", dates: "Jul 25 – Jul 30, 2028" },
// Closing ceremony — back at SoFi
{ name: "SoFi Stadium · Inglewood", short: "SoFi", headline: "Closing Ceremony", dates: "Jul 30, 2028" },
];
const MONTHS = { Jan:0, Feb:1, Mar:2, Apr:3, May:4, Jun:5, Jul:6, Aug:7, Sep:8, Oct:9, Nov:10, Dec:11 };
const MNAMES = Object.keys(MONTHS);
const DOW = ["SUN","MON","TUE","WED","THU","FRI","SAT"];
function expandDates(str) {
const clean = String(str || "").replace(/[–—]/g, "-").trim();
let m = clean.match(/^(\w{3})\s+(\d{1,2})\s*-\s*(\w{3})\s+(\d{1,2}),\s*(\d{4})$/);
if (m) {
const [, m1, d1, m2, d2, y] = m;
const start = new Date(+y, MONTHS[m1], +d1);
const end = new Date(+y, MONTHS[m2], +d2);
const out = [];
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
out.push({ key: d.toISOString().slice(0,10), date: new Date(d) });
}
return out;
}
m = clean.match(/^(\w{3})\s+(\d{1,2}),\s*(\d{4})$/);
if (m) {
const d = new Date(+m[3], MONTHS[m[1]], +m[2]);
return [{ key: d.toISOString().slice(0,10), date: d }];
}
return [];
}
// ------------ Calendar render ------------
function renderCalendar(filterVenue = "") {
const byDay = new Map();
const dayKeys = [];
const d0 = new Date(2028, 6, 11); // Jul 11 (soccer starts)
const dN = new Date(2028, 6, 30); // Jul 30 (closing)
for (let d = new Date(d0); d <= dN; d.setDate(d.getDate() + 1)) {
const k = d.toISOString().slice(0, 10);
byDay.set(k, []);
dayKeys.push(k);
}
VENUES.forEach(v => {
if (filterVenue && v.short !== filterVenue) return;
expandDates(v.dates).forEach(ex => {
if (byDay.has(ex.key)) byDay.get(ex.key).push(v);
});
});
const cal = document.getElementById("sched-calendar");
const months = ["Jul 2028"];
cal.innerHTML = dayKeys.map(k => {
const d = new Date(k);
const events = byDay.get(k);
const isOpen = events.some(e => /Opening/i.test(e.headline));
const isClose = events.some(e => /Closing/i.test(e.headline));
const headerTone = isOpen ? "open" : isClose ? "close" : "";
return `
<article class="day ${events.length ? "" : "empty"} ${headerTone}">
<header>
<span class="day-dow">${DOW[d.getDay()]}</span>
<span class="day-num">${d.getDate()}</span>
<span class="day-mon">JUL</span>
<span class="day-count">${events.length} event${events.length === 1 ? "" : "s"}</span>
</header>
<ul>
${events.map(e => `
<li class="ev ${isOpen && /Opening/i.test(e.headline) ? "ev-open" : ""} ${isClose && /Closing/i.test(e.headline) ? "ev-close" : ""}">
<b>${e.headline}</b>
<span>${e.short}</span>
</li>`).join("")}
${events.length === 0 ? `<li class="ev-none">Rest day — no competition</li>` : ""}
</ul>
</article>`;
}).join("");
}
// ------------ Venue filter chips ------------
function renderChips(active = "") {
const chips = document.getElementById("filter-chips");
const all = [...new Set(VENUES.map(v => v.short))];
chips.innerHTML = `
<button class="vchip ${active === "" ? "on" : ""}" data-v="">All venues</button>
${all.map(v => `<button class="vchip ${active === v ? "on" : ""}" data-v="${v}">${v}</button>`).join("")}
`;
chips.querySelectorAll(".vchip").forEach(b => b.addEventListener("click", () => {
const v = b.getAttribute("data-v");
renderChips(v);
renderCalendar(v);
}));
}
// ------------ Alphabetical grid ------------
function renderSportGrid() {
const sorted = [...VENUES].sort((a, b) => a.headline.localeCompare(b.headline));
document.getElementById("sport-grid").innerHTML = sorted.map(v => `
<article class="sport-card">
<div class="sport-head">${v.headline}</div>
<div class="sport-venue">${v.name}</div>
<div class="sport-dates">${v.dates}</div>
</article>
`).join("");
}
document.addEventListener("DOMContentLoaded", () => {
renderChips();
renderCalendar();
renderSportGrid();
});
})();