OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      89e7a2b974cc
DATE      2025-07-20
SUBJECT   web: use dynamic page
FILES     3 CHANGED
HASH      89e7a2b974cc
DATE      2025-07-20
SUBJECT   web: use dynamic page
FILES     3 CHANGED
 

diff --git a/web/src/components/DynamicRankings.astro b/web/src/components/Dynamic
Rankings.astro
new file mode 100644
index 0000000..707ba63
--- /dev/null
+++ b/web/src/components/DynamicRankings.astro
@@ -0,0 +1,547 @@
+---
+// This component will be client-side rendered for interactivity
+---
+
+<div class="rankings-container">
+  <div id="metadata-container"></div>
+  
+  <div class="controls">
+    <h3 class="controls-title">Simulation Options</h3>
+    <div class="controls-grid">
+      <div class="control-group">
+        <label for="targetCount">Target Count:</label>
+        <select id="targetCount">
+          <option value="single">Single Target</option>
+          <option value="three">3 Targets</option>
+          <option value="cleave">Cleave (2)</option>
+          <option value="ten">10 Targets</option>
+        </select>
+      </div>
+      
+      <div class="control-group">
+        <label for="encounterType">Encounter:</label>
+        <select id="encounterType">
+          <option value="raid">Raid</option>
+        </select>
+      </div>
+      
+      <div class="control-group">
+        <label for="duration">Duration:</label>
+        <select id="duration">
+          <option value="long">Long</option>
+        </select>
+      </div>
+      
+      <div class="control-group">
+        <label for="phase">Phase:</label>
+        <select id="phase">
+          <option value="p1">Phase 1</option>
+        </select>
+      </div>
+    </div>
+  </div>
+
+  <div id="loading" class="loading hidden">Loading simulation data...</div>
+  <div id="error" class="error hidden"></div>
+  
+  <div id="chart-container"></div>
+</div>
+
+<style>
+  .rankings-container {
+    max-width: 1200px;
+    margin: 0 auto;
+    padding: 20px;
+  }
+  
+  .controls {
+    margin-bottom: 30px;
+    padding: 20px;
+    background-color: #3a3a3a;
+    border-radius: 8px;
+    border: 1px solid #4a4a4a;
+  }
+  
+  .controls-title {
+    color: #ea6962;
+    margin: 0 0 15px 0;
+    font-size: 1.2em;
+    font-weight: 600;
+  }
+  
+  .controls-grid {
+    display: flex;
+    gap: 20px;
+    flex-wrap: wrap;
+  }
+  
+  .control-group {
+    display: flex;
+    flex-direction: column;
+    gap: 5px;
+  }
+  
+  .control-group label {
+    font-size: 0.9em;
+    color: #a9a9a9;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+  }
+  
+  .control-group select {
+    padding: 8px 12px;
+    background-color: #2a2a2a;
+    border: 1px solid #555;
+    border-radius: 4px;
+    color: #ffffff;
+    font-size: 0.9em;
+    min-width: 120px;
+  }
+  
+  .control-group select:focus {
+    outline: none;
+    border-color: #ea6962;
+  }
+  
+  .loading, .error {
+    text-align: center;
+    padding: 20px;
+    margin: 20px 0;
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    background-color: #2a2a2a;
+    border-radius: 8px;
+    z-index: 1000;
+    min-width: 200px;
+  }
+  
+  .loading {
+    color: #a9a9a9;
+    border: 1px solid #4a4a4a;
+  }
+  
+  .error {
+    color: #ea6962;
+    background-color: #3a2a2a;
+    border: 1px solid #ea6962;
+  }
+  
+  /* Prevent layout shifts by maintaining minimum heights */
+  #metadata-container {
+    min-height: 120px;
+    transition: opacity 0.2s ease;
+  }
+  
+  #chart-container {
+    min-height: 400px;
+    transition: opacity 0.2s ease;
+  }
+  
+  .hidden {
+    display: none;
+  }
+  
+  @media (max-width: 768px) {
+    .controls-grid {
+      flex-direction: column;
+      gap: 15px;
+    }
+  }
+</style>
+
+<script>
+  // WoW class colors for consistency
+  const classColors = {
+    death_knight: '#C41E3A',
+    druid: '#FF7C0A', 
+    hunter: '#AAD372',
+    mage: '#3FC7EB',
+    monk: '#00FF98',
+    paladin: '#F48CBA',
+    priest: '#FFFFFF',
+    rogue: '#FFF468',
+    shaman: '#0070DD',
+    warlock: '#8788EE',
+    warrior: '#C69B6D'
+  };
+
+  interface SimulationData {
+    metadata: {
+      name: string;
+      timestamp: string;
+      iterations: number;
+      specCount: number;
+      encounterDuration: number;
+      encounterVariation: number;
+      targetCount: number;
+      raidBuffs: Record<string, boolean | number>;
+    };
+    results: Record<string, Record<string, {
+      dps: number;
+      max: number;
+      min: number;
+      stdev: number;
+      loadout: any;
+    }>>;
+  }
+
+  class DynamicRankings {
+    private currentData: SimulationData | null = null;
+
+    constructor() {
+      this.bindEvents();
+      this.loadInitialData();
+    }
+
+    private bindEvents() {
+      const selects = document.querySelectorAll('#targetCount, #encounterType, #d
uration, #phase');
+      selects.forEach(select => {
+        select.addEventListener('change', () => this.loadData());
+      });
+    }
+
+    private async loadInitialData() {
+      await this.loadData();
+    }
+
+    private getFileName(): string {
+      const targetCount = (document.getElementById('targetCount') as HTMLSelectEl
ement).value;
+      const encounterType = (document.getElementById('encounterType') as HTMLSele
ctElement).value;
+      const duration = (document.getElementById('duration') as HTMLSelectElement)
.value;
+      const phase = (document.getElementById('phase') as HTMLSelectElement).value
;
+      
+      return `dps_${phase}_${encounterType}_${targetCount}_${duration}.json`;
+    }
+
+    private async loadData() {
+      const loadingEl = document.getElementById('loading')!;
+      const errorEl = document.getElementById('error')!;
+      const metadataContainer = document.getElementById('metadata-container')!;
+      const chartContainer = document.getElementById('chart-container')!;
+
+      // Save current scroll position
+      const scrollY = window.scrollY;
+
+      // Fade out content and show loading
+      metadataContainer.style.opacity = '0.5';
+      chartContainer.style.opacity = '0.5';
+      loadingEl.classList.remove('hidden');
+      errorEl.classList.add('hidden');
+
+      try {
+        const fileName = this.getFileName();
+        const response = await fetch(`/data/${fileName}`);
+        
+        if (!response.ok) {
+          throw new Error(`Failed to load ${fileName}: ${response.statusText}`);
+        }
+
+        this.currentData = await response.json();
+        
+        // Hide loading
+        loadingEl.classList.add('hidden');
+        
+        // Render components
+        this.renderMetadata();
+        this.renderChart();
+        
+        // Fade content back in
+        metadataContainer.style.opacity = '1';
+        chartContainer.style.opacity = '1';
+        
+        // Restore scroll position after content is rendered
+        requestAnimationFrame(() => {
+          window.scrollTo(0, scrollY);
+        });
+        
+      } catch (error) {
+        console.error('Error loading data:', error);
+        loadingEl.classList.add('hidden');
+        errorEl.textContent = `Error loading simulation data: ${error.message}`;
+        errorEl.classList.remove('hidden');
+        
+        // Clear content on error and restore opacity
+        metadataContainer.innerHTML = '';
+        chartContainer.innerHTML = '';
+        metadataContainer.style.opacity = '1';
+        chartContainer.style.opacity = '1';
+      }
+    }
+
+    private renderMetadata() {
+      if (!this.currentData) return;
+
+      const container = document.getElementById('metadata-container')!;
+      const metadata = this.currentData.metadata;
+      
+      const simulationDate = new Date(metadata.timestamp).toLocaleString('en-US',
 {
+        year: 'numeric',
+        month: 'long',
+        day: 'numeric',
+        hour: '2-digit',
+        minute: '2-digit',
+        timeZoneName: 'short'
+      });
+
+      const formatDuration = (seconds: number) => {
+        const minutes = Math.floor(seconds / 60);
+        const remainingSeconds = seconds % 60;
+        return minutes > 0 ? `${minutes}m ${remainingSeconds}s` : `${remainingSec
onds}s`;
+      };
+
+      const formatRaidBuffs = (buffs: Record<string, boolean | number>) => {
+        const activeBuffs = Object.entries(buffs)
+          .filter(([_, value]) => value !== false && value !== 0)
+          .map(([key, value]) => {
+            const readable = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => 
str.toUpperCase());
+            return typeof value === 'number' && value > 1 ? `${readable} (${value
})` : readable;
+          });
+        return activeBuffs.join(', ');
+      };
+
+      container.innerHTML = `
+        <div class="simulation-metadata">
+          <h3 class="metadata-title">Simulation Details</h3>
+          <div class="metadata-grid">
+            <div class="metadata-item">
+              <span class="metadata-label">Iterations</span>
+              <span class="metadata-value">${metadata.iterations.toLocaleString()
}</span>
+            </div>
+            <div class="metadata-item">
+              <span class="metadata-label">Specs Tested</span>
+              <span class="metadata-value">${metadata.specCount}</span>
+            </div>
+            <div class="metadata-item">
+              <span class="metadata-label">Encounter Duration</span>
+              <span class="metadata-value">${formatDuration(metadata.encounterDur
ation)}</span>
+            </div>
+            <div class="metadata-item">
+              <span class="metadata-label">Duration Variation</span>
+              <span class="metadata-value">?${metadata.encounterVariation}s</span
>
+            </div>
+            <div class="metadata-item">
+              <span class="metadata-label">Target Count</span>
+              <span class="metadata-value">${metadata.targetCount}</span>
+            </div>
+            <div class="metadata-item">
+              <span class="metadata-label">Date Simulated</span>
+              <span class="metadata-value">${simulationDate}</span>
+            </div>
+            <div class="metadata-item metadata-item-wide">
+              <span class="metadata-label">Active Raid Buffs</span>
+              <span class="metadata-value metadata-value-wrap">${formatRaidBuffs(
metadata.raidBuffs)}</span>
+            </div>
+          </div>
+        </div>
+      `;
+    }
+
+    private renderChart() {
+      if (!this.currentData) return;
+
+      const container = document.getElementById('chart-container')!;
+      
+      // Transform data for chart
+      const chartItems: Array<{label: string, sublabel: string, value: number, ca
tegory: string}> = [];
+      
+      for (const [className, classSpecs] of Object.entries(this.currentData.resul
ts)) {
+        for (const [specName, specData] of Object.entries(classSpecs)) {
+          chartItems.push({
+            label: specName,
+            sublabel: className,
+            value: specData.dps,
+            category: className
+          });
+        }
+      }
+
+      // Sort by DPS descending
+      chartItems.sort((a, b) => b.value - a.value);
+
+      // Create chart HTML
+      const maxDps = Math.max(...chartItems.map(item => item.value));
+      
+      container.innerHTML = `
+        <div class="chart-container">
+          <h2 class="chart-title">DPS Rankings</h2>
+          <div class="chart-bars">
+            ${chartItems.map((item, index) => `
+              <div class="chart-item">
+                <div class="chart-labels">
+                  <span class="chart-rank">#${index + 1}</span>
+                  <span class="chart-label">${item.label}</span>
+                  <span class="chart-sublabel">${item.sublabel}</span>
+                </div>
+                <div class="chart-bar-container">
+                  <div class="chart-bar" style="width: ${(item.value / maxDps) * 
100}%; background-color: ${classColors[item.category] || '#666'}">
+                  </div>
+                  <span class="chart-value">${Math.round(item.value).toLocaleStri
ng()}</span>
+                </div>
+              </div>
+            `).join('')}
+          </div>
+        </div>
+      `;
+    }
+  }
+
+  // Initialize when DOM is loaded
+  document.addEventListener('DOMContentLoaded', () => {
+    new DynamicRankings();
+  });
+</script>
+
+<!-- Include metadata and chart styles -->
+<style is:global>
+  .simulation-metadata {
+    background-color: #3a3a3a;
+    border-radius: 8px;
+    padding: 20px;
+    margin-bottom: 30px;
+    border: 1px solid #4a4a4a;
+  }
+  
+  .metadata-title {
+    color: #ea6962;
+    margin: 0 0 15px 0;
+    font-size: 1.2em;
+    font-weight: 600;
+  }
+  
+  .metadata-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 15px;
+  }
+  
+  .metadata-item {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+  }
+  
+  .metadata-item-wide {
+    grid-column: 1 / -1;
+  }
+  
+  .metadata-label {
+    font-size: 0.85em;
+    color: #a9a9a9;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+  }
+  
+  .metadata-value {
+    font-size: 1em;
+    color: #ffffff;
+    font-weight: 600;
+  }
+  
+  .metadata-value-wrap {
+    white-space: normal;
+    word-wrap: break-word;
+    line-height: 1.4;
+  }
+
+  .chart-container {
+    background-color: #3a3a3a;
+    border-radius: 8px;
+    padding: 20px;
+    border: 1px solid #4a4a4a;
+  }
+  
+  .chart-title {
+    color: #ea6962;
+    margin: 0 0 20px 0;
+    font-size: 1.4em;
+    font-weight: 600;
+  }
+  
+  .chart-bars {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+  
+  .chart-item {
+    display: flex;
+    align-items: center;
+    gap: 15px;
+    padding: 8px 0;
+  }
+  
+  .chart-labels {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    min-width: 200px;
+    flex-shrink: 0;
+  }
+  
+  .chart-rank {
+    font-size: 0.9em;
+    color: #a9a9a9;
+    font-weight: 600;
+    min-width: 30px;
+  }
+  
+  .chart-label {
+    font-weight: 600;
+    color: #ffffff;
+    text-transform: capitalize;
+  }
+  
+  .chart-sublabel {
+    font-size: 0.85em;
+    color: #a9a9a9;
+    text-transform: capitalize;
+  }
+  
+  .chart-bar-container {
+    display: flex;
+    align-items: center;
+    flex: 1;
+    gap: 10px;
+  }
+  
+  .chart-bar {
+    height: 20px;
+    border-radius: 3px;
+    min-width: 2px;
+    transition: width 0.3s ease;
+  }
+  
+  .chart-value {
+    font-weight: 600;
+    color: #ffffff;
+    font-size: 0.9em;
+    min-width: 60px;
+    text-align: right;
+  }
+  
+  @media (max-width: 768px) {
+    .metadata-grid {
+      grid-template-columns: 1fr;
+    }
+    
+    .chart-labels {
+      min-width: 150px;
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 2px;
+    }
+    
+    .chart-item {
+      flex-direction: column;
+      align-items: stretch;
+    }
+    
+    .chart-bar-container {
+      width: 100%;
+    }
+  }
+</style>
\ No newline at end of file

diff --git a/web/src/components/DynamicRanki
ngs.astro b/web/src/components/DynamicRankin
gs.astro
new file mode 100644
index 0000000..707ba63
--- /dev/null
+++ b/web/src/components/DynamicRankings.ast
ro
@@ -0,0 +1,547 @@
+---
+// This component will be client-side rende
red for interactivity
+---
+
+<div class="rankings-container">
+  <div id="metadata-container"></div>
+  
+  <div class="controls">
+    <h3 class="controls-title">Simulation O
ptions</h3>
+    <div class="controls-grid">
+      <div class="control-group">
+        <label for="targetCount">Target Cou
nt:</label>
+        <select id="targetCount">
+          <option value="single">Single Tar
get</option>
+          <option value="three">3 Targets</
option>
+          <option value="cleave">Cleave (2)
</option>
+          <option value="ten">10 Targets</o
ption>
+        </select>
+      </div>
+      
+      <div class="control-group">
+        <label for="encounterType">Encounte
r:</label>
+        <select id="encounterType">
+          <option value="raid">Raid</option
>
+        </select>
+      </div>
+      
+      <div class="control-group">
+        <label for="duration">Duration:</la
bel>
+        <select id="duration">
+          <option value="long">Long</option
>
+        </select>
+      </div>
+      
+      <div class="control-group">
+        <label for="phase">Phase:</label>
+        <select id="phase">
+          <option value="p1">Phase 1</optio
n>
+        </select>
+      </div>
+    </div>
+  </div>
+
+  <div id="loading" class="loading hidden">
Loading simulation data...</div>
+  <div id="error" class="error hidden"></di
v>
+  
+  <div id="chart-container"></div>
+</div>
+
+<style>
+  .rankings-container {
+    max-width: 1200px;
+    margin: 0 auto;
+    padding: 20px;
+  }
+  
+  .controls {
+    margin-bottom: 30px;
+    padding: 20px;
+    background-color: #3a3a3a;
+    border-radius: 8px;
+    border: 1px solid #4a4a4a;
+  }
+  
+  .controls-title {
+    color: #ea6962;
+    margin: 0 0 15px 0;
+    font-size: 1.2em;
+    font-weight: 600;
+  }
+  
+  .controls-grid {
+    display: flex;
+    gap: 20px;
+    flex-wrap: wrap;
+  }
+  
+  .control-group {
+    display: flex;
+    flex-direction: column;
+    gap: 5px;
+  }
+  
+  .control-group label {
+    font-size: 0.9em;
+    color: #a9a9a9;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+  }
+  
+  .control-group select {
+    padding: 8px 12px;
+    background-color: #2a2a2a;
+    border: 1px solid #555;
+    border-radius: 4px;
+    color: #ffffff;
+    font-size: 0.9em;
+    min-width: 120px;
+  }
+  
+  .control-group select:focus {
+    outline: none;
+    border-color: #ea6962;
+  }
+  
+  .loading, .error {
+    text-align: center;
+    padding: 20px;
+    margin: 20px 0;
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    background-color: #2a2a2a;
+    border-radius: 8px;
+    z-index: 1000;
+    min-width: 200px;
+  }
+  
+  .loading {
+    color: #a9a9a9;
+    border: 1px solid #4a4a4a;
+  }
+  
+  .error {
+    color: #ea6962;
+    background-color: #3a2a2a;
+    border: 1px solid #ea6962;
+  }
+  
+  /* Prevent layout shifts by maintaining m
inimum heights */
+  #metadata-container {
+    min-height: 120px;
+    transition: opacity 0.2s ease;
+  }
+  
+  #chart-container {
+    min-height: 400px;
+    transition: opacity 0.2s ease;
+  }
+  
+  .hidden {
+    display: none;
+  }
+  
+  @media (max-width: 768px) {
+    .controls-grid {
+      flex-direction: column;
+      gap: 15px;
+    }
+  }
+</style>
+
+<script>
+  // WoW class colors for consistency
+  const classColors = {
+    death_knight: '#C41E3A',
+    druid: '#FF7C0A', 
+    hunter: '#AAD372',
+    mage: '#3FC7EB',
+    monk: '#00FF98',
+    paladin: '#F48CBA',
+    priest: '#FFFFFF',
+    rogue: '#FFF468',
+    shaman: '#0070DD',
+    warlock: '#8788EE',
+    warrior: '#C69B6D'
+  };
+
+  interface SimulationData {
+    metadata: {
+      name: string;
+      timestamp: string;
+      iterations: number;
+      specCount: number;
+      encounterDuration: number;
+      encounterVariation: number;
+      targetCount: number;
+      raidBuffs: Record<string, boolean | n
umber>;
+    };
+    results: Record<string, Record<string, 
{
+      dps: number;
+      max: number;
+      min: number;
+      stdev: number;
+      loadout: any;
+    }>>;
+  }
+
+  class DynamicRankings {
+    private currentData: SimulationData | n
ull = null;
+
+    constructor() {
+      this.bindEvents();
+      this.loadInitialData();
+    }
+
+    private bindEvents() {
+      const selects = document.querySelecto
rAll('#targetCount, #encounterType, #duratio
n, #phase');
+      selects.forEach(select => {
+        select.addEventListener('change', (
) => this.loadData());
+      });
+    }
+
+    private async loadInitialData() {
+      await this.loadData();
+    }
+
+    private getFileName(): string {
+      const targetCount = (document.getElem
entById('targetCount') as HTMLSelectElement)
.value;
+      const encounterType = (document.getEl
ementById('encounterType') as HTMLSelectElem
ent).value;
+      const duration = (document.getElement
ById('duration') as HTMLSelectElement).value
;
+      const phase = (document.getElementByI
d('phase') as HTMLSelectElement).value;
+      
+      return `dps_${phase}_${encounterType}
_${targetCount}_${duration}.json`;
+    }
+
+    private async loadData() {
+      const loadingEl = document.getElement
ById('loading')!;
+      const errorEl = document.getElementBy
Id('error')!;
+      const metadataContainer = document.ge
tElementById('metadata-container')!;
+      const chartContainer = document.getEl
ementById('chart-container')!;
+
+      // Save current scroll position
+      const scrollY = window.scrollY;
+
+      // Fade out content and show loading
+      metadataContainer.style.opacity = '0.
5';
+      chartContainer.style.opacity = '0.5';
+      loadingEl.classList.remove('hidden');
+      errorEl.classList.add('hidden');
+
+      try {
+        const fileName = this.getFileName()
;
+        const response = await fetch(`/data
/${fileName}`);
+        
+        if (!response.ok) {
+          throw new Error(`Failed to load $
{fileName}: ${response.statusText}`);
+        }
+
+        this.currentData = await response.j
son();
+        
+        // Hide loading
+        loadingEl.classList.add('hidden');
+        
+        // Render components
+        this.renderMetadata();
+        this.renderChart();
+        
+        // Fade content back in
+        metadataContainer.style.opacity = '
1';
+        chartContainer.style.opacity = '1';
+        
+        // Restore scroll position after co
ntent is rendered
+        requestAnimationFrame(() => {
+          window.scrollTo(0, scrollY);
+        });
+        
+      } catch (error) {
+        console.error('Error loading data:'
, error);
+        loadingEl.classList.add('hidden');
+        errorEl.textContent = `Error loadin
g simulation data: ${error.message}`;
+        errorEl.classList.remove('hidden');
+        
+        // Clear content on error and resto
re opacity
+        metadataContainer.innerHTML = '';
+        chartContainer.innerHTML = '';
+        metadataContainer.style.opacity = '
1';
+        chartContainer.style.opacity = '1';
+      }
+    }
+
+    private renderMetadata() {
+      if (!this.currentData) return;
+
+      const container = document.getElement
ById('metadata-container')!;
+      const metadata = this.currentData.met
adata;
+      
+      const simulationDate = new Date(metad
ata.timestamp).toLocaleString('en-US', {
+        year: 'numeric',
+        month: 'long',
+        day: 'numeric',
+        hour: '2-digit',
+        minute: '2-digit',
+        timeZoneName: 'short'
+      });
+
+      const formatDuration = (seconds: numb
er) => {
+        const minutes = Math.floor(seconds 
/ 60);
+        const remainingSeconds = seconds % 
60;
+        return minutes > 0 ? `${minutes}m $
{remainingSeconds}s` : `${remainingSeconds}s
`;
+      };
+
+      const formatRaidBuffs = (buffs: Recor
d<string, boolean | number>) => {
+        const activeBuffs = Object.entries(
buffs)
+          .filter(([_, value]) => value !==
 false && value !== 0)
+          .map(([key, value]) => {
+            const readable = key.replace(/(
[A-Z])/g, ' $1').replace(/^./, str => str.to
UpperCase());
+            return typeof value === 'number
' && value > 1 ? `${readable} (${value})` : 
readable;
+          });
+        return activeBuffs.join(', ');
+      };
+
+      container.innerHTML = `
+        <div class="simulation-metadata">
+          <h3 class="metadata-title">Simula
tion Details</h3>
+          <div class="metadata-grid">
+            <div class="metadata-item">
+              <span class="metadata-label">
Iterations</span>
+              <span class="metadata-value">
${metadata.iterations.toLocaleString()}</spa
n>
+            </div>
+            <div class="metadata-item">
+              <span class="metadata-label">
Specs Tested</span>
+              <span class="metadata-value">
${metadata.specCount}</span>
+            </div>
+            <div class="metadata-item">
+              <span class="metadata-label">
Encounter Duration</span>
+              <span class="metadata-value">
${formatDuration(metadata.encounterDuration)
}</span>
+            </div>
+            <div class="metadata-item">
+              <span class="metadata-label">
Duration Variation</span>
+              <span class="metadata-value">
?${metadata.encounterVariation}s</span>
+            </div>
+            <div class="metadata-item">
+              <span class="metadata-label">
Target Count</span>
+              <span class="metadata-value">
${metadata.targetCount}</span>
+            </div>
+            <div class="metadata-item">
+              <span class="metadata-label">
Date Simulated</span>
+              <span class="metadata-value">
${simulationDate}</span>
+            </div>
+            <div class="metadata-item metad
ata-item-wide">
+              <span class="metadata-label">
Active Raid Buffs</span>
+              <span class="metadata-value m
etadata-value-wrap">${formatRaidBuffs(metada
ta.raidBuffs)}</span>
+            </div>
+          </div>
+        </div>
+      `;
+    }
+
+    private renderChart() {
+      if (!this.currentData) return;
+
+      const container = document.getElement
ById('chart-container')!;
+      
+      // Transform data for chart
+      const chartItems: Array<{label: strin
g, sublabel: string, value: number, category
: string}> = [];
+      
+      for (const [className, classSpecs] of
 Object.entries(this.currentData.results)) {
+        for (const [specName, specData] of 
Object.entries(classSpecs)) {
+          chartItems.push({
+            label: specName,
+            sublabel: className,
+            value: specData.dps,
+            category: className
+          });
+        }
+      }
+
+      // Sort by DPS descending
+      chartItems.sort((a, b) => b.value - a
.value);
+
+      // Create chart HTML
+      const maxDps = Math.max(...chartItems
.map(item => item.value));
+      
+      container.innerHTML = `
+        <div class="chart-container">
+          <h2 class="chart-title">DPS Ranki
ngs</h2>
+          <div class="chart-bars">
+            ${chartItems.map((item, index) 
=> `
+              <div class="chart-item">
+                <div class="chart-labels">
+                  <span class="chart-rank">
#${index + 1}</span>
+                  <span class="chart-label"
>${item.label}</span>
+                  <span class="chart-sublab
el">${item.sublabel}</span>
+                </div>
+                <div class="chart-bar-conta
iner">
+                  <div class="chart-bar" st
yle="width: ${(item.value / maxDps) * 100}%;
 background-color: ${classColors[item.catego
ry] || '#666'}">
+                  </div>
+                  <span class="chart-value"
>${Math.round(item.value).toLocaleString()}<
/span>
+                </div>
+              </div>
+            `).join('')}
+          </div>
+        </div>
+      `;
+    }
+  }
+
+  // Initialize when DOM is loaded
+  document.addEventListener('DOMContentLoad
ed', () => {
+    new DynamicRankings();
+  });
+</script>
+
+<!-- Include metadata and chart styles -->
+<style is:global>
+  .simulation-metadata {
+    background-color: #3a3a3a;
+    border-radius: 8px;
+    padding: 20px;
+    margin-bottom: 30px;
+    border: 1px solid #4a4a4a;
+  }
+  
+  .metadata-title {
+    color: #ea6962;
+    margin: 0 0 15px 0;
+    font-size: 1.2em;
+    font-weight: 600;
+  }
+  
+  .metadata-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit,
 minmax(200px, 1fr));
