PR2: unifica contractul _campaign la final in libJS.campaignDone()

Payload-ul parent.nextRoom({idx,stars,letter}) era scris identic in 3 locuri
(terminal finale(), SNIP.finalJs showFinal(), classic next()). Acum traieste o
singura data in libJS.campaignDone(), langa roomReady/beep/onerror.

Decizie: NU am pus terminalul pe showFinal() din SNIP (cum sugera formularea
initiala) — showFinal randeaza modal #fOverlay, dar terminalul are finale CRT
stilizat (ASCII 'EVADARE REUSITA' + RESTART), on-theme intentionat. Fortarea
modalului ar fi regresie vizuala pe terminalul standalone. Am unificat doar
ramura _campaign (payload identic), prezentarea standalone neatinsa.

- terminal finale(): say([...], 'ok', campaignDone)
- SNIP.finalJs showFinal(): if(_campaign){ campaignDone(); return; }
- arcade/chat/point primesc campaignDone via showFinal automat.
- classic ramane bespoke (nu foloseste libJS) — pliere = D7 (documentat in TODOS).

Suita 25/25 (terminal standalone + camere terminal in campanie E2E). Demo-uri
libJS regenerate; exemplu-clasic.html neatins.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-13 11:53:37 +00:00
parent da93d8498c
commit ab11089097
7 changed files with 44 additions and 33 deletions

View File

