Files
escape-builder/exemplu-campanie.html
Claude Agent a4b0ff4154 Campanie multi-stil — PR1 (T1-T8 + TD1-TD6)
Adauga al 6-lea stil de joc: campanie multi-stil care leaga puzzle-urile
in camere de stiluri diferite (clasic/terminal/arcade/chat/point in rotatie),
conectate prin coridoare cu usa, litera si stele.

Contract de montare (verificat la gate T1):
- gameCampaign: un <iframe srcdoc> per camera; camerele cheama parent.*
  pe un nivel (merge si pe file://); template per stil cu sentinel __CFG__
  injectat prin replace-functie (D1) + json.replace(/</g,'<') (D6)
- roomReady/roomError + timeout 4s -> skip cu 0 stele + cod eroare;
  idx detinut de parinte, accepta nextRoom doar de la contentWindow activ (D5)
- parent.beep in mod campanie (un singur AudioContext, D2)
- resume prin safeStore try/catch (D3) + cheie djb2 peste CFG embedat (D11)

Builder:
- selector de stil per puzzle ("Auto (stil)") + optiunea Campanie multi-stil
- normalizePuzzle() la load + import (sursa unica pt forma puzzle, D8)
- blocare export+preview la 0 puzzle-uri; persist() guarded (D12)
- letter normalizat [A-Za-z0-9] + esc la SVG point (D13)

Design (DESIGN.md): tokens --c-*, intro poster, coridor "usa ca erou",
chrome unica sursa de progres, 5 usi CSS/SVG (normal/stuck/crescendo),
mod camera per motor, buget vertical mobil, baseline a11y.

Tooling: tests/smoke.mjs (Playwright, zero-dependente prin npx), TODOS.md,
sectiune ## Testing in CLAUDE.md. Demo-uri regenerate + exemplu-campanie.html.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 08:34:57 +00:00

527 lines
63 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.

<!doctype html>
<html lang="ro">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Comoara ascunsa</title>
<style>
/*
* ASCII DIAGRAM — contractul parent.*:
* ┌──────────── orchestrator (window) ─────────────────────────┐
* │ nextRoom({idx,stars,letter}) ← camera.parent.nextRoom │
* │ roomReady(idx) ← camera.parent.roomReady │
* │ roomError(idx,msg) ← camera.parent.roomError │
* │ beep(ok) ← camera.parent.beep │
* └────────────────────────────────────────────────────────────┘
*/
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; overflow: hidden; }
body {
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
background: var(--c-bg, #0d0620); color: var(--c-ink, #fff);
display: flex; flex-direction: column;
--c-bg: #0d0620; --c-surface: #221440; --c-line: rgba(255,255,255,.18);
--c-ink: #fff; --c-gold: #fbbf24;
}
#chrome {
height: 48px; min-height: 48px; flex-shrink: 0;
background: #1a0e3a; border-bottom: 1px solid rgba(255,255,255,.15);
display: flex; align-items: center; padding: 0 16px; gap: 12px;
}
#chrome-title { font-size: 15px; font-weight: 700; }
#chrome .sp { flex: 1; }
#dots { display: flex; gap: 8px; }
#dots span {
width: 10px; height: 10px; border-radius: 50%;
background: rgba(255,255,255,.2); transition: background .3s; display: inline-block;
}
#dots span.active { background: #a78bfa; }
#dots span.done { background: var(--c-gold); }
#room-wrap { flex: 1; position: relative; min-height: 0; }
#room-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* Overlay-uri */
.overlay {
display: none; position: absolute; inset: 0;
flex-direction: column; align-items: center; justify-content: center;
gap: 18px; padding: 24px; text-align: center; overflow-y: auto;
}
.overlay.show { display: flex; }
/* Intro */
#intro { background: var(--c-bg); }
#intro h1 { margin: 0; font-size: clamp(22px,5vw,36px); font-weight: 900; }
#intro .story-text { color: rgba(255,255,255,.8); max-width: 56ch; line-height: 1.6; }
#intro .promise { color: rgba(255,255,255,.5); font-size: 14px; }
/* Coridor */
#corridor { background: var(--c-bg); }
#corr-reward { display: flex; align-items: center; gap: 16px; }
#corr-stars { font-size: 26px; letter-spacing: 3px; color: var(--c-gold); }
#corr-letter { font-size: 56px; font-weight: 900; color: var(--c-gold); line-height: 1; }
#corr-label { color: rgba(255,255,255,.6); font-size: 13px; }
#corr-next { color: rgba(255,255,255,.75); font-size: 15px; font-weight: 600; }
#corr-door { display: flex; align-items: center; justify-content: center; flex: 1; min-height: 0; padding: 8px 0; }
/* Skip */
#skip-banner { background: var(--c-bg); }
/* ===== UȘILE — 5 stiluri × 3 stări ===== */
:root { --c-gold: #fbbf24; }
/* Common */
.door-lock {
position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%);
background: rgba(0,0,0,.65); border-radius: 6px; padding: 2px 7px;
font-size: 13px; line-height: 1.4; z-index: 2; pointer-events: none;
}
@keyframes door-open {
0% { transform: scale(1) rotateY(0deg); opacity: 1; }
50% { transform: scale(1.06) rotateY(-30deg); opacity: .8; }
100% { transform: scale(.85) rotateY(-90deg); opacity: 0; }
}
.opening { animation: door-open .25s cubic-bezier(.4,0,1,1) forwards; transform-origin: left center; perspective: 600px; }
@media (prefers-reduced-motion: reduce) { .opening { animation: none; opacity: 0; } }
/* Classic */
.door-classic {
width: 88px; height: 124px; position: relative;
background: #fff; border-radius: 10px; border: 1.5px solid rgba(0,0,0,.06);
box-shadow: 0 8px 32px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.9);
display: flex; align-items: center; justify-content: center;
transition: transform .25s cubic-bezier(.22,1,.36,1), box-shadow .25s, filter .25s;
}
.door-classic .dq { font-size: 50px; font-weight: 900; line-height: 1; color: var(--c-gold); text-shadow: 0 2px 14px rgba(251,191,36,.6); user-select: none; }
.door-classic::after { content: ''; position: absolute; right: 12px; top: 50%; width: 8px; height: 8px; border-radius: 50%; background: rgba(0,0,0,.22); margin-top: -4px; }
.door-classic.stuck { filter: grayscale(1) brightness(.6); }
.door-classic.crescendo { transform: scale(1.35); box-shadow: 0 0 0 3px var(--c-gold), 0 14px 48px rgba(251,191,36,.35), 0 8px 32px rgba(0,0,0,.55); }
/* Terminal */
.door-terminal {
width: 88px; height: 124px; position: relative;
background: #000; border: 2px solid #39ff6e; overflow: hidden;
box-shadow: 0 0 16px rgba(57,255,110,.4), inset 0 0 12px rgba(57,255,110,.07);
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
transition: transform .25s cubic-bezier(.22,1,.36,1), box-shadow .25s, filter .25s;
}
.door-terminal::before { content: ''; position: absolute; inset: 0; pointer-events: none; background: repeating-linear-gradient(0deg, rgba(0,0,0,.32) 0 1px, transparent 1px 3px); }
.door-terminal .dt-txt { font: 700 10px/1.2 "Courier New", monospace; letter-spacing: .12em; color: #39ff6e; text-shadow: 0 0 8px rgba(57,255,110,.75); z-index: 1; }
.door-terminal .dt-cur { font: 16px/1 "Courier New", monospace; color: #39ff6e; text-shadow: 0 0 8px rgba(57,255,110,.75); animation: dt-blink 1s step-end infinite; z-index: 1; }
@keyframes dt-blink { 0%,100% { opacity: 1; } 50% { opacity: 0; } }
.door-terminal.stuck { filter: grayscale(1) brightness(.55); border-color: #444; box-shadow: none; }
.door-terminal.stuck .dt-cur { animation: none; opacity: 0; }
.door-terminal.crescendo { transform: scale(1.35); box-shadow: 0 0 0 2px #39ff6e, 0 0 32px rgba(57,255,110,.7), inset 0 0 18px rgba(57,255,110,.14); }
/* Arcade */
.door-arcade {
width: 88px; height: 124px; position: relative;
background: #18102e; border: 4px solid #4ade80;
box-shadow: inset 0 0 0 4px #166534, 0 0 0 4px #0a1606, 0 8px 24px rgba(0,0,0,.7);
display: flex; align-items: center; justify-content: center; image-rendering: pixelated;
transition: transform .25s cubic-bezier(.22,1,.36,1), box-shadow .25s, filter .25s;
}
.door-arcade::after { content: ''; position: absolute; right: 12px; top: 50%; margin-top: -5px; width: 10px; height: 10px; background: var(--c-gold); box-shadow: inset -2px -2px 0 #b45309, inset 1px 1px 0 #fde68a; }
.door-arcade .da-sprite { font: 900 24px/1 ui-monospace, monospace; color: #4ade80; text-shadow: 0 0 10px rgba(74,222,128,.55); user-select: none; }
.door-arcade.stuck { filter: grayscale(1) brightness(.55); box-shadow: none; }
.door-arcade.crescendo { transform: scale(1.35); box-shadow: inset 0 0 0 4px #166534, 0 0 0 4px #0a1606, 0 0 0 8px #4ade80, 0 0 28px rgba(74,222,128,.5), 0 8px 24px rgba(0,0,0,.7); }
/* Chat */
.door-chat {
width: 72px; height: 124px; position: relative;
background: linear-gradient(165deg, #1d4ed8 0%, #1e3a8a 100%);
border-radius: 12px; border: 1.5px solid rgba(255,255,255,.14);
box-shadow: 0 8px 28px rgba(29,78,216,.5), inset 0 1px 0 rgba(255,255,255,.2);
display: flex; flex-direction: column; align-items: center; padding: 8px 6px 10px; gap: 5px;
transition: transform .25s cubic-bezier(.22,1,.36,1), box-shadow .25s, filter .25s;
}
.door-chat .dc-notch { width: 18px; height: 4px; background: rgba(0,0,0,.4); border-radius: 2px; flex-shrink: 0; }
.door-chat .dc-screen { flex: 1; width: 100%; background: #0f172a; border-radius: 7px; padding: 7px 5px; display: flex; flex-direction: column; gap: 5px; overflow: hidden; }
.door-chat .dc-bub { border-radius: 8px; padding: 4px 7px; font-size: 9px; color: rgba(255,255,255,.85); line-height: 1.2; max-width: 80%; }
.door-chat .dc-npc { background: #1e40af; align-self: flex-start; }
.door-chat .dc-me { background: #3b82f6; align-self: flex-end; }
.door-chat .dc-home { width: 22px; height: 3px; background: rgba(255,255,255,.3); border-radius: 2px; flex-shrink: 0; }
.door-chat.stuck { filter: grayscale(1) brightness(.55); }
.door-chat.crescendo { transform: scale(1.35); box-shadow: 0 0 0 2px #3b82f6, 0 12px 48px rgba(29,78,216,.7), inset 0 1px 0 rgba(255,255,255,.25); }
/* Point */
.door-point { width: 88px; height: 124px; position: relative; display: flex; align-items: center; justify-content: center; transition: transform .25s cubic-bezier(.22,1,.36,1), filter .25s; }
.door-point svg { width: 88px; height: 124px; display: block; }
.door-point.stuck { filter: grayscale(1) brightness(.6); }
.door-point.crescendo { transform: scale(1.35); filter: drop-shadow(0 0 12px rgba(243,207,109,.6)) drop-shadow(0 0 32px rgba(138,90,43,.35)); }
#skip-banner h2 { margin: 0; font-size: 22px; }
#skip-code { font-family: ui-monospace, monospace; font-size: 12px; color: rgba(255,255,255,.35); margin-top: 4px; }
/* Final */
#finale { background: var(--c-bg); }
#finale h1 { margin: 0; font-size: 28px; }
#fin-stars { font-size: 26px; color: var(--c-gold); letter-spacing: 4px; }
#fin-word { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
#fin-word span {
width: 44px; height: 52px; background: var(--accent); border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 26px; font-weight: 800; animation: flipin .6s ease backwards;
}
@keyframes flipin { from { transform: rotateX(90deg); } to { transform: rotateX(0); } }
#fin-msg { color: rgba(255,255,255,.8); max-width: 56ch; }
/* Butoane */
.btn-main {
font: inherit; font-size: 16px; font-weight: 700;
background: var(--accent); color: #fff; border: none;
border-radius: 12px; padding: 14px 28px; cursor: pointer;
min-height: 44px; width: 100%; max-width: 320px;
}
.btn-main:hover { filter: brightness(1.1); }
.btn-main:disabled { opacity: .5; cursor: not-allowed; }
.btn-skip {
font: inherit; font-size: 15px; font-weight: 700;
background: #4b5563; color: #fff; border: none;
border-radius: 12px; padding: 12px 24px; cursor: pointer; min-height: 44px;
}
.confetti { position: fixed; top: -12px; width: 9px; height: 14px; z-index: 99; animation: fall linear forwards; }
@keyframes fall { to { transform: translateY(105vh) rotate(720deg); } }
@media (max-width: 599px) {
#chrome { height: 40px; min-height: 40px; }
#chrome-title { font-size: 13px; }
#dots span { width: 8px; height: 8px; }
}
</style>
</head>
<body>
<div id="chrome">
<span id="chrome-title">Comoara ascunsa</span>
<div class="sp"></div>
<div id="dots"></div>
</div>
<div id="room-wrap">
<iframe id="room-frame" data-room title="Camera curentă"></iframe>
<div id="intro" class="overlay show">
<h1 id="intro-title"></h1>
<p class="story-text" id="intro-story"></p>
<p class="promise" id="intro-promise"></p>
<button class="btn-main" id="btn-start">Începe aventura</button>
</div>
<div id="corridor" class="overlay">
<div id="corr-reward">
<div>
<div id="corr-label">Litera câștigată</div>
<div id="corr-letter"></div>
</div>
<div id="corr-stars"></div>
</div>
<div id="corr-door"></div>
<div id="corr-next"></div>
<button class="btn-main" id="btn-next">Deschide ușa →</button>
</div>
<div id="skip-banner" class="overlay">
<h2>⚠️ Ușa asta e înțepenită!</h2>
<div id="skip-door"></div>
<p>Camera nu a răspuns. Poți sări la cea următoare.</p>
<div class="skip-code" id="skip-code"></div>
<button class="btn-skip" id="btn-skip">Sari la camera următoare</button>
</div>
<div id="finale" class="overlay" data-final>
<h1>🏆 Evadare reușită!</h1>
<div class="fstars" id="fin-stars"></div>
<p>Cuvântul magic:</p>
<div id="fin-word"></div>
<p id="fin-msg"></p>
</div>
</div>
<script>
/*
* ASCII DIAGRAM — contractul parent.*:
* ┌──────────── orchestrator (window) ─────────────────────────┐
* │ nextRoom({idx,stars,letter}) ← camera.parent.nextRoom │
* │ roomReady(idx) ← camera.parent.roomReady │
* │ roomError(idx,msg) ← camera.parent.roomError │
* │ beep(ok) ← camera.parent.beep │
* └────────────────────────────────────────────────────────────┘
* Idempotență: nextRoom/roomError acceptate doar de la activeWindow.
* roomDone[idx]=true după primul nextRoom → duplicatele ignorate.
* Timeout 4s → skipRoom → aceeași compoziție de coridor (D5).
*/
var TPL = {"classic":"\u003c!doctype html>\n\u003chtml lang=\"ro\">\n\u003chead>\n\u003cmeta charset=\"utf-8\">\n\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\u003ctitle>\u003c/title>\n\u003cstyle>\n * { box-sizing: border-box; }\n body {\n margin: 0; min-height: 100vh; font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif;\n color: #fff; display: flex; align-items: center; justify-content: center; padding: 16px;\n background: linear-gradient(160deg, #14092e 0%, #2a1257 55%, #14092e 100%);\n }\n .card {\n width: 100%; max-width: 560px; background: rgba(255,255,255,.07);\n border: 1px solid rgba(255,255,255,.14); border-radius: 18px; padding: 26px;\n backdrop-filter: blur(6px); box-shadow: 0 18px 50px rgba(0,0,0,.45);\n }\n h1 { margin: 0 0 6px; font-size: 26px; text-align: center; }\n .story { color: rgba(255,255,255,.8); text-align: center; line-height: 1.5; }\n .screen { display: none; }\n .screen.on { display: block; animation: pop .35s ease; }\n @keyframes pop { from { transform: scale(.96); opacity: 0; } to { transform: scale(1); opacity: 1; } }\n .progress { height: 7px; background: rgba(255,255,255,.15); border-radius: 99px; overflow: hidden; margin: 14px 0 4px; }\n .progress i { display: block; height: 100%; background: var(--accent); width: 0; transition: width .4s ease; }\n .meta { display: flex; justify-content: space-between; font-size: 12px; color: rgba(255,255,255,.6); margin-bottom: 14px; }\n .letters { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; margin: 14px 0; }\n .tile {\n width: 34px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center;\n font-weight: 800; font-size: 18px; background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.18);\n color: rgba(255,255,255,.35);\n }\n .tile.won { background: var(--accent); color: #fff; border-color: transparent; animation: flip .5s ease; }\n @keyframes flip { from { transform: rotateX(90deg); } to { transform: rotateX(0); } }\n .qtitle { font-size: 13px; text-transform: uppercase; letter-spacing: .08em; color: var(--accent-light); font-weight: 700; }\n .question { font-size: 19px; line-height: 1.45; margin: 8px 0 18px; }\n input[type=text] {\n width: 100%; font: inherit; font-size: 18px; padding: 11px 13px; border-radius: 10px;\n border: 1px solid rgba(255,255,255,.25); background: rgba(0,0,0,.25); color: #fff; text-align: center;\n }\n input:focus { outline: 2px solid var(--accent); border-color: transparent; }\n button {\n font: inherit; cursor: pointer; border: none; border-radius: 10px; padding: 12px 18px;\n font-weight: 700; background: var(--accent); color: #fff; width: 100%; margin-top: 10px;\n }\n button:hover { filter: brightness(1.12); }\n button.opt { background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.2); font-weight: 600; text-align: left; }\n button.opt:hover { background: rgba(255,255,255,.18); }\n button.hint { background: none; border: none; color: rgba(255,255,255,.55); font-weight: 600; font-size: 13px; width: auto; display: block; margin: 12px auto 0; }\n button.hint:hover { color: #fff; }\n .hinttext { background: rgba(255,255,255,.1); border-radius: 9px; padding: 10px 12px; font-size: 14px; margin-top: 10px; white-space: pre-line; display: none; }\n .feedback { min-height: 22px; text-align: center; font-weight: 700; margin-top: 10px; }\n .feedback.bad { color: #fda4af; }\n .feedback.good { color: #86efac; }\n .shake { animation: shake .4s ease; }\n @keyframes shake { 20%,60% { transform: translateX(-8px); } 40%,80% { transform: translateX(8px); } }\n .stars { text-align: center; font-size: 26px; letter-spacing: 4px; color: #fbbf24; margin: 6px 0; }\n .bigword { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin: 18px 0; }\n .bigword span {\n width: 44px; height: 52px; border-radius: 10px; background: var(--accent); display: flex;\n align-items: center; justify-content: center; font-size: 26px; font-weight: 800; animation: flip .6s ease backwards;\n }\n .confetti { position: fixed; top: -12px; width: 9px; height: 14px; z-index: 5; animation: fall linear forwards; }\n @keyframes fall { to { transform: translateY(105vh) rotate(720deg); } }\n\u003c/style>\n\u003c/head>\n\u003cbody>\n\u003cdiv class=\"card\">\n \u003cdiv id=\"sStart\" class=\"screen on\">\n \u003ch1 id=\"gtitle\">\u003c/h1>\n \u003cp class=\"story\" id=\"gstory\">\u003c/p>\n \u003cbutton id=\"btnStart\">Incepe aventura\u003c/button>\n \u003c/div>\n\n \u003cdiv id=\"sGame\" class=\"screen\">\n \u003cdiv class=\"progress\">\u003ci id=\"bar\">\u003c/i>\u003c/div>\n \u003cdiv class=\"meta\">\u003cspan id=\"step\">\u003c/span>\u003cspan id=\"score\">\u003c/span>\u003c/div>\n \u003cdiv class=\"letters\" id=\"lettersBar\">\u003c/div>\n \u003cdiv id=\"qbox\">\n \u003cdiv class=\"qtitle\" id=\"qtitle\">\u003c/div>\n \u003cdiv class=\"question\" id=\"qtext\">\u003c/div>\n \u003cdiv id=\"answers\">\u003c/div>\n \u003cdiv class=\"feedback\" id=\"feedback\">\u003c/div>\n \u003cbutton class=\"hint\" id=\"btnHint\">Vreau un indiciu\u003c/button>\n \u003cdiv class=\"hinttext\" id=\"hinttext\">\u003c/div>\n \u003c/div>\n \u003c/div>\n\n \u003cdiv id=\"sFinal\" class=\"screen\">\n \u003ch1>Evadare reusita!\u003c/h1>\n \u003cdiv class=\"stars\" id=\"finalStars\">\u003c/div>\n \u003cdiv class=\"bigword\" id=\"bigword\">\u003c/div>\n \u003cp class=\"story\" id=\"finalMsg\">\u003c/p>\n \u003cbutton id=\"btnAgain\">Joaca din nou\u003c/button>\n \u003c/div>\n\u003c/div>\n\n\u003cscript>\nvar CFG = __CFG__;\ndocument.documentElement.style.setProperty('--accent', CFG.color || '#6d28d9');\ndocument.documentElement.style.setProperty('--accent-light', 'color-mix(in srgb, ' + (CFG.color || '#6d28d9') + ' 40%, white)');\n\nvar idx = 0, totalStars = 0, attempts = 0, hintUsed = false, won = [];\n\nfunction el(id) { return document.getElementById(id); }\nfunction norm(s) {\n return String(s).trim().toLowerCase().normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '').replace(/\\s+/g, ' ').replace(/,/g, '.');\n}\nfunction show(id) {\n var scr = document.querySelectorAll('.screen');\n for (var i = 0; i \u003c scr.length; i++) scr[i].classList.remove('on');\n el(id).classList.add('on');\n}\n\nel('gtitle').textContent = CFG.title;\nvar hello = CFG.player ? 'Salut, ' + CFG.player + '! ' : '';\nel('gstory').textContent = hello + CFG.story;\n\nel('btnStart').onclick = function () { show('sGame'); renderPuzzle(); };\nel('btnAgain').onclick = function () { location.reload(); };\n\nfunction lettersBar() {\n var bar = el('lettersBar');\n bar.innerHTML = '';\n var any = false;\n for (var i = 0; i \u003c CFG.puzzles.length; i++) {\n var L = (CFG.puzzles[i].letter || '').trim();\n if (!L) continue;\n any = true;\n var d = document.createElement('div');\n d.className = 'tile' + (won[i] ? ' won' : '');\n d.textContent = won[i] ? L.toUpperCase() : '?';\n bar.appendChild(d);\n }\n bar.style.display = any ? '' : 'none';\n}\n\nfunction renderPuzzle() {\n var p = CFG.puzzles[idx];\n attempts = 0; hintUsed = false;\n el('bar').style.width = (idx / CFG.puzzles.length * 100) + '%';\n el('step').textContent = 'Puzzle ' + (idx + 1) + ' din ' + CFG.puzzles.length;\n el('score').textContent = totalStars + ' \\u2605';\n el('qtitle').textContent = p.title || 'Puzzle ' + (idx + 1);\n el('qtext').textContent = p.question;\n el('feedback').textContent = ''; el('feedback').className = 'feedback';\n el('hinttext').style.display = 'none';\n el('hinttext').textContent = p.hint || '';\n el('btnHint').style.display = p.hint ? '' : 'none';\n lettersBar();\n\n var box = el('answers');\n box.innerHTML = '';\n if (p.type === 'free') {\n var inp = document.createElement('input');\n inp.type = 'text'; inp.autocomplete = 'off'; inp.placeholder = 'Scrie raspunsul...';\n var btn = document.createElement('button');\n btn.textContent = 'Verifica';\n btn.onclick = function () { check(inp.value, p.answer); };\n inp.onkeydown = function (e) { if (e.key === 'Enter') btn.click(); };\n box.appendChild(inp); box.appendChild(btn);\n setTimeout(function () { inp.focus(); }, 50);\n } else if (p.type === 'tf') {\n ['Adevarat', 'Fals'].forEach(function (v) {\n var b = document.createElement('button');\n b.className = 'opt'; b.textContent = v;\n b.onclick = function () { check(v, p.tfAnswer); };\n box.appendChild(b);\n });\n } else {\n var correct = '';\n var opts = (p.choices || '').split('\\n').map(function (l) { return l.trim(); }).filter(Boolean);\n opts.forEach(function (o) { if (o.charAt(0) === '*') correct = o.slice(1).trim(); });\n opts.map(function (o) { return o.charAt(0) === '*' ? o.slice(1).trim() : o; })\n .forEach(function (o) {\n var b = document.createElement('button');\n b.className = 'opt'; b.textContent = o;\n b.onclick = function () { check(o, correct); };\n box.appendChild(b);\n });\n if (!opts.length) box.textContent = '(puzzle fara variante - completeaza-le in builder)';\n }\n}\n\nel('btnHint').onclick = function () {\n hintUsed = true;\n el('hinttext').style.display = 'block';\n};\n\nfunction check(given, expected) {\n if (norm(given) === norm(expected) && norm(given) !== '') {\n var stars = (hintUsed || attempts >= 2) ? 1 : (attempts === 1 ? 2 : 3);\n totalStars += stars;\n won[idx] = true;\n beep(true);\n var f = el('feedback');\n f.textContent = 'Corect! +' + stars + ' \\u2605';\n f.className = 'feedback good';\n lettersBar();\n el('bar').style.width = ((idx + 1) / CFG.puzzles.length * 100) + '%';\n setTimeout(next, 900);\n } else {\n attempts++;\n beep(false);\n var fb = el('feedback');\n fb.textContent = 'Nu e bine, mai incearca!';\n fb.className = 'feedback bad';\n var card = document.querySelector('.card');\n card.classList.remove('shake');\n void card.offsetWidth;\n card.classList.add('shake');\n }\n}\n\nfunction next() {\n idx++;\n if (idx \u003c CFG.puzzles.length) { renderPuzzle(); return; }\n if(CFG._campaign){\n var L = ''; for(var ci=0;ci\u003cCFG.puzzles.length;ci++){var lc=(CFG.puzzles[ci].letter||'').trim();if(lc)L+=lc.toUpperCase();}\n try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:L.charAt(0)}); }catch(e){}\n return;\n }\n show('sFinal');\n var max = CFG.puzzles.length * 3;\n el('finalStars').textContent = totalStars + ' / ' + max + ' \\u2605';\n var word = '';\n for (var i = 0; i \u003c CFG.puzzles.length; i++) {\n var L = (CFG.puzzles[i].letter || '').trim();\n if (L) word += L.toUpperCase();\n }\n var bw = el('bigword');\n bw.innerHTML = '';\n for (var j = 0; j \u003c word.length; j++) {\n var s = document.createElement('span');\n s.textContent = word.charAt(j);\n s.style.animationDelay = (j * 0.18) + 's';\n bw.appendChild(s);\n }\n var name = CFG.player ? CFG.player + ', ' : '';\n el('finalMsg').textContent = name ? name + (CFG.finalMessage || '').charAt(0).toLowerCase() + (CFG.finalMessage || '').slice(1) : (CFG.finalMessage || '');\n confetti();\n}\n\nfunction confetti() {\n var colors = [CFG.color || '#6d28d9', '#fbbf24', '#34d399', '#60a5fa', '#f472b6'];\n for (var i = 0; i \u003c 90; i++) {\n var c = document.createElement('div');\n c.className = 'confetti';\n c.style.left = (i * 137 % 100) + 'vw';\n c.style.background = colors[i % colors.length];\n c.style.animationDuration = (2.2 + (i * 53 % 18) / 10) + 's';\n c.style.animationDelay = ((i * 31 % 14) / 10) + 's';\n document.body.appendChild(c);\n }\n}\n\nfunction beep(ok) {\n if(CFG._campaign){ try{ parent.beep(ok); }catch(e){} return; }\n try {\n var ctx = beep.ctx || (beep.ctx = new (window.AudioContext || window.webkitAudioContext)());\n var t = ctx.currentTime;\n var freqs = ok ? [523, 784] : [196];\n freqs.forEach(function (f, k) {\n var o = ctx.createOscillator(), g = ctx.createGain();\n o.frequency.value = f; o.type = 'triangle';\n g.gain.setValueAtTime(0.12, t + k * 0.09);\n g.gain.exponentialRampToValueAtTime(0.001, t + k * 0.09 + 0.25);\n o.connect(g); g.connect(ctx.destination);\n o.start(t + k * 0.09); o.stop(t + k * 0.09 + 0.3);\n });\n } catch (e) {}\n}\nwindow.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } };\nif(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} }\n\u003c/script>\n\u003c/body>\n\u003c/html>","terminal":"\u003c!doctype html>\n\u003chtml lang=\"ro\">\n\u003chead>\n\u003cmeta charset=\"utf-8\">\n\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\u003ctitle>\u003c/title>\n\u003cstyle>\n * { box-sizing: border-box; }\n body { margin: 0; min-height: 100vh; background: #04130a; color: #39ff6e; font-family: \"Courier New\", ui-monospace, monospace; }\n #crt { max-width: 760px; margin: 0 auto; padding: 20px 16px 80px; }\n .line { white-space: pre-wrap; word-break: break-word; line-height: 1.45; font-size: 15px; text-shadow: 0 0 7px rgba(57,255,110,.5); }\n .line.dim { color: #1f9c4a; }\n .line.warn { color: #ffd24a; text-shadow: 0 0 7px rgba(255,210,74,.45); }\n .line.bad { color: #ff6b6b; text-shadow: 0 0 7px rgba(255,107,107,.45); }\n .line.ok { color: #9dffc0; }\n #inline { display: flex; gap: 8px; align-items: baseline; font-size: 15px; text-shadow: 0 0 7px rgba(57,255,110,.5); }\n #cmd { flex: 1; background: none; border: none; outline: none; color: inherit; font: inherit; text-shadow: inherit; caret-color: #39ff6e; }\n .scan { position: fixed; inset: 0; pointer-events: none; background: repeating-linear-gradient(0deg, rgba(0,0,0,.28) 0 1px, transparent 1px 3px); }\n .vign { position: fixed; inset: 0; pointer-events: none; background: radial-gradient(ellipse at center, transparent 55%, rgba(0,0,0,.6)); }\n\u003c/style>\n\u003c/head>\n\u003cbody>\n\u003cdiv class=\"scan\">\u003c/div>\u003cdiv class=\"vign\">\u003c/div>\n\u003cdiv id=\"crt\">\u003cdiv id=\"out\">\u003c/div>\n\u003cdiv id=\"inline\">\u003cspan>&gt;\u003c/span>\u003cinput id=\"cmd\" autocomplete=\"off\" autofocus spellcheck=\"false\">\u003c/div>\n\u003c/div>\n\u003cscript>\nvar CFG = __CFG__;\ndocument.documentElement.style.setProperty('--accent', CFG.color || '#6d28d9');\nvar totalStars = 0;\nfunction el(id){ return document.getElementById(id); }\nfunction norm(s){ return String(s).trim().toLowerCase().normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').replace(/\\s+/g, ' ').replace(/,/g, '.'); }\nfunction starsFor(att, hint){ return (hint || att >= 2) ? 1 : (att === 1 ? 2 : 3); }\nfunction finalWord(){ var w = ''; for (var i = 0; i \u003c CFG.puzzles.length; i++){ var L = (CFG.puzzles[i].letter || '').trim(); if (L) w += L.toUpperCase(); } return w; }\nfunction choiceOpts(p){ return (p.choices || '').split('\\n').map(function(l){ return l.trim(); }).filter(Boolean).map(function(o){ return o.charAt(0) === '*' ? o.slice(1).trim() : o; }); }\nfunction choiceCorrect(p){ var ls = (p.choices || '').split('\\n'); for (var i = 0; i \u003c ls.length; i++){ var l = ls[i].trim(); if (l.charAt(0) === '*') return l.slice(1).trim(); } return ''; }\nfunction checkAnswer(p, given){ var exp = p.type === 'tf' ? p.tfAnswer : (p.type === 'choice' ? choiceCorrect(p) : p.answer); return norm(given) !== '' && norm(given) === norm(exp); }\nfunction beep(ok){ if(CFG._campaign){ try{ parent.beep(ok); }catch(e){} return; } try { var ctx = beep.ctx || (beep.ctx = new (window.AudioContext || window.webkitAudioContext)()); var t = ctx.currentTime; var fs = ok ? [523, 784] : [196]; fs.forEach(function(f, k){ var o = ctx.createOscillator(), g = ctx.createGain(); o.frequency.value = f; o.type = 'triangle'; g.gain.setValueAtTime(0.12, t + k * 0.09); g.gain.exponentialRampToValueAtTime(0.001, t + k * 0.09 + 0.25); o.connect(g); g.connect(ctx.destination); o.start(t + k * 0.09); o.stop(t + k * 0.09 + 0.3); }); } catch (e) {} }\nfunction confetti(){ var colors = [CFG.color || '#6d28d9', '#fbbf24', '#34d399', '#60a5fa', '#f472b6']; for (var i = 0; i \u003c 90; i++){ var c = document.createElement('div'); c.className = 'confetti'; c.style.left = (i * 137 % 100) + 'vw'; c.style.background = colors[i % colors.length]; c.style.animationDuration = (2.2 + (i * 53 % 18) / 10) + 's'; c.style.animationDelay = ((i * 31 % 14) / 10) + 's'; document.body.appendChild(c); } }\nfunction roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } }\nwindow.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } };\nif(CFG._campaign){\n /* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */\n var _cs = document.createElement('style');\n _cs.textContent = 'h1{display:none!important}.progress{display:none!important}.meta{display:none!important}';\n (document.head || document.documentElement).appendChild(_cs);\n}\nvar idx = -1, attempts = 0, hintUsed = false, done = false;\nvar solved = [];\nvar out = el('out'), cmd = el('cmd');\ndocument.body.addEventListener('click', function(){ cmd.focus(); });\n\nvar queue = [], typing = false;\nfunction say(lines, cls, cb){ queue.push({ lines: lines.slice(), cls: cls || '', cb: cb }); pump(); }\nfunction pump(){\n if (typing) return;\n var job = queue[0];\n if (!job) return;\n if (!job.lines.length) { queue.shift(); if (job.cb) job.cb(); pump(); return; }\n var text = job.lines.shift();\n typing = true;\n var d = document.createElement('div');\n d.className = 'line ' + job.cls;\n out.appendChild(d);\n var i = 0;\n (function tick(){\n d.textContent = text.slice(0, i);\n i += 3;\n window.scrollTo(0, document.body.scrollHeight);\n if (i \u003c= text.length + 2) setTimeout(tick, 11);\n else { d.textContent = text; typing = false; pump(); }\n })();\n}\nfunction echo(text, cls){ var d = document.createElement('div'); d.className = 'line ' + (cls || ''); d.textContent = text; out.appendChild(d); window.scrollTo(0, document.body.scrollHeight); }\n\nfunction collected(){ var w = ''; for (var i = 0; i \u003c CFG.puzzles.length; i++){ var L = (CFG.puzzles[i].letter || '').trim(); if (L) w += solved[i] ? L.toUpperCase() + ' ' : '_ '; } return w.trim() || '(niciuna)'; }\n\nvar bar = '==============================================';\nvar introLines = CFG._campaign\n ? [bar, ' ' + CFG.title.toUpperCase(), bar, 'Comenzi: INDICIU, AJUTOR. Scrie raspunsul si apasa Enter.']\n : [bar, ' ' + CFG.title.toUpperCase(), bar, ' ', (CFG.player ? CFG.player + ', ' : '') + CFG.story, ' ', 'Comenzi: INDICIU, LITERE, AJUTOR. In rest, scrie raspunsul si apasa Enter.'];\nsay(introLines, '', nextPuzzle);\n\nfunction nextPuzzle(){\n idx++; attempts = 0; hintUsed = false;\n if (idx >= CFG.puzzles.length) return finale();\n var p = CFG.puzzles[idx];\n var lines = [' ', '----------------------------------------------', '[' + (idx + 1) + '/' + CFG.puzzles.length + '] ' + (p.title || 'OBSTACOL').toUpperCase(), p.question];\n if (p.type === 'tf') lines.push('(raspunde: ADEVARAT sau FALS)');\n if (p.type === 'choice') { var o = choiceOpts(p); for (var i = 0; i \u003c o.length; i++) lines.push(' ' + (i + 1) + ') ' + o[i]); }\n say(lines);\n}\n\nfunction finale(){\n done = true;\n if(CFG._campaign){\n var s = totalStars; var L = finalWord().charAt(0);\n say(['>> CAMERA REZOLVATA! Stele: ' + s + (L ? ' | Litera: ' + L : '')], 'ok', function(){\n try{ parent.nextRoom({idx:CFG._campaign.idx, stars:s, letter:L}); }catch(e){}\n });\n return;\n }\n var w = finalWord().split('').join(' ');\n var lines = [' ', bar, ' E V A D A R E R E U S I T A', bar, 'Stele: ' + totalStars + ' / ' + (CFG.puzzles.length * 3)];\n if (w) lines.push('Cuvantul magic: ' + w);\n lines.push((CFG.player ? CFG.player + ', ' : '') + CFG.finalMessage);\n lines.push(' ');\n lines.push('Scrie RESTART pentru a juca din nou.');\n say(lines, 'ok');\n beep(true);\n}\n\ncmd.addEventListener('keydown', function(e){\n if (e.key !== 'Enter') return;\n var v = cmd.value.trim();\n cmd.value = '';\n if (!v) return;\n echo('> ' + v, 'dim');\n var n = norm(v);\n if (done) { if (n === 'restart') location.reload(); else echo('Scrie RESTART pentru a juca din nou.', 'dim'); return; }\n if (n === 'ajutor' || n === 'help') { say(['INDICIU = primesti un ajutor (dar pierzi stele)', 'LITERE = literele adunate pana acum', 'Orice altceva e tratat ca raspuns.']); return; }\n if (n === 'litere') { say(['Litere adunate: ' + collected()]); return; }\n var p = CFG.puzzles[idx];\n if (!p) return;\n if (n === 'indiciu' || n === 'hint') {\n if (p.hint) { hintUsed = true; say(['INDICIU: ' + p.hint], 'warn'); }\n else say(['Nu exista niciun indiciu aici.'], 'warn');\n return;\n }\n var given = v;\n if (p.type === 'choice') { var num = parseInt(v, 10); var o = choiceOpts(p); if (num >= 1 && o[num - 1]) given = o[num - 1]; }\n if (p.type === 'tf') { if (n === 'a' || n === 'adevarat') given = 'Adevarat'; if (n === 'f' || n === 'fals') given = 'Fals'; }\n if (checkAnswer(p, given)) {\n var s = starsFor(attempts, hintUsed);\n totalStars += s; solved[idx] = true; beep(true);\n var ls = ['>> ACCES PERMIS. +' + s + ' stele (total ' + totalStars + ')'];\n var L = (p.letter || '').trim();\n if (L) ls.push('>> AI GASIT LITERA: ' + L.toUpperCase() + ' [' + collected() + ']');\n say(ls, 'ok', nextPuzzle);\n } else {\n attempts++; beep(false);\n say(['>> ACCES RESPINS. Mai incearca.'], 'bad');\n }\n});\nroomReady();\n\u003c/script>\n\u003c/body>\n\u003c/html>","arcade":"\u003c!doctype html>\n\u003chtml lang=\"ro\">\n\u003chead>\n\u003cmeta charset=\"utf-8\">\n\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\u003ctitle>\u003c/title>\n\u003cstyle>\n * { box-sizing: border-box; }\n body { margin: 0; min-height: 100vh; background: #0d0820; color: #fff; font-family: ui-monospace, \"Courier New\", monospace; display: flex; flex-direction: column; align-items: center; }\n h1 { font-size: 17px; margin: 14px 0 6px; letter-spacing: .06em; text-transform: uppercase; }\n #hud { display: flex; gap: 16px; align-items: center; font-size: 13px; color: #b9aee0; margin-bottom: 8px; flex-wrap: wrap; justify-content: center; padding: 0 10px; }\n #hudLetters { display: flex; gap: 4px; }\n #hudLetters span { width: 22px; height: 26px; border-radius: 5px; background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.2); display: flex; align-items: center; justify-content: center; font-weight: 700; color: rgba(255,255,255,.4); font-size: 13px; }\n #hudLetters span.won { background: var(--accent); color: #fff; border-color: transparent; }\n canvas { border: 3px solid #36246b; border-radius: 8px; background: #18102e; max-width: calc(100vw - 16px); }\n .help { font-size: 12px; color: #6f639e; margin: 8px 0 4px; text-align: center; padding: 0 10px; }\n #dpad { display: flex; gap: 8px; margin: 6px 0 16px; }\n #dpad button { width: 52px; height: 44px; font-size: 18px; border-radius: 9px; border: 1px solid #4a3590; background: #221643; color: #cdc3f0; cursor: pointer; }\n #dpad button:active { background: var(--accent); }\n\n .confetti { position: fixed; top: -12px; width: 9px; height: 14px; z-index: 99; animation: fall linear forwards; }\n @keyframes fall { to { transform: translateY(105vh) rotate(720deg); } }\n .shake { animation: shake .4s ease; }\n @keyframes shake { 20%,60% { transform: translateX(-8px); } 40%,80% { transform: translateX(8px); } }\n #mOverlay { display: none; position: fixed; inset: 0; background: rgba(8,4,20,.72); z-index: 20; align-items: center; justify-content: center; padding: 16px; }\n #mCard { width: 100%; max-width: 460px; background: #221440; border: 1px solid rgba(255,255,255,.18); border-radius: 16px; padding: 22px; color: #fff; font-family: system-ui, sans-serif; box-shadow: 0 18px 50px rgba(0,0,0,.5); }\n #mCard .mtitle { font-size: 12px; text-transform: uppercase; letter-spacing: .08em; color: #c4b5fd; font-weight: 700; }\n #mCard .mq { font-size: 18px; line-height: 1.45; margin: 8px 0 16px; }\n #mCard input[type=text] { width: 100%; font: inherit; font-size: 17px; padding: 10px 12px; border-radius: 10px; border: 1px solid rgba(255,255,255,.25); background: rgba(0,0,0,.3); color: #fff; text-align: center; box-sizing: border-box; }\n #mCard input:focus { outline: 2px solid var(--accent); border-color: transparent; }\n #mCard button { font: inherit; cursor: pointer; border: none; border-radius: 10px; padding: 11px 16px; font-weight: 700; background: var(--accent); color: #fff; width: 100%; margin-top: 10px; box-sizing: border-box; }\n #mCard button.opt { background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.2); font-weight: 600; text-align: left; }\n #mCard button.opt:hover { background: rgba(255,255,255,.2); }\n #mCard .mfb { min-height: 20px; text-align: center; font-weight: 700; margin-top: 10px; }\n #mCard .mfb.bad { color: #fda4af; } #mCard .mfb.good { color: #86efac; }\n #mCard .mhint { background: none !important; color: rgba(255,255,255,.55) !important; font-weight: 600 !important; font-size: 13px; width: auto !important; display: block; margin: 10px auto 0; }\n #mCard .mhinttext { background: rgba(255,255,255,.1); border-radius: 9px; padding: 9px 11px; font-size: 14px; margin-top: 8px; display: none; white-space: pre-line; }\n #mCard .mclose { background: none !important; color: rgba(255,255,255,.4) !important; font-size: 12px; width: auto !important; margin: 6px auto 0; display: block; }\n #fOverlay { display: none; position: fixed; inset: 0; background: rgba(8,4,20,.88); z-index: 30; align-items: center; justify-content: center; padding: 16px; }\n #fOverlay .fcard { width: 100%; max-width: 480px; text-align: center; background: #221440; border: 1px solid rgba(255,255,255,.18); border-radius: 18px; padding: 28px; color: #fff; font-family: system-ui, sans-serif; }\n #fOverlay h1 { margin: 0 0 8px; font-size: 26px; }\n #fOverlay .fstars { font-size: 26px; letter-spacing: 4px; color: #fbbf24; margin: 6px 0; }\n #fOverlay .fword { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin: 16px 0; }\n #fOverlay .fword span { width: 44px; height: 52px; border-radius: 10px; background: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 26px; font-weight: 800; animation: flipin .6s ease backwards; }\n @keyframes flipin { from { transform: rotateX(90deg); } to { transform: rotateX(0); } }\n #fOverlay p { color: rgba(255,255,255,.8); line-height: 1.5; }\n #fOverlay button { font: inherit; cursor: pointer; border: none; border-radius: 10px; padding: 12px 18px; font-weight: 700; background: var(--accent); color: #fff; width: 100%; }\n\u003c/style>\n\u003c/head>\n\u003cbody>\n\u003ch1>\u003c/h1>\n\u003cdiv id=\"hud\">\u003cspan id=\"hudStep\">\u003c/span>\u003cspan id=\"hudStars\">\u003c/span>\u003cdiv id=\"hudLetters\">\u003c/div>\u003c/div>\n\u003ccanvas id=\"cv\">\u003c/canvas>\n\u003cdiv class=\"help\">Sageti / WASD (da click pe joc intai). Usile rosii iti pun intrebari; cufarul auriu e scaparea.\u003c/div>\n\u003cdiv id=\"dpad\">\u003cbutton data-d=\"L\">&#9664;\u003c/button>\u003cbutton data-d=\"U\">&#9650;\u003c/button>\u003cbutton data-d=\"D\">&#9660;\u003c/button>\u003cbutton data-d=\"R\">&#9654;\u003c/button>\u003c/div>\n\u003cdiv id=\"mOverlay\">\u003cdiv id=\"mCard\">\n \u003cdiv class=\"mtitle\" id=\"mTitle\">\u003c/div>\n \u003cdiv class=\"mq\" id=\"mQ\">\u003c/div>\n \u003cdiv id=\"mAnswers\">\u003c/div>\n \u003cdiv class=\"mfb\" id=\"mFeedback\">\u003c/div>\n \u003cbutton class=\"mhint\" id=\"mHintBtn\">Vreau un indiciu\u003c/button>\n \u003cdiv class=\"mhinttext\" id=\"mHintText\">\u003c/div>\n \u003cbutton class=\"mclose\" id=\"mClose\">Pleaca de aici\u003c/button>\n\u003c/div>\u003c/div>\n\u003cdiv id=\"fOverlay\">\u003cdiv class=\"fcard\">\n \u003ch1>Evadare reusita!\u003c/h1>\n \u003cdiv class=\"fstars\" id=\"fStars\">\u003c/div>\n \u003cdiv class=\"fword\" id=\"fWord\">\u003c/div>\n \u003cp id=\"fMsg\">\u003c/p>\n \u003cbutton id=\"fAgain\">Joaca din nou\u003c/button>\n\u003c/div>\u003c/div>\n\u003cscript>\nvar CFG = __CFG__;\ndocument.documentElement.style.setProperty('--accent', CFG.color || '#6d28d9');\nvar totalStars = 0;\nfunction el(id){ return document.getElementById(id); }\nfunction norm(s){ return String(s).trim().toLowerCase().normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').replace(/\\s+/g, ' ').replace(/,/g, '.'); }\nfunction starsFor(att, hint){ return (hint || att >= 2) ? 1 : (att === 1 ? 2 : 3); }\nfunction finalWord(){ var w = ''; for (var i = 0; i \u003c CFG.puzzles.length; i++){ var L = (CFG.puzzles[i].letter || '').trim(); if (L) w += L.toUpperCase(); } return w; }\nfunction choiceOpts(p){ return (p.choices || '').split('\\n').map(function(l){ return l.trim(); }).filter(Boolean).map(function(o){ return o.charAt(0) === '*' ? o.slice(1).trim() : o; }); }\nfunction choiceCorrect(p){ var ls = (p.choices || '').split('\\n'); for (var i = 0; i \u003c ls.length; i++){ var l = ls[i].trim(); if (l.charAt(0) === '*') return l.slice(1).trim(); } return ''; }\nfunction checkAnswer(p, given){ var exp = p.type === 'tf' ? p.tfAnswer : (p.type === 'choice' ? choiceCorrect(p) : p.answer); return norm(given) !== '' && norm(given) === norm(exp); }\nfunction beep(ok){ if(CFG._campaign){ try{ parent.beep(ok); }catch(e){} return; } try { var ctx = beep.ctx || (beep.ctx = new (window.AudioContext || window.webkitAudioContext)()); var t = ctx.currentTime; var fs = ok ? [523, 784] : [196]; fs.forEach(function(f, k){ var o = ctx.createOscillator(), g = ctx.createGain(); o.frequency.value = f; o.type = 'triangle'; g.gain.setValueAtTime(0.12, t + k * 0.09); g.gain.exponentialRampToValueAtTime(0.001, t + k * 0.09 + 0.25); o.connect(g); g.connect(ctx.destination); o.start(t + k * 0.09); o.stop(t + k * 0.09 + 0.3); }); } catch (e) {} }\nfunction confetti(){ var colors = [CFG.color || '#6d28d9', '#fbbf24', '#34d399', '#60a5fa', '#f472b6']; for (var i = 0; i \u003c 90; i++){ var c = document.createElement('div'); c.className = 'confetti'; c.style.left = (i * 137 % 100) + 'vw'; c.style.background = colors[i % colors.length]; c.style.animationDuration = (2.2 + (i * 53 % 18) / 10) + 's'; c.style.animationDelay = ((i * 31 % 14) / 10) + 's'; document.body.appendChild(c); } }\nfunction roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } }\nwindow.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } };\nif(CFG._campaign){\n /* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */\n var _cs = document.createElement('style');\n _cs.textContent = 'h1{display:none!important}.progress{display:none!important}.meta{display:none!important}';\n (document.head || document.documentElement).appendChild(_cs);\n}\nvar N = CFG.puzzles.length;\nvar GW = 13, RH = 4, ROOMS = N + 1, GH = ROOMS * RH + 1;\nvar TS = 38, VR = Math.min(GH, 11);\nvar map = [], doorAt = {}, doorPos = [], solvedFlags = [];\nfor (var y = 0; y \u003c GH; y++) {\n map[y] = [];\n for (var x = 0; x \u003c GW; x++) {\n map[y][x] = (x === 0 || x === GW - 1 || y === 0 || y === GH - 1 || y % RH === 0) ? 1 : 0;\n }\n}\nfor (var i = 0; i \u003c N; i++) {\n var dy = (i + 1) * RH, dx = (i % 2 === 0) ? GW - 3 : 2;\n map[dy][dx] = 2; doorAt[dy + '_' + dx] = i; doorPos.push({ y: dy, x: dx });\n}\nvar chest = { y: (ROOMS - 1) * RH + 2, x: Math.floor(GW / 2) };\nmap[chest.y][chest.x] = 4;\nvar hero = { y: 2, x: Math.floor(GW / 2) - 2 };\nvar finished = false;\n\nvar cv = el('cv'); cv.width = GW * TS; cv.height = VR * TS;\nvar ctx = cv.getContext('2d');\n\nfunction draw(){\n var offY = Math.max(0, Math.min(hero.y - Math.floor(VR / 2), GH - VR));\n ctx.clearRect(0, 0, cv.width, cv.height);\n for (var vy = 0; vy \u003c VR; vy++) {\n var y = vy + offY;\n for (var x = 0; x \u003c GW; x++) {\n var t = map[y][x], px = x * TS, py = vy * TS;\n if (t === 1) {\n ctx.fillStyle = '#33215f'; ctx.fillRect(px, py, TS, TS);\n ctx.fillStyle = '#241646';\n ctx.fillRect(px, py + TS / 2 - 1, TS, 2);\n ctx.fillRect(px + ((y % 2) ? TS / 2 : TS / 4) - 1, py, 2, TS / 2 - 1);\n } else {\n ctx.fillStyle = ((x + y) % 2) ? '#191130' : '#1c1336'; ctx.fillRect(px, py, TS, TS);\n if (t === 2 || t === 3) {\n ctx.fillStyle = t === 2 ? '#9f1239' : '#166534'; ctx.fillRect(px + 3, py + 2, TS - 6, TS - 4);\n ctx.fillStyle = t === 2 ? '#e11d48' : '#22c55e'; ctx.fillRect(px + 6, py + 5, TS - 12, TS - 10);\n ctx.fillStyle = '#fbbf24'; ctx.fillRect(px + TS / 2 - 2, py + TS / 2 - 2, 4, 7);\n }\n if (t === 4) {\n ctx.fillStyle = '#92400e'; ctx.fillRect(px + 5, py + 10, TS - 10, TS - 16);\n ctx.fillStyle = '#f59e0b'; ctx.fillRect(px + 5, py + 10, TS - 10, 7);\n ctx.fillStyle = '#fde68a'; ctx.fillRect(px + TS / 2 - 2, py + 13, 4, 8);\n }\n }\n }\n }\n var hx = hero.x * TS, hy = (hero.y - offY) * TS;\n ctx.fillStyle = CFG.color || '#6d28d9'; ctx.fillRect(hx + 7, hy + 5, TS - 14, TS - 10);\n ctx.fillStyle = '#fff'; ctx.fillRect(hx + 12, hy + 12, 5, 5); ctx.fillRect(hx + TS - 17, hy + 12, 5, 5);\n ctx.fillStyle = '#0d0820'; ctx.fillRect(hx + 13, hy + 14, 2, 2); ctx.fillRect(hx + TS - 16, hy + 14, 2, 2);\n ctx.fillStyle = '#fff'; ctx.fillRect(hx + 13, hy + TS - 14, TS - 26, 3);\n}\n\nfunction updateHud(){\n var open = 0; for (var i = 0; i \u003c N; i++) if (solvedFlags[i]) open++;\n el('hudStep').textContent = 'Usi: ' + open + '/' + N;\n el('hudStars').textContent = totalStars + ' \\u2605';\n var hb = el('hudLetters'); hb.innerHTML = '';\n for (var j = 0; j \u003c N; j++) {\n var L = (CFG.puzzles[j].letter || '').trim();\n if (!L) continue;\n var s = document.createElement('span');\n if (solvedFlags[j]) { s.textContent = L.toUpperCase(); s.className = 'won'; }\n else s.textContent = '?';\n hb.appendChild(s);\n }\n}\n\nfunction move(dx, dy){\n if (finished || modalOpen()) return;\n var nx = hero.x + dx, ny = hero.y + dy;\n if (ny \u003c 0 || ny >= GH || nx \u003c 0 || nx >= GW) return;\n var t = map[ny][nx];\n if (t === 1) return;\n if (t === 2) { openPuzzle(doorAt[ny + '_' + nx], onDoorSolved); return; }\n if (t === 4) { finished = true; showFinal(); return; }\n hero.x = nx; hero.y = ny; draw();\n}\n\nfunction onDoorSolved(i){\n solvedFlags[i] = true;\n map[doorPos[i].y][doorPos[i].x] = 3;\n updateHud(); draw();\n}\n\nwindow.addEventListener('keydown', function(e){\n var d = { ArrowUp: [0, -1], ArrowDown: [0, 1], ArrowLeft: [-1, 0], ArrowRight: [1, 0], w: [0, -1], s: [0, 1], a: [-1, 0], d: [1, 0] }[e.key];\n if (!d) return;\n e.preventDefault();\n move(d[0], d[1]);\n});\ndocument.querySelectorAll('#dpad button').forEach(function(b){\n b.addEventListener('click', function(){\n var m = { U: [0, -1], D: [0, 1], L: [-1, 0], R: [1, 0] }[b.dataset.d];\n move(m[0], m[1]);\n });\n});\nvar mIdx = -1, mAtt = 0, mHint = false, mCb = null;\nel('mHintBtn').onclick = function(){ mHint = true; el('mHintText').style.display = 'block'; };\nel('mClose').onclick = function(){ el('mOverlay').style.display = 'none'; };\nfunction modalOpen(){ return el('mOverlay').style.display === 'flex'; }\nfunction openPuzzle(i, cb){\n mIdx = i; mAtt = 0; mHint = false; mCb = cb;\n var p = CFG.puzzles[i];\n el('mTitle').textContent = p.title || ('Puzzle ' + (i + 1));\n el('mQ').textContent = p.question;\n el('mFeedback').textContent = ''; el('mFeedback').className = 'mfb';\n el('mHintText').style.display = 'none'; el('mHintText').textContent = p.hint || '';\n el('mHintBtn').style.display = p.hint ? '' : 'none';\n var box = el('mAnswers'); box.innerHTML = '';\n if (p.type === 'free') {\n var inp = document.createElement('input'); inp.type = 'text'; inp.placeholder = 'Scrie raspunsul...'; inp.autocomplete = 'off';\n var b = document.createElement('button'); b.textContent = 'Verifica';\n b.onclick = function(){ mCheck(inp.value); };\n inp.onkeydown = function(e){ e.stopPropagation(); if (e.key === 'Enter') b.click(); };\n box.appendChild(inp); box.appendChild(b);\n setTimeout(function(){ inp.focus(); }, 60);\n } else if (p.type === 'tf') {\n ['Adevarat', 'Fals'].forEach(function(v){ var b = document.createElement('button'); b.className = 'opt'; b.textContent = v; b.onclick = function(){ mCheck(v); }; box.appendChild(b); });\n } else {\n choiceOpts(p).forEach(function(o){ var b = document.createElement('button'); b.className = 'opt'; b.textContent = o; b.onclick = function(){ mCheck(o); }; box.appendChild(b); });\n }\n el('mOverlay').style.display = 'flex';\n}\nfunction mCheck(given){\n var p = CFG.puzzles[mIdx];\n if (checkAnswer(p, given)) {\n var s = starsFor(mAtt, mHint);\n totalStars += s; beep(true);\n el('mFeedback').textContent = 'Corect! +' + s + ' \\u2605'; el('mFeedback').className = 'mfb good';\n setTimeout(function(){ el('mOverlay').style.display = 'none'; var cb = mCb; mCb = null; if (cb) cb(mIdx, s); }, 750);\n } else {\n mAtt++; beep(false);\n el('mFeedback').textContent = 'Nu e bine, mai incearca!'; el('mFeedback').className = 'mfb bad';\n var c = el('mCard'); c.classList.remove('shake'); void c.offsetWidth; c.classList.add('shake');\n }\n}\nfunction showFinal(){\n if(CFG._campaign){\n var L = finalWord().charAt(0);\n try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:L}); }catch(e){}\n return;\n }\n el('fStars').textContent = totalStars + ' / ' + (CFG.puzzles.length * 3) + ' \\u2605';\n var w = finalWord(); var bw = el('fWord'); bw.innerHTML = '';\n for (var j = 0; j \u003c w.length; j++){ var s = document.createElement('span'); s.textContent = w.charAt(j); s.style.animationDelay = (j * 0.18) + 's'; bw.appendChild(s); }\n var msg = CFG.finalMessage || '';\n el('fMsg').textContent = CFG.player ? CFG.player + ', ' + msg.charAt(0).toLowerCase() + msg.slice(1) : msg;\n el('fOverlay').style.display = 'flex';\n beep(true); confetti();\n}\nel('fAgain').onclick = function(){ location.reload(); };\nupdateHud(); draw();\nroomReady();\n\u003c/script>\n\u003c/body>\n\u003c/html>"};
var MASTER = {"title":"Comoara ascunsa","player":"","color":"#6d28d9","charName":"Alex","style":"campaign","story":"O comoara a fost ascunsa, iar singurul drum spre ea trece prin cateva incercari.","finalMessage":"Felicitari! Ai gasit comoara!","puzzles":[{"title":"Incalzirea","type":"free","question":"Cat fac 7 x 8?","answer":"56","tfAnswer":"Adevarat","choices":"","hint":"Tabla inmultirii cu 7.","letter":"D","style":""},{"title":"Adevarat sau fals","type":"tf","question":"Romania are iesire la Marea Neagra.","answer":"","tfAnswer":"Adevarat","choices":"","hint":"","letter":"A","style":""},{"title":"Alege raspunsul","type":"choice","question":"Care este capitala Frantei?","answer":"","tfAnswer":"Adevarat","choices":"*Paris\nLyon\nMarsilia","hint":"Turnul Eiffel.","letter":"R","style":""}]};
var ROTATION = ['classic','terminal','arcade','chat','point'];
var TOKEN = '__CFG__';
document.documentElement.style.setProperty('--accent', MASTER.color || '#6d28d9');
var N = MASTER.puzzles.length;
var totalStars = 0;
var collected = [];
var skipped = {};
var activeIdx = -1;
var activeWindow = null;
var readyTimer = null;
var roomDone = {};
/* ----- Resume — safeStore (D3) + djb2 hash (D11) ----- */
function djb2(s){
var h = 5381;
for(var i=0;i<s.length;i++) h = ((h<<5)+h) ^ s.charCodeAt(i);
return (h >>> 0).toString(36);
}
var _RESUME_KEY = 'esc-camp-' + djb2(JSON.stringify(MASTER));
function safeGet(){ try{ return JSON.parse(sessionStorage.getItem(_RESUME_KEY)); }catch(e){ return null; } }
function safeSet(v){ try{ sessionStorage.setItem(_RESUME_KEY, JSON.stringify(v)); }catch(e){ /* webview restrictat — continuă fără resume */ } }
function saveProgress(){
safeSet({ idx: activeIdx, totalStars: totalStars, collected: collected.slice(), skipped: skipped });
}
function clearProgress(){ try{ sessionStorage.removeItem(_RESUME_KEY); }catch(e){} }
var frameEl = document.getElementById('room-frame');
var introEl = document.getElementById('intro');
var corridorEl = document.getElementById('corridor');
var skipEl = document.getElementById('skip-banner');
var finaleEl = document.getElementById('finale');
/* ----- Dots ----- */
function buildDots(){
var d = document.getElementById('dots'); d.innerHTML = '';
for(var i=0;i<N;i++){
var s = document.createElement('span');
s.id = 'dot-'+i;
s.setAttribute('aria-label','Camera '+(i+1)+' din '+N);
d.appendChild(s);
}
}
function setDot(i,cls){ var d=document.getElementById('dot-'+i); if(d) d.className=cls; }
/* ----- Ușa coridorului (§Design pct.7) ----- */
function doorHtml(style, isLast, isStuck){
var cls = (isLast ? ' crescendo' : '') + (isStuck ? ' stuck' : '');
var lock = isStuck ? '<span class="door-lock">&#128274;</span>' : '';
if(style === 'classic') return '<div class="door-classic'+cls+'"><span class="dq">?</span>'+lock+'</div>';
if(style === 'terminal') return '<div class="door-terminal'+cls+'"><span class="dt-txt">'+(isStuck?'BLOCKED':'ACCESS')+'</span>'+(isStuck?'':' <span class="dt-cur">_</span>')+lock+'</div>';
if(style === 'arcade') return '<div class="door-arcade'+cls+'"><span class="da-sprite">&#9654;</span>'+lock+'</div>';
if(style === 'chat') return '<div class="door-chat'+cls+'"><div class="dc-notch"></div><div class="dc-screen"><div class="dc-bub dc-npc">Salut!</div><div class="dc-bub dc-me">?</div></div><div class="dc-home"></div>'+lock+'</div>';
if(style === 'point') return '<div class="door-point'+cls+'"><svg viewBox="0 0 88 124" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="84" height="120" rx="6" fill="#5a3518"/><rect x="6" y="6" width="76" height="112" rx="4" fill="#7c4f2c"/><rect x="10" y="10" width="30" height="44" rx="3" fill="rgba(0,0,0,.22)"/><rect x="48" y="10" width="30" height="44" rx="3" fill="rgba(0,0,0,.22)"/><rect x="10" y="62" width="68" height="52" rx="3" fill="rgba(0,0,0,.15)"/><circle cx="67" cy="70" r="8" fill="#c8952a"/><circle cx="67" cy="70" r="5" fill="#f3cf6d"/><circle cx="67" cy="70" r="2" fill="#a07022"/><circle cx="32" cy="87" r="13" fill="none" stroke="#f3cf6d" stroke-width="3"/><line x1="42" y1="97" x2="50" y2="105" stroke="#f3cf6d" stroke-width="3.5" stroke-linecap="round"/></svg>'+lock+'</div>';
return doorHtml('point', isLast, isStuck); /* fallback */
}
/* ----- Beep — singurul AudioContext (D2) ----- */
function beep(ok){
try{
var ctx=beep._ctx||(beep._ctx=new(window.AudioContext||window.webkitAudioContext)());
var t=ctx.currentTime; var fs=ok?[523,784]:[196];
fs.forEach(function(f,k){
var o=ctx.createOscillator(),g=ctx.createGain();
o.frequency.value=f; o.type='triangle';
g.gain.setValueAtTime(0.12,t+k*0.09);
g.gain.exponentialRampToValueAtTime(0.001,t+k*0.09+0.25);
o.connect(g); g.connect(ctx.destination);
o.start(t+k*0.09); o.stop(t+k*0.09+0.3);
});
}catch(e){}
}
/* ----- parent.* API ----- */
window.nextRoom = function(data){
/* Guard: doar de la camera activă (D5) */
if(!activeWindow || frameEl.contentWindow !== activeWindow){
console.log('[campaign] nextRoom ignorat — frame stale'); return;
}
var idx = data ? +data.idx : activeIdx;
/* Idempotență (D4) */
if(roomDone[idx]){ console.log('[campaign] nextRoom ignorat — idx deja încheiat', idx); return; }
if(idx !== activeIdx){ console.log('[campaign] nextRoom idx mismatch ignorat'); return; }
clearTimeout(readyTimer);
roomDone[idx] = true;
totalStars += (data.stars || 0);
var letter = String(data.letter || '').replace(/[^A-Za-z0-9]/g,'').charAt(0).toUpperCase();
if(letter) collected.push(letter);
setDot(idx,'done');
saveProgress();
console.log('[campaign] camera',idx,'done. stars=',data.stars,'letter=',letter);
var next = idx + 1;
if(next >= N){ clearProgress(); showFinale(); } else { showCorridor(idx, data, next); }
};
window.roomReady = function(idx){
console.log('[campaign] roomReady',idx);
if(+idx !== activeIdx) return;
clearTimeout(readyTimer);
frameEl.setAttribute('data-room-ready','true'); /* hook pentru tests */
};
window.roomError = function(idx, msg){
console.warn('[campaign] roomError',idx,msg);
/* roomError are semantică ORICÂND (post-ready inclus, D5) */
if(!activeWindow || frameEl.contentWindow !== activeWindow) return;
if(+idx !== activeIdx) return;
if(roomDone[idx]) return;
skipRoom(+idx, String(msg||'eroare'));
};
/* ----- Timeout 4s → skip (T3) ----- */
function startReadyTimer(idx){
clearTimeout(readyTimer);
readyTimer = setTimeout(function(){
if(roomDone[idx]) return;
console.warn('[campaign] timeout 4s — skip',idx);
skipRoom(idx,'timeout');
}, 4000);
}
function skipRoom(idx, reason){
clearTimeout(readyTimer);
roomDone[idx] = true;
skipped[idx] = true;
setDot(idx,'done');
saveProgress();
var style = (MASTER.puzzles[idx]&&(MASTER.puzzles[idx].style||ROTATION[idx%5]))||'?';
var code = style + '\u00b7' + idx;
showSkipBanner(idx, code, reason);
}
/* ----- Montare cameră ----- */
function mountRoom(idx){
activeIdx = idx;
var puzzle = MASTER.puzzles[idx];
var style = (puzzle&&puzzle.style) || ROTATION[idx % ROTATION.length];
var tpl = TPL[style];
if(!tpl){
/* stil negăsit în template-uri — skip imediat */
console.warn('[campaign] template lipsă pentru stil',style);
skipRoom(idx,'template lipsă: '+style); return;
}
var camCfg = {
title: MASTER.title, player: MASTER.player, color: MASTER.color,
style: style, charName: MASTER.charName,
story: MASTER.story, finalMessage: MASTER.finalMessage,
puzzles: [puzzle],
_campaign: {
idx: idx, total: N,
stars: totalStars, letters: collected.slice(), deadline: null
}
};
/* json cu replace-funcție (D1 + D6) */
var json = JSON.stringify(camCfg).replace(/</g,'\u003c');
var srcdoc = tpl.replace(TOKEN, function(){ return json; });
hideAll();
setDot(idx,'active');
activeWindow = null;
frameEl.removeAttribute('data-room-ready');
frameEl.srcdoc = srcdoc;
setTimeout(function(){ activeWindow = frameEl.contentWindow; }, 0);
startReadyTimer(idx);
var isLast = (idx === N - 1);
document.getElementById('chrome-title').textContent = isLast
? MASTER.title + ' — Ultima cameră!' : MASTER.title;
console.log('[campaign] montat camera',idx,'stil',style);
}
/* ----- Coridor ----- */
function showCorridor(doneIdx, data, nextIdx){
hideAll();
var s = data.stars || 0; var stars = '';
for(var i=0;i<s;i++) stars += '\u2605';
document.getElementById('corr-stars').textContent = stars || '\u2606';
var letter = String(data.letter||'').replace(/[^A-Za-z0-9]/g,'').charAt(0).toUpperCase();
document.getElementById('corr-letter').textContent = letter || '\u2014';
var styleNames = {classic:'Clasic',terminal:'Terminal Retro',arcade:'Arcade Pixel',chat:'Story Chat',point:'Point-and-Click'};
var nextStyle = (MASTER.puzzles[nextIdx] && (MASTER.puzzles[nextIdx].style || ROTATION[nextIdx%5])) || 'classic';
var isLast = (nextIdx === N - 1);
document.getElementById('corr-next').textContent =
isLast ? '\u2605 Ultima cameră!' : 'Camera '+(nextIdx+1)+' — '+styleNames[nextStyle];
/* Ușa ca erou (§Design pct.2 + pct.6) */
var doorEl = document.getElementById('corr-door');
doorEl.innerHTML = doorHtml(nextStyle, isLast, false);
corridorEl.classList.add('show');
var btn = document.getElementById('btn-next');
btn.disabled = false;
btn.onclick = function(){
btn.disabled = true; /* idempotență buton (T4) */
/* Animație deschidere ușă ~250ms (§Design pct.4) */
var d = doorEl.firstElementChild;
if(d) d.classList.add('opening');
setTimeout(function(){ mountRoom(nextIdx); }, 280);
};
}
/* ----- Skip banner ----- */
function showSkipBanner(idx, code, reason){
hideAll();
var stuckStyle = (MASTER.puzzles[idx] && (MASTER.puzzles[idx].style || ROTATION[idx%5])) || 'classic';
document.getElementById('skip-door').innerHTML = doorHtml(stuckStyle, false, true);
document.getElementById('skip-code').textContent = 'Cod: ' + code + ' (' + reason + ')';
skipEl.classList.add('show');
var next = idx + 1;
var btn = document.getElementById('btn-skip');
btn.disabled = false;
btn.onclick = function(){
btn.disabled = true;
if(next >= N){ showFinale(); } else { showCorridor(idx,{stars:0,letter:''},next); }
};
}
/* ----- Final ----- */
function showFinale(){
hideAll(); finaleEl.classList.add('show');
var wEl = document.getElementById('fin-word'); wEl.innerHTML = '';
collected.forEach(function(l,j){
var s = document.createElement('span');
s.textContent = l;
s.style.animationDelay = (j*0.18)+'s';
wEl.appendChild(s);
});
/* dăle-lacăt pentru camere sărite */
Object.keys(skipped).forEach(function(i){
var s = document.createElement('span');
s.textContent = '\uD83D\uDD12'; /* 🔒 */
s.title = 'Camera '+(+i+1)+' sărită';
s.style.fontSize = '22px';
wEl.appendChild(s);
});
document.getElementById('fin-stars').textContent = totalStars + ' / ' + (N*3) + ' \u2605';
var msg = MASTER.finalMessage || '';
var pl = MASTER.player || '';
document.getElementById('fin-msg').textContent = pl ? pl+', '+msg.charAt(0).toLowerCase()+msg.slice(1) : msg;
beep(true); confetti();
}
/* ----- Confetti ----- */
function confetti(){
var colors=[MASTER.color||'#6d28d9','#fbbf24','#34d399','#60a5fa','#f472b6'];
for(var i=0;i<90;i++){
var c=document.createElement('div'); c.className='confetti';
c.style.left=(i*137%100)+'vw'; c.style.background=colors[i%colors.length];
c.style.animationDuration=(2.2+(i*53%18)/10)+'s';
c.style.animationDelay=((i*31%14)/10)+'s';
document.body.appendChild(c);
}
}
function hideAll(){
[introEl,corridorEl,skipEl,finaleEl].forEach(function(el){ el.classList.remove('show'); });
}
/* ----- Intro ----- */
document.getElementById('intro-title').textContent = MASTER.title;
document.getElementById('intro-story').textContent = (MASTER.player?'Salut, '+MASTER.player+'! ':'')+MASTER.story;
document.getElementById('intro-promise').textContent = N+' camere \u00b7 3 stiluri \u00b7 1 cuvânt magic';
document.getElementById('btn-start').onclick = function(){ clearProgress(); mountRoom(0); };
buildDots();
/* ----- Resume la reload (D11) ----- */
(function tryResume(){
if(MASTER._noResume) return; /* preview-ul nu reia niciodată */
var saved = safeGet();
if(!saved || typeof saved.idx !== 'number' || saved.idx < 0) return;
/* restaurăm starea */
totalStars = saved.totalStars || 0;
collected = saved.collected || [];
skipped = saved.skipped || {};
Object.keys(skipped).forEach(function(k){ roomDone[+k] = true; setDot(+k,'done'); });
/* repornim de la coridorul camerei next */
var resumeIdx = saved.idx + 1;
if(resumeIdx >= N){
/* ultima cameră deja terminată — mergi direct la final */
showFinale(); return;
}
showCorridor(saved.idx, {stars:0, letter: (collected[collected.length-1]||'')}, resumeIdx);
})();
</script>
</body>
</html>