Files
escape-builder/TODOS.md
Claude Agent dba7fff7a2 Iterația 3: Joc-în-URL + QR (compresie, player hash, encoder QR, UI share)
Stage 1: `deflateToBase64url`/`inflateFromBase64url` (CompressionStream deflate-raw,
offline, file://); `SNIP.compressJs` cu helpers inflate (doubled backslashes).

Stage 2: `campaignShell({tplJson,masterExpr,titleExpr,nStyles,bootMode})` refactor;
gameCampaign = wrapper subțire; bootMode='inline' (nop) | 'hash' (player).

Stage 3: `playerHTML()` — toate 5 motoare inline; boot async cu `(async function(){})()`
(fix: lipsea `function`, eroare Unexpected token '{' în Chromium); MASTER din
location.hash deflate-raw; orchestrator în <script type="text/plain" id="run">.

Stage 4: Encoder QR vanilla JS — GF(256), Reed-Solomon ECC L, byte mode, versiuni 1-22,
8 măști + penalty, BCH format/version. `makeQrSvg(text, opts)` → SVG.

Stage 5: UI builder — fieldset distribuie, #btnShare/#btnCopyLink/#btnDownloadPlayer/
#btnPrintQr, #qrBox, #qrCard (print A4). baseUrl în state, deleted din cleanState().

Tests: 41/41 (6 noi @share). Fix test player: necesită __ow.enterDoor(0) după btn-start
(overworld first, then enter room). Demo files: restaurate din git (configs hardcodate în @regresie).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 12:58:41 +00:00

21 KiB
Raw Blame History

TODOS — Escape Room Builder

Backlog post-PR1 și note tehnice pentru iterațiile viitoare. Referință plan complet: ~/.gstack/projects/romfast-escape-builder/ceo-plans/2026-06-12-campania-multi-stil.md

Acest fișier e BOARD-UL DE PROGRES (sursa durabilă). Task list-ul din harness se resetează între sesiuni → se uită. Aici nu. La fiecare sesiune: citește board-ul activ de mai jos ÎNTÂI, mută [ ]→[~]→[x] pe măsură ce avansezi, commit-uiește schimbarea. Convenție: [ ] neînceput · [~] în lucru · [x] gata+verificat · [!] blocat.


▶ PR2 în curs (le iau pe rând, cerere user 2026-06-13)

  • 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).
  • Narațiune vocală (D10) — LIVRAT (vezi §„Narațiune vocală" mai jos). Smoke 25/25.
  • Unificare contract _campaign la finallibJS.campaignDone() (vezi §dedicată mai jos).
  • Audit a11y motoare — LIVRAT (vezi §dedicată mai jos). Smoke 26/26.

PR2 livrat (2026-06-13): audio camere 651025b, voce da93d84, unificare ab11089, a11y (acest commit). Rămas din Etapa 2: Adventure Mode v0. (D7 + Timer Calm + Muzică T10 + Diplomă LIVRATE — vezi §§ mai jos.)

[x] Bomberman polish (feedback user 2026-06-13) — LIVRAT

Trei probleme raportate + o lipsă, toate în gameArcade (escape-builder.html):

  • Fără sunete în joc → adăugat sfx(type) (WebAudio local în iframe, deblocat de gesturile din arcade): bomb (plasare), explosion (zgomot filtrat lowpass + thump sine), enemy (dușman ucis), powerup (arpegiu), death. beep(ok) din libJS rămâne pt. răspuns corect/greșit.
  • Rază prea mareEXPLOSION_RANGE=3 const → bombRange variabil pornind de la BASE_RANGE=1 (Bomberman clasic). Similar maxBombs de la BASE_BOMBS=1.
  • Fără powerup-uri → la spargerea unei cutii, șansă POWERUP_CHANCE=0.32 să cadă 🔥 (rază+1) sau 💣 (bombe+1). Ridicate mergând pe ele; persistă peste respawn, reset la init(). HUD arată 💣/🔥.
  • Bug prins (drop=0 inițial): powerup-ul cădea pe celula cutiei, iar checkExplosionHits îl ștergea instant ca fiind „pe o celulă de explozie". Fix: colectez brokenBoxes, dau drop DUPĂ checkExplosionHits. Teste noi: smoke #27 (rază 1 + drop supraviețuiește + pickup crește rază/bombe). Hooks __game: powerups/bombRange/maxBombs/dropPowerupAt. Verificat: smoke 27/27 + live (drop ~30%, 0 erori).

▶ BOARD ACTIV — Iterația 2 (Adventure Mode / restyle)

Direcția cerută de user (decizii confirmate, vezi HANDOFF.md). Model hibrid ca la PR1: părțile grele se prototipează în PARALEL în scratch/, verificate jucabile, apoi integrator le portează în escape-builder.html (un singur fișier, integrare secvențială).

  • S1 — fix sunet campanie (GATA — REVENIT: fix-ul inițial era incomplet, user raporta tăcere) Cauză reală: gestul din iframe NU deblochează AudioContext-ul părintelui → ctx suspended → tăcere. Fix v1 (incomplet): deblocare DOAR în handler-ul btn-start. Lacună: calea de resume (reload mid-campanie, escape-builder.html:2199) intră direct pe hartă FĂRĂ btn-start → ctx nedeblocat → camere mute. Plus resume() singur nu ajunge pe iOS Safari. Fix v2 (real): unlockAudio() + listener GLOBAL one-time pe primul gest (pointerdown+keydown, capture) — acoperă fresh ȘI resume (mers pe hartă = keydown pe părinte); buffer silențios iOS-safe; beep() se auto-vindecă dacă ctx redevine suspended. escape-builder.html:1893. Lecție testare: headless Chromium creează ctx direct running (ignoră autoplay policy) → vechiul test „ctx running" trecea trivial, NU putea prinde tăcerea. Test nou (smoke #9): gest tastatură FĂRĂ btn-start → running (cale resume) + beep self-heal din ctx suspendat. Verificat: smoke 24/24 + live MCP (ArrowDown singur deblochează). Demo-uri regenerate.
  • S2a — prototip Bomberman completscratch/bomberman-proto.html (GATA, 8/8 verificat de mine) Grid 15×13, bombe timer 2.4s + explozii lanț, cutii distructibile, AI dușmani BFS urmărire, 3 vieți + respawn cu progres puzzle PĂSTRAT (stare separată), PRNG seedat (window.__seed), uși roșii openPuzzle(id,cb) + cufăr = scăpare. Hooks window.__game. Test: scratch/verify-bomberman.mjs (+ pw-bomberman.config.mjs). Am corectat testul AI: dușmanii merg DOAR pe podea → cei închiși în cutii nu se mișcă (corect); testul curăță cutiile. Note S3: dușmanii confinați de cutii e intenționat — la integrare asigură căi sau acceptă.
  • S2b — prototip hartă overworldscratch/overworld-proto.html (GATA, 7/7 verificat de mine) Hartă tile 20×18, player top-down (săgeți/WASD/dpad), 4 uși + exit deblocat după toate. Orchestrator identic cu gameCampaign: mountRoom/roomReady/nextRoom/roomError/timeout 4s, resume localStorage, idempotență. Hooks test window.__map. Test: scratch/test-overworld.mjs. Note S3: stub makeSrcdocTPL[style].replace('__CFG__', fn); DOOR_TILES paralel cu puzzles; backslash dublu la portare. Ordine rezolvare LIBERĂ.
  • S2c — STYLES.md — direcție restyle pentru cele 5 stiluri (GATA, 775 linii). Top 3 impact/efort: terminal .line.dim fix WCAG (3.1:1→6.1:1); classic card glow + progres bar; chat header backdrop-filter + bulă NPC distinctă. Consumat de S3.
  • S3 — integrare în escape-builder.html (GATA — toate 3 pas-urile; smoke 21/21) Portează prototipurile (template literals → DUBLEAZĂ backslash-urile) + regenerează demo-urile. Pas 1: Bomberman → gameArcade. Pas 2: Overworld → gameCampaign. Pas 3: restyle 5 stiluri. - [x] Pas 1 — Bomberman în gameArcade (GATA). Păstrează openPuzzle/onDoorSolved/showFinal/ modalOpen()/roomReady; uși=N puzzle-uri, cufăr=scăpare. Demo regenerat. Smoke 21/21 + verificare gameplay 6/6 (scratch/verify-arcade-integrated.mjs) + captură. - [x] Pas 2 — Overworld în gameCampaign (GATA). Hartă top-down #overworld înlocuiește coridorul; intro→showOverworld(0), nextRoom/skip/resume→showOverworld. Contractul (mountRoom/nextRoom/roomReady/roomError/timeout/finale) NESCHIMBAT. Cod coridor șters. Cele 8 teste campanie rescrise (enterRoom/waitOverworld/__ow). Smoke 21/21 + captură. - [x] Pas 3 — restyle 5 stiluri din STYLES.md (GATA, toate 5). Classic spotlight+card glow+ tile 44px; Terminal fix WCAG .dim #2ecc71 + bordură CRT + flicker; Arcade canvas neon + dpad fizic; Chat header frosted + bule distincte + tile reward; Point fundal distinct + fix contrast .note + ușă glow. prefers-reduced-motion peste tot. Toate 5 demo-uri regenerate. Smoke 21/21 + capturi pe fiecare stil.
  • S4 — extinde tests/smoke.mjs (GATA — 24/24) — 3 teste noi: audio S1 (ctx running), navigare overworld (mers tastatură + ieșire blocată), bomberman gameplay (bombă/AI/respawn). Arbore AGENTS.md actualizat 21→24.

Stare la 2026-06-13: PR1 livrat (a42c960). Iterația 2 COMPLETĂ — S1+S2+S3+S4 livrate și verificate. Suita 24/24. Comituri: S1 52f97af, S2c a9f3065, S3 4454df9 (+pas1/pas2).


Post-PR1 (după ship-ul campaniei)

[x] Diplomă A4 print-first (§Design pct.9) — LIVRAT (2026-06-13)

Certificat A4 portret, fundal alb, chenar dublu accent, titlu serif „DIPLOMĂ DE EVADARE" (singurul serif), numele copilului = cel mai mare element. Overlay #diploma; buton „Vezi diploma →" pe finale (+ „Joacă din nou"). buildDiploma() randează: rând de stele per cameră (★★★/★★☆; camere sărite = 🔒 „sărită"), cuvântul magic în dăle (aceeași iconografie ca finalul, lacăte pentru sărite), footer = dată + „creat de {creator}" + marcaj auriu „timpul a expirat" (dacă _timerExpired). Câmp builder nou creator. Per-cameră roomStars[] (persistat în resume). @media print izolează #diploma (rest visibility:hidden, margin:20mm, print-color-adjust:exact). Verificat: smoke 31/31 (test nou „diploma") + screenshot (scratch/diploma.png: A4, 🔒 cameră sărită, footer expirat). Rămas din Etapa 2: doar Adventure Mode v0.

[x] Timer Calm (§Design pct.10 / T10) — LIVRAT (2026-06-13)

Ceas M:SS în bara chrome a campaniei. Opt-in din builder (câmp „Timp limită (minute)", default 0 = fără; cleanState coerce la întreg 0..120). Pornește la „Începe aventura" (intro necronometrat); deadline ABSOLUT în sessionStorage (_DEADLINE_KEY) → resume-ul (reload mid-campanie) NU resetează ceasul. Sub 1 minut → auriu (.low); la expirare îngheață pe 0:00 + marcaj discret (.expired, auriu opac), jocul curge nestingherit (zero penalizare, stelele rămân). Fără roșu pulsant (public copii) → reduced-motion safe by default. exemplu-campanie.html regenerat (rămâne fără timer — opt-in, ca vocea). Verificat: smoke 29/29 (test nou „timer calm": format M:SS, prag auriu, freeze la expirare, jocul continuă, resume păstrează ceasul). Commit: (acest commit). Următorul: muzică T10 (accelerare sub 1 min — depinde de timer).

[x] Muzică ambient accelerată la timer (PR2 / T10) — LIVRAT (2026-06-13)

Opt-in din builder (checkbox music, default off). Orchestrator-only: părintele deține AudioContext (reutilizează beep._ctx, deblocat de gestul global); camerele NU știu de muzică. Arpegiu calm pe pentatonică minoră (_mTick, oscilatoare sine scurte la ~520ms); tempo accelerează spre ~1.8× pe ultimul minut (musicTempoFactor, legat de _deadline-ul Timer Calm). Buton 🎵/🔇 în bara chrome (#btn-music). Edge-uri tratate:

  • Duck pe voce: voiceSay setează u.onstart→duckMusic(true) / onend|onerror→duckMusic(false); voiceCancel și el unduck. Vocea are prioritate (gain muzică × 0.22 cât timp vorbește).
  • Fallback fără AudioContext: tot în try/catch → no-op, buton ascuns (zero penalizare).
  • pornește la „Începe aventura" + la resume; se oprește la showFinale (+ toggle).
  • fără timer → tempo rămâne 1.0 (loop calm, fără accelerare). Hook test window.__music (tempo(), state()). exemplu-campanie.html regenerat (rămâne fără muzică — opt-in, ca vocea). Verificat: smoke 30/30 (test nou „muzica ambient": opt-in, start, tempo crește sub 1 min, duck, toggle). Următorul roadmap: Diplomă (§Design pct.9) + Adventure Mode v0.

[x] Narațiune vocală (SpeechSynthesis, D10) — LIVRAT (PR2)

Feature NOU (nu doar edge-cases — voce nu exista deloc). Opt-in din builder (checkbox voice, off implicit), buton 🔊/🔇 în bara chrome a campaniei (părinte deține). Orchestrator-only voicing (uniform pe toate 5 motoarele, fără dublu-citit): poveste la „Începe aventura", întrebarea camerei la roomReady, mesajul final la showFinale. Toate edge-case-urile tratate:

  • getVoices() gol sincron → re-citire la onvoiceschanged (_pickVoice).
  • Fără voce ro-* → vocea default (nu setăm u.voice, doar u.lang='ro-RO').
  • speechSynthesis.cancel() în hideAll() → fără replici fantomă la schimbarea scenei.
  • Fără speechSynthesis în window → buton ascuns, tot devine no-op.
  • window.voiceSay expus pe părinte (pt. viitor: replici din motoare cu guard typeof). Bug prins de test: #btn-voice{display:inline-flex} bătea UA [hidden] → adăugat [hidden]{display:none}. Verificat: smoke 25/25 (test nou „voce — naratiune opt-in") + live MCP (buton, toggle, checkbox builder). 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.

[x] Unificarea contractului _campaign la final — libJS.campaignDone() (LIVRAT)

Decizie de design (abatere de la formularea inițială): NU am pus terminalul pe showFinal() din SNIP.finalJs. Motiv: showFinal() randează un modal mov #fOverlay, iar terminalul are finale stilizat în CRT (ASCII „EVADARE REUSITA" + comandă RESTART) — e on-theme intenționat; 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 _campaignsay([... CAMERA REZOLVATA ...], 'ok', campaignDone).
  • SNIP.finalJs showFinal() ramura _campaigncampaignDone().
  • 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.

[x] D7: migrarea gameClassic pe libJS — LIVRAT (2026-06-13)

Classic era ultimul motor bespoke (propriul CFG/norm/beep/confetti, star-logic inline, finalWord dublat, payload parent.nextRoom inline). Acum injectează libJS(cfg) și folosește checkAnswer/starsFor/finalWord/choiceOpts/campaignDone/roomReady/onerror din libJS ca celelalte 4 motoare → 5/5 uniform pe contractul de finalizare.

  • Decizie de design (păstrată din unificarea campaignDone): UI-ul bespoke al classicului (card sStart/sGame/sFinal) RĂMÂNE. NU am forțat modalul/overlay-ul SNIP.modal/SNIP.final — classic e quiz inline (nu deschide puzzle-uri dintr-o hartă), iar #sFinal e on-theme; forțarea SNIP-ului ar fi regresie vizuală pe demo-ul implicit (cel mai vizibil). Aceeași logică ca terminalul cu finale CRT. „Migrare pe libJS+SNIP" din formularea inițială = în practică migrare pe libJS; SNIP-ul modal nu se aplică unui motor non-modal (vezi și terminalul, care nu folosește SNIP.modal).
  • net 70 linii duplicate; campaignDone() rămâne singura sursă a payload-ului nextRoom.
  • exemplu-clasic.html regenerat (celelalte demo-uri byte-identice → classic a fost singura atingere).
  • Verificat: smoke 28/28 (regresie classic standalone test #1 + campanie E2E cu classic ca odaie test #14). Commit: bfe9be2.

[x] Known improvements — pasă de igienă (2026-06-13)

Auditate faptic. Cele mai multe erau deja livrate în PR-uri anterioare:

  • persist() try/catch → DEJA (escape-builder.html:211, D12).
  • esc(L) la point SVG → DEJA rezolvat la SURSĂ: cleanState() normalizează letter la 1 caracter alfanumeric (linia ~407, D13) → un < nu mai poate ajunge în scenă.
  • Validare 0 puzzle-uri → DEJA: export blocat cu alert + preview cu mesaj ghidant (🚪).
  • updateHud „identic" arcade/point → NU era identic (arcade arată vieți/dușmani/bombe/rază; point arată obiecte). REAL duplicat: scor + bara de litere câștigate → extras în SNIP.hudJs (hudLetters(isSolved), isSolved(j) diferă per motor: doorsSolved vs solvedFlags). Injectat în ambele; demo-uri arcade+point regenerate.
  • Stil top-level invalid la import (singurul gap rămas, T5/D8) → TOP_STYLES guard: fallback la classic + alert „Stil necunoscut …" la import; idem la load din storage corupt. Test nou smoke (stil top-level necunoscut → fallback classic + avertisment).

[x] Audit a11y motoare existente — LIVRAT (sub harness Playwright)

Auditat faptic (măsurat, nu presupus). Ce era DEJA OK (din restyle S3, nemodificat):

  • Tap ≥44px: arcade dpad 56×52, butoane classic 44/48, chat send/chip 44 — toate ✓.
  • Contrast: terminal .dim #2ecc71 pe #040f08 ≈ 9.4:1 ✓ (nota TODOS #1f9c4a era stale, schimbat la S3); classic button.hint .55 alb pe card #1a0e3d ≈ 6:1 ✓. Niciun fix necesar.
  • Focus & Enter: butoane reale peste tot (Enter/Space nativ); arcade+overworld navigabile cu săgeți (keydown pe document). „Deschide ușa" coridor = OBSOLET (overworld a înlocuit coridorul; ușile se intră mergând cu tastatura → owCheckEnter). Ce am REPARAT:
  • Tap: overworld dpad era 42×42 → 44×44 (singura țintă sub prag).
  • reduced-motion (lacune reale): .confetti (display:none) în classic + SNIP.baseCss + campanie; flipin final (SNIP.finalCss #fOverlay .fword span + campanie #fin-word span); dt-blink (cursor ușă terminal) în campanie. pop/flip/shake/bin/tile-pop/tp/door-glow/crt-flicker erau deja acoperite. NB: flipin/pop au backwards fill → animation:none le revine la starea vizibilă (nu rămân ascunse — verificat).
  • aria: #dots role=group+label; fiecare dot role=img cu aria-label ce reflectă STAREA (neînceputa/în curs/rezolvata) via setDot; dpad arcade+overworld au aria-label (Sus/Jos/Stânga/ Dreapta/Pune bomba); spacerele .sp overworld → aria-hidden+tabindex=-1. Test nou smoke #9c (a11y — tap>=44px + aria + reduced-motion, cu emulateMedia reducedMotion). 26/26. Referință: §Design pct. 13 (TD5, PR2); D19 din plan.

[x] Adventure Mode v0 — LIVRAT (2026-06-13)

Opt-in flag adventure (default off) → campanie cu ramificare per-răspuns. Zero regresie non-adventure.

E0adventure: false în defaultState(); checkbox data-gb="adventure" în builder (lângă voice/music); var ADVENTURE = !!MASTER.adventure în orchestrator.

E1_lastGiven în libJS; checkAnswer setează _lastGiven pe succes; campaignDone() calculează cheia branch ('*' free, text pentru tf, index string pentru choice) și o trimite în payload nextRoom.

E2resolveBranch(idx, key): non-adventure→liniar; adventure→p.branch[key] (fallback branch['*'], apoi liniar idx+1); 'end'/out-of-range→'end'. nextRoom pe ramura ADVENTURE: 'end'→owExitUnlocked=true+ showOverworld cu exit deblocat; număr→owUnlocked[dest]=true+owTargetIdx=dest+showOverworld(dest).

E3owCheckEnter: blocat dacă ADVENTURE && !owUnlocked[d.idx]; exit folosește owExitUnlocked în loc de owAllDone(). owRefreshDoors: stilul .locked (dim+🔒) pentru ușile nedeblcate; hint/exit folosesc owExitUnlocked. window.__ow.state: adaugă owUnlocked/owExitUnlocked.

E4saveProgress: adaugă doneList, owUnlocked, owExitUnlocked, target. tryResume: pe ADVENTURE reconstruiește din doneList (non-contiguu), nu bucla liniară 0..saved.idx.

E5buildDiploma: camerele ADVENTURE && !roomDone[i] → „neexplorată" (nu ☆☆☆ înșelător).

E6 — Builder UI: normalizePuzzle garantează p.branch={}; cleanState clampa țintele + strip branch când !adventure; puzzleCard afișează dropdown-uri ramificare per-puzzle (free=1, tf=2, choice=1/opțiune); data-fb/data-bkey handler în input listener; adventure change → renderPuzzles().

Verificat: smoke 35/35 (4 teste noi: branch-jump, resume non-contiguu, regression non-adventure, tf branch).

Iterația 3 — Joc-în-URL + QR LIVRAT (2026-06-14)

Scope livrat:

  • Stage 1 — Compresie URL: deflateToBase64url/inflateFromBase64url (CompressionStream native, offline, file://). CS_OK guard. SNIP.compressJs cu helpers inflate (doubled backslashes).
  • Stage 2 — Refactor campaignShell: parametrizat bootMode='inline'|'hash'. Zero schimbare de comportament pentru inline (35/35 smoke). chrome-title + document.title setate din MASTER.
  • Stage 3 — playerHTML() + boot din hash: player universal (toate 5 motoare); async IIFE (corect (async function(){...})()) decomprimă hash → setează MASTER → apendează orchestratorul din <script type="text/plain" id="run">. No-hash → "Niciun joc în acest link."
  • Stage 4 — Encoder QR: GF(256), Reed-Solomon, byte mode, ECC L, auto-versiune 1-22, selecție mască + BCH format/version. makeQrSvg(text, opts) → SVG string sau null.
  • Stage 5 — UI builder: fieldset „Distribuie (link+QR)", #btnShare/#btnCopyLink/ #btnDownloadPlayer/#btnPrintQr, #qrBox, #qrCard (print A4). baseUrl în state (deleted din cleanState() → nu intră în payload). Butoane disabled dacă !CS_OK.
  • Stage 6 — Docs: 41/41 smoke, TODOS/AGENTS actualizate.

DEFER (fast-follow): Import-din-hash în builder (escape-builder.html#hash → editabil). Reutilizează inflate+Save/Load; adaugă cale async la boot-ul builder (azi sincron).

Verificat: smoke 41/41. Capabilitate: 12+ puzzle-uri → ~636B → QR v10 L; 30+ puzzle-uri → ~750B. Scan manual cu telefon real: TODO (notat în HANDOFF).


Known improvements (oricând)

Toate cele listate inițial au fost rezolvate — vezi „[x] Known improvements — pasă de igienă" mai sus (updateHud dedup în SNIP.hudJs, persist guard D12, esc/letter D13, validare 0 puzzle, stil invalid la import T5/D8). Adaugă aici lucruri noi pe măsură ce apar.