// site-jogador/js/survey.jsx
// MOTOR DE PESQUISAS do site: renderizador generico dirigido por JSON.
// A pesquisa em si (secoes, perguntas, opcoes, ramificacao, copy da abertura e
// do fim, nos 3 idiomas) mora em data/surveys/<slug>.json. Pesquisa nova =
// arquivo JSON novo + 1 linha em genz_surveys. Este arquivo nao muda.
//
// Tipos de pergunta: single | multi (com max) | scale (1-5) | matrix (itens x 1-5)
//                    | textarea. Todo item aceita `comment:true` (comentario
//                    opcional) e opcao com `other:true` (texto livre).
// Ramificacao: `showIf:{idDaPergunta:[valores]}` em secao ou pergunta.
//
// PROGRESSO NUNCA SE PERDE (exigencia do Henrique), 2 camadas:
//   1. localStorage a cada resposta  -> F5, fechar aba, queda de luz
//   2. rascunho no servidor (3s)     -> limpar cache, trocar de aparelho
// Ao voltar, a barra de retomada devolve respostas + secao + idioma.
//
// GATE: conta no site com Discord e Steam vinculada (a Steam e' como a
// recompensa acha o personagem). Quem nao passa ve o portao com o caminho.
//
// CSS: portado do mockup aprovado (wiki/pesquisa-beta/), TODO escopado em .gzsv
// pra nao colidir com o resto do site (nomes tipo .card/.opt/.btn sao genericos).