+    gap: 15px;
+  }
+  
+  .metadata-item {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+  }
+  
+  .metadata-item-wide {
+    grid-column: 1 / -1;
+  }
+  
+  .metadata-label {
+    font-size: 0.85em;
+    color: #a9a9a9;
+    font-weight: 500;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+  }
+  
+  .metadata-value {
+    font-size: 1em;
+    color: #ffffff;
+    font-weight: 600;
+  }
+  
+  .metadata-value-wrap {
+    white-space: normal;
+    word-wrap: break-word;
+    line-height: 1.4;
+  }
+
+  .chart-container {
+    background-color: #3a3a3a;
+    border-radius: 8px;
+    padding: 20px;
+    border: 1px solid #4a4a4a;
+  }
+  
+  .chart-title {
+    color: #ea6962;
+    margin: 0 0 20px 0;
+    font-size: 1.4em;
+    font-weight: 600;
+  }
+  
+  .chart-bars {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+  
+  .chart-item {
+    display: flex;
+    align-items: center;
+    gap: 15px;
+    padding: 8px 0;
+  }
+  
+  .chart-labels {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    min-width: 200px;
+    flex-shrink: 0;
+  }
+  
+  .chart-rank {
+    font-size: 0.9em;
+    color: #a9a9a9;
+    font-weight: 600;
+    min-width: 30px;
+  }
+  
+  .chart-label {
+    font-weight: 600;
+    color: #ffffff;
+    text-transform: capitalize;
+  }
+  
+  .chart-sublabel {
+    font-size: 0.85em;
+    color: #a9a9a9;
+    text-transform: capitalize;
+  }
+  
+  .chart-bar-container {
+    display: flex;
+    align-items: center;
+    flex: 1;
+    gap: 10px;
+  }
+  
+  .chart-bar {
+    height: 20px;
+    border-radius: 3px;
+    min-width: 2px;
+    transition: width 0.3s ease;
+  }
+  
+  .chart-value {
+    font-weight: 600;
+    color: #ffffff;
+    font-size: 0.9em;
+    min-width: 60px;
+    text-align: right;
+  }
+  
+  @media (max-width: 768px) {
+    .metadata-grid {
+      grid-template-columns: 1fr;
+    }
+    
+    .chart-labels {
+      min-width: 150px;
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 2px;
+    }
+    
+    .chart-item {
+      flex-direction: column;
+      align-items: stretch;
+    }
+    
+    .chart-bar-container {
+      width: 100%;
+    }
+  }
+</style>
\ No newline at end of file
 

diff --git a/web/src/components/SimulationMetadata.astro b/web/src/components/Simu
lationMetadata.astro
index 9d44954..f2a51c7 100644
--- a/web/src/components/SimulationMetadata.astro
+++ b/web/src/components/SimulationMetadata.astro
@@ -7,6 +7,8 @@ export interface Props {
     specCount?: number;
     encounterDuration?: number;
     encounterVariation?: number;
+    targetCount?: number;
+    raidBuffs?: Record<string, boolean | number>;
   };
 }
 
