/* ════════════════════════════════════════════════════════════════
   AUTH-UI — peças visuais das telas de identidade (2026-09-19)

   Três adaptações da referência @_code_and_chill_, aplicadas onde o
   portal já tinha a função e faltava a forma:

     · VAULT  → medidor de força de senha
     · SIGNET → campo de código em células
     · VERSO  → a lâmina que separa senha de código no login

   Arquivo separado porque é usado por index.html, register.html,
   reset-password.html e dashboard.html (modal de 2FA do cliente).
   ════════════════════════════════════════════════════════════════ */


/* ════════════════════════════════════════════════════════════════
   MEDIDOR DE FORÇA DE SENHA — "do clipe de papel ao cofre de banco"

   Uma porta só, sempre no mesmo lugar, e a ferragem DESENHADA sobre ela
   traço a traço. Apagar um caractere apaga o desenho pelo mesmo caminho
   por onde ele veio — o nível é uma linha do tempo, não um interruptor.
   ════════════════════════════════════════════════════════════════ */

/* O PAINEL FLUTUA, NÃO EMPURRA.
   Na primeira versão ele abria empurrando tudo para baixo: os campos
   saltavam enquanto a pessoa digitava, e numa janela mais baixa a página
   passava a rolar por causa dele. Agora ele paira ancorado no campo de
   senha, então a altura da tela não muda enquanto se digita — o que
   resolve o salto e a rolagem de uma vez.

   Fundo opaco de propósito: é uma peça que passa por cima de outra. */
.field { position: relative; }

.forca {
  --cor-nivel: #ff4d6d;
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  z-index: 20;
  display: grid;
  gap: 11px;
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-color: color-mix(in srgb, var(--cor-nivel) 34%, transparent);
  border-radius: 12px;
  background: #100E09;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.62);
  padding: 12px 13px 11px;
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition:
    opacity 0.26s var(--ease, ease),
    transform 0.26s var(--ease, cubic-bezier(0.22, 1, 0.36, 1)),
    border-color 0.4s;
}

.forca.visivel {
  opacity: 1;
  transform: none;
}

/* ── As quatro barras ──
   Cada faixa tem cor PRÓPRIA e enche dentro de si. Na primeira versão as
   quatro assumiam a cor do nível atual, o que apagava o trajeto: dava para
   ver onde a senha estava, não de onde ela veio. */
.forca-barra {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 5px;
}

.forca-barra i {
  display: block;
  height: 4px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
}

.forca-barra i > b {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: var(--cor-seg);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.34s var(--ease, cubic-bezier(0.22, 1, 0.36, 1));
}

/* ── As quatro barras ──
   Cada faixa tem cor própria e enche dentro de si: a barra mostra o trajeto
   inteiro, não só onde a senha está agora. */
.forca-barra {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 5px;
}

.forca-barra i {
  display: block;
  height: 4px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
}

.forca-barra i > b {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: var(--cor-seg);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.34s var(--ease, cubic-bezier(0.22, 1, 0.36, 1));
}

/* ── A porta ──
   Uma porta só, sempre no mesmo lugar, e a ferragem desenhada sobre ela.
   NADA de escala nem rotação: a versão anterior fazia cross-fade de quatro
   ícones entrando com scale(.6) rotate(-14deg), e o que se via era um ícone
   saltando, não uma tranca sendo montada.

   Cada traço tem pathLength="1", então o nível controla o stroke-dashoffset:
   a peça se desenha de 1 para 0 ao entrar e se apaga de 0 para 1 ao sair.
   Durante a troca as duas convivem meio desenhadas — que é exatamente o que
   a referência mostra, o arame do clipe endireitando até virar a haste do
   cadeado. */
/* A placa é um objeto com luz caindo em cima, não um buraco escuro. Na
   referência ela lê como aço escovado: um gradiente diagonal com um brilho
   perto da borda. Era isso que o Victor viu como "textura". */
.forca-caixa {
  position: relative;
  flex: none;
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  background:
    linear-gradient(107deg,
      rgba(255, 255, 255, 0.11) 0%,
      rgba(255, 255, 255, 0.05) 34%,
      rgba(0, 0, 0, 0.22) 100%),
    #16130D;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07);
}