(function () {
  var SLUG = 'beta-s0';
  var DEF_URL = 'data/surveys/' + SLUG + '.json?v=1';
  var LS_KEY = 'gz_survey_' + SLUG;
  var AUTOSAVE_MS = 3000;

  var React = window.React;
  var useState = React.useState, useEffect = React.useEffect, useRef = React.useRef;

  // ---------- helpers puros (sem React; testaveis) ----------------------------

  // A resposta casa a condicao se QUALQUER valor marcado estiver na lista.
  // Toda chave do showIf precisa casar (E logico entre perguntas).
  function condOk(cond, a) {
    if (!cond) return true;
    for (var k in cond) {
      var v = a[k];
      if (v === undefined || v === null) return false;
      var arr = Array.isArray(v) ? v : [String(v)];
      var ok = false;
      for (var i = 0; i < cond[k].length; i++) if (arr.indexOf(cond[k][i]) >= 0) ok = true;
      if (!ok) return false;
    }
    return true;
  }
  function visSections(def, a) {
    return (def.sections || []).filter(function (s) { return condOk(s.showIf, a); });
  }
  function visQs(sec, a) {
    return (sec.qs || []).filter(function (q) { return condOk(q.showIf, a); });
  }
  function qAnswered(q, a) {
    var v = a[q.id];
    if (q.type === 'multi') return Array.isArray(v) && v.length > 0;
    if (q.type === 'matrix') {
      if (!v) return false;
      for (var i = 0; i < q.rows.length; i++) if (!v[q.rows[i].v]) return false;
      return true;
    }
    return v !== undefined && v !== null && String(v).trim() !== '';
  }
  function secOk(sec, a) {
    if (sec.optional) return true;
    var qs = visQs(sec, a);
    for (var i = 0; i < qs.length; i++) if (qs[i].req && !qAnswered(qs[i], a)) return false;
    return true;
  }

  // ---------- CSS (escopado em .gzsv) ----------------------------------------
  var CSS = [
    '.gzsv{--sbg:#222;--sbg2:#2a2a2a;--sbg3:#333;--sac:#a8ff00;--stx:#fff;',
    '--sdim:rgba(255,255,255,.55);--smute:rgba(255,255,255,.32);--sline:rgba(255,255,255,.12);',
    '--slinea:rgba(168,255,0,.25);--swarn:#ffb020;--shead:Rajdhani,system-ui,sans-serif;',
    "--smono:'Share Tech Mono',ui-monospace,monospace;",
    '--scut:polygon(0 0,calc(100% - 10px) 0,100% 10px,100% 100%,10px 100%,0 calc(100% - 10px));',
    // padding-top = altura do menu fixo do site (64px); a barra da pesquisa gruda logo abaixo dele
    'background:var(--sbg);color:var(--stx);font-family:var(--shead);font-weight:600;font-size:17px;line-height:1.5;min-height:100vh;padding-top:64px}',
    '.gzsv *{box-sizing:border-box}',
    '.gzsv .eyebrow{font-family:var(--smono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--sac);display:flex;align-items:center;gap:9px}',
    ".gzsv .eyebrow::before{content:'';width:22px;height:2px;background:var(--sac);flex:none}",
    // topo
    '.gzsv .top{position:sticky;top:64px;z-index:50;background:rgba(34,34,34,.94);backdrop-filter:blur(8px);border-bottom:1px solid var(--sline)}',
    '.gzsv .topbar{max-width:980px;margin:0 auto;padding:11px 22px;display:flex;align-items:center;gap:14px}',
    '.gzsv .brand{font-family:var(--smono);font-size:11px;letter-spacing:.2em;color:var(--sac);text-transform:uppercase}',
    '.gzsv .logo{width:25px;height:25px;object-fit:contain;flex:none;filter:drop-shadow(0 0 8px rgba(168,255,0,.5))}',
    '.gzsv .top-sp{flex:1}',
    '.gzsv .prog-wrap{height:3px;background:rgba(255,255,255,.08)}',
    '.gzsv .prog{height:3px;background:var(--sac);width:0;transition:width .35s ease;box-shadow:0 0 10px rgba(168,255,0,.6)}',
    '.gzsv .pill{font-family:var(--smono);font-size:10px;letter-spacing:.12em;padding:4px 9px;border:1px solid var(--sline);color:var(--sdim);text-transform:uppercase;background:transparent;cursor:pointer;font-weight:400}',
    '.gzsv .pill.on{border-color:var(--sac);color:var(--sac);background:rgba(168,255,0,.08)}',
    '.gzsv .langs{display:flex;gap:5px}',
    // shell
    '.gzsv .wrap{max-width:980px;margin:0 auto;padding:34px 22px 120px;display:grid;grid-template-columns:186px 1fr;gap:38px;align-items:start}',
    '.gzsv .wrap.solo{grid-template-columns:1fr;gap:0;justify-items:center;align-content:center;min-height:calc(100vh - 156px);padding:30px 22px 60px}',
    '.gzsv .wrap.solo .col{max-width:660px;width:100%}',
    '.gzsv .rail{position:sticky;top:142px;display:flex;flex-direction:column;gap:1px}',
    '.gzsv .rail button{all:unset;cursor:pointer;display:flex;align-items:center;gap:10px;padding:7px 9px;font-family:var(--smono);font-size:10.5px;letter-spacing:.08em;color:var(--smute);text-transform:uppercase;border-left:2px solid transparent}',
    '.gzsv .rail button:hover{color:var(--sdim);background:rgba(255,255,255,.03)}',
    '.gzsv .rail button.on{color:var(--sac);border-left-color:var(--sac);background:rgba(168,255,0,.06)}',
    '.gzsv .rail button.done{color:var(--sdim)}',
    '.gzsv .dot{width:6px;height:6px;flex:none;border:1px solid currentColor;border-radius:50%}',
    '.gzsv .rail button.done .dot{background:var(--sac);border-color:var(--sac)}',
    '.gzsv .col{min-width:0;max-width:660px}',
    // capa / titulos
    '.gzsv .cover{position:relative;border:1px solid var(--slinea);clip-path:var(--scut);overflow:hidden;line-height:0;margin-bottom:22px}',
    '.gzsv .cover img{width:100%;height:auto;display:block}',
    ".gzsv .cover::after{content:'';position:absolute;left:0;top:0;bottom:0;width:5px;background:var(--sac);box-shadow:0 0 26px rgba(168,255,0,.75)}",
    '.gzsv .lp-title{font-family:var(--shead);font-weight:700;text-transform:uppercase;font-size:clamp(30px,5.4vw,50px);line-height:.96;margin:13px 0 0;text-wrap:balance}',
    '.gzsv .lp-title span{color:var(--sac)}',
    // portao
    '.gzsv .gate{text-align:center}',
    '.gzsv .gate .ic{font-size:42px;line-height:1}',
    '.gzsv .gate h2{font-size:clamp(26px,4.4vw,36px);font-weight:700;text-transform:uppercase;margin:14px 0 10px;text-wrap:balance}',
    '.gzsv .steps{display:flex;flex-direction:column;gap:9px;margin:20px 0 0;text-align:left}',
    '.gzsv .step{display:flex;gap:13px;align-items:flex-start;padding:13px 15px;background:var(--sbg3);clip-path:polygon(0 0,calc(100% - 7px) 0,100% 7px,100% 100%,7px 100%,0 calc(100% - 7px))}',
    '.gzsv .step.ok{background:rgba(168,255,0,.08);border:1px solid var(--slinea)}',
    '.gzsv .step .n{font-family:var(--smono);font-size:12px;color:var(--sac);flex:none;margin-top:2px;width:15px}',
    '.gzsv .step p{margin:0;font-size:15.5px}',
    '.gzsv .step small{display:block;color:var(--smute);font-size:13.5px;margin-top:2px;line-height:1.45}',
    // botoes de login: copia 1:1 do auth.jsx (familiaridade)
    '.gzsv .btn-oauth{all:unset;box-sizing:border-box;width:100%;font-family:var(--shead);font-weight:700;font-size:15px;letter-spacing:.06em;padding:15px 20px;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:12px;text-transform:uppercase;clip-path:var(--scut);transition:background .2s,box-shadow .2s}',
    '.gzsv .btn-dc{background:#5865F2;color:#fff}',
    '.gzsv .btn-dc:hover{box-shadow:0 0 22px rgba(88,101,242,.6)}',
    '.gzsv .btn-st{background:#2a475e;color:#fff}',
    '.gzsv .btn-st:hover{box-shadow:0 0 22px rgba(102,192,244,.55)}',
    '.gzsv .btn-oauth svg{flex:none}',
    // fim
    '.gzsv .done-hero{border:1px solid var(--slinea);clip-path:var(--scut);overflow:hidden;line-height:0;margin-bottom:22px}',
    '.gzsv .done-hero img{width:100%;display:block}',
    '.gzsv .guar{display:flex;gap:14px;align-items:flex-start;text-align:left}',
    '.gzsv .guar .ic2{font-size:30px;line-height:1;flex:none;margin-top:1px}',
    '.gzsv .tag-ok{display:inline-flex;align-items:center;gap:7px;font-family:var(--smono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--sac);border:1px solid var(--slinea);background:rgba(168,255,0,.08);padding:6px 11px}',
    // blocos
    '.gzsv .lead{color:var(--sdim);font-size:16.5px;margin:16px 0 0}',
    '.gzsv .card{background:var(--sbg2);border:1px solid var(--sline);clip-path:var(--scut);padding:22px 24px;margin:18px 0}',
    '.gzsv .card.accent{border-color:var(--slinea);background:rgba(168,255,0,.045)}',
    '.gzsv .card h3{margin:0 0 8px;font-size:19px;font-weight:700;text-transform:uppercase}',
    '.gzsv .card p{margin:0;color:var(--sdim);font-size:15.5px}',
    '.gzsv .reward{display:flex;gap:16px;align-items:flex-start}',
    '.gzsv .reward .ic{font-size:26px;line-height:1;flex:none;margin-top:2px}',
    '.gzsv .sec-head{margin:0 0 6px}',
    '.gzsv .sec-head h2{font-family:var(--shead);font-weight:700;text-transform:uppercase;font-size:clamp(24px,4vw,34px);line-height:1.04;margin:12px 0 0;text-wrap:balance}',
    '.gzsv .sec-sub{color:var(--sdim);font-size:15.5px;margin:9px 0 0}',
    '.gzsv .optional{display:inline-block;font-family:var(--smono);font-size:9.5px;letter-spacing:.12em;color:var(--swarn);border:1px solid rgba(255,176,32,.4);padding:2px 7px;margin-left:8px;text-transform:uppercase;vertical-align:middle}',
    // perguntas
    '.gzsv .q{border-top:1px solid var(--sline);padding:26px 0 4px}',
    '.gzsv .q:first-of-type{border-top:none}',
    '.gzsv .q-n{font-family:var(--smono);font-size:10px;letter-spacing:.14em;color:var(--sac);text-transform:uppercase;margin-bottom:7px}',
    '.gzsv .q-t{font-size:19px;font-weight:700;line-height:1.28;margin:0 0 4px;text-wrap:balance}',
    '.gzsv .q-h{color:var(--smute);font-size:14px;margin:0 0 14px}',
    '.gzsv .req{color:var(--sac)}',
    '.gzsv .opts{display:flex;flex-direction:column;gap:8px;margin-top:6px}',
    '.gzsv .opt{display:flex;align-items:flex-start;gap:11px;padding:12px 14px;background:var(--sbg3);border:1px solid transparent;cursor:pointer;transition:border-color .15s,background .15s;clip-path:polygon(0 0,calc(100% - 7px) 0,100% 7px,100% 100%,7px 100%,0 calc(100% - 7px))}',
    '.gzsv .opt:hover{border-color:var(--slinea);background:#3a3a3a}',
    '.gzsv .opt.on{border-color:var(--sac);background:rgba(168,255,0,.1)}',
    '.gzsv .opt input{margin:2px 0 0;accent-color:var(--sac);flex:none;width:16px;height:16px}',
    '.gzsv .opt span{font-size:15.5px;line-height:1.35}',
    '.gzsv .scale{display:flex;gap:7px;margin-top:6px}',
    '.gzsv .scale button{all:unset;cursor:pointer;flex:1;text-align:center;padding:14px 4px;background:var(--sbg3);border:1px solid transparent;font-family:var(--smono);font-size:15px;color:var(--sdim);transition:.15s;clip-path:polygon(0 0,calc(100% - 6px) 0,100% 6px,100% 100%,6px 100%,0 calc(100% - 6px))}',
    '.gzsv .scale button:hover{border-color:var(--slinea);color:var(--stx)}',
    '.gzsv .scale button.on{background:var(--sac);color:#111;font-weight:700}',
    '.gzsv .scale-lg{display:flex;justify-content:space-between;margin-top:7px;font-family:var(--smono);font-size:10px;letter-spacing:.08em;color:var(--smute);text-transform:uppercase;gap:12px}',
    '.gzsv .scale-lg span:last-child{text-align:right}',
    '.gzsv .mx{overflow-x:auto;margin-top:10px;border:1px solid var(--sline)}',
    '.gzsv .mx table{border-collapse:collapse;width:100%;min-width:520px}',
    '.gzsv .mx th,.gzsv .mx td{padding:9px 6px;text-align:center;border-bottom:1px solid var(--sline)}',
    '.gzsv .mx thead th{font-family:var(--smono);font-size:9px;letter-spacing:.06em;color:var(--smute);text-transform:uppercase;background:var(--sbg3);font-weight:400;line-height:1.3}',
    '.gzsv .mx tbody th{text-align:left;font-size:15px;font-weight:600;padding-left:13px;min-width:170px;position:sticky;left:0;background:var(--sbg2)}',
    '.gzsv .mx tbody tr:hover th,.gzsv .mx tbody tr:hover td{background:#303030}',
    '.gzsv .mx input{accent-color:var(--sac);width:17px;height:17px;cursor:pointer}',
    '.gzsv .mx-legend{font-family:var(--smono);font-size:10px;letter-spacing:.06em;color:var(--smute);margin-top:8px;text-transform:uppercase;line-height:1.7}',
    '.gzsv textarea,.gzsv input[type=text]{width:100%;background:rgba(0,0,0,.3);border:1px solid var(--sline);color:var(--stx);font-family:var(--shead);font-weight:600;font-size:16px;padding:12px 14px;outline:none;resize:vertical}',
    '.gzsv textarea:focus,.gzsv input[type=text]:focus{border-color:var(--sac)}',
    '.gzsv textarea{min-height:96px}',
    '.gzsv .cbox{margin-top:11px}',
    '.gzsv .cbox summary{font-family:var(--smono);font-size:10px;letter-spacing:.11em;color:var(--smute);text-transform:uppercase;cursor:pointer;padding:5px 0;list-style:none}',
    '.gzsv .cbox summary::-webkit-details-marker{display:none}',
    ".gzsv .cbox summary::before{content:'+ ';color:var(--sac)}",
    ".gzsv .cbox[open] summary::before{content:'\\2013 '}",
    '.gzsv .other-in{margin-top:8px}',
    // botoes
    '.gzsv .nav{display:flex;gap:12px;margin-top:34px;padding-top:24px;border-top:1px solid var(--sline)}',
    '.gzsv .btn{all:unset;cursor:pointer;text-align:center;font-family:var(--shead);font-weight:700;font-size:15px;letter-spacing:.06em;text-transform:uppercase;padding:15px 30px;clip-path:var(--scut);transition:.2s}',
    '.gzsv .btn-p{background:var(--sac);color:#111;flex:1}',
    '.gzsv .btn-p:hover{box-shadow:0 0 22px rgba(168,255,0,.5)}',
    '.gzsv .btn-g{border:1px solid var(--sline);color:var(--sdim);padding:15px 22px}',
    '.gzsv .btn-g:hover{border-color:var(--sac);color:var(--sac)}',
    '.gzsv .btn[disabled]{opacity:.4;cursor:not-allowed;box-shadow:none}',
    // retomada + salvo
    '.gzsv .resume{position:fixed;left:50%;transform:translateX(-50%);bottom:22px;z-index:60;background:var(--sbg2);border:1px solid var(--sac);clip-path:var(--scut);padding:16px 20px;display:flex;align-items:center;gap:16px;max-width:min(620px,calc(100vw - 32px));box-shadow:0 12px 40px rgba(0,0,0,.6)}',
    '.gzsv .resume p{margin:0;font-size:15px}',
    '.gzsv .resume .mono{display:block;font-family:var(--smono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sac);margin-bottom:3px}',
    '.gzsv .resume-b{display:flex;gap:8px;flex:none}',
    '.gzsv .resume-b button{all:unset;cursor:pointer;font-family:var(--smono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;padding:9px 13px;border:1px solid var(--sline)}',
    '.gzsv .resume-b button.go{background:var(--sac);color:#111;border-color:var(--sac);font-weight:700}',
    '.gzsv .saved{position:fixed;right:18px;bottom:18px;z-index:40;font-family:var(--smono);font-size:10px;letter-spacing:.1em;color:var(--sac);background:rgba(34,34,34,.9);border:1px solid var(--slinea);padding:7px 12px;text-transform:uppercase;opacity:0;transition:opacity .3s;pointer-events:none}',
    '.gzsv .saved.on{opacity:1}',
    '.gzsv .errbox{border:1px solid rgba(255,138,138,.5);background:rgba(255,138,138,.08);color:#ffb3b3;padding:13px 16px;margin:16px 0;font-size:15px;clip-path:var(--scut)}',
    '.gzsv .ft{display:flex;align-items:center;justify-content:center;gap:11px;margin-top:38px;padding-top:22px;border-top:1px solid var(--sline)}',
    '.gzsv .ft img{width:30px;height:30px;object-fit:contain;opacity:.85}',
    '.gzsv .ft div{font-family:var(--smono);font-size:10px;letter-spacing:.13em;color:var(--smute);text-transform:uppercase;line-height:1.7}',
    '.gzsv .ft a{color:var(--sac);text-decoration:none}',
    '@media (max-width:860px){',
    '.gzsv .wrap{grid-template-columns:1fr;gap:0;padding:24px 16px 110px}',
    '.gzsv .rail{display:none}',
    '.gzsv .card{padding:18px 17px}',
    '.gzsv .resume{flex-direction:column;align-items:stretch;gap:11px}',
    '.gzsv .nav{flex-direction:column-reverse}',
    '.gzsv .btn-g{width:100%}',
    '.gzsv .brand{display:none}}',
    '@media (prefers-reduced-motion:reduce){.gzsv *{animation:none!important;transition:none!important}}',
  ].join('\n');

  // Icones oficiais (mesmos paths do auth.jsx: o botao daqui e' o botao de la).
  function IconDiscord() {
    return (
      <svg width="20" height="20" viewBox="0 0 127.14 96.36" fill="currentColor" aria-hidden="true">
        <path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z" />
      </svg>
    );
  }
  function IconSteam() {
    return (
      <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
        <path d="M11.98 2a10.03 10.03 0 0 0-9.96 8.9l5.35 2.21a2.82 2.82 0 0 1 1.6-.49l2.38-3.45v-.05a3.77 3.77 0 1 1 3.77 3.77h-.09l-3.4 2.43v.2a2.83 2.83 0 0 1-5.6.55l-3.83-1.58A10.03 10.03 0 1 0 11.98 2zm-4.7 15.16l-1.22-.5a2.13 2.13 0 0 0 1.1 1.03 2.12 2.12 0 0 0 2.77-1.14 2.1 2.1 0 0 0 0-1.62 2.12 2.12 0 0 0-1.15-1.15 2.12 2.12 0 0 0-1.6.01l1.27.52a1.56 1.56 0 1 1-1.2 2.88zm10.34-6.24a2.51 2.51 0 1 0 0-5.03 2.51 2.51 0 0 0 0 5.03zm0-.79a1.73 1.73 0 1 1 0-3.46 1.73 1.73 0 0 1 0 3.46z" />
      </svg>
    );
  }

  function Footer() {
    return (
      <footer className="ft">
        <img src="uploads/genz-logo.png" alt="Project Gen Z" />
        <div>
          Project Gen Z<br />
          <a href="https://projectgenz.com.br" target="_blank" rel="noopener noreferrer">projectgenz.com.br</a>
        </div>
      </footer>
    );
  }

  // ---------- uma pergunta ----------------------------------------------------
  function Question(props) {
    var q = props.q, a = props.a, tx = props.tx, UI = props.UI, set = props.set, n = props.n;
    var v = a[q.id];

    function toggleMulti(val) {
      var cur = Array.isArray(v) ? v.slice() : [];
      var i = cur.indexOf(val);
      if (i >= 0) cur.splice(i, 1);
      else { if (q.max && cur.length >= q.max) return; cur.push(val); }
      set(q.id, cur);
    }

    var body = null;

    if (q.type === 'single' || q.type === 'multi') {
      var isM = q.type === 'multi';
      body = (
        <div>
          {isM && q.max ? <p className="q-h">{tx(UI.max).replace('%n', q.max)}</p> : null}
          <div className="opts">
            {q.o.map(function (op) {
              var on = isM ? (Array.isArray(v) && v.indexOf(op.v) >= 0) : (v === op.v);
              return (
                <React.Fragment key={op.v}>
                  <label className={'opt' + (on ? ' on' : '')}>
                    <input
                      type={isM ? 'checkbox' : 'radio'}
                      name={q.id}
                      checked={!!on}
                      onChange={function () { isM ? toggleMulti(op.v) : set(q.id, op.v); }}
                    />
                    <span>{tx(op.t)}</span>
                  </label>
                  {op.other && on ? (
                    <input
                      type="text" className="other-in" placeholder={tx(UI.otherPh)}
                      value={a[q.id + '__other'] || ''}
                      onChange={function (e) { set(q.id + '__other', e.target.value); }}
                    />
                  ) : null}
                </React.Fragment>
              );
            })}
          </div>
        </div>
      );
    }

    if (q.type === 'scale') {
      var nums = [1, 2, 3, 4, 5];
      body = (
        <div>
          <div className="scale">
            {nums.map(function (i) {
              return (
                <button key={i} type="button" className={String(v) === String(i) ? 'on' : ''}
                  onClick={function () { set(q.id, i); }}>{i}</button>
              );
            })}
          </div>
          <div className="scale-lg"><span>{tx(q.lo)}</span><span>{tx(q.hi)}</span></div>
        </div>
      );
    }

    if (q.type === 'matrix') {
      var mv = v || {};
      var cols = [1, 2, 3, 4, 5];
      body = (
        <div>
          <div className="mx">
            <table>
              <thead>
                <tr><th /> {cols.map(function (s) { return <th key={s}>{s}</th>; })}</tr>
              </thead>
              <tbody>
                {q.rows.map(function (r) {
                  return (
                    <tr key={r.v}>
                      <th scope="row">{tx(r.t)}</th>
                      {cols.map(function (s) {
                        return (
                          <td key={s}>
                            <input type="radio" name={q.id + '_' + r.v}
                              checked={String(mv[r.v]) === String(s)}
                              onChange={function () {
                                var nx = Object.assign({}, mv); nx[r.v] = s; set(q.id, nx);
                              }} />
                          </td>
                        );
                      })}
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
          <div className="mx-legend">{tx(q.scale.lo)} &nbsp;·&nbsp; {tx(q.scale.hi)}</div>
        </div>
      );
    }

    if (q.type === 'textarea') {
      body = (
        <textarea placeholder={tx(UI.otherPh)} value={v || ''}
          onChange={function (e) { set(q.id, e.target.value); }} />
      );
    }

    return (
      <div className="q">
        <div className="q-n">{n}{q.req ? <span className="req"> · {tx(UI.req)}</span> : null}</div>
        <p className="q-t">{tx(q.t)}</p>
        {q.h ? <p className="q-h">{tx(q.h)}</p> : null}
        {body}
        {q.comment ? (
          <details className="cbox" open={!!a[q.id + '__c']}>
            <summary>{tx(UI.comment)}</summary>
            <textarea placeholder={tx(UI.otherPh)} value={a[q.id + '__c'] || ''}
              onChange={function (e) { set(q.id + '__c', e.target.value); }} />
          </details>
        ) : null}
      </div>
    );
  }

  // ---------- pagina ----------------------------------------------------------
  function SurveyPage(props) {
    var I = window.GZ_SURVEY_I18N;
    var s0 = useState(I ? I.detect() : 'pt'); var lang = s0[0], setLang = s0[1];
    var s1 = useState(undefined); var def = s1[0], setDef = s1[1];         // JSON
    var s2 = useState(undefined); var state = s2[0], setState = s2[1];     // my_survey_state
    var s3 = useState('lp');      var view = s3[0], setView = s3[1];       // lp | sec | done
    var s4 = useState(0);         var sec = s4[0], setSec = s4[1];
    var s5 = useState({});        var a = s5[0], setAns = s5[1];
    var s6 = useState(null);      var resume = s6[0], setResume = s6[1];
    var s7 = useState(false);     var sending = s7[0], setSending = s7[1];
    var s8 = useState(null);      var err = s8[0], setErr = s8[1];
    var s9 = useState(false);     var savedOn = s9[0], setSavedOn = s9[1];

    var session = window.useGzAuth ? window.useGzAuth() : null;
    var saveTimer = useRef(null);
    var flashTimer = useRef(null);
    var booted = useRef(false);

    var tx = function (o) { return I ? I.tx(o, lang) : ''; };
    var UI = I ? I.UI : {};
    var GATE = I ? I.GATE : {};
    var ERR = I ? I.ERR : {};

    // injeta o CSS uma vez
    useEffect(function () {
      if (document.getElementById('gzsv-css')) return;
      var el = document.createElement('style');
      el.id = 'gzsv-css'; el.textContent = CSS;
      document.head.appendChild(el);
    }, []);

    // 1) carrega a definicao da pesquisa (JSON versionado no git)
    useEffect(function () {
      fetch(DEF_URL, { cache: 'no-store' })
        .then(function (r) { return r.json(); })
        .then(function (j) { setDef(j); })
        .catch(function (e) { console.error('[GZ] survey def:', e); setDef(null); });
    }, []);

    // 2) estado no servidor (aberta? posso responder? tenho rascunho?)
    useEffect(function () {
      if (session === undefined) return;             // ainda carregando a sessao
      if (!window.gzSurveyState) { setState(null); return; }
      window.gzSurveyState(SLUG).then(function (st) { setState(st); });
    }, [session]);

    // 3) retomada: escolhe o rascunho mais novo entre local e servidor
    useEffect(function () {
      if (booted.current || def === undefined || state === undefined) return;
      booted.current = true;
      var local = null;
      try { local = JSON.parse(localStorage.getItem(LS_KEY) || 'null'); } catch (e) { local = null; }
      var srv = (state && state.has_draft) ? {
        a: state.answers || {}, sec: state.section || 0, lang: state.locale || lang,
        at: state.updated_at ? Date.parse(state.updated_at) : 0,
      } : null;
      var best = null;
      if (local && srv) best = (local.at || 0) >= (srv.at || 0) ? local : srv;
      else best = local || srv;
      // so oferece retomada se tem resposta de verdade
      if (best && best.a && Object.keys(best.a).length > 0) setResume(best);
      if (state && state.completed) setView('done');
    }, [def, state]);

    // grava local na hora + agenda o rascunho no servidor
    function persist(nextA, nextSec) {
      try {
        localStorage.setItem(LS_KEY, JSON.stringify({
          a: nextA, sec: nextSec, lang: lang, at: Date.now(),
        }));
      } catch (e) {}
      setSavedOn(true);
      clearTimeout(flashTimer.current);
      flashTimer.current = setTimeout(function () { setSavedOn(false); }, 1400);

      if (!window.gzSurveySave) return;
      clearTimeout(saveTimer.current);
      saveTimer.current = setTimeout(function () {
        window.gzSurveySave(SLUG, lang, nextA, false, nextSec);   // completed=false
      }, AUTOSAVE_MS);
    }

    function set(id, v) {
      var nx = Object.assign({}, a); nx[id] = v;
      setAns(nx); persist(nx, sec);
    }

    function goSec(i) {
      setSec(i); persist(a, i);
      window.scrollTo({ top: 0, behavior: 'smooth' });
    }

    function submit() {
      if (sending || !window.gzSurveySave) return;
      // mata o autosave pendente: senao ele dispararia DEPOIS do envio e tomaria
      // 'already_answered' na cara (inofensivo no banco, feio no console)
      clearTimeout(saveTimer.current);
      setSending(true); setErr(null);
      window.gzSurveySave(SLUG, lang, a, true, sec).then(function (r) {
        setSending(false);
        if (r && r.success) {
          try { localStorage.removeItem(LS_KEY); } catch (e) {}
          setView('done');
          window.scrollTo({ top: 0, behavior: 'smooth' });
        } else {
          setErr((r && r.error) || 'rpc_error');
        }
      });
    }

    // ---- telas ----
    if (!I) return null;
    if (def === undefined || state === undefined) {
      return (
        <div className="gzsv"><div className="wrap solo"><div className="col">
          <p className="lead" style={{ textAlign: 'center' }}>{tx(UI.loading)}</p>
        </div></div></div>
      );
    }
    if (def === null) {
      return (
        <div className="gzsv"><div className="wrap solo"><div className="col">
          <div className="errbox">{tx(ERR.load_failed)}</div>
        </div></div></div>
      );
    }

    var secs = visSections(def, a);
    // A ramificacao pode ENCOLHER a lista de secoes (ex: voltar e trocar "joguei"
    // pra "nao joguei" corta 8 secoes). Sem isso o indice apontaria pro vazio.
    // Grampeia na ultima valida em vez de mexer em estado durante o render.
    var idx = Math.min(sec, Math.max(0, secs.length - 1));
    var cur = secs[idx];
    var pct = view === 'sec' && secs.length ? Math.round((idx / secs.length) * 100) : (view === 'done' ? 100 : 0);

    function TopBar() {
      return (
        <div className="top">
          <div className="topbar">
            <img className="logo" src="uploads/genz-logo.png" alt="" />
            <span className="brand">{tx(def.title)}</span>
            <span className="top-sp" />
            <div className="langs">
              {['pt', 'en', 'es'].map(function (l) {
                return (
                  <button key={l} className={'pill' + (lang === l ? ' on' : '')}
                    onClick={function () { setLang(l); I.setLang(l); }}>{l.toUpperCase()}</button>
                );
              })}
            </div>
          </div>
          <div className="prog-wrap"><div className="prog" style={{ width: pct + '%' }} /></div>
        </div>
      );
    }

    // portao: deslogado / sem conta / sem Steam
    function Gate(mode) {
      var noSteam = mode === 'steam';
      var noAccount = mode === 'account';
      return (
        <div className="gzsv">
          {TopBar()}
          <div className="wrap solo"><div className="col">
            <div className="gate">
              <div className="ic">{noSteam ? '🎮' : '🔒'}</div>
              <h2>{noSteam ? tx(GATE.steamH) : tx(GATE.loginH)}</h2>
              <p className="lead" style={{ marginTop: 10 }}>
                {noSteam ? tx(GATE.steamP) : noAccount ? tx(GATE.noAccount) : tx(GATE.loginP)}
              </p>
              <div className="steps">
                <div className={'step' + (noSteam ? ' ok' : '')}>
                  <span className="n">{noSteam ? '✓' : '1'}</span>
                  <div><p>{tx(GATE.step1)}</p><small>{tx(GATE.step1s)}</small></div>
                </div>
                <div className="step">
                  <span className="n">2</span>
                  <div><p>{tx(GATE.step2)}</p><small>{tx(GATE.step2s)}</small></div>
                </div>
              </div>
              <div className="nav" style={{ border: 'none', paddingTop: 22 }}>
                {noSteam ? (
                  <button className="btn-oauth btn-st" onClick={function () { window.gzSteamStart && window.gzSteamStart(); }}>
                    <IconSteam /><span>{tx(GATE.btnSteam)}</span>
                  </button>
                ) : (
                  <button className="btn-oauth btn-dc" onClick={function () { window.gzLoginDiscord && window.gzLoginDiscord(); }}>
                    <IconDiscord /><span>{tx(GATE.btnDiscord)}</span>
                  </button>
                )}
              </div>
            </div>
            <Footer />
          </div></div>
        </div>
      );
    }

    // ja respondeu, ou acabou de enviar
    if (view === 'done' || (state && state.completed)) {
      var D = def.done || {};
      return (
        <div className="gzsv">
          {TopBar()}
          <div className="wrap solo"><div className="col">
            {def.intro && def.intro.cover ? (
              <div className="done-hero"><img src={def.intro.cover} alt="" /></div>
            ) : null}
            <div style={{ textAlign: 'center' }}>
              <span className="tag-ok">{tx(D.tag)}</span>
              <h1 className="lp-title" style={{ marginTop: 14 }}>
                {tx(D.titleA)} <span>{tx(D.titleB)}</span>
              </h1>
              <p className="lead" style={{ marginTop: 12 }}>{tx(D.lead)}</p>
            </div>
            {D.reward ? (
              <div className="card accent"><div className="guar">
                <div className="ic2">{D.reward.icon || '🚐'}</div>
                <div><h3>{tx(D.reward.title)}</h3><p>{tx(D.reward.body)}</p></div>
              </div></div>
            ) : null}
            {D.next ? (
              <div className="card"><h3>{tx(D.next.title)}</h3><p>{tx(D.next.body)}</p></div>
            ) : null}
            <div className="nav">
              <button className="btn btn-p" onClick={function () { props.onNavigate && props.onNavigate('home'); }}>
                {tx(UI.site)}
              </button>
            </div>
            <Footer />
          </div></div>
        </div>
      );
    }

    // gates (so depois do done, pra quem ja respondeu ver o agradecimento)
    if (!state || !state.authenticated) return Gate('login');
    if (state.blocked_by === 'no_account') return Gate('account');
    if (state.blocked_by === 'steam_required') return Gate('steam');
    if (state.blocked_by === 'survey_closed' || !state.open) {
      return (
        <div className="gzsv">
          {TopBar()}
          <div className="wrap solo"><div className="col">
            <div className="gate">
              <div className="ic">⏳</div>
              <h2>{tx(def.title)}</h2>
              <p className="lead" style={{ marginTop: 10 }}>{tx(ERR.survey_closed)}</p>
            </div>
            <Footer />
          </div></div>
        </div>
      );
    }

    // banner de retomada
    var resumeBar = resume ? (
      <div className="resume">
        <p>
          <span className="mono">{tx(UI.resumeT)}</span>
          {tx(UI.resumeB)}
        </p>
        <div className="resume-b">
          <button className="go" onClick={function () {
            setAns(resume.a || {});
            setSec(resume.sec || 0);
            if (resume.lang) { setLang(resume.lang); }
            setView('sec'); setResume(null);
          }}>{tx(UI.resumeGo)}</button>
          <button onClick={function () {
            try { localStorage.removeItem(LS_KEY); } catch (e) {}
            setResume(null);
          }}>{tx(UI.resumeNew)}</button>
        </div>
      </div>
    ) : null;

    // abertura
    if (view === 'lp') {
      var N = def.intro || {};
      return (
        <div className="gzsv">
          {TopBar()}
          <div className="wrap solo"><div className="col">
            {N.cover ? <div className="cover"><img src={N.cover} alt={tx(def.title)} /></div> : null}
            <div className="eyebrow">{N.eyebrow}</div>
            <h1 className="lp-title">{tx(N.titleA)} <span>{tx(N.titleB)}</span></h1>
            <p className="lead">{tx(N.lead)}</p>
            {N.reward ? (
              <div className="card accent"><div className="reward">
                <div className="ic">{N.reward.icon || '🚐'}</div>
                <div><h3>{tx(N.reward.title)}</h3><p>{tx(N.reward.body)}</p></div>
              </div></div>
            ) : null}
            {N.before ? (
              <div className="card"><h3>{tx(N.before.title)}</h3><p>{tx(N.before.body)}</p></div>
            ) : null}
            <div className="nav">
              <button className="btn btn-p" onClick={function () { setView('sec'); window.scrollTo({ top: 0 }); }}>
                {tx(UI.start)}
              </button>
            </div>
            <Footer />
          </div></div>
          {resumeBar}
        </div>
      );
    }

    // pesquisa
    if (!cur) return null;                     // pesquisa sem secao visivel (nao deve acontecer)
    var qs = visQs(cur, a);
    var last = idx >= secs.length - 1;
    var ok = secOk(cur, a);

    return (
      <div className="gzsv">
        {TopBar()}
        <div className="wrap">
          <aside className="rail">
            {secs.map(function (s, i) {
              var cls = i === idx ? 'on' : (secOk(s, a) && i < idx ? 'done' : '');
              return (
                <button key={s.id} className={cls} onClick={function () { if (i <= idx) goSec(i); }}>
                  <span className="dot" />{tx(s.title)}
                </button>
              );
            })}
          </aside>
          <div className="col">
            <div className="sec-head">
              <div className="eyebrow">{tx(UI.sec)} {cur.code}</div>
              <h2>{tx(cur.title)}{cur.optional ? <span className="optional">{tx(UI.optional)}</span> : null}</h2>
              <p className="sec-sub">{tx(cur.sub)}</p>
            </div>
            {qs.map(function (q, i) {
              return <Question key={q.id} q={q} a={a} tx={tx} UI={UI} set={set} n={cur.code + '.' + (i + 1)} />;
            })}
            {err ? <div className="errbox">{tx(ERR[err] || ERR.rpc_error)}</div> : null}
            <div className="nav">
              {idx > 0 ? (
                <button className="btn btn-g" onClick={function () { goSec(idx - 1); }}>{tx(UI.prev)}</button>
              ) : null}
              <button className="btn btn-p" disabled={!ok || sending}
                onClick={function () { last ? submit() : goSec(idx + 1); }}>
                {sending ? tx(UI.sending) : (last ? tx(UI.send) : tx(UI.next))}
              </button>
            </div>
          </div>
        </div>
        <div className={'saved' + (savedOn ? ' on' : '')}>{tx(UI.saved)}</div>
        {resumeBar}
      </div>
    );
  }

  Object.assign(window, {
    SurveyPage: SurveyPage,
    // expostos pra teste headless (node) sem DOM
    _gzSurveyHelpers: { condOk: condOk, visSections: visSections, visQs: visQs, qAnswered: qAnswered, secOk: secOk },
  });
})();
