(function () { 'use strict'; var METAS_URL = 'https://raw.githubusercontent.com/FNTDUG/characters.json/refs/heads/main/metas.json'; var UNITS_URL = 'https://raw.githubusercontent.com/FNTDUG/characters.json/refs/heads/main/json'; if (!document.getElementById('metaGFonts')) { var lnk = document.createElement('link'); lnk.id = 'metaGFonts'; lnk.rel = 'stylesheet'; lnk.href = 'https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'; (document.head || document.documentElement).appendChild(lnk); } if (!document.querySelector('meta[name="viewport"]')) { var vpm = document.createElement('meta'); vpm.name = 'viewport'; vpm.content = 'width=device-width, initial-scale=1'; (document.head || document.documentElement).appendChild(vpm); } if (!document.getElementById('metaCSS')) { var CSS = [ '@keyframes metaShiny{0%{background-position:0% 50%}100%{background-position:200% 50%}}', '.metaRoot{width:100vw;position:relative;left:50%;transform:translateX(-50%);' + 'background-size:cover;background-position:center;background-repeat:no-repeat;' + 'color:#fff;font-family:"Press Start 2P",monospace;box-sizing:border-box;' + 'padding:clamp(14px,2.5vw,28px) clamp(8px,1.5vw,16px) clamp(18px,3vw,36px);' + 'border:1px solid rgba(246,155,85,1);margin-bottom:15px}', '.metaRoot *{box-sizing:border-box}', '.metaTitle{font-family:"Press Start 2P",monospace;font-size:clamp(14px,3vw,36px);' + 'color:#fff;text-align:center;margin:0 0 clamp(8px,1.5vw,16px);padding:4px 8px;' + 'line-height:1.35;word-break:break-word;' + 'text-shadow:0 2px 10px rgba(0,0,0,0.95),0 0 2px rgba(0,0,0,1)}', '.metaControls{display:flex;justify-content:flex-end;margin-bottom:6px}', '.metaRow{--mg:clamp(4px,1.2vw,10px);display:flex;justify-content:center;' + 'flex-wrap:nowrap;gap:var(--mg);padding:4px 0}', '.metaFooter{display:flex;flex-direction:column;align-items:center;' + 'gap:clamp(6px,1vw,10px);margin-top:clamp(10px,2vw,26px)}', '.metaLogo{width:clamp(40px,5vw,64px);height:clamp(40px,5vw,64px);' + 'border-radius:50%;object-fit:cover;display:block}', '.metaSiteUrl{font-family:"Press Start 2P",monospace;font-size:clamp(8px,1.1vw,13px);' + 'color:#fff;text-align:center;text-shadow:0 1px 6px rgba(0,0,0,0.95)}', /* seamless — ~30% smaller than the original values */ '.seamlessRoot{width:100vw;position:relative;left:50%;transform:translateX(-50%);' + 'background-size:cover;background-position:center;background-repeat:no-repeat;' + 'color:#fff;font-family:"Press Start 2P",monospace;box-sizing:border-box;' + 'padding:clamp(8px,1.4vw,17px) 0 clamp(10px,1.75vw,20px);' + 'border:1px solid rgba(246,155,85,1);margin-bottom:5px;overflow:hidden}', '.seamlessRoot *{box-sizing:border-box}', '.seamlessHeader{display:flex;align-items:center;justify-content:center;' + 'gap:clamp(6px,1vw,14px);' + 'padding:0 clamp(6px,1.05vw,11px);margin-bottom:3px;' + 'min-height:clamp(32px,4.9vw,56px)}', '.seamlessLogo{flex-shrink:0;' + 'width:clamp(38px,5.8vw,70px);height:clamp(38px,5.8vw,70px);' + 'border-radius:50%;object-fit:cover}', '.seamlessSiteName{font-family:"Press Start 2P",monospace;' + 'font-size:clamp(16px,3.5vw,48px);color:#fff;line-height:1.2;text-align:center;' + 'text-shadow:0 2px 10px rgba(0,0,0,0.95)}', '.seamlessSiteUrl{font-family:"Press Start 2P",monospace;' + 'font-size:clamp(7px,1.1vw,13px);color:#fff;text-align:center;' + 'padding:0 clamp(6px,1.05vw,11px);' + 'margin-bottom:clamp(4px,0.7vw,8px);text-shadow:0 1px 4px rgba(0,0,0,0.9)}', '.seamlessPageTitle{font-family:"Press Start 2P",monospace;' + 'font-size:clamp(11px,2.24vw,28px);color:#00e5ff;text-align:center;' + 'background:rgba(0,180,230,0.12);' + 'padding:clamp(4px,0.7vw,8px) clamp(6px,1.05vw,11px);' + 'margin:0 0 clamp(2px,0.4vw,4px);' + 'text-shadow:0 0 18px rgba(0,220,255,0.55),0 2px 8px rgba(0,0,0,0.8)}', '.seamlessSection{padding:0 clamp(6px,1.05vw,11px);margin-top:clamp(2px,0.3vw,4px)}', '.seamlessSectionTitle{font-family:"Press Start 2P",monospace;' + 'font-size:clamp(10px,1.82vw,24px);color:#fff;text-align:center;' + 'margin:0 0 clamp(3px,0.56vw,7px);' + 'text-shadow:0 2px 10px rgba(0,0,0,0.95),0 0 2px rgba(0,0,0,1)}', '.seamlessControls{display:flex;justify-content:center;' + 'padding:clamp(3px,0.56vw,6px) clamp(6px,1.05vw,11px) clamp(4px,0.7vw,7px)}', '.seamlessControls .meta-clutter-btn{font-size:clamp(5px,0.7vw,8px);' + 'padding:clamp(3px,0.42vw,6px) clamp(6px,0.84vw,11px)}', /* page switcher tabs — only rendered when pages.length > 1 */ '.seamlessPageTabBar{display:flex;flex-wrap:wrap;justify-content:center;gap:5px;' + 'padding:clamp(3px,0.5vw,6px) clamp(6px,1.05vw,11px) clamp(5px,0.8vw,9px)}', '.seamlessPageTab{font-family:"Press Start 2P",monospace;font-size:clamp(5px,0.8vw,9px);' + 'padding:clamp(3px,0.5vw,6px) clamp(7px,1vw,12px);' + 'background:rgba(0,0,0,0.5);border:1px solid rgba(246,155,85,0.35);' + 'color:rgba(255,255,255,0.4);border-radius:4px;cursor:pointer;' + 'transition:all .15s;user-select:none}', '.seamlessPageTab:hover{background:rgba(246,155,85,0.1);border-color:rgba(246,155,85,0.7);color:rgba(255,255,255,0.8)}', '.seamlessPageTab.seamlessPageTabActive{background:rgba(246,155,85,0.18);border-color:rgba(246,155,85,1);color:#ffa45b}', '.meta-card{width:100%;aspect-ratio:1/1;' + 'border-radius:12px;padding:3px;position:relative;cursor:default}', '.meta-card.meta-shiny{background-size:200% 200%;animation:metaShiny 6s linear infinite}', '.meta-card-inner{position:relative;width:100%;height:100%;' + 'border-radius:9px;overflow:hidden;background:rgba(14,11,28,0.88)}', '.meta-card-img{width:100%;height:100%;object-fit:cover;object-position:center top;' + 'display:block;pointer-events:none}', '.meta-byte{position:absolute;top:2px;right:2px;z-index:2;aspect-ratio:1/1;' + 'width:clamp(16px,28%,54px);border-radius:9px;padding:2px;pointer-events:none}', '.meta-byte.meta-shiny{background-size:200% 200%;animation:metaShiny 6s linear infinite}', '.meta-byte-inner{width:100%;height:100%;border-radius:7px;overflow:hidden;background:rgba(14,11,28,0.88)}', '.meta-byte-inner img{width:100%;height:100%;object-fit:cover;display:block}', '.meta-chip{position:absolute;bottom:2px;left:2px;z-index:2;aspect-ratio:1/1;' + 'width:clamp(16px,28%,54px);border-radius:9px;padding:2px;pointer-events:none}', '.meta-chip.meta-shiny{background-size:200% 200%;animation:metaShiny 6s linear infinite}', '.meta-chip-inner{width:100%;height:100%;border-radius:7px;overflow:hidden;background:rgba(14,11,28,0.88)}', '.meta-chip-inner img{width:100%;height:100%;object-fit:cover;display:block}', '.meta-chip.chip-mid-right{bottom:auto;left:auto;right:2px;top:50%;transform:translateY(-50%)}', '.meta-chip.chip-bottom-right{left:auto;right:2px}', '.meta-enchant{position:absolute;top:2px;left:2px;z-index:2;aspect-ratio:1/1;' + 'width:clamp(14px,24%,48px);border-radius:8px;padding:2px;pointer-events:none}', '.meta-enchant-inner{width:100%;height:100%;border-radius:6px;overflow:hidden;background:rgba(14,11,28,0.88)}', '.meta-enchant-inner img{width:100%;height:100%;object-fit:cover;display:block}', '.meta-replacement{position:absolute;bottom:2px;right:2px;z-index:2;aspect-ratio:1/1;' + 'width:clamp(16px,28%,54px);border-radius:9px;padding:2px;pointer-events:none}', '.meta-replacement.meta-shiny{background-size:200% 200%;animation:metaShiny 6s linear infinite}', '.meta-replacement-inner{width:100%;height:100%;border-radius:7px;overflow:hidden;background:rgba(14,11,28,0.88)}', '.meta-replacement-inner img{width:100%;height:100%;object-fit:cover;object-position:center top;display:block}', '.meta-label{position:absolute;bottom:2px;left:2px;padding:2px;' + 'border-radius:5px;pointer-events:none;z-index:2}', '.meta-label-inner{background:rgba(0,0,0,0.88);color:#39ff14;' + 'font-family:"Press Start 2P",monospace;' + 'font-size:clamp(5px,0.9vw,9px);padding:3px 6px;border-radius:3px;display:block}', '.meta-slot{flex:0 1 calc((100% - 5 * var(--mg)) / 6);min-width:0;' + 'display:flex;flex-direction:column;align-items:center;gap:clamp(3px,0.6vw,7px)}', '.meta-caption{font-family:"Press Start 2P",monospace;font-size:clamp(5px,0.85vw,10px);' + 'color:#fff;text-align:center;width:100%;line-height:1.5;' + 'word-break:break-word;text-shadow:0 1px 4px rgba(0,0,0,0.9);user-select:none}', '.metaControls{display:flex;justify-content:flex-end;margin-bottom:6px}', '.meta-clutter-btn{font-family:"Press Start 2P",monospace;font-size:7px;' + 'padding:4px 8px;background:rgba(0,0,0,0.65);' + 'border:1px solid rgba(246,155,85,0.75);color:#ffa45b;' + 'border-radius:4px;cursor:pointer;user-select:none;transition:opacity .15s,border-color .15s,color .15s}', '.meta-clutter-btn:hover{background:rgba(246,155,85,0.12)}', '.meta-clutter-btn.cltr-off{border-color:rgba(255,255,255,0.2);color:rgba(255,255,255,0.3)}', /* camera button — shares the clutter button's frame so the controls row stays even */ '.meta-cam-btn{display:inline-flex;align-items:center;justify-content:center;' + 'padding:4px 8px;background:rgba(0,0,0,0.65);' + 'border:1px solid rgba(246,155,85,0.75);color:#ffa45b;' + 'border-radius:4px;cursor:pointer;user-select:none;transition:opacity .15s,border-color .15s,color .15s}', '.meta-cam-btn:hover{background:rgba(246,155,85,0.12)}', '.meta-cam-btn[disabled]{opacity:.5;cursor:default}', '.meta-cam-btn svg{display:block;width:11px;height:11px}', '.metaControls{gap:6px}', '.seamlessControls .meta-cam-btn{padding:clamp(3px,0.42vw,6px) clamp(6px,0.84vw,11px)}', '.seamlessControls .meta-cam-btn svg{width:clamp(8px,1.1vw,12px);height:clamp(8px,1.1vw,12px)}', '.seamlessControls{gap:clamp(4px,0.7vw,8px)}', '.meta-shot-overlay{position:fixed;inset:0;z-index:100000;background:rgba(0,0,0,0.86);' + 'display:flex;flex-direction:column;align-items:center;justify-content:center;' + 'gap:16px;padding:20px;box-sizing:border-box}', '.meta-shot-overlay img{max-width:92vw;max-height:66vh;border-radius:10px;' + 'box-shadow:0 10px 34px rgba(0,0,0,0.75)}', '.meta-shot-actions{display:flex;gap:12px;flex-wrap:wrap;justify-content:center}', '.meta-shot-btn{font-family:"Press Start 2P",monospace;font-size:11px;padding:12px 18px;' + 'border-radius:10px;cursor:pointer;border:2px solid;transition:background .12s,color .12s,border-color .12s}', '.meta-shot-save{background:#2a1f3a;border-color:#ffc84d;color:#ffc84d}', '.meta-shot-save:hover{background:#3a2a4a;color:#fff}', '.meta-shot-close{background:rgba(30,30,40,0.9);border-color:rgba(255,255,255,0.25);color:#ccc}', '.meta-shot-close:hover{color:#fff;border-color:rgba(255,255,255,0.5)}', '@media(max-width:600px){.meta-shot-btn{font-size:9px;padding:10px 14px}}', '.meta-clutter-off .meta-byte,' + '.meta-clutter-off .meta-chip,' + '.meta-clutter-off .meta-enchant,' + '.meta-clutter-off .meta-replacement,' + '.meta-clutter-off .meta-label{display:none!important}', '#metaTip{position:fixed;pointer-events:none;opacity:0;transition:opacity .18s;' + 'z-index:10000;display:none;padding:2px;border-radius:8px}', '#metaTipInner{font-family:"Press Start 2P",monospace;font-size:10px;line-height:1.8;' + 'background:rgba(6,3,18,0.97);color:#fff;padding:14px 18px;border-radius:8px;' + 'text-align:left;max-width:300px;word-break:normal;overflow-wrap:break-word}', '.mt-name{font-size:13px;color:#fff;text-align:center;display:block;margin-bottom:6px}', '.mt-sep{border:none;border-top:1px solid rgba(255,255,255,0.18);margin:7px 0}', '.mt-row{display:flex;align-items:center;gap:9px;margin-top:7px}', '.mt-icon{width:36px;height:36px;border-radius:6px;padding:2px;flex-shrink:0}', '.mt-icon.meta-shiny{background-size:200% 200%;animation:metaShiny 6s linear infinite}', '.mt-icon-in{width:100%;height:100%;border-radius:4px;overflow:hidden;background:rgba(14,11,28,0.88)}', '.mt-icon-in img{width:100%;height:100%;object-fit:cover;display:block}', '.mt-text{font-size:9.5px;opacity:0.9;line-height:1.5;white-space:pre-line}', '@media(min-width:901px){' + '.seamlessRoot{padding:clamp(5px,0.9vw,11px) 0 clamp(6px,1.12vw,13px)}' + '.seamlessHeader{gap:clamp(4px,0.64vw,9px);padding:0 clamp(4px,0.67vw,7px);min-height:clamp(21px,3.12vw,36px);margin-bottom:1px}' + '.seamlessLogo{width:clamp(24px,3.68vw,45px);height:clamp(24px,3.68vw,45px)}' + '.seamlessSiteName{font-size:clamp(10px,2.24vw,30px)}' + '.seamlessSiteUrl{font-size:clamp(5px,0.7vw,8px);padding:0 clamp(4px,0.67vw,7px);margin-bottom:clamp(2px,0.45vw,5px)}' + '.seamlessPageTitle{font-size:clamp(7px,1.43vw,18px);padding:clamp(2px,0.45vw,5px) clamp(4px,0.67vw,7px);margin:0 0 2px}' + '.seamlessSection{padding:0 clamp(8px,1.5vw,16px);margin-top:2px}' + '.seamlessSectionTitle{font-size:clamp(6px,1.17vw,15px);margin:0 0 clamp(2px,0.36vw,5px)}' + '.seamlessControls{padding:clamp(2px,0.36vw,4px) clamp(4px,0.67vw,7px) clamp(2px,0.45vw,5px)}' + '.seamlessControls .meta-clutter-btn{font-size:clamp(5px,0.7vw,8px);padding:clamp(3px,0.42vw,6px) clamp(6px,0.84vw,11px)}' + '.seamlessPageTabBar{gap:5px;padding:clamp(3px,0.5vw,6px) clamp(6px,1.05vw,11px) clamp(5px,0.8vw,9px)}' + '.seamlessPageTab{font-size:clamp(5px,0.8vw,9px);padding:clamp(3px,0.5vw,6px) clamp(7px,1vw,12px)}' + '}', '@media(max-width:900px){' + '.metaRoot{padding-left:4px;padding-right:4px}' + '.metaRow{--mg:3px}' + '.seamlessSection .metaRow{--mg:3px}' + '.seamlessControls .meta-clutter-btn{font-size:7px;padding:4px 8px}' + '.seamlessHeader{min-height:clamp(30px,5vw,46px);margin-bottom:2px}' + '.seamlessLogo{width:clamp(30px,5vw,46px);height:clamp(30px,5vw,46px)}' + '.seamlessSiteName{font-size:clamp(20px,4.5vw,42px)}' + '.seamlessSiteUrl{margin-bottom:3px}' + '.meta-card{padding:2px;border-radius:8px}' + '.meta-card-inner{border-radius:6px}' + '.meta-byte{top:2px;right:2px;padding:1px;border-radius:6px}' + '.meta-byte-inner{border-radius:5px}' + '.meta-chip{bottom:2px;left:2px;padding:1px;border-radius:6px}' + '.meta-chip-inner{border-radius:5px}' + '.meta-enchant{top:2px;left:2px;padding:1px;border-radius:6px}' + '.meta-enchant-inner{border-radius:5px}' + '.meta-replacement{bottom:2px;right:2px;padding:1px;border-radius:6px}' + '.meta-replacement-inner{border-radius:5px}' + '.meta-label{bottom:2px;left:2px;padding:1px}' + '.meta-label-inner{border-radius:2px}' + '}', '@media(max-width:600px){' + '#metaTipInner{font-size:7px;padding:7px 9px;max-width:190px}' + '.mt-name{font-size:8px;margin-bottom:4px}' + '.mt-sep{margin:4px 0}' + '.mt-row{gap:5px;margin-top:4px}' + '.mt-icon{width:20px;height:20px}' + '.mt-text{font-size:6px}' + '.meta-label-inner{font-size:5px;padding:2px 4px}' + '}' ].join(''); var styleEl = document.createElement('style'); styleEl.id = 'metaCSS'; styleEl.textContent = CSS; (document.head || document.documentElement).appendChild(styleEl); } var tip, tipInner; if (!document.getElementById('metaTip')) { tip = document.createElement('div'); tip.id = 'metaTip'; tipInner = document.createElement('div'); tipInner.id = 'metaTipInner'; tip.appendChild(tipInner); document.body.appendChild(tip); } else { tip = document.getElementById('metaTip'); tipInner = document.getElementById('metaTipInner'); } var RARITY = { uncommon: 'linear-gradient(90deg,#5CFF4D,#3FFF8F)', rare: 'linear-gradient(90deg,#58A6FF,#1C3AA0)', epic: 'linear-gradient(90deg,#FF35FF,#87009F)', mythic: 'linear-gradient(90deg,#FFB81F,#FFFF00)', exclusive: 'linear-gradient(90deg,rgb(140,255,203) 0%,rgb(20,115,91) 25.1%,rgb(51,231,255) 50%,rgb(20,68,112) 68%,rgb(21,74,118) 74.6%,rgb(79,164,255) 100%)', secret: 'linear-gradient(90deg,#FF8800,#FF0C0C)', nightmare: 'linear-gradient(90deg,#492590,#2A1E42)', apex: 'linear-gradient(90deg,#9D0078,#0063F8)', hero: 'linear-gradient(90deg,#FFCD19,#353815,#FFFB85)', radiant: 'linear-gradient(90deg,#FF6600,#FFCC33)' }; var SHINY = { 'Astral Bonnie': 'linear-gradient(90deg,#ffeb3b,#ff9800,#f44336,#9c27b0,#2196f3,#4caf50,#ffeb3b)', 'Frozen King Foxy': 'linear-gradient(90deg,#ff85a2,#ffc107,#e91e63,#9c27b0,#ff85a2)', 'Dragon Endo 01': 'linear-gradient(90deg,#4a00e0,#6e25ff,#1e0066,#6e25ff,#4a00e0)', 'Kronos Endo Freddy': 'linear-gradient(90deg,#00ffff,#17a2b8,#000,#17a2b8,#00ffff)', 'Headless Horseman Mangle':'linear-gradient(90deg,#50f2d0,#42a2f8,#1c734d,#8b5cf6,#000,#1c734d,#50f2d0)', 'Hacker King RWQFSFASXC': 'linear-gradient(90deg,#000,#ff3131,#000,#ff3131,#000)', 'Scooped Michael': 'linear-gradient(90deg,#a020f0,#6a0dad,#000,#bf00ff,#7d26cd)', 'Yeti Oni Bonnie': 'linear-gradient(90deg,#5bb9ff,#fff,#2b70d9,#000,#2b70d9,#5bb9ff)', 'Plushtrap': 'linear-gradient(90deg,#000,#555,#aaa,#555,#000)', 'Turkey Chica': 'linear-gradient(90deg,#000,#ff3131,#000,#ff3131,#000)' }; var SHINY_DEF = 'linear-gradient(90deg,red,orange,yellow,lime,cyan,blue,magenta,red)'; function getRarityBg(name, rar) { if (rar === 'shiny') return SHINY[name] || SHINY_DEF; return RARITY[rar] || '#888888'; } var unitMap = {}; var unitMapReady = false; function showTip(card, unitCfg, u, META_CFG) { var rar = (u.rarity || '').toLowerCase(), sh = rar === 'shiny'; tip.style.background = getRarityBg(unitCfg.name, rar); tip.style.backgroundSize = sh ? '200% 200%' : ''; tip.style.animation = sh ? 'metaShiny 6s linear infinite' : ''; tipInner.innerHTML = ''; var nameEl = document.createElement('span'); nameEl.className = 'mt-name'; nameEl.textContent = unitCfg.name; tipInner.appendChild(nameEl); var sep = document.createElement('hr'); sep.className = 'mt-sep'; tipInner.appendChild(sep); function tipRow(iconBg, iconSh, iconSrc, label) { var row = document.createElement('div'); row.className = 'mt-row'; var ic = document.createElement('div'); ic.className = 'mt-icon' + (iconSh ? ' meta-shiny' : ''); ic.style.background = iconBg; if (iconSh) ic.style.backgroundSize = '200% 200%'; var inn = document.createElement('div'); inn.className = 'mt-icon-in'; var im = document.createElement('img'); im.src = iconSrc; im.alt = ''; inn.appendChild(im); ic.appendChild(inn); var tx = document.createElement('div'); tx.className = 'mt-text'; tx.textContent = label; row.appendChild(ic); row.appendChild(tx); return row; } var byteData = META_CFG.bytes[unitCfg.byte]; if (byteData) { var bR = (byteData.rarity || '').toLowerCase(); tipInner.appendChild(tipRow(getRarityBg(unitCfg.byte, bR), bR === 'shiny', byteData.url, 'Byte: ' + unitCfg.byte + (byteData.note ? '\n' + byteData.note : ''))); } var chipData = META_CFG.chips && META_CFG.chips[unitCfg.chip]; if (chipData) { var chR = (chipData.rarity || '').toLowerCase(); tipInner.appendChild(tipRow(getRarityBg(unitCfg.chip, chR), chR === 'shiny', chipData.url, 'Chip: ' + unitCfg.chip + (chipData.note ? '\n' + chipData.note : ''))); } var enchData = META_CFG.enchants[unitCfg.enchant]; if (enchData) { tipInner.appendChild(tipRow(enchData.color, false, enchData.url, 'Enchant: ' + unitCfg.enchant)); } var role = u.role || u.type || ''; if (role) { var rRow = document.createElement('div'); rRow.className = 'mt-row'; var rTx = document.createElement('div'); rTx.className = 'mt-text'; rTx.textContent = 'Role: ' + role; rRow.appendChild(rTx); tipInner.appendChild(rRow); } if (unitCfg.replacement) { var rrU = unitMap[unitCfg.replacement] || {}; var rrR = (rrU.rarity || '').toLowerCase(); tipInner.appendChild(tipRow(getRarityBg(unitCfg.replacement, rrR), rrR === 'shiny', rrU.img || '', 'Alt: ' + unitCfg.replacement)); } tip.style.display = 'block'; tip.style.opacity = '0'; var r = card.getBoundingClientRect(); var tw = tip.offsetWidth, th = tip.offsetHeight; var left = Math.max(4, Math.min(r.left + r.width / 2 - tw / 2, window.innerWidth - tw - 4)); var top = r.bottom + 6; if (top + th > window.innerHeight - 4) top = r.top - th - 6; tip.style.left = left + 'px'; tip.style.top = top + 'px'; tip.style.opacity = '1'; } function hideTip() { tip.style.opacity = '0'; tip.style.display = 'none'; } /* ── Capture ────────────────────────────────────────────────────────────── Same behaviour as the trade calculator's screenshot button. html2canvas is pulled in on first click rather than up front, so a page that never uses the button never pays for the library. Unit renders come from the fntd2-units R2 bucket, which must send CORS headers or html2canvas cannot read them and the cards render blank. */ var _h2cState = 0; // 0 = untouched, 1 = loading, 2 = ready, 3 = failed var _h2cWaiting = []; function withHtml2Canvas(cb) { if (typeof window.html2canvas === 'function') { cb(true); return; } if (_h2cState === 3) { cb(false); return; } _h2cWaiting.push(cb); if (_h2cState === 1) return; _h2cState = 1; var s = document.createElement('script'); s.src = 'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js'; s.onload = function () { _h2cState = typeof window.html2canvas === 'function' ? 2 : 3; var q = _h2cWaiting; _h2cWaiting = []; q.forEach(function (f) { f(_h2cState === 2); }); }; s.onerror = function () { _h2cState = 3; var q = _h2cWaiting; _h2cWaiting = []; q.forEach(function (f) { f(false); }); }; (document.head || document.documentElement).appendChild(s); } /* Point an at a url in CORS mode. html2canvas re-requests every image with crossOrigin set; if the first load was plain, the browser reuses that cached non-CORS copy, the check fails, and the image is dropped from the capture — instantly, with no network round trip. Setting crossOrigin on the original load is what makes the cached copy usable. Must precede .src. */ function _metaImgSrc(im, url) { im.crossOrigin = 'anonymous'; im.src = url || ''; im.addEventListener('error', function () { // CORS unavailable for this one — retry plain so the page still shows it // (it just will not make it into a capture). if (im.getAttribute('crossorigin')) { var s = im.getAttribute('src'); im.removeAttribute('crossorigin'); im.src = s; } }); } /* Same problem for CSS background-image, which has no crossOrigin attribute: warm the cache with a CORS fetch first, then apply it. */ function _metaSetBg(el, url) { if (!url) return; var pre = new Image(); pre.crossOrigin = 'anonymous'; var apply = function () { el.style.backgroundImage = 'url(' + url + ')'; }; pre.onload = apply; pre.onerror = apply; // show it regardless; only the capture suffers pre.src = url; } /* Every URL the capture needs: sources plus CSS background-image urls (the root's artwork, rarity plates, and so on, which have no crossOrigin attribute to set). */ function _metaImageUrls(rootEl) { var urls = {}; Array.prototype.forEach.call(rootEl.querySelectorAll('img'), function (im) { var s = im.getAttribute('src'); if (s && s.indexOf('data:') !== 0) urls[s] = 1; }); var els = [rootEl].concat(Array.prototype.slice.call(rootEl.querySelectorAll('*'))); els.forEach(function (el) { var bg; try { bg = window.getComputedStyle(el).backgroundImage; } catch (e) { return; } if (!bg || bg === 'none') return; var re = /url\((['"]?)(.*?)\1\)/g, m; while ((m = re.exec(bg))) { if (m[2] && m[2].indexOf('data:') !== 0) urls[m[2]] = 1; } }); return Object.keys(urls); } /* html2canvas re-requests every image with crossOrigin set (useCORS). If the browser cache only holds a plain non-CORS copy it reuses that, the CORS check fails, and the image is dropped from the render — the page still looks right, so the loss is invisible until you look at the screenshot. Fetching each URL once in CORS mode first puts a CORS-valid copy in the cache, so html2canvas's own loads all hit. Also clears loading="lazy", which would otherwise leave below-the-fold cards unfetched. */ /* Can html2canvas read this image? It re-requests with crossOrigin, so an image the browser only holds a plain non-CORS copy of will fail — drawing it to a throwaway canvas and reading a pixel back is the same test. */ function _metaReadable(im) { if (!im || !im.naturalWidth) return false; try { var c = document.createElement('canvas'); c.width = 1; c.height = 1; var x = c.getContext('2d'); x.drawImage(im, 0, 0, 1, 1); x.getImageData(0, 0, 1, 1); return true; } catch (e) { return false; } } /* Anything not readable gets re-fetched in CORS mode with the cache bypassed and handed to the clone as a blob: url, which is same-origin and needs no CORS at all. That also replaces the poisoned cache entry, so later captures are cheap. Returns a { url -> blobUrl } map for onclone to apply. */ function _metaPreloadImages(rootEl, cb) { Array.prototype.forEach.call(rootEl.querySelectorAll('img[loading]'), function (im) { im.removeAttribute('loading'); }); var byUrl = {}; Array.prototype.forEach.call(rootEl.querySelectorAll('img'), function (im) { var s = im.getAttribute('src'); if (s) byUrl[s] = byUrl[s] || im; }); var need = _metaImageUrls(rootEl).filter(function (u) { return !_metaReadable(byUrl[u]); // background urls have no , so always re-fetched }); var map = {}, pending = need.length, finished = false; function finish() { if (!finished) { finished = true; cb(map); } } if (!pending) return finish(); need.forEach(function (u) { var settle = function () { if (--pending <= 0) finish(); }; if (!window.fetch) return settle(); window.fetch(u, { mode: 'cors', cache: 'reload', credentials: 'omit' }) .then(function (r) { return r.ok ? r.blob() : null; }) .then(function (b) { if (b) { try { map[u] = URL.createObjectURL(b); } catch (e) {} } settle(); }) .catch(settle); // best effort: a failure just means that one drops }); setTimeout(finish, 20000); // never leave the button stuck on a stalled image } function _metaDlBlob(blob, filename) { var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(function () { URL.revokeObjectURL(url); }, 1000); } /* Preview + Save. Saving from that button is a FRESH user gesture, which is what iOS needs for navigator.share -> Save to Photos (sharing straight after the async capture loses the original tap's activation). */ function _metaShowShot(blob, filename) { if (!blob) return; var canShareFiles = false; try { canShareFiles = !!(navigator.canShare && navigator.canShare({ files: [new File([blob], filename, { type: 'image/png' })] })); } catch (e) {} var touch = (navigator.maxTouchPoints || 0) > 0 || ('ontouchstart' in window); if (!(canShareFiles && touch)) { _metaDlBlob(blob, filename); return; } var overlay = document.createElement('div'); overlay.className = 'meta-shot-overlay'; var img = document.createElement('img'); img.src = URL.createObjectURL(blob); overlay.appendChild(img); function close() { try { URL.revokeObjectURL(img.src); } catch (e) {} if (overlay.parentNode) overlay.parentNode.removeChild(overlay); } var actions = document.createElement('div'); actions.className = 'meta-shot-actions'; var save = document.createElement('button'); save.type = 'button'; save.className = 'meta-shot-btn meta-shot-save'; save.textContent = canShareFiles ? 'Save Image' : 'Download'; save.addEventListener('click', function () { if (!canShareFiles) { _metaDlBlob(blob, filename); close(); return; } navigator.share({ files: [new File([blob], filename, { type: 'image/png' })] }) .then(close) .catch(function (err) { if (err && err.name === 'AbortError') return; // user cancelled the sheet _metaDlBlob(blob, filename); close(); // genuine failure -> download }); }); var closeB = document.createElement('button'); closeB.type = 'button'; closeB.className = 'meta-shot-btn meta-shot-close'; closeB.textContent = 'Close'; closeB.addEventListener('click', close); overlay.addEventListener('click', function (e) { if (e.target === overlay) close(); }); actions.appendChild(save); actions.appendChild(closeB); overlay.appendChild(actions); document.body.appendChild(overlay); } function _metaCapture(rootEl, filename, btn) { _metaPreloadImages(rootEl, function (blobMap) { function _revoke() { Object.keys(blobMap).forEach(function (k) { try { URL.revokeObjectURL(blobMap[k]); } catch (e) {} }); } withHtml2Canvas(function (ok) { if (!ok) { if (btn) { btn.disabled = false; btn.title = 'Screenshot unavailable'; } _revoke(); return; } window.html2canvas(rootEl, { backgroundColor: '#07060f', useCORS: true, scale: 2, logging: false, imageTimeout: 30000, // default is 15s, which slow connections overrun -> blank tiles windowWidth: 1400, windowHeight: Math.max(2000, window.innerHeight || 900), onclone: function (doc) { // The live roots are full-bleed via width:100vw + left:50% + translateX(-50%). // In the clone that offset would push the render half a viewport sideways, // so pin them to a plain fixed-width block instead. ['.metaRoot', '.seamlessRoot'].forEach(function (sel) { Array.prototype.forEach.call(doc.querySelectorAll(sel), function (el) { el.style.width = '1400px'; el.style.left = '0'; el.style.transform = 'none'; el.style.marginBottom = '0'; }); }); if (doc.documentElement) doc.documentElement.style.width = '1400px'; if (doc.body) { doc.body.style.width = '1400px'; doc.body.style.margin = '0'; } // html2canvas renders the clone in an off-screen iframe, so images still // marked loading="lazy" are never in its viewport and the browser never // fetches them — they paint blank. Strip it in the clone. Array.prototype.forEach.call(doc.querySelectorAll('img[loading]'), function (im) { im.removeAttribute('loading'); }); // Swap in the same-origin blob copies fetched above, so html2canvas // never has to make a cross-origin request of its own. Array.prototype.forEach.call(doc.querySelectorAll('img'), function (im) { var s = im.getAttribute('src'); if (s && blobMap[s]) { im.removeAttribute('crossorigin'); im.src = blobMap[s]; } }); var _bgEls = [doc.documentElement].concat( Array.prototype.slice.call(doc.querySelectorAll('*'))); _bgEls.forEach(function (el) { if (!el || !el.style) return; var bg = el.style.backgroundImage; if (!bg || bg === 'none') return; Object.keys(blobMap).forEach(function (u) { if (bg.indexOf(u) !== -1) el.style.backgroundImage = 'url("' + blobMap[u] + '")'; }); }); // Controls are UI, not content — keep them out of the shot. Array.prototype.forEach.call( doc.querySelectorAll('.metaControls,.seamlessControls'), function (el) { el.style.display = 'none'; } ); } }).then(function (canvas) { canvas.toBlob(function (blob) { _metaShowShot(blob, filename); if (btn) btn.disabled = false; _revoke(); }, 'image/png'); }).catch(function (e) { console.error('Meta screenshot failed:', e); if (btn) btn.disabled = false; _revoke(); }); }); }); } function makeCamBtn(rootEl, nameFn) { var btn = document.createElement('button'); btn.type = 'button'; btn.className = 'meta-cam-btn'; btn.setAttribute('aria-label', 'Screenshot'); btn.title = 'Screenshot'; btn.innerHTML = '' + ''; btn.addEventListener('click', function () { if (btn.disabled) return; btn.disabled = true; _metaCapture(rootEl, (typeof nameFn === 'function' ? nameFn() : 'fntd-meta') + '.png', btn); }); return btn; } function makeClutterBtn(container) { var on = true; var btn = document.createElement('button'); btn.className = 'meta-clutter-btn'; btn.textContent = 'ICONS'; btn.addEventListener('click', function () { on = !on; container.classList.toggle('meta-clutter-off', !on); btn.classList.toggle('cltr-off', !on); }); return btn; } function buildCardRow(row, META_CFG) { function renderCards() { row.innerHTML = ''; META_CFG.units.forEach(function (unitCfg) { var u = unitMap[unitCfg.name] || {}; var rar = (u.rarity || '').toLowerCase(), isShiny = rar === 'shiny'; var bg = getRarityBg(unitCfg.name, rar); var card = document.createElement('div'); card.className = 'meta-card' + (isShiny ? ' meta-shiny' : ''); card.style.background = bg; var inner = document.createElement('div'); inner.className = 'meta-card-inner'; var img = document.createElement('img'); img.className = 'meta-card-img'; img.alt = unitCfg.name; // Load in CORS mode so the browser caches a CORS-valid copy. html2canvas // re-requests these with crossOrigin set; if the cache only holds a plain // non-CORS response the browser reuses it, the CORS check fails, and the // render is silently dropped from the screenshot while the page looks fine. // Must be set before .src — assigning it afterwards has no effect. img.crossOrigin = 'anonymous'; img.src = u.img || ''; img.draggable = false; img.loading = 'lazy'; img.onerror = function () { // If CORS is unavailable, retry as a plain load so the page still shows // the render (it just will not appear in a capture). if (this.getAttribute('crossorigin') && this.getAttribute('src')) { var s = this.getAttribute('src'); this.removeAttribute('crossorigin'); this.src = s; return; } this.style.cssText = 'width:100%;height:100%;display:block'; this.removeAttribute('src'); }; inner.appendChild(img); var enchData = META_CFG.enchants[unitCfg.enchant]; if (enchData) { var eW = document.createElement('div'); eW.className = 'meta-enchant'; eW.style.background = enchData.color; var eI = document.createElement('div'); eI.className = 'meta-enchant-inner'; var eM = document.createElement('img'); _metaImgSrc(eM, enchData.url); eM.alt = unitCfg.enchant; eI.appendChild(eM); eW.appendChild(eI); inner.appendChild(eW); } var byteData = META_CFG.bytes[unitCfg.byte]; if (byteData) { var bR = (byteData.rarity || '').toLowerCase(), bSh = bR === 'shiny'; var bW = document.createElement('div'); bW.className = 'meta-byte' + (bSh ? ' meta-shiny' : ''); bW.style.background = getRarityBg(unitCfg.byte, bR); if (bSh) bW.style.backgroundSize = '200% 200%'; var bI = document.createElement('div'); bI.className = 'meta-byte-inner'; var bM = document.createElement('img'); _metaImgSrc(bM, byteData.url); bM.alt = unitCfg.byte; bI.appendChild(bM); bW.appendChild(bI); inner.appendChild(bW); } if (unitCfg.replacement) { var rU = unitMap[unitCfg.replacement] || {}, rR = (rU.rarity || '').toLowerCase(), rSh = rR === 'shiny'; var rW = document.createElement('div'); rW.className = 'meta-replacement' + (rSh ? ' meta-shiny' : ''); rW.style.background = getRarityBg(unitCfg.replacement, rR); if (rSh) rW.style.backgroundSize = '200% 200%'; var rI = document.createElement('div'); rI.className = 'meta-replacement-inner'; var rM = document.createElement('img'); _metaImgSrc(rM, rU.img || ''); rM.alt = unitCfg.replacement; rM.draggable = false; rM.loading = 'lazy'; rI.appendChild(rM); rW.appendChild(rI); inner.appendChild(rW); } if (unitCfg.label) { var lO = document.createElement('div'); lO.className = 'meta-label' + (isShiny ? ' meta-shiny' : ''); lO.style.background = bg; if (isShiny) lO.style.backgroundSize = '200% 200%'; var lI = document.createElement('div'); lI.className = 'meta-label-inner'; lI.textContent = unitCfg.label; lO.appendChild(lI); inner.appendChild(lO); } if (unitCfg.chip) { var chipD = META_CFG.chips && META_CFG.chips[unitCfg.chip]; if (chipD) { var chR = (chipD.rarity || '').toLowerCase(), chSh = chR === 'shiny'; var chipPosClass = ''; if (unitCfg.label) { chipPosClass = unitCfg.replacement ? ' chip-mid-right' : ' chip-bottom-right'; } var cW = document.createElement('div'); cW.className = 'meta-chip' + (chSh ? ' meta-shiny' : '') + chipPosClass; cW.style.background = getRarityBg(unitCfg.chip, chR); if (chSh) cW.style.backgroundSize = '200% 200%'; var cI = document.createElement('div'); cI.className = 'meta-chip-inner'; var cM = document.createElement('img'); _metaImgSrc(cM, chipD.url); cM.alt = unitCfg.chip; cI.appendChild(cM); cW.appendChild(cI); inner.appendChild(cW); } } card.appendChild(inner); (function (c, cfg, data) { c.addEventListener('mouseenter', function () { showTip(c, cfg, data, META_CFG); }); c.addEventListener('mouseleave', hideTip); }(card, unitCfg, u)); var slot = document.createElement('div'); slot.className = 'meta-slot'; slot.appendChild(card); if (unitCfg.caption) { var cap = document.createElement('div'); cap.className = 'meta-caption'; cap.textContent = unitCfg.caption; slot.appendChild(cap); } row.appendChild(slot); }); } renderCards(); return renderCards; } function renderMeta(mountEl, META_CFG) { var root = document.createElement('div'); root.className = 'metaRoot'; _metaSetBg(root, META_CFG.background); var titleEl = document.createElement('div'); titleEl.className = 'metaTitle'; titleEl.textContent = META_CFG.title; root.appendChild(titleEl); var controls = document.createElement('div'); controls.className = 'metaControls'; controls.appendChild(makeCamBtn(root, function () { return 'fntd-meta-' + (_metaSlug(META_CFG.title) || 'team'); })); controls.appendChild(makeClutterBtn(root)); root.appendChild(controls); var row = document.createElement('div'); row.className = 'metaRow'; root.appendChild(row); var footer = document.createElement('div'); footer.className = 'metaFooter'; if (META_CFG.logoUrl) { var logoEl = document.createElement('img'); logoEl.className = 'metaLogo'; _metaImgSrc(logoEl, META_CFG.logoUrl); logoEl.alt = ''; footer.appendChild(logoEl); } var siteEl = document.createElement('div'); siteEl.className = 'metaSiteUrl'; siteEl.textContent = META_CFG.siteUrl; footer.appendChild(siteEl); root.appendChild(footer); mountEl.appendChild(root); return buildCardRow(row, META_CFG); } function _metaSlug(s) { return String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, ''); } // Deep link: reflect the active seamless tab in /fntd2/meta-teams/ (first tab = base URL). function _metaSyncUrl(slug, idx) { if (location.pathname.indexOf('meta-teams') === -1) return; var base = location.pathname.replace(/(meta-teams)(\/.*)?$/, '$1'); var np = base + (idx > 0 && slug ? '/' + slug : ''); try { history.replaceState(null, '', np + location.search + location.hash); } catch (e) {} } function renderSeamless(mountEl, metasData) { var shared = metasData.shared || {}; var seamCfg = metasData.seamless || {}; if (seamCfg === true) seamCfg = {}; /* support old format (single pageTitle+order) and new format (pages array) */ var pages = seamCfg.pages; if (!pages || !pages.length) { pages = [{ pageTitle: seamCfg.pageTitle, pageNote: seamCfg.pageNote, order: seamCfg.order }]; } var root = document.createElement('div'); root.className = 'seamlessRoot'; _metaSetBg(root, shared.background || ''); /* ── fixed header ── */ var header = document.createElement('div'); header.className = 'seamlessHeader'; if (shared.logoUrl) { var logo = document.createElement('img'); logo.className = 'seamlessLogo'; _metaImgSrc(logo, shared.logoUrl); logo.alt = ''; header.appendChild(logo); } var siteName = document.createElement('div'); siteName.className = 'seamlessSiteName'; siteName.textContent = seamCfg.siteName || 'FNTD USER GUIDE'; header.appendChild(siteName); root.appendChild(header); if (shared.siteUrl) { var siteUrlEl = document.createElement('div'); siteUrlEl.className = 'seamlessSiteUrl'; siteUrlEl.textContent = shared.siteUrl; root.appendChild(siteUrlEl); } var controls = document.createElement('div'); controls.className = 'seamlessControls'; // Name the file after whichever tab is open when the shot is taken. pageWrappers // is filled in below, but this only runs on click, so it is populated by then. controls.appendChild(makeCamBtn(root, function () { var idx = 0; for (var i = 0; i < pageWrappers.length; i++) { if (pageWrappers[i].style.display !== 'none') { idx = i; break; } } var t = (pages[idx] && pages[idx].pageTitle) || seamCfg.pageTitle || ''; return 'fntd-meta-' + (_metaSlug(t) || 'teams'); })); controls.appendChild(makeClutterBtn(root)); root.appendChild(controls); /* ── page-switcher tab bar (only when multiple pages) ── */ var tabBar = null; var pageWrappers = []; var reRenders = []; if (pages.length > 1) { tabBar = document.createElement('div'); tabBar.className = 'seamlessPageTabBar'; root.appendChild(tabBar); } pages.forEach(function (page, pageIdx) { /* wrapper holds the pageTitle + all sections for this page */ var wrap = document.createElement('div'); if (pageIdx > 0) wrap.style.display = 'none'; pageWrappers.push(wrap); if (page.pageTitle) { var ptEl = document.createElement('div'); ptEl.className = 'seamlessPageTitle'; ptEl.textContent = page.pageTitle; wrap.appendChild(ptEl); } if (page.pageNote) { var pnEl = document.createElement('div'); pnEl.className = 'seamlessPageNote'; pnEl.textContent = page.pageNote; wrap.appendChild(pnEl); } var order = page.order || Object.keys(metasData.metas || {}); order.forEach(function (metaName) { var cfg = metasData.metas && metasData.metas[metaName]; if (!cfg) return; var section = document.createElement('div'); section.className = 'seamlessSection'; var titleEl = document.createElement('div'); titleEl.className = 'seamlessSectionTitle'; titleEl.textContent = metaName; section.appendChild(titleEl); var row = document.createElement('div'); row.className = 'metaRow'; section.appendChild(row); wrap.appendChild(section); var META_CFG = { title: metaName, logoUrl: '', siteUrl: '', background: '', bytes: shared.bytes || {}, enchants: shared.enchants || {}, chips: shared.chips || {}, units: cfg.units || [] }; reRenders.push(buildCardRow(row, META_CFG)); }); root.appendChild(wrap); /* build the tab button for this page */ if (tabBar) { var tab = document.createElement('button'); tab.className = 'seamlessPageTab' + (pageIdx === 0 ? ' seamlessPageTabActive' : ''); tab.textContent = page.pageTitle || ('List ' + (pageIdx + 1)); (function (i) { tab.addEventListener('click', function () { pageWrappers.forEach(function (w, j) { w.style.display = j === i ? '' : 'none'; }); var tabs = tabBar.querySelectorAll('.seamlessPageTab'); for (var k = 0; k < tabs.length; k++) tabs[k].classList.toggle('seamlessPageTabActive', k === i); _metaSyncUrl(_metaSlug(pages[i].pageTitle || ''), i); }); }(pageIdx)); tabBar.appendChild(tab); } }); /* Deep link: /fntd2/meta-teams/ opens that tab on load. */ if (tabBar) { var _seg = (location.pathname.match(/meta-teams\/([^\/?#]+)/) || [])[1]; if (_seg) { _seg = decodeURIComponent(_seg).toLowerCase(); var _target = -1; pages.forEach(function (pg, idx) { if (_metaSlug(pg.pageTitle || '') === _seg) _target = idx; }); if (_target > 0) { pageWrappers.forEach(function (w, j) { w.style.display = j === _target ? '' : 'none'; }); var _tabs = tabBar.querySelectorAll('.seamlessPageTab'); for (var _k = 0; _k < _tabs.length; _k++) _tabs[_k].classList.toggle('seamlessPageTabActive', _k === _target); } } } mountEl.appendChild(root); return reRenders; } var metasData = null; var activeReRenders = []; function renderAllMounts() { var mounts = Array.prototype.slice.call( document.querySelectorAll('.metaMount[data-meta], .metaMount[data-seamless], #tlMount[data-meta]') ); mounts.forEach(function (mountEl) { if (mountEl.getAttribute('data-rendered')) return; mountEl.setAttribute('data-rendered', '1'); if (mountEl.hasAttribute('data-seamless')) { var rrs = renderSeamless(mountEl, metasData || { shared: {}, metas: {}, seamless: {} }); rrs.forEach(function (fn) { activeReRenders.push(fn); if (unitMapReady) fn(); }); return; } var name = mountEl.getAttribute('data-meta'); var cfg = metasData && metasData.metas && metasData.metas[name]; if (!cfg) { var err = document.createElement('div'); err.style.cssText = 'color:#ffa45b;font-family:monospace;padding:10px;font-size:12px'; err.textContent = 'Meta "' + name + '" not found in metas.json'; mountEl.appendChild(err); return; } var shared = (metasData && metasData.shared) || {}; var META_CFG = { title: name, logoUrl: shared.logoUrl || '', siteUrl: shared.siteUrl || '', background: shared.background || '', bytes: shared.bytes || {}, enchants: shared.enchants || {}, chips: shared.chips || {}, units: cfg.units || [] }; var rr = renderMeta(mountEl, META_CFG); activeReRenders.push(rr); if (unitMapReady) rr(); }); } var xM = new XMLHttpRequest(); xM.open('GET', METAS_URL, true); xM.onload = function () { try { metasData = JSON.parse(xM.responseText); } catch (e) { metasData = { shared: {}, metas: {} }; } renderAllMounts(); }; xM.onerror = function () { metasData = { shared: {}, metas: {} }; renderAllMounts(); }; xM.send(); var xC = new XMLHttpRequest(); xC.open('GET', UNITS_URL, true); xC.onload = function () { try { JSON.parse(xC.responseText).forEach(function (u) { unitMap[u.name] = { img: u.imgNormal || '', rarity: u.rarity || '', role: u.role || u.type || '' }; }); } catch (e) {} unitMapReady = true; activeReRenders.forEach(function (fn) { fn(); }); }; xC.send(); }());