/* Slot misteri yang hidup (RENCANA 3.7). Lima petak 'locked-N' di layar pilih karakter (menu.js) menjadi sistem petunjuk bertahap.
Data: content/mystery.js (window.MYSTERY). Kemajuan dibaca langsung dari Meta.save. Gaya: mystery.css. Tanpa gambar baru:
siluet digambar sebagai SVG sebaris (bentuk tubuh disalin dari assets/menu/locked.svg), makin tajam tiap petunjuk terbuka,
dan sebuah benda khas muncul samar lalu jelas di dua tahap terakhir.
menu.js memanggil: Mystery.panel (panel pratinjau slot yang disorot), decorate (petak roster, tiap selesai render), preview
(teks di bawah roster saat sorotan berpindah) dan notice (pesan saat slot terkunci ditekan). Slot tetap tidak bisa dipilih.
Fungsi publik tidak pernah melempar galat; bila gagal, layar kembali ke tampilan slot terkunci yang lama.
Semua petunjuk terbuka = pencapaian PENASARAN BERAT (meta.js). ?mystery=N (atau all) di URL hanya mempratinjau tahap N pada
semua slot, tanpa menyimpan apa pun dan tanpa membuka pencapaian. Petunjuk yang sudah dilihat diingat di
localStorage['konoha.mystery.seen'] (hanya untuk titik BARU dan toast; bukan bagian Meta.save). */
(() => {
'use strict';
const SEEN_KEY = 'konoha.mystery.seen', ACH_ALL = 'penasaran_berat', FALLBACK_COLOR = '#7fe0ff';
const PREVIEW = (() => { try { const v = new URLSearchParams(location.search).get('mystery'); if (v === 'all') return 99; const n = v === null || v === '' ? -1 : parseInt(v, 10); return n >= 0 ? n : null; } catch (_) { return null; } })();
const num = v => (typeof v === 'number' && isFinite(v) ? v : 0);
const len = a => (Array.isArray(a) ? a.length : 0);
const count = (o, ok) => (o && typeof o === 'object' ? Object.keys(o).filter(k => o[k] && ok(k)).length : 0);
const esc = t => String(t).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
const save = () => window.Meta?.save || {};
const enabled = () => Array.isArray(window.MYSTERY) && window.MYSTERY.length > 0;
const slotIndex = id => { const m = /^locked-(\d+)$/.exec(id || ''); return m ? +m[1] : -1; };
// Syarat yang dikenali data (need.type). read = nilai sekarang dari Meta.save; brief = tujuan satu kalimat (l = sisa, n = target);
// hint = cara mencapainya (opsional); avail = false bila fitur pendukungnya tidak ada (tahap itu dilewati).
const GOALS = {
wins: { read: s => num(s.stats?.wins), brief: l => `Menang ${l} kali lagi` },
matches: { read: s => num(s.stats?.wins) + num(s.stats?.losses), brief: l => `Main ${l} pertandingan lagi` },
perfects: { read: s => num(s.stats?.perfects), brief: l => `Menang bersih ${l} kali lagi`, hint: 'Menang tanpa kehilangan satu ronde pun.' },
combo: { read: s => num(s.stats?.bestCombo), brief: (l, n) => `Capai kombo ${n} pukulan` },
survival: { read: s => num(s.best?.survival), brief: (l, n) => `Menang ${n}x beruntun (Survival)`, hint: 'Yang dihitung rekor beruntun terbaikmu di Survival.' },
arcade: { read: s => (s.ach?.arcade_clear ? 1 : 0), brief: () => 'Tamatkan mode Arcade' },
ach: { read: s => count(s.ach, k => k !== ACH_ALL), brief: l => `Buka ${l} pencapaian lagi` },
eggs: { read: s => count(s.eggs, k => !/^(code|dialog):/.test(k)), brief: l => `Temukan ${l} kejutan lagi`, hint: 'Coba ketik, ketuk, atau klik sesuatu yang tidak biasa.' },
pairs: { read: s => count(s.eggs, k => k.startsWith('dialog:')), brief: l => `Temukan ${l} dialog rahasia lagi`, hint: 'Pertemukan pasangan lawan tertentu di mode Versus.', avail: () => Object.keys(window.DIALOGS?.rahasia || {}).length > 0 },
shop: { read: s => Math.max(0, len(s.owned?.skin) - 1) + len(s.owned?.move) + len(s.owned?.alt), brief: l => `Beli ${l} barang toko lagi`, hint: 'Skin, jurus toko, atau varian skill.' },
coins: { read: s => num(s.earned), brief: l => `Kumpulkan ${l} koin lagi` }
};
const need = st => Math.max(1, Math.floor(num(st.need.n)));
const hex = c => (/^#[0-9a-f]{6}$/i.test(c || '') ? c : FALLBACK_COLOR);
// Keadaan satu slot: stage = jumlah petunjuk terbuka (berurutan), next = syarat petunjuk berikutnya (null bila semua terbuka).
function compute(i, s) {
const d = window.MYSTERY[i];
if (!d || !Array.isArray(d.stages)) return null;
const stages = d.stages.filter(st => st && st.need && GOALS[st.need.type] && (!GOALS[st.need.type].avail || GOALS[st.need.type].avail()));
if (!stages.length) return null;
let open = 0;
while (open < stages.length && GOALS[stages[open].need.type].read(s) >= need(stages[open])) open++;
if (PREVIEW !== null) open = Math.min(PREVIEW, stages.length);
const nx = stages[open], g = nx && GOALS[nx.need.type], n = nx ? need(nx) : 0, cur = nx ? Math.min(n, g.read(s)) : 0;
return { i, id: String(d.id || 'm' + i), color: hex(d.color), shape: d.shape, max: stages.length, stage: open, clues: stages.slice(0, open),
next: nx ? { n, cur, brief: g.brief(n - cur, n, cur), hint: g.hint || '', pct: Math.round(cur / n * 100) } : null };
}
// Petak yang punya data saja; locked = jumlah petak terkunci di roster (menu.js) supaya "N dari M" sesuai yang tampil.
function stateOf(id, locked) {
const i = slotIndex(id), cap = Math.min(locked == null ? Infinity : locked, enabled() ? window.MYSTERY.length : 0);
if (i < 0 || i >= cap) return null;
const st = compute(i, save());
if (st) { st.n = i + 1; st.total = cap; }
return st;
}
function states(locked) {
const cap = Math.min(locked == null ? Infinity : locked, enabled() ? window.MYSTERY.length : 0), s = save();
return Array.from({ length: cap }, (_, i) => { const st = compute(i, s); if (st) { st.n = i + 1; st.total = cap; } return st; });
}
// ---- siluet: tubuh dari locked.svg + satu benda per slot (muncul samar di tahap kedua terakhir, jelas di tahap akhir) ----
const BODY = 'm50 58 9-27 18-12 20 8 17 24-6 7 2 24-12 15-1 13 28 12 12 13 13 45H10l13-45 12-13 28-12-1-13-12-15-2-24Z';
const RING = 'fill="none" stroke="currentColor" stroke-width="3.2"';
const SHAPES = {
palu: '';
const dots = Array.from({ length: st.max }, (_, k) => ``).join('');
const clues = st.clues.length ? st.clues.map(c => `