┌─ web/src/components/ItemDatabase.astro ────────────────────────────────────┐
│ diff --git a/web/src/components/ItemDatabase.astro b/web/src/components/ItemDataba │
│ se.astro │
│ new file mode 100644 │
│ index 0000000..27c8110 │
│ --- /dev/null │
│ +++ b/web/src/components/ItemDatabase.astro │
│ @@ -0,0 +1,151 @@ │
│ +--- │
│ +// ItemDatabase.astro - Handles item display, reforging, and Wowhead integration │
│ +--- │
│ + │
│ +<script> │
│ +// Reforging data mapping │
│ +const reforgeData = { │
│ + "113": { "id": 113, "i1": 6, "s1": "spi", "i2": 13, "s2": "dodgertng", "v": 0.4 │
│ }, │
│ + "114": { "id": 114, "i1": 6, "s1": "spi", "i2": 14, "s2": "parryrtng", "v": 0.4 │
│ }, │
│ + "115": { "id": 115, "i1": 6, "s1": "spi", "i2": 31, "s2": "hitrtng", "v": 0.4 } │
│ , │
│ + "116": { "id": 116, "i1": 6, "s1": "spi", "i2": 32, "s2": "critstrkrtng", "v": │
│ 0.4 }, │
│ + "117": { "id": 117, "i1": 6, "s1": "spi", "i2": 36, "s2": "hastertng", "v": 0.4 │
│ }, │
│ + "118": { "id": 118, "i1": 6, "s1": "spi", "i2": 37, "s2": "exprtng", "v": 0.4 } │
│ , │
│ + "119": { "id": 119, "i1": 6, "s1": "spi", "i2": 49, "s2": "mastrtng", "v": 0.4 │
│ }, │
│ + "120": { "id": 120, "i1": 13, "s1": "dodgertng", "i2": 6, "s2": "spi", "v": 0.4 │
│ }, │
│ + "121": { "id": 121, "i1": 13, "s1": "dodgertng", "i2": 14, "s2": "parryrtng", " │
│ v": 0.4 }, │
│ + "122": { "id": 122, "i1": 13, "s1": "dodgertng", "i2": 31, "s2": "hitrtng", "v" │
│ : 0.4 }, │
│ + "123": { "id": 123, "i1": 13, "s1": "dodgertng", "i2": 32, "s2": "critstrkrtng" │
│ , "v": 0.4 }, │
│ + "124": { "id": 124, "i1": 13, "s1": "dodgertng", "i2": 36, "s2": "hastertng", " │
│ v": 0.4 }, │
│ + "125": { "id": 125, "i1": 13, "s1": "dodgertng", "i2": 37, "s2": "exprtng", "v" │
│ : 0.4 }, │
│ + "126": { "id": 126, "i1": 13, "s1": "dodgertng", "i2": 49, "s2": "mastrtng", "v │
│ ": 0.4 }, │
│ + "127": { "id": 127, "i1": 14, "s1": "parryrtng", "i2": 6, "s2": "spi", "v": 0.4 │
│ }, │
│ + "128": { "id": 128, "i1": 14, "s1": "parryrtng", "i2": 13, "s2": "dodgertng", " │
│ v": 0.4 }, │
│ + "129": { "id": 129, "i1": 14, "s1": "parryrtng", "i2": 31, "s2": "hitrtng", "v" │
│ : 0.4 }, │
│ + "130": { "id": 130, "i1": 14, "s1": "parryrtng", "i2": 32, "s2": "critstrkrtng" │
│ , "v": 0.4 }, │
│ + "131": { "id": 131, "i1": 14, "s1": "parryrtng", "i2": 36, "s2": "hastertng", " │
│ v": 0.4 }, │
│ + "132": { "id": 132, "i1": 14, "s1": "parryrtng", "i2": 37, "s2": "exprtng", "v" │
│ : 0.4 }, │
│ + "133": { "id": 133, "i1": 14, "s1": "parryrtng", "i2": 49, "s2": "mastrtng", "v │
│ ": 0.4 }, │
│ + "134": { "id": 134, "i1": 31, "s1": "hitrtng", "i2": 6, "s2": "spi", "v": 0.4 } │
│ , │
│ + "135": { "id": 135, "i1": 31, "s1": "hitrtng", "i2": 13, "s2": "dodgertng", "v" │
│ : 0.4 }, │
│ + "136": { "id": 136, "i1": 31, "s1": "hitrtng", "i2": 14, "s2": "parryrtng", "v" │
│ : 0.4 }, │
│ + "137": { "id": 137, "i1": 31, "s1": "hitrtng", "i2": 32, "s2": "critstrkrtng", │
│ "v": 0.4 }, │
│ + "138": { "id": 138, "i1": 31, "s1": "hitrtng", "i2": 36, "s2": "hastertng", "v" │
│ : 0.4 }, │
│ + "139": { "id": 139, "i1": 31, "s1": "hitrtng", "i2": 37, "s2": "exprtng", "v": │
│ 0.4 }, │
│ + "140": { "id": 140, "i1": 31, "s1": "hitrtng", "i2": 49, "s2": "mastrtng", "v": │
│ 0.4 }, │
│ + "141": { "id": 141, "i1": 32, "s1": "critstrkrtng", "i2": 6, "s2": "spi", "v": │
│ 0.4 }, │
│ + "142": { "id": 142, "i1": 32, "s1": "critstrkrtng", "i2": 13, "s2": "dodgertng" │
│ , "v": 0.4 }, │
│ + "143": { "id": 143, "i1": 32, "s1": "critstrkrtng", "i2": 14, "s2": "parryrtng" │
│ , "v": 0.4 }, │
│ + "144": { "id": 144, "i1": 32, "s1": "critstrkrtng", "i2": 31, "s2": "hitrtng", │
│ "v": 0.4 }, │
│ + "145": { "id": 145, "i1": 32, "s1": "critstrkrtng", "i2": 36, "s2": "hastertng" │
│ , "v": 0.4 }, │
│ + "146": { "id": 146, "i1": 32, "s1": "critstrkrtng", "i2": 37, "s2": "exprtng", │
│ "v": 0.4 }, │
│ + "147": { "id": 147, "i1": 32, "s1": "critstrkrtng", "i2": 49, "s2": "mastrtng", │
│ "v": 0.4 }, │
│ + "148": { "id": 148, "i1": 36, "s1": "hastertng", "i2": 6, "s2": "spi", "v": 0.4 │
│ }, │
│ + "149": { "id": 149, "i1": 36, "s1": "hastertng", "i2": 13, "s2": "dodgertng", " │
│ v": 0.4 }, │
│ + "150": { "id": 150, "i1": 36, "s1": "hastertng", "i2": 14, "s2": "parryrtng", " │
│ v": 0.4 }, │
│ + "151": { "id": 151, "i1": 36, "s1": "hastertng", "i2": 31, "s2": "hitrtng", "v" │
│ : 0.4 }, │
│ + "152": { "id": 152, "i1": 36, "s1": "hastertng", "i2": 32, "s2": "critstrkrtng" │
│ , "v": 0.4 }, │
│ + "153": { "id": 153, "i1": 36, "s1": "hastertng", "i2": 37, "s2": "exprtng", "v" │
│ : 0.4 }, │
│ + "154": { "id": 154, "i1": 36, "s1": "hastertng", "i2": 49, "s2": "mastrtng", "v │
│ ": 0.4 }, │
│ + "155": { "id": 155, "i1": 37, "s1": "exprtng", "i2": 6, "s2": "spi", "v": 0.4 } │
│ , │
│ + "156": { "id": 156, "i1": 37, "s1": "exprtng", "i2": 13, "s2": "dodgertng", "v" │
│ : 0.4 }, │
│ + "157": { "id": 157, "i1": 37, "s1": "exprtng", "i2": 14, "s2": "parryrtng", "v" │
│ : 0.4 }, │
│ + "158": { "id": 158, "i1": 37, "s1": "exprtng", "i2": 31, "s2": "hitrtng", "v": │
│ 0.4 }, │
│ + "159": { "id": 159, "i1": 37, "s1": "exprtng", "i2": 32, "s2": "critstrkrtng", │
│ "v": 0.4 }, │
│ + "160": { "id": 160, "i1": 37, "s1": "exprtng", "i2": 36, "s2": "hastertng", "v" │
│ : 0.4 }, │
│ + "161": { "id": 161, "i1": 37, "s1": "exprtng", "i2": 49, "s2": "mastrtng", "v": │
│ 0.4 }, │
│ + "162": { "id": 162, "i1": 49, "s1": "mastrtng", "i2": 6, "s2": "spi", "v": 0.4 │
│ }, │
│ + "163": { "id": 163, "i1": 49, "s1": "mastrtng", "i2": 13, "s2": "dodgertng", "v │
│ ": 0.4 }, │
│ + "164": { "id": 164, "i1": 49, "s1": "mastrtng", "i2": 14, "s2": "parryrtng", "v │
│ ": 0.4 }, │
│ + "165": { "id": 165, "i1": 49, "s1": "mastrtng", "i2": 31, "s2": "hitrtng", "v": │
│ 0.4 }, │
│ + "166": { "id": 166, "i1": 49, "s1": "mastrtng", "i2": 32, "s2": "critstrkrtng", │
│ "v": 0.4 }, │
│ + "167": { "id": 167, "i1": 49, "s1": "mastrtng", "i2": 36, "s2": "hastertng", "v │
│ ": 0.4 }, │
│ + "168": { "id": 168, "i1": 49, "s1": "mastrtng", "i2": 37, "s2": "exprtng", "v": │
│ 0.4 } │
│ +}; │
│ + │
│ +// Stat name mappings │
│ +const statNames = { │
│ + "spi": "Spirit", │
│ + "dodgertng": "Dodge", │
│ + "parryrtng": "Parry", │
│ + "hitrtng": "Hit", │
│ + "critstrkrtng": "Crit", │
│ + "hastertng": "Haste", │
│ + "exprtng": "Expertise", │
│ + "mastrtng": "Mastery" │
│ +}; │
│ + │
│ +// Global functions for item database functionality │
│ +window.ItemDatabase = { │
│ + // Helper function to format reforging │
│ + formatReforge: (reforgeId) => { │
│ + if (!reforgeId || reforgeId === 0) return ''; │
│ + const reforge = reforgeData[reforgeId.toString()]; │
│ + if (!reforge) return ` [R:${reforgeId}]`; │
│ + │
│ + const fromStat = statNames[reforge.s1] || reforge.s1; │
│ + const toStat = statNames[reforge.s2] || reforge.s2; │
│ + return ` [${fromStat} ? ${toStat}]`; │
│ + }, │
│ + │
│ + // Generate Wowhead URL for items │
│ + getWowheadUrl: (itemId) => { │
│ + return `https://www.wowhead.com/mop-classic/item=${itemId}`; │
│ + }, │
│ + │
│ + // Format equipment item with all details │
│ + formatEquipmentItem: (item) => { │
│ + if (!item || !item.id) return null; │
│ + │
│ + const itemName = `Item ${item.id}`; // TODO: Replace with actual item names │
│ + const wowheadUrl = window.ItemDatabase.getWowheadUrl(item.id); │
│ + │
│ + // Build details array │
│ + const details = []; │
│ + │
│ + // Reforging │
│ + if (item.reforging) { │
│ + const reforge = reforgeData[item.reforging.toString()]; │
│ + if (reforge) { │
│ + const fromStat = statNames[reforge.s1] || reforge.s1; │
│ + const toStat = statNames[reforge.s2] || reforge.s2; │
│ + details.push(`${fromStat} ? ${toStat}`); │
│ + } │
│ + } │
│ + │
│ + // Gems │
│ + if (item.gems && item.gems.length > 0) { │
│ + const activeGems = item.gems.filter(g => g && g !== 0); │
│ + activeGems.forEach(gemId => { │
│ + details.push(`Gem ${gemId}`); // TODO: Replace with actual gem names and │
│ stats │
│ + }); │
│ + } │
│ + │
│ + // Enchant │
│ + if (item.enchant) { │
│ + details.push(`Enchant ${item.enchant}`); // TODO: Replace with actual encha │
│ nt names │
│ + } │
│ + │
│ + // Tinker │
│ + if (item.tinker) { │
│ + details.push(`Tinker ${item.tinker}`); // TODO: Replace with actual tinker │
│ names │
│ + } │
│ + │
│ + return { │
│ + itemId: item.id, │
│ + itemName: itemName, │
│ + itemDetails: details, │
│ + wowheadUrl: wowheadUrl │
│ + }; │
│ + }, │
│ + │
│ + // Format gem information │
│ + formatGems: (gems) => { │
│ + if (!gems || gems.length === 0) return ''; │
│ + const activeGems = gems.filter(g => g && g !== 0); │
│ + if (activeGems.length === 0) return ''; │
│ + │
│ + return activeGems.map(gemId => `Gem ${gemId}`).join(', '); │
│ + } │
│ +}; │
│ +</script> │
│ \ No newline at end of file │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...components/ItemDatabase.astro ───┐
│ diff --git a/web/src/components/ItemDatabase │
│ .astro b/web/src/components/ItemDatabase.ast │
│ ro │
│ new file mode 100644 │
│ index 0000000..27c8110 │
│ --- /dev/null │
│ +++ b/web/src/components/ItemDatabase.astro │
│ @@ -0,0 +1,151 @@ │
│ +--- │
│ +// ItemDatabase.astro - Handles item displa │
│ y, reforging, and Wowhead integration │
│ +--- │
│ + │
│ +<script> │
│ +// Reforging data mapping │
│ +const reforgeData = { │
│ + "113": { "id": 113, "i1": 6, "s1": "spi", │
│ "i2": 13, "s2": "dodgertng", "v": 0.4 }, │
│ + "114": { "id": 114, "i1": 6, "s1": "spi", │
│ "i2": 14, "s2": "parryrtng", "v": 0.4 }, │
│ + "115": { "id": 115, "i1": 6, "s1": "spi", │
│ "i2": 31, "s2": "hitrtng", "v": 0.4 }, │
│ + "116": { "id": 116, "i1": 6, "s1": "spi", │
│ "i2": 32, "s2": "critstrkrtng", "v": 0.4 }, │
│ + "117": { "id": 117, "i1": 6, "s1": "spi", │
│ "i2": 36, "s2": "hastertng", "v": 0.4 }, │
│ + "118": { "id": 118, "i1": 6, "s1": "spi", │
│ "i2": 37, "s2": "exprtng", "v": 0.4 }, │
│ + "119": { "id": 119, "i1": 6, "s1": "spi", │
│ "i2": 49, "s2": "mastrtng", "v": 0.4 }, │
│ + "120": { "id": 120, "i1": 13, "s1": "dodg │
│ ertng", "i2": 6, "s2": "spi", "v": 0.4 }, │
│ + "121": { "id": 121, "i1": 13, "s1": "dodg │
│ ertng", "i2": 14, "s2": "parryrtng", "v": 0. │
│ 4 }, │
│ + "122": { "id": 122, "i1": 13, "s1": "dodg │
│ ertng", "i2": 31, "s2": "hitrtng", "v": 0.4 │
│ }, │
│ + "123": { "id": 123, "i1": 13, "s1": "dodg │
│ ertng", "i2": 32, "s2": "critstrkrtng", "v": │
│ 0.4 }, │
│ + "124": { "id": 124, "i1": 13, "s1": "dodg │
│ ertng", "i2": 36, "s2": "hastertng", "v": 0. │
│ 4 }, │
│ + "125": { "id": 125, "i1": 13, "s1": "dodg │
│ ertng", "i2": 37, "s2": "exprtng", "v": 0.4 │
│ }, │
│ + "126": { "id": 126, "i1": 13, "s1": "dodg │
│ ertng", "i2": 49, "s2": "mastrtng", "v": 0.4 │
│ }, │
│ + "127": { "id": 127, "i1": 14, "s1": "parr │
│ yrtng", "i2": 6, "s2": "spi", "v": 0.4 }, │
│ + "128": { "id": 128, "i1": 14, "s1": "parr │
│ yrtng", "i2": 13, "s2": "dodgertng", "v": 0. │
│ 4 }, │
│ + "129": { "id": 129, "i1": 14, "s1": "parr │
│ yrtng", "i2": 31, "s2": "hitrtng", "v": 0.4 │
│ }, │
│ + "130": { "id": 130, "i1": 14, "s1": "parr │
│ yrtng", "i2": 32, "s2": "critstrkrtng", "v": │
│ 0.4 }, │
│ + "131": { "id": 131, "i1": 14, "s1": "parr │
│ yrtng", "i2": 36, "s2": "hastertng", "v": 0. │
│ 4 }, │
│ + "132": { "id": 132, "i1": 14, "s1": "parr │
│ yrtng", "i2": 37, "s2": "exprtng", "v": 0.4 │
│ }, │
│ + "133": { "id": 133, "i1": 14, "s1": "parr │
│ yrtng", "i2": 49, "s2": "mastrtng", "v": 0.4 │
│ }, │
│ + "134": { "id": 134, "i1": 31, "s1": "hitr │
│ tng", "i2": 6, "s2": "spi", "v": 0.4 }, │
│ + "135": { "id": 135, "i1": 31, "s1": "hitr │
│ tng", "i2": 13, "s2": "dodgertng", "v": 0.4 │
│ }, │
│ + "136": { "id": 136, "i1": 31, "s1": "hitr │
│ tng", "i2": 14, "s2": "parryrtng", "v": 0.4 │
│ }, │
│ + "137": { "id": 137, "i1": 31, "s1": "hitr │
│ tng", "i2": 32, "s2": "critstrkrtng", "v": 0 │
│ .4 }, │
│ + "138": { "id": 138, "i1": 31, "s1": "hitr │
│ tng", "i2": 36, "s2": "hastertng", "v": 0.4 │
│ }, │
│ + "139": { "id": 139, "i1": 31, "s1": "hitr │
│ tng", "i2": 37, "s2": "exprtng", "v": 0.4 }, │
│ + "140": { "id": 140, "i1": 31, "s1": "hitr │
│ tng", "i2": 49, "s2": "mastrtng", "v": 0.4 } │
│ , │
│ + "141": { "id": 141, "i1": 32, "s1": "crit │
│ strkrtng", "i2": 6, "s2": "spi", "v": 0.4 }, │
│ + "142": { "id": 142, "i1": 32, "s1": "crit │
│ strkrtng", "i2": 13, "s2": "dodgertng", "v": │
│ 0.4 }, │
│ + "143": { "id": 143, "i1": 32, "s1": "crit │
│ strkrtng", "i2": 14, "s2": "parryrtng", "v": │
│ 0.4 }, │
│ + "144": { "id": 144, "i1": 32, "s1": "crit │
│ strkrtng", "i2": 31, "s2": "hitrtng", "v": 0 │
│ .4 }, │
│ + "145": { "id": 145, "i1": 32, "s1": "crit │
│ strkrtng", "i2": 36, "s2": "hastertng", "v": │
│ 0.4 }, │
│ + "146": { "id": 146, "i1": 32, "s1": "crit │
│ strkrtng", "i2": 37, "s2": "exprtng", "v": 0 │
│ .4 }, │
│ + "147": { "id": 147, "i1": 32, "s1": "crit │
│ strkrtng", "i2": 49, "s2": "mastrtng", "v": │
│ 0.4 }, │
│ + "148": { "id": 148, "i1": 36, "s1": "hast │
│ ertng", "i2": 6, "s2": "spi", "v": 0.4 }, │
│ + "149": { "id": 149, "i1": 36, "s1": "hast │
│ ertng", "i2": 13, "s2": "dodgertng", "v": 0. │
│ 4 }, │
│ + "150": { "id": 150, "i1": 36, "s1": "hast │
│ ertng", "i2": 14, "s2": "parryrtng", "v": 0. │
│ 4 }, │
│ + "151": { "id": 151, "i1": 36, "s1": "hast │
│ ertng", "i2": 31, "s2": "hitrtng", "v": 0.4 │
│ }, │
│ + "152": { "id": 152, "i1": 36, "s1": "hast │
│ ertng", "i2": 32, "s2": "critstrkrtng", "v": │
│ 0.4 }, │
│ + "153": { "id": 153, "i1": 36, "s1": "hast │
│ ertng", "i2": 37, "s2": "exprtng", "v": 0.4 │
│ }, │
│ + "154": { "id": 154, "i1": 36, "s1": "hast │
│ ertng", "i2": 49, "s2": "mastrtng", "v": 0.4 │
│ }, │
│ + "155": { "id": 155, "i1": 37, "s1": "expr │
│ tng", "i2": 6, "s2": "spi", "v": 0.4 }, │
│ + "156": { "id": 156, "i1": 37, "s1": "expr │
│ tng", "i2": 13, "s2": "dodgertng", "v": 0.4 │
│ }, │
│ + "157": { "id": 157, "i1": 37, "s1": "expr │
│ tng", "i2": 14, "s2": "parryrtng", "v": 0.4 │
│ }, │
│ + "158": { "id": 158, "i1": 37, "s1": "expr │
│ tng", "i2": 31, "s2": "hitrtng", "v": 0.4 }, │
│ + "159": { "id": 159, "i1": 37, "s1": "expr │
│ tng", "i2": 32, "s2": "critstrkrtng", "v": 0 │
│ .4 }, │
│ + "160": { "id": 160, "i1": 37, "s1": "expr │
│ tng", "i2": 36, "s2": "hastertng", "v": 0.4 │
│ }, │
│ + "161": { "id": 161, "i1": 37, "s1": "expr │
│ tng", "i2": 49, "s2": "mastrtng", "v": 0.4 } │
│ , │
│ + "162": { "id": 162, "i1": 49, "s1": "mast │
│ rtng", "i2": 6, "s2": "spi", "v": 0.4 }, │
│ + "163": { "id": 163, "i1": 49, "s1": "mast │
│ rtng", "i2": 13, "s2": "dodgertng", "v": 0.4 │
│ }, │
│ + "164": { "id": 164, "i1": 49, "s1": "mast │
│ rtng", "i2": 14, "s2": "parryrtng", "v": 0.4 │
│ }, │
│ + "165": { "id": 165, "i1": 49, "s1": "mast │
│ rtng", "i2": 31, "s2": "hitrtng", "v": 0.4 } │
│ , │
│ + "166": { "id": 166, "i1": 49, "s1": "mast │
│ rtng", "i2": 32, "s2": "critstrkrtng", "v": │
│ 0.4 }, │
│ + "167": { "id": 167, "i1": 49, "s1": "mast │
│ rtng", "i2": 36, "s2": "hastertng", "v": 0.4 │
│ }, │
│ + "168": { "id": 168, "i1": 49, "s1": "mast │
│ rtng", "i2": 37, "s2": "exprtng", "v": 0.4 } │
│ +}; │
│ + │
│ +// Stat name mappings │
│ +const statNames = { │
│ + "spi": "Spirit", │
│ + "dodgertng": "Dodge", │
│ + "parryrtng": "Parry", │
│ + "hitrtng": "Hit", │
│ + "critstrkrtng": "Crit", │
│ + "hastertng": "Haste", │
│ + "exprtng": "Expertise", │
│ + "mastrtng": "Mastery" │
│ +}; │
│ + │
│ +// Global functions for item database funct │
│ ionality │
│ +window.ItemDatabase = { │
│ + // Helper function to format reforging │
│ + formatReforge: (reforgeId) => { │
│ + if (!reforgeId || reforgeId === 0) retu │
│ rn ''; │
│ + const reforge = reforgeData[reforgeId.t │
│ oString()]; │
│ + if (!reforge) return ` [R:${reforgeId}] │
│ `; │
│ + │
│ + const fromStat = statNames[reforge.s1] │
│ || reforge.s1; │
│ + const toStat = statNames[reforge.s2] || │
│ reforge.s2; │
│ + return ` [${fromStat} ? ${toStat}]`; │
│ + }, │
│ + │
│ + // Generate Wowhead URL for items │
│ + getWowheadUrl: (itemId) => { │
│ + return `https://www.wowhead.com/mop-cla │
│ ssic/item=${itemId}`; │
│ + }, │
│ + │
│ + // Format equipment item with all details │
│ + formatEquipmentItem: (item) => { │
│ + if (!item || !item.id) return null; │
│ + │
│ + const itemName = `Item ${item.id}`; // │
│ TODO: Replace with actual item names │
│ + const wowheadUrl = window.ItemDatabase. │
│ getWowheadUrl(item.id); │
│ + │
│ + // Build details array │
│ + const details = []; │
│ + │
│ + // Reforging │
│ + if (item.reforging) { │
│ + const reforge = reforgeData[item.refo │
│ rging.toString()]; │
│ + if (reforge) { │
│ + const fromStat = statNames[reforge. │
│ s1] || reforge.s1; │
│ + const toStat = statNames[reforge.s2 │
│ ] || reforge.s2; │
│ + details.push(`${fromStat} ? ${toSta │
│ t}`); │
│ + } │
│ + } │
│ + │
│ + // Gems │
│ + if (item.gems && item.gems.length > 0) │
│ { │
│ + const activeGems = item.gems.filter(g │
│ => g && g !== 0); │
│ + activeGems.forEach(gemId => { │
│ + details.push(`Gem ${gemId}`); // TO │
│ DO: Replace with actual gem names and stats │
│ + }); │
│ + } │
│ + │
│ + // Enchant │
│ + if (item.enchant) { │
│ + details.push(`Enchant ${item.enchant} │
│ `); // TODO: Replace with actual enchant nam │
│ es │
│ + } │
│ + │
│ + // Tinker │
│ + if (item.tinker) { │
│ + details.push(`Tinker ${item.tinker}`) │
│ ; // TODO: Replace with actual tinker names │
│ + } │
│ + │
│ + return { │
│ + itemId: item.id, │
│ + itemName: itemName, │
│ + itemDetails: details, │
│ + wowheadUrl: wowheadUrl │
│ + }; │
│ + }, │
│ + │
│ + // Format gem information │
│ + formatGems: (gems) => { │
│ + if (!gems || gems.length === 0) return │
│ ''; │
│ + const activeGems = gems.filter(g => g & │
│ & g !== 0); │
│ + if (activeGems.length === 0) return ''; │
│ + │
│ + return activeGems.map(gemId => `Gem ${g │
│ emId}`).join(', '); │
│ + } │
│ +}; │
│ +</script> │
│ \ No newline at end of file │
└──────────────────────────────────────────────┘
┌─ web/src/layouts/ChartLayout.astro ────────────────────────────────────────┐
│ diff --git a/web/src/layouts/ChartLayout.astro b/web/src/layouts/ChartLayout.astro │
│ index f3ffc68..4c148bf 100644 │
│ --- a/web/src/layouts/ChartLayout.astro │
│ +++ b/web/src/layouts/ChartLayout.astro │
│ @@ -1,6 +1,7 @@ │
│ --- │
│ import Navigation from '../components/Navigation.astro'; │
│ import Warning from '../components/Warning.astro'; │
│ +import ItemDatabase from '../components/ItemDatabase.astro'; │
│ │
│ export interface Props { │
│ title: string; │
│ @@ -231,6 +232,13 @@ const isFixedClassSpec = fixedClass && fixedSpec; │
│ align-items: center; │
│ gap: 15px; │
│ padding: 8px 0; │
│ + cursor: pointer; │
│ + border-radius: 4px; │
│ + transition: background-color 0.2s ease; │
│ + } │
│ + │
│ + .chart-item:hover { │
│ + background-color: rgba(255,255,255,0.05); │
│ } │
│ │
│ .chart-labels { │
│ @@ -293,6 +301,203 @@ const isFixedClassSpec = fixedClass && fixedSpec; │
│ text-align: right; │
│ flex-shrink: 0; │
│ } │
│ + │
│ + /* Expandable chart item styles */ │
│ + .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-item-content { │
│ + display: grid; │
│ + grid-template-columns: 180px 1fr; │
│ + align-items: center; │
│ + gap: 15px; │
│ + } │
│ + │
│ + .chart-expand-icon { │
│ + margin-left: 8px; │
│ + font-size: 0.8em; │
│ + color: var(--text-muted); │
│ + transition: transform 0.2s ease; │
│ + } │
│ + │
│ + .chart-item-expanded .chart-expand-icon { │
│ + transform: rotate(90deg); │
│ + } │
│ + │
│ + .chart-dropdown { │
│ + grid-column: 1 / -1; │
│ + margin-top: 12px; │
│ + padding: 16px; │
│ + background-color: rgba(255,255,255,0.03); │
│ + 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); │
│ + } │
│ + } │
│ + │
│ + .loadout-section { │
│ + margin-bottom: 20px; │
│ + } │
│ + │
│ + .loadout-section:last-child { │
│ + margin-bottom: 0; │
│ + } │
│ + │
│ + .loadout-title { │
│ + color: var(--highlight-color); │
│ + font-size: 1em; │
│ + font-weight: 600; │
│ + margin-bottom: 8px; │
│ + display: flex; │
│ + align-items: center; │
│ + gap: 8px; │
│ + } │
│ + │
│ + .loadout-grid { │
│ + display: grid; │
│ + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); │
│ + gap: 12px; │
│ + } │
│ + │
│ + .loadout-item { │
│ + display: flex; │
│ + flex-direction: column; │
│ + gap: 3px; │
│ + } │
│ + │
│ + .loadout-label { │
│ + font-size: 0.8em; │
│ + color: var(--text-muted); │
│ + font-weight: 500; │
│ + text-transform: uppercase; │
│ + letter-spacing: 0.5px; │
│ + } │
│ + │
│ + .loadout-value { │
│ + font-size: 0.9em; │
│ + color: var(--text-primary); │
│ + font-weight: 500; │
│ + } │
│ + │
│ + .loadout-talents { │
│ + font-family: 'Courier New', monospace; │
│ + font-size: 1.1em; │
│ + letter-spacing: 2px; │
│ + color: var(--highlight-color); │
│ + } │
│ + │
│ + .loadout-actions { │
│ + display: flex; │
│ + gap: 12px; │
│ + margin-top: 16px; │
│ + padding-top: 16px; │
│ + border-top: 1px solid rgba(255,255,255,0.1); │
│ + } │
│ + │
│ + .loadout-button { │
│ + padding: 6px 12px; │
│ + background-color: var(--bg-primary); │
│ + border: 1px solid rgba(255,255,255,0.2); │
│ + border-radius: 4px; │
│ + color: var(--text-primary); │
│ + text-decoration: none; │
│ + font-size: 0.85em; │
│ + font-weight: 500; │
│ + transition: all 0.2s ease; │
│ + cursor: pointer; │
│ + } │
│ + │
│ + .loadout-button:hover { │
│ + background-color: rgba(255,255,255,0.1); │
│ + border-color: var(--highlight-color); │
│ + } │
│ + │
│ + .equipment-grid { │
│ + display: grid; │
│ + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); │
│ + gap: 8px; │
│ + } │
│ + │
│ + .equipment-slot { │
│ + display: flex; │
│ + justify-content: space-between; │
│ + align-items: flex-start; │
│ + padding: 8px; │
│ + background-color: rgba(255,255,255,0.05); │
│ + border-radius: 3px; │
│ + font-size: 0.85em; │
│ + gap: 12px; │
│ + } │
│ + │
│ + .equipment-slot-name { │
│ + color: var(--text-muted); │
│ + font-weight: 500; │
│ + min-width: 80px; │
│ + } │
│ + │
│ + .equipment-item-link { │
│ + color: #ffd100; │
│ + text-decoration: none; │
│ + font-family: 'Courier New', monospace; │
│ + font-size: 0.9em; │
│ + transition: color 0.2s ease; │
│ + } │
│ + │
│ + .equipment-item-link:hover { │
│ + color: #ffed4e; │
│ + text-decoration: underline; │
│ + } │
│ + │
│ + .equipment-item-info { │
│ + flex: 1; │
│ + display: flex; │
│ + flex-direction: column; │
│ + gap: 2px; │
│ + } │
│ + │
│ + .equipment-detail { │
│ + color: var(--text-muted); │
│ + font-size: 0.8em; │
│ + line-height: 1.2; │
│ + } │
│ + │
│ + .equipment-item { │
│ + color: var(--text-primary); │
│ + text-align: right; │
│ + font-family: 'Courier New', monospace; │
│ + } │
│ </style> │
│ </head> │
│ <body> │
│ @@ -456,6 +661,230 @@ const formatSimulationDate = (timestamp) => { │
│ }); │
│ }; │
│ │
│ +// Loadout formatting functions │
│ +const formatLoadout = (loadout) => { │
│ + if (!loadout) return null; │
│ + │
│ + const sections = []; │
│ + │
│ + // Character Info │
│ + if (loadout.race || loadout.class || loadout.profession1 || loadout.profession2 │
│ ) { │
│ + const items = []; │
│ + if (loadout.race) items.push({ label: 'Race', value: formatRace(loadout.race) │
│ }); │
│ + if (loadout.class) items.push({ label: 'Class', value: formatClass(loadout.cl │
│ ass) }); │
│ + if (loadout.profession1) items.push({ label: 'Profession 1', value: loadout.p │
│ rofession1 }); │
│ + if (loadout.profession2) items.push({ label: 'Profession 2', value: loadout.p │
│ rofession2 }); │
│ + sections.push({ title: 'Character', items }); │
│ + } │
│ + │
│ + // Talents & Glyphs │
│ + if (loadout.talentsString || loadout.glyphs) { │
│ + const items = []; │
│ + if (loadout.talentsString) { │
│ + items.push({ label: 'Talents', value: loadout.talentsString, isSpecial: 'ta │
│ lents' }); │
│ + } │
│ + if (loadout.glyphs) { │
│ + const glyphText = formatGlyphs(loadout.glyphs); │
│ + if (glyphText) items.push({ label: 'Glyphs', value: glyphText }); │
│ + } │
│ + sections.push({ title: 'Talents & Glyphs', items }); │
│ + } │
│ + │
│ + // Consumables │
│ + if (loadout.consumables) { │
│ + const items = formatConsumables(loadout.consumables); │
│ + if (items.length > 0) sections.push({ title: 'Consumables', items }); │
│ + } │
│ + │
│ + // Equipment Summary │
│ + if (loadout.equipment && loadout.equipment.items) { │
│ + const equipmentSummary = formatEquipmentSummary(loadout.equipment.items); │
│ + if (equipmentSummary.length > 0) { │
│ + sections.push({ title: 'Equipment', items: equipmentSummary, isEquipment: t │
│ rue }); │
│ + } │
│ + } │
│ + │
│ + return sections; │
│ +}; │
│ + │
│ +const formatRace = (race) => { │
│ + return race.replace('Race', '').replace(/([A-Z])/g, ' $1').trim(); │
│ +}; │
│ + │
│ +const formatClass = (className) => { │
│ + return className.replace('Class', '').replace(/([A-Z])/g, ' $1').trim(); │
│ +}; │
│ + │
│ +const formatGlyphs = (glyphs) => { │
│ + const glyphList = []; │
│ + ['major1', 'major2', 'major3', 'minor1', 'minor2', 'minor3'].forEach(slot => { │
│ + if (glyphs[slot]) glyphList.push(`${slot}: ${glyphs[slot]}`); │
│ + }); │
│ + return glyphList.length > 0 ? glyphList.join(', ') : null; │
│ +}; │
│ + │
│ +const formatConsumables = (consumables) => { │
│ + const items = []; │
│ + if (consumables.flaskId) { │
│ + items.push({ │
│ + label: 'Flask', │
│ + value: consumables.flaskId, │
│ + wowheadUrl: `https://www.wowhead.com/mop-classic/item=${consumables.flaskId │
│ }`, │
│ + isItem: true │
│ + }); │
│ + } │
│ + if (consumables.foodId) { │
│ + items.push({ │
│ + label: 'Food', │
│ + value: consumables.foodId, │
│ + wowheadUrl: `https://www.wowhead.com/mop-classic/item=${consumables.foodId} │
│ `, │
│ + isItem: true │
│ + }); │
│ + } │
│ + if (consumables.potId) { │
│ + items.push({ │
│ + label: 'Potion', │
│ + value: consumables.potId, │
│ + wowheadUrl: `https://www.wowhead.com/mop-classic/item=${consumables.potId}` │
│ , │
│ + isItem: true │
│ + }); │
│ + } │
│ + if (consumables.prepotId) { │
│ + items.push({ │
│ + label: 'Pre-Potion', │
│ + value: consumables.prepotId, │
│ + wowheadUrl: `https://www.wowhead.com/mop-classic/item=${consumables.prepotI │
│ d}`, │
│ + isItem: true │
│ + }); │
│ + } │
│ + return items; │
│ +}; │
│ + │
│ +const formatEquipmentSummary = (items) => { │
│ + const slotNames = [ │
│ + 'Head', 'Neck', 'Shoulders', 'Back', 'Chest', 'Wrists', │
│ + 'Hands', 'Waist', 'Legs', 'Feet', 'Ring 1', 'Ring 2', │
│ + 'Trinket 1', 'Trinket 2', 'Main Hand', 'Off Hand' │
│ + ]; │
│ + │
│ + const equipment = []; │
│ + items.forEach((item, index) => { │
│ + if (item && item.id) { │
│ + const itemInfo = window.ItemDatabase ? window.ItemDatabase.formatEquipmentI │
│ tem(item) : { │
│ + itemId: item.id, │
│ + itemName: `Item ${item.id}`, │
│ + itemDetails: [], │
│ + wowheadUrl: `https://www.wowhead.com/mop-classic/item=${item.id}` │
│ + }; │
│ + │
│ + if (itemInfo) { │
│ + equipment.push({ │
│ + slot: slotNames[index] || `Slot ${index + 1}`, │
│ + ...itemInfo │
│ + }); │
│ + } │
│ + } │
│ + }); │
│ + return equipment; │
│ +}; │
│ + │
│ +const generateRawJsonUrl = (chartData) => { │
│ + // Generate URL to the raw JSON file used for this simulation │
│ + const fileName = chartData.fileName || 'unknown.json'; │
│ + const dataPath = chartData.dataPath || '/data/'; │
│ + return `${dataPath}${fileName}`; │
│ +}; │
│ + │
│ +// Generate loadout dropdown HTML │
│ +const generateLoadoutDropdown = (loadout, chartData) => { │
│ + const sections = formatLoadout(loadout); │
│ + if (!sections || sections.length === 0) return ''; │
│ + │
│ + const sectionsHtml = sections.map(section => { │
│ + if (section.isEquipment) { │
│ + // Special handling for equipment │
│ + const equipmentHtml = section.items.map(eq => { │
│ + const detailsHtml = eq.itemDetails && eq.itemDetails.length > 0 │
│ + ? eq.itemDetails.map(detail => `<div class="equipment-detail">${detail} │
│ </div>`).join('') │
│ + : ''; │
│ + │
│ + return ` │
│ + <div class="equipment-slot"> │
│ + <span class="equipment-slot-name">${eq.slot}:</span> │
│ + <div class="equipment-item-info"> │
│ + <a href="${eq.wowheadUrl}" target="_blank" class="equipment-item-li │
│ nk">${eq.itemName}</a> │
│ + ${detailsHtml} │
│ + </div> │
│ + </div> │
│ + `; │
│ + }).join(''); │
│ + │
│ + return ` │
│ + <div class="loadout-section"> │
│ + <h4 class="loadout-title">${section.title}</h4> │
│ + <div class="equipment-grid"> │
│ + ${equipmentHtml} │
│ + </div> │
│ + </div> │
│ + `; │
│ + } else { │
│ + // Regular sections │
│ + const itemsHtml = section.items.map(item => { │
│ + const valueClass = item.isSpecial === 'talents' ? 'loadout-value loadout- │
│ talents' : 'loadout-value'; │
│ + const valueContent = item.isItem && item.wowheadUrl │
│ + ? `<a href="${item.wowheadUrl}" target="_blank" class="equipment-item-l │
│ ink">Item ${item.value}</a>` │
│ + : `<span class="${valueClass}">${item.value}</span>`; │
│ + │
│ + return ` │
│ + <div class="loadout-item"> │
│ + <span class="loadout-label">${item.label}</span> │
│ + ${valueContent} │
│ + </div> │
│ + `; │
│ + }).join(''); │
│ + │
│ + return ` │
│ + <div class="loadout-section"> │
│ + <h4 class="loadout-title">${section.title}</h4> │
│ + <div class="loadout-grid"> │
│ + ${itemsHtml} │
│ + </div> │
│ + </div> │
│ + `; │
│ + } │
│ + }).join(''); │
│ + │
│ + const rawJsonUrl = generateRawJsonUrl(chartData); │
│ + │
│ + return ` │
│ + <div class="chart-dropdown"> │
│ + ${sectionsHtml} │
│ + <div class="loadout-actions"> │
│ + <a href="${rawJsonUrl}" target="_blank" class="loadout-button">View Raw J │
│ SON</a> │
│ + <button class="loadout-button" onclick="alert('WowSims link coming soon!' │
│ )">Open in WowSims</button> │
│ + </div> │
│ + </div> │
│ + `; │
│ +}; │
│ + │
│ +// Chart interaction handlers - global function for onclick handlers │
│ +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); │
│ +}; │
│ + │
│ // Class specs for comparison mode │
│ const classSpecs = { │
│ death_knight: [ │
│ @@ -912,7 +1341,8 @@ class UnifiedChart { │
│ value: specData[sortBy] || specData.dps, │
│ category: className, │
│ className: className, │
│ - specName: specName │
│ + specName: specName, │
│ + loadout: specData.loadout || null │
│ }); │
│ } │
│ } │
│ @@ -941,19 +1371,24 @@ class UnifiedChart { │
│ const tooltip = sortBy === 'dps' ? displayValue : `${displayValue} (A │
│ vg: ${avgDps})`; │
│ │
│ const barColor = classColors[item.className] || '#666'; │
│ + const dropdownContent = item.loadout ? generateLoadoutDropdown(item.l │
│ oadout, this.currentData) : ''; │
│ │
│ return ` │
│ - <div class="chart-item"> │
│ - <div class="chart-labels"> │
│ - <span class="chart-rank">#${index + 1}</span> │
│ - <span class="chart-label">${item.label}</span> │
│ - </div> │
│ - <div class="chart-bar-container"> │
│ - <div class="chart-bar-track"> │
│ - <div class="chart-bar" style="width: ${barWidth}%; background-c │
│ olor: ${barColor};"></div> │
│ + <div class="chart-item-wrapper"> │
│ + <div class="chart-item" onclick="toggleChartItem(this.parentElement │
│ )"> │
│ + <div class="chart-labels"> │
│ + <span class="chart-rank">#${index + 1}</span> │
│ + <span class="chart-label">${item.label}</span> │
│ + ${item.loadout ? '<span class="chart-expand-indicator">?</span> │
│ ' : ''} │
│ + </div> │
│ + <div class="chart-bar-container"> │
│ + <div class="chart-bar-track"> │
│ + <div class="chart-bar" style="width: ${barWidth}%; background │
│ -color: ${barColor};"></div> │
│ + </div> │
│ + <span class="chart-value" title="${tooltip}">${displayValue}</s │
│ pan> │
│ </div> │
│ - <span class="chart-value" title="${tooltip}">${displayValue}</spa │
│ n> │
│ </div> │
│ + ${dropdownContent} │
│ </div> │
│ `; │
│ }).join('')} │
│ @@ -977,7 +1412,8 @@ class UnifiedChart { │
│ stdev: itemData.stdev, │
│ value: itemData[sortBy] || itemData.dps, │
│ category: itemName, │
│ - rawName: itemName │
│ + rawName: itemName, │
│ + loadout: itemData.loadout || null │
│ }); │
│ } │
│ │
│ @@ -1007,19 +1443,24 @@ class UnifiedChart { │
│ │
│ const barColor = currentComparisonType === 'race' ? │
│ (classColors[this.getCurrentClass()] || '#666') : '#666'; │
│ + const dropdownContent = item.loadout ? generateLoadoutDropdown(item.l │
│ oadout, this.currentData) : ''; │
│ │
│ return ` │
│ - <div class="chart-item"> │
│ - <div class="chart-labels"> │
│ - <span class="chart-rank">#${index + 1}</span> │
│ - <span class="chart-label">${item.label}</span> │
│ - </div> │
│ - <div class="chart-bar-container"> │
│ - <div class="chart-bar-track"> │
│ - <div class="chart-bar" style="width: ${barWidth}%; background-c │
│ olor: ${barColor};"></div> │
│ + <div class="chart-item-wrapper"> │
│ + <div class="chart-item" onclick="toggleChartItem(this.parentElement │
│ )"> │
│ + <div class="chart-labels"> │
│ + <span class="chart-rank">#${index + 1}</span> │
│ + <span class="chart-label">${item.label}</span> │
│ + ${item.loadout ? '<span class="chart-expand-indicator">?</span> │
│ ' : ''} │
│ + </div> │
│ + <div class="chart-bar-container"> │
│ + <div class="chart-bar-track"> │
│ + <div class="chart-bar" style="width: ${barWidth}%; background │
│ -color: ${barColor};"></div> │
│ + </div> │
│ + <span class="chart-value" title="${tooltip}">${displayValue}</s │
│ pan> │
│ </div> │
│ - <span class="chart-value" title="${tooltip}">${displayValue}</spa │
│ n> │
│ </div> │
│ + ${dropdownContent} │
│ </div> │
│ `; │
│ }).join('')} │
│ @@ -1035,5 +1476,8 @@ document.addEventListener('DOMContentLoaded', () => { │
│ new UnifiedChart(); │
│ }); │
│ </script> │
│ + │
│ + <!-- ItemDatabase component for reforge data and item utilities --> │
│ + <ItemDatabase /> │
│ </body> │
│ </html> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...src/layouts/ChartLayout.astro ───┐
│ diff --git a/web/src/layouts/ChartLayout.ast │
│ ro b/web/src/layouts/ChartLayout.astro │
│ index f3ffc68..4c148bf 100644 │
│ --- a/web/src/layouts/ChartLayout.astro │
│ +++ b/web/src/layouts/ChartLayout.astro │
│ @@ -1,6 +1,7 @@ │
│ --- │
│ import Navigation from '../components/Navig │
│ ation.astro'; │
│ import Warning from '../components/Warning. │
│ astro'; │
│ +import ItemDatabase from '../components/Ite │
│ mDatabase.astro'; │
│ │
│ export interface Props { │
│ title: string; │
│ @@ -231,6 +232,13 @@ const isFixedClassSpec │
│ = fixedClass && fixedSpec; │
│ align-items: center; │
│ gap: 15px; │
│ padding: 8px 0; │
│ + cursor: pointer; │
│ + border-radius: 4px; │
│ + transition: background-color 0.2s e │
│ ase; │
│ + } │
│ + │
│ + .chart-item:hover { │
│ + background-color: rgba(255,255,255, │
│ 0.05); │
│ } │
│ │
│ .chart-labels { │
│ @@ -293,6 +301,203 @@ const isFixedClassSpec │
│ = fixedClass && fixedSpec; │
│ text-align: right; │
│ flex-shrink: 0; │
│ } │
│ + │
│ + /* Expandable chart item styles */ │
│ + .chart-item-wrapper { │
│ + border-radius: 6px; │
│ + transition: background-color 0.2s e │
│ ase; │
│ + } │
│ + │
│ + .chart-item-header { │
│ + cursor: pointer; │
│ + padding: 8px 12px; │
│ + border-radius: 6px; │
│ + transition: background-color 0.2s e │
│ ase; │
│ + } │
│ + │
│ + .chart-item-header:hover { │
│ + background-color: rgba(255,255,255, │
│ 0.05); │
│ + } │
│ + │
│ + .chart-item-expanded .chart-item-head │
│ er { │
│ + background-color: rgba(255,255,255, │
│ 0.08); │
│ + } │
│ + │
│ + .chart-item-content { │
│ + display: grid; │
│ + grid-template-columns: 180px 1fr; │
│ + align-items: center; │
│ + gap: 15px; │
│ + } │
│ + │
│ + .chart-expand-icon { │
│ + margin-left: 8px; │
│ + font-size: 0.8em; │
│ + color: var(--text-muted); │
│ + transition: transform 0.2s ease; │
│ + } │
│ + │
│ + .chart-item-expanded .chart-expand-ic │
│ on { │
│ + transform: rotate(90deg); │
│ + } │
│ + │
│ + .chart-dropdown { │
│ + grid-column: 1 / -1; │
│ + margin-top: 12px; │
│ + padding: 16px; │
│ + background-color: rgba(255,255,255, │
│ 0.03); │
│ + 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 eas │
│ e-out; │
│ + } │
│ + │
│ + @keyframes dropdown-expand { │
│ + from { │
│ + opacity: 0; │
│ + transform: translateY(-8px); │
│ + } │
│ + to { │
│ + opacity: 1; │
│ + transform: translateY(0); │
│ + } │
│ + } │
│ + │
│ + .loadout-section { │
│ + margin-bottom: 20px; │
│ + } │
│ + │
│ + .loadout-section:last-child { │
│ + margin-bottom: 0; │
│ + } │
│ + │
│ + .loadout-title { │
│ + color: var(--highlight-color); │
│ + font-size: 1em; │
│ + font-weight: 600; │
│ + margin-bottom: 8px; │
│ + display: flex; │
│ + align-items: center; │
│ + gap: 8px; │
│ + } │
│ + │
│ + .loadout-grid { │
│ + display: grid; │
│ + grid-template-columns: repeat(auto- │
│ fit, minmax(200px, 1fr)); │
│ + gap: 12px; │
│ + } │
│ + │
│ + .loadout-item { │
│ + display: flex; │
│ + flex-direction: column; │
│ + gap: 3px; │
│ + } │
│ + │
│ + .loadout-label { │
│ + font-size: 0.8em; │
│ + color: var(--text-muted); │
│ + font-weight: 500; │
│ + text-transform: uppercase; │
│ + letter-spacing: 0.5px; │
│ + } │
│ + │
│ + .loadout-value { │
│ + font-size: 0.9em; │
│ + color: var(--text-primary); │
│ + font-weight: 500; │
│ + } │
│ + │
│ + .loadout-talents { │
│ + font-family: 'Courier New', monospa │
│ ce; │
│ + font-size: 1.1em; │
│ + letter-spacing: 2px; │
│ + color: var(--highlight-color); │
│ + } │
│ + │
│ + .loadout-actions { │
│ + display: flex; │
│ + gap: 12px; │
│ + margin-top: 16px; │
│ + padding-top: 16px; │
│ + border-top: 1px solid rgba(255,255, │
│ 255,0.1); │
│ + } │
│ + │
│ + .loadout-button { │
│ + padding: 6px 12px; │
│ + background-color: var(--bg-primary) │
│ ; │
│ + border: 1px solid rgba(255,255,255, │
│ 0.2); │
│ + border-radius: 4px; │
│ + color: var(--text-primary); │
│ + text-decoration: none; │
│ + font-size: 0.85em; │
│ + font-weight: 500; │
│ + transition: all 0.2s ease; │
│ + cursor: pointer; │
│ + } │
│ + │
│ + .loadout-button:hover { │
│ + background-color: rgba(255,255,255, │
│ 0.1); │
│ + border-color: var(--highlight-color │
│ ); │
│ + } │
│ + │
│ + .equipment-grid { │
│ + display: grid; │
│ + grid-template-columns: repeat(auto- │
│ fit, minmax(250px, 1fr)); │
│ + gap: 8px; │
│ + } │
│ + │
│ + .equipment-slot { │
│ + display: flex; │
│ + justify-content: space-between; │
│ + align-items: flex-start; │
│ + padding: 8px; │
│ + background-color: rgba(255,255,255, │
│ 0.05); │
│ + border-radius: 3px; │
│ + font-size: 0.85em; │
│ + gap: 12px; │
│ + } │
│ + │
│ + .equipment-slot-name { │
│ + color: var(--text-muted); │
│ + font-weight: 500; │
│ + min-width: 80px; │
│ + } │
│ + │
│ + .equipment-item-link { │
│ + color: #ffd100; │
│ + text-decoration: none; │
│ + font-family: 'Courier New', monospa │
│ ce; │
│ + font-size: 0.9em; │
│ + transition: color 0.2s ease; │
│ + } │
│ + │
│ + .equipment-item-link:hover { │
│ + color: #ffed4e; │
│ + text-decoration: underline; │
│ + } │
│ + │
│ + .equipment-item-info { │
│ + flex: 1; │
│ + display: flex; │
│ + flex-direction: column; │
│ + gap: 2px; │
│ + } │
│ + │
│ + .equipment-detail { │
│ + color: var(--text-muted); │
│ + font-size: 0.8em; │
│ + line-height: 1.2; │
│ + } │
│ + │
│ + .equipment-item { │
│ + color: var(--text-primary); │
│ + text-align: right; │
│ + font-family: 'Courier New', monospa │
│ ce; │
│ + } │
│ </style> │
│ </head> │
│ <body> │
│ @@ -456,6 +661,230 @@ const formatSimulation │
│ Date = (timestamp) => { │
│ }); │
│ }; │
│ │
│ +// Loadout formatting functions │
│ +const formatLoadout = (loadout) => { │
│ + if (!loadout) return null; │
│ + │
│ + const sections = []; │
│ + │
│ + // Character Info │
│ + if (loadout.race || loadout.class || load │
│ out.profession1 || loadout.profession2) { │
│ + const items = []; │
│ + if (loadout.race) items.push({ label: ' │
│ Race', value: formatRace(loadout.race) }); │
│ + if (loadout.class) items.push({ label: │
│ 'Class', value: formatClass(loadout.class) } │
│ ); │
│ + if (loadout.profession1) items.push({ l │
│ abel: 'Profession 1', value: loadout.profess │
│ ion1 }); │
│ + if (loadout.profession2) items.push({ l │
│ abel: 'Profession 2', value: loadout.profess │
│ ion2 }); │
│ + sections.push({ title: 'Character', ite │
│ ms }); │
│ + } │
│ + │
│ + // Talents & Glyphs │
│ + if (loadout.talentsString || loadout.glyp │
│ hs) { │
│ + const items = []; │
│ + if (loadout.talentsString) { │
│ + items.push({ label: 'Talents', value: │
│ loadout.talentsString, isSpecial: 'talents' │
│ }); │
│ + } │
│ + if (loadout.glyphs) { │
│ + const glyphText = formatGlyphs(loadou │
│ t.glyphs); │
│ + if (glyphText) items.push({ label: 'G │
│ lyphs', value: glyphText }); │
│ + } │
│ + sections.push({ title: 'Talents & Glyph │
│ s', items }); │
│ + } │
│ + │
│ + // Consumables │
│ + if (loadout.consumables) { │
│ + const items = formatConsumables(loadout │
│ .consumables); │
│ + if (items.length > 0) sections.push({ t │
│ itle: 'Consumables', items }); │
│ + } │
│ + │
│ + // Equipment Summary │
│ + if (loadout.equipment && loadout.equipmen │
│ t.items) { │
│ + const equipmentSummary = formatEquipmen │
│ tSummary(loadout.equipment.items); │
│ + if (equipmentSummary.length > 0) { │
│ + sections.push({ title: 'Equipment', i │
│ tems: equipmentSummary, isEquipment: true }) │
│ ; │
│ + } │
│ + } │
│ + │
│ + return sections; │
│ +}; │
│ + │
│ +const formatRace = (race) => { │
│ + return race.replace('Race', '').replace(/ │
│ ([A-Z])/g, ' $1').trim(); │
│ +}; │
│ + │
│ +const formatClass = (className) => { │
│ + return className.replace('Class', '').rep │
│ lace(/([A-Z])/g, ' $1').trim(); │
│ +}; │
│ + │
│ +const formatGlyphs = (glyphs) => { │
│ + const glyphList = []; │
│ + ['major1', 'major2', 'major3', 'minor1', │
│ 'minor2', 'minor3'].forEach(slot => { │
│ + if (glyphs[slot]) glyphList.push(`${slo │
│ t}: ${glyphs[slot]}`); │
│ + }); │
│ + return glyphList.length > 0 ? glyphList.j │
│ oin(', ') : null; │
│ +}; │
│ + │
│ +const formatConsumables = (consumables) => │
│ { │
│ + const items = []; │
│ + if (consumables.flaskId) { │
│ + items.push({ │
│ + label: 'Flask', │
│ + value: consumables.flaskId, │
│ + wowheadUrl: `https://www.wowhead.com/ │
│ mop-classic/item=${consumables.flaskId}`, │
│ + isItem: true │
│ + }); │
│ + } │
│ + if (consumables.foodId) { │
│ + items.push({ │
│ + label: 'Food', │
│ + value: consumables.foodId, │
│ + wowheadUrl: `https://www.wowhead.com/ │
│ mop-classic/item=${consumables.foodId}`, │
│ + isItem: true │
│ + }); │
│ + } │
│ + if (consumables.potId) { │
│ + items.push({ │
│ + label: 'Potion', │
│ + value: consumables.potId, │
│ + wowheadUrl: `https://www.wowhead.com/ │
│ mop-classic/item=${consumables.potId}`, │
│ + isItem: true │
│ + }); │
│ + } │
│ + if (consumables.prepotId) { │
│ + items.push({ │
│ + label: 'Pre-Potion', │
│ + value: consumables.prepotId, │
│ + wowheadUrl: `https://www.wowhead.com/ │
│ mop-classic/item=${consumables.prepotId}`, │
│ + isItem: true │
│ + }); │
│ + } │
│ + return items; │
│ +}; │
│ + │
│ +const formatEquipmentSummary = (items) => { │
│ + const slotNames = [ │
│ + 'Head', 'Neck', 'Shoulders', 'Back', 'C │
│ hest', 'Wrists', │
│ + 'Hands', 'Waist', 'Legs', 'Feet', 'Ring │
│ 1', 'Ring 2', │
│ + 'Trinket 1', 'Trinket 2', 'Main Hand', │
│ 'Off Hand' │
│ + ]; │
│ + │
│ + const equipment = []; │
│ + items.forEach((item, index) => { │
│ + if (item && item.id) { │
│ + const itemInfo = window.ItemDatabase │
│ ? window.ItemDatabase.formatEquipmentItem(it │
│ em) : { │
│ + itemId: item.id, │
│ + itemName: `Item ${item.id}`, │
│ + itemDetails: [], │
│ + wowheadUrl: `https://www.wowhead.co │
│ m/mop-classic/item=${item.id}` │
│ + }; │
│ + │
│ + if (itemInfo) { │
│ + equipment.push({ │
│ + slot: slotNames[index] || `Slot $ │
│ {index + 1}`, │
│ + ...itemInfo │
│ + }); │
│ + } │
│ + } │
│ + }); │
│ + return equipment; │
│ +}; │
│ + │
│ +const generateRawJsonUrl = (chartData) => { │
│ + // Generate URL to the raw JSON file used │
│ for this simulation │
│ + const fileName = chartData.fileName || 'u │
│ nknown.json'; │
│ + const dataPath = chartData.dataPath || '/ │
│ data/'; │
│ + return `${dataPath}${fileName}`; │
│ +}; │
│ + │
│ +// Generate loadout dropdown HTML │
│ +const generateLoadoutDropdown = (loadout, c │
│ hartData) => { │
│ + const sections = formatLoadout(loadout); │
│ + if (!sections || sections.length === 0) r │
│ eturn ''; │
│ + │
│ + const sectionsHtml = sections.map(section │
│ => { │
│ + if (section.isEquipment) { │
│ + // Special handling for equipment │
│ + const equipmentHtml = section.items.m │
│ ap(eq => { │
│ + const detailsHtml = eq.itemDetails │
│ && eq.itemDetails.length > 0 │
│ + ? eq.itemDetails.map(detail => `< │
│ div class="equipment-detail">${detail}</div> │
│ `).join('') │
│ + : ''; │
│ + │
│ + return ` │
│ + <div class="equipment-slot"> │
│ + <span class="equipment-slot-nam │
│ e">${eq.slot}:</span> │
│ + <div class="equipment-item-info │
│ "> │
│ + <a href="${eq.wowheadUrl}" ta │
│ rget="_blank" class="equipment-item-link">${ │
│ eq.itemName}</a> │
│ + ${detailsHtml} │
│ + </div> │
│ + </div> │
│ + `; │
│ + }).join(''); │
│ + │
│ + return ` │
│ + <div class="loadout-section"> │
│ + <h4 class="loadout-title">${secti │
│ on.title}</h4> │
│ + <div class="equipment-grid"> │
│ + ${equipmentHtml} │
│ + </div> │
│ + </div> │
│ + `; │
│ + } else { │
│ + // Regular sections │
│ + const itemsHtml = section.items.map(i │
│ tem => { │
│ + const valueClass = item.isSpecial = │
│ == 'talents' ? 'loadout-value loadout-talent │
│ s' : 'loadout-value'; │
│ + const valueContent = item.isItem && │
│ item.wowheadUrl │
│ + ? `<a href="${item.wowheadUrl}" t │
│ arget="_blank" class="equipment-item-link">I │
│ tem ${item.value}</a>` │
│ + : `<span class="${valueClass}">${ │
│ item.value}</span>`; │
│ + │
│ + return ` │
│ + <div class="loadout-item"> │
│ + <span class="loadout-label">${i │
│ tem.label}</span> │
│ + ${valueContent} │
│ + </div> │
│ + `; │
│ + }).join(''); │
│ + │
│ + return ` │
│ + <div class="loadout-section"> │
│ + <h4 class="loadout-title">${secti │
│ on.title}</h4> │
│ + <div class="loadout-grid"> │
│ + ${itemsHtml} │
│ + </div> │
│ + </div> │
│ + `; │
│ + } │
│ + }).join(''); │
│ + │
│ + const rawJsonUrl = generateRawJsonUrl(cha │
│ rtData); │
│ + │
│ + return ` │
│ + <div class="chart-dropdown"> │
│ + ${sectionsHtml} │
│ + <div class="loadout-actions"> │
│ + <a href="${rawJsonUrl}" target="_bl │
│ ank" class="loadout-button">View Raw JSON</a │
│ > │
│ + <button class="loadout-button" oncl │
│ ick="alert('WowSims link coming soon!')">Ope │
│ n in WowSims</button> │
│ + </div> │
│ + </div> │
│ + `; │
│ +}; │
│ + │
│ +// Chart interaction handlers - global func │
│ tion for onclick handlers │
│ +window.toggleChartItem = (element) => { │
│ + const wrapper = element.closest('.chart-i │
│ tem-wrapper'); │
│ + if (!wrapper) return; │
│ + │
│ + const isExpanded = wrapper.classList.cont │
│ ains('chart-item-expanded'); │
│ + │
│ + // Close all other expanded items │
│ + document.querySelectorAll('.chart-item-ex │
│ panded').forEach(item => { │
│ + if (item !== wrapper) { │
│ + item.classList.remove('chart-item-exp │
│ anded'); │
│ + } │
│ + }); │
│ + │
│ + // Toggle current item │
│ + wrapper.classList.toggle('chart-item-expa │
│ nded', !isExpanded); │
│ +}; │
│ + │
│ // Class specs for comparison mode │
│ const classSpecs = { │
│ death_knight: [ │
│ @@ -912,7 +1341,8 @@ class UnifiedChart { │
│ value: specData[sortBy] || specDa │
│ ta.dps, │
│ category: className, │
│ className: className, │
│ - specName: specName │
│ + specName: specName, │
│ + loadout: specData.loadout || null │
│ }); │
│ } │
│ } │
│ @@ -941,19 +1371,24 @@ class UnifiedChart { │
│ const tooltip = sortBy === 'dps │
│ ' ? displayValue : `${displayValue} (Avg: ${ │
│ avgDps})`; │
│ │
│ const barColor = classColors[it │
│ em.className] || '#666'; │
│ + const dropdownContent = item.lo │
│ adout ? generateLoadoutDropdown(item.loadout │
│ , this.currentData) : ''; │
│ │
│ return ` │
│ - <div class="chart-item"> │
│ - <div class="chart-labels"> │
│ - <span class="chart-rank">#$ │
│ {index + 1}</span> │
│ - <span class="chart-label">$ │
│ {item.label}</span> │
│ - </div> │
│ - <div class="chart-bar-contain │
│ er"> │
│ - <div class="chart-bar-track │
│ "> │
│ - <div class="chart-bar" st │
│ yle="width: ${barWidth}%; background-color: │
│ ${barColor};"></div> │
│ + <div class="chart-item-wrapper" │
│ > │
│ + <div class="chart-item" oncli │
│ ck="toggleChartItem(this.parentElement)"> │
│ + <div class="chart-labels"> │
│ + <span class="chart-rank"> │
│ #${index + 1}</span> │
│ + <span class="chart-label" │
│ >${item.label}</span> │
│ + ${item.loadout ? '<span c │
│ lass="chart-expand-indicator">?</span>' : '' │
│ } │
│ + </div> │
│ + <div class="chart-bar-conta │
│ iner"> │
│ + <div class="chart-bar-tra │
│ ck"> │
│ + <div class="chart-bar" │
│ style="width: ${barWidth}%; background-color │
│ : ${barColor};"></div> │
│ + </div> │
│ + <span class="chart-value" │
│ title="${tooltip}">${displayValue}</span> │
│ </div> │
│ - <span class="chart-value" t │
│ itle="${tooltip}">${displayValue}</span> │
│ </div> │
│ + ${dropdownContent} │
│ </div> │
│ `; │
│ }).join('')} │
│ @@ -977,7 +1412,8 @@ class UnifiedChart { │
│ stdev: itemData.stdev, │
│ value: itemData[sortBy] || itemData │
│ .dps, │
│ category: itemName, │
│ - rawName: itemName │
│ + rawName: itemName, │
│ + loadout: itemData.loadout || null │
│ }); │
│ } │
│ │
│ @@ -1007,19 +1443,24 @@ class UnifiedChart { │
│ │
│ const barColor = currentCompari │
│ sonType === 'race' ? │
│ (classColors[this.getCurrentC │
│ lass()] || '#666') : '#666'; │
│ + const dropdownContent = item.lo │
│ adout ? generateLoadoutDropdown(item.loadout │
│ , this.currentData) : ''; │
│ │
│ return ` │
│ - <div class="chart-item"> │
│ - <div class="chart-labels"> │
│ - <span class="chart-rank">#$ │
│ {index + 1}</span> │
│ - <span class="chart-label">$ │
│ {item.label}</span> │
│ - </div> │
│ - <div class="chart-bar-contain │
│ er"> │
│ - <div class="chart-bar-track │
│ "> │
│ - <div class="chart-bar" st │
│ yle="width: ${barWidth}%; background-color: │
│ ${barColor};"></div> │
│ + <div class="chart-item-wrapper" │
│ > │
│ + <div class="chart-item" oncli │
│ ck="toggleChartItem(this.parentElement)"> │
│ + <div class="chart-labels"> │
│ + <span class="chart-rank"> │
│ #${index + 1}</span> │
│ + <span class="chart-label" │
│ >${item.label}</span> │
│ + ${item.loadout ? '<span c │
│ lass="chart-expand-indicator">?</span>' : '' │
│ } │
│ + </div> │
│ + <div class="chart-bar-conta │
│ iner"> │
│ + <div class="chart-bar-tra │
│ ck"> │
│ + <div class="chart-bar" │
│ style="width: ${barWidth}%; background-color │
│ : ${barColor};"></div> │
│ + </div> │
│ + <span class="chart-value" │
│ title="${tooltip}">${displayValue}</span> │
│ </div> │
│ - <span class="chart-value" t │
│ itle="${tooltip}">${displayValue}</span> │
│ </div> │
│ + ${dropdownContent} │
│ </div> │
│ `; │
│ }).join('')} │
│ @@ -1035,5 +1476,8 @@ document.addEventListe │
│ ner('DOMContentLoaded', () => { │
│ new UnifiedChart(); │
│ }); │
│ </script> │
│ + │
│ + <!-- ItemDatabase component for reforge │
│ data and item utilities --> │
│ + <ItemDatabase /> │
│ </body> │
│ </html> │
└──────────────────────────────────────────────┘