mtg_python_deckbuilder/code/web/templates/build/_step2.html

567 lines
29 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.

<section>
{# Step phases removed #}
{% set partner_preview_payload = partner_preview if partner_preview else (combined_commander if combined_commander else None) %}
<div class="two-col two-col-left-rail">
<aside class="card-preview" data-card-name="{{ commander.name }}">
{# Strip synergy annotation for Scryfall search and image fuzzy param #}
{% set commander_base = (commander.name.split(' - Synergy (')[0] if ' - Synergy (' in commander.name else commander.name) %}
<a href="https://scryfall.com/search?q={{ commander_base|urlencode }}" target="_blank" rel="noopener">
<img src="{{ commander_base|card_image('normal') }}" alt="{{ commander.name }} card image" data-card-name="{{ commander_base }}" />
</a>
</aside>
{% if partner_preview_payload %}
{% set partner_secondary_name = partner_preview_payload.secondary_name %}
{% set partner_role_label = partner_preview_payload.secondary_role_label or 'Partner commander' %}
{% set partner_theme_tags = partner_preview_payload.theme_tags if partner_preview_payload.theme_tags else [] %}
{% set partner_tags_joined = partner_theme_tags|join(', ') %}
{% set partner_primary_name = partner_preview_payload.primary_name or commander.name %}
{% set partner_image_url = partner_preview_payload.secondary_image_url or partner_preview_payload.image_url %}
{% if partner_secondary_name %}
{% set partner_name_base = (partner_secondary_name.split(' - Synergy (')[0] if ' - Synergy (' in partner_secondary_name else partner_secondary_name) %}
{% else %}
{% set partner_name_base = partner_secondary_name %}
{% endif %}
{% if not partner_image_url and partner_name_base %}
{% set partner_image_url = partner_name_base|card_image('normal') %}
{% endif %}
{% set partner_href = partner_preview_payload.secondary_scryfall_url or partner_preview_payload.scryfall_url %}
{% if not partner_href and partner_name_base %}
{% set partner_href = 'https://scryfall.com/search?q=' ~ partner_name_base|urlencode %}
{% endif %}
<div class="commander-card partner-card" tabindex="0"
data-card-name="{{ partner_name_base }}"
data-original-name="{{ partner_secondary_name }}"
data-role="{{ partner_role_label }}"
{% if partner_tags_joined %}data-tags="{{ partner_tags_joined }}" data-overlaps="{{ partner_tags_joined }}"{% endif %}>
{% if partner_href %}<a href="{{ partner_href }}" target="_blank" rel="noopener">{% endif %}
{% if partner_name_base %}
<img src="{{ partner_name_base|card_image('normal') }}" alt="{{ (partner_secondary_name or 'Selected card') ~ ' card image' }}"
width="320"
data-card-name="{{ partner_name_base }}"
data-original-name="{{ partner_secondary_name }}"
data-role="{{ partner_role_label }}"
{% if partner_tags_joined %}data-tags="{{ partner_tags_joined }}" data-overlaps="{{ partner_tags_joined }}"{% endif %}
loading="lazy" decoding="async" data-lqip="1"
srcset="{{ partner_name_base|card_image('small') }} 160w, {{ partner_name_base|card_image('normal') }} 488w"
sizes="(max-width: 900px) 100vw, 320px" />
{% else %}
<img src="{{ partner_image_url }}" alt="{{ (partner_secondary_name or 'Selected card') ~ ' card image' }}" loading="lazy" decoding="async" width="320" />
{% endif %}
{% if partner_href %}</a>{% endif %}
</div>
<div class="muted partner-label" style="margin-top:.35rem;">
{{ partner_role_label }}:
<span data-card-name="{{ partner_secondary_name }}"
data-original-name="{{ partner_secondary_name }}"
data-role="{{ partner_role_label }}"
{% if partner_tags_joined %}data-tags="{{ partner_tags_joined }}" data-overlaps="{{ partner_tags_joined }}"{% endif %}>{{ partner_secondary_name }}</span>
</div>
<div class="muted partner-meta" style="font-size:12px; margin-top:.25rem;">
Pairing: {{ partner_primary_name }}{% if partner_secondary_name %} + {{ partner_secondary_name }}{% endif %}
</div>
{% if partner_preview_payload.color_label %}
<div class="muted partner-meta" style="font-size:12px; margin-top:.25rem;">
Colors: {{ partner_preview_payload.color_label }}
</div>
{% endif %}
{% if partner_theme_tags %}
<div class="muted partner-meta" style="font-size:12px; margin-top:.25rem;">
Theme emphasis: {{ partner_theme_tags|join(', ') }}
</div>
{% endif %}
{% endif %}
<div class="grow" data-skeleton>
<div hx-get="/build/banner" hx-trigger="load"></div>
<form hx-post="/build/step2" hx-target="#wizard" hx-swap="innerHTML">
<input type="hidden" name="commander" value="{{ commander.name }}" />
{% if error %}
<div style="color:#a00; margin:.5rem 0;">{{ error }}</div>
{% endif %}
<fieldset>
<legend>Theme Tags</legend>
{% if tags %}
<input type="hidden" name="primary_tag" id="primary_tag" value="{{ primary_tag or '' }}" />
<input type="hidden" name="secondary_tag" id="secondary_tag" value="{{ secondary_tag or '' }}" />
<input type="hidden" name="tertiary_tag" id="tertiary_tag" value="{{ tertiary_tag or '' }}" />
<input type="hidden" name="tag_mode" id="tag_mode" value="{{ tag_mode or 'AND' }}" />
<div class="muted" style="font-size:12px; margin-bottom:.35rem;">Pick up to three themes. Toggle AND/OR to control how themes combine.</div>
<div style="display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; margin-bottom:.35rem;">
<span class="muted" style="font-size:12px;">Combine</span>
<div role="group" aria-label="Combine mode" aria-describedby="combine-help-tip">
<label style="margin-right:.35rem;" title="AND prioritizes cards that match multiple of your themes (tighter synergy, smaller pool).">
<input type="radio" name="combine_mode_radio" value="AND" {% if (tag_mode or 'AND') == 'AND' %}checked{% endif %} /> AND
</label>
<label title="OR treats your themes as a union (broader pool, fills easier).">
<input type="radio" name="combine_mode_radio" value="OR" {% if tag_mode == 'OR' %}checked{% endif %} /> OR
</label>
</div>
<button type="button" id="reset-tags" class="chip" style="margin-left:.35rem;">Reset themes</button>
<span id="tag-count" class="muted" style="font-size:12px;"></span>
</div>
<div id="combine-help-tip" class="muted" style="font-size:12px; margin:-.15rem 0 .5rem 0;">Tip: Choose OR for a stronger initial theme pool; switch to AND to tighten synergy.</div>
<div id="tag-order" class="muted" style="font-size:12px; margin-bottom:.4rem;"></div>
<div id="tag-reco-block" data-has-reco="{% if recommended and recommended|length %}1{% else %}0{% endif %}" {% if not (recommended and recommended|length) %}style="display:none;"{% endif %}>
<div id="tag-reco-header" style="display:flex; align-items:center; gap:.5rem; margin:.25rem 0 .35rem 0;">
<div class="muted" style="font-size:13px; font-weight:600;">⭐ Recommended</div>
<button type="button" id="reco-why" class="chip" aria-expanded="false" aria-controls="reco-why-panel" title="Why these are recommended?" {% if not (recommended and recommended|length) %}style="display:none;"{% endif %}>Why?</button>
</div>
<div id="reco-why-panel" role="group" aria-label="Why Recommended" aria-hidden="true" data-default-reasons='{{ (recommended_reasons or {}) | tojson }}' style="display:none; border:1px solid #e2e2e2; border-radius:8px; padding:.75rem; margin:-.15rem 0 .5rem 0; background:#f7f7f7; box-shadow:0 2px 8px rgba(0,0,0,.06);">
<div class="reco-why-title" style="font-size:12px; color:#222; margin-bottom:.5rem;">Why these themes? <span class="muted" style="color:#555;">Signals from oracle text, color identity, and your local build history.</span></div>
<ul class="reco-why-list" style="margin:.25rem 0; padding-left:1.1rem;">
{% if recommended and recommended|length %}
{% for r in recommended %}
{% set tip = (recommended_reasons[r] if (recommended_reasons is defined and recommended_reasons and recommended_reasons.get(r)) else 'From this commander\'s theme list') %}
<li style="font-size:12px; color:#222; line-height:1.35;"><strong>{{ r }}</strong>: <span style="color:#333;">{{ tip }}</span></li>
{% endfor %}
{% endif %}
</ul>
</div>
<div id="tag-reco-list" aria-label="Recommended themes" data-original-tags='{{ (recommended or []) | tojson }}' style="display:flex; gap:.35rem; flex-wrap:wrap; margin-bottom:.5rem;">
{% if recommended and recommended|length %}
{# R21: Recommended themes (always flat, never sectioned) #}
{% for r in recommended %}
{% set is_sel_r = (r == (primary_tag or '')) or (r == (secondary_tag or '')) or (r == (tertiary_tag or '')) %}
{% set tip = (recommended_reasons[r] if (recommended_reasons is defined and recommended_reasons and recommended_reasons.get(r)) else 'Recommended for this commander') %}
{% set pool_count = pool_size.get(r|slugify, 0) %}
<button type="button" class="chip chip-reco{% if is_sel_r %} active{% endif %}" data-tag="{{ r }}" data-pool-size="{{ pool_count }}" aria-pressed="{% if is_sel_r %}true{% else %}false{% endif %}" title="{{ tip }}">★ {{ r }} <span class="badge badge-pool" title="Pool size: approximately {{ pool_count }} cards available for this theme">{{ pool_count }}</span></button>
{% endfor %}
{% endif %}
<button type="button" id="reco-select-all" class="chip" title="Add recommended up to 3" {% if not (recommended and recommended|length) %}style="display:none;"{% endif %}>Select all</button>
</div>
</div>
{% if recommended and recommended|length %}
<hr style="border:none; border-top:1px solid var(--border); margin:.75rem 0 .5rem 0;" />
<div style="display:flex; align-items:baseline; gap:.4rem; flex-wrap:wrap; margin-bottom:.35rem;">
<div class="muted" style="font-size:13px; font-weight:600;">All Available Themes</div>
<div class="muted" style="font-size:11px;" title="The number on each theme is the approximate number of eligible cards in that theme's pool for your commander's color identity. Sections: Vast (1000+), Large (500999), Moderate (200499), Small (50199), Tiny (&lt;50).">— badge = card pool size</div>
</div>
{% endif %}
<div id="tag-chip-list" aria-label="Available themes" style="display:flex; gap:.35rem; flex-wrap:wrap;">
{% if use_sections and tag_sections %}
{# R21: Sectioned general themes #}
{% for section in tag_sections %}
<div style="width:100%; margin-bottom:.5rem;">
{% set section_tip = {'Vast': '1000+ cards', 'Large': '500999 cards', 'Moderate': '200499 cards', 'Small': '50199 cards', 'Tiny': 'fewer than 50 cards'} %}
<div class="muted" style="font-size:11px; margin-bottom:.25rem;" title="{{ section.label }} pool: themes with approximately {{ section_tip.get(section.label, '') }} available for your commander.">{{ section.label }} Pool ({{ section.themes|length }})</div>
<div style="display:flex; gap:.35rem; flex-wrap:wrap;">
{% for t in section.themes %}
{% set is_sel = (t == (primary_tag or '')) or (t == (secondary_tag or '')) or (t == (tertiary_tag or '')) %}
{% set pool_count = pool_size.get(t|slugify, 0) %}
<button type="button" class="chip{% if is_sel %} active{% endif %}" data-tag="{{ t }}" data-pool-size="{{ pool_count }}" aria-pressed="{% if is_sel %}true{% else %}false{% endif %}" title="Pool size: approximately {{ pool_count }} cards available for this theme">{{ t }} <span class="badge badge-pool">{{ pool_count }}</span></button>
{% endfor %}
</div>
</div>
{% endfor %}
{% else %}
{# R21: Flat sorted general themes #}
{% for t in tags %}
{% set is_sel = (t == (primary_tag or '')) or (t == (secondary_tag or '')) or (t == (tertiary_tag or '')) %}
{% set pool_count = pool_size.get(t|slugify, 0) %}
<button type="button" class="chip{% if is_sel %} active{% endif %}" data-tag="{{ t }}" data-pool-size="{{ pool_count }}" aria-pressed="{% if is_sel %}true{% else %}false{% endif %}" title="Pool size: approximately {{ pool_count }} cards available for this theme">{{ t }} <span class="badge badge-pool">{{ pool_count }}</span></button>
{% endfor %}
{% endif %}
</div>
{% else %}
<p>No theme tags available for this commander.</p>
{% endif %}
</fieldset>
{% set partner_id_prefix = 'step2' %}
{% set partner_scope = 'step2' %}
{% include "build/_partner_controls.html" %}
<fieldset>
<legend>Budget/Power Bracket</legend>
<div style="display:grid; gap:.5rem;">
{% for b in brackets %}
{% if not gc_commander or b.level >= 3 %}
<label style="display:flex; gap:.5rem; align-items:flex-start;">
<input type="radio" name="bracket" value="{{ b.level }}" {% if (selected_bracket is defined and selected_bracket == b.level) or (selected_bracket is not defined and loop.first) %}checked{% endif %} />
<span><strong>{{ b.name }}</strong><small>{{ b.desc }}</small></span>
</label>
{% endif %}
{% endfor %}
</div>
<div class="muted" style="margin-top:.35rem; font-size:.9em;">
Note: This guides deck creation and relaxes/raises constraints, but it is not a guarantee the final deck strictly fits that bracket.
</div>
</fieldset>
<div style="margin-top:1rem;">
<button type="submit" class="btn-continue" data-action="continue">Continue to Ideals</button>
</div>
</form>
<div style="margin-top:.5rem;">
<form hx-post="/build/reset-all" hx-target="#wizard" hx-swap="innerHTML" style="display:inline; margin:0;">
<button type="submit">Start over</button>
</form>
</div>
</div>
</div>
</section>
<script>
(function(){
var chipHost = document.getElementById('tag-chip-list');
var recoBlock = document.getElementById('tag-reco-block');
var recoHost = document.getElementById('tag-reco-list');
var resetBtn = document.getElementById('reset-tags');
var primary = document.getElementById('primary_tag');
var secondary = document.getElementById('secondary_tag');
var tertiary = document.getElementById('tertiary_tag');
var tagMode = document.getElementById('tag_mode');
var countEl = document.getElementById('tag-count');
var orderEl = document.getElementById('tag-order');
var whyBtn = document.getElementById('reco-why');
var whyPanel = document.getElementById('reco-why-panel');
var commander = '{{ commander.name|e }}';
var clearPersisted = '{{ (clear_persisted|default(false)) and "1" or "0" }}' === '1';
if (!chipHost) return;
function escapeHtml(str){
return String(str || "").replace(/[&<>"']/g, function(ch){
return ({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"}[ch]);
});
}
function getSelectAllBtn(){ return document.getElementById('reco-select-all'); }
function getRecoHost(){ return document.getElementById('tag-reco-list'); }
function getRecoBlock(){ return document.getElementById('tag-reco-block'); }
function getWhyBtn(){ return document.getElementById('reco-why'); }
function getWhyPanel(){ return document.getElementById('reco-why-panel'); }
function originalRecommendedTags(){
var host = getRecoHost();
if (!host || !host.dataset.originalTags) return [];
try { var parsed = JSON.parse(host.dataset.originalTags); return Array.isArray(parsed) ? parsed : []; }
catch(_){ return []; }
}
function defaultReasonMap(){
var panel = getWhyPanel();
if (!panel || !panel.getAttribute('data-default-reasons')) return {};
try { var parsed = JSON.parse(panel.getAttribute('data-default-reasons')); return parsed && typeof parsed === 'object' ? parsed : {}; }
catch(_){ return {}; }
}
var previewScope = 'step2';
function storageKey(suffix){ return 'step2-' + (commander || 'unknown') + '-' + suffix; }
function readPartnerPreviewTags(){
if (typeof window === 'undefined') return [];
var store = window.partnerPreviewState;
if (!store) return [];
var state = store[previewScope];
if (!state) return [];
if (Array.isArray(state.theme_tags) && state.theme_tags.length){ return state.theme_tags.slice(); }
var payload = state.payload;
if (payload && Array.isArray(payload.theme_tags)){ return payload.theme_tags.slice(); }
return [];
}
function getSelected(){
var arr = [];
if (primary && primary.value) arr.push(primary.value);
if (secondary && secondary.value) arr.push(secondary.value);
if (tertiary && tertiary.value) arr.push(tertiary.value);
return arr;
}
function setSelected(arr){
arr = Array.from(new Set(arr || [])).filter(Boolean).slice(0,3);
if (primary) primary.value = arr[0] || '';
if (secondary) secondary.value = arr[1] || '';
if (tertiary) tertiary.value = arr[2] || '';
updateCount();
persist();
updateOrderUI();
}
function toggleTag(t){
var cur = getSelected();
var idx = cur.indexOf(t);
if (idx >= 0) { cur.splice(idx, 1); }
else {
if (cur.length >= 3) { cur = cur.slice(1); }
cur.push(t);
}
setSelected(cur);
updateChipsState();
}
function updateCount(){
try { if (countEl) countEl.textContent = getSelected().length + ' / 3 selected'; } catch(_){}
}
function persist(){
try {
localStorage.setItem(storageKey('tags'), JSON.stringify(getSelected()));
if (tagMode) localStorage.setItem(storageKey('mode'), tagMode.value || 'AND');
} catch(_){}
}
function loadPersisted(){
try {
// If this page load follows a fresh commander confirmation, wipe persisted values.
if (clearPersisted){
try {
localStorage.removeItem(storageKey('tags'));
localStorage.removeItem(storageKey('mode'));
} catch(_){ }
}
var savedTags = JSON.parse(localStorage.getItem(storageKey('tags')) || '[]');
var savedMode = localStorage.getItem(storageKey('mode')) || (tagMode && tagMode.value) || 'AND';
if ((!primary.value && !secondary.value && !tertiary.value) && Array.isArray(savedTags) && savedTags.length){ setSelected(savedTags); }
if (tagMode) { tagMode.value = (savedMode === 'OR' ? 'OR' : 'AND'); }
// sync radios
syncModeRadios();
} catch(_){}
}
function syncModeRadios(){
try {
var radios = document.querySelectorAll('input[name="combine_mode_radio"]');
Array.prototype.forEach.call(radios, function(r){ r.checked = (r.value === (tagMode && tagMode.value || 'AND')); });
} catch(_){}
}
function updateChipsState(){
var sel = getSelected();
function applyToContainer(container){
if (!container) return;
var chips = Array.prototype.slice.call(container.querySelectorAll('button.chip'));
chips.forEach(function(btn){
var t = btn.dataset.tag || '';
var active = sel.indexOf(t) >= 0;
btn.classList.toggle('active', active);
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
// update numeric badge for order
var old = btn.querySelector('sup.tag-order');
if (old) { try { old.remove(); } catch(_){} }
if (active){
var idx = sel.indexOf(t);
if (idx >= 0){
var sup = document.createElement('sup');
sup.className = 'tag-order';
sup.style.marginLeft = '.25rem';
sup.style.opacity = '.75';
sup.textContent = String(idx + 1);
btn.appendChild(sup);
}
}
});
}
applyToContainer(chipHost);
applyToContainer(recoHost);
updateCount();
updateOrderUI();
updateSelectAllState();
}
function updateOrderUI(){
if (!orderEl) return;
var sel = getSelected();
if (!sel.length){ orderEl.textContent = ''; return; }
try {
var parts = sel.map(function(t, i){ return (i+1) + '. ' + t; });
orderEl.textContent = 'Selected order: ' + parts.join(' • ');
} catch(_){ orderEl.textContent = ''; }
}
// bind mode radios
Array.prototype.forEach.call(document.querySelectorAll('input[name="combine_mode_radio"]'), function(r){
r.addEventListener('change', function(){ if (tagMode) { tagMode.value = r.value; persist(); } });
});
if (resetBtn) resetBtn.addEventListener('click', function(){ setSelected([]); updateChipsState(); });
chipHost.addEventListener('click', function(e){
var btn = e.target.closest('button.chip');
if (!btn || !chipHost.contains(btn)) return;
var t = btn.dataset.tag || '';
if (!t) return;
toggleTag(t);
});
chipHost.addEventListener('keydown', function(e){
var btn = e.target.closest('button.chip');
if (!btn || !chipHost.contains(btn)) return;
var t = btn.dataset.tag || '';
if (!t) return;
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
toggleTag(t);
} else if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
e.preventDefault();
var chips = Array.prototype.slice.call(chipHost.querySelectorAll('button.chip'));
var ix = chips.indexOf(btn);
if (ix >= 0){
var next = (e.key === 'ArrowRight') ? chips[Math.min(ix+1, chips.length-1)] : chips[Math.max(ix-1, 0)];
if (next && next.focus){
try { next.focus(); } catch(_){ }
}
}
}
});
if (recoHost){
recoHost.addEventListener('click', function(e){
var btn = e.target.closest('button');
if (!btn || !recoHost.contains(btn)) return;
if (btn.id === 'reco-select-all'){
e.preventDefault();
try {
var sel = getSelected();
var recs = Array.prototype.slice.call(recoHost.querySelectorAll('button.chip-reco')).map(function(b){ return b.dataset.tag || ''; }).filter(Boolean);
var combined = sel.slice();
recs.forEach(function(t){ if (combined.indexOf(t) === -1) combined.push(t); });
combined = combined.slice(-3);
setSelected(combined);
updateChipsState();
updateSelectAllState();
} catch(_){ }
return;
}
if (btn.classList.contains('chip-reco')){
var t = btn.dataset.tag || '';
if (t){ toggleTag(t); }
}
});
}
function toggleWhyPanel(open){
if (!whyBtn || !whyPanel) return;
whyBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
whyPanel.style.display = open ? 'block' : 'none';
whyPanel.setAttribute('aria-hidden', open ? 'false' : 'true');
}
if (whyBtn && whyPanel){
whyBtn.addEventListener('click', function(e){
e.stopPropagation();
var isOpen = whyBtn.getAttribute('aria-expanded') === 'true';
toggleWhyPanel(!isOpen);
if (!isOpen){ try { whyPanel.focus && whyPanel.focus(); } catch(_){ } }
});
document.addEventListener('click', function(e){
try {
var isOpen = whyBtn.getAttribute('aria-expanded') === 'true';
if (!isOpen) return;
if (whyPanel.contains(e.target) || whyBtn.contains(e.target)) return;
toggleWhyPanel(false);
} catch(_){ }
});
document.addEventListener('keydown', function(e){
if (e.key === 'Escape'){ toggleWhyPanel(false); }
});
}
function refreshWhyPanel(partnerTags){
var panel = getWhyPanel();
if (!panel) return;
var list = panel.querySelector('.reco-why-list');
if (!list) return;
var reasons = defaultReasonMap();
var base = originalRecommendedTags();
var seen = new Set();
var items = [];
base.forEach(function(tag){
var value = String(tag || '').trim();
if (!value) return;
var key = value.toLowerCase();
if (seen.has(key)) return;
seen.add(key);
var tip = reasons && reasons[value] ? reasons[value] : 'From this commander\'s theme list';
items.push('<li style="font-size:12px; color:#222; line-height:1.35;"><strong>' + escapeHtml(value) + '</strong>: <span style="color:#333;">' + escapeHtml(tip) + '</span></li>');
});
(Array.isArray(partnerTags) ? partnerTags : []).forEach(function(tag){
var value = String(tag || '').trim();
if (!value) return;
var key = value.toLowerCase();
if (seen.has(key)) return;
seen.add(key);
items.push('<li style="font-size:12px; color:#222; line-height:1.35;"><strong>' + escapeHtml(value) + '</strong>: <span style="color:#333;">Synergizes with selected partner pairing</span></li>');
});
list.innerHTML = items.join('');
if (!items.length){
toggleWhyPanel(false);
}
}
function updatePartnerRecommendations(tags){
var host = getRecoHost();
var block = getRecoBlock();
if (!host || !block) return;
var selectAllBtn = getSelectAllBtn();
Array.prototype.slice.call(host.querySelectorAll('button.partner-suggestion')).forEach(function(btn){ btn.remove(); });
var unique = [];
var seen = new Set();
(Array.isArray(tags) ? tags : []).forEach(function(tag){
var value = String(tag || '').trim();
if (!value) return;
var key = value.toLowerCase();
if (seen.has(key)) return;
seen.add(key);
unique.push(value);
});
var insertBefore = selectAllBtn && selectAllBtn.parentElement === host ? selectAllBtn : null;
unique.forEach(function(tag){
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'chip chip-reco partner-suggestion';
btn.dataset.tag = tag;
btn.setAttribute('aria-pressed', getSelected().indexOf(tag) >= 0 ? 'true' : 'false');
btn.title = 'Synergizes with selected partner pairing';
btn.textContent = '★ ' + tag;
if (insertBefore){ host.insertBefore(btn, insertBefore); }
else { host.appendChild(btn); }
});
var hasAny = host.querySelectorAll('button.chip-reco').length > 0;
block.style.display = hasAny ? '' : 'none';
block.setAttribute('data-has-reco', hasAny ? '1' : '0');
var btnEl = getWhyBtn();
if (btnEl){ btnEl.style.display = hasAny ? '' : 'none'; }
if (selectAllBtn){ selectAllBtn.style.display = hasAny ? '' : 'none'; }
refreshWhyPanel(unique);
updateSelectAllState();
updateChipsState();
}
function updateSelectAllState(){
try {
var selAllBtn = getSelectAllBtn();
if (!selAllBtn) return;
var sel = getSelected();
var host = getRecoHost();
var recs = host ? Array.prototype.slice.call(host.querySelectorAll('button.chip-reco')).map(function(b){ return b.dataset.tag || ''; }).filter(Boolean) : [];
var unselected = recs.filter(function(t){ return sel.indexOf(t) === -1; });
var atCap = sel.length >= 3;
var noNew = unselected.length === 0;
var disable = atCap || noNew;
selAllBtn.disabled = disable;
selAllBtn.setAttribute('aria-disabled', disable ? 'true' : 'false');
if (disable){
selAllBtn.title = atCap ? 'Already have 3 themes selected' : 'All recommended already selected';
} else {
selAllBtn.title = 'Add recommended up to 3';
}
} catch(_){ }
}
document.addEventListener('partner:preview', function(evt){
var detail = (evt && evt.detail) || {};
if (detail.scope && detail.scope !== previewScope) return;
var tags = Array.isArray(detail.theme_tags) && detail.theme_tags.length ? detail.theme_tags : [];
if (!tags.length && detail.payload && Array.isArray(detail.payload.theme_tags)){
tags = detail.payload.theme_tags;
}
updatePartnerRecommendations(tags);
});
var initialPartnerTags = readPartnerPreviewTags();
if (initialPartnerTags.length){
updatePartnerRecommendations(initialPartnerTags);
} else {
refreshWhyPanel([]);
}
// initial: set from template-selected values, then maybe load persisted if none
updateChipsState();
loadPersisted();
updateChipsState();
})();
</script>