@@ -28,6 +30,18 @@ const formatDuration = (seconds: number) => {
   const remainingSeconds = seconds % 60;
   return minutes > 0 ? `${minutes}m ${remainingSeconds}s` : `${remainingSeconds}s
`;
 };
+
+// Format raid buffs for display
+const formatRaidBuffs = (buffs: Record<string, boolean | number>) => {
+  const activeBuffs = Object.entries(buffs)
+    .filter(([_, value]) => value !== false && value !== 0)
+    .map(([key, value]) => {
+      // Convert camelCase to readable format
+      const readable = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.to
UpperCase());
+      return typeof value === 'number' && value > 1 ? `${readable} (${value})` : 
readable;
+    });
+  return activeBuffs.join(', ');
+};
 ---
 
 <div class="simulation-metadata">
@@ -59,10 +73,24 @@ const formatDuration = (seconds: number) => {
       </div>
     )}
     
+    {metadata.targetCount && (
+      <div class="metadata-item">
+        <span class="metadata-label">Target Count</span>
+        <span class="metadata-value">{metadata.targetCount}</span>
+      </div>
+    )}
+    
     <div class="metadata-item">
-      <span class="metadata-label">Simulated</span>
+      <span class="metadata-label">Date Simulated</span>
       <span class="metadata-value">{simulationDate}</span>
     </div>
