┌─ web/src/pages/challenge-mode/[...slug].astro ─────────────────────────────┐
│ diff --git a/web/src/pages/challenge-mode/[...slug].astro b/web/src/pages/challeng │
│ e-mode/[...slug].astro │
│ new file mode 100644 │
│ index 0000000..a8a2477 │
│ --- /dev/null │
│ +++ b/web/src/pages/challenge-mode/[...slug].astro │
│ @@ -0,0 +1,822 @@ │
│ +--- │
│ +import PageLayout from "../../components/PageLayout.astro"; │
│ +import ControlPanel from "../../components/ControlPanel.astro"; │
│ +import FormSelect from "../../components/FormSelect.astro"; │
│ +import LoadingState from "../../components/LoadingState.astro"; │
│ +import WoWClassColors from "../../components/WoWClassColors.astro"; │
│ + │
│ +const baseUrl = import.meta.env.BASE_URL; │
│ + │
│ +export async function getStaticPaths() { │
│ + const DATA_MAP = { │
│ + us: { │
│ + realms: { │
│ + angerforge: "Angerforge", │
│ + arugal: "Arugal", │
│ + ashkandi: "Ashkandi", │
│ + atiesh: "Atiesh", │
│ + azursong: "Azuresong", │
│ + benediction: "Benediction", │
│ + bloodsailBuccaneers: "Bloodsail Buccaneers", │
│ + earthfury: "Earthfury", │
│ + eranikus: "Eranikus", │
│ + faerlina: "Faerlina", │
│ + grobbulus: "Grobbulus", │
│ + maladath: "Maladath", │
│ + mankrik: "Mankrik", │
│ + myzrael: "Myzrael", │
│ + old_blanchy: "Old Blanchy", │
│ + pagle: "Pagle", │
│ + remulos: "Remulos", │
│ + skyfury: "Skyfury", │
│ + sulfuras: "Sulfuras", │
│ + westfall: "Westfall", │
│ + whitemane: "Whitemane", │
│ + windseeker: "Windseeker", │
│ + yojamba: "Yojamba", │
│ + }, │
│ + }, │
│ + eu: { │
│ + realms: { │
│ + amnennar: "Amnennar", │
│ + ashbringer: "Ashbringer", │
│ + auberdine: "Auberdine", │
│ + chromie: "Chromie", │
│ + earthshaker: "Earthshaker", │
│ + everlook: "Everlook", │
│ + firemaw: "Firemaw", │
│ + flamegor: "Flamegor", │
│ + gehennas: "Gehennas", │
│ + giantstalker: "Giantstalker", │
│ + golemagg: "Golemagg", │
│ + hydraxianWaterlords: "Hydraxian Waterlords", │
│ + jindo: "Jin'do", │
│ + lakeshire: "Lakeshire", │
│ + mandokir: "Mandokir", │
│ + mirageRaceway: "Mirage Raceway", │
│ + mograine: "Mograine", │
│ + nethergardeKeep: "Nethergarde Keep", │
│ + patchwerk: "Patchwerk", │
│ + pyrewoodVillage: "Pyrewood Village", │
│ + razorfen: "Razorfen", │
│ + sulfuron: "Sulfuron", │
│ + thekal: "Thekal", │
│ + transcendence: "Transcendence", │
│ + venoxis: "Venoxis", │
│ + }, │
│ + }, │
│ + kr: { │
│ + realms: { │
│ + frostmourne: "Frostmourne", │
│ + iceblood: "Iceblood", │
│ + lokholar: "Lok'holar", │
│ + ragnaros: "Ragnaros", │
│ + shimmeringFlats: "Shimmering Flats", │
│ + }, │
│ + }, │
│ + }; │
│ + │
│ + const dungeonNames = { │
│ + "2": "Temple of the Jade Serpent", │
│ + "56": "Stormstout Brewery", │
│ + "57": "Gate of the Setting Sun", │
│ + "58": "Shado-Pan Monastery", │
│ + "59": "Siege of Niuzao Temple", │
│ + "60": "Mogu'shan Palace", │
│ + "76": "Scholomance", │
│ + "77": "Scarlet Halls", │
│ + "78": "Scarlet Monastery", │
│ + }; │
│ + │
│ + const paths = []; │
│ + │
│ + // Generate all possible combinations │
│ + for (const [regionKey, regionData] of Object.entries(DATA_MAP)) { │
│ + for (const realmKey of Object.keys(regionData.realms)) { │
│ + for (const [dungeonId, dungeonName] of Object.entries(dungeonNames)) { │
│ + const dungeonSlug = dungeonName.toLowerCase().replace(/[^a-z0-9]/g, '-'); │
│ + paths.push({ │
│ + params: { │
│ + slug: `${regionKey}/${realmKey}/${dungeonSlug}` │
│ + }, │
│ + props: { │
│ + initialRegion: regionKey, │
│ + initialRealm: realmKey, │
│ + initialDungeon: dungeonId │
│ + } │
│ + }); │
│ + } │
│ + } │
│ + } │
│ + │
│ + return paths; │
│ +} │
│ + │
│ +// Get initial values from props passed by getStaticPaths │
│ +const { initialRegion, initialRealm, initialDungeon } = Astro.props; │
│ +console.log('Initial values from props:', { initialRegion, initialRealm, initialD │
│ ungeon }); │
│ +--- │
│ + │
│ +<PageLayout │
│ + title="Challenge Mode Leaderboards" │
│ + description="" │
│ + pageTitle="Challenge Mode Leaderboards - MoP Classic" │
│ +> │
│ + <WoWClassColors /> │
│ + │
│ + <style> │
│ + .leaderboard-container { │
│ + background-color: var(--bg-secondary); │
│ + border-radius: 8px; │
│ + border: 1px solid var(--border-color); │
│ + overflow: hidden; │
│ + margin-top: 20px; │
│ + } │
│ + │
│ + .hidden { │
│ + display: none; │
│ + } │
│ + │
│ + .loading-overlay, │
│ + .error-overlay { │
│ + position: fixed; │
│ + top: 50%; │
│ + left: 50%; │
│ + transform: translate(-50%, -50%); │
│ + z-index: 1000; │
│ + min-width: 300px; │
│ + } │
│ + │
│ + #content { │
│ + min-height: 400px; │
│ + transition: opacity 0.2s ease; │
│ + } │
│ + │
│ + .content-loading { │
│ + opacity: 0.3; │
│ + pointer-events: none; │
│ + } │
│ + │
│ + .no-data-message { │
│ + margin: 20px 0; │
│ + } │
│ + │
│ + .no-data-content { │
│ + text-align: center; │
│ + padding: 40px 20px; │
│ + } │
│ + │
│ + .no-data-content h3 { │
│ + color: var(--highlight-color); │
│ + margin: 0 0 15px 0; │
│ + font-size: 1.4em; │
│ + font-weight: 600; │
│ + } │
│ + │
│ + .no-data-content p { │
│ + color: var(--text-secondary); │
│ + margin: 0; │
│ + font-size: 1.1em; │
│ + } │
│ + │
│ + #leaderboard-rows { │
│ + display: table; │
│ + width: 100%; │
│ + } │
│ + │
│ + .info-grid { │
│ + display: grid; │
│ + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); │
│ + gap: 15px; │
│ + } │
│ + │
│ + .info-item { │
│ + display: flex; │
│ + flex-direction: column; │
│ + gap: 4px; │
│ + } │
│ + │
│ + .info-label { │
│ + font-size: 0.85em; │
│ + color: var(--text-muted); │
│ + font-weight: 500; │
│ + text-transform: uppercase; │
│ + letter-spacing: 0.5px; │
│ + } │
│ + │
│ + .info-value { │
│ + font-size: 1em; │
│ + color: var(--text-primary); │
│ + font-weight: 600; │
│ + } │
│ + │
│ + @media (max-width: 768px) { │
│ + .info-grid { │
│ + grid-template-columns: 1fr; │
│ + } │
│ + } │
│ + @media (max-width: 1024px) { │
│ + .leaderboard-header, │
│ + .leaderboard-row { │
│ + grid-template-columns: 60px 100px 1fr 80px 120px; │
│ + gap: 15px; │
│ + } │
│ + │
│ + .player { │
│ + min-width: 75px; │
│ + font-size: 0.75em; │
│ + } │
│ + } │
│ + │
│ + @media (max-width: 768px) { │
│ + .container { │
│ + padding: 10px; │
│ + } │
│ + │
│ + .leaderboard-header, │
│ + .leaderboard-row { │
│ + grid-template-columns: 1fr; │
│ + gap: 10px; │
│ + text-align: center; │
│ + padding: 10px 15px; │
│ + } │
│ + │
│ + .team-composition { │
│ + justify-content: center; │
│ + } │
│ + │
│ + .player { │
│ + min-width: 70px; │
│ + font-size: 0.75em; │
│ + } │
│ + │
│ + .controls-grid { │
│ + flex-direction: column; │
│ + } │
│ + │
│ + .rank, │
│ + .duration, │
│ + .mythic-rating, │
│ + .completed-time { │
│ + font-size: 1.1em; │
│ + margin: 5px 0; │
│ + } │
│ + } │
│ + │
│ + /* Callout component styles */ │
│ + .callout { │
│ + background-color: #2a3a3a; │
│ + border: 1px solid #458588; │
│ + border-left: 4px solid #458588; │
│ + border-radius: 6px; │
│ + padding: 16px; │
│ + margin-bottom: 20px; │
│ + } │
│ + │
│ + .callout-header { │
│ + display: flex; │
│ + align-items: center; │
│ + gap: 10px; │
│ + margin-bottom: 8px; │
│ + } │
│ + │
│ + .callout-icon { │
│ + width: 20px; │
│ + height: 20px; │
│ + color: #458588; │
│ + flex-shrink: 0; │
│ + } │
│ + │
│ + .callout-title { │
│ + color: #458588; │
│ + margin: 0; │
│ + font-size: 1em; │
│ + font-weight: 600; │
│ + } │
│ + │
│ + .callout-content { │
│ + color: var(--text-primary); │
│ + line-height: 1.5; │
│ + margin-left: 30px; │
│ + } │
│ + │
│ + .callout-content p { │
│ + margin: 0 0 8px 0; │
│ + } │
│ + │
│ + .callout-content p:last-child { │
│ + margin-bottom: 0; │
│ + } │
│ + │
│ + .callout-content a { │
│ + color: #458588; │
│ + text-decoration: none; │
│ + } │
│ + │
│ + .callout-content a:hover { │
│ + text-decoration: underline; │
│ + } │
│ + │
│ + .callout-content strong { │
│ + color: #458588; │
│ + } │
│ + │
│ + .callout-content code { │
│ + background-color: rgba(255, 255, 255, 0.1); │
│ + padding: 2px 4px; │
│ + border-radius: 3px; │
│ + font-family: "Courier New", monospace; │
│ + font-size: 0.9em; │
│ + } │
│ + </style> │
│ + │
│ + <ControlPanel title="Leaderboard Options"> │
│ + <FormSelect │
│ + id="region" │
│ + label="Region" │
│ + options={[ │
│ + { value: "us", label: "US", selected: initialRegion === "us" }, │
│ + { value: "eu", label: "EU", selected: initialRegion === "eu" }, │
│ + { value: "kr", label: "KR", selected: initialRegion === "kr" }, │
│ + ]} │
│ + /> │
│ + <FormSelect id="realm" label="Realm" options={[]} /> │
│ + <FormSelect │
│ + id="dungeon" │
│ + label="Dungeon" │
│ + options={[ │
│ + { value: "2", label: "Temple of the Jade Serpent", selected: initialDunge │
│ on === "2" }, │
│ + { value: "56", label: "Stormstout Brewery", selected: initialDungeon === │
│ "56" }, │
│ + { value: "57", label: "Gate of the Setting Sun", selected: initialDungeon │
│ === "57" }, │
│ + { value: "58", label: "Shado-Pan Monastery", selected: initialDungeon === │
│ "58" }, │
│ + { value: "59", label: "Siege of Niuzao Temple", selected: initialDungeon │
│ === "59" }, │
│ + { value: "60", label: "Mogu'shan Palace", selected: initialDungeon === "6 │
│ 0" }, │
│ + { value: "76", label: "Scholomance", selected: initialDungeon === "76" }, │
│ + { value: "77", label: "Scarlet Halls", selected: initialDungeon === "77" │
│ }, │
│ + { value: "78", label: "Scarlet Monastery", selected: initialDungeon === " │
│ 78" }, │
│ + ]} │
│ + /> │
│ + </ControlPanel> │
│ + │
│ + <div id="loading" class="loading-overlay"> │
│ + <LoadingState message="Loading Challenge Mode leaderboard data..." /> │
│ + </div> │
│ + │
│ + <div id="error" class="error-overlay hidden"> │
│ + <LoadingState type="error" /> │
│ + </div> │
│ + │
│ + <div id="no-data" class="no-data-message hidden"> │
│ + <div class="card"> │
│ + <div class="no-data-content"> │
│ + <h3>No Times Found</h3> │
│ + <p> │
│ + No Challenge Mode times were found for this realm and dungeon │
│ + combination. │
│ + </p> │
│ + </div> │
│ + </div> │
│ + </div> │
│ + │
│ + <div id="content"> │
│ + <div class="card"> │
│ + <div class="info-grid"> │
│ + <div class="info-item"> │
│ + <span class="info-label">Dungeon</span> │
│ + <span class="info-value" id="dungeon-name">Loading...</span> │
│ + </div> │
│ + <div class="info-item"> │
│ + <span class="info-label">Period</span> │
│ + <span class="info-value" id="period">-</span> │
│ + </div> │
│ + <div class="info-item"> │
│ + <span class="info-label">Realm</span> │
│ + <span class="info-value" id="realm-name">-</span> │
│ + </div> │
│ + <div class="info-item"> │
│ + <span class="info-label">Period Start</span> │
│ + <span class="info-value" id="period-start">-</span> │
│ + </div> │
│ + <div class="info-item"> │
│ + <span class="info-label">Period End</span> │
│ + <span class="info-value" id="period-end">-</span> │
│ + </div> │
│ + </div> │
│ + </div> │
│ + │
│ + <div class="leaderboard-container"> │
│ + <div id="leaderboard-rows"></div> │
│ + </div> │
│ + </div> │
│ +</PageLayout> │
│ + │
│ +<style is:global> │
│ + /* Global styles for dynamically generated content */ │
│ + #leaderboard-rows .leaderboard-row { │
│ + display: grid !important; │
│ + grid-template-columns: 80px 120px 2fr 100px 140px !important; │
│ + gap: 20px !important; │
│ + align-items: center !important; │
│ + padding: 15px 20px !important; │
│ + border-bottom: 1px solid #4a4a4a !important; │
│ + min-height: 60px !important; │
│ + } │
│ + │
│ + #leaderboard-rows .rank { │
│ + font-size: 1.2em !important; │
│ + font-weight: bold !important; │
│ + text-align: center !important; │
│ + color: #d8a657 !important; │
│ + } │
│ + │
│ + #leaderboard-rows .duration { │
│ + font-family: "Courier New", monospace !important; │
│ + font-weight: bold !important; │
│ + color: #00ff88 !important; │
│ + text-align: right !important; │
│ + } │
│ + │
│ + #leaderboard-rows .team-composition { │
│ + display: flex !important; │
│ + flex-wrap: wrap !important; │
│ + gap: 6px !important; │
│ + justify-content: flex-start !important; │
│ + align-items: center !important; │
│ + } │
│ + │
│ + #leaderboard-rows .mythic-rating { │
│ + color: #d8a657 !important; │
│ + font-weight: bold !important; │
│ + text-align: center !important; │
│ + } │
│ + │
│ + #leaderboard-rows .completed-time { │
│ + color: #aaaaaa !important; │
│ + font-size: 0.9em !important; │
│ + text-align: center !important; │
│ + } │
│ + │
│ + @media (max-width: 1024px) { │
│ + #leaderboard-rows .leaderboard-row { │
│ + grid-template-columns: 60px 100px 1fr 80px 120px !important; │
│ + gap: 15px !important; │
│ + } │
│ + } │
│ + │
│ + @media (max-width: 768px) { │
│ + #leaderboard-rows .leaderboard-row { │
│ + grid-template-columns: 1fr !important; │
│ + gap: 10px !important; │
│ + text-align: center !important; │
│ + padding: 10px 15px !important; │
│ + } │
│ + │
│ + #leaderboard-rows .team-composition { │
│ + justify-content: center !important; │
│ + } │
│ + │
│ + #leaderboard-rows .rank, │
│ + #leaderboard-rows .duration, │
│ + #leaderboard-rows .mythic-rating, │
│ + #leaderboard-rows .completed-time { │
│ + font-size: 1.1em !important; │
│ + margin: 5px 0 !important; │
│ + } │
│ + } │
│ +</style> │
│ + │
│ +<script is:inline define:vars={{ baseUrl, initialRegion, initialRealm, initialDun │
│ geon }}> │
│ + const DATA_MAP = { │
│ + us: { │
│ + name: "US", │
│ + realms: { │
│ + angerforge: "Angerforge", │
│ + arugal: "Arugal", │
│ + ashkandi: "Ashkandi", │
│ + atiesh: "Atiesh", │
│ + azursong: "Azuresong", │
│ + benediction: "Benediction", │
│ + bloodsailBuccaneers: "Bloodsail Buccaneers", │
│ + earthfury: "Earthfury", │
│ + eranikus: "Eranikus", │
│ + faerlina: "Faerlina", │
│ + grobbulus: "Grobbulus", │
│ + maladath: "Maladath", │
│ + mankrik: "Mankrik", │
│ + myzrael: "Myzrael", │
│ + old_blanchy: "Old Blanchy", │
│ + pagle: "Pagle", │
│ + remulos: "Remulos", │
│ + skyfury: "Skyfury", │
│ + sulfuras: "Sulfuras", │
│ + westfall: "Westfall", │
│ + whitemane: "Whitemane", │
│ + windseeker: "Windseeker", │
│ + yojamba: "Yojamba", │
│ + }, │
│ + }, │
│ + eu: { │
│ + name: "EU", │
│ + realms: { │
│ + amnennar: "Amnennar", │
│ + ashbringer: "Ashbringer", │
│ + auberdine: "Auberdine", │
│ + chromie: "Chromie", │
│ + earthshaker: "Earthshaker", │
│ + everlook: "Everlook", │
│ + firemaw: "Firemaw", │
│ + flamegor: "Flamegor", │
│ + gehennas: "Gehennas", │
│ + giantstalker: "Giantstalker", │
│ + golemagg: "Golemagg", │
│ + hydraxianWaterlords: "Hydraxian Waterlords", │
│ + jindo: "Jin'do", │
│ + lakeshire: "Lakeshire", │
│ + mandokir: "Mandokir", │
│ + mirageRaceway: "Mirage Raceway", │
│ + mograine: "Mograine", │
│ + nethergardeKeep: "Nethergarde Keep", │
│ + patchwerk: "Patchwerk", │
│ + pyrewoodVillage: "Pyrewood Village", │
│ + razorfen: "Razorfen", │
│ + sulfuron: "Sulfuron", │
│ + thekal: "Thekal", │
│ + transcendence: "Transcendence", │
│ + venoxis: "Venoxis", │
│ + }, │
│ + }, │
│ + kr: { │
│ + name: "KR", │
│ + realms: { │
│ + frostmourne: "Frostmourne", │
│ + iceblood: "Iceblood", │
│ + lokholar: "Lok'holar", │
│ + ragnaros: "Ragnaros", │
│ + shimmeringFlats: "Shimmering Flats", │
│ + }, │
│ + }, │
│ + }; │
│ + // Dungeon name mapping │
│ + const dungeonNames = { │
│ + "2": "Temple of the Jade Serpent", │
│ + "56": "Stormstout Brewery", │
│ + "57": "Gate of the Setting Sun", │
│ + "58": "Shado-Pan Monastery", │
│ + "59": "Siege of Niuzao Temple", │
│ + "60": "Mogu'shan Palace", │
│ + "76": "Scholomance", │
│ + "77": "Scarlet Halls", │
│ + "78": "Scarlet Monastery", │
│ + }; │
│ + │
│ + function formatDuration(milliseconds) { │
│ + const totalSeconds = Math.floor(milliseconds / 1000); │
│ + const minutes = Math.floor(totalSeconds / 60); │
│ + const seconds = totalSeconds % 60; │
│ + return `${minutes}:${seconds.toString().padStart(2, "0")}`; │
│ + } │
│ + │
│ + function formatTimestamp(timestamp) { │
│ + return new Date(timestamp).toLocaleDateString("en-US", { │
│ + month: "short", │
│ + day: "numeric", │
│ + hour: "2-digit", │
│ + minute: "2-digit", │
│ + }); │
│ + } │
│ + │
│ + function createLeaderboardRow(group) { │
│ + console.log("Creating row for group:", group); │
│ + │
│ + const row = document.createElement("div"); │
│ + row.className = "leaderboard-row"; │
│ + │
│ + let teamHtml = ""; │
│ + group.members.forEach((member) => { │
│ + // Handle missing specialization data (deleted/transferred characters) │
│ + const specId = member.specialization?.id; │
│ + const spec = specId │
│ + ? window.WoW.getSpecInfo(specId) │
│ + : { │
│ + role: "dps", │
│ + class: "Unknown", │
│ + spec: "Unknown", │
│ + }; │
│ + const iconUrl = window.WoW.getSpecIcon(spec.class, spec.spec); │
│ + const iconHtml = iconUrl │
│ + ? `<img src="${iconUrl}" alt="${spec.spec} ${spec.class}" style="width: 1 │
│ 6px; height: 16px; border-radius: 2px; margin-right: 4px; vertical-align: middle; │
│ flex-shrink: 0;">` │
│ + : ""; │
│ + │
│ + teamHtml += `<span style="display: inline-flex; align-items: center; margin │
│ -right: 8px; gap: 4px;"> │
│ + ${iconHtml} │
│ + <span style="color: ${window.WoW.getClassColor(spec.class)}; font-weight: │
│ 600; font-size: 0.9em;">${member.profile.name}</span> │
│ + </span>`; │
│ + }); │
│ + │
│ + row.innerHTML = ` │
│ + <div style="display: table-cell; padding: 15px 10px; vertical-align: mi │
│ ddle; font-size: 1.2em; font-weight: bold; color: #d8a657; text-align: center; wid │
│ th: 80px;">#${group.ranking}</div> │
│ + <div style="display: table-cell; padding: 15px 10px; vertical-align: mi │
│ ddle; font-family: 'Courier New', monospace; font-weight: bold; color: #ffffff; te │
│ xt-align: right; width: 120px;">${formatDuration(group.duration)}</div> │
│ + <div style="display: table-cell; padding: 15px 10px; vertical-align: mi │
│ ddle;">${teamHtml}</div> │
│ + <div style="display: table-cell; padding: 15px 10px; vertical-align: mi │
│ ddle; color: #aaaaaa; font-size: 0.9em; text-align: center; width: 140px;">${forma │
│ tTimestamp(group.completed_timestamp)}</div> │
│ + `; │
│ + │
│ + // Apply table-row styles │
│ + row.style.cssText = ` │
│ + display: table-row !important; │
│ + border-bottom: 1px solid #4a4a4a !important; │
│ + background-color: #32302f; │
│ + transition: background-color 0.2s ease; │
│ + `; │
│ + │
│ + // Add hover effect │
│ + row.addEventListener("mouseenter", () => { │
│ + row.style.backgroundColor = "rgba(255, 215, 0, 0.05)"; │
│ + }); │
│ + row.addEventListener("mouseleave", () => { │
│ + row.style.backgroundColor = "#32302f"; │
│ + }); │
│ + │
│ + console.log("Row created:", row); │
│ + return row; │
│ + } │
│ + │
│ + function displayLeaderboard(data) { │
│ + console.log("Displaying leaderboard data:", data); │
│ + │
│ + // Check if we have no data (missing or empty leading_groups) │
│ + if (!data.leading_groups || data.leading_groups.length === 0) { │
│ + showNoData(); │
│ + return; │
│ + } │
│ + │
│ + // Handle localized dungeon names │
│ + const dungeonName = │
│ + typeof data.map.name === "object" │
│ + ? data.map.name.en_US || data.map.name │
│ + : data.map.name; │
│ + document.getElementById("dungeon-name").textContent = dungeonName; │
│ + document.getElementById("period").textContent = data.period; │
│ + document.getElementById("period-start").textContent = formatTimestamp( │
│ + data.period_start_timestamp, │
│ + ); │
│ + document.getElementById("period-end").textContent = formatTimestamp( │
│ + data.period_end_timestamp, │
│ + ); │
│ + │
│ + const currentRegion = document.getElementById("region").value; │
│ + const currentRealm = document.getElementById("realm").value; │
│ + const regionData = DATA_MAP[currentRegion]; │
│ + const realmName = regionData?.realms?.[currentRealm] || currentRealm; │
│ + document.getElementById("realm-name").textContent = realmName; │
│ + │
│ + const rowsContainer = document.getElementById("leaderboard-rows"); │
│ + rowsContainer.innerHTML = ""; │
│ + │
│ + console.log("Creating rows for", data.leading_groups.length, "groups"); │
│ + data.leading_groups.forEach((group, index) => { │
│ + console.log("Creating row for group", index + 1); │
│ + const row = createLeaderboardRow(group); │
│ + rowsContainer.appendChild(row); │
│ + }); │
│ + │
│ + document.getElementById("loading").classList.add("hidden"); │
│ + document.getElementById("error").classList.add("hidden"); │
│ + document.getElementById("no-data").classList.add("hidden"); │
│ + document.getElementById("content").classList.remove("content-loading"); │
│ + document.getElementById("content").classList.remove("hidden"); │
│ + │
│ + console.log("Leaderboard displayed successfully"); │
│ + } │
│ + │
│ + function showNoData() { │
│ + document.getElementById("loading").classList.add("hidden"); │
│ + document.getElementById("error").classList.add("hidden"); │
│ + document.getElementById("no-data").classList.remove("hidden"); │
│ + document.getElementById("content").classList.add("hidden"); │
│ + } │
│ + │
│ + function showError(message) { │
│ + const errorDiv = document.getElementById("error"); │
│ + const loadingMessage = errorDiv.querySelector(".loading-message"); │
│ + if (loadingMessage) { │
│ + loadingMessage.textContent = message; │
│ + } else { │
│ + // Fallback if LoadingState structure isn't found │
│ + errorDiv.innerHTML = `<div class="loading-state error"><p class="loading-me │
│ ssage">${message}</p></div>`; │
│ + } │
│ + errorDiv.classList.remove("hidden"); │
│ + document.getElementById("loading").classList.add("hidden"); │
│ + document.getElementById("no-data").classList.add("hidden"); │
│ + document.getElementById("content").classList.add("hidden"); │
│ + } │
│ + │
│ + function showLoading() { │
│ + document.getElementById("loading").classList.remove("hidden"); │
│ + document.getElementById("error").classList.add("hidden"); │
│ + document.getElementById("no-data").classList.add("hidden"); │
│ + document.getElementById("content").classList.add("content-loading"); │
│ + } │
│ + │
│ + function updateRealmOptions() { │
│ + const region = document.getElementById("region").value; │
│ + const realmSelect = document.getElementById("realm"); │
│ + │
│ + // Clear existing options │
│ + realmSelect.innerHTML = ""; │
│ + │
│ + // Add realms for selected region │
│ + const regionData = DATA_MAP[region]; │
│ + if (regionData && regionData.realms) { │
│ + Object.entries(regionData.realms).forEach(([slug, name]) => { │
│ + const option = document.createElement("option"); │
│ + option.value = slug; │
│ + option.textContent = name; │
│ + realmSelect.appendChild(option); │
│ + }); │
│ + } │
│ + } │
│ + │
│ + function getDataFileName() { │
│ + const region = document.getElementById("region").value; │
│ + const realm = document.getElementById("realm").value; │
│ + const dungeon = document.getElementById("dungeon").value; │
│ + │
│ + console.log('Getting data filename with:', { region, realm, dungeon }); │
│ + console.log('Dungeon name lookup:', dungeonNames[dungeon]); │
│ + │
│ + const dungeonName = dungeonNames[dungeon] │
│ + .toLowerCase() │
│ + .replace(/[^a-z0-9]/g, "-"); │
│ + const fileName = `${region}/${realm}/${dungeonName}/${realm}-${dungeonName}-l │
│ eaderboard.json`; │
│ + console.log('Generated filename:', fileName); │
│ + return fileName; │
│ + } │
│ + │
│ + async function loadLeaderboard() { │
│ + showLoading(); │
│ + │
│ + try { │
│ + const fileName = getDataFileName(); │
│ + console.log("Loading file:", fileName); │
│ + const fullUrl = `${baseUrl}data/challenge-mode/${fileName}`.replace('//', ' │
│ /'); │
│ + console.log("Full fetch URL:", fullUrl); │
│ + const response = await fetch(fullUrl); │
│ + │
│ + if (!response.ok) { │
│ + throw new Error( │
│ + `Failed to load leaderboard data: ${response.statusText}`, │
│ + ); │
│ + } │
│ + │
│ + const data = await response.json(); │
│ + console.log("Data loaded:", data); │
│ + displayLeaderboard(data); │
│ + } catch (error) { │
│ + console.error("Error loading leaderboard:", error); │
│ + showError(`Error loading leaderboard data: ${error.message}`); │
│ + } │
│ + } │
│ + │
│ + function updateURL() { │
│ + const region = document.getElementById("region").value; │
│ + const realm = document.getElementById("realm").value; │
│ + const dungeon = document.getElementById("dungeon").value; │
│ + │
│ + const dungeonName = dungeonNames[dungeon] │
│ + .toLowerCase() │
│ + .replace(/[^a-z0-9]/g, "-"); │
│ + │
│ + const newURL = `/challenge-mode/${region}/${realm}/${dungeonName}`; │
│ + window.history.pushState({}, '', newURL); │
│ + } │
│ + │
│ + // Event listeners │
│ + document.addEventListener("DOMContentLoaded", () => { │
│ + // Initialize realm options │
│ + updateRealmOptions(); │
│ + │
│ + // Set initial values from route params │
│ + document.getElementById("region").value = initialRegion; │
│ + updateRealmOptions(); │
│ + document.getElementById("realm").value = initialRealm; │
│ + document.getElementById("dungeon").value = initialDungeon; │
│ + │
│ + // Load initial data │
│ + loadLeaderboard(); │
│ + │
│ + // Bind change events │
│ + document.getElementById("region").addEventListener("change", () => { │
│ + updateRealmOptions(); │
│ + updateURL(); │
│ + loadLeaderboard(); │
│ + }); │
│ + document.getElementById("realm").addEventListener("change", () => { │
│ + updateURL(); │
│ + loadLeaderboard(); │
│ + }); │
│ + document.getElementById("dungeon").addEventListener("change", () => { │
│ + updateURL(); │
│ + loadLeaderboard(); │
│ + }); │
│ + }); │
│ +</script> │
│ \ No newline at end of file │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...hallenge-mode/[...slug].astro ───┐
│ diff --git a/web/src/pages/challenge-mode/[. │
│ ..slug].astro b/web/src/pages/challenge-mode │
│ /[...slug].astro │
│ new file mode 100644 │
│ index 0000000..a8a2477 │
│ --- /dev/null │
│ +++ b/web/src/pages/challenge-mode/[...slug] │
│ .astro │
│ @@ -0,0 +1,822 @@ │
│ +--- │
│ +import PageLayout from "../../components/Pa │
│ geLayout.astro"; │
│ +import ControlPanel from "../../components/ │
│ ControlPanel.astro"; │
│ +import FormSelect from "../../components/Fo │
│ rmSelect.astro"; │
│ +import LoadingState from "../../components/ │
│ LoadingState.astro"; │
│ +import WoWClassColors from "../../component │
│ s/WoWClassColors.astro"; │
│ + │
│ +const baseUrl = import.meta.env.BASE_URL; │
│ + │
│ +export async function getStaticPaths() { │
│ + const DATA_MAP = { │
│ + us: { │
│ + realms: { │
│ + angerforge: "Angerforge", │
│ + arugal: "Arugal", │
│ + ashkandi: "Ashkandi", │
│ + atiesh: "Atiesh", │
│ + azursong: "Azuresong", │
│ + benediction: "Benediction", │
│ + bloodsailBuccaneers: "Bloodsail Buc │
│ caneers", │
│ + earthfury: "Earthfury", │
│ + eranikus: "Eranikus", │
│ + faerlina: "Faerlina", │
│ + grobbulus: "Grobbulus", │
│ + maladath: "Maladath", │
│ + mankrik: "Mankrik", │
│ + myzrael: "Myzrael", │
│ + old_blanchy: "Old Blanchy", │
│ + pagle: "Pagle", │
│ + remulos: "Remulos", │
│ + skyfury: "Skyfury", │
│ + sulfuras: "Sulfuras", │
│ + westfall: "Westfall", │
│ + whitemane: "Whitemane", │
│ + windseeker: "Windseeker", │
│ + yojamba: "Yojamba", │
│ + }, │
│ + }, │
│ + eu: { │
│ + realms: { │
│ + amnennar: "Amnennar", │
│ + ashbringer: "Ashbringer", │
│ + auberdine: "Auberdine", │
│ + chromie: "Chromie", │
│ + earthshaker: "Earthshaker", │
│ + everlook: "Everlook", │
│ + firemaw: "Firemaw", │
│ + flamegor: "Flamegor", │
│ + gehennas: "Gehennas", │
│ + giantstalker: "Giantstalker", │
│ + golemagg: "Golemagg", │
│ + hydraxianWaterlords: "Hydraxian Wat │
│ erlords", │
│ + jindo: "Jin'do", │
│ + lakeshire: "Lakeshire", │
│ + mandokir: "Mandokir", │
│ + mirageRaceway: "Mirage Raceway", │
│ + mograine: "Mograine", │
│ + nethergardeKeep: "Nethergarde Keep" │
│ , │
│ + patchwerk: "Patchwerk", │
│ + pyrewoodVillage: "Pyrewood Village" │
│ , │
│ + razorfen: "Razorfen", │
│ + sulfuron: "Sulfuron", │
│ + thekal: "Thekal", │
│ + transcendence: "Transcendence", │
│ + venoxis: "Venoxis", │
│ + }, │
│ + }, │
│ + kr: { │
│ + realms: { │
│ + frostmourne: "Frostmourne", │
│ + iceblood: "Iceblood", │
│ + lokholar: "Lok'holar", │
│ + ragnaros: "Ragnaros", │
│ + shimmeringFlats: "Shimmering Flats" │
│ , │
│ + }, │
│ + }, │
│ + }; │
│ + │
│ + const dungeonNames = { │
│ + "2": "Temple of the Jade Serpent", │
│ + "56": "Stormstout Brewery", │
│ + "57": "Gate of the Setting Sun", │
│ + "58": "Shado-Pan Monastery", │
│ + "59": "Siege of Niuzao Temple", │
│ + "60": "Mogu'shan Palace", │
│ + "76": "Scholomance", │
│ + "77": "Scarlet Halls", │
│ + "78": "Scarlet Monastery", │
│ + }; │
│ + │
│ + const paths = []; │
│ + │
│ + // Generate all possible combinations │
│ + for (const [regionKey, regionData] of Obj │
│ ect.entries(DATA_MAP)) { │
│ + for (const realmKey of Object.keys(regi │
│ onData.realms)) { │
│ + for (const [dungeonId, dungeonName] o │
│ f Object.entries(dungeonNames)) { │
│ + const dungeonSlug = dungeonName.toL │
│ owerCase().replace(/[^a-z0-9]/g, '-'); │
│ + paths.push({ │
│ + params: { │
│ + slug: `${regionKey}/${realmKey} │
│ /${dungeonSlug}` │
│ + }, │
│ + props: { │
│ + initialRegion: regionKey, │
│ + initialRealm: realmKey, │
│ + initialDungeon: dungeonId │
│ + } │
│ + }); │
│ + } │
│ + } │
│ + } │
│ + │
│ + return paths; │
│ +} │
│ + │
│ +// Get initial values from props passed by │
│ getStaticPaths │
│ +const { initialRegion, initialRealm, initia │
│ lDungeon } = Astro.props; │
│ +console.log('Initial values from props:', { │
│ initialRegion, initialRealm, initialDungeon │
│ }); │
│ +--- │
│ + │
│ +<PageLayout │
│ + title="Challenge Mode Leaderboards" │
│ + description="" │
│ + pageTitle="Challenge Mode Leaderboards - │
│ MoP Classic" │
│ +> │
│ + <WoWClassColors /> │
│ + │
│ + <style> │
│ + .leaderboard-container { │
│ + background-color: var(--bg-secondary) │
│ ; │
│ + border-radius: 8px; │
│ + border: 1px solid var(--border-color) │
│ ; │
│ + overflow: hidden; │
│ + margin-top: 20px; │
│ + } │
│ + │
│ + .hidden { │
│ + display: none; │
│ + } │
│ + │
│ + .loading-overlay, │
│ + .error-overlay { │
│ + position: fixed; │
│ + top: 50%; │
│ + left: 50%; │
│ + transform: translate(-50%, -50%); │
│ + z-index: 1000; │
│ + min-width: 300px; │
│ + } │
│ + │
│ + #content { │
│ + min-height: 400px; │
│ + transition: opacity 0.2s ease; │
│ + } │
│ + │
│ + .content-loading { │
│ + opacity: 0.3; │
│ + pointer-events: none; │
│ + } │
│ + │
│ + .no-data-message { │
│ + margin: 20px 0; │
│ + } │
│ + │
│ + .no-data-content { │
│ + text-align: center; │
│ + padding: 40px 20px; │
│ + } │
│ + │
│ + .no-data-content h3 { │
│ + color: var(--highlight-color); │
│ + margin: 0 0 15px 0; │
│ + font-size: 1.4em; │
│ + font-weight: 600; │
│ + } │
│ + │
│ + .no-data-content p { │
│ + color: var(--text-secondary); │
│ + margin: 0; │
│ + font-size: 1.1em; │
│ + } │
│ + │
│ + #leaderboard-rows { │
│ + display: table; │
│ + width: 100%; │
│ + } │
│ + │
│ + .info-grid { │
│ + display: grid; │
│ + grid-template-columns: repeat(auto-fi │
│ t, minmax(200px, 1fr)); │
│ + gap: 15px; │
│ + } │
│ + │
│ + .info-item { │
│ + display: flex; │
│ + flex-direction: column; │
│ + gap: 4px; │
│ + } │
│ + │
│ + .info-label { │
│ + font-size: 0.85em; │
│ + color: var(--text-muted); │
│ + font-weight: 500; │
│ + text-transform: uppercase; │
│ + letter-spacing: 0.5px; │
│ + } │
│ + │
│ + .info-value { │
│ + font-size: 1em; │
│ + color: var(--text-primary); │
│ + font-weight: 600; │
│ + } │
│ + │
│ + @media (max-width: 768px) { │
│ + .info-grid { │
│ + grid-template-columns: 1fr; │
│ + } │
│ + } │
│ + @media (max-width: 1024px) { │
│ + .leaderboard-header, │
│ + .leaderboard-row { │
│ + grid-template-columns: 60px 100px 1 │
│ fr 80px 120px; │
│ + gap: 15px; │
│ + } │
│ + │
│ + .player { │
│ + min-width: 75px; │
│ + font-size: 0.75em; │
│ + } │
│ + } │
│ + │
│ + @media (max-width: 768px) { │
│ + .container { │
│ + padding: 10px; │
│ + } │
│ + │
│ + .leaderboard-header, │
│ + .leaderboard-row { │
│ + grid-template-columns: 1fr; │
│ + gap: 10px; │
│ + text-align: center; │
│ + padding: 10px 15px; │
│ + } │
│ + │
│ + .team-composition { │
│ + justify-content: center; │
│ + } │
│ + │
│ + .player { │
│ + min-width: 70px; │
│ + font-size: 0.75em; │
│ + } │
│ + │
│ + .controls-grid { │
│ + flex-direction: column; │
│ + } │
│ + │
│ + .rank, │
│ + .duration, │
│ + .mythic-rating, │
│ + .completed-time { │
│ + font-size: 1.1em; │
│ + margin: 5px 0; │
│ + } │
│ + } │
│ + │
│ + /* Callout component styles */ │
│ + .callout { │
│ + background-color: #2a3a3a; │
│ + border: 1px solid #458588; │
│ + border-left: 4px solid #458588; │
│ + border-radius: 6px; │
│ + padding: 16px; │
│ + margin-bottom: 20px; │
│ + } │
│ + │
│ + .callout-header { │
│ + display: flex; │
│ + align-items: center; │
│ + gap: 10px; │
│ + margin-bottom: 8px; │
│ + } │
│ + │
│ + .callout-icon { │
│ + width: 20px; │
│ + height: 20px; │
│ + color: #458588; │
│ + flex-shrink: 0; │
│ + } │
│ + │
│ + .callout-title { │
│ + color: #458588; │
│ + margin: 0; │
│ + font-size: 1em; │
│ + font-weight: 600; │
│ + } │
│ + │
│ + .callout-content { │
│ + color: var(--text-primary); │
│ + line-height: 1.5; │
│ + margin-left: 30px; │
│ + } │
│ + │
│ + .callout-content p { │
│ + margin: 0 0 8px 0; │
│ + } │
│ + │
│ + .callout-content p:last-child { │
│ + margin-bottom: 0; │
│ + } │
│ + │
│ + .callout-content a { │
│ + color: #458588; │
│ + text-decoration: none; │
│ + } │
│ + │
│ + .callout-content a:hover { │
│ + text-decoration: underline; │
│ + } │
│ + │
│ + .callout-content strong { │
│ + color: #458588; │
│ + } │
│ + │
│ + .callout-content code { │
│ + background-color: rgba(255, 255, 255, │
│ 0.1); │
│ + padding: 2px 4px; │
│ + border-radius: 3px; │
│ + font-family: "Courier New", monospace │
│ ; │
│ + font-size: 0.9em; │
│ + } │
│ + </style> │
│ + │
│ + <ControlPanel title="Leaderboard Options" │
│ > │
│ + <FormSelect │
│ + id="region" │
│ + label="Region" │
│ + options={[ │
│ + { value: "us", label: "US", selecte │
│ d: initialRegion === "us" }, │
│ + { value: "eu", label: "EU", selecte │
│ d: initialRegion === "eu" }, │
│ + { value: "kr", label: "KR", selecte │
│ d: initialRegion === "kr" }, │
│ + ]} │
│ + /> │
│ + <FormSelect id="realm" label="Realm" op │
│ tions={[]} /> │
│ + <FormSelect │
│ + id="dungeon" │
│ + label="Dungeon" │
│ + options={[ │
│ + { value: "2", label: "Temple of the │
│ Jade Serpent", selected: initialDungeon === │
│ "2" }, │
│ + { value: "56", label: "Stormstout B │
│ rewery", selected: initialDungeon === "56" } │
│ , │
│ + { value: "57", label: "Gate of the │
│ Setting Sun", selected: initialDungeon === " │
│ 57" }, │
│ + { value: "58", label: "Shado-Pan Mo │
│ nastery", selected: initialDungeon === "58" │
│ }, │
│ + { value: "59", label: "Siege of Niu │
│ zao Temple", selected: initialDungeon === "5 │
│ 9" }, │
│ + { value: "60", label: "Mogu'shan Pa │
│ lace", selected: initialDungeon === "60" }, │
│ + { value: "76", label: "Scholomance" │
│ , selected: initialDungeon === "76" }, │
│ + { value: "77", label: "Scarlet Hall │
│ s", selected: initialDungeon === "77" }, │
│ + { value: "78", label: "Scarlet Mona │
│ stery", selected: initialDungeon === "78" }, │
│ + ]} │
│ + /> │
│ + </ControlPanel> │
│ + │
│ + <div id="loading" class="loading-overlay" │
│ > │
│ + <LoadingState message="Loading Challeng │
│ e Mode leaderboard data..." /> │
│ + </div> │
│ + │
│ + <div id="error" class="error-overlay hidd │
│ en"> │
│ + <LoadingState type="error" /> │
│ + </div> │
│ + │
│ + <div id="no-data" class="no-data-message │
│ hidden"> │
│ + <div class="card"> │
│ + <div class="no-data-content"> │
│ + <h3>No Times Found</h3> │
│ + <p> │
│ + No Challenge Mode times were foun │
│ d for this realm and dungeon │
│ + combination. │
│ + </p> │
│ + </div> │
│ + </div> │
│ + </div> │
│ + │
│ + <div id="content"> │
│ + <div class="card"> │
│ + <div class="info-grid"> │
│ + <div class="info-item"> │
│ + <span class="info-label">Dungeon< │
│ /span> │
│ + <span class="info-value" id="dung │
│ eon-name">Loading...</span> │
│ + </div> │
│ + <div class="info-item"> │
│ + <span class="info-label">Period</ │
│ span> │
│ + <span class="info-value" id="peri │
│ od">-</span> │
│ + </div> │
│ + <div class="info-item"> │
│ + <span class="info-label">Realm</s │
│ pan> │
│ + <span class="info-value" id="real │
│ m-name">-</span> │
│ + </div> │
│ + <div class="info-item"> │
│ + <span class="info-label">Period S │
│ tart</span> │
│ + <span class="info-value" id="peri │
│ od-start">-</span> │
│ + </div> │
│ + <div class="info-item"> │
│ + <span class="info-label">Period E │
│ nd</span> │
│ + <span class="info-value" id="peri │
│ od-end">-</span> │
│ + </div> │
│ + </div> │
│ + </div> │
│ + │
│ + <div class="leaderboard-container"> │
│ + <div id="leaderboard-rows"></div> │
│ + </div> │
│ + </div> │
│ +</PageLayout> │
│ + │
│ +<style is:global> │
│ + /* Global styles for dynamically generate │
│ d content */ │
│ + #leaderboard-rows .leaderboard-row { │
│ + display: grid !important; │
│ + grid-template-columns: 80px 120px 2fr 1 │
│ 00px 140px !important; │
│ + gap: 20px !important; │
│ + align-items: center !important; │
│ + padding: 15px 20px !important; │
│ + border-bottom: 1px solid #4a4a4a !impor │
│ tant; │
│ + min-height: 60px !important; │
│ + } │
│ + │
│ + #leaderboard-rows .rank { │
│ + font-size: 1.2em !important; │
│ + font-weight: bold !important; │
│ + text-align: center !important; │
│ + color: #d8a657 !important; │
│ + } │
│ + │
│ + #leaderboard-rows .duration { │
│ + font-family: "Courier New", monospace ! │
│ important; │
│ + font-weight: bold !important; │
│ + color: #00ff88 !important; │
│ + text-align: right !important; │
│ + } │
│ + │
│ + #leaderboard-rows .team-composition { │
│ + display: flex !important; │
│ + flex-wrap: wrap !important; │
│ + gap: 6px !important; │
│ + justify-content: flex-start !important; │
│ + align-items: center !important; │
│ + } │
│ + │
│ + #leaderboard-rows .mythic-rating { │
│ + color: #d8a657 !important; │
│ + font-weight: bold !important; │
│ + text-align: center !important; │
│ + } │
│ + │
│ + #leaderboard-rows .completed-time { │
│ + color: #aaaaaa !important; │
│ + font-size: 0.9em !important; │
│ + text-align: center !important; │
│ + } │
│ + │
│ + @media (max-width: 1024px) { │
│ + #leaderboard-rows .leaderboard-row { │
│ + grid-template-columns: 60px 100px 1fr │
│ 80px 120px !important; │
│ + gap: 15px !important; │
│ + } │
│ + } │
│ + │
│ + @media (max-width: 768px) { │
│ + #leaderboard-rows .leaderboard-row { │
│ + grid-template-columns: 1fr !important │
│ ; │
│ + gap: 10px !important; │
│ + text-align: center !important; │
│ + padding: 10px 15px !important; │
│ + } │
│ + │
│ + #leaderboard-rows .team-composition { │
│ + justify-content: center !important; │
│ + } │
│ + │
│ + #leaderboard-rows .rank, │
│ + #leaderboard-rows .duration, │
│ + #leaderboard-rows .mythic-rating, │
│ + #leaderboard-rows .completed-time { │
│ + font-size: 1.1em !important; │
│ + margin: 5px 0 !important; │
│ + } │
│ + } │
│ +</style> │
│ + │
│ +<script is:inline define:vars={{ baseUrl, i │
│ nitialRegion, initialRealm, initialDungeon } │
│ }> │
│ + const DATA_MAP = { │
│ + us: { │
│ + name: "US", │
│ + realms: { │
│ + angerforge: "Angerforge", │
│ + arugal: "Arugal", │
│ + ashkandi: "Ashkandi", │
│ + atiesh: "Atiesh", │
│ + azursong: "Azuresong", │
│ + benediction: "Benediction", │
│ + bloodsailBuccaneers: "Bloodsail Buc │
│ caneers", │
│ + earthfury: "Earthfury", │
│ + eranikus: "Eranikus", │
│ + faerlina: "Faerlina", │
│ + grobbulus: "Grobbulus", │
│ + maladath: "Maladath", │
│ + mankrik: "Mankrik", │
│ + myzrael: "Myzrael", │
│ + old_blanchy: "Old Blanchy", │
│ + pagle: "Pagle", │
│ + remulos: "Remulos", │
│ + skyfury: "Skyfury", │
│ + sulfuras: "Sulfuras", │
│ + westfall: "Westfall", │
│ + whitemane: "Whitemane", │
│ + windseeker: "Windseeker", │
│ + yojamba: "Yojamba", │
│ + }, │
│ + }, │
│ + eu: { │
│ + name: "EU", │
│ + realms: { │
│ + amnennar: "Amnennar", │
│ + ashbringer: "Ashbringer", │
│ + auberdine: "Auberdine", │
│ + chromie: "Chromie", │
│ + earthshaker: "Earthshaker", │
│ + everlook: "Everlook", │
│ + firemaw: "Firemaw", │
│ + flamegor: "Flamegor", │
│ + gehennas: "Gehennas", │
│ + giantstalker: "Giantstalker", │
│ + golemagg: "Golemagg", │
│ + hydraxianWaterlords: "Hydraxian Wat │
│ erlords", │
│ + jindo: "Jin'do", │
│ + lakeshire: "Lakeshire", │
│ + mandokir: "Mandokir", │
│ + mirageRaceway: "Mirage Raceway", │
│ + mograine: "Mograine", │
│ + nethergardeKeep: "Nethergarde Keep" │
│ , │
│ + patchwerk: "Patchwerk", │
│ + pyrewoodVillage: "Pyrewood Village" │
│ , │
│ + razorfen: "Razorfen", │
│ + sulfuron: "Sulfuron", │
│ + thekal: "Thekal", │
│ + transcendence: "Transcendence", │
│ + venoxis: "Venoxis", │
│ + }, │
│ + }, │
│ + kr: { │
│ + name: "KR", │
│ + realms: { │
│ + frostmourne: "Frostmourne", │
│ + iceblood: "Iceblood", │
│ + lokholar: "Lok'holar", │
│ + ragnaros: "Ragnaros", │
│ + shimmeringFlats: "Shimmering Flats" │
│ , │
│ + }, │
│ + }, │
│ + }; │
│ + // Dungeon name mapping │
│ + const dungeonNames = { │
│ + "2": "Temple of the Jade Serpent", │
│ + "56": "Stormstout Brewery", │
│ + "57": "Gate of the Setting Sun", │
│ + "58": "Shado-Pan Monastery", │
│ + "59": "Siege of Niuzao Temple", │
│ + "60": "Mogu'shan Palace", │
│ + "76": "Scholomance", │
│ + "77": "Scarlet Halls", │
│ + "78": "Scarlet Monastery", │
│ + }; │
│ + │
│ + function formatDuration(milliseconds) { │
│ + const totalSeconds = Math.floor(millise │
│ conds / 1000); │
│ + const minutes = Math.floor(totalSeconds │
│ / 60); │
│ + const seconds = totalSeconds % 60; │
│ + return `${minutes}:${seconds.toString() │
│ .padStart(2, "0")}`; │
│ + } │
│ + │
│ + function formatTimestamp(timestamp) { │
│ + return new Date(timestamp).toLocaleDate │
│ String("en-US", { │
│ + month: "short", │
│ + day: "numeric", │
│ + hour: "2-digit", │
│ + minute: "2-digit", │
│ + }); │
│ + } │
│ + │
│ + function createLeaderboardRow(group) { │
│ + console.log("Creating row for group:", │
│ group); │
│ + │
│ + const row = document.createElement("div │
│ "); │
│ + row.className = "leaderboard-row"; │
│ + │
│ + let teamHtml = ""; │
│ + group.members.forEach((member) => { │
│ + // Handle missing specialization data │
│ (deleted/transferred characters) │
│ + const specId = member.specialization? │
│ .id; │
│ + const spec = specId │
│ + ? window.WoW.getSpecInfo(specId) │
│ + : { │
│ + role: "dps", │
│ + class: "Unknown", │
│ + spec: "Unknown", │
│ + }; │
│ + const iconUrl = window.WoW.getSpecIco │
│ n(spec.class, spec.spec); │
│ + const iconHtml = iconUrl │
│ + ? `<img src="${iconUrl}" alt="${spe │
│ c.spec} ${spec.class}" style="width: 16px; h │
│ eight: 16px; border-radius: 2px; margin-righ │
│ t: 4px; vertical-align: middle; flex-shrink: │
│ 0;">` │
│ + : ""; │
│ + │
│ + teamHtml += `<span style="display: in │
│ line-flex; align-items: center; margin-right │
│ : 8px; gap: 4px;"> │
│ + ${iconHtml} │
│ + <span style="color: ${window.WoW.ge │
│ tClassColor(spec.class)}; font-weight: 600; │
│ font-size: 0.9em;">${member.profile.name}</s │
│ pan> │
│ + </span>`; │
│ + }); │
│ + │
│ + row.innerHTML = ` │
│ + <div style="display: table-cell; │
│ padding: 15px 10px; vertical-align: middle; │
│ font-size: 1.2em; font-weight: bold; color: │
│ #d8a657; text-align: center; width: 80px;"># │
│ ${group.ranking}</div> │
│ + <div style="display: table-cell; │
│ padding: 15px 10px; vertical-align: middle; │
│ font-family: 'Courier New', monospace; font- │
│ weight: bold; color: #ffffff; text-align: ri │
│ ght; width: 120px;">${formatDuration(group.d │
│ uration)}</div> │
│ + <div style="display: table-cell; │
│ padding: 15px 10px; vertical-align: middle;" │
│ >${teamHtml}</div> │
│ + <div style="display: table-cell; │
│ padding: 15px 10px; vertical-align: middle; │
│ color: #aaaaaa; font-size: 0.9em; text-align │
│ : center; width: 140px;">${formatTimestamp(g │
│ roup.completed_timestamp)}</div> │
│ + `; │
│ + │
│ + // Apply table-row styles │
│ + row.style.cssText = ` │
│ + display: table-row !important; │
│ + border-bottom: 1px solid #4a4a4a │
│ !important; │
│ + background-color: #32302f; │
│ + transition: background-color 0.2s │
│ ease; │
│ + `; │
│ + │
│ + // Add hover effect │
│ + row.addEventListener("mouseenter", () = │
│ > { │
│ + row.style.backgroundColor = "rgba(255 │
│ , 215, 0, 0.05)"; │
│ + }); │
│ + row.addEventListener("mouseleave", () = │
│ > { │
│ + row.style.backgroundColor = "#32302f" │
│ ; │
│ + }); │
│ + │
│ + console.log("Row created:", row); │
│ + return row; │
│ + } │
│ + │
│ + function displayLeaderboard(data) { │
│ + console.log("Displaying leaderboard dat │
│ a:", data); │
│ + │
│ + // Check if we have no data (missing or │
│ empty leading_groups) │
│ + if (!data.leading_groups || data.leadin │
│ g_groups.length === 0) { │
│ + showNoData(); │
│ + return; │
│ + } │
│ + │
│ + // Handle localized dungeon names │
│ + const dungeonName = │
│ + typeof data.map.name === "object" │
│ + ? data.map.name.en_US || data.map.n │
│ ame │
│ + : data.map.name; │
│ + document.getElementById("dungeon-name") │
│ .textContent = dungeonName; │
│ + document.getElementById("period").textC │
│ ontent = data.period; │
│ + document.getElementById("period-start") │
│ .textContent = formatTimestamp( │
│ + data.period_start_timestamp, │
│ + ); │
│ + document.getElementById("period-end").t │
│ extContent = formatTimestamp( │
│ + data.period_end_timestamp, │
│ + ); │
│ + │
│ + const currentRegion = document.getEleme │
│ ntById("region").value; │
│ + const currentRealm = document.getElemen │
│ tById("realm").value; │
│ + const regionData = DATA_MAP[currentRegi │
│ on]; │
│ + const realmName = regionData?.realms?.[ │
│ currentRealm] || currentRealm; │
│ + document.getElementById("realm-name").t │
│ extContent = realmName; │
│ + │
│ + const rowsContainer = document.getEleme │
│ ntById("leaderboard-rows"); │
│ + rowsContainer.innerHTML = ""; │
│ + │
│ + console.log("Creating rows for", data.l │
│ eading_groups.length, "groups"); │
│ + data.leading_groups.forEach((group, ind │
│ ex) => { │
│ + console.log("Creating row for group", │
│ index + 1); │
│ + const row = createLeaderboardRow(grou │
│ p); │
│ + rowsContainer.appendChild(row); │
│ + }); │
│ + │
│ + document.getElementById("loading").clas │
│ sList.add("hidden"); │
│ + document.getElementById("error").classL │
│ ist.add("hidden"); │
│ + document.getElementById("no-data").clas │
│ sList.add("hidden"); │
│ + document.getElementById("content").clas │
│ sList.remove("content-loading"); │
│ + document.getElementById("content").clas │
│ sList.remove("hidden"); │
│ + │
│ + console.log("Leaderboard displayed succ │
│ essfully"); │
│ + } │
│ + │
│ + function showNoData() { │
│ + document.getElementById("loading").clas │
│ sList.add("hidden"); │
│ + document.getElementById("error").classL │
│ ist.add("hidden"); │
│ + document.getElementById("no-data").clas │
│ sList.remove("hidden"); │
│ + document.getElementById("content").clas │
│ sList.add("hidden"); │
│ + } │
│ + │
│ + function showError(message) { │
│ + const errorDiv = document.getElementByI │
│ d("error"); │
│ + const loadingMessage = errorDiv.querySe │
│ lector(".loading-message"); │
│ + if (loadingMessage) { │
│ + loadingMessage.textContent = message; │
│ + } else { │
│ + // Fallback if LoadingState structure │
│ isn't found │
│ + errorDiv.innerHTML = `<div class="loa │
│ ding-state error"><p class="loading-message" │
│ >${message}</p></div>`; │
│ + } │
│ + errorDiv.classList.remove("hidden"); │
│ + document.getElementById("loading").clas │
│ sList.add("hidden"); │
│ + document.getElementById("no-data").clas │
│ sList.add("hidden"); │
│ + document.getElementById("content").clas │
│ sList.add("hidden"); │
│ + } │
│ + │
│ + function showLoading() { │
│ + document.getElementById("loading").clas │
│ sList.remove("hidden"); │
│ + document.getElementById("error").classL │
│ ist.add("hidden"); │
│ + document.getElementById("no-data").clas │
│ sList.add("hidden"); │
│ + document.getElementById("content").clas │
│ sList.add("content-loading"); │
│ + } │
│ + │
│ + function updateRealmOptions() { │
│ + const region = document.getElementById( │
│ "region").value; │
│ + const realmSelect = document.getElement │
│ ById("realm"); │
│ + │
│ + // Clear existing options │
│ + realmSelect.innerHTML = ""; │
│ + │
│ + // Add realms for selected region │
│ + const regionData = DATA_MAP[region]; │
│ + if (regionData && regionData.realms) { │
│ + Object.entries(regionData.realms).for │
│ Each(([slug, name]) => { │
│ + const option = document.createEleme │
│ nt("option"); │
│ + option.value = slug; │
│ + option.textContent = name; │
│ + realmSelect.appendChild(option); │
│ + }); │
│ + } │
│ + } │
│ + │
│ + function getDataFileName() { │
│ + const region = document.getElementById( │
│ "region").value; │
│ + const realm = document.getElementById(" │
│ realm").value; │
│ + const dungeon = document.getElementById │
│ ("dungeon").value; │
│ + │
│ + console.log('Getting data filename with │
│ :', { region, realm, dungeon }); │
│ + console.log('Dungeon name lookup:', dun │
│ geonNames[dungeon]); │
│ + │
│ + const dungeonName = dungeonNames[dungeo │
│ n] │
│ + .toLowerCase() │
│ + .replace(/[^a-z0-9]/g, "-"); │
│ + const fileName = `${region}/${realm}/${ │
│ dungeonName}/${realm}-${dungeonName}-leaderb │
│ oard.json`; │
│ + console.log('Generated filename:', file │
│ Name); │
│ + return fileName; │
│ + } │
│ + │
│ + async function loadLeaderboard() { │
│ + showLoading(); │
│ + │
│ + try { │
│ + const fileName = getDataFileName(); │
│ + console.log("Loading file:", fileName │
│ ); │
│ + const fullUrl = `${baseUrl}data/chall │
│ enge-mode/${fileName}`.replace('//', '/'); │
│ + console.log("Full fetch URL:", fullUr │
│ l); │
│ + const response = await fetch(fullUrl) │
│ ; │
│ + │
│ + if (!response.ok) { │
│ + throw new Error( │
│ + `Failed to load leaderboard data: │
│ ${response.statusText}`, │
│ + ); │
│ + } │
│ + │
│ + const data = await response.json(); │
│ + console.log("Data loaded:", data); │
│ + displayLeaderboard(data); │
│ + } catch (error) { │
│ + console.error("Error loading leaderbo │
│ ard:", error); │
│ + showError(`Error loading leaderboard │
│ data: ${error.message}`); │
│ + } │
│ + } │
│ + │
│ + function updateURL() { │
│ + const region = document.getElementById( │
│ "region").value; │
│ + const realm = document.getElementById(" │
│ realm").value; │
│ + const dungeon = document.getElementById │
│ ("dungeon").value; │
│ + │
│ + const dungeonName = dungeonNames[dungeo │
│ n] │
│ + .toLowerCase() │
│ + .replace(/[^a-z0-9]/g, "-"); │
│ + │
│ + const newURL = `/challenge-mode/${regio │
│ n}/${realm}/${dungeonName}`; │
│ + window.history.pushState({}, '', newURL │
│ ); │
│ + } │
│ + │
│ + // Event listeners │
│ + document.addEventListener("DOMContentLoad │
│ ed", () => { │
│ + // Initialize realm options │
│ + updateRealmOptions(); │
│ + │
│ + // Set initial values from route params │
│ + document.getElementById("region").value │
│ = initialRegion; │
│ + updateRealmOptions(); │
│ + document.getElementById("realm").value │
│ = initialRealm; │
│ + document.getElementById("dungeon").valu │
│ e = initialDungeon; │
│ + │
│ + // Load initial data │
│ + loadLeaderboard(); │
│ + │
│ + // Bind change events │
│ + document.getElementById("region").addEv │
│ entListener("change", () => { │
│ + updateRealmOptions(); │
│ + updateURL(); │
│ + loadLeaderboard(); │
│ + }); │
│ + document.getElementById("realm").addEve │
│ ntListener("change", () => { │
│ + updateURL(); │
│ + loadLeaderboard(); │
│ + }); │
│ + document.getElementById("dungeon").addE │
│ ventListener("change", () => { │
│ + updateURL(); │
│ + loadLeaderboard(); │
│ + }); │
│ + }); │
│ +</script> │
│ \ No newline at end of file │
└──────────────────────────────────────────────┘