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>
527 lines
63 KiB
HTML
527 lines
63 KiB
HTML
<!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>>\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\">◀\u003c/button>\u003cbutton data-d=\"U\">▲\u003c/button>\u003cbutton data-d=\"D\">▼\u003c/button>\u003cbutton data-d=\"R\">▶\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">🔒</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">▶</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> |