+    
+    {metadata.raidBuffs && (
+      <div class="metadata-item metadata-item-wide">
+        <span class="metadata-label">Active Raid Buffs</span>
+        <span class="metadata-value metadata-value-wrap">{formatRaidBuffs(metadat
a.raidBuffs)}</span>
+      </div>
+    )}
   </div>
 </div>
 
@@ -108,9 +136,19 @@ const formatDuration = (seconds: number) => {
     font-weight: 600;
   }
   
+  .metadata-item-wide {
+    grid-column: 1 / -1;
+  }
+  
+  .metadata-value-wrap {
+    white-space: normal;
+    word-wrap: break-word;
+    line-height: 1.4;
+  }
+  
   @media (max-width: 768px) {
     .metadata-grid {
       grid-template-columns: 1fr;
     }
   }
-</style>
\ No newline at end of file
+</style>

diff --git a/web/src/components/SimulationMe
tadata.astro b/web/src/components/Simulation
Metadata.astro
index 9d44954..f2a51c7 100644
--- a/web/src/components/SimulationMetadata.
astro
+++ b/web/src/components/SimulationMetadata.
astro
@@ -7,6 +7,8 @@ export interface Props {
     specCount?: number;
     encounterDuration?: number;
     encounterVariation?: number;
+    targetCount?: number;
+    raidBuffs?: Record<string, boolean | nu
mber>;
   };
 }
 
