mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-16 07:30:13 +01:00
431 lines
No EOL
27 KiB
HTML
431 lines
No EOL
27 KiB
HTML
{% 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> |