PageSourceSearch

https://nrconexoes.com.br/assets/js/site-gate.js?v=60

js nrconexoes.com.br collected 2026-10-08 12:28:59 UTC 28,191 bytes, 785 lines download raw bytes

1/**
2 * Portão: config pública (API ou JSON estático). Validação da senha só no navegador (nada de senha em claro ao servidor).
3 * Sessão: localStorage com horário de início.
4 */
5(function () {
6  "use strict";
7
8  var STORAGE_KEY = "nr_site_gate_v1";
9  var SESSION_MS = 30 * 60 * 1000;
10  var SESSION_GAME_MS = 2 * 60 * 1000;
11  var SESSION_RIBBON_ID = "nr-site-gate-session";
12  var CONFIG_URLS = ["/api/site-admin/public/site-gate-public.json", "assets/data/site-gate-public.json"];
13  var GAME_STATS_GET = "/api/site-admin/public/site-gate-game-stats.json";
14  var GAME_STATS_POST = "/api/site-admin/public/site-gate-game-stats";
15  /** Graus de tolerância no alinhamento do logo (desafio “difícil”). */
16  var GAME_ANGLE_TOL = 3;
17
18  var JOKES = [
19    "Sem a senha certa não rola.",
20    "Só entra quem a equipe liberou.",
21    "Fibra é sério. Senha também.",
22    "Chute óbvio não cola.",
23    "Rafael treina pesado e o site também pede senha.",
24  ];
25
26  /** Mensagem principal ao errar a senha (sem detalhes técnicos). */
27  var WRONG_PRIMARY = [
28    "Senha incorreta. Tente de novo.",
29    "Não confere. Revise e tente outra vez.",
30    "Ainda não. Conferência não passou.",
31    "Não foi dessa vez. Tente novamente.",
32    "Acesso negado para essa tentativa.",
33  ];
34
35  var WRONG_EXTRA = [
36    "Fibra no trabalho, caractere certo no teclado.",
37    "Creatina no copo, caps lock no lugar certo.",
38    "Agacha o ego, sobe a atenção nas letras.",
39    "Terra pesada, senha leve (na cabeça).",
40    "Char certo antes do Enter.",
41    "Relaxa o ombro e revisa o que digitou.",
42    "Teimosia boa; senha ainda não.",
43    "Quem tem a credencial certa é a equipe.",
44    "Respira, tenta de novo.",
45  ];
46
47  /** Terminal decorativo: sem nomes de produtos, portas ou stack. */
48  var TERMINAL_FAIL_SNIPPETS = [
49    "$ verificar-acesso\n> negado: tente novamente",
50    "$ checagem\n> não confere",
51    "$ porta-virtual\n> fechada para esta tentativa",
52    "$ fila-de-entrada\n> aguardando credencial válida",
53    "$ revisão\n> tentativa não aceita",
54    "$ sinal\n> sem autorização neste momento",
55  ];
56
57  var KONAMI = "38,38,40,40,37,39,37,39,66,65";
58  var konamiBuf = [];
59
60  /** Sem portão: só prévia CMS pedida (não todo o host preview) + caminhos /api/site-admin */
61  function shouldSkipSiteGate() {
62    try {
63      var pathname = window.location.pathname || "";
64      if (pathname.indexOf("/api/site-admin") !== -1) {
65        return true;
66      }
67      var file = ((pathname.split("/").pop() || "").split("?")[0] || "").toLowerCase();
68      if (
69        file === "preview.html" ||
70        file === "preview-fatura.html" ||
71        file === "preview-cookies.html" ||
72        file === "preview-trabalhe.html" ||
73        file === "preview-empresa-sustentavel.html"
74      ) {
75        return true;
76      }
77    } catch (e) {}
78    return false;
79  }
80
81  function pick(arr) {
82    return arr[Math.floor(Math.random() * arr.length)];
83  }
84
85  function sessionDurationMs(j) {
86    if (!j || typeof j.t !== "number") {
87      return SESSION_MS;
88    }
89    return j.mode === "game" ? SESSION_GAME_MS : SESSION_MS;
90  }
91
92  function getSessionPayload() {
93    try {
94      var raw = localStorage.getItem(STORAGE_KEY);
95      if (!raw || raw === "1") {
96        return null;
97      }
98      var j = JSON.parse(raw);
99      if (!j || typeof j.t !== "number") {
100        return null;
101      }
102      return j;
103    } catch (e) {
104      return null;
105    }
106  }
107
108  function gateSessionOk() {
109    try {
110      var raw = localStorage.getItem(STORAGE_KEY);
111      if (!raw) return false;
112      if (raw === "1") {
113        localStorage.removeItem(STORAGE_KEY);
114        return false;
115      }
116      var j = JSON.parse(raw);
117      if (!j || typeof j.t !== "number") return false;
118      if (Date.now() - j.t > sessionDurationMs(j)) {
119        localStorage.removeItem(STORAGE_KEY);
120        return false;
121      }
122      return true;
123    } catch (e) {
124      try {
125        localStorage.removeItem(STORAGE_KEY);
126      } catch (x) {}
127      return false;
128    }
129  }
130
131  function loadPublicConfig() {
132    for (var i = 0; i < CONFIG_URLS.length; i++) {
133      try {
134        var xhr = new XMLHttpRequest();
135        var u = CONFIG_URLS[i];
136        xhr.open("GET", u + "?t=" + String(Date.now()), false);
137        xhr.send(null);
138        var st = xhr.status;
139        if (st !== 200 && st !== 0) {
140          continue;
141        }
142        var j = JSON.parse(xhr.responseText);
143        if (j && typeof j === "object") {
144          return j;
145        }
146      } catch (e) {
147        /* próxima URL */
148      }
149    }
150    return null;
151  }
152
153  function gateEnabledFromConfig(cfg) {
154    if (!cfg || typeof cfg !== "object") {
155      return false;
156    }
157    var v = cfg.gate_enabled;
158    return v === true || v === 1 || v === "1" || v === "true";
159  }
160
161  function hasPasswordFromConfig(cfg) {
162    if (!cfg || typeof cfg !== "object") {
163      return false;
164    }
165    if (cfg.has_password === true || cfg.has_password === 1 || cfg.has_password === "1") {
166      return true;
167    }
168    // Compatibilidade com deploys antigos que ainda emitiam o hash no JSON público
169    var legacyHex = String(cfg.password_sha256_hex || "").trim();
170    return /^[a-f0-9]{64}$/i.test(legacyHex);
171  }
172
173  function openAccess() {
174    document.documentElement.classList.remove("site-gate-pending");
175    document.documentElement.classList.add("site-gate-booted");
176  }
177
178  /** Portão desligado no painel: limpa sessão do navegador e remove faixa (contador / IP). */
179  function clearGateSessionAndRibbon() {
180    try {
181      localStorage.removeItem(STORAGE_KEY);
182    } catch (e) {}
183    try {
184      document.documentElement.classList.remove("has-site-gate-session");
185    } catch (e2) {}
186    var rib = document.getElementById(SESSION_RIBBON_ID);
187    if (rib) {
188      try {
189        rib.remove();
190      } catch (e3) {}
191    }
192  }
193
194  function sessionDeadlineMs() {
195    try {
196      var raw = localStorage.getItem(STORAGE_KEY);
197      if (!raw || raw === "1") return 0;
198      var j = JSON.parse(raw);
199      if (!j || typeof j.t !== "number") return 0;
200      return j.t + sessionDurationMs(j);
201    } catch (e) {
202      return 0;
203    }
204  }
205
206  function formatRemaining(ms) {
207    if (ms <= 0) return "00:00";
208    var sec = Math.ceil(ms / 1000);
209    var m = Math.floor(sec / 60);
210    var s = sec % 60;
211    return (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
212  }
213
214  function installSessionRibbonIfNeeded() {
215    if (shouldSkipSiteGate()) return;
216    if (!gateSessionOk()) return;
217    if (document.getElementById(SESSION_RIBBON_ID)) return;
218    var cfgRibbon = loadPublicConfig();
219    if (!gateEnabledFromConfig(cfgRibbon) || !hasPasswordFromConfig(cfgRibbon)) {
220      return;
221    }
222
223    var sessRibbon = getSessionPayload();
224    var ribbonGame = sessRibbon && sessRibbon.mode === "game";
225    var ribbonTotalMs = ribbonGame ? SESSION_GAME_MS : SESSION_MS;
226    var kickerTxt = ribbonGame
227      ? "Acesso por desafio · janela menor"
228      : "Sessão liberada · ainda rola";
229    var unitTxt = ribbonGame
230      ? "Expira em breve. O portão volta."
231      : "Até pedir senha de novo";
232
233    var deadline = sessionDeadlineMs();
234    if (deadline <= 0) return;
235
236    document.documentElement.classList.add("has-site-gate-session");
237
238    var bar = document.createElement("div");
239    bar.id = SESSION_RIBBON_ID;
240    bar.className = "site-gate-session" + (ribbonGame ? " site-gate-session--turbo" : "");
241    bar.setAttribute("role", "complementary");
242    bar.setAttribute("aria-label", "Tempo restante da sessão do portão NR Conexões");
243    bar.innerHTML =
244      '<div class="site-gate-session__card">' +
245      '<div class="site-gate-session__glow" aria-hidden="true"></div>' +
246      '<div class="site-gate-session__row">' +
247      '<img class="site-gate-session__logo" src="assets/img/logo-branco.png" alt="" width="120" height="40" decoding="async" />' +
248      '<div class="site-gate-session__meta">' +
249      '<span class="site-gate-session__kicker">' +
250      kickerTxt +
251      "</span>" +
252      '<div class="site-gate-session__time-row">' +
253      '<span class="site-gate-session__time" id="site-gate-session-remaining">30:00</span>' +
254      '<span class="site-gate-session__unit">' +
255      unitTxt +
256      "</span>" +
257      "</div>" +
258      '<div class="site-gate-session__track" aria-hidden="true"><div class="site-gate-session__fill" id="site-gate-session-fill"></div></div>' +
259      "</div></div>" +
260      '<p class="site-gate-session__ip">' +
261      'Seu IP: <strong id="site-gate-session-ip">…</strong> · ' +
262      "Este acesso é monitorado e registrado.</p>" +
263      "</div>";
264
265    document.body.appendChild(bar);
266
267    var elTime = document.getElementById("site-gate-session-remaining");
268    var elFill = document.getElementById("site-gate-session-fill");
269    var elIp = document.getElementById("site-gate-session-ip");
270
271    function tickRibbon() {
272      var left = deadline - Date.now();
273      if (left <= 0) {
274        try {
275          localStorage.removeItem(STORAGE_KEY);
276        } catch (e) {}
277        document.documentElement.classList.remove("has-site-gate-session");
278        bar.remove();
279        window.location.reload();
280        return;
281      }
282      if (elTime) elTime.textContent = formatRemaining(left);
283      if (elFill) {
284        elFill.style.width = Math.min(100, Math.max(0, (left / ribbonTotalMs) * 100)) + "%";
285      }
286    }
287    tickRibbon();
288    var iv = window.setInterval(tickRibbon, 1000);
289
290    window.addEventListener(
291      "beforeunload",
292      function () {
293        window.clearInterval(iv);
294      },
295      { once: true }
296    );
297
298    if (elIp) {
299      function setIpFromJson(j) {
300        if (j && typeof j.ip === "string" && j.ip.trim() !== "") {
301          elIp.textContent = j.ip.trim();
302          return true;
303        }
304        return false;
305      }
306      fetch("/api/site-admin/public/client-ip.json?t=" + String(Date.now()), {
307        cache: "no-store",
308        credentials: "same-origin",
309      })
310        .then(function (r) {
311          if (r.status === 429) return null;
312          return r.ok ? r.json() : null;
313        })
314        .then(function (j) {
315          if (setIpFromJson(j)) return null;
316          return fetch("https://api.ipify.org?format=json", {
317            cache: "no-store",
318            credentials: "omit",
319          });
320        })
321        .then(function (r) {
322          if (!r || typeof r.json !== "function") return null;
323          return r.ok ? r.json() : null;
324        })
325        .then(function (j) {
326          if (j && setIpFromJson(j)) return;
327          if (elIp.textContent === "…") elIp.textContent = "-";
328        })
329        .catch(function () {
330          if (elIp.textContent === "…") elIp.textContent = "-";
331        });
332    }
333  }
334
335  function unlock() {
336    try {
337      localStorage.setItem(STORAGE_KEY, JSON.stringify({ t: Date.now() }));
338    } catch (e) {}
339    openAccess();
340    var g = document.getElementById("nr-site-gate");
341    if (g) {
342      g.remove();
343    }
344    installSessionRibbonIfNeeded();
345  }
346
347  function unlockGame() {
348    try {
349      localStorage.setItem(
350        STORAGE_KEY,
351        JSON.stringify({ t: Date.now(), mode: "game" })
352      );
353    } catch (e) {}
354    openAccess();
355    var g2 = document.getElementById("nr-site-gate");
356    if (g2) {
357      g2.remove();
358    }
359    installSessionRibbonIfNeeded();
360  }
361
362  function angularDiffDeg(a, b) {
363    var d = Math.abs(a - b) % 360;
364    return Math.min(d, 360 - d);
365  }
366
367  function setArenaStats(elOk, elFail, ok, fail) {
368    if (elOk) elOk.textContent = String(ok);
369    if (elFail) elFail.textContent = String(fail);
370  }
371
372  function fetchArenaStats(elOk, elFail) {
373    try {
374      fetch(GAME_STATS_GET + "?t=" + String(Date.now()), {
375        cache: "no-store",
376        credentials: "same-origin",
377      })
378        .then(function (r) {
379          if (r.status === 429) return null;
380          return r.ok ? r.json() : null;
381        })
382        .then(function (j) {
383          if (
384            j &&
385            typeof j.ok === "number" &&
386            typeof j.fail === "number"
387          ) {
388            setArenaStats(elOk, elFail, j.ok, j.fail);
389          }
390        })
391        .catch(function () {});
392    } catch (e) {}
393  }
394
395  function postArenaResult(which, elOk, elFail) {
396    return fetch(GAME_STATS_POST, {
397      method: "POST",
398      headers: { "Content-Type": "application/json" },
399      cache: "no-store",
400      credentials: "same-origin",
401      body: JSON.stringify({ result: which }),
402    })
403      .then(function (r) {
404        if (r.status === 429 || r.status === 422) return null;
405        return r.ok ? r.json() : null;
406      })
407      .then(function (j) {
408        if (
409          j &&
410          j.ok === true &&
411          j.stats &&
412          typeof j.stats.ok === "number" &&
413          typeof j.stats.fail === "number"
414        ) {
415          setArenaStats(elOk, elFail, j.stats.ok, j.stats.fail);
416        }
417      })
418      .catch(function () {});
419  }
420
421  function initSiteGateMiniGame(wrap) {
422    var elOk = wrap.querySelector("#site-gate-st-ok");
423    var elFail = wrap.querySelector("#site-gate-st-fail");
424    fetchArenaStats(elOk, elFail);
425
426    var btnToggle = wrap.querySelector("#site-gate-game-toggle");
427    var panel = wrap.querySelector("#site-gate-game-panel");
428    var targetImg = wrap.querySelector("#site-gate-game-target-img");
429    var userImg = wrap.querySelector("#site-gate-game-user-img");
430    var spin = wrap.querySelector("#site-gate-game-spin");
431    var degRead = wrap.querySelector("#site-gate-game-deg-readout");
432    var btnCheck = wrap.querySelector("#site-gate-game-check");
433    var toast = wrap.querySelector("#site-gate-game-toast");
434    if (!btnToggle || !panel || !targetImg || !userImg || !spin || !btnCheck) {
435      return;
436    }
437
438    var targetDeg = 0;
439
440    function applyRot(el, deg) {
441      el.style.transform = "rotate(" + String(deg) + "deg)";
442    }
443
444    function prepRound() {
445      targetDeg = Math.floor(Math.random() * 360);
446      applyRot(targetImg, targetDeg);
447      spin.value = "0";
448      applyRot(userImg, 0);
449      if (degRead) degRead.textContent = "0°";
450      if (toast) toast.textContent = "";
451    }
452
453    btnToggle.addEventListener("click", function () {
454      var willOpen = panel.hidden;
455      panel.hidden = !willOpen;
456      btnToggle.setAttribute("aria-expanded", willOpen ? "true" : "false");
457      if (willOpen) {
458        prepRound();
459      }
460    });
461
462    spin.addEventListener("input", function () {
463      var v = Number(spin.value) || 0;
464      applyRot(userImg, v);
465      if (degRead) degRead.textContent = String(v) + "°";
466    });
467
468    btnCheck.addEventListener("click", function () {
469      var userDeg = Number(spin.value) || 0;
470      if (angularDiffDeg(userDeg, targetDeg) <= GAME_ANGLE_TOL) {
471        btnCheck.disabled = true;
472        if (toast) toast.textContent = "Encaixou! Liberando acesso…";
473        postArenaResult("ok", elOk, elFail).finally(function () {
474          window.setTimeout(unlockGame, 380);
475        });
476      } else {
477        var miss = angularDiffDeg(userDeg, targetDeg);
478        if (toast) {
479          toast.textContent =
480            "Quase… falta uns " + String(Math.max(1, Math.round(miss))) + "° de precisão.";
481        }
482        postArenaResult("fail", elOk, elFail);
483        panel.classList.remove("site-gate__game-panel--nudge");
484        void panel.offsetWidth;
485        panel.classList.add("site-gate__game-panel--nudge");
486        window.setTimeout(function () {
487          panel.classList.remove("site-gate__game-panel--nudge");
488        }, 420);
489      }
490    });
491  }
492
493  function startGateCounters() {
494    var reduceMotion = false;
495    try {
496      reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
497    } catch (e) {}
498    var t0 = Date.now();
499    var elElapsed = document.getElementById("site-gate-ct-elapsed");
500    var elHype = document.getElementById("site-gate-ct-hype");
501    var mental = 0;
502    function tickElapsed() {
503      var sec = Math.floor((Date.now() - t0) / 1000);
504      var m = Math.floor(sec / 60);
505      var s = sec % 60;
506      if (elElapsed) {
507        elElapsed.textContent = (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
508      }
509    }
510    tickElapsed();
511    if (reduceMotion) {
512      setInterval(tickElapsed, 10000);
513    } else {
514      setInterval(tickElapsed, 400);
515    }
516    if (elHype) {
517      elHype.textContent = "0";
518    }
519    if (!reduceMotion) {
520      setInterval(function () {
521        mental += 1 + Math.floor(Math.random() * 3);
522        if (elHype) {
523          elHype.textContent = String(mental);
524        }
525      }, 1400);
526    }
527  }
528
529  function bootLocked() {
530    var joke = pick(JOKES);
531    var termLine = "$ liberar-site.sh\n> esperando senha da equipe...";
532
533    var wrap = document.createElement("div");
534    wrap.id = "nr-site-gate";
535    wrap.setAttribute("role", "dialog");
536    wrap.setAttribute("aria-modal", "true");
537    wrap.setAttribute("aria-labelledby", "site-gate-title");
538    wrap.innerHTML =
539      '<div class="site-gate__panel site-gate__panel--wide">' +
540      '<div class="site-gate__hero">' +
541      '<div class="site-gate__hero-glow" aria-hidden="true"></div>' +
542      '<div class="site-gate__brand">' +
543      '<img class="site-gate__brand-logo" src="assets/img/logo-branco.png" alt="" width="200" height="67" decoding="async" loading="eager" />' +
544      '<div class="site-gate__brand-orbit" aria-hidden="true"></div>' +
545      "</div></div>" +
546      '<div class="site-gate__layout">' +
547      '<div class="site-gate__col site-gate__col--form">' +
548      '<p class="site-gate__pretitle">Acesso NR</p>' +
549      '<h1 class="site-gate__title" id="site-gate-title">Antes de entrar, coloca a senha</h1>' +
550      '<p class="site-gate__joke"></p>' +
551      '<div class="site-gate__terminal" aria-hidden="true"></div>' +
552      '<form class="site-gate__form" autocomplete="off">' +
553      '<label class="site-gate__label" for="site-gate-pass">Senha</label>' +
554      '<input class="site-gate__input" id="site-gate-pass" name="nr_gate_p" type="password" ' +
555      'placeholder="Não é 123456" required maxlength="200" />' +
556      '<button class="site-gate__btn" type="submit">Entrar no site</button>' +
557      '<p class="site-gate__msg" role="status" aria-live="polite"></p>' +
558      '<p class="site-gate__msg-extra" aria-live="polite" aria-hidden="true" hidden></p>' +
559      "</form>" +
560      '<div class="site-gate__game-arena">' +
561      '<button type="button" class="site-gate__game-trigger" id="site-gate-game-toggle" aria-expanded="false" aria-controls="site-gate-game-panel">' +
562      '<span class="site-gate__game-trigger-icon" aria-hidden="true">◎</span>' +
563      '<span class="site-gate__game-trigger-label">Sem credencial? Use o <strong>desafio opcional</strong> do logo para ganhar acesso curto ao alinhar o ângulo.</span>' +
564      "</button>" +
565      '<div class="site-gate__game-panel" id="site-gate-game-panel" hidden>' +
566      '<div class="site-gate__arena-board site-gate__arena-board--panel" role="status" aria-live="polite" aria-atomic="true">' +
567      '<span class="site-gate__arena-chip site-gate__arena-chip--hit">' +
568      '<span class="site-gate__arena-glyph" aria-hidden="true">✓</span>' +
569      '<span id="site-gate-st-ok">0</span></span>' +
570      '<span class="site-gate__arena-chip site-gate__arena-chip--miss">' +
571      '<span class="site-gate__arena-glyph" aria-hidden="true">✗</span>' +
572      '<span id="site-gate-st-fail">0</span></span>' +
573      "</div>" +
574      '<div class="site-gate__game-duel">' +
575      '<div class="site-gate__game-pillar">' +
576      '<span class="site-gate__game-pillar-cap">Alvo fantasma</span>' +
577      '<div class="site-gate__game-stage">' +
578      '<img class="site-gate__game-img site-gate__game-img--target" src="assets/img/logo-branco.png" alt="" width="130" height="44" id="site-gate-game-target-img" />' +
579      "</div></div>" +
580      '<div class="site-gate__game-pillar">' +
581      '<span class="site-gate__game-pillar-cap">Teu giro</span>' +
582      '<div class="site-gate__game-stage">' +
583      '<img class="site-gate__game-img" src="assets/img/logo-branco.png" alt="" width="130" height="44" id="site-gate-game-user-img" />' +
584      "</div></div></div>" +
585      '<div class="site-gate__game-slider-row">' +
586      '<label class="site-gate__game-slider-lbl" for="site-gate-game-spin">Giro (°)</label>' +
587      '<input type="range" id="site-gate-game-spin" class="site-gate__game-slider" min="0" max="359" value="
5870" />' +
588      '<span class="site-gate__game-deg" id="site-gate-game-deg-readout" aria-hidden="true">0°</span>' +
589      "</div>" +
590      '<button type="button" class="site-gate__game-fire" id="site-gate-game-check">Conferir alinhamento</button>' +
591      '<p class="site-gate__game-toast" id="site-gate-game-toast" role="status" aria-live="polite"></p>' +
592      "</div></div>" +
593      '<p class="site-gate__foot">Site em construção e validação. Só quem tem a senha vê o resto. Senha errada não queima cabo óptico. Errou? Tenta outra vez, de boa.</p>' +
594      "</div>" +
595      '<aside class="site-gate__col site-gate__col--show" aria-label="Informações">' +
596      '<p class="site-gate__dev-kicker">NR Conexões</p>' +
597      '<p class="site-gate__dev-name">Acesso restrito.</p>' +
598      '<div class="site-gate__counter-grid" aria-live="polite">' +
599      '<div class="site-gate__counter-card">' +
600      '<span class="site-gate__counter-label">Tempo parado nesta tela</span>' +
601      '<span class="site-gate__counter-val" id="site-gate-ct-elapsed">00:00</span>' +
602      '<span class="site-gate__counter-sub">Quanto mais sobe, mais você tá curioso.</span>' +
603      "</div>" +
604      '<div class="site-gate__counter-card">' +
605      '<span class="site-gate__counter-label">Contador bobo de "ganho"</span>' +
606      '<span class="site-gate__counter-val" id="site-gate-ct-hype">0</span>' +
607      '<span class="site-gate__counter-sub">Brincadeira, não é ciência.</span>' +
608      "</div></div>" +
609      '<div class="site-gate__embed-section">' +
610      '<p class="site-gate__embed-cap">Três vídeos curtos antes da senha</p>' +
611      '<div class="site-gate__embed-row">' +
612      '<div class="site-gate__embed-wrap">' +
613      '<iframe class="site-gate__iframe" src="https://www.youtube.com/embed/M7cPMJYEXbQ?autoplay=1&amp;mute=1&amp;playsinline=1&amp;rel=0" ' +
614      'title="Rafael Vieira, treino, vídeo 1" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>' +
615      "</div>" +
616      '<div class="site-gate__embed-wrap">' +
617      '<iframe class="site-gate__iframe" src="https://www.youtube.com/embed/Kzr9C4UoCoU?autoplay=1&amp;mute=1&amp;playsinline=1&amp;rel=0" ' +
618      'title="Rafael Vieira, treino, vídeo 2" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>' +
619      "</div>" +
620      '<div class="site-gate__embed-wrap">' +
621      '<iframe class="site-gate__iframe" src="https://www.youtube.com/embed/KBBlBIu0DK8?autoplay=1&amp;mute=1&amp;playsinline=1&amp;rel=0" ' +
622      'title="Rafael Vieira, treino, vídeo 3" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>' +
623      "</div></div>" +
624      '<p class="site-gate__embed-tagline">Treino, vibe e carreira</p>' +
625      "</div>" +
626      "</aside></div></div>";
627
628    wrap.querySelector(".site-gate__joke").textContent = joke;
629    var term = wrap.querySelector(".site-gate__terminal");
630    term.textContent = termLine;
631    var cursor = document.createElement("span");
632    cursor.className = "site-gate__cursor";
633    cursor.setAttribute("aria-hidden", "true");
634    term.appendChild(cursor);
635
636    var form = wrap.querySelector(".site-gate__form");
637    var input = wrap.querySelector("#site-gate-pass");
638    var msg = wrap.querySelector(".site-gate__msg");
639    var msgExtra = wrap.querySelector(".site-gate__msg-extra");
640    var btn = wrap.querySelector(".site-gate__btn");
641
642    function clearExtraMsg() {
643      msgExtra.textContent = "";
644      msgExtra.hidden = true;
645      msgExtra.setAttribute("aria-hidden", "true");
646    }
647
648    function showWrongPassword() {
649      msg.textContent = pick(WRONG_PRIMARY);
650      msg.classList.remove("site-gate__msg--ok");
651      msgExtra.textContent = pick(WRONG_EXTRA);
652      msgExtra.hidden = false;
653      msgExtra.setAttribute("aria-hidden", "false");
654      term.textContent = pick(TERMINAL_FAIL_SNIPPETS);
655      term.appendChild(cursor);
656      form.classList.remove("site-gate__form--shake");
657      void form.offsetWidth;
658      form.classList.add("site-gate__form--shake");
659      window.setTimeout(function () {
660        form.classList.remove("site-gate__form--shake");
661      }, 500);
662    }
663
664    form.addEventListener("submit", function (ev) {
665      ev.preventDefault();
666      var raw = (input.value || "").trim();
667      msg.textContent = "";
668      clearExtraMsg();
669      msg.classList.remove("site-gate__msg--ok");
670      if (!raw) {
671        msg.textContent = "Coloca a senha aí.";
672        return;
673      }
674      btn.disabled = true;
675      // A verificação é feita no servidor — o navegador nunca vê o hash da senha.
676      // O endpoint tem rate limit por IP para evitar tentativa exaustiva.
677      fetch("/api/site-admin/public/site-gate/check", {
678        method: "POST",
679        credentials: "same-origin",
680        headers: { "Content-Type": "application/json" },
681        body: JSON.stringify({ password: raw }),
682      })
683        .then(function (r) {
684          if (r.status === 429) {
685            return { ok: false, rate: true };
686          }
687          if (r.status === 410) {
688            return { ok: true, gateOff: true };
689          }
690          return r.json().catch(function () {
691            return { ok: false };
692          });
693        })
694        .then(function (j) {
695          if (j && j.gateOff) {
696            // Admin desligou o portão durante a sessão; libera o site direto.
697            clearExtraMsg();
698            msg.classList.add("site-gate__msg--ok");
699            msg.textContent = "Portão liberado.";
700            setTimeout(unlock, 320);
701            return;
702          }
703          if (j && j.ok) {
704            clearExtraMsg();
705            msg.classList.add("site-gate__msg--ok");
706            msg.textContent = "Acesso confirmado.";
707            setTimeout(unlock, 420);
708            return;
709          }
710          if (j && j.rate) {
711            clearExtraMsg();
712            msg.textContent = "Muitas tentativas seguidas; tenta de novo em alguns minutos.";
713            btn.disabled = false;
714            return;
715          }
716          showWrongPassword();
717          btn.disabled = false;
718          input.select();
719        })
720        .catch(function () {
721          clearExtraMsg();
722          msg.textContent = "Não foi possível verificar agora. Tenta de novo em instantes.";
723          btn.disabled = false;
724        });
725    });
726
727    document.addEventListener(
728      "keydown",
729      function (ev) {
730        konamiBuf.push(ev.keyCode);
731        if (konamiBuf.length > 10) {
732          konamiBuf.shift();
733        }
734        if (konamiBuf.join(",") === KONAMI) {
735          konamiBuf.length = 0;
736          msg.classList.remove("site-gate__msg--ok");
737          msg.textContent = "Atalho registrado; o acesso continua protegido.";
738          clearExtraMsg();
739        }
740      },
741      true
742    );
743
744    document.body.insertBefore(wrap, document.body.firstChild);
745    startGateCounters();
746    initSiteGateMiniGame(wrap);
747    setTimeout(function () {
748      input.focus();
749    }, 50);
750  }
751
752  try {
753    if (shouldSkipSiteGate()) {
754      openAccess();
755      return;
756    }
757  } catch (e) {
758    openAccess();
759    return;
760  }
761
762  var cfg = loadPublicConfig();
763  // O backend novo só publica gate_enabled + has_password. Em deploys antigos vinha junto
764  // o hash da senha; aceitamos os dois formatos para a transição não quebrar caches.
765  var gateOn = gateEnabledFromConfig(cfg) && hasPasswordFromConfig(cfg);
766
767  if (!gateOn) {
768    clearGateSessionAndRibbon();
769    openAccess();
770    return;
771  }
772
773  try {
774    if (gateSessionOk()) {
775      openAccess();
776      installSessionRibbonIfNeeded();
777      return;
778    }
779  } catch (e2) {
780    openAccess();
781    return;
782  }
783
784  bootLocked();
785})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.