@@ -14,7 +14,7 @@ Referință plan complet: `~/.gstack/projects/romfast-escape-builder/ceo-plans/2
- [x] **Audio camere** — fix REAL (vezi S1 mai jos, commit `651025b`): unlock pe primul gest global - [x] **Audio camere** — fix REAL (vezi S1 mai jos, commit `651025b`): unlock pe primul gest global
(acoperă resume), nu doar btn-start; test rescris (headless crea ctx `running` trivial). (acoperă resume), nu doar btn-start; test rescris (headless crea ctx `running` trivial).
- [x] **Narațiune vocală (D10)** — LIVRAT (vezi §„Narațiune vocală" mai jos). Smoke 25/25. - [x] **Narațiune vocală (D10)** — LIVRAT (vezi §„Narațiune vocală" mai jos). Smoke 25/25.
- [ ] **Unificare `finale()` terminal pe `SNIP.finalJs`** (vezi §dedicată mai jos). - [x] **Unificare contract `_campaign` la final**`libJS.campaignDone()` (vezi §dedicată mai jos).
- [ ] **Audit a11y motoare** (vezi §dedicată mai jos). - [ ] **Audit a11y motoare** (vezi §dedicată mai jos).
--- ---
@@ -101,11 +101,27 @@ Verificat: smoke 25/25 (test nou „voce — naratiune opt-in") + live MCP (buto
NOTĂ scope: motoarele NU cheamă încă `parent.voiceSay` (am evitat dublu-citit cu roomReady); dacă NOTĂ scope: motoarele NU cheamă încă `parent.voiceSay` (am evitat dublu-citit cu roomReady); dacă
pe viitor vrei replici chat citite individual, adaugă în `charMsg` cu guard `typeof parent.voiceSay`. pe viitor vrei replici chat citite individual, adaugă în `charMsg` cu guard `typeof parent.voiceSay`.
### Unificarea `finale()` din terminal pe `SNIP.finalJs` (PR2 primul pas) ### [x] Unificarea contractului `_campaign` la final — `libJS.campaignDone()` (LIVRAT)
- Astăzi terminalul are propria funcție `finale()` (escape-builder.html:863) care NU folosește `SNIP.finalJs`. **Decizie de design (abatere de la formularea inițială):** NU am pus terminalul pe `showFinal()`
- Migrarea pe SNIP.finalJs deblochează ramura `_campaign` uniformă pentru toate cele 5 motoare. din `SNIP.finalJs`. Motiv: `showFinal()` randează un modal mov `#fOverlay`, iar terminalul are
- Prim pas al Etapei 2 (D7): migrarea `gameClassic` pe `libJS+SNIP` → regresie manuală pe classic. finale stilizat în CRT (ASCII „EVADARE REUSITA" + comandă `RESTART`) — e on-theme intenționat;
- Referință: planul §Etapa 2 pct. 1; D7. forțarea modalului ar fi o **regresie vizuală** pe terminalul standalone.
Ce am unificat în schimb (adevărata duplicare): payload-ul `parent.nextRoom({idx,stars,letter})`
era scris identic în 3 locuri (terminal `finale()`, `SNIP.finalJs showFinal()`, classic `next()`).
Acum trăiește o singură dată în `libJS.campaignDone()` (lângă `roomReady`/`beep`/`onerror`).
- terminal `finale()` ramura `_campaign``say([... CAMERA REZOLVATA ...], 'ok', campaignDone)`.
- `SNIP.finalJs showFinal()` ramura `_campaign``campaignDone()`.
- arcade/chat/point folosesc `showFinal` → primesc automat `campaignDone`.
- **classic rămâne bespoke** (nu folosește `libJS`) → contractul lui e încă inline. Pliere completă
= D7 (migrarea `gameClassic` pe `libJS+SNIP`, cu regresie manuală pe classic). RĂMAs DE FĂCUT.
Verificat: smoke 25/25 (terminal standalone test 2 + camere terminal în campanie E2E test 1).
Referință: planul §Etapa 2 pct. 1; D7.
### [ ] D7 rămas: migrarea `gameClassic` pe `libJS+SNIP`
- Classic (escape-builder.html:451) e singurul motor bespoke: propriul `totalStars`, `beep`,
inline `finalWord` (dublat de 2 ori în `next()`), propriul modal final `#sFinal`.
- După migrare: classic folosește `libJS.campaignDone()` + `SNIP` ca celelalte 4 → 5/5 uniform.
- Necesită regresie manuală pe classic standalone (e demo-ul implicit, cel mai vizibil).
### Audit a11y motoare existente (post-PR1, sub harness Playwright) ### Audit a11y motoare existente (post-PR1, sub harness Playwright)
- **Ținte tap ≥ 44px**: dpad arcade, butoane tf/choice, butonul "Trimite" din chat. - **Ținte tap ≥ 44px**: dpad arcade, butoane tf/choice, butonul "Trimite" din chat.

View File

@@ -755,6 +755,9 @@ function checkAnswer(p, given){ var exp = p.type === 'tf' ? p.tfAnswer : (p.type
function 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) {} } function 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) {} }
function confetti(){ var colors = [CFG.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 confetti(){ var colors = [CFG.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 roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } } function roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } }
/* Contract de finalizare a camerei — un singur loc pentru payload-ul parent.nextRoom
(înlocuiește duplicatele din showFinal/finale; D7). Citește totalStars + finalWord() la apel. */
function campaignDone(){ if(CFG._campaign){ try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:finalWord().charAt(0)}); }catch(e){} } }
window.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } }; window.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } };
if(CFG._campaign){ if(CFG._campaign){
/* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */ /* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */
@@ -859,11 +862,7 @@ SNIP.finalHtml = `<div id="fOverlay"><div class="fcard">
</div></div>`; </div></div>`;
SNIP.finalJs = `function showFinal(){ SNIP.finalJs = `function showFinal(){
if(CFG._campaign){ if(CFG._campaign){ campaignDone(); return; }
var L = finalWord().charAt(0);
try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:L}); }catch(e){}
return;
}
el('fStars').textContent = totalStars + ' / ' + (CFG.puzzles.length * 3) + ' \\u2605'; el('fStars').textContent = totalStars + ' / ' + (CFG.puzzles.length * 3) + ' \\u2605';
var w = finalWord(); var bw = el('fWord'); bw.innerHTML = ''; var w = finalWord(); var bw = el('fWord'); bw.innerHTML = '';
for (var j = 0; j < w.length; j++){ var s = document.createElement('span'); s.textContent = w.charAt(j); s.style.animationDelay = (j * 0.18) + 's'; bw.appendChild(s); } for (var j = 0; j < w.length; j++){ var s = document.createElement('span'); s.textContent = w.charAt(j); s.style.animationDelay = (j * 0.18) + 's'; bw.appendChild(s); }
@@ -958,9 +957,7 @@ function finale(){
done = true; done = true;
if(CFG._campaign){ if(CFG._campaign){
var s = totalStars; var L = finalWord().charAt(0); var s = totalStars; var L = finalWord().charAt(0);
say(['>> CAMERA REZOLVATA! Stele: ' + s + (L ? ' | Litera: ' + L : '')], 'ok', function(){ say(['>> CAMERA REZOLVATA! Stele: ' + s + (L ? ' | Litera: ' + L : '')], 'ok', campaignDone);
try{ parent.nextRoom({idx:CFG._campaign.idx, stars:s, letter:L}); }catch(e){}
});
return; return;
} }
var w = finalWord().split('').join(' '); var w = finalWord().split('').join(' ');

View File

@@ -90,6 +90,9 @@ function checkAnswer(p, given){ var exp = p.type === 'tf' ? p.tfAnswer : (p.type
function 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) {} } function 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) {} }
function confetti(){ var colors = [CFG.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 confetti(){ var colors = [CFG.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 roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } } function roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } }
/* Contract de finalizare a camerei — un singur loc pentru payload-ul parent.nextRoom
(înlocuiește duplicatele din showFinal/finale; D7). Citește totalStars + finalWord() la apel. */
function campaignDone(){ if(CFG._campaign){ try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:finalWord().charAt(0)}); }catch(e){} } }
window.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } }; window.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } };
if(CFG._campaign){ if(CFG._campaign){
/* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */ /* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */
@@ -359,11 +362,7 @@ function mCheck(given){
} }
} }
function showFinal(){ function showFinal(){
if(CFG._campaign){ if(CFG._campaign){ campaignDone(); return; }
var L = finalWord().charAt(0);
try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:L}); }catch(e){}
return;
}
el('fStars').textContent = totalStars + ' / ' + (CFG.puzzles.length * 3) + ' \u2605'; el('fStars').textContent = totalStars + ' / ' + (CFG.puzzles.length * 3) + ' \u2605';
var w = finalWord(); var bw = el('fWord'); bw.innerHTML = ''; var w = finalWord(); var bw = el('fWord'); bw.innerHTML = '';
for (var j = 0; j < w.length; j++){ var s = document.createElement('span'); s.textContent = w.charAt(j); s.style.animationDelay = (j * 0.18) + 's'; bw.appendChild(s); } for (var j = 0; j < w.length; j++){ var s = document.createElement('span'); s.textContent = w.charAt(j); s.style.animationDelay = (j * 0.18) + 's'; bw.appendChild(s); }

File diff suppressed because one or more lines are too long

View File

@@ -74,6 +74,9 @@ function checkAnswer(p, given){ var exp = p.type === 'tf' ? p.tfAnswer : (p.type
function 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) {} } function 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) {} }
function confetti(){ var colors = [CFG.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 confetti(){ var colors = [CFG.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 roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } } function roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } }
/* Contract de finalizare a camerei — un singur loc pentru payload-ul parent.nextRoom
(înlocuiește duplicatele din showFinal/finale; D7). Citește totalStars + finalWord() la apel. */
function campaignDone(){ if(CFG._campaign){ try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:finalWord().charAt(0)}); }catch(e){} } }
window.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } }; window.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } };
if(CFG._campaign){ if(CFG._campaign){
/* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */ /* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */
@@ -170,11 +173,7 @@ var chatIntro = CFG._campaign
: ['Salut' + (CFG.player ? ', ' + CFG.player : '') + '!'].concat(storyChunks()).concat(['Ma ajuti sa ies de aici?']); : ['Salut' + (CFG.player ? ', ' + CFG.player : '') + '!'].concat(storyChunks()).concat(['Ma ajuti sa ies de aici?']);
seq(chatIntro, next); seq(chatIntro, next);
function showFinal(){ function showFinal(){
if(CFG._campaign){ if(CFG._campaign){ campaignDone(); return; }
var L = finalWord().charAt(0);
try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:L}); }catch(e){}
return;
}
el('fStars').textContent = totalStars + ' / ' + (CFG.puzzles.length * 3) + ' \u2605'; el('fStars').textContent = totalStars + ' / ' + (CFG.puzzles.length * 3) + ' \u2605';
var w = finalWord(); var bw = el('fWord'); bw.innerHTML = ''; var w = finalWord(); var bw = el('fWord'); bw.innerHTML = '';
for (var j = 0; j < w.length; j++){ var s = document.createElement('span'); s.textContent = w.charAt(j); s.style.animationDelay = (j * 0.18) + 's'; bw.appendChild(s); } for (var j = 0; j < w.length; j++){ var s = document.createElement('span'); s.textContent = w.charAt(j); s.style.animationDelay = (j * 0.18) + 's'; bw.appendChild(s); }

View File

@@ -88,6 +88,9 @@ function checkAnswer(p, given){ var exp = p.type === 'tf' ? p.tfAnswer : (p.type
function 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) {} } function 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) {} }
function confetti(){ var colors = [CFG.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 confetti(){ var colors = [CFG.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 roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } } function roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } }
/* Contract de finalizare a camerei — un singur loc pentru payload-ul parent.nextRoom
(înlocuiește duplicatele din showFinal/finale; D7). Citește totalStars + finalWord() la apel. */
function campaignDone(){ if(CFG._campaign){ try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:finalWord().charAt(0)}); }catch(e){} } }
window.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } }; window.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } };
if(CFG._campaign){ if(CFG._campaign){
/* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */ /* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */
@@ -202,11 +205,7 @@ function mCheck(given){
} }
} }
function showFinal(){ function showFinal(){
if(CFG._campaign){ if(CFG._campaign){ campaignDone(); return; }
var L = finalWord().charAt(0);
try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:L}); }catch(e){}
return;
}
el('fStars').textContent = totalStars + ' / ' + (CFG.puzzles.length * 3) + ' \u2605'; el('fStars').textContent = totalStars + ' / ' + (CFG.puzzles.length * 3) + ' \u2605';
var w = finalWord(); var bw = el('fWord'); bw.innerHTML = ''; var w = finalWord(); var bw = el('fWord'); bw.innerHTML = '';
for (var j = 0; j < w.length; j++){ var s = document.createElement('span'); s.textContent = w.charAt(j); s.style.animationDelay = (j * 0.18) + 's'; bw.appendChild(s); } for (var j = 0; j < w.length; j++){ var s = document.createElement('span'); s.textContent = w.charAt(j); s.style.animationDelay = (j * 0.18) + 's'; bw.appendChild(s); }

View File

@@ -41,6 +41,9 @@ function checkAnswer(p, given){ var exp = p.type === 'tf' ? p.tfAnswer : (p.type
function 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) {} } function 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) {} }
function confetti(){ var colors = [CFG.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 confetti(){ var colors = [CFG.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 roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } } function roomReady(){ if(CFG._campaign){ try{ parent.roomReady(CFG._campaign.idx); }catch(e){} } }
/* Contract de finalizare a camerei — un singur loc pentru payload-ul parent.nextRoom
(înlocuiește duplicatele din showFinal/finale; D7). Citește totalStars + finalWord() la apel. */
function campaignDone(){ if(CFG._campaign){ try{ parent.nextRoom({idx:CFG._campaign.idx, stars:totalStars, letter:finalWord().charAt(0)}); }catch(e){} } }
window.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } }; window.onerror = function(msg){ if(CFG._campaign){ try{ parent.roomError(CFG._campaign.idx, String(msg)); }catch(e){} } };
if(CFG._campaign){ if(CFG._campaign){
/* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */ /* Mod cameră (§Design pct.12): ascunde h1, progres propriu, restart propriu */
@@ -98,9 +101,7 @@ function finale(){
done = true; done = true;
if(CFG._campaign){ if(CFG._campaign){
var s = totalStars; var L = finalWord().charAt(0); var s = totalStars; var L = finalWord().charAt(0);
say(['>> CAMERA REZOLVATA! Stele: ' + s + (L ? ' | Litera: ' + L : '')], 'ok', function(){ say(['>> CAMERA REZOLVATA! Stele: ' + s + (L ? ' | Litera: ' + L : '')], 'ok', campaignDone);
try{ parent.nextRoom({idx:CFG._campaign.idx, stars:s, letter:L}); }catch(e){}
});
return; return;
} }
var w = finalWord().split('').join(' '); var w = finalWord().split('').join(' ');