@@ -28,6 +30,18 @@ const formatDuration = (s
econds: number) => {
   const remainingSeconds = seconds % 60;
   return minutes > 0 ? `${minutes}m ${remai
ningSeconds}s` : `${remainingSeconds}s`;
 };
+
+// Format raid buffs for display
+const formatRaidBuffs = (buffs: Record<stri
ng, boolean | number>) => {
+  const activeBuffs = Object.entries(buffs)
+    .filter(([_, value]) => value !== false
 && value !== 0)
+    .map(([key, value]) => {
+      // Convert camelCase to readable form
at
+      const readable = key.replace(/([A-Z])
/g, ' $1').replace(/^./, str => str.toUpperC
ase());
+      return typeof value === 'number' && v
alue > 1 ? `${readable} (${value})` : readab
le;
+    });
+  return activeBuffs.join(', ');
+};
 ---
 
 <div class="simulation-metadata">
@@ -59,10 +73,24 @@ const formatDuration = (
seconds: number) => {
       </div>
     )}
     
+    {metadata.targetCount && (
+      <div class="metadata-item">
+        <span class="metadata-label">Target
 Count</span>
+        <span class="metadata-value">{metad
ata.targetCount}</span>
+      </div>
+    )}
+    
     <div class="metadata-item">
-      <span class="metadata-label">Simulate
d</span>
+      <span class="metadata-label">Date Sim
ulated</span>
       <span class="metadata-value">{simulat
ionDate}</span>
     </div>
