┌─ ASTRO ────────────────────────────────────────────────────────────────────┐
│ --- │
│ import PageLayout from "../layouts/PageLayout.astro"; │
│ import PlayerSearch from "../components/PlayerSearch/PlayerSearch.astro"; │
│ import LeaderboardTypeNav from "../components/Leaderboard/LeaderboardTypeNav/Leade │
│ rboardTypeNav.astro"; │
│ import RecentRuns from "../components/Home/RecentRuns/RecentRuns.astro"; │
│ import DungeonRecordCard from "../components/Home/DungeonRecordCard/DungeonRecordC │
│ ard.astro"; │
│ import TopPlayersList from "../components/Home/TopPlayersList/TopPlayersList.astro │
│ "; │
│ import Carousel from "../components/Home/Carousel/Carousel.astro"; │
│ │
│ export const prerender = false; │
│ │
│ let home: any = null; │
│ try { │
│ const response = await fetch(`${Astro.url.origin}/api/home.json`); │
│ if (response.ok) { │
│ home = await response.json(); │
│ } │
│ } catch (error) { │
│ console.error("[Home] Failed to load home.json:", error); │
│ } │
│ │
│ // Seasons sorted descending so the latest is the default tab. │
│ const seasonKeys = home │
│ ? Object.keys(home.seasons).sort((a, b) => parseInt(b) - parseInt(a)) │
│ : []; │
│ const defaultSeasonKey = seasonKeys[0]; │
│ │
│ // render every season server-side; client toggles visibility for instant switch │
│ const seasons = seasonKeys.map((key) => { │
│ const s = home.seasons[key]; │
│ return { │
│ key, │
│ label: s.season_name || `Season ${key}`, │
│ data: s, │
│ dungeonEntries: Object.entries( │
│ s.top_runs_per_dungeon as Record<string, any[]>, │
│ ) │
│ .filter(([, runs]) => runs && runs.length > 0) │
│ .sort( │
│ ([, a], [, b]) => (a[0]?.dungeon_id ?? 0) - (b[0]?.dungeon_id ?? 0), │
│ ), │
│ }; │
│ }); │
│ │
│ const recentRuns = home?.recent_top_runs ?? []; │
│ const generatedAt = home?.generated_at ? new Date(home.generated_at) : null; │
│ --- │
│ │
│ <PageLayout │
│ title="WoW Mists of Pandaria Challenge Mode Leaderboards" │
│ description="Top challenge mode times, recent record-setting runs, and the world │
│ 's best players for World of Warcraft: Mists of Pandaria Classic." │
│ > │
│ <div class="home"> │
│ { │
│ !home && ( │
│ <div class="home__error"> │
│ <h2>Couldn't load home data</h2> │
│ <p> │
│ The home.json file is missing or unreachable. Try regenerating it │
│ via <code>ookstats generate home</code>. │
│ </p> │
│ </div> │
│ ) │
│ } │
│ │
│ { │
│ home && ( │
│ <> │
│ <header class="home__hero"> │
│ <h1>Challenge Mode Leaderboards</h1> │
│ {generatedAt && ( │
│ <span class="home__updated"> │
│ Data updated{" "} │
│ {generatedAt.toLocaleString("en-US", { │
│ month: "short", │
│ day: "numeric", │
│ hour: "2-digit", │
│ minute: "2-digit", │
│ })} │
│ </span> │
│ )} │
│ </header> │
│ │
│ <PlayerSearch /> │
│ │
│ <LeaderboardTypeNav │
│ currentTab="home" │
│ currentSeason={parseInt(defaultSeasonKey)} │
│ /> │
│ │
│ {seasons.length > 1 && ( │
│ <nav class="home__season-tabs" role="tablist" aria-label="Season"> │
│ {seasons.map((s) => ( │
│ <button │
│ type="button" │
│ class:list={[ │
│ "home__season-tab", │
│ { active: s.key === defaultSeasonKey }, │
│ ]} │
│ role="tab" │
│ aria-selected={s.key === defaultSeasonKey ? "true" : "false"} │
│ data-season-key={s.key} │
│ > │
│ {s.label} │
│ </button> │
│ ))} │
│ </nav> │
│ )} │
│ │
│ {seasons.map((s) => ( │
│ <div │
│ class:list={[ │
│ "home__season-panel", │
│ { hidden: s.key !== defaultSeasonKey }, │
│ ]} │
│ data-season-panel={s.key} │
│ aria-hidden={s.key !== defaultSeasonKey ? "true" : "false"} │
│ > │
│ <section class="home__section"> │
│ <header class="home__section-header"> │
│ <h2>Top Players - {s.label}</h2> │
│ </header> │
│ <Carousel │
│ ariaLabel={`Top players by region - ${s.label}`} │
│ class="home__players-carousel" │
│ perView={3} │
│ perViewMobile={1} │
│ > │
│ <TopPlayersList │
│ title="Global" │
│ scope="global" │
│ players={s.data.top_players.global} │
│ viewAllHref={`/challenge-mode/season${s.key}/players/global`} │
│ /> │
│ <TopPlayersList │
│ title="US" │
│ scope="regional" │
│ players={s.data.top_players.us} │
│ viewAllHref={`/challenge-mode/season${s.key}/players/us`} │
│ /> │
│ <TopPlayersList │
│ title="EU" │
│ scope="regional" │
│ players={s.data.top_players.eu} │
│ viewAllHref={`/challenge-mode/season${s.key}/players/eu`} │
│ /> │
│ <TopPlayersList │
│ title="KR" │
│ scope="regional" │
│ players={s.data.top_players.kr} │
│ viewAllHref={`/challenge-mode/season${s.key}/players/kr`} │
│ /> │
│ <TopPlayersList │
│ title="TW" │
│ scope="regional" │
│ players={s.data.top_players.tw} │
│ viewAllHref={`/challenge-mode/season${s.key}/players/tw`} │
│ /> │
│ </Carousel> │
│ </section> │
│ │
│ <section class="home__section"> │
│ <header class="home__section-header"> │
│ <h2>Dungeon Records - {s.label}</h2> │
│ </header> │
│ <Carousel │
│ ariaLabel={`Dungeon records - ${s.label}`} │
│ class="home__dungeons-carousel" │
│ perView={2} │
│ perViewMobile={1} │
│ > │
│ {s.dungeonEntries.map(([slug, runs]) => ( │
│ <DungeonRecordCard │
│ dungeonName={runs[0].dungeon_name} │
│ dungeonSlug={slug} │
│ runs={runs} │
│ seasonId={parseInt(s.key)} │
│ /> │
│ ))} │
│ </Carousel> │
│ </section> │
│ </div> │
│ ))} │
│ │
│ <section class="home__section"> │
│ <RecentRuns runs={recentRuns} /> │
│ </section> │
│ </> │
│ ) │
│ } │
│ </div> │
│ </PageLayout> │
│ │
│ <script> │
│ // toggle which server-rendered season-panel is visible │
│ function wireSeasonTabs() { │
│ const tabs = │
│ document.querySelectorAll<HTMLButtonElement>(".home__season-tab"); │
│ const panels = document.querySelectorAll<HTMLElement>( │
│ ".home__season-panel", │
│ ); │
│ if (tabs.length === 0) return; │
│ tabs.forEach((tab) => { │
│ tab.addEventListener("click", () => { │
│ const key = tab.dataset.seasonKey; │
│ if (!key) return; │
│ tabs.forEach((t) => { │
│ const active = t === tab; │
│ t.classList.toggle("active", active); │
│ t.setAttribute("aria-selected", active ? "true" : "false"); │
│ }); │
│ panels.forEach((p) => { │
│ const isActive = p.dataset.seasonPanel === key; │
│ p.classList.toggle("hidden", !isActive); │
│ p.setAttribute("aria-hidden", isActive ? "false" : "true"); │
│ }); │
│ // carousel arrows read scrollWidth, which is 0 while hidden; │
│ // resize event makes them re-evaluate after reveal │
│ window.dispatchEvent(new Event("resize")); │
│ }); │
│ }); │
│ } │
│ wireSeasonTabs(); │
│ document.addEventListener("astro:page-load", wireSeasonTabs); │
│ </script> │
│ │
│ <style lang="scss"> │
│ @use "../styles/core/tokens" as *; │
│ │
│ // matches .leaderboard-page wrapper on /challenge-mode/* for visual cohesion │
│ .home { │
│ max-width: 1200px; │
│ margin: 0 auto; │
│ padding: 20px; │
│ } │
│ │
│ .home__error { │
│ background: var(--bg-secondary); │
│ border: 1px solid var(--border-color); │
│ border-radius: $radius-lg; │
│ padding: $spacing-2xl; │
│ text-align: center; │
│ color: var(--text-secondary); │
│ code { │
│ background: var(--bg-primary); │
│ padding: 2px 6px; │
│ border-radius: $radius-sm; │
│ color: var(--highlight-color); │
│ } │
│ } │
│ │
│ // mirrors LeaderboardHeader spacing/typography │
│ .home__hero { │
│ display: flex; │
│ align-items: baseline; │
│ justify-content: space-between; │
│ gap: $spacing-lg; │
│ flex-wrap: wrap; │
│ margin: 8px 0 16px 0; │
│ │
│ h1 { │
│ margin: 0; │
│ font-size: 2em; │
│ color: var(--text-primary); │
│ font-weight: $font-weight-bold; │
│ } │
│ } │
│ │
│ .home__updated { │
│ color: var(--text-tertiary); │
│ font-size: $font-size-sm; │
│ } │
│ │
│ // pill-style tabs matching the stats page region tabs │
│ .home__season-tabs { │
│ display: flex; │
│ flex-wrap: wrap; │
│ gap: $spacing-xs; │
│ margin-bottom: $spacing-lg; │
│ } │
│ │
│ .home__season-tab { │
│ background: var(--bg-secondary); │
│ border: 1px solid var(--border-color); │
│ color: var(--text-secondary); │
│ padding: 6px 14px; │
│ cursor: pointer; │
│ font-size: $font-size-sm; │
│ font-weight: $font-weight-semibold; │
│ border-radius: $radius-md; │
│ transition: │
│ color 0.15s ease, │
│ background 0.15s ease, │
│ border-color 0.15s ease; │
│ │
│ &:hover { │
│ color: var(--text-primary); │
│ border-color: var(--highlight-color); │
│ } │
│ │
│ &.active { │
│ color: var(--bg-primary); │
│ background: var(--highlight-color); │
│ border-color: var(--highlight-color); │
│ } │
│ } │
│ │
│ .home__season-panel { │
│ display: flex; │
│ flex-direction: column; │
│ gap: $spacing-2xl; │
│ │
│ &.hidden { │
│ display: none; │
│ } │
│ } │
│ │
│ .home__section { │
│ display: flex; │
│ flex-direction: column; │
│ gap: $spacing-lg; │
│ │
│ & + &, │
│ // The Recent Runs section sits as a sibling of the season-panel wrapper, │
│ // not another `.home__section`, so the adjacency selector above wouldn't │
│ // reach it without this second rule. Keeps section rhythm consistent. │
│ .home__season-panel + & { │
│ margin-top: $spacing-2xl; │
│ } │
│ } │
│ │
│ .home__section-header { │
│ h2 { │
│ margin: 0; │
│ font-size: $font-size-2xl; │
│ color: var(--text-primary); │
│ font-weight: $font-weight-semibold; │
│ } │
│ } │
│ │
│ // Carousel handles per-card sizing via its `perView` prop (see Carousel.scss). │
│ // No per-card overrides needed here. │
│ │
│ // mobile overrides at end of stylesheet to win the cascade │
│ @media (max-width: 768px) { │
│ .home { │
│ padding: 15px; │
│ } │
│ │
│ // Mirror LeaderboardHeader's mobile font-size drop (~1.5em). │
│ .home__hero h1 { │
│ font-size: 1.5em; │
│ } │
│ } │
│ </style> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ASTRO ──────────────────────────────┐
│ --- │
│ import PageLayout from "../layouts/PageLayou │
│ t.astro"; │
│ import PlayerSearch from "../components/Play │
│ erSearch/PlayerSearch.astro"; │
│ import LeaderboardTypeNav from "../component │
│ s/Leaderboard/LeaderboardTypeNav/Leaderboard │
│ TypeNav.astro"; │
│ import RecentRuns from "../components/Home/R │
│ ecentRuns/RecentRuns.astro"; │
│ import DungeonRecordCard from "../components │
│ /Home/DungeonRecordCard/DungeonRecordCard.as │
│ tro"; │
│ import TopPlayersList from "../components/Ho │
│ me/TopPlayersList/TopPlayersList.astro"; │
│ import Carousel from "../components/Home/Car │
│ ousel/Carousel.astro"; │
│ │
│ export const prerender = false; │
│ │
│ let home: any = null; │
│ try { │
│ const response = await fetch(`${Astro.url. │
│ origin}/api/home.json`); │
│ if (response.ok) { │
│ home = await response.json(); │
│ } │
│ } catch (error) { │
│ console.error("[Home] Failed to load home. │
│ json:", error); │
│ } │
│ │
│ // Seasons sorted descending so the latest i │
│ s the default tab. │
│ const seasonKeys = home │
│ ? Object.keys(home.seasons).sort((a, b) => │
│ parseInt(b) - parseInt(a)) │
│ : []; │
│ const defaultSeasonKey = seasonKeys[0]; │
│ │
│ // render every season server-side; client t │
│ oggles visibility for instant switch │
│ const seasons = seasonKeys.map((key) => { │
│ const s = home.seasons[key]; │
│ return { │
│ key, │
│ label: s.season_name || `Season ${key}`, │
│ data: s, │
│ dungeonEntries: Object.entries( │
│ s.top_runs_per_dungeon as Record<strin │
│ g, any[]>, │
│ ) │
│ .filter(([, runs]) => runs && runs.len │
│ gth > 0) │
│ .sort( │
│ ([, a], [, b]) => (a[0]?.dungeon_id │
│ ?? 0) - (b[0]?.dungeon_id ?? 0), │
│ ), │
│ }; │
│ }); │
│ │
│ const recentRuns = home?.recent_top_runs ?? │
│ []; │
│ const generatedAt = home?.generated_at ? new │
│ Date(home.generated_at) : null; │
│ --- │
│ │
│ <PageLayout │
│ title="WoW Mists of Pandaria Challenge Mod │
│ e Leaderboards" │
│ description="Top challenge mode times, rec │
│ ent record-setting runs, and the world's bes │
│ t players for World of Warcraft: Mists of Pa │
│ ndaria Classic." │
│ > │
│ <div class="home"> │
│ { │
│ !home && ( │
│ <div class="home__error"> │
│ <h2>Couldn't load home data</h2> │
│ <p> │
│ The home.json file is missing or │
│ unreachable. Try regenerating it │
│ via <code>ookstats generate home │
│ </code>. │
│ </p> │
│ </div> │
│ ) │
│ } │
│ │
│ { │
│ home && ( │
│ <> │
│ <header class="home__hero"> │
│ <h1>Challenge Mode Leaderboards< │
│ /h1> │
│ {generatedAt && ( │
│ <span class="home__updated"> │
│ Data updated{" "} │
│ {generatedAt.toLocaleString( │
│ "en-US", { │
│ month: "short", │
│ day: "numeric", │
│ hour: "2-digit", │
│ minute: "2-digit", │
│ })} │
│ </span> │
│ )} │
│ </header> │
│ │
│ <PlayerSearch /> │
│ │
│ <LeaderboardTypeNav │
│ currentTab="home" │
│ currentSeason={parseInt(defaultS │
│ easonKey)} │
│ /> │
│ │
│ {seasons.length > 1 && ( │
│ <nav class="home__season-tabs" r │
│ ole="tablist" aria-label="Season"> │
│ {seasons.map((s) => ( │
│ <button │
│ type="button" │
│ class:list={[ │
│ "home__season-tab", │
│ { active: s.key === defa │
│ ultSeasonKey }, │
│ ]} │
│ role="tab" │
│ aria-selected={s.key === d │
│ efaultSeasonKey ? "true" : "false"} │
│ data-season-key={s.key} │
│ > │
│ {s.label} │
│ </button> │
│ ))} │
│ </nav> │
│ )} │
│ │
│ {seasons.map((s) => ( │
│ <div │
│ class:list={[ │
│ "home__season-panel", │
│ { hidden: s.key !== defaultS │
│ easonKey }, │
│ ]} │
│ data-season-panel={s.key} │
│ aria-hidden={s.key !== default │
│ SeasonKey ? "true" : "false"} │
│ > │
│ <section class="home__section" │
│ > │
│ <header class="home__section │
│ -header"> │
│ <h2>Top Players - {s.label │
│ }</h2> │
│ </header> │
│ <Carousel │
│ ariaLabel={`Top players by │
│ region - ${s.label}`} │
│ class="home__players-carou │
│ sel" │
│ perView={3} │
│ perViewMobile={1} │
│ > │
│ <TopPlayersList │
│ title="Global" │
│ scope="global" │
│ players={s.data.top_play │
│ ers.global} │
│ viewAllHref={`/challenge │
│ -mode/season${s.key}/players/global`} │
│ /> │
│ <TopPlayersList │
│ title="US" │
│ scope="regional" │
│ players={s.data.top_play │
│ ers.us} │
│ viewAllHref={`/challenge │
│ -mode/season${s.key}/players/us`} │
│ /> │
│ <TopPlayersList │
│ title="EU" │
│ scope="regional" │
│ players={s.data.top_play │
│ ers.eu} │
│ viewAllHref={`/challenge │
│ -mode/season${s.key}/players/eu`} │
│ /> │
│ <TopPlayersList │
│ title="KR" │
│ scope="regional" │
│ players={s.data.top_play │
│ ers.kr} │
│ viewAllHref={`/challenge │
│ -mode/season${s.key}/players/kr`} │
│ /> │
│ <TopPlayersList │
│ title="TW" │
│ scope="regional" │
│ players={s.data.top_play │
│ ers.tw} │
│ viewAllHref={`/challenge │
│ -mode/season${s.key}/players/tw`} │
│ /> │
│ </Carousel> │
│ </section> │
│ │
│ <section class="home__section" │
│ > │
│ <header class="home__section │
│ -header"> │
│ <h2>Dungeon Records - {s.l │
│ abel}</h2> │
│ </header> │
│ <Carousel │
│ ariaLabel={`Dungeon record │
│ s - ${s.label}`} │
│ class="home__dungeons-caro │
│ usel" │
│ perView={2} │
│ perViewMobile={1} │
│ > │
│ {s.dungeonEntries.map(([sl │
│ ug, runs]) => ( │
│ <DungeonRecordCard │
│ dungeonName={runs[0].d │
│ ungeon_name} │
│ dungeonSlug={slug} │
│ runs={runs} │
│ seasonId={parseInt(s.k │
│ ey)} │
│ /> │
│ ))} │
│ </Carousel> │
│ </section> │
│ </div> │
│ ))} │
│ │
│ <section class="home__section"> │
│ <RecentRuns runs={recentRuns} /> │
│ </section> │
│ </> │
│ ) │
│ } │
│ </div> │
│ </PageLayout> │
│ │
│ <script> │
│ // toggle which server-rendered season-pan │
│ el is visible │
│ function wireSeasonTabs() { │
│ const tabs = │
│ document.querySelectorAll<HTMLButtonEl │
│ ement>(".home__season-tab"); │
│ const panels = document.querySelectorAll │
│ <HTMLElement>( │
│ ".home__season-panel", │
│ ); │
│ if (tabs.length === 0) return; │
│ tabs.forEach((tab) => { │
│ tab.addEventListener("click", () => { │
│ const key = tab.dataset.seasonKey; │
│ if (!key) return; │
│ tabs.forEach((t) => { │
│ const active = t === tab; │
│ t.classList.toggle("active", activ │
│ e); │
│ t.setAttribute("aria-selected", ac │
│ tive ? "true" : "false"); │
│ }); │
│ panels.forEach((p) => { │
│ const isActive = p.dataset.seasonP │
│ anel === key; │
│ p.classList.toggle("hidden", !isAc │
│ tive); │
│ p.setAttribute("aria-hidden", isAc │
│ tive ? "false" : "true"); │
│ }); │
│ // carousel arrows read scrollWidth, │
│ which is 0 while hidden; │
│ // resize event makes them re-evalua │
│ te after reveal │
│ window.dispatchEvent(new Event("resi │
│ ze")); │
│ }); │
│ }); │
│ } │
│ wireSeasonTabs(); │
│ document.addEventListener("astro:page-load │
│ ", wireSeasonTabs); │
│ </script> │
│ │
│ <style lang="scss"> │
│ @use "../styles/core/tokens" as *; │
│ │
│ // matches .leaderboard-page wrapper on /c │
│ hallenge-mode/* for visual cohesion │
│ .home { │
│ max-width: 1200px; │
│ margin: 0 auto; │
│ padding: 20px; │
│ } │
│ │
│ .home__error { │
│ background: var(--bg-secondary); │
│ border: 1px solid var(--border-color); │
│ border-radius: $radius-lg; │
│ padding: $spacing-2xl; │
│ text-align: center; │
│ color: var(--text-secondary); │
│ code { │
│ background: var(--bg-primary); │
│ padding: 2px 6px; │
│ border-radius: $radius-sm; │
│ color: var(--highlight-color); │
│ } │
│ } │
│ │
│ // mirrors LeaderboardHeader spacing/typog │
│ raphy │
│ .home__hero { │
│ display: flex; │
│ align-items: baseline; │
│ justify-content: space-between; │
│ gap: $spacing-lg; │
│ flex-wrap: wrap; │
│ margin: 8px 0 16px 0; │
│ │
│ h1 { │
│ margin: 0; │
│ font-size: 2em; │
│ color: var(--text-primary); │
│ font-weight: $font-weight-bold; │
│ } │
│ } │
│ │
│ .home__updated { │
│ color: var(--text-tertiary); │
│ font-size: $font-size-sm; │
│ } │
│ │
│ // pill-style tabs matching the stats page │
│ region tabs │
│ .home__season-tabs { │
│ display: flex; │
│ flex-wrap: wrap; │
│ gap: $spacing-xs; │
│ margin-bottom: $spacing-lg; │
│ } │
│ │
│ .home__season-tab { │
│ background: var(--bg-secondary); │
│ border: 1px solid var(--border-color); │
│ color: var(--text-secondary); │
│ padding: 6px 14px; │
│ cursor: pointer; │
│ font-size: $font-size-sm; │
│ font-weight: $font-weight-semibold; │
│ border-radius: $radius-md; │
│ transition: │
│ color 0.15s ease, │
│ background 0.15s ease, │
│ border-color 0.15s ease; │
│ │
│ &:hover { │
│ color: var(--text-primary); │
│ border-color: var(--highlight-color); │
│ } │
│ │
│ &.active { │
│ color: var(--bg-primary); │
│ background: var(--highlight-color); │
│ border-color: var(--highlight-color); │
│ } │
│ } │
│ │
│ .home__season-panel { │
│ display: flex; │
│ flex-direction: column; │
│ gap: $spacing-2xl; │
│ │
│ &.hidden { │
│ display: none; │
│ } │
│ } │
│ │
│ .home__section { │
│ display: flex; │
│ flex-direction: column; │
│ gap: $spacing-lg; │
│ │
│ & + &, │
│ // The Recent Runs section sits as a sib │
│ ling of the season-panel wrapper, │
│ // not another `.home__section`, so the │
│ adjacency selector above wouldn't │
│ // reach it without this second rule. Ke │
│ eps section rhythm consistent. │
│ .home__season-panel + & { │
│ margin-top: $spacing-2xl; │
│ } │
│ } │
│ │
│ .home__section-header { │
│ h2 { │
│ margin: 0; │
│ font-size: $font-size-2xl; │
│ color: var(--text-primary); │
│ font-weight: $font-weight-semibold; │
│ } │
│ } │
│ │
│ // Carousel handles per-card sizing via it │
│ s `perView` prop (see Carousel.scss). │
│ // No per-card overrides needed here. │
│ │
│ // mobile overrides at end of stylesheet t │
│ o win the cascade │
│ @media (max-width: 768px) { │
│ .home { │
│ padding: 15px; │
│ } │
│ │
│ // Mirror LeaderboardHeader's mobile fon │
│ t-size drop (~1.5em). │
│ .home__hero h1 { │
│ font-size: 1.5em; │
│ } │
│ } │
│ </style> │
└──────────────────────────────────────────────┘