.forca-caixa svg {
  position: absolute;
  width: 46px;
  height: 46px;
}

.forca-caixa path {
  fill: none;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition:
    stroke-dashoffset 0.52s var(--ease, cubic-bezier(0.22, 1, 0.36, 1)),
    stroke 0.38s;
}

/* A placa fica sempre desenhada. O batente — a linha vertical que cruzava o
   meio dela — saiu: passava por cima da ferragem e lia como um risco no
   cadeado. A cor do nível vive agora só nas barras e no rótulo. */
.forca-porta path {
  stroke: rgba(255, 255, 255, 0.15);
  stroke-dashoffset: 0;
}

/* A FERRAGEM NÃO TOMA A COR DO NÍVEL.
   O autor enuncia a regra na legenda de outro componente dele: "colour is
   reserved for verdicts: attention is just light". No VAULT ela aparece assim
   — a tranca é de aço em todos os níveis, e a cor vive só no batente, nas
   barras e no rótulo. A peça mostra QUANTO você construiu pela forma; a cor
   fica guardada para dizer se está bom ou não. */
.forca-ferragem path {
  stroke: rgba(244, 239, 230, 0.82);
}

.forca[data-nivel="0"] .forca-ferragem[data-nivel="0"] path,
.forca[data-nivel="1"] .forca-ferragem[data-nivel="1"] path,
.forca[data-nivel="2"] .forca-ferragem[data-nivel="2"] path,
.forca[data-nivel="3"] .forca-ferragem[data-nivel="3"] path {
  stroke-dashoffset: 0;
}

/* ── Texto ── */
.forca-linha {
  display: flex;
  align-items: center;
  gap: 13px;
}

.forca-texto {
  display: grid;
  gap: 3px;
  min-width: 0;
}

.forca-rotulo {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--cor-nivel);
  transition: color 0.38s;
}

.forca-tempo {
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--text-dim, #A89B82);
}