+    
+    {metadata.raidBuffs && (
+      <div class="metadata-item metadata-it
em-wide">
+        <span class="metadata-label">Active
 Raid Buffs</span>
+        <span class="metadata-value metadat
a-value-wrap">{formatRaidBuffs(metadata.raid
Buffs)}</span>
+      </div>
+    )}
   </div>
 </div>
 
@@ -108,9 +136,19 @@ const formatDuration = 
(seconds: number) => {
     font-weight: 600;
   }
   
+  .metadata-item-wide {
+    grid-column: 1 / -1;
+  }
+  
+  .metadata-value-wrap {
+    white-space: normal;
+    word-wrap: break-word;
+    line-height: 1.4;
+  }
+  
   @media (max-width: 768px) {
     .metadata-grid {
       grid-template-columns: 1fr;
     }
   }
-</style>
\ No newline at end of file
+</style>
 

diff --git a/web/src/pages/index.astro b/web/src/pages/index.astro
index 6aba47c..3a5123a 100644
--- a/web/src/pages/index.astro
+++ b/web/src/pages/index.astro
@@ -1,9 +1,49 @@
 ---
-import RankingsLayout from '../layouts/RankingsLayout.astro';
+import DynamicRankings from '../components/DynamicRankings.astro';
 ---
 
