OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      678d3bf1180c
DATE      2025-08-31
SUBJECT   web: new dungeon leaderboard options panel
FILES     3 CHANGED
HASH      678d3bf1180c
DATE      2025-08-31
SUBJECT   web: new dungeon leaderboard options
          panel
FILES     3 CHANGED
 

diff --git a/web/src/components/HorizontalSelector.astro b/web/src/components/Hori
zontalSelector.astro
new file mode 100644
index 0000000..eb2d0ad
--- /dev/null
+++ b/web/src/components/HorizontalSelector.astro
@@ -0,0 +1,126 @@
+---
+export interface Props {
+  id: string;
+  label: string;
+  options: Array<{ value: string; label: string; selected?: boolean }>;
+  class?: string;
+}
+
+const { id, label, options, class: className } = Astro.props;
+---
+
+<div class={`form-group horizontal-selector ${className || ""}`.trim()}>
+  <label class="form-label">{label}:</label>
+  <div class="horizontal-options" data-selector-id={id}>
+    {
+      options.map((option) => (
+        <button
+          type="button"
+          class={`horizontal-option ${option.selected ? "selected" : ""}`}
+          data-value={option.value}
+          data-selector-target={id}
+        >
+          {option.label}
+        </button>
+      ))
+    }
+  </div>
+  <select id={id} class="form-select" style="display: none;">
+    {
+      options.map((option) => (
+        <option value={option.value} selected={option.selected}>
+          {option.label}
+        </option>
+      ))
+    }
+  </select>
+</div>
+
+<style>
+  .horizontal-selector {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .horizontal-options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    align-items: center;
+  }
+
+  .horizontal-option {
+    padding: 8px 16px;
+    background-color: var(--bg-primary);
+    border: 1px solid var(--border-color);
+    border-radius: 6px;
+    color: var(--text-primary);
+    font-size: 0.9em;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    white-space: nowrap;
+  }
+
+  .horizontal-option:hover {
+    background-color: var(--bg-selection);
+    border-color: var(--highlight-color);
+  }
+
+  .horizontal-option.selected {
+    background-color: var(--highlight-color);
+    border-color: var(--highlight-color);
+    color: #000;
+    font-weight: 600;
+  }
+
+  .horizontal-option:focus {
+    outline: none;
+    box-shadow: 0 0 0 2px rgba(218, 165, 32, 0.2);
+  }
+
+  /* Mobile responsiveness */
+  @media (max-width: 768px) {
+    .horizontal-options {
+      gap: 6px;
+    }
+
+    .horizontal-option {
+      padding: 6px 12px;
+      font-size: 0.8em;
+    }
+  }
+</style>
+
+<script>
+  // Handle horizontal option selection
+  document.addEventListener("DOMContentLoaded", () => {
+    document.querySelectorAll("[data-selector-target]").forEach((button) => {
+      button.addEventListener("click", (e) => {
+        const button = e.target as HTMLButtonElement;
+        const selectorId = button.dataset.selectorTarget;
+        const value = button.dataset.value;
+
+        if (!selectorId) return;
+
+        // update hidden select
+        const select = document.getElementById(selectorId) as HTMLSelectElement;
+        if (select) {
+          select.value = value || "";
+          // trigger change event
+          select.dispatchEvent(new Event("change"));
+        }
+
+        // update visual state
+        const container = button.closest(".horizontal-options");
+        if (container) {
+          container.querySelectorAll(".horizontal-option").forEach((opt) => {
+            opt.classList.remove("selected");
+          });
+          button.classList.add("selected");
+        }
+      });
+    });
+  });
+</script>
+

diff --git a/web/src/components/HorizontalSe
lector.astro b/web/src/components/Horizontal
Selector.astro
new file mode 100644
index 0000000..eb2d0ad
--- /dev/null
+++ b/web/src/components/HorizontalSelector.
astro
@@ -0,0 +1,126 @@
+---
+export interface Props {
+  id: string;
+  label: string;
+  options: Array<{ value: string; label: st
ring; selected?: boolean }>;
+  class?: string;
+}
+
+const { id, label, options, class: classNam
e } = Astro.props;
+---
+
+<div class={`form-group horizontal-selector
 ${className || ""}`.trim()}>
+  <label class="form-label">{label}:</label
>
+  <div class="horizontal-options" data-sele
ctor-id={id}>
+    {
+      options.map((option) => (
+        <button
+          type="button"
+          class={`horizontal-option ${optio
n.selected ? "selected" : ""}`}
+          data-value={option.value}
+          data-selector-target={id}
+        >
+          {option.label}
+        </button>
+      ))
+    }
+  </div>
+  <select id={id} class="form-select" style
="display: none;">
+    {
+      options.map((option) => (
+        <option value={option.value} select
ed={option.selected}>
+          {option.label}
+        </option>
+      ))
+    }
+  </select>
+</div>
+
+<style>
+  .horizontal-selector {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .horizontal-options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    align-items: center;
+  }
+
+  .horizontal-option {
+    padding: 8px 16px;
+    background-color: var(--bg-primary);
+    border: 1px solid var(--border-color);
+    border-radius: 6px;
+    color: var(--text-primary);
+    font-size: 0.9em;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    white-space: nowrap;
+  }
+
+  .horizontal-option:hover {
+    background-color: var(--bg-selection);
+    border-color: var(--highlight-color);
+  }
+
+  .horizontal-option.selected {
+    background-color: var(--highlight-color
);
+    border-color: var(--highlight-color);
+    color: #000;
+    font-weight: 600;
+  }
+
+  .horizontal-option:focus {
+    outline: none;
+    box-shadow: 0 0 0 2px rgba(218, 165, 32
, 0.2);
+  }
+
+  /* Mobile responsiveness */
+  @media (max-width: 768px) {
+    .horizontal-options {
+      gap: 6px;
+    }
+
+    .horizontal-option {
+      padding: 6px 12px;
+      font-size: 0.8em;
+    }
+  }
+</style>
+
+<script>
+  // Handle horizontal option selection
+  document.addEventListener("DOMContentLoad
ed", () => {
+    document.querySelectorAll("[data-select
or-target]").forEach((button) => {
+      button.addEventListener("click", (e) 
=> {
+        const button = e.target as HTMLButt
onElement;
+        const selectorId = button.dataset.s
electorTarget;
+        const value = button.dataset.value;
+
+        if (!selectorId) return;
+
+        // update hidden select
+        const select = document.getElementB
yId(selectorId) as HTMLSelectElement;
+        if (select) {
+          select.value = value || "";
+          // trigger change event
+          select.dispatchEvent(new Event("c
hange"));
+        }
+
+        // update visual state
+        const container = button.closest(".
horizontal-options");
+        if (container) {
+          container.querySelectorAll(".hori
zontal-option").forEach((opt) => {
+            opt.classList.remove("selected"
);
+          });
+          button.classList.add("selected");
+        }
+      });
+    });
+  });
+</script>
+
 

diff --git a/web/src/layouts/ChallengeModeLayout.astro b/web/src/layouts/Challenge
ModeLayout.astro
index c95c045..97d6690 100644
--- a/web/src/layouts/ChallengeModeLayout.astro
+++ b/web/src/layouts/ChallengeModeLayout.astro
@@ -2,6 +2,7 @@
 import PageLayout from "../components/PageLayout.astro";
 import ControlPanel from "../components/ControlPanel.astro";
 import FormSelect from "../components/FormSelect.astro";
+import HorizontalSelector from "../components/HorizontalSelector.astro";
 import LoadingState from "../components/LoadingState.astro";
 import WoWClassColors from "../components/WoWClassColors.astro";
 import { DATA_MAP } from "../data/realms.js";