.forca-bits {
  font-family: var(--font-mono, monospace);
  font-size: 10.5px;
  letter-spacing: 0.07em;
  color: var(--text-mute, #6E6347);
}

/* ── As regras que o servidor cobra ── */
.forca-regras {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.forca-regras li {
  font-family: var(--font-mono, monospace);
  font-size: 10px;
  letter-spacing: 0.08em;
  padding: 3px 8px;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.10);
  color: var(--text-mute, #6E6347);
  transition: color 0.26s, border-color 0.26s, background 0.26s;
}

.forca-regras li.ok {
  color: #00e5a0;
  border-color: rgba(0, 229, 160, 0.4);
  background: rgba(0, 229, 160, 0.08);
}


/* ════════════════════════════════════════════════════════════════
   CÓDIGO EM CÉLULAS — "vazio é relevo, preenchido é fundo"

   Todas as sombras da linha saem do MESMO par (--lx, --ly). Célula
   vazia se projeta para fora; célula com dígito afunda. O progresso
   fica legível pela forma, não só pela cor.
   ════════════════════════════════════════════════════════════════ */

/* O input original continua sendo a fonte da verdade — some da vista,
   não da árvore. */
input.otp-host {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
  clip-path: inset(50%);
}

.otp {
  --lx: 0;
  --ly: -1;
  display: flex;
  gap: 8px;
}

.otp .otp-celula {
  flex: 1 1 0;
  min-width: 0;
  height: 58px;
  padding: 0;
  text-align: center;
  font-family: var(--font-mono, monospace);
  font-size: 24px;
  font-weight: 500;
  color: var(--text, #F4EFE6);
  caret-color: var(--accent, #F5A623);
  background: var(--bg-3, #1A1710);
  border: 1px solid rgba(255, 255, 255, 0.11);
  border-radius: 11px;
  outline: none;
  /* Em relevo: a sombra cai no lado oposto ao da luz.
     Guardada em --relevo porque a regra de foco precisa repetir o mesmo
     valor — ver o comentário em :focus logo abaixo. */
  --relevo:
    calc(var(--lx) * -5px) calc(var(--ly) * -5px) 12px rgba(0, 0, 0, 0.55),
    inset calc(var(--lx) * 1.5px) calc(var(--ly) * 1.5px) 0 rgba(255, 255, 255, 0.07);
  box-shadow: var(--relevo);
  transition:
    background 0.24s, border-color 0.24s, color 0.24s,
    box-shadow 0.24s, transform 0.24s var(--ease, ease);
}

/* Preenchida = AFUNDADA E MAIS CLARA, não dourada.
   Mesma regra: "attention is just light". O dígito já se distingue pelo
   relevo e pelo brilho; gastar o âmbar aqui queima a cor que deveria
   significar veredito, e deixa a tela dourada antes de qualquer coisa ter
   sido decidida. */
.otp .otp-celula.preenchida {
  color: #FFFBF2;
  background: #0B0A07;
  border-color: rgba(255, 255, 255, 0.2);
  transform: translateY(1px);
  /* Afundada: a sombra entra pelo lado DA luz — é a borda de cima do
     poço que escurece o fundo. */
  --relevo:
    inset calc(var(--lx) * 6px) calc(var(--ly) * 6px) 12px rgba(0, 0, 0, 0.8),
    inset calc(var(--lx) * -3px) calc(var(--ly) * -3px) 7px rgba(255, 255, 255, 0.07);
  box-shadow: var(--relevo);
}

/* O anel vai no outline para não brigar com o relevo — mas o box-shadow
   precisa ser REDECLARADO aqui, e não é redundância.
   `.field input:focus` do login.css tem especificidade (0,2,1) e vence
   `.otp .otp-celula` (0,2,0): sem esta linha, focar uma célula apagava o
   relevo inteiro e punha no lugar um anel chapado de 3px. A célula
   parecia crescer e mudar de forma ao receber o foco — o oposto do que
   o componente promete, que é relevo estável e só o preenchimento
   mudando. */
.otp .otp-celula:focus {
  border-color: var(--accent, #F5A623);
  box-shadow: var(--relevo);
  outline: 2px solid var(--accent-glow, rgba(245, 166, 35, 0.35));
  outline-offset: 2px;
}

/* Os dois !important abaixo respondem a outro: dashboard.css e admin.css
   têm `input, select, textarea { font-size: 16px !important }` em telas
   estreitas, para impedir o zoom automático do iOS. Sem revidar, as
   células de 24px caíam para 16px e ficavam perdidas dentro do quadrado.
   Os valores continuam ≥16px, então a regra do iOS segue respeitada. */
/* ── O ANEL ────────────────────────────────────────────────────────────
   Adaptado do OTP Verification V3: "4 boxes. One ring. Type the last digit
   and the row curls onto an orbit, spins a turn and a quarter, and screws
   down into one verified tile. The turn is two keyframes — move the
   transform-origin to the hub and rotate() draws the circle for you."

   Aqui a órbita vem de translate(--dx,--dy) — que leva a célula ao centro da
   linha — seguido de rotate(--ang) translateY(-raio), que a joga para fora
   naquele ângulo. Os três valores são medidos em JS porque a largura da linha
   muda entre o card do login e o modal do dashboard.

   Quem gira a volta e um quarto é o CONTÊINER, não cada célula: um transform
   só, e as peças vão junto como um mecanismo. A rotação começa em 0,34s,
   depois que o anel terminou de se formar — girar durante a montagem vira
   confusão em vez de movimento.

   É só aparência. Preenche um tempo de espera que já existia. */

.otp {
  position: relative;
}

.otp.anel {
  animation: anelGira 0.92s 0.34s cubic-bezier(0.55, 0, 0.3, 1) forwards;
}

@keyframes anelGira {
  from { transform: rotate(0deg); }
  to   { transform: rotate(450deg); }
}

.otp.anel .otp-celula {
  pointer-events: none;
  animation: anelCelula 1.26s cubic-bezier(0.55, 0, 0.3, 1) forwards;
}

@keyframes anelCelula {
  0% {
    transform: none;
  }
  28% {
    transform: translate(var(--dx), var(--dy))
               rotate(var(--ang)) translateY(calc(var(--raio) * -1));
    opacity: 1;
  }
  68% {
    transform: translate(var(--dx), var(--dy))
               rotate(var(--ang)) translateY(calc(var(--raio) * -1));
    opacity: 1;
  }
  100% {
    transform: translate(var(--dx), var(--dy))
               rotate(var(--ang)) translateY(0) scale(0.18);
    opacity: 0;
  }
}

/* A peça em que tudo se aparafusa. Fica neutra enquanto o servidor responde:
   a cor é reservada para o veredito. */
.otp-nucleo {
  --brilho: rgba(255, 255, 255, 0.16);
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  border-radius: 13px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background:
    linear-gradient(107deg, rgba(255, 255, 255, 0.10) 0%, rgba(0, 0, 0, 0.26) 100%),
    #16130D;
  opacity: 0;
  pointer-events: none;
  transition: border-color 0.3s;
}

.otp.anel .otp-nucleo {
  animation:
    nucleoEntra 0.46s 0.86s cubic-bezier(0.22, 1, 0.36, 1) forwards,
    nucleoPulsa 1.5s 1.32s ease-in-out infinite;
}

/* As duas animações convivem porque tocam propriedades diferentes: uma mexe
   em opacity e transform, a outra só em box-shadow. */
@keyframes nucleoEntra {
  from { opacity: 0; transform: scale(0.35); }
  to   { opacity: 1; transform: scale(1); }
}

@keyframes nucleoPulsa {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  50%      { box-shadow: 0 0 18px 2px var(--brilho); }
}

/* O veredito troca só a cor do pulso — sem redeclarar animação, que
   reiniciaria a entrada e daria um segundo "pop". */
.otp-nucleo.ok {
  --brilho: rgba(0, 229, 160, 0.5);
  border-color: rgba(0, 229, 160, 0.6);
}

.otp-nucleo.erro {
  --brilho: rgba(255, 77, 109, 0.5);
  border-color: rgba(255, 77, 109, 0.6);
}

/* ── O PALCO ────────────────────────────────────────────────────────
   Enquanto as células se recolhem, a órbita passa por cima do rótulo,
   do texto de instrução e do botão. Espremer o movimento até caber
   dentro da linha o mataria — a linha tem 58px de altura, que é a
   altura de uma célula.

   Então o resto sai da frente: a vizinhança apaga durante o
   recolhimento e volta quando ele se desfaz. Durante o anel não há
   nada ali para ler — o anel É o estado. */
.otp-recolhido > * {
  transition: opacity 0.3s var(--ease, ease);
}

.otp-recolhido > *:not(.otp-campo) {
  opacity: 0.1;
  pointer-events: none;
}

/* O rótulo do próprio campo também sai: a órbita passa bem em cima
   dele, que fica logo acima da linha. */
.otp-recolhido .otp-campo > label {
  opacity: 0;
  transition: opacity 0.22s var(--ease, ease);
}

/* A linha sobe na pilha para que nenhuma sobra da vizinhança corte as
   células em voo. */
.otp-recolhido .otp-campo {
  position: relative;
  z-index: 5;
}

/* ── O VISTO ────────────────────────────────────────────────────────
   O núcleo ficava só verde e pulsando. Verde é cor, e cor sozinha não
   serve a quem não a distingue: o veredito positivo passa a ter FORMA.
   O traço se escreve por stroke-dashoffset, como o selo da confirmação
   e a trava do portão. */
/* O VISTO FICAVA DEITADO, e o Victor viu antes de mim: `anelGira` gira
   o contêiner da linha em 450deg — a volta e um quarto da referencia —
   e o nucleo e filho dele, entao herdava os 90 graus que sobram. Num
   quadrado de cantos arredondados ninguem percebe; num visto, sim.

   A contra-rotacao usa a propriedade `rotate`, que e independente de
   `transform`: assim nao briga com o `scale` de nucleoEntra. E fica
   presa a `.anel`, porque so ali existe rotacao para cancelar. */
.otp.anel .otp-nucleo svg {
  rotate: -450deg;
}

.otp-nucleo svg {
  position: absolute;
  inset: 0;
  width: 29px;
  height: 29px;
  margin: auto;
}

.otp-nucleo svg path {
  fill: none;
  stroke: #00e5a0;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  filter: drop-shadow(0 0 5px rgba(0, 229, 160, 0.5));
}

.otp-nucleo.ok svg path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 0.4s var(--ease, cubic-bezier(0.22, 1, 0.36, 1)) 0.08s;
}

/* Código errado: o anel se desfaz pelo caminho de volta em vez de sumir. */
.otp.desfazendo {
  animation: none;
}

.otp.desfazendo .otp-celula {
  animation: anelVolta 0.5s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

@keyframes anelVolta {
  from {
    transform: translate(var(--dx), var(--dy))
               rotate(var(--ang)) translateY(0) scale(0.18);
    opacity: 0;
  }
  to {
    transform: none;
    opacity: 1;
  }
}

.otp.desfazendo .otp-nucleo {
  animation: nucleoSai 0.26s ease-out forwards;
}

@keyframes nucleoSai {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(0.4); }
}

@media (max-width: 768px) {
  .otp .otp-celula { font-size: 22px !important; }
}

@media (max-width: 400px) {
  .otp { gap: 6px; }
  .otp .otp-celula { height: 50px; font-size: 20px !important; border-radius: 9px; }
}


/* ════════════════════════════════════════════════════════════════
   TROCA DE PAINEL NO LOGIN — a lâmina

   Na referência a lâmina separa entrar de criar conta. Aqui ela separa
   a senha do código de duas etapas, que é a troca de painel que o
   portal realmente tem.

   fill-mode backwards de propósito: `both` deixaria o clip-path grudado
   depois do fim, e o tremor da mensagem de erro (translateX ±4px)
   sairia cortado na borda do painel.
   ════════════════════════════════════════════════════════════════ */

#loginForm,
#mfaView {
  position: relative;
  animation: painelEntra 0.5s var(--ease, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
}

@keyframes painelEntra {
  from { opacity: 0; clip-path: inset(0 100% 0 0); }
  to   { opacity: 1; clip-path: inset(0 0 0 0); }
}

#mfaView::before {
  content: '';
  position: absolute;
  inset: -16px 0;
  pointer-events: none;
  background: linear-gradient(90deg,
      transparent 0%,
      var(--accent-2, #FFD166) 47%,
      var(--accent, #F5A623) 53%,
      transparent 100%);
  animation: laminaPassa 0.58s var(--ease, cubic-bezier(0.22, 1, 0.36, 1)) forwards;
}

@keyframes laminaPassa {
  from { opacity: 0.5; transform: translateX(-100%) scaleX(0.3); }
  to   { opacity: 0;   transform: translateX(100%) scaleX(0.3); }
}

/* Varredura de progresso no botão enquanto verifica — o botão já é
   dourado, então a barra é o vazio escuro atravessando. */
.btn-login {
  position: relative;
  overflow: hidden;
}

.btn-login.loading::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 38%;
  height: 2px;
  background: rgba(11, 10, 7, 0.55);
  animation: varreduraBotao 1.1s var(--ease, ease) infinite;
}

@keyframes varreduraBotao {
  from { transform: translateX(-100%); }
  to   { transform: translateX(360%); }
}

@media (prefers-reduced-motion: reduce) {
  .forca,
  .forca-barra i > b,
  .forca-caixa path,
  .otp .otp-celula {
    transition-duration: 0.01ms;
  }

  #loginForm,
  #mfaView {
    animation: none;
  }

  #mfaView::before,
  .btn-login.loading::after {
    display: none;
  }

  .otp-nucleo.ok svg path {
    transition: none;
    stroke-dashoffset: 0;
  }

  .otp-recolhido > *,
  .otp-recolhido .otp-campo > label {
    transition: none;
  }

  /* Sem a volta e um quarto: as células apenas somem e o núcleo aparece. */
  .otp.anel,
  .otp.desfazendo .otp-celula,
  .otp.desfazendo .otp-nucleo {
    animation: none;
  }

  .otp.anel .otp-celula {
    animation: none;
    opacity: 0;
  }

  .otp.anel .otp-nucleo {
    animation: none;
    opacity: 1;
  }
}


/* ════════════════════════════════════════════════════════════════
   O PAINEL — as telas de identidade como uma peça usinada

   A referência SIGNET diz, na legenda: "the light source is your cursor.
   Move it, and the whole card is re-lit." Lá isso vale para o campo de
   código; aqui vale para o card inteiro. Campo AFUNDA, botão SE PROJETA,
   e as duas sombras saem do mesmo par (--lx, --ly). Sombra que discorda
   da direção da luz é o que faz uma tela parecer colada de pedaços.

   Os cantos em colchete vêm dos cards do portal do cliente — a linguagem
   já existia no produto, só não tinha chegado ao login.

   A régua vertical inteira é proporcional à altura da janela. Tela de
   identidade não tem nada abaixo da dobra, então rolagem ali é sempre
   respiro grande demais, nunca conteúdo.
   ════════════════════════════════════════════════════════════════ */

body { --lx: 0; --ly: -1; }

body.tela-fixa {
  overflow: hidden;
  height: 100vh;
}

.login-wrap {
  width: min(430px, calc(100vw - 32px));
}

/* ── Régua vertical elástica ── */
body:has(.login-card) { padding: clamp(14px, 3vh, 40px) 20px; }

.login-logo { margin-bottom: clamp(-60px, -5vh, -26px); }
.login-logo img { height: clamp(108px, 21vh, 260px); }

.login-card {
  --lx: 0;
  --ly: -1;
  --canto: rgba(245, 166, 35, 0.34);
  --braco: 13px;
  padding: clamp(20px, 3.1vh, 30px) clamp(20px, 3vw, 32px);
  border-radius: 14px;
  /* Colchetes nos quatro cantos: oito faixas de 1px posicionadas, sem
     elemento extra no HTML. */
  background-color: var(--bg-2);
  background-repeat: no-repeat;
  background-image:
    linear-gradient(var(--canto), var(--canto)), linear-gradient(var(--canto), var(--canto)),
    linear-gradient(var(--canto), var(--canto)), linear-gradient(var(--canto), var(--canto)),
    linear-gradient(var(--canto), var(--canto)), linear-gradient(var(--canto), var(--canto)),
    linear-gradient(var(--canto), var(--canto)), linear-gradient(var(--canto), var(--canto));
  background-size:
    var(--braco) 1px, 1px var(--braco),
    var(--braco) 1px, 1px var(--braco),
    var(--braco) 1px, 1px var(--braco),
    var(--braco) 1px, 1px var(--braco);
  background-position:
    10px 10px, 10px 10px,
    right 10px top 10px, right 10px top 10px,
    10px bottom 10px, 10px bottom 10px,
    right 10px bottom 10px, right 10px bottom 10px;
}

/* A faixa do topo varre uma vez na entrada e para. Um brilho que fica
   pulsando vira ruído numa tela que se olha todo dia. */
.login-card::before {
  animation: varreTopo 1.5s cubic-bezier(0.22, 1, 0.36, 1) 0.25s both;
}

/* Textura de placa: linhas finíssimas, quase invisíveis de perto, que tiram
   o card do "retângulo chapado" e o deixam com cara de material. É o mesmo
   truque da placa da tranca no medidor de senha — superfície, não cor. */
.login-card::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background-image: repeating-linear-gradient(
    0deg,
    rgba(255, 255, 255, 0.022) 0 1px,
    transparent 1px 3px
  );
  mix-blend-mode: overlay;
}

/* O conteúdo fica acima da textura. */
.login-card > * { position: relative; z-index: 1; }

@keyframes varreTopo {
  from { transform: translateX(-100%); }
  to   { transform: translateX(0); }
}

.login-heading { margin-bottom: 4px; }
.divider { margin: clamp(14px, 2.6vh, 28px) 0; }
.field { margin-bottom: clamp(10px, 1.7vh, 16px); }

/* ── Rótulos gravados ── */
.field label {
  font-size: 10px;
  letter-spacing: 0.16em;
  color: var(--text-mute);
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.6);
}

/* ── Campo: um poço aberto na placa ── */
.field input {
  border-radius: 9px;
  padding: 12px 15px;
  background: #0A0906;
  border: 1px solid rgba(255, 255, 255, 0.09);
  box-shadow:
    inset calc(var(--lx) * 4px) calc(var(--ly) * 4px) 9px rgba(0, 0, 0, 0.75),
    calc(var(--lx) * -1px) calc(var(--ly) * -1px) 0 rgba(255, 255, 255, 0.05);
  transition: border-color 0.24s, box-shadow 0.24s, background 0.24s;
}

.field input:focus {
  border-color: var(--accent);
  background: #0D0B07;
  box-shadow:
    inset calc(var(--lx) * 4px) calc(var(--ly) * 4px) 9px rgba(0, 0, 0, 0.75),
    0 0 0 3px var(--accent-glow-soft);
}

/* ── Botão: a peça que se projeta da placa ── */
.btn-login {
  border-radius: 9px;
  margin-top: clamp(14px, 2.4vh, 24px);
  box-shadow:
    calc(var(--lx) * -3px) calc(var(--ly) * -3px) 14px rgba(0, 0, 0, 0.55),
    inset calc(var(--lx) * 1px) calc(var(--ly) * 1px) 0 rgba(255, 255, 255, 0.3);
}

.btn-login:hover {
  box-shadow:
    calc(var(--lx) * -3px) calc(var(--ly) * -3px) 18px var(--accent-glow),
    inset calc(var(--lx) * 1px) calc(var(--ly) * 1px) 0 rgba(255, 255, 255, 0.35);
}

/* Apertado, ele afunda na placa — a mesma troca relevo/poço das células. */
.btn-login:active {
  transform: translateY(1px);
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.45);
}

.login-footer { margin-top: clamp(12px, 2vh, 20px); }

.no-access {
  margin-top: clamp(10px, 1.6vh, 16px);
  padding: clamp(11px, 1.8vh, 16px) 20px;
  border-radius: 10px;
}

@media (prefers-reduced-motion: reduce) {
  .login-card::before { animation: none; }
}


/* ════════════════════════════════════════════════════════════════
   ESPERA E CONFIRMAÇÃO DO E-MAIL
   ════════════════════════════════════════════════════════════════ */

/* ── O radar ──
   Não é um spinner girando à toa: o pulso dispara a cada pergunta real ao
   servidor (register.js reinicia a animação a cada volta do laço). O
   intervalo entre pulsos É o intervalo entre consultas. */
.faixa-espera {
  margin-top: 20px;
  padding: 14px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.radar-espera {
  position: relative;
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent, #F5A623);
  box-shadow: 0 0 8px var(--accent-glow, rgba(245, 166, 35, 0.35));
}

.radar-espera::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid var(--accent, #F5A623);
  opacity: 0;
}

.radar-espera.pulsa::after {
  animation: radarPulso 1.6s cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes radarPulso {
  from { transform: scale(1);   opacity: 0.85; }
  to   { transform: scale(4.4); opacity: 0; }
}

/* ── O selo ──
   "Struck like a wax seal": o anel se fecha e a marca é batida dentro dele.
   Desenho por stroke-dashoffset, como a ferragem do medidor de senha — o
   traço se escreve em vez de aparecer pronto. */
.confirmado {
  text-align: center;
  padding: 10px 0 4px;
}

.selo {
  width: 74px;
  height: 74px;
  margin: 0 auto 18px;
  display: block;
  overflow: visible;
}

.selo path {
  fill: none;
  stroke: #00e5a0;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  filter: drop-shadow(0 0 7px rgba(0, 229, 160, 0.4));
}

/* O anel primeiro, a marca depois — batida só acontece em selo fechado. */
.selo path:nth-child(1) { animation: seloRisca 0.62s cubic-bezier(0.65, 0, 0.35, 1) 0.1s forwards; }
.selo path:nth-child(2) { animation: seloRisca 0.34s cubic-bezier(0.34, 1.2, 0.5, 1) 0.66s forwards; }

@keyframes seloRisca {
  to { stroke-dashoffset: 0; }
}

.confirmado-titulo {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text, #F4EFE6);
  animation: confirmaSobe 0.45s var(--ease, ease) 0.78s both;
}

.confirmado-texto {
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-dim, #A89B82);
  margin-top: 10px;
  animation: confirmaSobe 0.45s var(--ease, ease) 0.88s both;
}

.confirmado .btn-login {
  animation: confirmaSobe 0.45s var(--ease, ease) 0.98s both;
}

/* Quanto falta para entrar sozinho. Espera sem indicação parece travamento —
   e quem não quiser esperar tem o botão logo acima. */
.confirmado-trilho {
  height: 2px;
  margin-top: 12px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
  animation: confirmaSobe 0.4s var(--ease, ease) 1.08s both;
}

.confirmado-trilho i {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: #00e5a0;
  transform-origin: left center;
  animation: trilhoCorre 1.3s linear 1.1s both;
}

@keyframes trilhoCorre {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@keyframes confirmaSobe {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .selo path { animation: none; stroke-dashoffset: 0; }
  .confirmado-titulo,
  .confirmado-texto,
  .confirmado .btn-login,
  .radar-espera.pulsa::after { animation: none; }
}


/* ════════════════════════════════════════════════════════════════
   A ENTRADA DO PAINEL — a peça é montada, não aparece pronta

   Quatro tempos, na ordem em que um objeto real se monta: os colchetes
   dos cantos se abrem, a placa acende, a faixa do topo varre, e os
   campos sobem escalonados. Nada aqui é decoração solta — é a mesma
   ideia do cadeado que se desenha no medidor: a peça se constrói, e
   quem olha entende do que ela é feita.

   Os braços dos cantos crescem de 0 até o comprimento final por
   @property, que é o que permite animar um comprimento guardado em
   variável. Onde @property não existe, os cantos simplesmente já
   aparecem prontos — o resto da entrada continua valendo.
   ════════════════════════════════════════════════════════════════ */

@property --braco {
  syntax: '<length>';
  inherits: false;
  initial-value: 13px;
}

.login-card {
  animation: placaAcende 0.62s cubic-bezier(0.22, 1, 0.36, 1) both,
             cantosAbrem 0.5s cubic-bezier(0.22, 1, 0.36, 1) 0.34s both;
}

@keyframes placaAcende {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

@keyframes cantosAbrem {
  from { --braco: 0px; }
  to   { --braco: 13px; }
}

/* Campos e botão sobem em fila, depois que a placa já está no lugar. */
/* fill-mode `backwards`, não `both`. Com `both` o último quadro
   (`opacity: 1`) fica cravado no elemento para SEMPRE, e qualquer regra
   posterior que queira mexer na opacidade é ignorada em silêncio — foi
   o que impediu o botão de apagar quando o anel do código se recolhe
   por cima dele. Com `backwards` a propriedade é devolvida ao elemento
   quando a entrada termina. */
.login-heading,
.divider,
.step-indicator,
.field,
.cf-turnstile,
.btn-login,
.login-footer,
#btnBack {
  animation: peçaSobe 0.5s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

.login-heading   { animation-delay: 0.16s; }
.divider         { animation-delay: 0.22s; }
.step-indicator  { animation-delay: 0.24s; }
.field:nth-of-type(1) { animation-delay: 0.28s; }
.field:nth-of-type(2) { animation-delay: 0.34s; }
.field:nth-of-type(3) { animation-delay: 0.40s; }
.field:nth-of-type(4) { animation-delay: 0.46s; }
.cf-turnstile    { animation-delay: 0.48s; }
.btn-login       { animation-delay: 0.52s; }
#btnBack         { animation-delay: 0.56s; }
.login-footer    { animation-delay: 0.60s; }

@keyframes peçaSobe {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: none; }
}

/* O medidor de senha é posicionado; animá-lo aqui brigaria com o
   transform que o faz pairar. */
.forca { animation: none !important; }

/* ── Entrar <-> criar conta ──
   São duas páginas, e a navegação entre elas piscava branco. Com a
   transição de view o cartão e a marca são a MESMA peça nas duas: o
   navegador interpola entre os dois estados em vez de trocar de tela.
   Onde não houver suporte, a navegação acontece como sempre. */
@view-transition { navigation: auto; }

.login-card { view-transition-name: painel-auth; }
.login-logo { view-transition-name: marca-auth; }

::view-transition-old(painel-auth),
::view-transition-new(painel-auth) {
  animation-duration: 0.42s;
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}

@media (prefers-reduced-motion: reduce) {
  .login-card,
  .login-heading, .divider, .step-indicator, .field,
  .cf-turnstile, .btn-login, .login-footer, #btnBack {
    animation: none;
  }
  @view-transition { navigation: none; }
}