-<RankingsLayout 
-  title="Single Target DPS Rankings" 
-  dataFile="dps_p1_raid_single_long.json"
-  description="Simulation results for single target raid encounters in MoP"
-/>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+    <meta name="viewport" content="width=device-width" />
+    <title>WoW MoP DPS Rankings</title>
+    <style>
+      body {
+        font-family: Arial, sans-serif;
+        margin: 0;
+        padding: 0;
+        background-color: #282828;
+        color: #ffffff;
+      }
+      
+      .page-header {
+        text-align: center;
+        padding: 30px 20px;
+        background-color: #3a3a3a;
+        border-bottom: 1px solid #4a4a4a;
+      }
+      
+      .page-title {
+        color: #ea6962;
+        margin: 0 0 10px 0;
+        font-size: 2.5em;
+        font-weight: 700;
+      }
+      
+      .page-description {
+        color: #aaaaaa;
+        font-size: 1.1em;
+        margin: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="page-header">
+      <h1 class="page-title">WoW MoP DPS Rankings</h1>
+      <p class="page-description">Interactive simulation results for all encounte
rs and target counts</p>
+    </div>
+    
+    <DynamicRankings client:load />
+  </body>
+</html>

diff --git a/web/src/pages/index.astro b/web
/src/pages/index.astro
index 6aba47c..3a5123a 100644
--- a/web/src/pages/index.astro
+++ b/web/src/pages/index.astro
@@ -1,9 +1,49 @@
 ---
-import RankingsLayout from '../layouts/Rank
ingsLayout.astro';
+import DynamicRankings from '../components/
DynamicRankings.astro';
 ---
 
-<RankingsLayout 
-  title="Single Target DPS Rankings" 
-  dataFile="dps_p1_raid_single_long.json"
-  description="Simulation results for singl
e target raid encounters in MoP"
-/>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <link rel="icon" type="image/svg+xml" h
ref="/favicon.svg" />
+    <meta name="viewport" content="width=de
vice-width" />
+    <title>WoW MoP DPS Rankings</title>
+    <style>
+      body {
+        font-family: Arial, sans-serif;
+        margin: 0;
+        padding: 0;
+        background-color: #282828;
+        color: #ffffff;
+      }
+      
+      .page-header {
+        text-align: center;
+        padding: 30px 20px;
+        background-color: #3a3a3a;
+        border-bottom: 1px solid #4a4a4a;
+      }
+      
+      .page-title {
+        color: #ea6962;
+        margin: 0 0 10px 0;
+        font-size: 2.5em;
+        font-weight: 700;
+      }
+      
+      .page-description {
+        color: #aaaaaa;
+        font-size: 1.1em;
+        margin: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="page-header">
+      <h1 class="page-title">WoW MoP DPS Ra
nkings</h1>
+      <p class="page-description">Interacti
ve simulation results for all encounters and
 target counts</p>
+    </div>
+    
+    <DynamicRankings client:load />
+  </body>
+</html>
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET