mtg_python_deckbuilder/code/web/templates/themes/preview_fragment.html

431 lines
No EOL
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% if preview %}
<div class="preview-modal-content theme-preview-expanded{% if minimal %} minimal-variant{% endif %}">
{% if not minimal %}
<div class="preview-header">
<h3 data-preview-heading>{{ preview.theme }}</h3>
<button id="preview-close-btn" onclick="document.getElementById('theme-preview-modal') && document.getElementById('theme-preview-modal').remove();" class="btn btn-ghost">Close ✕</button>
</div>
{% if preview.stub %}<div class="note note-stub">Stub sample (placeholder logic)</div>{% endif %}
<div class="preview-controls">
<label><input type="checkbox" id="curated-only-toggle"/> Curated Only</label>
<label><input type="checkbox" id="reasons-toggle" checked/> Reasons <span class="help-icon" title="Toggle why the payoff is included (i.e. overlapping themes or other reasoning)">?</span></label>
<label><input type="checkbox" id="show-duplicates-toggle"/> Show Collapsed Duplicates</label>
<span id="preview-status" aria-live="polite"></span>
</div>
<details id="preview-rationale" class="preview-rationale">
<summary>Commander Overlap & Diversity Rationale</summary>
<div class="preview-rationale-controls">
<button type="button" class="btn btn-ghost" onclick="toggleHoverCompactMode()" title="Toggle compact hover panel (smaller image & condensed metadata)">Hover Compact</button>
<span id="hover-compact-indicator">Mode: <span data-mode>normal</span></span>
</div>
<ul id="rationale-points">
{% if preview.commander_rationale and preview.commander_rationale|length > 0 %}
{% for r in preview.commander_rationale %}
<li>
<strong>{{ r.label }}</strong>: {{ r.value }}
{% if r.detail %}<span class="detail">({{ r.detail|join(', ') }})</span>{% endif %}
{% if r.instances %}<span class="instances"> ({{ r.instances }} instances)</span>{% endif %}
</li>
{% endfor %}
{% else %}
<li>Computing…</li>
{% endif %}
</ul>
</details>
{% endif %}
<div class="preview-two-col" role="group" aria-label="Theme preview cards and commanders">
<div class="preview-col-divider"></div>
<div class="col-left">
{% if not minimal %}{% if not suppress_curated %}<h4 class="preview-section-header">Example Cards</h4>{% else %}<h4 class="preview-section-header">Sampled Synergy Cards</h4>{% endif %}{% endif %}
<hr class="preview-section-hr" />
<div class="cards-flow" data-synergies="{{ preview.synergies_used|join(',') if preview.synergies_used }}" data-pin-scope="{{ preview.theme_id }}">
{% set inserted = {'examples': False, 'curated_synergy': False, 'payoff': False, 'enabler_support': False, 'wildcard': False} %}
{% for c in preview.sample if (not suppress_curated and ('example' in c.roles or 'curated_synergy' in c.roles)) or 'payoff' in c.roles or 'enabler' in c.roles or 'support' in c.roles or 'wildcard' in c.roles %}
{% if c.dup_collapsed %}{% set dup_class = ' is-collapsed-duplicate' %}{% else %}{% set dup_class = '' %}{% endif %}
{% set primary = c.roles[0] if c.roles else '' %}
{% if (not suppress_curated) and 'example' in c.roles and not inserted.examples %}<div class="group-separator" data-group="examples">Curated Examples</div>{% set _ = inserted.update({'examples': True}) %}{% endif %}
{% if (not suppress_curated) and primary == 'curated_synergy' and not inserted.curated_synergy %}<div class="group-separator mt-larger" data-group="curated_synergy">Curated Synergy</div>{% set _ = inserted.update({'curated_synergy': True}) %}{% endif %}
{% if primary == 'payoff' and not inserted.payoff %}<div class="group-separator mt-larger" data-group="payoff">Payoffs</div>{% set _ = inserted.update({'payoff': True}) %}{% endif %}
{% if primary in ['enabler','support'] and not inserted.enabler_support %}<div class="group-separator mt-larger" data-group="enabler_support">Enablers & Support</div>{% set _ = inserted.update({'enabler_support': True}) %}{% endif %}
{% if primary == 'wildcard' and not inserted.wildcard %}<div class="group-separator mt-larger" data-group="wildcard">Wildcards</div>{% set _ = inserted.update({'wildcard': True}) %}{% endif %}
{% set overlaps = [] %}
{% if preview.synergies_used and c.tags %}
{% for tg in c.tags %}{% if tg in preview.synergies_used %}{% set _ = overlaps.append(tg) %}{% endif %}{% endfor %}
{% endif %}
<div class="card-sample{{ dup_class }}{% if overlaps %} has-overlap{% endif %}" data-card-name="{{ c.name }}" data-role="{{ c.roles[0] if c.roles }}" data-reasons="{{ c.reasons|join('; ') if c.reasons }}" data-tags="{{ c.tags|join(', ') if c.tags }}" data-overlaps="{{ overlaps|join(',') }}" data-mana="{{ c.mana_cost if c.mana_cost }}" data-rarity="{{ c.rarity if c.rarity }}" {% if c.dup_group_size %}data-dup-group-size="{{ c.dup_group_size }}"{% endif %} {% if c.dup_anchor %}data-dup-anchor="1"{% endif %} {% if c.dup_collapsed %}data-dup-collapsed="1" data-dup-anchor-name="{{ c.dup_anchor_name }}"{% endif %}>
<div class="thumb-wrap">
<img class="card-thumb" width="230" loading="lazy" decoding="async" src="{{ c.name|card_image('small') }}" alt="{{ c.name }} image" data-card-name="{{ c.name }}" data-role="{{ c.roles[0] if c.roles }}" data-tags="{{ c.tags|join(', ') if c.tags }}" {% if overlaps %}data-overlaps="{{ overlaps|join(',') }}"{% endif %} data-placeholder-color="#0b0d12" onload="this.setAttribute('data-loaded', '1');" />
<span class="role-chip role-{{ c.roles[0] if c.roles }}" title="Primary role: {{ c.roles[0] if c.roles }}">{{ c.roles[0][0]|upper if c.roles }}</span>
{% if overlaps %}<span class="overlap-badge" title="Synergy overlaps: {{ overlaps|join(', ') }}">{{ overlaps|length }}</span>{% endif %}
{% if c.dup_anchor and c.dup_group_size and c.dup_group_size > 1 %}<span class="dup-badge" title="{{ c.dup_group_size - 1 }} similar cards collapsed">+{{ c.dup_group_size - 1 }}</span>{% endif %}
<button type="button" class="pin-btn" aria-label="Pin card" title="Pin card" data-pin-btn></button>
</div>
<div class="meta">
<div class="ci-ribbon" aria-label="Color identity"></div>
<div class="nm" title="{{ c.name }}">{{ c.name }}</div>
<div class="mana-line" aria-label="Mana Cost"></div>
{% if c.rarity %}<div class="rarity-badge rarity-{{ c.rarity }}" title="Rarity: {{ c.rarity }}">{{ c.rarity }}</div>{% endif %}
<div class="role">
{% for r in c.roles %}<span class="mini-badge role-{{ r }}" title="{{ r }} role">{{ r[0]|upper }}</span>{% endfor %}
</div>
{% if c.reasons %}<div class="reasons" data-reasons-block title="Heuristics: {{ c.reasons|join(', ') }}">{{ c.reasons|map('replace','commander_bias','cmbias')|join(' · ') }}</div>{% endif %}
</div>
</div>
{% endfor %}
{% set has_synth = false %}
{% for c in preview.sample %}{% if 'synthetic' in c.roles %}{% set has_synth = true %}{% endif %}{% endfor %}
{% if has_synth %}
<div class="full-width-spacer"></div>
{% for c in preview.sample %}
{% if 'synthetic' in c.roles %}
<div class="card-sample synthetic" data-card-name="{{ c.name }}" data-role="synthetic" data-reasons="{{ c.reasons|join('; ') if c.reasons }}" data-tags="{{ c.tags|join(', ') if c.tags }}" data-overlaps="">
<div class="name">{{ c.name }}</div>
<div class="roles">{{ c.roles|join(', ') }}</div>
{% if c.reasons %}<div class="reasons-text">{{ c.reasons|join(', ') }}</div>{% endif %}
</div>
{% endif %}
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-right">
{% if not minimal %}{% if not suppress_curated %}<h4 class="preview-section-header">Example Commanders</h4>{% else %}<h4 class="preview-section-header">Synergy Commanders</h4>{% endif %}{% endif %}
<hr class="preview-section-hr" />
{% if example_commanders and not suppress_curated %}
<div class="commander-grid">
{% for name in example_commanders %}
{# Derive per-commander overlaps; still show full theme synergy set in data-tags for context #}
{% set base = name %}
{% set overlaps = [] %}
{% if ' - Synergy (' in name %}
{% set base = name.split(' - Synergy (')[0] %}
{% set annot = name.split(' - Synergy (')[1].rstrip(')') %}
{% for sy in annot.split(',') %}{% set _ = overlaps.append(sy.strip()) %}{% endfor %}
{% endif %}
{% set tags_all = preview.synergies_used[:] if preview.synergies_used else [] %}
{% for ov in overlaps %}{% if ov not in tags_all %}{% set _ = tags_all.append(ov) %}{% endif %}{% endfor %}
<div class="commander-cell" data-card-name="{{ base }}" data-role="commander_example" data-tags="{{ tags_all|join(', ') if tags_all }}" data-overlaps="{{ overlaps|join(', ') if overlaps }}" data-original-name="{{ name }}">
<img class="card-thumb" width="230" src="{{ base|card_image('small') }}" alt="{{ base }} image" loading="lazy" decoding="async" data-card-name="{{ base }}" data-role="commander_example" data-tags="{{ tags_all|join(', ') if tags_all }}" data-overlaps="{{ overlaps|join(', ') if overlaps }}" data-original-name="{{ name }}" data-placeholder-color="#0b0d12" onload="this.setAttribute('data-loaded', '1');" />
<div class="commander-name" title="{{ name }}">{{ name }}</div>
</div>
{% endfor %}
</div>
{% elif not suppress_curated %}
<div class="no-commanders-message">No curated commander examples.</div>
{% endif %}
{% if synergy_commanders %}
<div class="synergy-commanders-section">
<div class="synergy-commanders-header">
<h5>Synergy Commanders</h5>
<span class="derived-badge" title="Derived from synergy overlap heuristics">Derived</span>
</div>
<div class="commander-grid">
{% for name in synergy_commanders[:8] %}
{# Strip any appended ' - Synergy (...' suffix for image lookup while preserving display #}
{% set base = name %}
{% if ' - Synergy' in name %}{% set base = name.split(' - Synergy')[0] %}{% endif %}
{% set overlaps = [] %}
{% if ' - Synergy (' in name %}
{% set annot = name.split(' - Synergy (')[1].rstrip(')') %}
{% for sy in annot.split(',') %}{% set _ = overlaps.append(sy.strip()) %}{% endfor %}
{% endif %}
{% set tags_all = preview.synergies_used[:] if preview.synergies_used else [] %}
{% for ov in overlaps %}{% if ov not in tags_all %}{% set _ = tags_all.append(ov) %}{% endif %}{% endfor %}
<div class="commander-cell synergy" data-card-name="{{ base }}" data-role="synergy_commander" data-tags="{{ tags_all|join(', ') if tags_all }}" data-overlaps="{{ overlaps|join(', ') if overlaps }}" data-original-name="{{ name }}">
<img class="card-thumb" width="230" src="{{ base|card_image('small') }}" alt="{{ base }} image" loading="lazy" decoding="async" data-card-name="{{ base }}" data-role="synergy_commander" data-tags="{{ tags_all|join(', ') if tags_all }}" data-overlaps="{{ overlaps|join(', ') if overlaps }}" data-original-name="{{ name }}" data-placeholder-color="#0b0d12" onload="this.setAttribute('data-loaded', '1');" />
<div class="commander-name" title="{{ name }}">{{ name }}</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
{% if not minimal %}<div class="preview-help-text">Hover any card or commander for a larger preview and tag breakdown. Use Curated Only to hide sampled roles. Role chips: P=Payoff, E=Enabler, S=Support, W=Wildcard, X=Curated Example.</div>{% endif %}
</div>
{% else %}
<div class="preview-modal-content preview-skeleton">
<div class="sk-header">
<div class="sk-bar title"></div>
<div class="sk-bar close"></div>
</div>
<div class="sk-cards">
{% for i in range(8) %}<div class="sk-card"></div>{% endfor %}
</div>
</div>
{% endif %}
<style>
.theme-preview-expanded .card-thumb { width:230px; height:auto; border-radius:10px; border:1px solid var(--border); background:#0b0d12; object-fit:cover; }
.theme-preview-expanded .role-chip { position:absolute; top:4px; left:4px; background:rgba(0,0,0,0.65); color:#fff; font-size:10px; padding:2px 5px; border-radius:10px; line-height:1; letter-spacing:.5px; }
.theme-preview-expanded .mini-badge { background:var(--panel-alt); border:1px solid var(--border); padding:1px 4px; font-size:9px; border-radius:8px; line-height:1; }
.theme-preview-expanded .role-payoff .role-chip, .mini-badge.role-payoff { background:#2563eb; color:#fff; }
.mini-badge.role-payoff { background:#1d4ed8; color:#fff; }
.mini-badge.role-enabler { background:#047857; color:#fff; }
.mini-badge.role-support { background:#6d28d9; color:#fff; }
.mini-badge.role-wildcard { background:#92400e; color:#fff; }
.mini-badge.role-example, .mini-badge.role-curated_synergy { background:#4f46e5; color:#fff; }
.theme-preview-expanded .commander-grid .card-thumb { width:230px; }
.theme-preview-expanded.minimal-variant .preview-header,
.theme-preview-expanded.minimal-variant .preview-controls,
.theme-preview-expanded.minimal-variant .preview-rationale { display:none !important; }
.theme-preview-expanded.minimal-variant h4 { display:none; }
.theme-preview-expanded .commander-cell.synergy .card-thumb { filter:grayscale(.15) contrast(1.05); }
.theme-preview-expanded .card-sample.synthetic { display:flex; flex-direction:column; justify-content:flex-start; }
.theme-preview-expanded .card-sample.has-overlap { outline:1px solid var(--accent); outline-offset:2px; }
/* Hover panel parity styling */
#hover-card-panel { font-family: inherit; }
#hover-card-panel .hcp-role { display:inline-block; margin-left:6px; padding:2px 6px; font-size:10px; letter-spacing:.5px; border:1px solid var(--border); border-radius:10px; background:var(--panel-alt); text-transform:uppercase; }
#hover-card-panel.is-payoff .hcp-role { background:var(--accent, #38bdf8); color:#fff; border-color:var(--accent, #38bdf8); }
#hover-card-panel .hcp-reasons li { margin:2px 0; }
#hover-card-panel .hcp-reasons { scrollbar-width:thin; }
#hover-card-panel .hcp-tags { font-size:10px; opacity:.75; }
.theme-preview-expanded .overlap-badge { position:absolute; top:4px; right:4px; background:#0f766e; color:#fff; font-size:10px; padding:2px 5px; border-radius:10px; }
.theme-preview-expanded .mana-symbol { width:14px; height:14px; border-radius:50%; background:#222; color:#fff; display:inline-flex; align-items:center; justify-content:center; font-size:9px; font-weight:600; box-shadow:0 0 0 1px #000 inset; }
.theme-preview-expanded .mana-symbol.W { background:#f3f2dc; color:#222; }
.theme-preview-expanded .mana-symbol.U { background:#5b8dd6; }
.theme-preview-expanded .mana-symbol.B { background:#2d2d2d; }
.theme-preview-expanded .mana-symbol.R { background:#c4472d; }
.theme-preview-expanded .mana-symbol.G { background:#2f6b3a; }
.theme-preview-expanded .mana-symbol.C { background:#555; }
.theme-preview-expanded .ci-ribbon .pip { width:10px; height:10px; border-radius:50%; display:inline-block; box-shadow:0 0 0 1px #000 inset; }
.theme-preview-expanded .ci-ribbon .pip.W { background:#f3f2dc; }
.theme-preview-expanded .ci-ribbon .pip.U { background:#5b8dd6; }
.theme-preview-expanded .ci-ribbon .pip.B { background:#2d2d2d; }
.theme-preview-expanded .ci-ribbon .pip.R { background:#c4472d; }
.theme-preview-expanded .ci-ribbon .pip.G { background:#2f6b3a; }
.theme-preview-expanded .tooltip-reasons ul { margin:0; padding-left:14px; }
.theme-preview-expanded .tooltip-reasons li { list-style:disc; margin:0; padding:0; }
.theme-preview-expanded .rarity-common { color:#9ca3af; }
.theme-preview-expanded .rarity-uncommon { color:#60a5fa; }
.theme-preview-expanded .rarity-rare { color:#fbbf24; }
.theme-preview-expanded .rarity-mythic { color:#fb923c; }
@media (max-width: 950px){ .theme-preview-expanded .two-col { grid-template-columns: 1fr; } .theme-preview-expanded .col-right { order:-1; } }
</style>
<style>
.card-sample.pinned { outline:2px solid var(--accent); outline-offset:2px; }
.card-sample .pin-btn.active { background:var(--accent); color:#000; }
.card-sample.is-collapsed-duplicate { display:none; }
</style>
<script>
// sessionStorage preview fragment cache (keyed by theme + limit + commander). Stores HTML + ETag.
(function(){ if(document.querySelector('.theme-preview-expanded.minimal-variant')) return;
try {
var root = document.getElementById('theme-preview-modal');
if(!root) return;
var container = root.querySelector('.preview-modal-content');
if(!container) return;
// Attach a marker for quick retrieval
container.setAttribute('data-preview-fragment','1');
} catch(_){}
})();
</script>
<script>
// Collapsed duplicate toggle logic (persist in localStorage global scope)
(function(){
try {
var toggle = document.getElementById('show-duplicates-toggle');
if(!toggle) return;
var STORE_KEY = 'preview.showCollapsedDuplicates';
function apply(){
var show = !!toggle.checked;
document.querySelectorAll('.card-sample.is-collapsed-duplicate').forEach(function(el){
el.style.display = show ? '' : 'none';
});
}
var saved = localStorage.getItem(STORE_KEY);
if(saved === '1'){ toggle.checked = true; }
apply();
toggle.addEventListener('change', function(){
localStorage.setItem(STORE_KEY, toggle.checked ? '1':'0');
apply();
});
} catch(_){}
})();
</script>
<script>
// Client-side pin/unpin personalized examples (localStorage scoped by theme_id)
(function(){
try {
var root = document.querySelector('.cards-flow[data-pin-scope]');
if(!root) return;
var scope = root.getAttribute('data-pin-scope');
var storeKey = 'preview.pins.'+scope;
function loadPins(){
try { return JSON.parse(localStorage.getItem(storeKey) || '[]'); } catch(_) { return []; }
}
function savePins(pins){ try { localStorage.setItem(storeKey, JSON.stringify(pins.slice(0,100))); } catch(_){} }
function setState(){
var pins = loadPins();
var cards = root.querySelectorAll('.card-sample');
cards.forEach(function(cs){
var name = cs.getAttribute('data-card-name');
var btn = cs.querySelector('[data-pin-btn]');
var pinned = pins.indexOf(name) !== -1;
cs.classList.toggle('pinned', pinned);
if(btn){ btn.classList.toggle('active', pinned); btn.textContent = pinned ? '★' : '☆'; btn.setAttribute('aria-label', pinned ? 'Unpin card' : 'Pin card'); }
});
}
root.addEventListener('click', function(e){
var btn = e.target.closest('[data-pin-btn]');
if(!btn) return;
var card = btn.closest('.card-sample');
if(!card) return;
var name = card.getAttribute('data-card-name');
var pins = loadPins();
var idx = pins.indexOf(name);
if(idx === -1) pins.push(name); else pins.splice(idx,1);
savePins(pins);
setState();
});
setState();
} catch(_){ }
})();
</script>
<script>
// Lazy-load fallback for browsers ignoring loading=lazy (very old) + intersection observer prefetch enhancement
(function(){
try {
if('loading' in HTMLImageElement.prototype) return; // native supported
var imgs = Array.prototype.slice.call(document.querySelectorAll('.theme-preview-expanded img[loading="lazy"]'));
imgs.forEach(function(img){
if(!img.dataset.src){ img.dataset.src = img.src; }
img.src = img.dataset.src;
});
} catch(_){}
})();
</script>
<script>
// Lightweight hover tooltip for card reasons (progressive enhancement)
(function(){
var host = document.currentScript && document.currentScript.parentElement;
if(!host) return;
var tip = document.createElement('div');
tip.className='tooltip-reasons';
tip.style.position='fixed'; tip.style.pointerEvents='none'; tip.style.zIndex=9500; tip.style.padding='6px 8px'; tip.style.fontSize='11px'; tip.style.background='rgba(0,0,0,0.8)'; tip.style.color='#fff'; tip.style.border='1px solid var(--border)'; tip.style.borderRadius='6px'; tip.style.boxShadow='0 2px 8px rgba(0,0,0,0.4)'; tip.style.display='none'; maxWidth='260px';
document.body.appendChild(tip);
function show(e, html){ tip.innerHTML = html; tip.style.display='block'; move(e); }
function move(e){ tip.style.top=(e.clientY+14)+'px'; tip.style.left=(e.clientX+12)+'px'; }
function hide(){ tip.style.display='none'; }
host.addEventListener('mouseover', function(ev){
if(ev.target.closest('.thumb-wrap')) return;
var t = ev.target.closest('.card-sample');
if(!t) return;
var name = t.querySelector('.nm') ? t.querySelector('.nm').textContent : t.getAttribute('data-card-name');
var role = t.getAttribute('data-role');
var reasons = t.getAttribute('data-reasons') || '';
var tags = t.getAttribute('data-tags') || '';
var overlaps = t.getAttribute('data-overlaps') || '';
var html = '<strong>'+ (name||'') +'</strong><br/><em>'+ (role||'') +'</em>';
if(tags){
if(overlaps){
var tagArr = tags.split(/\s*,\s*/);
var overlapSet = new Set(overlaps.split(/\s*,\s*/).filter(Boolean));
var rendered = tagArr.map(function(x){ return overlapSet.has(x) ? '<span style="color:#0ea5e9; font-weight:600;">'+x+'</span>' : x; }).join(', ');
html += '<br/><span style="opacity:.85">'+ rendered +'</span>';
} else {
html += '<br/><span style="opacity:.8">'+tags+'</span>';
}
}
if(reasons){
var items = reasons.split(/;\s*/).filter(Boolean).map(function(r){ return '<li>'+r+'</li>'; }).join('');
html += '<div style="margin-top:4px; font-size:10px; line-height:1.25;"><ul>'+items+'</ul></div>';
}
show(ev, html);
});
host.addEventListener('mousemove', function(ev){ if(tip.style.display==='block') move(ev); });
host.addEventListener('mouseleave', function(ev){ if(!ev.relatedTarget || !ev.relatedTarget.closest('.card-sample')) hide(); }, true);
host.addEventListener('mouseout', function(ev){ if(!ev.relatedTarget || !ev.relatedTarget.closest('.card-sample')) hide(); });
})();
</script>
<script>
// Post-render safety pass: normalize commander thumbnails.
// 1. If annotated form 'Name - Synergy (A, B)' still in data-card-name, strip to base.
// 2. If annotation present in original name but data-tags/data-overlaps empty, populate them.
(function(){
try {
document.querySelectorAll('.theme-preview-expanded img.card-thumb').forEach(function(img){
var n = img.getAttribute('data-card-name') || '';
var orig = img.getAttribute('data-original-name') || n;
// Patterns to strip: ' - Synergy (' plus any trailing text/paren and optional closing paren
var m = /(.*?)(\s*-\s*Synergy\b.*)$/i.exec(orig);
if(m){
var base = m[1].trim();
if(base && base !== n){
img.setAttribute('data-card-name', base);
img.src = '/api/images/small/' + encodeURIComponent(base);
}
// Attempt to derive overlaps if not already present
if(!img.getAttribute('data-overlaps')){
var annMatch = /-\s*Synergy\s*\(([^)]+)\)/i.exec(orig);
if(annMatch){
var list = annMatch[1].split(',').map(function(x){return x.trim();}).filter(Boolean).join(', ');
if(list){
// Preserve existing broader data-tags if present; only set overlaps
if(!img.getAttribute('data-tags')) img.setAttribute('data-tags', list);
img.setAttribute('data-overlaps', list);
}
}
}
}
});
} catch(_){ }
})();
</script>
<script>
// Mana cost parser to convert {X}{2}{U}{B/P} style strings into colored symbol bubbles.
// Removed legacy client-side mana parser (server now supplies normalized mana & pip/color metadata)
// Placeholder: if server later supplies pre-rendered HTML we simply inject it here.
// (Intentionally no-op; roadmap EXIT Server-side mana/rarity ingestion follow-up.)
(()=>{})();
</script>
<script>
// Color identity ribbon (simple heuristic from mana cost symbols); shown above name.
// Removed heuristic color identity derivation (server now provides authoritative color_identity_list)
// Future: server can inline <span class="pip W"></span> elements directly; leaving ribbon container empty if absent.
(()=>{})();
</script>
<script>
// (Removed fragment-specific large hover panel; using global unified panel in base.html)
</script>
<script>
// Commander overlap & diversity rationale (client-side derivation) Phase 1 tooltip implementation
(function(){
try {
var listHost = document.getElementById('rationale-points');
if(!listHost) return;
var modeLabel = document.querySelector('#hover-compact-indicator [data-mode]');
document.addEventListener('mtg:hoverCompactToggle', function(){ if(modeLabel){ modeLabel.textContent = window.__hoverCompactMode ? 'compact' : 'normal'; }});
var cards = Array.from(document.querySelectorAll('.theme-preview-expanded .card-sample'))
.filter(c=>!(c.classList.contains('synthetic')));
if(!cards.length){ listHost.innerHTML='<li>No real cards in sample.</li>'; return; }
var roleCounts = {payoff:0,enabler:0,support:0,wildcard:0,example:0,curated_synergy:0,synthetic:0};
var overlapTotals = 0; var overlapSet = new Set();
cards.forEach(c=>{
var role = c.getAttribute('data-role')||'';
if(roleCounts[role]!==undefined) roleCounts[role]++;
var overlaps = (c.getAttribute('data-overlaps')||'').split(/\s*,\s*/).filter(Boolean);
overlaps.forEach(o=>overlapSet.add(o));
overlapTotals += overlaps.length;
});
var totalReal = cards.length;
function pct(n){ return (n/totalReal*100).toFixed(1)+'%'; }
var diversityScore = 0;
var coreRoles = ['payoff','enabler','support','wildcard'];
var ideal = {payoff:0.4,enabler:0.2,support:0.2,wildcard:0.2};
coreRoles.forEach(r=>{ var actual = roleCounts[r]/Math.max(1,totalReal); diversityScore += (1 - Math.abs(actual - ideal[r])); });
diversityScore = (diversityScore / coreRoles.length * 100).toFixed(1);
var avgOverlap = (overlapTotals / Math.max(1,totalReal)).toFixed(2);
var points = [];
points.push('Roles mix: '+coreRoles.map(r=>r[0].toUpperCase()+r.slice(1)+"="+roleCounts[r]+' ('+pct(roleCounts[r])+')').join(', '));
points.push('Distinct synergy overlaps represented: '+overlapSet.size);
points.push('Average synergy overlaps per card: '+avgOverlap);
points.push('Diversity heuristic score: '+diversityScore);
var curated = roleCounts.example + roleCounts.curated_synergy;
points.push('Curated cards: '+curated+' ('+pct(curated)+')');
// Placeholder future richer analytics (P2 roadmap): spread index, top synergy concentration
var spreadIndex = (overlapSet.size / Math.max(1, (cards.length))).toFixed(2);
points.push('Synergy spread index: '+spreadIndex);
listHost.innerHTML = points.map(p=>'<'+'li>'+p+'</li>').join('');
} catch(e){ /* silent */ }
})();
</script>