@@ -283,15 +284,10 @@ const baseUrl = import.meta.env.BASE_URL;
         label="Realm"
         options={[{ value: "", label: "Select Region First", selected: true }]}
       />
-      <FormSelect
+      <HorizontalSelector
         id="dungeon"
         label="Dungeon"
         options={[
-          {
-            value: "",
-            label: "Select Dungeon",
-            selected: initialDungeon === "",
-          },
           {
             value: "2",
             label: "Temple of the Jade Serpent",

diff --git a/web/src/layouts/ChallengeModeLa
yout.astro b/web/src/layouts/ChallengeModeLa
yout.astro
index c95c045..97d6690 100644
--- a/web/src/layouts/ChallengeModeLayout.as
tro
+++ b/web/src/layouts/ChallengeModeLayout.as
tro
@@ -2,6 +2,7 @@
 import PageLayout from "../components/PageL
ayout.astro";
 import ControlPanel from "../components/Con
trolPanel.astro";
 import FormSelect from "../components/FormS
elect.astro";
+import HorizontalSelector from "../componen
ts/HorizontalSelector.astro";
 import LoadingState from "../components/Loa
dingState.astro";
 import WoWClassColors from "../components/W
oWClassColors.astro";
 import { DATA_MAP } from "../data/realms.js
";
@@ -283,15 +284,10 @@ const baseUrl = import
.meta.env.BASE_URL;
         label="Realm"
         options={[{ value: "", label: "Sele
ct Region First", selected: true }]}
       />
-      <FormSelect
+      <HorizontalSelector
         id="dungeon"
         label="Dungeon"
         options={[
-          {
-            value: "",
-            label: "Select Dungeon",
-            selected: initialDungeon === ""
,
-          },
           {
             value: "2",
             label: "Temple of the Jade Serp
ent",
 

diff --git a/web/src/layouts/TeamLeaderboardLayout.astro b/web/src/layouts/TeamLea
derboardLayout.astro
deleted file mode 100644
index 52574e4..0000000
--- a/web/src/layouts/TeamLeaderboardLayout.astro
+++ /dev/null
@@ -1,730 +0,0 @@
----
-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";
-import "../styles/components.scss";
-
-export interface Props {
-  initialMode?: string;
-}
-
-const { initialMode = "players" } = Astro.props;
-const baseUrl = import.meta.env.BASE_URL;
----
-
-<PageLayout
-  title="Team Leaderboards"
-  description=""
-  pageTitle="Team Leaderboards - MoP Classic"
->
-  <WoWClassColors />
-
-  <style>
-    .leaderboard-container {
-      overflow: hidden;
-      margin-top: 20px;
-    }
-
-    .loading-overlay,
-    .error-overlay {
-      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 p {
-      color: var(--text-secondary);
-      margin: 0;
-      font-size: 1.1em;
-    }
-
-    #team-rows {
-      display: table;
-      width: 100%;
-      table-layout: fixed;
-      border-spacing: 0;
-      border-collapse: collapse;
-    }
-
-    @media (max-width: 1024px) {
-      #team-rows .leaderboard-row {
-        display: block !important;
-        padding: 15px;
-      }
-
-      #team-rows .leaderboard-row > div {
-        display: block;
-        padding: 5px 0;
-        text-align: center;
-      }
-    }
-
-    /* Callout styles */
-    .callout {
-      background-color: #2a3a3a;
-      border: 1px solid #458588;
-      border-left: 4px solid #458588;
-      border-radius: 6px;
-      padding: 16px;
-      margin-bottom: 20px;
-    }
-
-    .callout-content {
-      color: var(--text-primary);
-      line-height: 1.5;
-      margin: 0;
-    }
-  </style>
-
-  <style is:global>
-    /* Global styles for dynamically generated team leaderboard content */
-    #team-rows .leaderboard-row,
-    #team-rows .chart-item-header {
-      display: table-row !important;
-      border-bottom: 1px solid #4a4a4a !important;
-      background-color: #32302f !important;
-      transition: background-color 0.2s ease !important;
-    }
-
-    #team-rows .leaderboard-row:hover,
-    #team-rows .chart-item-header:hover {
-      background-color: rgba(255, 215, 0, 0.05) !important;
-    }
-
-    /* Expandable chart item styles (copied from ChartLayout.astro) */
-    .chart-item-wrapper {
-      border-radius: 6px;
-      transition: background-color 0.2s ease;
-    }
-
-    .chart-item-header {
-      cursor: pointer;
-      padding: 8px 12px;
-      border-radius: 6px;
-      transition: background-color 0.2s ease;
-    }
-
-    .chart-item-header:hover {
-      background-color: rgba(255, 255, 255, 0.05);
-    }
-
-    .chart-item-expanded .chart-item-header {
-      background-color: rgba(255, 255, 255, 0.08);
-    }
-
-    .chart-expand-icon {
-      margin-left: 8px;
-      font-size: 0.8em;
-      color: var(--text-muted);
-      transition: transform 0.2s ease;
-      user-select: none;
-    }
-
-    .chart-item-expanded .chart-expand-icon {
-      transform: rotate(90deg);
-    }
-
-    .chart-dropdown {
-      margin-top: 12px;
-      padding: 16px;
-      background-color: var(--bg-selection);
-      border-radius: 6px;
-      border: 1px solid rgba(255, 255, 255, 0.1);
-      display: none;
-    }
-
-    .chart-item-expanded .chart-dropdown {
-      display: block;
-      animation: dropdown-expand 0.2s ease-out;
-    }
-
-    @keyframes dropdown-expand {
-      from {
-        opacity: 0;
-        transform: translateY(-8px);
-      }
-      to {
-        opacity: 1;
-        transform: translateY(0);
-      }
-    }
-  </style>
-
-  <ControlPanel title="Leaderboard Options">
-    <FormSelect
-      id="leaderboard-mode"
-      label="Leaderboard Mode"
-      options={[
-        {
-          value: "teams",
-          label: "Teams",
-          selected: initialMode === "teams",
-        },
-        {
-          value: "players",
-          label: "Players",
-          selected: initialMode === "players",
-        },
-      ]}
-    />
-  </ControlPanel>
-
-  <div class="callout">
-    <div class="callout-content">
-      <p id="leaderboard-description">
-        <strong>Team Leaderboards</strong> identify 3-player cores that have comp
leted
-        ALL 9 dungeons. Each team shows their core members plus extended roster.
-        Rankings are based on the sum of their best times across all dungeons.
-      </p>
-    </div>
-  </div>
-
-  <div id="loading" class="loading-overlay hidden">
-    <LoadingState message="Loading team 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 class="section-title-large">No Teams Found</h3>
-        <p>
-          No team data was found. Please ensure the team leaderboard generator
-          has been run.
-        </p>
-      </div>
-    </div>
-  </div>
-
-  <div id="content" class="hidden">
-    <div class="card">
-      <div class="info-grid">
-        <div class="info-item">
-          <span class="info-label">Leaderboard</span>
-          <span class="info-value" id="leaderboard-title">Loading...</span>
-        </div>
-        <div class="info-item">
-          <span class="info-label">Generated</span>
-          <span class="info-value" id="generated-time">-</span>
-        </div>
-      </div>
-    </div>
-
-    <div class="leaderboard-container card">
-      <div id="team-rows"></div>
-    </div>
-  </div>
-</PageLayout>
-
-<script
-  is:inline
-  define:vars={{
-    baseUrl,
-    initialMode,
-  }}
->
-  function formatDuration(milliseconds) {
-    const totalSeconds = Math.floor(milliseconds / 1000);
-    const minutes = Math.floor(totalSeconds / 60);
-    const seconds = totalSeconds % 60;
-    const ms = milliseconds % 1000;
-    return (
-      minutes +
-      ":" +
-      seconds.toString().padStart(2, "0") +
-      "." +
-      ms.toString().padStart(3, "0")
-    );
-  }
-
-  function formatTimestamp(timestamp) {
-    return new Date(timestamp).toLocaleDateString("en-US", {
-      month: "short",
-      day: "numeric",
-      hour: "2-digit",
-      minute: "2-digit",
-    });
-  }
-
-  // Global function for chart item expansion (similar to chart-logic.js)
-  window.toggleChartItem = (element) => {
-    const wrapper = element.closest(".chart-item-wrapper");
-    if (!wrapper) return;
-
-    const isExpanded = wrapper.classList.contains("chart-item-expanded");
-
-    // Close all other expanded items
-    document.querySelectorAll(".chart-item-expanded").forEach((item) => {
-      if (item !== wrapper) {
-        item.classList.remove("chart-item-expanded");
-      }
-    });
-
-    // Toggle current item
-    wrapper.classList.toggle("chart-item-expanded", !isExpanded);
-  };
-
-  function getSpecColor(specId) {
-    // WoW class colors based on spec_id
-    const specColors = {
-      // Death Knight
-      250: "#C41F3B",
-      251: "#C41F3B",
-      252: "#C41F3B",
-      // Druid
-      102: "#FF7D0A",
-      103: "#FF7D0A",
-      104: "#FF7D0A",
-      105: "#FF7D0A",
-      // Hunter
-      253: "#ABD473",
-      254: "#ABD473",
-      255: "#ABD473",
-      // Mage
-      62: "#69CCF0",
-      63: "#69CCF0",
-      64: "#69CCF0",
-      // Monk
-      268: "#00FF96",
-      269: "#00FF96",
-      270: "#00FF96",
-      // Paladin
-      65: "#F58CBA",
-      66: "#F58CBA",
-      70: "#F58CBA",
-      // Priest
-      256: "#FFFFFF",
-      257: "#FFFFFF",
-      258: "#FFFFFF",
-      // Rogue
-      259: "#FFF569",
-      260: "#FFF569",
-      261: "#FFF569",
-      // Shaman
-      262: "#0070DE",
-      263: "#0070DE",
-      264: "#0070DE",
-      // Warlock
-      265: "#9482C9",
-      266: "#9482C9",
-      267: "#9482C9",
-      // Warrior
-      71: "#C79C6E",
-      72: "#C79C6E",
-      73: "#C79C6E",
-    };
-    return specColors[specId] || "#FFFFFF";
-  }
-
-  function createMemberLookup(team) {
-    // Create a lookup map from member names to their full member data with spec_
id
-    const memberLookup = {};
-
-    // Process all runs to build a comprehensive lookup
-    team.all_runs.forEach((run) => {
-      run.members.forEach((member) => {
-        if (!memberLookup[member.name]) {
-          memberLookup[member.name] = {
-            name: member.name,
-            realm_slug: member.realm_slug,
-            spec_id: member.spec_id,
-            id: member.id,
-          };
-        }
-      });
-    });
-
-    return memberLookup;
-  }
-
-  function createTeamComposition(memberNames, memberLookup) {
-    // Create styled team composition similar to Challenge Mode
-    return memberNames
-      .map((memberName) => {
-        const member = memberLookup[memberName];
-        if (!member) {
-          // Fallback if member not found in lookup
-          return `<span style="color: var(--text-primary); margin-right: 8px;">${
memberName}</span>`;
-        }
-
-        const spec = member.spec_id
-          ? window.WoW.getSpecInfo(member.spec_id)
-          : null;
-        const iconUrl = spec
-          ? window.WoW.getSpecIcon(spec.class, spec.spec)
-          : null;
-        const classColor = spec
-          ? window.WoW.getClassColor(spec.class)
-          : "#FFFFFF";
-
-        const iconHtml = iconUrl
-          ? `<img src="${iconUrl}" alt="${spec.spec} ${spec.class}" style="width:
 16px; height: 16px; border-radius: 2px; margin-right: 4px; vertical-align: middle
; flex-shrink: 0;">`
-          : "";
-
-        return `<span style="display: inline-flex; align-items: center; margin-ri
ght: 8px; gap: 4px;">
-        ${iconHtml}
-        <span style="color: ${classColor}; font-weight: 600; font-size: 0.9em;">$
{member.name}@${member.realm_slug}</span>
-      </span>`;
-      })
-      .join("");
-  }
-
-  function createTeamRow(team) {
-    const wrapper = document.createElement("div");
-    wrapper.className = "chart-item-wrapper";
-
-    // Create extended roster display with spec colors
-    const extendedRoster = team.extended_roster
-      .map((member) => {
-        const specColor = member.spec_id
-          ? getSpecColor(member.spec_id)
-          : "#FFFFFF";
-        return `<span style="display: inline-flex; align-items: center; margin-ri
ght: 8px; gap: 4px;">
-        <span style="color: ${specColor}; font-weight: 600; font-size: 0.9em;">${
member.name}</span>
-        <span style="color: var(--text-secondary); font-size: 0.8em;">@${member.r
ealm_slug}</span>
-      </span>`;
-      })
-      .join("");
-
-    // Create the team details content
-    const teamDetails = createTeamDetailsContent(team);
-
-    wrapper.innerHTML = `
-      <div class="leaderboard-row chart-item-header" onclick="toggleChartItem(thi
s)" style="display: table-row !important; border-bottom: 1px solid #4a4a4a !import
ant; background-color: #32302f; transition: background-color 0.2s ease; cursor: po
inter; width: 100%;">
-        <div style="display: table-cell; padding: 15px 10px; vertical-align: midd
le; font-size: 1.2em; font-weight: bold; color: #d8a657; text-align: center; width
: 80px;">#${team.ranking}</div>
-        <div style="display: table-cell; padding: 15px 10px; vertical-align: midd
le; font-family: 'Courier New', monospace; font-weight: bold; color: #ffffff; text
-align: right; width: 120px;">${formatDuration(team.combined_best_time)}</div>
-        <div style="display: table-cell; padding: 15px 10px; vertical-align: midd
le; display: flex; align-items: center; overflow: hidden;">
-          <span class="chart-expand-icon" style="margin-right: 8px; flex-shrink: 
0;">?</span>
-          <div style="display: flex; flex-wrap: wrap; align-items: center; overfl
ow: hidden; min-width: 0;">${extendedRoster}</div>
-        </div>
-      </div>
-      <div class="chart-dropdown">
-        ${teamDetails}
-      </div>
-    `;
-
-    return wrapper;
-  }
-
-  function createPlayerRow(player) {
-    const wrapper = document.createElement("div");
-    wrapper.className = "chart-item-wrapper";
-
-    // Get player spec info for display
-    const spec = player.main_spec_id
-      ? window.WoW.getSpecInfo(player.main_spec_id)
-      : null;
-    const iconUrl = spec ? window.WoW.getSpecIcon(spec.class, spec.spec) : null;
-    const classColor = spec ? window.WoW.getClassColor(spec.class) : "#FFFFFF";
-
-    const iconHtml = iconUrl
-      ? `<img src="${iconUrl}" alt="${spec.spec} ${spec.class}" style="width: 20p
x; height: 20px; border-radius: 2px; margin-right: 8px; vertical-align: middle; fl
ex-shrink: 0;">`
-      : "";
-
-    // Create the player details content
-    const playerDetails = createPlayerDetailsContent(player);
-
-    wrapper.innerHTML = `
-      <div class="leaderboard-row chart-item-header" onclick="toggleChartItem(thi
s.parentElement)" style="display: table-row !important; border-bottom: 1px solid #
4a4a4a !important; transition: background-color 0.2s ease !important; cursor: poin
ter;">
-        <div style="display: table-cell; padding: 15px 10px; vertical-align: midd
le; font-size: 1.2em; font-weight: bold; color: #d8a657; text-align: center; width
: 80px;">#${player.ranking}</div>
-        <div style="display: table-cell; padding: 15px 10px; vertical-align: midd
le; font-family: 'Courier New', monospace; font-weight: bold; color: #ffffff; text
-align: right; width: 120px; white-space: nowrap;">${formatDuration(player.combine
d_best_time)}</div>
-        <div style="display: table-cell; padding: 15px 10px; vertical-align: midd
le; width: 100%;">
-          <div style="display: flex; justify-content: space-between; align-items:
 center; width: 100%;">
-            <div style="display: flex; align-items: center;">
-              <span class="chart-expand-icon" style="margin-right: 8px;">?</span>
-              ${iconHtml}
-              <span style="color: ${classColor}; font-weight: 600; font-size: 1.1
em;">${player.name}</span>
-            </div>
-            <span style="color: var(--text-secondary); font-size: 0.9em;">@${play
er.realm_slug}</span>
-          </div>
-        </div>
-      </div>
-      <div class="chart-dropdown">
-        ${playerDetails}
-      </div>
-    `;
-
-    return wrapper;
-  }
-
-  function createPlayerDetailsContent(player) {
-    // Create dungeon run entries styled like Challenge Mode leaderboard rows
-    const dungeonEntries = Object.entries(player.best_runs_per_dungeon || {})
-      .map(([_dungeonSlug, run]) => {
-        // Create team composition with spec icons using all_members data
-        const teamComposition = (run.all_members || [])
-          .map((member) => {
-            const spec = member.spec_id
-              ? window.WoW.getSpecInfo(member.spec_id)
-              : null;
-            const iconUrl = spec
-              ? window.WoW.getSpecIcon(spec.class, spec.spec)
-              : null;
-            const classColor = spec
-              ? window.WoW.getClassColor(spec.class)
-              : "#FFFFFF";
-
-            const iconHtml = iconUrl
-              ? `<img src="${iconUrl}" alt="${spec.spec} ${spec.class}" style="wi
dth: 16px; height: 16px; border-radius: 2px; margin-right: 4px; vertical-align: mi
ddle; flex-shrink: 0;">`
-              : "";
-
-            return `<span style="display: inline-flex; align-items: center; margi
n-right: 8px; gap: 4px;">
-          ${iconHtml}
-          <span style="color: ${classColor}; font-weight: 600; font-size: 0.9em;"
>${member.name}@${member.realm_slug}</span>
-        </span>`;
-          })
-          .join("");
-
-        return `
-        <div style="display: grid; grid-template-columns: 80px 120px 2fr 100px 14
0px; gap: 20px; align-items: center; padding: 15px 20px; border-bottom: 1px solid 
#3a3a3a; min-height: 60px;">
-          <div style="font-size: 1.2em; font-weight: bold; color: #d8a657; text-a
lign: center;">${run.ranking === "~" ? "~" : "#" + run.ranking}</div>
-          <div style="font-family: 'Courier New', monospace; font-weight: bold; c
olor: #ffffff; text-align: right;">${formatDuration(run.duration)}</div>
-          <div style="display: flex; flex-wrap: wrap; gap: 6px; justify-content: 
flex-start; align-items: center;">${teamComposition}</div>
-          <div style="color: var(--text-primary); text-align: center; font-weight
: 600;">${run.dungeon_name}</div>
-          <div style="color: #aaaaaa; font-size: 0.9em; text-align: center;">${fo
rmatTimestamp(run.completed_timestamp)}</div>
-        </div>
-      `;
-      })
-      .join("");
-
-    return `
-      <div>
-        <h4 style="color: var(--highlight-color); margin-bottom: 10px;">Best Time
s Per Dungeon</h4>
-        <div style="border-radius: 6px; overflow: hidden; border: 1px solid #4a4a
4a;">
-          <div style="display: grid; grid-template-columns: 80px 120px 2fr 100px 
140px; gap: 20px; padding: 15px 20px; background-color: rgba(255, 255, 255, 0.03);
 border-bottom: 1px solid #4a4a4a; font-weight: 600; color: var(--text-secondary);
 font-size: 0.9em; text-transform: uppercase; letter-spacing: 0.5px;">
-            <div>Global Rank</div>
-            <div style="text-align: right;">Time</div>
-            <div>Team Composition</div>
-            <div style="text-align: center;">Dungeon</div>
-            <div style="text-align: center;">Date</div>
-          </div>
-          ${dungeonEntries}
-        </div>
-      </div>
-    `;
-  }
-
-  function createTeamDetailsContent(team) {
-    // Create member lookup for this team
-    const memberLookup = createMemberLookup(team);
-
-    // Create core members display with Challenge Mode styling
-    const coreMembers = team.core_members
-      .map((member) => {
-        const spec = member.spec_id
-          ? window.WoW.getSpecInfo(member.spec_id)
-          : null;
-        const iconUrl = spec
-          ? window.WoW.getSpecIcon(spec.class, spec.spec)
-          : null;
-        const classColor = spec
-          ? window.WoW.getClassColor(spec.class)
-          : "#FFFFFF";
-
-        const iconHtml = iconUrl
-          ? `<img src="${iconUrl}" alt="${spec.spec} ${spec.class}" style="width:
 16px; height: 16px; border-radius: 2px; margin-right: 4px; vertical-align: middle
; flex-shrink: 0;">`
-          : "";
-
-        return `<span style="display: inline-flex; align-items: center; margin-ri
ght: 15px; gap: 4px;">
-        ${iconHtml}
-        <span style="color: ${classColor}; font-weight: 600; font-size: 0.9em;">$
{member.name}@${member.realm_slug}</span>
-      </span>`;
-      })
-      .join("");
-
-    // Create dungeon run entries styled like Challenge Mode leaderboard rows
-    const dungeonEntries = Object.entries(team.best_runs_per_dungeon || {})
-      .map(([_dungeonSlug, run]) => {
-        const teamComposition = createTeamComposition(
-          run.members,
-          memberLookup,
-        );
-
-        return `
-        <div style="display: grid; grid-template-columns: 80px 120px 2fr 100px 14
0px; gap: 20px; align-items: center; padding: 15px 20px; border-bottom: 1px solid 
#3a3a3a; min-height: 60px;">
-          <div style="font-size: 1.2em; font-weight: bold; color: #d8a657; text-a
lign: center;">#${run.ranking}</div>
-          <div style="font-family: 'Courier New', monospace; font-weight: bold; c
olor: #ffffff; text-align: right;">${formatDuration(run.duration)}</div>
-          <div style="display: flex; flex-wrap: wrap; gap: 6px; justify-content: 
flex-start; align-items: center;">${teamComposition}</div>
-          <div style="color: var(--text-primary); text-align: center; font-weight
: 600;">${run.dungeon_name}</div>
-          <div style="color: #aaaaaa; font-size: 0.9em; text-align: center;">${fo
rmatTimestamp(run.completed_timestamp)}</div>
-        </div>
-      `;
-      })
-      .join("");
-
-    return `
-      <div style="margin-bottom: 20px;">
-        <h4 style="color: var(--highlight-color); margin-bottom: 10px;">Core Team
 (3-player consistent core)</h4>
-        <div style="display: flex; flex-wrap: wrap; align-items: center;">${coreM
embers}</div>
-      </div>
-      
-      <div>
-        <h4 style="color: var(--highlight-color); margin-bottom: 10px;">Best Time
s Per Dungeon</h4>
-        <div style="border-radius: 6px; overflow: hidden; border: 1px solid #4a4a
4a;">
-          <div style="display: grid; grid-template-columns: 80px 120px 2fr 100px 
140px; gap: 20px; padding: 15px 20px; background-color: rgba(255, 255, 255, 0.03);
 border-bottom: 1px solid #4a4a4a; font-weight: 600; color: var(--text-secondary);
 font-size: 0.9em; text-transform: uppercase; letter-spacing: 0.5px;">
-            <div>Rank</div>
-            <div style="text-align: right;">Time</div>
-            <div>Team Composition</div>
-            <div style="text-align: center;">Dungeon</div>
-            <div style="text-align: center;">Date</div>
-          </div>
-          ${dungeonEntries}
-        </div>
-      </div>
-    `;
-  }
-
-  function displayLeaderboard(data, mode) {
-    console.log(`Displaying ${mode} leaderboard data:`, data);
-
-    if (!data.leaderboard || data.leaderboard.length === 0) {
-      showNoData();
-      return;
-    }
-
-    document.getElementById("leaderboard-title").textContent = data.title;
-    document.getElementById("generated-time").textContent =
-      data.generated_timestamp
-        ? formatTimestamp(data.generated_timestamp)
-        : "Unknown";
-
-    const rowsContainer = document.getElementById("team-rows");
-    rowsContainer.innerHTML = "";
-
-    data.leaderboard.forEach((item, index) => {
-      // Add ranking based on position in sorted array
-      item.ranking = index + 1;
-
-      const wrapper =
-        mode === "players" ? createPlayerRow(item) : createTeamRow(item);
-      rowsContainer.appendChild(wrapper);
-    });
-
-    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(`${mode} 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;
-    }
-    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");
-  }
-
-  async function loadLeaderboard() {
-    const mode = document.getElementById("leaderboard-mode").value;
-
-    showLoading();
-
-    try {
-      const dataPath =
-        mode === "players" ? "player-leaderboards" : "team-leaderboards";
-      const fileName = "best-overall.json";
-      const fullUrl = `/data/${dataPath}/${fileName}`;
-      console.log(`Loading ${mode} leaderboard from:`, fullUrl);
-
-      const response = await fetch(fullUrl);
-
-      if (!response.ok) {
-        throw new Error(
-          `Failed to load ${mode} leaderboard data: ` + response.statusText,
-        );
-      }
-
-      const data = await response.json();
-      console.log(`${mode} leaderboard data loaded:`, data);
-      displayLeaderboard(data, mode);
-
-      // Update description based on mode
-      updateDescription(mode);
-    } catch (error) {
-      console.error(`Error loading ${mode} leaderboard:`, error);
-      showError(`Error loading ${mode} leaderboard data: ` + error.message);
-    }
-  }
-
-  function updateDescription(mode) {
-    const description = document.getElementById("leaderboard-description");
-    if (mode === "players") {
-      description.innerHTML =
-        "<strong>Player Leaderboards</strong> identify individual players who hav
e completed ALL 9 dungeons. Rankings are based on the sum of their best times acro
ss all dungeons.";
-    } else {
-      description.innerHTML =
-        "<strong>Team Leaderboards</strong> identify 3-player cores that have com
pleted ALL 9 dungeons. Each team shows their core members plus extended roster. Ra
nkings are based on the sum of their best times across all dungeons.";
-    }
-  }
-
-  function updateURL() {
-    const mode = document.getElementById("leaderboard-mode").value;
-    const newURL =
-      mode === "players" ? `/player-leaderboards` : `/team-leaderboards`;
-    window.history.pushState({}, "", newURL);
-  }
-
-  // Event listeners
-  document.addEventListener("DOMContentLoaded", () => {
-    // Set initial value if provided
-    if (initialMode) {
-      document.getElementById("leaderboard-mode").value = initialMode;
-      loadLeaderboard();
-    }
-
-    // Bind change event
-    document
-      .getElementById("leaderboard-mode")
-      .addEventListener("change", () => {
-        console.log(
-          "Leaderboard mode changed to:",
-          document.getElementById("leaderboard-mode").value,
-        );
-        updateURL();
-        loadLeaderboard();
-      });
-  });
-</script>

diff --git a/web/src/layouts/TeamLeaderboard
Layout.astro b/web/src/layouts/TeamLeaderboa
rdLayout.astro
deleted file mode 100644
index 52574e4..0000000
--- a/web/src/layouts/TeamLeaderboardLayout.
astro
+++ /dev/null
@@ -1,730 +0,0 @@
----
-import PageLayout from "../components/PageL
ayout.astro";
-import ControlPanel from "../components/Con
trolPanel.astro";
-import FormSelect from "../components/FormS
elect.astro";
-import LoadingState from "../components/Loa
dingState.astro";
-import WoWClassColors from "../components/W
oWClassColors.astro";
-import "../styles/components.scss";
-
-export interface Props {
-  initialMode?: string;
-}
-
-const { initialMode = "players" } = Astro.p
rops;
-const baseUrl = import.meta.env.BASE_URL;
----
-
-<PageLayout
-  title="Team Leaderboards"
-  description=""
-  pageTitle="Team Leaderboards - MoP Classi
c"
->
-  <WoWClassColors />
-
-  <style>
-    .leaderboard-container {
-      overflow: hidden;
-      margin-top: 20px;
-    }
-
-    .loading-overlay,
-    .error-overlay {
-      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 p {
-      color: var(--text-secondary);
-      margin: 0;
-      font-size: 1.1em;
-    }
-
-    #team-rows {
-      display: table;
-      width: 100%;
-      table-layout: fixed;
-      border-spacing: 0;
-      border-collapse: collapse;
-    }
-
-    @media (max-width: 1024px) {
-      #team-rows .leaderboard-row {
-        display: block !important;
-        padding: 15px;
-      }
-
-      #team-rows .leaderboard-row > div {
-        display: block;
-        padding: 5px 0;
-        text-align: center;
-      }
-    }
-
-    /* Callout styles */
-    .callout {
-      background-color: #2a3a3a;
-      border: 1px solid #458588;
-      border-left: 4px solid #458588;
-      border-radius: 6px;
-      padding: 16px;
-      margin-bottom: 20px;
-    }
-
-    .callout-content {
-      color: var(--text-primary);
-      line-height: 1.5;
-      margin: 0;
-    }
-  </style>
-
-  <style is:global>
-    /* Global styles for dynamically genera
ted team leaderboard content */
-    #team-rows .leaderboard-row,
-    #team-rows .chart-item-header {
-      display: table-row !important;
-      border-bottom: 1px solid #4a4a4a !imp
ortant;
-      background-color: #32302f !important;
-      transition: background-color 0.2s eas
e !important;
-    }
-
-    #team-rows .leaderboard-row:hover,
-    #team-rows .chart-item-header:hover {
-      background-color: rgba(255, 215, 0, 0
.05) !important;
-    }
-
-    /* Expandable chart item styles (copied
 from ChartLayout.astro) */
-    .chart-item-wrapper {
-      border-radius: 6px;
-      transition: background-color 0.2s eas
e;
-    }
-
-    .chart-item-header {
-      cursor: pointer;
-      padding: 8px 12px;
-      border-radius: 6px;
-      transition: background-color 0.2s eas
e;
-    }
-
-    .chart-item-header:hover {
-      background-color: rgba(255, 255, 255,
 0.05);
-    }
-
-    .chart-item-expanded .chart-item-header
 {
-      background-color: rgba(255, 255, 255,
 0.08);
-    }
-
-    .chart-expand-icon {
-      margin-left: 8px;
-      font-size: 0.8em;
-      color: var(--text-muted);
-      transition: transform 0.2s ease;
-      user-select: none;
-    }
-
-    .chart-item-expanded .chart-expand-icon
 {
-      transform: rotate(90deg);
-    }
-
-    .chart-dropdown {
-      margin-top: 12px;
-      padding: 16px;
-      background-color: var(--bg-selection)
;
-      border-radius: 6px;
-      border: 1px solid rgba(255, 255, 255,
 0.1);
-      display: none;
-    }
-
-    .chart-item-expanded .chart-dropdown {
-      display: block;
-      animation: dropdown-expand 0.2s ease-
out;
-    }
-
-    @keyframes dropdown-expand {
-      from {
-        opacity: 0;
-        transform: translateY(-8px);
-      }
-      to {
-        opacity: 1;
-        transform: translateY(0);
-      }
-    }
-  </style>
-
-  <ControlPanel title="Leaderboard Options"
>
-    <FormSelect
-      id="leaderboard-mode"
-      label="Leaderboard Mode"
-      options={[
-        {
-          value: "teams",
-          label: "Teams",
-          selected: initialMode === "teams"
,
-        },
-        {
-          value: "players",
-          label: "Players",
-          selected: initialMode === "player
s",
-        },
-      ]}
-    />
-  </ControlPanel>
-
-  <div class="callout">
-    <div class="callout-content">
-      <p id="leaderboard-description">
-        <strong>Team Leaderboards</strong> 
identify 3-player cores that have completed
-        ALL 9 dungeons. Each team shows the
ir core members plus extended roster.
-        Rankings are based on the sum of th
eir best times across all dungeons.
-      </p>
-    </div>
-  </div>
-
-  <div id="loading" class="loading-overlay 
hidden">
-    <LoadingState message="Loading team lea
derboard 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 class="section-title-large">No 
Teams Found</h3>
-        <p>
-          No team data was found. Please en
sure the team leaderboard generator
-          has been run.
-        </p>
-      </div>
-    </div>
-  </div>
-
-  <div id="content" class="hidden">
-    <div class="card">
-      <div class="info-grid">
-        <div class="info-item">
-          <span class="info-label">Leaderbo
ard</span>
-          <span class="info-value" id="lead
erboard-title">Loading...</span>
-        </div>
-        <div class="info-item">
-          <span class="info-label">Generate
d</span>
-          <span class="info-value" id="gene
rated-time">-</span>
-        </div>
-      </div>
-    </div>
-
-    <div class="leaderboard-container card"
>
-      <div id="team-rows"></div>
-    </div>
-  </div>
-</PageLayout>
-
-<script
-  is:inline
-  define:vars={{
-    baseUrl,
-    initialMode,
-  }}
->
-  function formatDuration(milliseconds) {
-    const totalSeconds = Math.floor(millise
conds / 1000);
-    const minutes = Math.floor(totalSeconds
 / 60);
-    const seconds = totalSeconds % 60;
-    const ms = milliseconds % 1000;
-    return (
-      minutes +
-      ":" +
-      seconds.toString().padStart(2, "0") +
-      "." +
-      ms.toString().padStart(3, "0")
-    );
-  }
-
-  function formatTimestamp(timestamp) {
-    return new Date(timestamp).toLocaleDate
String("en-US", {
-      month: "short",
-      day: "numeric",
-      hour: "2-digit",
-      minute: "2-digit",
-    });
-  }
-
-  // Global function for chart item expansi
on (similar to chart-logic.js)
-  window.toggleChartItem = (element) => {
-    const wrapper = element.closest(".chart
-item-wrapper");
-    if (!wrapper) return;
-
-    const isExpanded = wrapper.classList.co
ntains("chart-item-expanded");
-
-    // Close all other expanded items
-    document.querySelectorAll(".chart-item-
expanded").forEach((item) => {
-      if (item !== wrapper) {
-        item.classList.remove("chart-item-e
xpanded");
-      }
-    });
-
-    // Toggle current item
-    wrapper.classList.toggle("chart-item-ex
panded", !isExpanded);
-  };
-
-  function getSpecColor(specId) {
-    // WoW class colors based on spec_id
-    const specColors = {
-      // Death Knight
-      250: "#C41F3B",
-      251: "#C41F3B",
-      252: "#C41F3B",
-      // Druid
-      102: "#FF7D0A",
-      103: "#FF7D0A",
-      104: "#FF7D0A",
-      105: "#FF7D0A",
-      // Hunter
-      253: "#ABD473",
-      254: "#ABD473",
-      255: "#ABD473",
-      // Mage
-      62: "#69CCF0",
-      63: "#69CCF0",
-      64: "#69CCF0",
-      // Monk
-      268: "#00FF96",
-      269: "#00FF96",
-      270: "#00FF96",
-      // Paladin
-      65: "#F58CBA",
-      66: "#F58CBA",
-      70: "#F58CBA",
-      // Priest
-      256: "#FFFFFF",
-      257: "#FFFFFF",
-      258: "#FFFFFF",
-      // Rogue
-      259: "#FFF569",
-      260: "#FFF569",
-      261: "#FFF569",
-      // Shaman
-      262: "#0070DE",
-      263: "#0070DE",
-      264: "#0070DE",
-      // Warlock
-      265: "#9482C9",
-      266: "#9482C9",
-      267: "#9482C9",
-      // Warrior
-      71: "#C79C6E",
-      72: "#C79C6E",
-      73: "#C79C6E",
-    };
-    return specColors[specId] || "#FFFFFF";
-  }
-
-  function createMemberLookup(team) {
-    // Create a lookup map from member name
s to their full member data with spec_id
-    const memberLookup = {};
-
-    // Process all runs to build a comprehe
nsive lookup
-    team.all_runs.forEach((run) => {
-      run.members.forEach((member) => {
-        if (!memberLookup[member.name]) {
-          memberLookup[member.name] = {
-            name: member.name,
-            realm_slug: member.realm_slug,
-            spec_id: member.spec_id,
-            id: member.id,
-          };
-        }
-      });
-    });
-
-    return memberLookup;
-  }
-
-  function createTeamComposition(memberName
s, memberLookup) {
-    // Create styled team composition simil
ar to Challenge Mode
-    return memberNames
-      .map((memberName) => {
-        const member = memberLookup[memberN
ame];
-        if (!member) {
-          // Fallback if member not found i
n lookup
-          return `<span style="color: var(-
-text-primary); margin-right: 8px;">${member
Name}</span>`;
-        }
-
-        const spec = member.spec_id
-          ? window.WoW.getSpecInfo(member.s
pec_id)
-          : null;
-        const iconUrl = spec
-          ? window.WoW.getSpecIcon(spec.cla
ss, spec.spec)
-          : null;
-        const classColor = spec
-          ? window.WoW.getClassColor(spec.c
lass)
-          : "#FFFFFF";
-
-        const iconHtml = iconUrl
-          ? `<img src="${iconUrl}" alt="${s
pec.spec} ${spec.class}" style="width: 16px;
 height: 16px; border-radius: 2px; margin-ri
ght: 4px; vertical-align: middle; flex-shrin
k: 0;">`
-          : "";
-
-        return `<span style="display: inlin
e-flex; align-items: center; margin-right: 8
px; gap: 4px;">
-        ${iconHtml}
-        <span style="color: ${classColor}; 
font-weight: 600; font-size: 0.9em;">${membe
r.name}@${member.realm_slug}</span>
-      </span>`;
-      })
-      .join("");
-  }
-
-  function createTeamRow(team) {
-    const wrapper = document.createElement(
"div");
-    wrapper.className = "chart-item-wrapper
";
-
-    // Create extended roster display with 
spec colors
-    const extendedRoster = team.extended_ro
ster
-      .map((member) => {
-        const specColor = member.spec_id
-          ? getSpecColor(member.spec_id)
-          : "#FFFFFF";
-        return `<span style="display: inlin
e-flex; align-items: center; margin-right: 8
px; gap: 4px;">
-        <span style="color: ${specColor}; f
ont-weight: 600; font-size: 0.9em;">${member
.name}</span>
-        <span style="color: var(--text-seco
ndary); font-size: 0.8em;">@${member.realm_s
lug}</span>
-      </span>`;
-      })
-      .join("");
-
-    // Create the team details content
-    const teamDetails = createTeamDetailsCo
ntent(team);
-
-    wrapper.innerHTML = `
-      <div class="leaderboard-row chart-ite
m-header" onclick="toggleChartItem(this)" st
yle="display: table-row !important; border-b
ottom: 1px solid #4a4a4a !important; backgro
und-color: #32302f; transition: background-c
olor 0.2s ease; cursor: pointer; width: 100%
;">
-        <div style="display: table-cell; pa
dding: 15px 10px; vertical-align: middle; fo
nt-size: 1.2em; font-weight: bold; color: #d
8a657; text-align: center; width: 80px;">#${
team.ranking}</div>
-        <div style="display: table-cell; pa
dding: 15px 10px; vertical-align: middle; fo
nt-family: 'Courier New', monospace; font-we
ight: bold; color: #ffffff; text-align: righ
t; width: 120px;">${formatDuration(team.comb
ined_best_time)}</div>
-        <div style="display: table-cell; pa
dding: 15px 10px; vertical-align: middle; di
splay: flex; align-items: center; overflow: 
hidden;">
-          <span class="chart-expand-icon" s
tyle="margin-right: 8px; flex-shrink: 0;">?<
/span>
-          <div style="display: flex; flex-w
rap: wrap; align-items: center; overflow: hi
dden; min-width: 0;">${extendedRoster}</div>
-        </div>
-      </div>
-      <div class="chart-dropdown">
-        ${teamDetails}
-      </div>
-    `;
-
-    return wrapper;
-  }
-
-  function createPlayerRow(player) {
-    const wrapper = document.createElement(
"div");
-    wrapper.className = "chart-item-wrapper
";
-
-    // Get player spec info for display
-    const spec = player.main_spec_id
-      ? window.WoW.getSpecInfo(player.main_
spec_id)
-      : null;
-    const iconUrl = spec ? window.WoW.getSp
ecIcon(spec.class, spec.spec) : null;
-    const classColor = spec ? window.WoW.ge
tClassColor(spec.class) : "#FFFFFF";
-
-    const iconHtml = iconUrl
-      ? `<img src="${iconUrl}" alt="${spec.
spec} ${spec.class}" style="width: 20px; hei
ght: 20px; border-radius: 2px; margin-right:
 8px; vertical-align: middle; flex-shrink: 0
;">`
-      : "";
-
-    // Create the player details content
-    const playerDetails = createPlayerDetai
lsContent(player);
-
-    wrapper.innerHTML = `
-      <div class="leaderboard-row chart-ite
m-header" onclick="toggleChartItem(this.pare
ntElement)" style="display: table-row !impor
tant; border-bottom: 1px solid #4a4a4a !impo
rtant; transition: background-color 0.2s eas
e !important; cursor: pointer;">
-        <div style="display: table-cell; pa
dding: 15px 10px; vertical-align: middle; fo
nt-size: 1.2em; font-weight: bold; color: #d
8a657; text-align: center; width: 80px;">#${
player.ranking}</div>
-        <div style="display: table-cell; pa
dding: 15px 10px; vertical-align: middle; fo
nt-family: 'Courier New', monospace; font-we
ight: bold; color: #ffffff; text-align: righ
t; width: 120px; white-space: nowrap;">${for
matDuration(player.combined_best_time)}</div
>
-        <div style="display: table-cell; pa
dding: 15px 10px; vertical-align: middle; wi
dth: 100%;">
-          <div style="display: flex; justif
y-content: space-between; align-items: cente
r; width: 100%;">
-            <div style="display: flex; alig
n-items: center;">
-              <span class="chart-expand-ico
n" style="margin-right: 8px;">?</span>
-              ${iconHtml}
-              <span style="color: ${classCo
lor}; font-weight: 600; font-size: 1.1em;">$
{player.name}</span>
-            </div>
-            <span style="color: var(--text-
secondary); font-size: 0.9em;">@${player.rea
lm_slug}</span>
-          </div>
-        </div>
-      </div>
-      <div class="chart-dropdown">
-        ${playerDetails}
-      </div>
-    `;
-
-    return wrapper;
-  }
-
-  function createPlayerDetailsContent(playe
r) {
-    // Create dungeon run entries styled li
ke Challenge Mode leaderboard rows
-    const dungeonEntries = Object.entries(p
layer.best_runs_per_dungeon || {})
-      .map(([_dungeonSlug, run]) => {
-        // Create team composition with spe
c icons using all_members data
-        const teamComposition = (run.all_me
mbers || [])
-          .map((member) => {
-            const spec = member.spec_id
-              ? window.WoW.getSpecInfo(memb
er.spec_id)
-              : null;
-            const iconUrl = spec
-              ? window.WoW.getSpecIcon(spec
.class, spec.spec)
-              : null;
-            const classColor = spec
-              ? window.WoW.getClassColor(sp
ec.class)
-              : "#FFFFFF";
-
-            const iconHtml = iconUrl
-              ? `<img src="${iconUrl}" alt=
"${spec.spec} ${spec.class}" style="width: 1
6px; height: 16px; border-radius: 2px; margi
n-right: 4px; vertical-align: middle; flex-s
hrink: 0;">`
-              : "";
-
-            return `<span style="display: i
nline-flex; align-items: center; margin-righ
t: 8px; gap: 4px;">
-          ${iconHtml}
-          <span style="color: ${classColor}
; font-weight: 600; font-size: 0.9em;">${mem
ber.name}@${member.realm_slug}</span>
-        </span>`;
-          })
-          .join("");
-
-        return `
-        <div style="display: grid; grid-tem
plate-columns: 80px 120px 2fr 100px 140px; g
ap: 20px; align-items: center; padding: 15px
 20px; border-bottom: 1px solid #3a3a3a; min
-height: 60px;">
-          <div style="font-size: 1.2em; fon
t-weight: bold; color: #d8a657; text-align: 
center;">${run.ranking === "~" ? "~" : "#" +
 run.ranking}</div>
-          <div style="font-family: 'Courier
 New', monospace; font-weight: bold; color: 
#ffffff; text-align: right;">${formatDuratio
n(run.duration)}</div>
-          <div style="display: flex; flex-w
rap: wrap; gap: 6px; justify-content: flex-s
tart; align-items: center;">${teamCompositio
n}</div>
-          <div style="color: var(--text-pri
mary); text-align: center; font-weight: 600;
">${run.dungeon_name}</div>
-          <div style="color: #aaaaaa; font-
size: 0.9em; text-align: center;">${formatTi
mestamp(run.completed_timestamp)}</div>
-        </div>
-      `;
-      })
-      .join("");
-
-    return `
-      <div>
-        <h4 style="color: var(--highlight-c
olor); margin-bottom: 10px;">Best Times Per 
Dungeon</h4>
-        <div style="border-radius: 6px; ove
rflow: hidden; border: 1px solid #4a4a4a;">
-          <div style="display: grid; grid-t
emplate-columns: 80px 120px 2fr 100px 140px;
 gap: 20px; padding: 15px 20px; background-c
olor: rgba(255, 255, 255, 0.03); border-bott
om: 1px solid #4a4a4a; font-weight: 600; col
or: var(--text-secondary); font-size: 0.9em;
 text-transform: uppercase; letter-spacing: 
0.5px;">
-            <div>Global Rank</div>
-            <div style="text-align: right;"
>Time</div>
-            <div>Team Composition</div>
-            <div style="text-align: center;
">Dungeon</div>
-            <div style="text-align: center;
">Date</div>
-          </div>
-          ${dungeonEntries}
-        </div>
-      </div>
-    `;
-  }
-
-  function createTeamDetailsContent(team) {
-    // Create member lookup for this team
-    const memberLookup = createMemberLookup
(team);
-
-    // Create core members display with Cha
llenge Mode styling
-    const coreMembers = team.core_members
-      .map((member) => {
-        const spec = member.spec_id
-          ? window.WoW.getSpecInfo(member.s
pec_id)
-          : null;
-        const iconUrl = spec
-          ? window.WoW.getSpecIcon(spec.cla
ss, spec.spec)
-          : null;
-        const classColor = spec
-          ? window.WoW.getClassColor(spec.c
lass)
-          : "#FFFFFF";
-
-        const iconHtml = iconUrl
-          ? `<img src="${iconUrl}" alt="${s
pec.spec} ${spec.class}" style="width: 16px;
 height: 16px; border-radius: 2px; margin-ri
ght: 4px; vertical-align: middle; flex-shrin
k: 0;">`
-          : "";
-
-        return `<span style="display: inlin
e-flex; align-items: center; margin-right: 1
5px; gap: 4px;">
-        ${iconHtml}
-        <span style="color: ${classColor}; 
font-weight: 600; font-size: 0.9em;">${membe
r.name}@${member.realm_slug}</span>
-      </span>`;
-      })
-      .join("");
-
-    // Create dungeon run entries styled li
ke Challenge Mode leaderboard rows
-    const dungeonEntries = Object.entries(t
eam.best_runs_per_dungeon || {})
-      .map(([_dungeonSlug, run]) => {
-        const teamComposition = createTeamC
omposition(
-          run.members,
-          memberLookup,
-        );
-
-        return `
-        <div style="display: grid; grid-tem
plate-columns: 80px 120px 2fr 100px 140px; g
ap: 20px; align-items: center; padding: 15px
 20px; border-bottom: 1px solid #3a3a3a; min
-height: 60px;">
-          <div style="font-size: 1.2em; fon
t-weight: bold; color: #d8a657; text-align: 
center;">#${run.ranking}</div>
-          <div style="font-family: 'Courier
 New', monospace; font-weight: bold; color: 
#ffffff; text-align: right;">${formatDuratio
n(run.duration)}</div>
-          <div style="display: flex; flex-w
rap: wrap; gap: 6px; justify-content: flex-s
tart; align-items: center;">${teamCompositio
n}</div>
-          <div style="color: var(--text-pri
mary); text-align: center; font-weight: 600;
">${run.dungeon_name}</div>
-          <div style="color: #aaaaaa; font-
size: 0.9em; text-align: center;">${formatTi
mestamp(run.completed_timestamp)}</div>
-        </div>
-      `;
-      })
-      .join("");
-
-    return `
-      <div style="margin-bottom: 20px;">
-        <h4 style="color: var(--highlight-c
olor); margin-bottom: 10px;">Core Team (3-pl
ayer consistent core)</h4>
-        <div style="display: flex; flex-wra
p: wrap; align-items: center;">${coreMembers
}</div>
-      </div>
-      
-      <div>
-        <h4 style="color: var(--highlight-c
olor); margin-bottom: 10px;">Best Times Per 
Dungeon</h4>
-        <div style="border-radius: 6px; ove
rflow: hidden; border: 1px solid #4a4a4a;">
-          <div style="display: grid; grid-t
emplate-columns: 80px 120px 2fr 100px 140px;
 gap: 20px; padding: 15px 20px; background-c
olor: rgba(255, 255, 255, 0.03); border-bott
om: 1px solid #4a4a4a; font-weight: 600; col
or: var(--text-secondary); font-size: 0.9em;
 text-transform: uppercase; letter-spacing: 
0.5px;">
-            <div>Rank</div>
-            <div style="text-align: right;"
>Time</div>
-            <div>Team Composition</div>
-            <div style="text-align: center;
">Dungeon</div>
-            <div style="text-align: center;
">Date</div>
-          </div>
-          ${dungeonEntries}
-        </div>
-      </div>
-    `;
-  }
-
-  function displayLeaderboard(data, mode) {
-    console.log(`Displaying ${mode} leaderb
oard data:`, data);
-
-    if (!data.leaderboard || data.leaderboa
rd.length === 0) {
-      showNoData();
-      return;
-    }
-
-    document.getElementById("leaderboard-ti
tle").textContent = data.title;
-    document.getElementById("generated-time
").textContent =
-      data.generated_timestamp
-        ? formatTimestamp(data.generated_ti
mestamp)
-        : "Unknown";
-
-    const rowsContainer = document.getEleme
ntById("team-rows");
-    rowsContainer.innerHTML = "";
-
-    data.leaderboard.forEach((item, index) 
=> {
-      // Add ranking based on position in s
orted array
-      item.ranking = index + 1;
-
-      const wrapper =
-        mode === "players" ? createPlayerRo
w(item) : createTeamRow(item);
-      rowsContainer.appendChild(wrapper);
-    });
-
-    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(`${mode} leaderboard displa
yed successfully`);
-  }
-
-  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;
-    }
-    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");
-  }
-
-  async function loadLeaderboard() {
-    const mode = document.getElementById("l
eaderboard-mode").value;
-
-    showLoading();
-
-    try {
-      const dataPath =
-        mode === "players" ? "player-leader
boards" : "team-leaderboards";
-      const fileName = "best-overall.json";
-      const fullUrl = `/data/${dataPath}/${
fileName}`;
-      console.log(`Loading ${mode} leaderbo
ard from:`, fullUrl);
-
-      const response = await fetch(fullUrl)
;
-
-      if (!response.ok) {
-        throw new Error(
-          `Failed to load ${mode} leaderboa
rd data: ` + response.statusText,
-        );
-      }
-
-      const data = await response.json();
-      console.log(`${mode} leaderboard data
 loaded:`, data);
-      displayLeaderboard(data, mode);
-
-      // Update description based on mode
-      updateDescription(mode);
-    } catch (error) {
-      console.error(`Error loading ${mode} 
leaderboard:`, error);
-      showError(`Error loading ${mode} lead
erboard data: ` + error.message);
-    }
-  }
-
-  function updateDescription(mode) {
-    const description = document.getElement
ById("leaderboard-description");
-    if (mode === "players") {
-      description.innerHTML =
-        "<strong>Player Leaderboards</stron
g> identify individual players who have comp
leted ALL 9 dungeons. Rankings are based on 
the sum of their best times across all dunge
ons.";
-    } else {
-      description.innerHTML =
-        "<strong>Team Leaderboards</strong>
 identify 3-player cores that have completed
 ALL 9 dungeons. Each team shows their core 
members plus extended roster. Rankings are b
ased on the sum of their best times across a
ll dungeons.";
-    }
-  }
-
-  function updateURL() {
-    const mode = document.getElementById("l
eaderboard-mode").value;
-    const newURL =
-      mode === "players" ? `/player-leaderb
oards` : `/team-leaderboards`;
-    window.history.pushState({}, "", newURL
);
-  }
-
-  // Event listeners
-  document.addEventListener("DOMContentLoad
ed", () => {
-    // Set initial value if provided
-    if (initialMode) {
-      document.getElementById("leaderboard-
mode").value = initialMode;
-      loadLeaderboard();
-    }
-
-    // Bind change event
-    document
-      .getElementById("leaderboard-mode")
-      .addEventListener("change", () => {
-        console.log(
-          "Leaderboard mode changed to:",
-          document.getElementById("leaderbo
ard-mode").value,
-        );
-        updateURL();
-        loadLeaderboard();
-      });
-  });
-</script>
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET