/*!***********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/.pnpm/next@15.5.22_@babel+core@7.29.7_@opentelemetry+api@1.9.1_@playwright+test@1.62.1_react-dom@19_zdgsas7delec4eirrgkog66ojm/node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[10].use[2]!../../node_modules/.pnpm/next@15.5.22_@babel+core@7.29.7_@opentelemetry+api@1.9.1_@playwright+test@1.62.1_react-dom@19_zdgsas7delec4eirrgkog66ojm/node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[14].oneOf[10].use[3]!../../packages/design-tokens/tokens.css ***!
  \***********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
/* =============================================================================
   TOKENS — direção "Cronômetro" (docs/design/ARTE-02-ESCOLHIDA.md)

   PACOTE COMPARTILHADO. Vive fora dos apps porque o CLAUDE.md diz que estes
   valores têm UMA fonte, e loja e admin precisam dos mesmos: era isso que fazia
   o produto ter duas caras. Copiar o arquivo para o admin resolveria a
   aparência e quebraria a regra — o segundo arquivo começa a divergir na
   primeira semana.

   As @font-face NÃO estão aqui (ver `fontes.css`): o admin é painel denso de
   operação e não usa a display, e declarar a fonte onde ela não é usada só
   pediria um woff2 que aquele app não serve.

   Tese: você não comprou um arquivo, comprou um INTERVALO. A interface é um
   instrumento de medição — calibrada, precisa, sem ornamento.

   FONTE ÚNICA de cor, tipo, espaço, raio, elevação e movimento. Nenhum desses
   valores deve existir fora daqui. Precisou de um novo? Vira token ou não entra.
   ============================================================================= */

:root {
  /* --- cor: rampa de aço ------------------------------------------------
     Temperatura AZULADA constante do fundo ao texto — metal sob luz fria.
     Nenhum neutro puro: #888 não existe neste produto. Cada degrau sobe o
     lightness e desce levemente a saturação, como metal escovado pega luz. */
  --aco-900: #040208; /* fundo da pagina — roxo-indigo mais escuro, base da vinheta radial. Operador escolheu opcao 2 do comparativo 2026-10-09: centro #0d0a1f, bordas #040208, contraste fundo->card 1.8:1, presenca roxa alta no centro */
  /**
   * Os dois degraus de superfície, RECALIBRADOS por medida (ARTE-04, item 32).
   *
   * Estavam em #1e1a2e (campo) e #1c262e (campo antigo) — e havia dois problemas.
   *
   * 1. O card ficava a **1,10:1** do fundo. Uma diferença dessas só se enxerga
   *    em sala escura; o que segurava o card era a SOMBRA, e sombra é escuro
   *    sobre escuro, a primeira coisa que a luz do ambiente apaga. Com véu de
   *    0,4 (sombra de rua) card, campo e bordas de chip sumiam todos juntos e a
   *    página virava um campo chapado com texto flutuando.
   * 2. O campo "rebaixado" era mais CLARO que a superfície onde ele afunda —
   *    ao contrário da física que o resto do sistema segue à risca (uma fonte
   *    de luz, toda sombra desce). Só funcionava porque a sombra interna
   *    mentia por ele.
   *
   * Agora o card é 800→700 (o degrau mais claro, porque é o elevado) e o campo
   * é 700→800 (o mais escuro, porque é o afundado). Card contra fundo:
   * **1,46:1**, na faixa onde tema escuro de verdade opera — agora com tinta
   * violeta em vez de azul frio, em linha com a paleta Proton.
   *
   * Nenhuma palheta escura sobrevive a sol direto — isso se resolve com brilho
   * de tela, não com hex. O que dá para comprar é margem, e ela foi comprada.
   */
  --aco-800: #120e1d; /* campo rebaixado - mais escuro que o card para manter a fisica do sistema */
  --aco-700: #1a1528; /* card - escuro para profundidade maxima e contraste forte com texto branco */
  /**
   * DEGRAU INTERMEDIARIO — cartão de decisão da PDP (2026-09-30, rodada 6).
   *
   * O cartão de decisão (.produto-checkout) tem função semântica diferente
   * do card do sistema: é o INSTRUMENTO da tela, não um contentor genérico.
   * No painel de PIX (que também é instrumento), o --aco-700 funciona porque
   * a tela toda é decisão. Na PDP o cartão é UM objeto dentro de uma página
   * editorial, e --aco-700 ficava "alto demais" — a hierarquia visual puxava
   * o olho para o cartão em vez de para o produto.
   *
   * A física do sistema (uma fonte de luz, toda sombra desce) continua
   * válida: --aco-650 (#11101e) é MAIS escuro que --aco-700 e MAIS claro
   * que o fundo --aco-900. A luminância relativa ao fundo cai de ~1,46:1
   * para ~1,30:1 — ainda separável pela hairline + glow, mas com presença
   * menor. O glow precisa de mais destacar porque o cartão é mais discreto.
   *
   * Por que 650 e não 660/680: o teste de calibragem com três tons
   * (#0e0a17 / #11101e / #14101f) mostrou #11101e como o ponto onde o
   * cartão deixa de competir com o produto sem virar fantasma (#0e0a17)
   * ou ficar igual ao card do sistema (#14101f). É o único dos três
   * onde a presença é INTERMEDIÁRIA de verdade, não "quase um dos extremos".
   *
   * Escopo de uso: por enquanto, SÓ o cartão de decisão da PDP. Se outra
   * tela pedir um card mais discreto, ela também pode usar — mas sem virar
   * cor solta em componente. Token sem uso por mais de uma rodada volta
   * para a lista de "matar" no próximo ciclo de crítica.
   */
  --aco-650: #11101e; /* cartão de decisão PDP — intermediário entre fundo e card */
  --aco-600: #3a3346; /* superfície de toque, hairline sólida */
  /**
   * A ESCALA GRAVADA: o traço apagado do mostrador e o ponto de estado "vazio".
   *
   * Calibrado por medida, entre dois limites que se puxam. Contra o painel
   * precisa de 3:1 — é elemento gráfico que carrega significado, e a parte
   * apagada da régua é o que transforma "um tanto de laranja" em "12 de 30".
   * Contra o --sinal precisa sobrar luminância, senão aceso e apagado viram a
   * mesma coisa em escala de cinza. Era --aco-600, que dava 1,44:1 no painel e
   * simplesmente sumia.
   *
   * Reajustado quando o card clareou (de #556b7c para cá): contra a superfície
   * nova, o valor antigo caía para 2,73:1. Agora dá 3,20:1 contra o card e
   * 1,82:1 contra o sinal — os dois limites se puxam mais forte com o card mais
   * claro, e quem perde é a separação aceso/apagado.
   *
   * Isso é aceitável porque não é ela que carrega a leitura sem cor: quem marca
   * a fronteira é o ÍNDICE, branco e 26px contra 12px, impossível de perder em
   * escala de cinza. A cor do traço é reforço, não o mecanismo.
   */
  --aco-500: #5f7688;
  /**
   * Texto secundário. Subiu de #6b7f8d junto com a recalibragem das superfícies:
   * contra o card clareado ele caía para 3,65:1, abaixo do piso de AA — e é
   * MUITO texto (descrição de produto, rótulo, nota, dica, unidade). O piso vale
   * contra a superfície mais clara em que ele aparece, que hoje é o card.
   *
   * Quem achou foi o teste `test/contraste.test.ts`, na mesma rodada em que o
   * card mudou. É exatamente o motivo de o teste existir: tom de interface não é
   * escolha isolada — mexer num degrau move todos os pares que dependem dele.
   */
  --aco-400: #a8a3b8; /* texto secundario - levemente mais claro que o anterior */
  --aco-200: #c2d0d9; /* texto — 12.8:1 */
  --aco-050: #edf3f6; /* leitura em destaque — 16.1:1 */

  /* O ÚNICO elemento quente da tela. É o ponteiro do instrumento: se
     aparecer em tudo, deixa de apontar para alguma coisa. */
  --sinal: #6d4dff; /* roxo vibrante e luminoso - a cor da acao primaria */
  --sinal-texto: #ffffff; /* branco puro sobre o botao violeta - 5.1:1 */
  --sinal-fraco: #1a1530; /* fundo de destaque, 8% do sinal sobre aço */

  /* Estado. Este produto vive disso — "pendente" tem cor PRÓPRIA, não é o
     acento reaproveitado nem amarelo genérico. */
  --espera: #f2c744;
  --espera-fraco: #2b2410;
  --ok: #2ed27a;
  --erro: #ff5348;
  --erro-fraco: #2e1413;

  --foco: #9b7dff; /* anel de foco: violeta claro, distinto do sinal mas na mesma família */

  /**
   * LINK, e por que ele NÃO é `--sinal` (ARTE-08 §1).
   *
   * A regra deste sistema é que o quente tem duas funções e nenhuma terceira:
   * tempo (o mostrador aceso) e a decisão da tela (o botão primário). `.link`
   * era `--sinal` e virava a terceira — não por descuido de uma tela, mas no
   * seletor, ou seja, em todas.
   *
   * O efeito medido era pior justamente onde a tela NÃO tem botão primário: no
   * portal, com os dois botões em contorno, o elemento mais saturado abaixo do
   * mostrador era "Não está funcionando? Pedir troca". O olho ia para a ação que
   * o negócio menos quer. Na página de produto, o primeiro laranja que o olho
   * encontra era o link de categoria, acima do nome do produto e muito antes do
   * botão Comprar.
   *
   * O sublinhado é que carrega a afordância — ele fica, com o offset de 3px que
   * já existia. Cor quente em link é convenção de web, não de instrumento.
   *
   * `--link-hover` devolve o quente no toque: ali ele deixa de competir por
   * atenção e passa a confirmar intenção, que é o mesmo papel que
   * `.copiar:active` já dá a ele.
   */
  --link: var(--aco-050);
  --link-hover: var(--sinal);

  /**
   * Hairline com opacidade, nunca cor sólida: a borda tem que se comportar como
   * reflexo de luz na quina, não como traço desenhado — sobre o card e sobre o
   * campo ela precisa ser a mesma ideia de luz, e cor fixa quebraria isso.
   *
   * Subiu de 8%/14% para 14%/24% na mesma medição que recalibrou as
   * superfícies. A 8% a borda do card ficava a 1,24:1 e morria com véu de 0,07
   * — praticamente a luz de um escritório. A 14% dá 1,52:1, e é a borda que
   * segura a quina quando o preenchimento não dá conta.
   */
  --linha: rgb(255 255 255 / 6%);
  --linha-forte: rgb(255 255 255 / 12%);

  /* --- tipo -------------------------------------------------------------
     Duas famílias da MESMA superfamília: o peso óptico do número é idêntico
     ao do título, o que nenhuma combinação de famílias diferentes entrega.
     Texto no stack do sistema — zero byte em 4G, e o Roboto do Android é
     excelente em corpo pequeno. */
  /* "Chivo Fallback" entra ANTES do stack de sistema: é a métrica ajustada que
     segura o layout enquanto a woff2 baixa (CLS = 0). Fora dela, o swap muda a
     altura da linha e a página pula. */
  --f-display: "Chivo", "Chivo Fallback", var(--f-texto);
  --f-num: "Chivo Mono", ui-monospace, monospace;
  --f-texto: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* MONO DE TEXTO, que não é `--f-num`. O subset da Chivo Mono tem dígitos e
     separadores e mais nada — jogar letra nele faz cada palavra cair no
     fallback, e a linha sai com duas fontes. Onde o conteúdo é código com
     letras (o copia-e-cola do PIX, nome de token no guia), o stack é este, que
     custa zero byte porque já está no sistema. */
  --f-mono: ui-monospace, "SF Mono", "Roboto Mono", "Segoe UI Mono", monospace;

  /* Escala de razão 1.25 (terça maior) a partir de 16px, arredondada para
     pixels inteiros — meio pixel em tela densa borra o traço fino. */
  --t-xs: 0.75rem; /* 12 — rótulo de instrumento, aviso legal */
  --t-sm: 0.875rem; /* 14 — texto secundário */
  --t-base: 1rem; /* 16 — corpo. PISO em input: menos que isso o iOS dá zoom */
  --t-lg: 1.25rem; /* 20 */
  --t-xl: 1.5rem; /* 24 — título de tela */
  --t-2xl: 2rem; /* 32 — leitura principal: preço, contagem */
  /**
   * O ASSUNTO DA TELA, e nada mais (ARTE-08 §2).
   *
   * A escala não tinha topo: o degrau mais alto (2xl) estava reservado a NÚMERO
   * — "leitura principal: preço, contagem" —, então em toda tela do produto o
   * maior elemento era um número e o assunto vinha um degrau abaixo. Na tela do
   * PIX isso é certo, porque ali o número É o assunto. Na página de produto,
   * não: o nome do produto ficava em 24px ao lado de um preço de 32px, e a
   * seção "Escolha o plano" (20px) disputava com ele a 1,2× de distância.
   *
   * Com este degrau a ladeira passa a ser assunto 40 › número 32 › seção 20 ›
   * corpo 16: quatro passos com razão >= 1,25 entre cada par, contra os três
   * degraus efetivos de antes. Um elemento manda na vista e o resto desce em
   * passos grandes — que é a construção que esta rodada foi buscar.
   */
  --t-3xl: 2.5rem; /* 40 — o assunto da tela. Só h1. */

  /* Entrelinha DIMINUI conforme o corpo cresce: título apertado, texto
     respirando. Linha frouxa em display faz o título parecer solto. */
  --lh-leitura: 1.05; /* 32px */
  --lh-titulo: 1.2; /* 24-20px */
  --lh-corpo: 1.55; /* 16px */
  --lh-nota: 1.45; /* 12-14px */

  /* Tracking NEGATIVO em display: em corpo grande o espaçamento padrão abre
     demais e o título perde densidade. Positivo em corpo pequeno e caixa
     alta, onde o olho precisa de ar para separar letra de letra. */
  --tr-display: -0.02em;
  --tr-leitura: -0.03em; /* 32px sofre mais */
  --tr-corpo: 0;
  --tr-nota: 0.01em;
  --tr-caps: 0.08em;

  /* --- espaço: escala de 4, sem valor solto ----------------------------- */
  --e-1: 4px;
  --e-2: 8px;
  --e-3: 12px;
  --e-4: 16px;
  --e-5: 24px;
  --e-6: 32px;
  --e-7: 48px;
  --e-8: 64px;

  /* --- raio -------------------------------------------------------------
     REGRA DE ANINHAMENTO: raio interno = raio externo − padding. Um campo
     de raio 12 dentro de uma ficha de raio 16 com padding 4 encaixa; com
     raio igual, a quina interna parece apertada contra a externa. */
  --r-1: 6px; /* elemento pequeno dentro de campo */
  --r-2: 12px; /* campo, botão, chip */
  --r-3: 20px; /* ficha (10 + 4 de padding interno, com ar Proton generoso) */
  --r-full: 999px;

  /* --- elevação ---------------------------------------------------------
     UMA fonte de luz para todo o produto: acima e ligeiramente à frente
     (0, -1). Toda sombra desce. Duas camadas por nível — uma de contato,
     curta e densa, e uma de difusão, longa e suave. Sombra de uma camada só
     parece adesivo colado. */
  --elev-0: none;
  --elev-1: 0 1px 2px rgb(0 0 0 / 40%), 0 2px 8px rgb(0 0 0 / 24%);
  --elev-2: 0 2px 4px rgb(0 0 0 / 44%), 0 8px 24px rgb(0 0 0 / 28%);
  /* não há --elev-3: esta direção não tem modal nem folha sobreposta, e nada
     no produto flutua acima do painel. Token reservado "para depois" é token
     que ninguém calibra — quando aparecer a superfície, ele nasce com ela. */
  /* campo REBAIXADO: a luz vem de cima, então a sombra interna fica no topo */
  --elev-dentro: inset 0 1px 2px rgb(0 0 0 / 50%);
  /**
   * GLOW DO PAINEL — a luz violeta que sai do card como se a superfície
   * estivesse ligeiramente acesa por dentro (efeito Proton). É um reforço,
   * não o mecanismo: sozinho não basta para separar card do fundo — quem
   * continua fazendo isso é o contraste CARD vs PAGINA e a hairline. Quando o
   * glow falha com luz ambiente, o sistema continua legível.
   */
  --glow-card: 0 12px 32px rgb(124 92 255 / 18%), 0 2px 8px rgb(124 92 255 / 12%);

  /**
   * GLOW DO MOSTRADOR — a onda que percorre a régua quando o tempo está
   * correndo (tela do PIX). Mesmo desenho do `--glow-card`: duas camadas,
   * uma curta e densa e outra larga e suave.
   *
   * A cor é BRANCO translúcido, e não um tom da rampa de aço, porque isto
   * não é superfície — é LUZ. O traço está aceso (laranja `--sinal`) e a
   * sombra é o brilho em volta dele, não uma cor de fundo. Nenhum degrau
   * `--aco-*` reproduz "luz branca a 55%", e usar um deles trocaria brilho
   * por cor.
   *
   * Nasce token (e não literal no `@keyframes` de `globals.css`) porque a
   * regra do sistema é "fonte única de cor" — e isto é cor. A AUD-2026-09-28
   * mediu o `rgb(255 255 255 / 0.55)` solto na linha 878 de `globals.css`.
   */
  --glow-mostrador: 0 0 8px 2px rgb(255 255 255 / 55%), 0 0 14px 4px var(--sinal);

  /* --- movimento --------------------------------------------------------
     Escada de duração e uma curva só. `ease-in-out` genérico faz tudo
     parecer preguiçoso: saída rápida (o elemento sai da inércia depressa),
     entrada suave (desacelera no destino). */
  --dur-micro: 120ms; /* estado de toque, cor */
  --dur-padrao: 200ms; /* aparecer, revelar */
  --dur-tela: 300ms; /* transição maior */
  /* atraso entre um traço e o seguinte na varredura da escala. 30 traços × 10ms
     dá 300ms de gesto — a mesma duração de --dur-tela, para a escala terminar
     de acender junto com o painel terminar de chegar. */
  --passo-escala: 10ms;
  --curva: cubic-bezier(0.2, 0, 0, 1);
  --curva-saida: cubic-bezier(0.4, 0, 1, 1);

  --toque: 48px; /* piso confortável; o mínimo absoluto é --toque-min */
  /* O MÍNIMO ABSOLUTO de alvo tocável. Era literal em dois lugares da loja
     (`.aba` e `.link`) e o esqueleto precisava do mesmo número para reservar a
     altura certa — três cópias do mesmo 44 e nada ligando uma à outra.
     `--toque` (48) continua sendo o piso de BOTÃO, que é o que se acerta com o
     polegar em movimento; este é o chão de alvo embutido em texto. */
  --toque-min: 44px;

  color-scheme: dark;
}


/*!***********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/.pnpm/next@15.5.22_@babel+core@7.29.7_@opentelemetry+api@1.9.1_@playwright+test@1.62.1_react-dom@19_zdgsas7delec4eirrgkog66ojm/node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[10].use[2]!../../node_modules/.pnpm/next@15.5.22_@babel+core@7.29.7_@opentelemetry+api@1.9.1_@playwright+test@1.62.1_react-dom@19_zdgsas7delec4eirrgkog66ojm/node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[14].oneOf[10].use[3]!../../packages/design-tokens/fontes.css ***!
  \***********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
/* =============================================================================
   FONTES — self-hospedadas. Nada de fonts.gstatic.com: um host a mais custa
   DNS + TCP + TLS antes do primeiro byte, e entrega o IP do cliente a um
   terceiro sem necessidade.

   Separado de `tokens.css` porque só a LOJA serve estes arquivos. O admin usa
   os mesmos tokens sem importar isto: `--f-display` cai no stack do sistema, e
   nenhum woff2 é pedido a um app que não o tem em `public/`.
   ============================================================================= */
@font-face {
  font-family: "Chivo";
  src: url("/fonts/chivo-display-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020-00FF, U+2010-2027;
}

/**
 * O mono desenha NÚMERO, nunca palavra: subset de dígitos e separadores, 7 KB.
 * É por isso que dá para ter duas famílias por menos peso que uma.
 */
@font-face {
  font-family: "Chivo Mono";
  src: url("/fonts/chivo-mono-400.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020, U+0024, U+0025, U+0028-0029, U+002B-002F, U+0030-003B,
    U+0052, U+005B, U+005D, U+00B7, U+00D7, U+2013-2014, U+20AC;
}

/**
 * Fallback com métricas ajustadas à Chivo. Sem isto, a troca no `swap` muda a
 * altura das linhas e empurra o layout — a página "pulando" durante o
 * carregamento, que é o guardrail de zero layout shift.
 */
@font-face {
  font-family: "Chivo Fallback";
  src: local("Arial Bold"), local("Roboto Bold"), local("Helvetica Neue Bold");
  font-weight: 700;
  size-adjust: 101%;
  ascent-override: 91%;
  descent-override: 23%;
  line-gap-override: 0%;
}

/*!*************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
  !*** css ../../node_modules/.pnpm/next@15.5.22_@babel+core@7.29.7_@opentelemetry+api@1.9.1_@playwright+test@1.62.1_react-dom@19_zdgsas7delec4eirrgkog66ojm/node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[10].use[2]!../../node_modules/.pnpm/next@15.5.22_@babel+core@7.29.7_@opentelemetry+api@1.9.1_@playwright+test@1.62.1_react-dom@19_zdgsas7delec4eirrgkog66ojm/node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[14].oneOf[10].use[3]!./app/globals.css ***!
  \*************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
/* =============================================================================
   TOKENS LOCAIS — extendem tokens.css sem criar dependência de pacote.
   ============================================================================= */

:root {
  /* RGB tripleto do sinal primário — usado em rgba() com alpha variável.
     Centraliza o valor para que mudar --sinal propague para os brilhos. */
  --glow-sinal: 109 77 255;
}

/* =============================================================================
   SISTEMA — direção "Cronômetro" (docs/design/ARTE-02-ESCOLHIDA.md).
   Tudo consome tokens.css; nenhum valor solto. Mobile-first: base 360–430px,
   desktop é progressive enhancement.
   ============================================================================= */

* {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  /*
   * O `<html>` é pintado como fallback estrutural.
   *
   * Por que pintar `<html>` mesmo com `<body>` já tendo cor:
   *
   *   O modal de campo (`apps/web/app/ui/modal-campo.tsx`) faz o
   *   `<body>` virar `position: fixed` para travar scroll por trás.
   *   Quando isso acontece em uma região onde o `<body>` é menor
   *   que a viewport OU onde o `visualViewport` reduziu para abrir
   *   o teclado virtual, o que sobra embaixo do overlay NÃO é mais
   *   `<body>` (ele saiu do lugar) — é `<html>`. Se o `<html>`
   *   não tiver cor de fundo, a UA-sheet do navegador aparece:
   *   branco no Chrome Android, cinza-claro no Safari iOS, branco
   *   no desktop. Resultado: o overlay escuro fica PARCHADO em uma
   *   região menor que a viewport, e o checkout vaza por baixo.
   *
   *   Isso é exatamente o sintoma do print de 2026-10-08 — o modal
   *   "não ficava no topo da tela do celular" e o conteúdo do
   *   checkout aparecia em fundo branco embaixo.
   *
   * O admin já trata isso desde a primeira versão (`apps/admin/app/globals.css:40`),
   * com `background: var(--aco-900)` no `<html>`. A web não tinha,
   * porque o `<body>` da loja tem gradiente e parece "já pintado":
   *   - até o gradiente estar cobrindo a viewport inteira (não cobre
   *     quando o body é menor que a tela);
   *   - até o gradiente sobreviver ao `position: fixed` (em alguns
   *     navegadores o fixed + `background-attachment: fixed` congela
   *     o gradiente em coordenada errada).
   *
   * `color-scheme: dark` aqui também garante que scrollbars,
   *   selects nativos e `<input type="date">` herdem o tema escuro
   *   sem pedir à UA — o admin faz o mesmo.
   */
  background: var(--aco-900);
  color-scheme: dark;
}

body {
  margin: 0;
  /*
   * `background-color` no `<body>` POR BAIXO do gradiente. O gradiente
   * é semitransparente (alpha 0.18 na origem, fade para zero): em
   * uma tela alta, o degradê para transparente deixa o `background-color`
   * aparecer. Sem `background-color`, o `<body>` seria transparente E
   * o que aparece embaixo é... `<html>`. Pintar os dois é redundante
   * mas à prova de regressão: se alguém amanhã trocar o gradiente por
   * outro com outro alpha, o `<body>` continua opaco.
   */
  background-color: var(--aco-900);
  color: var(--aco-200);
  font-family: var(--f-texto);
  font-size: var(--t-base);
  line-height: var(--lh-corpo);
  letter-spacing: var(--tr-corpo);
  /*
   * VINHETA ROXA — a iluminação da marca. Centro em y=-10% da viewport,
   * raio 80%. Cor sólida no centro (#0d0a1f) que desvanece para a cor
   * da borda (#040208). O centro mais roxo "respira" nos cartões que
   * estão no campo de visão do cliente.
   *
   * Operador escolheu a "Vinheta roxa" no comparativo de 2026-10-09:
   * contraste fundo->card sobe de ~1.6:1 para ~1.8:1, presenca roxa
   * alta no centro, sem ficar saturado.
   *
   * `background-attachment: fixed`: o gradiente fica fixo no viewport
   * enquanto o conteudo rola — sem isso, em uma tela com muito
   * conteudo (catalogo, conta) o glow "desce" com o scroll e some.
   */
  background-image: radial-gradient(80% 80% at 50% -10%, #0d0a1f 0%, #040208 100%);
  background-attachment: fixed;
}

a {
  color: inherit;
}

/* --- foco: um anel só, coerente em tudo que recebe teclado ---------------- */
:where(a, button, input, textarea, select, [tabindex]):focus-visible {
  outline: 2px solid var(--foco);
  outline-offset: 2px;
  border-radius: var(--r-1);
}

/**
 * SÓ PARA QUEM OUVE A TELA.
 *
 * Nada de `display: none` nem `visibility: hidden`: os dois tiram o elemento
 * da árvore de acessibilidade junto com o pixel, que é o oposto do que se quer
 * aqui. A técnica é encolher a caixa e recortá-la — o texto continua existindo
 * para o leitor de tela e some para o olho.
 *
 * `white-space: nowrap` está aí porque sem ele o texto quebra a cada caractere
 * dentro da caixa de 1px, e alguns leitores soletram letra por letra.
 */
.so-leitor {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  -webkit-clip-path: inset(50%);
          clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/**
 * A FAIXA DE REDE — sem conexão, e a volta dela.
 *
 * `position: sticky` e não `fixed`: fixa, ela cobriria a barra do polegar em
 * tela curta, que é onde vive a ação primária. Grudada no topo ela empurra o
 * conteúdo uma vez e para — e o conteúdo continua todo alcançável.
 *
 * Ocupa a largura inteira da janela, fora da coluna de leitura: é estado do
 * APARELHO, não da tela, e alinhá-la ao texto a faria parecer mais um aviso
 * daquela página.
 */
.faixa-rede {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  z-index: 5;
  margin: 0;
  padding: var(--e-3) var(--e-4);
  padding-top: max(var(--e-3), env(safe-area-inset-top));
  font-size: var(--t-sm);
  line-height: var(--lh-nota);
  color: var(--aco-050);
  background: var(--aco-700);
  border-bottom: 1px solid var(--linha-forte);
  /* a barra à esquerda é a mesma gramática do `.aviso`: cor + palavra, nunca
     só cor — o `<strong>` diz o que houve mesmo em escala de cinza */
  border-left: 3px solid var(--erro);
}

.faixa-rede[data-tipo="ok"] {
  border-left-color: var(--ok);
}

.faixa-rede strong {
  color: var(--aco-050);
}

/**
 * PULAR PARA O CONTEÚDO — o primeiro foco de toda página.
 *
 * Sem ele, quem navega por teclado atravessa o cabeçalho inteiro em CADA
 * página antes de chegar ao que veio ler. São dois links aqui, o que parece
 * pouco — até se multiplicar por toda navegação de uma sessão de compra.
 *
 * Invisível até receber foco, e aí vira um alvo com todo o peso de um botão:
 * um link de pular que aparece pequeno e cinza no canto é o mesmo que não
 * existir para quem enxerga pouco e navega por teclado.
 */
.pular {
  position: absolute;
  left: var(--e-3);
  top: var(--e-3);
  z-index: 10;
  padding: var(--e-3) var(--e-4);
  background: var(--sinal);
  color: var(--sinal-texto);
  font-weight: 700;
  text-decoration: none;
  border-radius: var(--r-2);
  box-shadow: var(--elev-2);
  /* fora da tela até o foco chegar — `transform` e não `display`, para o
     elemento continuar focável e o browser conseguir chegar nele pelo Tab */
  transform: translateY(-200%);
}

.pular:focus {
  transform: translateY(0);
}

/* =============================================================================
   TIPOGRAFIA
   ============================================================================= */
h1,
h2,
h3 {
  font-family: var(--f-display);
  font-weight: 700;
  letter-spacing: var(--tr-display);
  /* sem viúva em título: a última palavra não fica sozinha na linha */
  text-wrap: balance;
  margin: 0;
  /**
   * PALAVRA MAIOR QUE A CAIXA QUEBRA, em vez de vazar.
   *
   * Título de card carrega nome de produto, e nome de produto vem do banco —
   * o catálogo é 100% banco, então o texto é arbitrário por definição. Com a
   * fonte do sistema a 200% num aparelho de 320px, "Armazenamento" mede 314px
   * numa caixa de 254 e escorria para fora: a `<h3>` mantinha o tamanho certo,
   * o texto passava por cima, e a PÁGINA ganhava 27px de rolagem horizontal.
   *
   * Por isso `anywhere` e não `break-word`: só `anywhere` entra no cálculo de
   * min-content. Com `break-word` a palavra quebra na hora de desenhar mas
   * continua ditando a largura mínima da coluna do grid — o texto para de vazar
   * e a página continua larga, que é o pior dos dois mundos: some a evidência e
   * fica o defeito.
   */
  overflow-wrap: anywhere;
}

h1 {
  /**
   * O ASSUNTO DA TELA (ARTE-08 §2). Subiu de --t-xl (24) para --t-3xl (40): o
   * maior elemento da tela passa a ser o assunto, não o preço.
   *
   * Entrelinha de LEITURA e não de título: a 40px o 1,2 de --lh-titulo abre
   * demais entre linhas de um nome que quebra, e a escala deste sistema já
   * aperta a entrelinha conforme o corpo cresce.
   */
  font-size: var(--t-3xl);
  line-height: var(--lh-leitura);
  letter-spacing: var(--tr-leitura);
  color: var(--aco-050);
}

h2 {
  font-size: var(--t-lg);
  line-height: var(--lh-titulo);
  color: var(--aco-050);
}

h3 {
  font-size: var(--t-lg);
  line-height: var(--lh-titulo);
}

/* leitura do instrumento: preço, contagem, número que muda */
.leitura {
  display: flex;
  align-items: baseline;
  gap: 0.28em;
  font-family: var(--f-num);
  font-weight: 700;
  font-size: var(--t-2xl);
  line-height: var(--lh-leitura);
  letter-spacing: var(--tr-leitura);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  color: var(--aco-050);
}

/**
 * "R$" é UNIDADE DE MEDIDA, não a medida. Sai do mono (onde herdava o avanço
 * largo do dígito e abria um buraco antes do número), volta ao display, cai
 * dois degraus de tamanho e um de cor. Quem lê preço lê o número.
 */
.valor-simbolo {
  font-family: var(--f-display);
  font-size: 0.5em;
  font-weight: 700;
  letter-spacing: var(--tr-caps);
  color: var(--aco-400);
  transform: translateY(-0.08em); /* alinhamento óptico: a baseline mente aqui */
}

.valor-num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  white-space: nowrap;
}

/* kerning manual de um par conhecido: a vírgula ocupa o avanço inteiro do
   dígito no mono e fica boiando. Puxar dos dois lados é o que um tipógrafo
   faria à mão — e é sempre o mesmo par, então não há caso a descobrir. */
.valor-sep {
  margin: 0 -0.16em;
}

/* preço dentro de linha de texto (resumo, lista de pedido) */
.valor-linha {
  font-family: var(--f-num);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  font-weight: 700;
  color: var(--aco-050);
  white-space: nowrap;
}

.valor-linha .valor-num {
  font-weight: 800;
}

.valor-linha .valor-simbolo {
  font-size: 0.8em;
  margin-right: 0.2em;
}

/* rótulo do instrumento: pequeno, caixa alta, tracking aberto */
.rotulo {
  display: block;
  font-size: var(--t-xs);
  line-height: var(--lh-nota);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  font-weight: 600;
  color: var(--aco-400);
}

.nota {
  margin: 0;
  font-size: var(--t-sm);
  line-height: var(--lh-nota);
  letter-spacing: var(--tr-nota);
  color: var(--aco-400);
  max-width: 62ch; /* largura de leitura confortável */
  /* descrição de produto também vem do banco, e pode trazer URL sem espaço */
  overflow-wrap: anywhere;
}

.n {
  font-family: var(--f-num);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}

/* =============================================================================
   ASSINATURA — O MOSTRADOR GRADUADO

   Não é barra de progresso: é escala com marcações. Traço fino por unidade,
   traço alto a cada cinco, índice na posição atual. O significado está na
   QUANTIDADE de traços — sobrevive em escala de cinza, com fonte aumentada e
   sem cor. É assim que ele carrega estado sem gastar um tom da palheta.
   ============================================================================= */
/**
 * AS ALTURAS EM `rem`, E O MOTIVO.
 *
 * Eram 12/20/26px fixos. Com a fonte do sistema em 200%, a leitura ao lado
 * ("17 dias") passava de 31px para 62px de altura e a régua continuava com os
 * mesmos 26px: o instrumento ficava com a metade numérica ampliada e a metade
 * gráfica encolhida em relação a tudo. Quem aumenta a fonte é justamente quem
 * mais depende de conseguir contar os traços.
 *
 * `rem` e não `em` de propósito: `em` seguiria o contexto local (dentro de uma
 * `.nota` o mostrador encolheria sem ninguém pedir), enquanto `rem` segue a
 * raiz — que é exatamente onde a preferência do usuário mora. A 100% os valores
 * batem com os antigos ao pixel: 0.75rem = 12px, 1.25rem = 20px, 1.625rem = 26.
 */

/* =============================================================================
   SISTEMA DE LAYOUT RESPONSIVO
   ============================================================================= */

/**
 * CONTAINER FLUIDO — pusat sempre o espaço disponível, com padding.
 *
 * Breakpoints padronizados:
 * - sm:  480px  (mobile grande / phablet)
 * - md:  640px  (tablet)
 * - lg:  768px  (tablet grande / desktop pequeno)
 * - xl:  900px  (desktop)
 * - 2xl: 1040px (desktop grande)
 */

.container {
  width: 100%;
  padding-left: var(--e-3);
  padding-right: var(--e-3);
}

@media (min-width: 480px) {
  .container {
    padding-left: var(--e-4);
    padding-right: var(--e-4);
  }
}

@media (min-width: 768px) {
  .container {
    padding-left: var(--e-5);
    padding-right: var(--e-5);
  }
}

@media (min-width: 900px) {
  .container {
    max-width: 900px;
    margin-left: auto;
    margin-right: auto;
  }
}

@media (min-width: 1040px) {
  .container {
    max-width: 1040px;
  }
}

/**
 * GRID DE CARDS — adaptação de colunas por breakpoint.
 */
.card-grid {
  display: grid;
  grid-gap: var(--e-3);
  gap: var(--e-3);
  grid-template-columns: 1fr;
}

@media (min-width: 480px) {
  .card-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 768px) {
  .card-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 1040px) {
  .card-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

/**
 * LAYOUT DE TELA — sempre usar .tela para páginas.
 */
.tela {
  display: flex;
  flex-direction: column;
  min-height: calc(100dvh - 56px - var(--e-4));
  padding: var(--e-3) var(--e-3) 0;
}

@media (min-width: 480px) {
  .tela {
    padding: var(--e-3) var(--e-4) 0;
  }
}

@media (min-width: 768px) {
  .tela {
    padding: var(--e-4) var(--e-5) 0;
    max-width: 640px;
    margin: 0 auto;
  }
}

/**
 * POLEGAR — ação primária sempre no rodapé.
 * Garantir que fique preso ao fundo em mobile.
 */
.polegar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  padding: var(--e-3);
  background: var(--aco-900);
  border-top: 1px solid var(--linha);
  z-index: 50;
}

@media (min-width: 768px) {
  .polegar {
    position: static;
    padding: var(--e-4) 0 0;
    margin-top: auto;
    border-top: none;
    background: transparent;
  }
}

/**
 * PILHA — espaçamento consistente vertical.
 */
.pilha {
  display: flex;
  flex-direction: column;
  gap: var(--e-3);
}

.pilha > * + * {
  margin-top: 0;
}

@media (min-width: 480px) {
  .pilha {
    gap: var(--e-4);
  }
}

/**
 * ALINHAMENTO DE TEXTO RESPONSIVO.
 */
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }

@media (min-width: 768px) {
  .md\:text-left { text-align: left; }
  .md\:text-center { text-align: center; }
  .md\:text-right { text-align: right; }
}

/**
 * OCULTAR/MOSTRAR POR BREAKPOINT.
 */
.hide-mobile { display: none; }
.show-mobile { display: block; }

@media (min-width: 768px) {
  .hide-mobile { display: block; }
  .show-mobile { display: none; }
}

/**
 * ALINHAMENTO DE FICHAS E CARDS.
 */
.ficha, .painel {
  width: 100%;
  box-sizing: border-box;
}

/* Garantir que fichas em grid não estourem */
.card-list .ficha,
.card-grid .ficha {
  min-width: 0;
}

/**
 * TOUCH TARGETS — garantir 44px mínimo em mobile.
 */
@media (max-width: 480px) {
  .acao,
  button,
  a.button,
  .link {
    min-height: var(--toque);
    min-width: var(--toque);
  }
}

/**
 * INPUTS responsivos.
 */
/**
 * OBLIGATÓRIO: todos os campos de entrada ocupam 100% da largura do pai,
 * com box-sizing border-box para o padding não estourar.
 *
 * Por que `box-sizing: border-box` global importa:
 * - Por padrão, o navegador soma padding e border ao width declarado.
 *   Um `input { width: 100% }` num `.campo { padding: 1rem }` ESTOURA o pai.
 * - Solução canônica: `*, *::before, *::after { box-sizing: border-box }`
 *   no reset global. Está aplicado em globals.css (ver topo).
 *   Esta regra aqui é redundância defensiva para o contexto de campo.
 *
 * Por que `min-width: 0`:
 * - Item de flex/grid nasce com `min-width: auto` (= largura intrínseca).
 *   Um input com placeholder longo (ex.: "seu-email-muito-grande@exemplo.com")
 *   cresce além do card e o card ESTOURA o viewport.
 * - `min-width: 0` permite o encolhimento e o overflow-wrap tratar o texto.
 */
.campo input,
.campo select,
.campo textarea {
  display: block;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  box-sizing: border-box;
  padding: var(--e-2) var(--e-3);
  background: transparent;
  border: none;
  color: var(--aco-050);
  font-family: var(--f-texto);
  font-size: var(--t-base);
  line-height: var(--lh-corpo);
}

/**
 * REGRA MESTRA ANTI-ESTOURO DE TEXTO.
 *
 * O defeito original: texto em campos, cards e linhas longas cresce além do
 * container pai e "fura" o card. A solução definitiva é tratar overflow
 * em três frentes:
 *
 * 1. `min-width: 0` no filho flex/grid — permite encolhimento abaixo da
 *    largura intrínseca do conteúdo (o input pode ser menor que o placeholder).
 *
 * 2. `overflow-wrap: anywhere` no conteúdo — quebra texto em qualquer ponto
 *    quando não couber inteiro. Diferente de `break-word` que só quebra em
 *    espaços; `anywhere` quebra no meio de palavra também (essencial para
 *    e-mails longos e URLs).
 *
 * 3. `max-width: 100%` no próprio elemento — limite duro que impede estourar
 *    mesmo se as outras regras falharem.
 *
 * Aplicada em TODOS os containers que podem conter texto:
 * - card (.ficha, .painel, .card)
 * - campo (.campo, input, textarea)
 * - linha com texto (.linha, .specs, .fatos)
 * - qualquer div de texto longo
 */
.ficha, .painel, .card,
.campo, .campo input, .campo textarea, .campo select,
.linha, .linha > *,
.specs, .specs > div,
.fatos, .fatos > li,
.nota, .rotulo, .leitura,
.estado, .selo,
.specs > div > *,
.mostrador-legenda, .mostrador-leitura,
.renewal-card, .renewal-card-info, .renewal-card-detalhe,
.renewal-banner-texto, .renewal-banner-texto p,
.esqueleto {
  min-width: 0;
  max-width: 100%;
  overflow-wrap: anywhere;
  word-wrap: break-word;
}

/* Hífens suaves para evitar palavras muito longas */
.ficha p, .nota, .campo-valor, .leitura {
  hyphens: auto;
  -webkit-hyphens: auto;
}

/* Textos em linha única (rótulos, valores curtos) — ellipsis em vez de quebrar.
 *
 * IMPORTANTE: `.estado` foi REMOVIDO deste grupo em 2026-10-09.
 * O .estado carrega frases como "esperando o PIX cair — esta tela vira
 * sozinha" (47 chars), que NÃO cabem em 360px sem quebrar. O `nowrap`
 * global escondia a última letra ("sozinha" virava "sozinh...") sem
 * mostrar ellipsis, porque o .estado em flex não tem `width` definido
 * e o `text-overflow: ellipsis` não dispara.
 *
 * O .estado tem sua regra específica em `globals.css:3160` (`.estado`),
 * que já está SEM `white-space: nowrap` — o comentário no lugar explica
 * o motivo. Mas a regra global daqui (anterior) sobrepunha com mesma
 * especificidade. Removendo .estado daqui, a regra local vence. */
.rotulo, .selo,
.mostrador-leitura,
.identidade-email {
  overflow-wrap: normal;
  word-wrap: normal;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}

/**
 * Inputs dentro de campo: garantir que placeholder longo não estoure.
 * O placeholder não é conteúdo real (não há valor), mas o navegador reserva
 * largura para ele no estado vazio. Sem `min-width: 0`, o input é tão largo
 * quanto o placeholder mais longo que já viu — e o card estoura.
 */
.campo input::placeholder,
.campo textarea::placeholder,
input::placeholder,
textarea::placeholder {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/**
 * Garantia adicional: nada dentro de um card pode ter largura maior que o card.
 * `overflow: hidden` no card evita que filhos com bug visualizem fora dele.
 */
.ficha, .painel {
  overflow: hidden;
}

/**
 * LINHA com alinhamento consistente.
 */
.linha {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e-3);
  min-width: 0;
}

.linha > * {
  min-width: 0;
}

.linha-texto {
  display: flex;
  flex-direction: column;
  gap: var(--e-1);
  min-width: 0;
}

/**
 * ABAS responsivas.
 */
.abas {
  display: flex;
  gap: var(--e-1);
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.abas::-webkit-scrollbar {
  display: none;
}

@media (min-width: 640px) {
  .abas {
    flex-wrap: wrap;
    overflow-x: visible;
  }
}

.aba {
  flex-shrink: 0;
  white-space: nowrap;
}

/**
 * CABEÇALHO responsivo.
 */
.site-header {
  padding: 0 var(--e-3);
}

@media (min-width: 480px) {
  .site-header {
    padding: 0 var(--e-4);
  }
}

@media (min-width: 768px) {
  .site-header {
    padding: 0 var(--e-5);
  }
}


/**
 * AS TRÊS ALTURAS DA RÉGUA, EM :root E NÃO DENTRO DE `.mostrador`.
 *
 * Elas eram locais, e o esqueleto — que precisa desenhar o vulto da régua com a
 * altura certa, senão a página pula quando o dado chega — não alcançava daqui.
 * Resultado: `esqueletos.tsx` repetia `1.625rem` à mão, com um comentário
 * prometendo "mesma altura do mostrador real". Dois lugares com o mesmo número
 * e nada ligando um ao outro é como a promessa se quebra em silêncio.
 *
 * Ficam aqui e não em `tokens.css` de propósito: `tokens.css` é a fonte
 * compartilhada com o admin, e o admin não tem mostrador. Medida de componente
 * da loja mora na folha da loja.
 */
:root {
  /* Garante que scroll-into-view, validação nativa e âncoras parem
     >= 150px antes do fim da viewport — compensando o .polegar sticky. */
  scroll-padding-bottom: 150px;

  --mostrador-traco: 0.75rem;
  --mostrador-marco: 1.25rem;
  --mostrador-indice: 1.625rem;
}

.mostrador {
  --traco: var(--mostrador-traco);
  --marco: var(--mostrador-marco);
  --indice: var(--mostrador-indice);
  /**
   * A LARGURA DO TRAÇO também escala, com piso.
   *
   * O `max()` mantém 2px a 100% (o valor calibrado no ciclo 1) e deixa o traço
   * engrossar junto com o resto quando a fonte cresce — senão, a 200%, ele
   * viraria um fio fino demais no meio de tudo ampliado, que é o oposto do que
   * quem ampliou a fonte pediu.
   *
   * O que NÃO muda é a regra do ciclo 1: a largura é FIXA dentro de um mesmo
   * mostrador e o VÃO é que estica. Traço elástico vira barra de progresso
   * segmentada. Mesmo a 200% o vão continua maior que o traço (medido), então o
   * desenho segue sendo régua.
   */
  --traco-largura: max(2px, 0.125rem);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  height: var(--indice);
}

.mostrador i {
  flex: 0 0 var(--traco-largura);
  width: var(--traco-largura);
  height: var(--traco);
  /* a escala gravada — sempre visível, é contra ela que se lê o índice */
  background: var(--aco-500);
  transition:
    background var(--dur-micro) var(--curva),
    box-shadow var(--dur-micro) var(--curva),
    transform var(--dur-micro) var(--curva);
}

/* Hover: traço cresce levemente */
.mostrador i:hover {
  transform: scaleY(1.15);
}

.mostrador i[data-on="1"]:hover {
  box-shadow: 0 0 8px 2px color-mix(in srgb, var(--sinal) 60%, transparent);
}

/* a cada cinco unidades o traço é mais alto: dá ao olho onde se apoiar para
   contar sem contar, igual à régua */
.mostrador i[data-marco="1"] {
  height: var(--marco);
}

.mostrador i[data-on="1"] {
  background: var(--sinal);
  /* Glow sutil para parecer "aceso" */
  box-shadow: 0 0 4px 1px color-mix(in srgb, var(--sinal) 40%, transparent);
}

/* o índice: a posição atual. Único traço claro e o mais alto — é o ponteiro. */
.mostrador i[data-indice="1"] {
  height: var(--indice);
  background: var(--aco-050);
  /* Glow mais forte para o ponteiro */
  box-shadow: 0 0 8px 2px color-mix(in srgb, var(--aco-050) 60%, transparent);
}

.mostrador-bloco[data-esgotado="1"] .mostrador i[data-on="1"],
.mostrador-bloco[data-esgotado="1"] .mostrador i[data-indice="1"] {
  background: var(--erro);
  box-shadow: 0 0 4px 1px color-mix(in srgb, var(--erro) 50%, transparent);
}

/**
 * PRAZO APERTANDO — um degrau só, para a cor de pendência.
 *
 * O índice fica claro mesmo aqui: ele é o ponteiro, e trocar a cor dele
 * apagaria a fronteira que carrega a leitura em escala de cinza.
 */
.mostrador-bloco[data-atencao="1"] .mostrador i[data-on="1"] {
  background: var(--espera);
  box-shadow: 0 0 4px 1px color-mix(in srgb, var(--espera) 40%, transparent);
}

.mostrador-bloco[data-atencao="1"] .mostrador i[data-indice="1"] {
  box-shadow: 0 0 8px 2px color-mix(in srgb, var(--aco-050) 60%, transparent);
}

.mostrador-bloco[data-atencao="1"] .mostrador-leitura {
  color: var(--espera);
}

.mostrador-legenda {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--e-3);
  margin: var(--e-2) 0 0;
  /* "VENCEU EM" + "04/08/2026": a data é tabular e não quebra, então a fileira
     inteira virava indivisível. A 200% de fonte num aparelho de 320px ela mede
     232px sozinha e empurrava a página. Quebrada, a leitura cai para a linha de
     baixo e continua alinhada à direita. */
  flex-wrap: wrap;
}

/* a leitura ao lado da escala: grande o bastante para ser o número da tela,
   menor que a leitura principal para não disputar com ela */
.mostrador-leitura {
  display: flex;
  align-items: baseline;
  gap: 0.3em;
  font-size: var(--t-lg);
  font-weight: 700;
  letter-spacing: var(--tr-display);
  color: var(--aco-050);
}

/* leitura que é PALAVRA ("hoje", "amanhã"): família de display, a mesma dos
   títulos, que cobre Latin-1. Não vai em `--f-num` porque aquele é subset de
   dígitos — letra ali cai no fallback do sistema e a leitura mais importante do
   card sai numa família que não aparece em mais nenhum lugar. */
.mostrador-leitura .palavra {
  font-family: var(--f-display);
}

/* a unidade volta para a família de texto e recua um degrau de cor: quem lê
   "restam 17 dias" lê o 17 */
.mostrador-unidade {
  font-family: var(--f-texto);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: var(--tr-nota);
  color: var(--aco-400);
}

.mostrador-bloco[data-esgotado="1"] .mostrador-leitura {
  color: var(--erro);
}

/**
 * A ONDA CONTÍNUA — brilho que viaja pela régua, L→R, em loop.
 *
 * Só ativa em /conta: o cliente está medindo o tempo dele, a régua precisa
 * parecer que está funcionando. O PIX usa `varrer` (uma vez só, gesto de
 * inversão); aqui não há inversão — só sinal de vida.
 *
 * A onda é um PULSO de brilho por cima da cor do traço (não substitui a cor).
 * Como os traços já estão roxos (`--sinal`), animar o `background` apagava
 * visualmente — era troca de cor, não brilho. A sombra colorida ilumina o
 * traço sem mexer na cor base.
 *
 * O pulso tem duas fases: 0% sem brilho, 25% no pico, 50% sem brilho, 50–100%
 * parado. Só metade do ciclo ilumina; a outra metade é silêncio. Isso impede
 * que o delta visual seja contínuo e pareça "uma linha piscando".
 *
 * Duração: 30 traços × 30ms de passo = 900ms por onda percorrer a régua. A
 * animação dura 1500ms — sobra 600ms de "onda nova" no escuro antes da
 * próxima chegar, e o olho lê isso como respiração.
 */
.mostrador-bloco[data-ondulado="1"] .mostrador i[data-on="1"] {
  animation: onda 1500ms linear infinite;
  animation-delay: calc(var(--i) * 30ms);
  animation-fill-mode: backwards;
}

@keyframes onda {
  0%, 50%, 100% {
    box-shadow: none;
  }
  25% {
    box-shadow:
      0 0 8px 2px rgb(255 255 255 / 0.55),
      0 0 14px 4px var(--sinal);
  }
}

/*
 * A VARREDURA — escala acendendo uma vez, L→R, ao aparecer.
 *
 * Usado na confirmação do PIX: o instrumento inverteu de "quanto falta" para
 * "quanto você comprou" diante do cliente. A varredura comunica essa inversão.
 * Executa uma vez só, sem loop.
 */
.mostrador-bloco[data-varrendo="1"] .mostrador i[data-on="1"] {
  animation: acender var(--dur-micro) var(--curva) backwards;
  animation-delay: calc(var(--i) * var(--passo-escala));
}

@keyframes acender {
  from {
    background: var(--aco-500);
  }
}


/* =============================================================================
   ESQUELETOS — estados de carregamento com a forma do que vem
   ============================================================================= */

/**
 * Base: retângulo cinza que pulsa suavemente.
 * A animação é subtle para não distrair do conteúdo real quando carrega.
 */
.esqueleto {
  background: var(--aco-700);
  border-radius: var(--r-1);
  animation: pulsar var(--dur-tela) ease-in-out infinite;
}

@keyframes pulsar {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.6;
  }
}

/* Linha de texto */
.esqueleto-linha {
  height: 0.875em;
  margin-top: var(--e-1);
}

/* Título principal */
.esqueleto-titulo {
  height: 2rem;
  width: 60%;
  margin-bottom: var(--e-3);
}

/* Botão de ação */
.esqueleto-acao {
  height: var(--toque);
  width: 100%;
}

/* Card de catálogo */
.esqueleto-card {
  aspect-ratio: 3 / 4;
  border-radius: var(--r-2);
}

/* Mostrador/escala */
.esqueleto-mostrador {
  height: var(--mostrador-indice);
  width: 100%;
}

/* Prevenir animação em prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  .esqueleto {
    animation: none;
    opacity: 0.7;
  }
}

/* =============================================================================
   PÁGINA DE PRODUTO — LAYOUT DE DUAS COLUNAS
   ============================================================================= */

/**
 * O grid raiz da página de produto.
 *
 * Coluna única em todas as viewports: a informação editorial do item
 * (.produto-info) fica em cima, e o cartão de decisão (.produto-checkout)
 * fica centralizado embaixo. Antes era 2 colunas (info | 380px fixo à
 * direita), mas a decisão de compra tem que dominar a página, não morar
 * numa faixa lateral estreita — bate com a referência visual.
 *
 * `justify-items: center` centraliza o cartão no eixo horizontal do grid;
 * `align-items: center` alinha verticalmente info e cartão quando o cartão
 * é mais alto que a info, sem depender do `margin-top: auto` no filho.
 */
.produto-corpo {
  display: grid;
  grid-gap: var(--e-5);
  gap: var(--e-5);
  align-items: center;
  justify-items: center;
  width: 100%;
}

/**
 * A coluna de informação. Flex na vertical para o título e a descrição
 * quebrarem de forma natural. O container já está centralizado no viewport
 * (max-width: 640px dentro do grid centrado); aqui alinhamos o conteúdo
 * interno — breadcrumb, h1, descrição — ao centro do container, para que
 * tudo apareça visualmente centralizado no viewport junto com o cartão de
 * decisão à direita.
 *
 * A imagem (.arte-produto) flutua à direita com `float: right` (a partir
 * de 640px); isso não é afetado por text-align do pai.
 */
.produto-info {
  display: flex;
  flex-direction: column;
  gap: var(--e-4);
  text-align: center;
}

.produto-corpo .produto-info {
  max-width: 640px;
  width: 100%;
}

/**
 * O BLOCO DE DECISÃO — a superfície onde a compra acontece.
 *
 * Elevação, borda e fundo o separam visualmente do conteúdo editorial
 * da esquerda. Os fatos (garantia, entrega etc.) ficam dentro dele,
 * alinhados à largura do card.
 */
.produto-checkout {
  display: flex;
  flex-direction: column;
  gap: var(--e-5);
  padding: var(--e-6);
  /* Cartão de decisão da PDP — superfície entre fundo e card, ver
   * `tokens.css` comentário de `--aco-650` (rodada 6, 30/09/2026):
   * intermediário de propósito, separado do fundo pela hairline e pelo
   * `--glow-card` (proposta 2 do ARTE-17). Não usar `--aco-700` (card)
   * nem `--aco-800` (campo) aqui. Guardado por teste em contraste.test.ts
   * ("o cartão de decisão PDP consome --aco-650"). */
  background: var(--aco-650);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-3);
  /* Sombra paritária com `.painel` (globals.css:2513): o glow violeta vem
   * ANTES da sombra preta para que a última continue sendo o que recorta o
   * painel do fundo. Sem a ordem, o glow cobriria o contact-shadow e o
   * cartão pareceria flutuar sem chão (ver globals.css:2507-2511). Guardado
   * por teste em contraste.test.ts ("consome --glow-card antes de --elev-2"). */
  box-shadow: var(--glow-card), var(--elev-2);
  /* Layout do painel de decisão — cartao centralizado na pagina,
   * conteúdo majoritariamente centralizado (preço, pílula, botão).
   * O estado (.estado-topo) é ETIQUETA no canto superior direito, não
   * cabeçalho: ele "flutua" para o lado enquanto o conteúdo central
   * continua sendo o protagonista visual. Mesma lógica do caso 2-planos
   * (h3 à esquerda, .estado à direita) — só que aqui o centro é o preço.
   * Decisão registrada em ARTE-11 (próxima rodada de crítica).
   *
   * `max-width: 480px` no desktop — referência visual pede cartão mais
   * estreito, com respiro lateral generoso; o conteúdo (preço grande,
   * botão único) não precisa de 600px. Em viewports < 768px o cartão
   * ocupa a largura disponível (`width: 100%`). */
  text-align: center;
  width: 100%;
  max-width: 480px;
  justify-self: center;
}

/**
 * "Escolha o plano" é um título de seção, não de página.
 * Desce dois degraus: peso 600 e cor secundária.
 */
.produto-checkout > h2 {
  margin: 0;
  font-size: var(--t-base);
  font-weight: 600;
  color: var(--aco-400);
}

/**
 * Os fatos dentro do bloco perdem a hairline e o padding-top
 * que tinham na pilha global. O contorno do .produto-checkout
 * é suficiente para agrupar. Sobem de 14px para o corpo (16px) —
 * texto longo de garantia fica mais confortável de ler no cartão
 * maior. text-align volta a ser left (herdava text-align: center
 * do pai e os parágrafos longos ficavam esquisitos).
 */
.produto-checkout .fatos {
  margin: 0;
  border-top: none;
  padding-top: 0;
  text-align: left;
  font-size: var(--t-base);
  color: var(--aco-300);
  line-height: var(--lh-corpo);
}
/**
 * CARD INTERNO — fundido com o painel exterior.
 *
 * A .ficha original tem borda, fundo e sombra, o que criava uma
 * "caixa dentro da caixa" redundante sobre o painel já elevado.
 * Aqui ela perde tudo isso e flui direto dentro do .produto-checkout.
 * Nome, selo, preço e botão ocupam a largura inteira do card.
 */
.produto-checkout .ficha {
  background: transparent;
  border: none;
  box-shadow: none;
  padding: 0;
  border-radius: 0;
  /* Respiro vertical entre estado / preço / pílula / botão.
   * Sem o gap, a única folga era a margin-bottom do estado (12px) e os
   * outros filhos ficavam grudados (preço→pílula 0, pílula→botão 0).
   * `--e-4` (16px) é o piso de corpo-a-corpo da loja — mesmo vão
   * usado na .card-list e na .pilha. 24px afundava o botão no fundo
   * do cartão e alongava a página inteira sem ganho de leitura. */
  gap: var(--e-4);
}

/* preço do card */
.produto-checkout .leitura {
  font-size: var(--t-2xl);
}

/* Preço em destaque (ARTE-08 §2 exceção): no caso 1-plano o número é a
   decisão única da tela e sobe para --t-3xl (40px), par com o h1 da página.
   No caso 2-planos os dois preços ficam em --t-2xl (comparação lado a lado
   não admite um maior que o outro). Ver ARTE-08-ESTETICA.md §2 (2026-10-01). */
.produto-checkout .preco-destacado,
.produto-checkout .preco-destacado .leitura {
  font-size: var(--t-3xl);
}

/* Estado (.estado-topo) quando ha 1 so plano — flutua no canto superior
   direito do cartão (etiqueta, não cabeçalho). align-self: flex-end
   coloca o span no fim do eixo cruzado. O gap da .ficha (--e-4) já
   dá o respiro até o preço — margin-bottom aqui dobraria o vão. */
.produto-checkout .estado-topo {
  display: inline-flex;
  align-self: flex-end;
}

.produto-checkout .card-list {
  gap: var(--e-4);
}

/* ADR-0012-4: o painel de decisão centraliza o conteúdo principal
 * (preço, pílula, botão). No caso 2-planos o .ficha-topo tem h3 à
 * esquerda + .estado à direita; no caso 1-plano o .selo-wrap tem só
 * a pílula, centralizada horizontalmente. O estado de 1-plano está
 * FORA do .ficha-topo (é .estado-topo, irmão) e flutua em flex-end
 * do cartão, no canto superior direito. */
.produto-checkout .ficha-topo {
  align-items: center;
  justify-content: center;
}

/* mesmo motivo: margem nativa do h3 e do span desalinham o baseline.
 * Zera aqui so — em outros lugares o `.ficha-topo` ja convive com margem
 * em um dos lados (ex.: .specs) e nao tem o problema do .estado.
 *
 * O preco-bloco (R$ + numero + ,cents) precisa centralizar como conjunto
 * — senao fica grudado na esquerda do cartao. inline-flex + .preco-wrap
 * com text-align: center garante. */
.produto-checkout .preco-wrap {
  text-align: center;
}
.produto-checkout .ficha-topo h3,
.produto-checkout .ficha-topo .estado {
  margin: 0;
}

/* align-items: baseline no preco — R$ fica na mesma linha que o numero,
   gap: 0.25em separa sem grudar. O .leitura ja e flex, mas o wrapper
   garante que o alinhamento funcione mesmo se o componente mudar.
   ADR-0012-4: o conjunto (R$ + valor) centraliza dentro do cartão
   de decisão (.produto-checkout). */
.preco-bloco {
  display: flex;
  align-items: baseline;
  gap: 0.25em;
  justify-content: center;
}

/* Garante centralizacao tambem no preco-bloco dentro do produto-checkout.
   Redundante com .preco-bloco global, mas explicito aqui para sobreviver
   a uma futura mudanca do componente (ex: width: fit-content). */
.produto-checkout .preco-bloco {
  justify-content: center;
}

/**
 * Nota "equivale a R$ X por mês" do card de plano.
 *
 * Sem margem inferior própria, ela gruda no `.campo-acoes` de baixo (botão
 * Comprar com PIX). O `.campo-acoes` global tem `margin-top: var(--e-3)`
 * (12px), mas é pouco respiro entre uma legenda de preço e uma CTA
 * primária. `e-4` (16px) mantém a nota visivelmente ligada ao preço de
 * cima — é uma legenda, não um parágrafo independente — mas abre o
 * suficiente para o olho separar mentalmente "isso explica o preço" de
 * "isso é a ação".
 *
 * Aplicada só no contexto de produto: fora daqui o `.nota + .campo-acoes`
 * tem o respiro de 12px (`.campo-acoes { margin-top: e-3 }`) que serve
 * para outros fluxos.
 */
.produto-checkout .ficha > .nota + .campo-acoes {
  margin-top: var(--e-4);
}

/* produto-checkout: campo de acoes centraliza o botao Comprar com PIX
 * (ADR-0012-4). O botao tem width: 100% mas o wrapper precisa centralizar
 * a acao tambem — senao ela fica grudada na esquerda quando ha 1 so plano. */
.produto-checkout .campo-acoes {
  justify-content: center;
}

/**
 * E mais um respiro entre o preço (preco-bloco) e a nota "equivale a".
 * O `marginBottom: var(--e-4)` no wrapper do preco-bloco (no JSX) já
 * garante a separação de cima; esta regra garante a de baixo quando o
 * preco-bloco não tem o marginBottom inline. Idempotente: se já tem
 * marginBottom, ela continua valendo e a regra do `:has()` aqui é só
 * uma camada extra de defesa.
 */
.produto-checkout .preco-bloco + .nota {
  margin-top: var(--e-2);
}



/**
 * CTA full-width — sobrescreve .acao {width:100%} que é global
 * e também o media query desktop (width:auto, min-width:200px).
 * No painel de decisão o botão é a única ação visível, então sobe de
 * 48px para 56px de altura para ter presença de decisão primária.
 * Continua respeitando --toque (48px) em pointer: coarse. */
.produto-checkout .acao {
  width: 100%;
  min-height: 56px;
}

/* =============================================================================
   SUPERFÍCIES
   ============================================================================= */

/**
 * Wrapper externo de cada entitlement na aba Assinaturas.
 * Sem margem inferior, os cards ficam colados (cada um é um .passe-digital
 * solto, sem .card-list em volta). O `--e-4` casa com o padding interno da
 * ficha e com o gap padrão de .card-list — quem viu a tela antes não estranha.
 */
.passe-digital {
  margin-bottom: var(--e-4);
}
.passe-digital:last-child {
  margin-bottom: 0;
}

/**
 * ADR-0012-1 (2026-09-30) — Conta (`.passe-digital-ficha`): centralização
 * do CONTEÚDO do cartão de assinatura, revertendo parcialmente ARTE-15c
 * (regra 2). Cada `<section class="ficha passe-digital-ficha">` da aba
 * Assinaturas de /conta é um CARTÃO DE DECISÃO — "renovar", "revelar",
 * "pedir troca" — e o conteúdo centralizado casa com a regra do laranja
 * (a ação principal é o que ocupa o centro). O resto do /conta (faixa
 * de identidade, `.campo`, `.suporte-bloco` do detalhe) fica à esquerda
 * por exceção documentada.
 *
 * Por que modificador escopado, não regra global do `.ficha`:
 *   - PDP mantém a `.ficha` à esquerda (comparação por coluna).
 *   - Detalhe do Suporte mantém conversa à esquerda (ARTE-15c §3).
 *   - Catálogo mantém especificação à esquerda (comparação técnica).
 *   - Só o `.passe-digital-ficha` é "cartão de decisão" sem conversa
 *     nem comparação — esse é o escopo.
 *
 * Quatro regras, todas escopadas:
 *   1. `text-align: center` no cartão — cascateia pra h3 (`.ficha-topo > h3`
 *      via inline context do `<h3>` se for inline), `<span class="estado">`
 *      dentro da specs, e o texto de `.acao`. Atenção: como o `.ficha-topo`
 *      é `display: flex`, o h3 dentro dele é FLEX ITEM e o `text-align` do
 *      pai flex não pega — por isso o override (2) abaixo é necessário.
 *   2. `.ficha-topo` muda de `justify-content: space-between` (regra global,
 *      que serve pra empurrar h3 e selo pras extremidades) para `center` —
 *      aqui só tem h3, sem selo, e o espaço-between deixava o título à
 *      esquerda. Centralizar o fileira alinha o h3 com o resto do cartão.
 *   3. `.nota` (filho DIRETO do `.passe-digital-ficha`) ganha
 *      `margin-inline: auto` — a `.nota` global é `display: block` com
 *      `max-width: 62ch`, e sem `margin-inline: auto` a caixa fica à
 *      esquerda do cartão mesmo com `text-align: center` no pai (que
 *      cascateia texto, não caixa). O combinador `>` evita atingir `.nota`
 *      internas de sub-componentes que NÃO devem centralizar.
 *   4. `.campo-acoes` ganha `flex-direction: column` — o `.campo-acoes` é
 *      `display: flex` (row) por padrão, e como o `.acao` tem `width: 100%`
 *      dentro do `.passe-digital-ficha` (sem o `.campo > .campo-acoes` que
 *      só vale dentro de `.campo`), as ações em row colapsariam para 100%
 *      cada, brigando por espaço. Em coluna, cada `.acao` ocupa sua linha.
 *
 * O que NÃO muda (verificado):
 *   - `.acao` permanece `justify-content: var(--alinhamento-acao, center)`
 *     (texto dentro do botão continua centralizado por padrão).
 *   - O `.campo-acoes > .acao { width: 100% }` continua valendo.
 *   - `.passe-digital-ficha > .ficha-topo` continua `align-items: baseline`
 *     (herdado do `.ficha-topo` global).
 *   - `.estado` (ponto + palavra) cascateia `text-align: center` e centraliza.
 *   - A `.ficha-alvo` global (e a `.ficha` interna do detalhe do Suporte,
 *     Conta, etc.) não são tocadas — escopo rigoroso.
 *
 * Histórico: aplicado em `426cb7d` (2026-09-30), perdido acidentalmente em
 * `64de1ad` (2026-09-29, "h1 do PIX centralizado" — o commit removeu 711
 * linhas e adicionou 32; o autor não percebeu que tinha apagado estas 4
 * regras + `.tela-checkout` + `.produto-checkout-demo`). Re-aplicado em
 * `baa81a4` (2026-10-02) durante destravamento do marge. AUD-E2E-001.
 */
.passe-digital-ficha {
  text-align: center;
}
.passe-digital-ficha .ficha-topo {
  justify-content: center;
}
.passe-digital-ficha > .nota {
  margin-inline: auto;
}
.passe-digital-ficha .campo-acoes {
  flex-direction: column;
}

.ficha {
  display: block;
  background: var(--aco-700);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-3);
  padding: var(--e-4);
  box-shadow: var(--elev-1);
  text-decoration: none;
  color: inherit;
  transition:
    box-shadow var(--dur-micro) var(--curva),
    border-color var(--dur-micro) var(--curva),
    transform var(--dur-micro) var(--curva);
}

/**
 * A ficha como ALVO: `<button>` inteiro clicável, para lista em que o card não
 * é um link de navegação mas dispara um estado (abrir a conversa do chamado).
 * Sem isto o `<button>` do browser traria fundo, borda e centralização próprios.
 */
.ficha-alvo {
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
}

/**
 * Skeleton da LISTA DE CHAMADOS (1.1) — reusa o sistema `.esqueleto*` que
 * já existe para outras telas (`style/page.tsx` §22). Aqui na lista a
 * composição é: um título mais alto (.esqueleto-titulo) + duas linhas
 * finas com larguras diferentes — uma estreita (data) e uma larga (specs).
 *
 * Larguras via `style={{ width: ... }}` em vez de classes novas porque
 * são valores de uso único — três classes a mais no vocabulário do
 * sistema só para esqueleto de Suporte pesaria no `.guia-vivo.test`
 * sem trazer reuso.
 */

/* Ação Tátil Mecânica: o recuo afunda no eixo Y simulando o peso de um botão físico e zera a elevação.
   A variável --dur-micro (já no container base) assegura uma queda brusca sem esperas. */
a.ficha:active,
button.ficha:active {
  transform: translateY(2px) scale(0.995);
  box-shadow: var(--elev-0);
  border-color: var(--aco-500);
}

/* Paridade rigorosa com teclado: navegação via Tab (focus-visible) garante a mesma
   resposta de elevação de quem passa o mouse, mesmo em dispositivos de toque. */
a.ficha:focus-visible,
button.ficha:focus-visible {
  border-color: var(--aco-500);
  box-shadow: var(--elev-2);
  outline: none; /* A borda luminosa assume o papel do outline nativo */
}

@media (hover: hover) {
  a.ficha:hover,
  button.ficha:hover {
    border-color: rgb(var(--glow-sinal) / 35%);
    box-shadow: var(--elev-2), 0 0 16px rgb(var(--glow-sinal) / 20%);
    transform: translateY(-2px);
  }
}

/* sem estoque: recua um degrau de elevação e de contraste. Continua legível e
   continua clicável — só para de disputar atenção com o que está à venda. */
.ficha[data-recuado="1"] {
  background: transparent;
  box-shadow: none;
}

.ficha[data-recuado="1"] h3 {
  color: var(--aco-400);
}

.ficha[data-perigo="1"] {
  border-color: var(--erro);
  background-color: var(--erro-fraco);
  box-shadow: none;
}

.ficha[data-perigo="1"] h3 {
  color: var(--erro);
}

/**
 * FASE 3 (2026-09) — destaque BINÁRIO.
 *
 * O card de status da conversa (`cartao-status` em
 * `support-tab.tsx`) é uma DECISÃO grande — "Marcar como
 * resolvido" / "Reabrir este chamado". Ele não é uma "etapa" da
 * conversa nem o formulário de envio; é um botão binário que
 * muda o estado do chamado inteiro. Merece ler diferente das
 * mensagens (que têm `data-de="voce"` / `data-de="suporte"`).
 *
 * Como diferenciamos sem inventar um sistema novo:
 *   - checkmark textual `✓` no início do texto (via `.marca-sim`)
 *   - hairline superior mais grossa (`border-top: 2px`)
 *   - mesma paleta do fundo da ficha, sem ganhar cor nova
 *
 * O resultado é "este card representa uma decisão binária".
 * Vale para QUALQUER `.ficha[data-destaque="binario"]`, não só
 * o de suporte — futuro pode reusar.
 */
.ficha[data-destaque="binario"] {
  border-top-width: 2px;
  border-top-color: var(--ok);
}

.marca-sim {
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-right: var(--e-1);
  /* text-align em inline-block não pega — forço via flex no pai
     seria mais forte, mas aqui é uma frase corrida de texto
     longo, e o ✓ fica visualmente alinhado ao cap-height do
     parágrafo. +0.1em abaixo compensa a base do glifo (a fonte
     do sistema posiciona o ✓ alto demais sem isso). */
  font-weight: 700;
  color: var(--ok);
  vertical-align: -0.1em;
}

.ficha-topo {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--e-3);
  /* título e estado quebram para linhas separadas quando não cabem lado a lado
     — mesma razão da linha de `.specs`: uma fileira indivisível vira a largura
     mínima do card e empurra a coluna inteira do grid */
  flex-wrap: wrap;
}

/**
 * CABEÇA DA FICHA — a arte à esquerda, o nome à direita.
 *
 * Miniatura e não foto de topo: o card continua sendo uma LINHA DE LISTA, e
 * cabem quatro produtos na tela. Uma faixa de imagem no topo de cada card
 * transformaria o catálogo numa galeria — bonito de mostrar, ruim de comparar,
 * e a comparação é o trabalho que esta tela existe para fazer.
 *
 * Sem arte, o grid some e o título ocupa a largura inteira. É o estado mais
 * comum enquanto o dono não sobe as imagens, então ele não pode ser um buraco
 * esperando ser preenchido: precisa parecer intencional sozinho.
 */
.ficha-cabeca {
  display: flex;
  align-items: center;
}

.ficha-cabeca[data-com-arte="1"] {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-gap: var(--e-3);
  gap: var(--e-3);
  align-items: center;
}

/**
 * O POÇO onde a arte é montada. Fundo rebaixado, hairline e sombra interna, os
 * mesmos do campo de credencial — a arte não é colada sobre o instrumento, é
 * encaixada nele. Sem isso a imagem flutuaria como adesivo, que é exatamente o
 * que a direção evita em todo o resto.
 */
.ficha-arte {
  display: block;
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  overflow: hidden;
  background: var(--aco-800);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-2);
  color: var(--aco-050);
  box-shadow: var(--elev-dentro);
}

/**
 * `object-fit: cover` corta, nunca distorce — arte esticada é o único defeito
 * de imagem que o olho perdoa menos que arte cortada. E a `<img>` recebe
 * width/height do contrato para reservar a caixa; estes 100% só a acomodam
 * dentro do poço depois que ela chega.
 */
.ficha-arte img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ficha-titulo {
  min-width: 0; /* deixa o texto quebrar em vez de esticar a coluna do grid */
  margin: 0; /* h2 tem margem herdada; aqui estamos dentro de um
                 wrapper com gap, a margem abriria vão fantasma */
  font-size: var(--t-xl); /* sobe o título do detalhe de suporte —
                             cabeçalho de conversa é maior que o
                             título de uma ficha comum */
  line-height: var(--lh-titulo);
}

/**
 * Linha de "atualizado há Xmin" no cabeçalho do detalhe de suporte
 * (Fase 2 do redesign, 2026-09). `.nota` já existe — aqui só
 * afasta do bloco de cima para a data relativa não colar no
 * chip de status.
 *
 * Limitado ao PRIMEIRO `<p className="nota">` filho do wrapper
 * com `data-status`: o resto da página pode ter várias notas e
 * a margem não pode se aplicar a todas (o erro com `<strong>`
 * dentro de `.nota` é a próxima ocorrência que NÃO deve ganhar
 * esse margin-top).
 */
[data-status] > p.nota:first-of-type {
  margin-top: var(--e-2);
}

/**
 * A arte na PÁGINA DO PRODUTO. Mesmo poço, tamanho maior — é o que justifica
 * exigir 320px de resolução no upload. Largura limitada porque nem aqui ela
 * vira cartaz: o produto é um intervalo de tempo, e a imagem ilustra, não
 * argumenta.
 */
.arte-produto {
  display: block;
  width: min(40vw, 200px);
  aspect-ratio: auto;
  margin: var(--e-3) 0;
  overflow: hidden;
  background: var(--aco-800);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-3);
  color: var(--aco-050);
  box-shadow: var(--elev-dentro);
}

.arte-produto img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}


/**
 * Em desktop, a arte flutua à direita para melhor aproveitamento do espaço
 * visual da coluna de leitura.
 */
@media (min-width: 640px) {
  .arte-produto {
    width: min(25vw, 180px);
    float: right;
    margin-left: var(--e-4);
  }
}

/**
 * FICHA TÉCNICA — pares rótulo/valor separados por hairline.
 *
 * É o que dá ao card a cara de instrumento sem gastar o mostrador (que só
 * aparece onde há relógio correndo). Cada linha tem rótulo em caixa alta à
 * esquerda e valor alinhado à direita; a hairline entre elas é o que faz o
 * olho ler "especificação" em vez de "parágrafo".
 */
.specs {
  margin: var(--e-4) 0 0;
  border-top: 1px solid var(--linha);
}

/**
 * OS FATOS, no momento da decisão (ARTE-08 §5).
 *
 * A página de produto não dizia NADA sobre o que a loja garante — e este produto
 * tem garantias verificáveis que ele próprio cumpre: troca dentro da vigência
 * (existe, com fila no painel), entrega automática assim que o pagamento cai, e
 * nada de cadastro, cartão ou taxa no fim.
 *
 * NÃO É SELO, e a distinção é a decisão de desenho aqui. Selo mudaria o desenho
 * do card e gastaria o quente num terceiro papel — os dois proibidos pela
 * direção, e o destaque de produto já foi decidido como POSIÇÃO no ciclo 18.
 * Isto é texto factual em tom secundário: o instrumento afirmando o que ele
 * mesmo garante, sem medalha e sem ícone colorido.
 *
 * A hairline acima separa sem criar caixa: mais um card ali competiria com o
 * bloco de planos, que é onde a decisão acontece.
 */
.fatos {
  display: grid;
  grid-gap: var(--e-2);
  gap: var(--e-2);
  margin: 0;
  padding: var(--e-4) 0 0;
  list-style: none;
  border-top: 1px solid var(--linha);
  font-size: var(--t-sm);
  line-height: var(--lh-nota);
  color: var(--aco-400);
  /* item de grid nasce com min-width: auto — a mesma regra de `.pilha` */
  min-width: 0;
}

.fatos > li {
  min-width: 0;
  overflow-wrap: anywhere;
}

.specs > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--e-3);
  padding: var(--e-2) 0;
  /**
   * Rótulo e valor QUEBRAM para linhas separadas quando não cabem lado a lado.
   *
   * Sem isto, a linha inteira era indivisível: a largura mínima da ficha virava
   * `rótulo + vão + valor`, e como o preço leva `white-space: nowrap` (para o
   * número não partir no meio) essa soma cresce junto com a fonte do sistema. A
   * 200%, "PREÇO" + "R$ 149,00 / 6 meses" não cabia em 380px e empurrava a
   * página — e, por ser a coluna do grid que estourava, TODOS os cards ficavam
   * largos ao mesmo tempo, sem nenhum parecer o culpado.
   *
   * Quebrado, o valor cai para a linha de baixo e o `text-align: right` do `dd`
   * mantém a coluna alinhada à direita. Continua legível como ficha técnica.
   */
  flex-wrap: wrap;
}

.specs > div + div {
  border-top: 1px solid var(--linha);
}

.specs dt {
  font-size: var(--t-xs);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  font-weight: 600;
  color: var(--aco-400);
}

.specs dd {
  margin: 0;
  text-align: right;
  font-size: var(--t-base);
  color: var(--aco-200);
}

.specs-sufixo {
  color: var(--aco-400);
  font-size: var(--t-sm);
}

/* campo REBAIXADO. Luz vem de cima ⇒ sombra interna no topo.
   raio interno = raio da ficha (14) − padding (4) = 10.
   Borda --linha (6%): agora é puramente ESTÉTICA — reflexo de luz na quina.
   A separação do campo contra o card vem do preenchimento rebaixado, nao da
   borda. Custo: .campo-copiavel:active usa --aco-500 como distincao de estado. */
.campo {
  background: var(--aco-800);
  border: 1px solid var(--linha);
  border-radius: var(--r-2);
  padding: var(--e-3) var(--e-4);
  color: var(--aco-050);
  box-shadow: var(--elev-dentro);
  /**
   * Captura 5 (2026-09-29): position: relative para acomodar
   * filhos absolutos (InputLed dos campos do checkout).
   *
   * O e-mail e o CPF do checkout usam um LED de estado à
   * direita do texto. O LED precisa de `position: absolute`
   * (right: 16px = padding lateral do .campo) para flutuar
   * dentro do campo sem distorcer o input.
   *
   * Sem `position: relative` no pai, o absolute escapa do
   * .campo e vai para o .tela — o LED aparecia no canto da
   * pagina, fora do campo.
   */
  position: relative;
}

.campo + .campo {
  margin-top: var(--e-2);
}

/**
 * LED de estado dentro de um `.campo` — wrapper absoluto do
 * `<InputLed />`, um SVG de 20x20 que aparece à direita do
 * texto do input.
 *
 * Por que wrapper, não o SVG direto: permite trocar o LED
 * por outro elemento (texto de erro inline, por ex.) sem
 * reescrever o seletor CSS. E o `pointer-events: none` não
 * rouba o clique do input.
 *
 * Posição: `right: 16px` alinha com o padding lateral do
 * `.campo` (16px), e `top: 50%; transform: translateY(-50%)`
 * centraliza verticalmente — funciona em qualquer altura de
 * .campo (input 48px, textarea maior).
 *
 * O seletor `.campo:has(> .input-led-wrap) > input` adiciona
 * `padding-right: 36px` (= 16px gap + 20px LED) ao input
 * para o texto digitado não invadir o LED. Sem isso, o
 * cursor do input passa por baixo do LED nos últimos 20px.
 *
 * Histórico: introduzido em `4ea7af5` (2026-09-29, "inputs do
 * checkout ganham o visual .campo"), removido em `263111e`
 * (2026-09-29, "escopar --alinhamento-acao para .suporte-bloco"
 * — o autor reescreveu o bloco `.campo` para remover só o
 * `position: relative`, mas apagou também o `.input-led-wrap`
 * que dele dependia). Re-aplicado em `baa81a4` (2026-10-02) —
 * AUD-E2E-001.
 */
.input-led-wrap {
  position: absolute;
  right: var(--e-4);
  /* Alinhamento vertical: o LED precisa ficar centralizado no texto do input,
   * não no .campo (que tem o .campo-rotulo acima, deslocando o centro
   * do .campo para cima em relação ao input).
   *
   * O input ocupa a metade inferior do .campo:
   *   - input padding-top (--e-2 = 8px) + line-height (--lh-corpo ≈ 24.8px) ÷ 2
   *     = 20.4px do topo do input até o centro do texto
   *
   * O `top: 65%` do .campo (89.4px) = 58.1px = ~60.4px do topo do .campo ao
   * centro do texto do input (medido). Empírico, mas robusto: o input
   * sempre ocupa o mesmo retângulo dentro do .campo independente do label.
   */
  top: 65%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  pointer-events: none;
}

.campo:has(> .input-led-wrap) > input,
.campo:has(> .input-led-wrap) > textarea {
  padding-right: calc(var(--e-4) + 20px);
}

/**
 * FASE 3 (2026-09) — campo de upload de arquivo.
 *
 * Borda tracejada em vez de hairline contínua para sugerir
 * "zona de arquivo" sem implementar uma dropzone (a dropzone
 * da biblioteca é JS-only e fugiria do sistema visual). O
 * tracejado é mais discreto que um retângulo destacado —
 * herda o `--linha-forte` (24% branco) para se aproximar do
 * peso de um campo de input, mas ganha padrão tracejado.
 *
 * O input `type="file"` não recebe estilo especial aqui —
 * o navegador entrega o botão nativo, e estilizá-lo é quebra
 * de expectativa. O gancho visual fica no `.campo`.
 */
.campo[data-anexo] {
  border-style: dashed;
  border-color: var(--linha-forte);
}
.campo[data-anexo] input[type="file"] {
  /* o file input do navegador tem width padrão menor que o
     container — força ocupar a largura toda do campo para
     o botão "Escolher arquivo" não ficar miúdo na ponta. */
  width: 100%;
}

/**
 * FASE 6 (2026-09) — drag-hover na zona de anexo.
 *
 * Quando o cliente está com uma imagem arrastando sobre o
 * `.campo[data-anexo]`, a borda tracejada muda de "passiva"
 * (cinza fino) para "ativa" (mais grossa + tint de fundo).
 * A transformação é instantânea (sem transition) porque
 * drag é um gesto rápido — qualquer animação de 200ms ia
 * parecer lag.
 *
 * O tint é `rgba(255,255,255,0.04)` — o mesmo fundo dos
 * anexos da Fase 4 — para coerência visual.
 *
 * Por que `cursor: copy` não é suficiente: o navegador
 * já mostra esse cursor via `dropEffect = "copy"` no
 * `onDragOver`. A borda é a pista VISUAL para quem não
 * está olhando pro cursor (cliente de celular com trackpad,
 * por ex.).
 */
.campo[data-anexo][data-drag-hover="1"] {
  border-style: dashed;
  border-width: 2px;
  border-color: var(--sinal);
  background: rgba(255, 255, 255, 0.04);
}
.campo-rotulo {
  display: block;
  font-size: var(--t-xs);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  font-weight: 600;
  color: var(--aco-400);
  margin-bottom: var(--e-1);
}

/**
 * FASE 7 (2026-09) — counter de caracteres no textarea.
 *
 * Pequena linha à direita da nota (`Aperte Enviar quando
 * terminar.`). Cor varia por estado via `data-estado`:
 *   - ok     (default, < 80% do limite): cor de texto suave
 *   - aviso  (80-100%): cor amarela — "tá crescendo"
 *   - erro   (> 100%): cor vermelha + texto extra
 *
 * Posicionamento: à direita do texto de ajuda via
 * `float: right` (simples, dispensa flex no `.campo`).
 * O texto da `.nota` flui ao redor — alinha automaticamente.
 *
 * O counter usa `tabular-nums` para os dígitos não pularem
 * quando o número muda (cada dígito tem a mesma largura).
 */
.campo-contador {
  float: right;
  font-size: var(--t-xs);
  color: var(--aco-400);
  font-variant-numeric: tabular-nums;
  margin-left: var(--e-2);
  font-feature-settings: "tnum";
}

/* Soft warning a 80% do limite — cor âmbar (a mesma que
   o GitHub usa para "approaching limit"). Hardcoded
   porque o app não tem variável `--aviso` hoje (só
   `--erro` e `--ok`). Se um dia criarmos, basta substituir
   pelo var. */
.campo-contador[data-estado="aviso"] {
  color: #f59e0b;
  font-weight: 600;
}

/* Ultrapassou o limite — cor de erro (já existe no app),
   negrito. O texto extra " · tire N caracteres" adicionado
   no JSX explica o motivo sem precisar de tooltip. */
.campo-contador[data-estado="erro"] {
  color: var(--erro);
  font-weight: 700;
}

/**
 * FASE 10 (2026-09) — indicador de rascunho salvo.
 *
 * Aside no topo do painel de suporte, antes da lista de
 * chamados, quando o cliente tem algo digitado mas não
 * enviou. Visual:
 *   - Borda esquerda verde-água (4px) — sinaliza "atenção
 *     positiva", diferente do vermelho de erro
 *   - Fundo levemente amarelado (`--aviso-fraco`) — coerente
 *     com `.aviso[data-tipo="atencao"]` mas mais suave
 *   - Layout em 2 colunas: texto à esquerda, botões à direita
 *     (em mobile vira coluna única via media query)
 *
 * Por que não usar `.aviso` direto: o `<p className="aviso">`
 * é inline no fluxo (sem padding lateral generoso nem duas
 * colunas). O rascunho precisa de mais espaço para os dois
 * botões + texto explicativo.
 *
 * Por que `<aside>` e não `<div>`: o rascunho é conteúdo
 * RELACIONADO mas não PRINCIPAL — semanticamente é tangencial
 * ao fluxo "lista de chamados". `<aside>` é o tag certo
 * para isso (mesmo uso de `<aside>` no detalhe de suporte).
 */
.rascunho-aviso {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e-4);
  padding: var(--e-3) var(--e-4);
  margin-bottom: var(--e-4);
  border-left: 4px solid var(--sinal);
  background: rgba(109, 77, 255, 0.06);
  border-radius: var(--raio-md, 8px);
}

/* Em mobile (largura < 720px), os botões quebram para baixo
   do texto para não espremer. O flex vira coluna. */
@media (max-width: 720px) {
  .rascunho-aviso {
    flex-direction: column;
    align-items: stretch;
  }
}

.rascunho-acoes {
  display: flex;
  gap: var(--e-3);
  align-items: center;
  flex-shrink: 0;
}

.campo-valor {
  display: block;
  /* mono do sistema (não `--f-num`, que é subset de dígitos): credencial tem
     letra, e é lida caractere a caractere quando algo dá errado */
  font-family: var(--f-mono);
  font-size: var(--t-base);
  line-height: var(--lh-corpo);
  color: var(--aco-050);
  /* `anywhere` e não `break-all`: quebra só quando não couber, e prefere o
     limite de palavra. `break-all` picotava "fornecedor.dev" no meio mesmo
     quando havia lugar melhor. */
  overflow-wrap: anywhere;
}

.campo-acoes {
  display: flex;
  gap: var(--e-2);
  margin-top: var(--e-3);
}


/**
 * Dentro do `.campo` (estado mascarado — credencial ainda não revelada), a
 * fileira de ações precisa preencher a largura do campo, não shrink-to-fit
 * em torno do botão. Sem isto, num `.campo` de ~470px o `.campo-acoes` (que
 * é `display: flex`) colapsa para 200px — a largura intrínseca do botão
 * com `min-width: 200px` do media query desktop — e o "Revelar acesso" vira
 * uma faixa estreita à esquerda do campo, deixando vácuo à direita.
 *
 * Fora do `.campo` (Pedir código / Renovar acesso / ações de card), o
 * `.campo-acoes` é filho direto de `.tela` ou `.ficha`, e a regra não casa
 * — comportamento preservado.
 */
.campo > .campo-acoes {
  width: 100%;
}


/**
 * O botão dentro da fileira de ações ocupa toda a largura — a regra de
 * desktop (`@media (pointer: fine) .acao { width: auto }`) foi feita para
 * o `.polegar .acao` (CTA do rodapé), mas pega todos os .acao. Aqui
 * o botão é a ação PRINCIPAL da seção do card: "Revelar acesso", "Assinar",
 * "Enviar pedido" — ele precisa encher a fileira em qualquer largura.
 */
.campo-acoes > .acao {
  width: 100%;
}
/**
 * Centraliza quando o .campo-acoes tem SÓ um .link (caso "Não está
 * funcionando? Pedir troca"). O mesmo wrapper guarda 3 estados diferentes
 * (nota de aviso / form aberto / link solto) — centralizar via `:has()`
 * evita classe condicional no JSX.
 *
 * `.campo-acoes` é `display: flex` (row) e `.link` é `align-self: flex-start`
 * herdado do caso `.tela` (flex column). Aqui anulamos os dois: o `justify-content`
 * centraliza no eixo principal, o `align-self` no eixo cruzado.
 */
.campo-acoes:has(> .link) {
  justify-content: center;
}
.campo-acoes:has(> .link) > .link {
  align-self: center;
}

/**
 * Faixa de identidade do portal: e-mail do usuário centralizado, "Sair"
 * logo abaixo, também centralizado.
 *
 * Layout em coluna em toda largura — não é mais "linha" em desktop e coluna
 * em 360px, porque o e-mail muito comprido (até 64 caracteres no pior caso)
 * espremia o "Sair" e ele perdia a hairline. Centralizado, o cartão respira:
 * o e-mail tem sua linha própria, o botão tem a sua, e nada compete por
 * largura. `.identidade-email` continua com `text-overflow: ellipsis` para
 * o e-mail que mesmo assim passar da tela — trunca com reticências em vez
 * de empurrar o layout.
 *
 * O "Sair" continua sendo um botão terciário real, com a borda hairline que
 * o sistema reserva para "segundo caminho" — visível o bastante para não ser
 * confundido com texto, discreto o bastante para não brigar com a aba
 * "Assinaturas" que vem logo abaixo e que é onde o usuário está prestando
 * atenção.
 */
.identidade-conta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e-2);
  margin: var(--e-2) 0 var(--e-3);
}

.identidade-email {
  color: var(--aco-050);
  font-weight: 600;
  font-size: var(--t-base);
  text-align: center;
  /* e-mail longo não pode empurrar nada para fora da tela — trunca com "…"
   * em vez de estourar o container ou trocar o cartão de linha */
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/**
 * `.acao` é `width: 100%` por padrão (assume coluna cheia em formulários).
 * Aqui ele é um chip centralizado — largura pelo conteúdo, sem esticar até
 * as bordas do cartão. A classe extra existe só para esta regra de largura.
 */
.identidade-sair {
  width: auto;
}

/**
 * Conteúdo só para tecnologia assistiva. Existe para mantermos o `<h1>`
 * da página sem repetir visualmente o rótulo "Minha conta" — o cabeçalho de
 * navegação já disse onde estamos. Padrão clássico de "visualmente oculto,
 * presente para leitor de tela": `clip` em vez de `display: none` para que
 * NVDA/VoiceOver ainda anunciem.
 */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/**
 * O campo como alvo de cópia: o dado ocupa a largura inteira e a área tocável
 * é a superfície toda (>80px de altura, contra o piso de 48). Botão ao lado
 * roubava largura do valor e quebrava credencial longa em duas linhas.
 */
.campo-copiavel {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
  transition:
    border-color var(--dur-micro) var(--curva),
    background var(--dur-micro) var(--curva);
}

.campo-copiavel:active {
  border-color: var(--aco-500);
  background: var(--aco-600);
}

.campo-topo {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--e-3);
  flex-wrap: wrap; /* rótulo e dica quebram antes de empurrar a coluna */
}

/**
 * A dica de toque fica em caixa BAIXA de propósito. Em caixa alta ela tinha o
 * mesmo peso do rótulo do campo, e a linha virava dois títulos brigando. Caixa
 * baixa ao lado de caixa alta é a diferença hierárquica mais barata que existe
 * — sem gastar tamanho nem cor.
 */
.campo-dica {
  font-size: var(--t-xs);
  letter-spacing: var(--tr-nota);
  font-weight: 500;
  color: var(--aco-400);
  white-space: nowrap;
}

.campo-copiavel[data-copiado="1"] {
  border-color: var(--ok);
}

.campo-copiavel[data-copiado="1"] .campo-dica {
  color: var(--ok);
}


/* =============================================================================
   FAMÍLIA .botao — a loja inteira usa esses três nomes.
   =============================================================================

   .botao           → botão neutro (a maioria: "Voltar", "Fechar")
   .botao-primario  → ação da tela (um por formulário, como no admin)
   .botao-secundario→ contorno, par do primário, ou ação de fila

   O default É o neutro — a regra é a mesma do admin (ARTE-09 §1):
   quem escreve .botao está pedindo "um botão", não "o botão mais
   importante da tela", e ganha o peso certo sem precisar lembrar.
   Primário custa uma palavra, e quem a escreve afirma que aquela é
   A ação da tela.

   Contraste medido contra --aco-700 (card):
     texto  --aco-200 sobre transparente/card   11,5:1 ✓ AAA
     borda  --linha-forte (24%) sobre card       2,1:1 ✓ 3:1 grão gráfico
     texto  --sinal-texto sobre --sinal          5,1:1 ✓ AA (já no teste)
   ============================================================================= */

.botao,
.botao-primario,
.botao-secundario {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--e-2);
  min-height: var(--toque); /* 48px — piso de polegar */
  padding: 0 var(--e-4);
  font: inherit;
  font-size: var(--t-base);
  font-weight: 600;
  letter-spacing: var(--tr-corpo);
  border-radius: var(--r-2);
  border: 1px solid transparent;
  cursor: pointer;
  transition:
    background-color var(--dur-micro) var(--curva),
    border-color var(--dur-micro) var(--curva),
    color var(--dur-micro) var(--curva);
}

.botao:disabled,
.botao-primario:disabled,
.botao-secundario:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* neutro — contorno discreto, ação sem peso */
.botao {
  background: transparent;
  color: var(--aco-200);
  border-color: var(--linha-forte);
}

.botao:hover {
  background: var(--aco-800);
  color: var(--aco-050);
  border-color: var(--aco-500);
}

/* cheio — a decisão da tela, um por formulário aberto */
.botao-primario {
  background: var(--sinal);
  color: var(--sinal-texto);
  border-color: var(--sinal);
}

.botao-primario:hover {
  box-shadow: var(--elev-1);
}

/* contorno — par do primário, ação de fila, "Pedir código", "Fechar" */
.botao-secundario {
  background: transparent;
  color: var(--aco-200);
  border-color: var(--linha-forte);
  width: 100%;
}

.botao-secundario:hover {
  background: var(--aco-800);
  color: var(--aco-050);
  border-color: var(--aco-500);
}

/* nota + acao leve na mesma linha, para a acao nao virar um bloco a mais */

.linha-nota {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e-3);
  margin: var(--e-3) 0 0;
  flex-wrap: wrap; /* a nota e a ação leve quebram antes de empurrar a coluna */
}

/**
 * Variante do .linha-nota quando a ação precisa ocupar 100% da largura
 * (botão "Pedir código" — fica sozinho na linha, então vira bloco).
 * Sem essa variante, o `display: flex` + `align-items: center` do pai
 * deixa o filho na largura do texto, mesmo com `width: 100%` no botão.
 */
.linha-nota--cheia {
  display: block;
  margin: var(--e-3) 0 0;
}

/**
 * "copiar" ao lado do dado, não abaixo dele. Alvo de 48px (o piso de toque)
 * mas peso visual de um chip: a credencial é o produto entregue, e o botão
 * não pode gritar mais alto que ela.
 */
.copiar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  min-height: var(--toque);
  padding: 0 var(--e-3);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-1);
  background: transparent;
  color: var(--aco-200);
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 600;
  cursor: pointer;
  transition:
    color var(--dur-micro) var(--curva),
    border-color var(--dur-micro) var(--curva);
}

.copiar:active {
  border-color: var(--sinal);
  color: var(--sinal);
}

.copiar[data-copiado="1"] {
  border-color: var(--ok);
  color: var(--ok);
}

/* =============================================================================
   AÇÃO
   ============================================================================= */
.acao {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: var(--toque);
  padding: 0 var(--e-4);
  border: 1px solid transparent;
  border-radius: var(--r-2);
  background: var(--sinal);
  color: var(--sinal-texto);
  font-family: var(--f-texto);
  font-size: var(--t-base);
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--elev-1);
  transition:
    transform var(--dur-micro) var(--curva),
    box-shadow var(--dur-micro) var(--curva),
    background var(--dur-micro) var(--curva);
}

.acao:active {
  transform: scale(0.98);
  box-shadow: var(--elev-0);
}

/**
 * TRÊS PESOS DE AÇÃO, e a diferença entre eles é hierarquia, não decoração.
 *
 *   laranja cheio  a decisão da tela — só onde há UMA decisão, ou onde há hora
 *                  marcada (renovar com o prazo acabando)
 *   secundária     a ação principal de um card, numa tela que é lista
 *   terciária      ação de apoio: o segundo caminho, o "ou então"
 *
 * O terceiro peso nasceu de um print: "Ocultar agora" e "Renovar acesso"
 * empilhados eram dois retângulos idênticos, e nada dizia qual importava mais
 * (ARTE-05, item 6). Dois pesos não bastam quando o card tem duas ações.
 */
.acao[data-secundaria="1"] {
  background: transparent;
  color: var(--aco-050);
  border-color: var(--aco-500);
  box-shadow: none;
}

/**
 * A terciária mantém a BORDA, só que na hairline fraca. Sem borda nenhuma ela
 * virava texto cinza centralizado no meio do card e lia como título de seção,
 * não como controle — dá para ficar quieto sem deixar de parecer botão.
 */
.acao[data-perigo="1"] {
  background: var(--erro);
  color: var(--fundo);
}
.acao[data-perigo="1"]:active {
  box-shadow: var(--elev-0);
  transform: scale(0.98);
}

.acao[data-terciaria="1"] {
  background: transparent;
  color: var(--aco-200);
  border-color: var(--linha);
  box-shadow: none;
  font-weight: 600;
}

.acao[data-terciaria="1"]:active {
  color: var(--aco-050);
  border-color: var(--aco-500);
}

/**
 * Desabilitado PERDE O PREENCHIMENTO em vez de baixar a opacidade.
 *
 * `opacity: 0.4` sobre o laranja produzia um marrom sujo que não lê como
 * "desligado" — lê como "cor errada". Vazio com texto apagado é o que o olho
 * entende como indisponível, e de quebra o botão para de gritar numa tela onde
 * ele ainda não pode ser usado.
 */
.acao:disabled,
.acao[aria-disabled="true"] {
  background: transparent;
  color: var(--aco-400);
  border-color: var(--linha);
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.acao[data-copiado="1"] {
  background: var(--ok);
  color: var(--sinal-texto);
}

/* carregando: o botão não muda de tamanho, só troca o conteúdo */
.acao[data-carregando="1"] {
  color: transparent;
  position: relative;
}

.acao[data-carregando="1"]::after {
  content: "";
  position: absolute;
  width: 16px;
  height: 16px;
  border: 2px solid var(--sinal-texto);
  border-top-color: transparent;
  border-radius: var(--r-full);
  animation: girar 700ms linear infinite;
}

@keyframes girar {
  to {
    transform: rotate(360deg);
  }
}

/* =============================================================================
   LAYOUT DE TELA — ação primária ao alcance do polegar
   ============================================================================= */
.tela {
  display: flex;
  flex-direction: column;
  gap: var(--e-4);
  min-height: calc(100dvh - 56px - var(--e-4));
  padding-top: var(--e-3);
}

/**
 * RITMO: o vão tem que ser DESIGUAL para agrupar (ARTE-08 §3).
 *
 * Medido antes: os vãos entre blocos de primeiro nível da página de produto
 * eram `16, 16` — tudo `--e-4`. Espaçamento constante não tem grupos, tem uma
 * lista: nada pertence junto e nada se separa. O que faz uma composição parecer
 * calma não é ter MUITO espaço, é ter espaço desigual — o que pertence junto
 * quase se toca, e entre um assunto e outro cabe um respiro grande.
 *
 * Duas regras bastam, e nenhuma precisa de token novo:
 *
 * 1. TÍTULO DE SEÇÃO ABRE COM FOLGA. Um `h2`/`h3` que não é o primeiro filho
 *    ganha espaço extra acima: o vão passa de 16 para 40. É ali que começa
 *    outro assunto.
 * 2. O QUE VEM LOGO DEPOIS DO TÍTULO DA TELA GRUDA NELE. Nome do produto e
 *    descrição são um bloco só, não dois — a descrição existe para qualificar o
 *    título, e 16px entre os dois os apresentava como irmãos.
 *
 * `.polegar` não entra: `margin-top: auto` vence e é a regra do polegar, que
 * manda o botão para o fim da tela de propósito.
 */


/* o primeiro h1 da tela nao tem espacamento proprio --
   a Pilha nao tem padding-top como a .tela, e a margem
   nativa do browser (~16-32px) empurrava o titulo para
   baixo. Zeramos a margem em ambos os containers. */
.tela > h1,
.pilha > h1 {
  margin-top: 0;
}

/* o h1 também abre seção: o que vem depois dele começa o conteúdo da tela */
.tela > h1 + *,
.pilha > h1 + * {
  margin-top: var(--e-5);
}

/**
 * ADR-0012-2 (2026-09-30) — Checkout (`.tela-checkout`): modificador
 * escopado à `<div className="tela tela-checkout">` do `checkout-form.tsx`.
 * Centraliza o CONTAINER e o CONTEÚDO da tela de checkout (h1, h3 do card
 * de plano, rótulos de campo, texto da `.nota`), revertendo parcialmente
 * ARTE-15c (regra 2). O carrinho é uma decisão de compra, o cliente está
 * olhando o que vai pagar — centralização casa com a regra do laranja.
 *
 * O `.tela-checkout` é aplicado em 3 branches do checkout-form:
 *   - carrinho vazio (linha 269)
 *   - carrinho com tudo esgotado (linha 311)
 *   - checkout com itens (linha 340)
 *
 * Três regras, todas escopadas:
 *   1. `text-align: center` no container — cascateia pra inline content
 *      (texto do h1, texto da `.nota`, rótulos do `.campo`).
 *   2. `.ficha-topo` muda de `justify-content: space-between` (regra global,
 *      que serve pra h3+selo nas extremidades) pra `center` — aqui os
 *      planos são cartões de decisão (h3+preço OU selo+estado), não
 *      comparação de coluna.
 *   3. `.campo-rotulo` ganha `text-align: center` explícito — o `<label>`
 *      é `display: block` por default mas sem texto inline (só texto puro),
 *      então `text-align` no pai não cascateia com a mesma confiança que
 *      num parágrafo. Explícito aqui documenta a decisão.
 *
 * Exceções verificadas (não muda):
 *   - `.linha-total` (convenção tabela rótulo↔preço) tem `display: flex;
 *     justify-content: space-between` — `text-align` no pai não pega.
 *   - `<input>` é replaced element; texto digitado lê mal centralizado.
 *   - `.polegar` já tem `justify-content: center` global.
 *   - `.campo-acoes` da ficha do item do carrinho (Remover) é filho
 *     direto do `.tela-checkout`, não do `.passe-digital-ficha`, então
 *     `.passe-digital-ficha .campo-acoes { flex-direction: column }`
 *     do ADR-0012-1 NÃO aplica aqui.
 *
 * Histórico: aplicado em `285ba36` (2026-09-30), perdido acidentalmente em
 * `64de1ad` (2026-09-29, "h1 do PIX centralizado"). Re-aplicado em `baa81a4`
 * (2026-10-02) durante destravamento do marge. AUD-E2E-001.
 */
.tela-checkout {
  text-align: center;
  /* O polegar é FIXO no rodapé da viewport em mobile. Para que o
   * cliente possa rolar até ver TODO o conteúdo acima do polegar,
   * o .tela-checkout precisa de padding-bottom generoso (altura do
   * polegar + folga). Sem isso, o cliente rolaria até o fim e o
   * último bloco visível seria cortado pelo polegar. */
  padding-bottom: calc(80px + env(safe-area-inset-bottom));
}
.tela-checkout form.form {
  /* margin-bottom zerada: o <form> rola com a página e termina antes
   * do polegar fixed (que está em bottom: 0 da viewport). Não há
   * sobreposição. */
  margin-bottom: 0;
}
/**
 * TECLADO VIRTUAL MOBILE — quando o teclado sobe, o polegar (fixed) fica
 * atrás do teclado (clicável mas invisível). O hook useMantemCampoVisivel
 * (apps/web/app/ui/teclado.ts) marca `body.tela-com-teclado` quando o
 * `visualViewport.height` encolhe — aqui desfazemos o polegar e damos folga
 * ao formulário para o hook poder rolar até o campo sem cortar.
 *
 * Só age em pointer:coarse (touch). Em desktop o teclado não sobrepõe a
 * viewport, e desfazer o fixed abriria 80px de vazio à toa. 
 *
 * Por que 480px no padding-bottom: altura típica de teclado mobile (Pixel 7,
 * 40% de 896px) + polegar (~80px) + folga (16px). 480 é folga generosa —
 * medido em E2E (`apps/web/e2e/teclado-checkout.spec.ts`), com 320px o campo
 * ainda ficava cortado em viewport de 896.
 *
 * Ver CLAUDE.md § A regra do laranja e § polegar — polegar precisa de
 * `position: sticky` em tela curta, mas com teclado aberto ele some do
 * viewport shrink de iOS; `static` aqui é o caminho certo.
 */
@media (pointer: coarse) {
  body.tela-com-teclado .polegar {
    position: static;
    margin-top: var(--e-4);
    border-top: 1px solid var(--linha);
    background: transparent;
    padding: var(--e-3) 0 0;
  }
  body.tela-com-teclado .tela-checkout {
    /**
     * Folga para o hook rolar o campo acima do teclado. Sem isso, o hook
     * pede para rolar mais do que `scrollHeight - innerHeight` permite e o
     * campo fica cortado no topo da viewport visível.
     *
     * `--altura-teclado` é setado por teclado.ts em CSS custom prop. Quando
     * não há teclado (ou navegador sem `visualViewport`), cai no fallback
     * 320px. Com o valor dinâmico: o padding é EXATAMENTE a altura que o
     * teclado ocupou, sem vão sobrando quando o cliente rola a página
     * intencionalmente para baixo.
     */
    padding-bottom: max(var(--altura-teclado, 320px), var(--e-3));
  }
}
.tela-checkout .campo-rotulo {
  text-align: center;
}

/* Centraliza o valor preenchido dentro do .campo-trigger no checkout
 * (ex: "owner@owner.com", "713.983.351-69"). Sem isto, o label fica
 * centralizado mas o valor fica a esquerda — inconsistente.
 *
 * Por que centralizar aqui mas NAO no <input>: o .campo-trigger e'
 * um <button> (nao e' input replaced element), entao o texto
 * centraliza naturalmente. <input> continua com text-align: left
 * (regra global, ver comentario em .campo input) porque digitar
 * texto longo centralizado le' mal. */
.tela-checkout .campo-trigger {
  text-align: center;
}
/**
 * Centralização horizontal do bloco .ficha-topo dentro da ficha. Era regra 2
 * do ADR-0012-2 (2026-09-30) e foi removida por acidente em 5eb3ccf (PR #1,
 * "fechar 5 itens inacabados") sem reposição — o @media (pointer: coarse)
 * adicionado na mesma região NÃO substitui esta (só age com teclado virtual
 * aberto, e o resto do tempo os filhos do .ficha-topo ficam em
 * space-between herdado do .ficha-topo global).
 *
 * Sem esta regra, os testes adr-0012-checkout.spec.ts:163 e
 * adr-0012-suporte.spec.ts (mesma medida no Suporte) falham: o h3 do item
 * fica à esquerda em vez de centralizado dentro da ficha. 18 falhas no CI
 * do PR #4 expostas pelo primeiro run completo da suíte E2E.
 */
.tela-checkout .ficha-topo {
  justify-content: center;
}

/**
 * ADR-0012-5 — centralização no Pedido (`/pedido/[id]`, 2026-10-09).
 *
 * Espelha o que `.tela-checkout` faz (ADR-0012-2) mas sem o `padding-bottom`
 * generoso (Pedido não tem polegar fixo). Centraliza o container, centraliza
 * texto DENTRO do container (h1, botão, rótulos). Mantém `<input>` à
 * esquerda (digitar não funciona centralizado).
 */
.tela-pedido {
  text-align: center;
  /* O polegar é FIXO no rodapé em mobile. Para o cliente rolar até ver
   * TODO o conteúdo acima do polegar, padding-bottom generoso (altura
   * do polegar + folga). Sem isso, o último bloco (cartão de aviso) é
   * cortado pelo polegar — Operador viu isso em 2026-10-09 e reportou
   * "cartão de amarelo tampado".
   *
   * Espelha o que .tela-checkout faz (ADR-0012-2, globals.css:2525). */
  padding-bottom: calc(80px + env(safe-area-inset-bottom));
}

/**
 * O `.ficha` dentro de `.tela-pedido` centraliza o conteúdo. Sem isto, o
 * `.leitura` (preço) fica grudado à esquerda porque é `display: flex` e
 * flex items não herdam `text-align` — eles são posicionados pelo
 * `align-items` do container.
 *
 * Espelha o que `.tela-checkout .ficha-topo { justify-content: center }`
 * faz no Checkout (ADR-0012-2).
 */
.tela-pedido .ficha {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.tela-pedido .ficha > * {
  width: 100%;
  text-align: center;
}
/**
 * Ritmo vertical do painel: blocos separados por `--e-3` (16px).
 *
 * O `.ficha` dentro de `.tela-pedido` é flex column (linha 2615). `gap`
 * aplica vão UNIFORME entre todos os filhos — mais simples que seletores `+`
 * e adequado aqui porque cada bloco do painel é semanticamente separado
 * (valor, contagem regressiva, status).
 *
 * A exceção é o contêiner da contagem (`div` com Arco+Mostrador), que tem
 * `margin: 0 auto var(--e-3)` inline no JSX — isso se soma ao `gap` e
 * daria 32px entre o valor e a contagem. Para compensar, o gap interno
 * desse contêiner com seus filhos é `var(--e-2)`.
 */
.tela-pedido .ficha {
  gap: var(--e-3);
}

/**
 * POLIMENTO POSICIONAL — micro-ajustes DENTRO dos blocos.
 *
 * O gap uniforme `--e-3` separa blocos entre si. Mas dentro de cada bloco
 * os elementos precisam de ajuste fino (ARTE-08 §3: mesmo peso = vão curto).
 *
 * Hierarquia do painel:
 *   Bloco 1: <div> (rótulo + Dinheiro)
 *   Bloco 2: <div> (MostradorArco SVG + Mostrador)
 *     - Arco e escala são o MESMO bloco visual (contagem)
 *     - Legenda é par da escala (mesma leitura)
 *   Bloco 3: <p> (status)
 */

/* Bloco 2: gap entre o SVG do Arco e o Mostrador (mesmo peso: contagem) */
.tela-pedido .ficha > div:nth-child(2) {
  gap: var(--e-2);
}
/* Legenda separada da escala por --e-2 (mesmo peso: leitura do instrumento) */
.tela-pedido .ficha .mostrador-legenda {
  margin-top: var(--e-2);
}
/* Código PIX: acima do gap vem do painel, abaixo vem do pilha-curta */
.tela-pedido .pilha-curta {
  margin-top: var(--e-3);
}
/* O `.estado` é flex column com `align-items: flex-start` (ponto na primeira
 * linha do texto). Para centralizar o CONTEÚDO (ponto + texto) no box,
 * `justify-content: center` resolve — sem mexer no `align-items` (que
 * garante o ponto na primeira linha). */
.tela-pedido .ficha .estado {
  justify-content: center;
}
/* O `.leitura` (preço) é display: flex com align-items: baseline — centraliza
 * o BOX (por causa de `width: 100%` acima) mas o CONTEÚDO fica à esquerda
 * do box. `justify-content: center` resolve: alinha R$/número no centro. */
.tela-pedido .ficha .leitura {
  justify-content: center;
}
/* Código PIX copia-e-cola: à esquerda para ser legível (string longa com
 * word-break, centralizada vira coluna desnecessária). Exceção explícita. */
.tela-pedido .pix-codigo,
.tela-pedido .pilha-curta .pix-codigo {
  text-align: left;
}

/**
 * Título centralizado dentro de uma `.tela`. Usado no Pedido e em qualquer
 * outra `.tela` que precise de h1 centralizado (sem mexer no `text-align`
 * do container). Override local.
 */
.tela-titulo-centralizado {
  text-align: center;
  margin-inline: auto;
}

/**
 * Item do carrinho (2026-10-05): preço + duração abaixo do nome, e vãos
 * agrupando nome↔preço (assunto do item) e separando duração↔ação.
 *
 * O `gap` do .ficha é zerado dentro do item do checkout: os vãos são
 * controlados por margin-top em cada bloco, porque o `gap` do flex column
 * não consegue variar entre filhos. Por que "nome+preço colados, preço+duração
 * juntos, duração+ação separados":
 *   - nome e preço carregam o mesmo pensamento (o quê, quanto)
 *   - preço e duração são especificações do produto (as duas faces do plano)
 *   - ação não é dado
 * ARTE-08 §3: vão uniforme só vale quando os blocos têm o mesmo peso.
 */
.tela-checkout .ficha-topo.grupo-assunto + .ficha-topo.grupo-assunto {
  margin-top: var(--e-1);
}
/* preço e duração são as duas faces do plano (especificação), por isso o
   vão é o mesmo do grupo-assunto (4px). */
.tela-checkout .ficha-topo.grupo-assunto + .ficha-topo.grupo-meta {
  margin-top: var(--e-1);
}
/* mas a ação "Remover" é decisão, não dado — separado por --e-3. */
.tela-checkout .ficha-topo.grupo-meta + .campo-acoes {
  margin-top: var(--e-3);
}
/* Quando há chip de "esgotado" entre o selo e a ação, mantém o mesmo vão
   do seletor anterior: o "esgotado" é continuação do bloco de metadados. */
.tela-checkout .linha-nota + .campo-acoes {
  margin-top: var(--e-3);
}

/**
 * OS CAMPOS DO CHECKOUT PREENCHEM O FORM.
 *
 * A regra global (`@media (pointer: fine)`) limita `.campo:has(> input)`
 * a 480px — projetado para telas densas (conta, suporte) onde 560px de
 * input viraria faixa sem respiro. Mas o checkout é decisão de compra,
 * com o `.linha-total` ocupando 560px e o `.form` mesmo tanto. Limitar
 * o campo a 480px quebra a alinhamento: o campo fica 80px mais estreito
 * que o total, parecendo deslocado.
 *
 * No checkout, o campo ocupa 100% do `.form` (já garantido por
 * `.form > .campo { width: 100% }`), e o `.form` tem padding de
 * `--e-5` de cada lado — o que resulta nos 560px medidos.
 */
.tela-checkout .campo:has(> input),
.tela-checkout .campo:has(> select),
.tela-checkout .campo:has(> textarea),
.tela-checkout .campo > input,
.tela-checkout .campo > select,
.tela-checkout .campo > textarea {
  max-width: none;
}

/**
 * Título do item do carrinho sobe para `--t-xl` (24px): o nome do produto
 * é o assunto deste bloco, e o preço abaixo (mesmo tamanho) reforça a
 * paridade "o quê / quanto". Sem regra, herdaria `--t-lg` (20px) do `h3`
 * global.
 */
.tela-checkout .ficha-topo h3 {
  font-size: var(--t-xl);
  line-height: var(--lh-titulo);
}

/**
 * `.valor-preco` — preço do item do carrinho (e variantes que pedem 24px).
 *
 * Sem esta regra o span herdava 16px em `system-ui` (a fonte do corpo), e a
 * vírgula em proporcional grudava no dígito anterior — "29,90" lia como
 * "2990". Em `--f-num` (mono, subset só de dígitos e separadores), o avanço
 * tabular afasta a vírgula um dígito de cada lado. Regra do número (ARTE-08):
 * dígito quer avanço fixo, separador também.
 */
.valor-preco {
  font-family: var(--f-num);
  font-size: var(--t-xl);
  font-weight: 700;
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
  color: var(--aco-050);
  letter-spacing: var(--tr-leitura);
}
.valor-preco .valor-sep {
  /* Espaço manual entre inteiro e centavos: o mono tabular já afasta, mas
     a vírgula ainda fica colada no último dígito. 0.08em é o kerning
     calibrado para a Chivo Mono em 24px (proporcional à fonte). */
  margin-right: 0.08em;
}

/**
 * Preço em destaque — usado no painel do Pedido (`/pedido/[id]`).
 *
 * 40px (--t-3xl): maior que leitura (32px) mas menor que o QR code (220px),
 * e não compete com o QR. Sem símbolo, centralizado, mono tabular para
 * consistência com os outros preços do sistema.
 */
.valor-destaque {
  display: flex;
  align-items: baseline;
  justify-content: center;
  font-family: var(--f-num);
  font-size: var(--t-3xl);
  font-weight: 700;
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
  color: var(--aco-050);
  letter-spacing: var(--tr-leitura);
  line-height: 1;
}
.valor-destaque .valor-sep {
  /* Mesmo kerning do .valor-preco, proporcional à fonte maior */
  margin-right: 0.1em;
}

.tela > h2:not(:first-child),
.tela > h3:not(:first-child),
.pilha > h2:not(:first-child),
.pilha > h3:not(:first-child) {
  margin-top: var(--e-5);
}

.tela > h1 + p,
.tela > h1 + .nota,
.pilha > h1 + p,
.pilha > h1 + .nota {
  margin-top: calc(var(--e-2) - var(--e-4));
}

/* .tela > .polegar { margin-top: auto } — REMOVIDO:
   o polegar é agora fixed, não participa do flex-flow, margin-top:auto é inerte.
*/

.polegar {
  /* MOBILE: o polegar é FIXO no rodapé da viewport (não sticky).
   *
   * Razão: com position: sticky bottom: 0, o polegar flutuava SOBRE
   * o conteúdo que estivesse no rodapé da viewport. Em particular,
   * com o <form> do checkout ocupando 272px e o polegar 69px, em
   * viewport pequena (Pixel 7, vh=839) o polegar sticky ficava
   * sobreposto a um dos campos do formulário (o último campo, CPF).
   *
   * Com position: fixed, o polegar fica FORA do fluxo do documento e
   * sempre no rodapé da viewport. O conteúdo rola normalmente por
   * trás do polegar (que tem z-index: 50 e fundo opaco). O
   * .tela-checkout tem padding-bottom = 80px + safe-area-inset para
   * que o cliente possa rolar até ver TODO o conteúdo acima do
   * polegar. */
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 50;
  display: flex;
  justify-content: center;
  align-items: center;
  padding-top: var(--e-2);
  padding-right: var(--e-4);
  padding-left: var(--e-4);
  padding-bottom: calc(var(--e-3) + env(safe-area-inset-bottom));
  background: var(--aco-900);
}

@media (min-width: 768px) {
  /* DESKTOP: o polegar é estático no fim do .tela (flex container),
   * com margin-top: auto para colar no rodapé. Sem altura mínima no
   * .tela (regra separada, ver comentário lá), o polegar não estica
   * a página — fica logo após o conteúdo. */
  .polegar {
    position: static;
    padding: var(--e-4) 0 0;
    margin-top: auto;
    border-top: none;
    background: transparent;
  }
}

@media (pointer: fine) {
  /* Em pointer: fine (mouse), o polegar volta a ser sticky para ficar
   * acessível sem scroll. Em pointer: coarse (touch), fixo é melhor
   * porque garante o CTA sempre no rodapé do polegar. */
  .polegar {
    position: -webkit-sticky;
    position: sticky;
    bottom: 0;
    margin-top: auto;
    padding-inline: 0;
    background: none;
  }
}

/**
 * Suporte — botões no fluxo normal, sem flutuar sobre o formulário.
 * Empilhados com gap: Cancelar fica abaixo de Enviar, alinhados à direita.
 */
/**
 * Container responsivo mobile-first para ações de suporte.
 * Mobile: ações primárias empilhadas à direita
 * Desktop (>=768px): container em row com justify-between
 */
.suporte-bloco {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  width: 100%;
  margin-top: var(--e-6);
  gap: var(--e-2);
  /* Sem position sticky/fixed/absolute — flui no documento */
}

/**
 * Mobile (padrao):
 * - .suporte-bloco: coluna, itens a direita
 * - .acao: largura total
 *
 * Desktop (>=768px):
 * - .suporte-bloco: row, espaco-between
 * - .acao: largura natural
 */
@media (min-width: 768px) {
  .suporte-bloco {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--e-2);
  }

  .suporte-bloco .acao {
    width: auto;
    min-width: 200px;
  }
}

/* -----------------------------------------------------------------------------
   Suporte: busca e ver mais (ACHADO-6 + ACHADO-7)
   ----------------------------------------------------------------------------- */

/**
 * Campo de busca na lista de chamados.
 * Sprint 2 melhorias: estilizado como campo de formulario.
 */
.busca-bloco {
  margin-bottom: var(--e-3);
}

.busca-bloco input[type="search"] {
  width: 100%;
  padding: var(--e-2) var(--e-3);
  border: 1px solid var(--aco-600);
  border-radius: var(--r-2);
  background: var(--aco-900);
  color: var(--aco-100);
  font: inherit;
  font-size: var(--t-base);
}

.busca-bloco input[type="search"]::placeholder {
  color: var(--aco-400);
}

.busca-bloco input[type="search"]:focus {
  outline: 2px solid var(--aco-400);
  outline-offset: 2px;
}

/**
 * Lista de mensagens da conversa.
 * Sprint 2 melhorias: scroll se muitas mensagens.
 * O scroll-behavior:smooth fica dentro de prefers-reduced-motion:no-preference
 * (padrao do produto: ver @media no footer do arquivo).
 */
.conversa-lista {
  max-height: 60vh;
  overflow-y: auto;
}

@media (prefers-reduced-motion: no-preference) {
  .conversa-lista {
    scroll-behavior: smooth;
  }
}

/* Fim do suporte */
/**
 * Botao "Ver mais" abaixo da lista.
 * Centralizado para nao competir com a lista nem com o polegar.
 */
.ver-mais {
  display: flex;
  justify-content: center;
  margin-top: var(--e-4);
}

/*
 * Sprint 3 eficiencia: abas de filtro de status.
 * Segue o padrao de .abas existente no projeto.
 */
.suporte-filtros {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--aco-700);
  margin-bottom: var(--e-3);
}

.suporte-filtros .filtro-btn {
  flex: 1 1;
  padding: var(--e-2) var(--e-1);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--aco-400);
  font: inherit;
  font-size: var(--t-sm);
  cursor: pointer;
  transition: color var(--dur-micro) var(--curva);
  white-space: nowrap;
}

.suporte-filtros .filtro-btn:hover {
  color: var(--aco-200);
}

.suporte-filtros .filtro-btn.ativo {
  color: var(--aco-100);
  border-bottom-color: var(--aco-400);
}

/* Fim do suporte */

/* Assinatura visual — as barrinhas vivem no fluxo normal de .tela,
   fora do polegar fixed. */
.barrinhas {
  display: block;
  margin: var(--e-2) auto var(--e-2);
}

/**
 * ONDE NÃO HÁ POLEGAR, NÃO HÁ REGRA DO POLEGAR.
 *
 * Colar a ação no rodapé existe por um motivo físico: a mão que segura o
 * telefone alcança a base da tela, não o meio dela. Com mouse esse motivo não
 * existe — e o efeito colateral fica: a `.tela` esticava até a altura da
 * viewport e abria centenas de pixels de vazio entre o conteúdo e o botão,
 * porque o botão estava sendo empurrado para um lugar que ninguém precisa que
 * ele esteja.
 *
 * `pointer: fine` e não largura: o que decide é o dispositivo apontador, não o
 * tamanho da tela. Tablet com dedo continua ganhando a barra; laptop pequeno,
 * não.
 */
@media (pointer: fine) {
  /**
   * Sem altura mínima no desktop: a regra de `min-height: calc(100dvh - 56px)`
   * existe para garantir que o `.polegar` (que tem `margin-top: auto`) cole
   * no rodapé da viewport no CELULAR. No desktop essa altura deixa um vazio
   * enorme entre o conteúdo curto — ex.: QR + código PIX — e o botão "Copiar",
   * porque o `margin-top: auto` empurra o botão lá para baixo de qualquer
   * jeito. Sem a altura forçada, o conteúdo define a altura da tela, e o
   * `.polegar` (já rebaixado a `position: static` logo abaixo) vem logo
   * depois do conteúdo, onde ninguém precisa de vácuo.
   *
   * `pointer: fine` e não largura: a decisão é o dispositivo apontador, não
   * o tamanho da tela. Tablet com dedo continua ganhando a altura cheia.
   */

  /*
   * Dentro do checkout fixo de 380px o botão ocupa a largura inteira.
   * Sobrescreve o .acao {width:auto} e .acao {min-width:200px} acima.
   */
  .produto-checkout .acao {
    width: 100% !important;
    min-width: 0 !important;
  }


  .tela {
    min-height: 0;
  }

  .polegar {
    position: -webkit-sticky;
    position: sticky;
    bottom: 0;
    /* margin-top: auto empurra o polegar para o fim da .tela no desktop.
       Funciona porque .tela { min-height: 0 } no desktop (linha 1348),
       então o sticky não estica a página — o polegar fica logo após o conteúdo. */
    margin-top: auto;
    padding-inline: 0;
    background: none;
  }

  /**
   * A ação para de ocupar a largura inteira. `width: 100%` é decisão de
   * celular — lá o botão largo é o alvo que a mão acerta sem mirar. Com mouse
   * o alvo é o cursor, e um botão de 570px vira uma faixa: no monitor a página
   * do produto tinha três barras "Comprar" atravessando a tela.
   */
  .acao {
    width: auto;
    min-width: 200px;
  }

  .polegar .acao {
    min-width: 240px;
  }

  /**
   * OS CAMPOS NÃO ESTICAM INFINITAMENTE.
   *
   * `max-width` num container de 480px alinha a coluna de formulário à coluna
   * de leitura do `.container` (640px) — inputs e selects param de virar
   * faixas estreitas sem ponto de quebra natural. No celular (container
   * tipicamente 360–430px) `max-width` maior que o container não limita nada,
   * então a regra é inerte fora do desktop.
   *
   * `pointer: fine` e não largura: tablet com dedo continua com campos
   * cheios, que é o alvo correto para o polegar.
   *
   * O `max-width` só vale para o `.campo` usado como **campo de formulário**
   * (que contém um `<input>`, `<select>` ou `<textarea>`). O `.campo` puro
   * (rótulo + valor + ação, ex.: "seus dados de acesso" no cartão de
   * entitlement) é um bloco de dado que precisa preencher a largura do
   * cartão em que está — sem este cuidado, ele aparece com 480px dentro de
   * um `.ficha` de 640px e o "Revelar acesso" fica desalinhado do resto.
   */
  .campo:has(> input),
  .campo:has(> select),
  .campo:has(> textarea),
  .campo > input,
  .campo > select,
  .campo > textarea {
    max-width: 480px;
  }
}

.pilha {
  display: grid;
  grid-gap: var(--e-4);
  gap: var(--e-4);
}

.pilha-curta {
  display: grid;
  grid-gap: var(--e-2);
  gap: var(--e-2);
}

/**
 * A PILHA NUNCA ALARGA.
 *
 * Item de grid nasce com `min-width: auto`, que resolve para o min-content do
 * conteúdo — e uma coluna de grid é dimensionada pelo item mais largo. Basta um
 * filho indivisível (uma faixa de abas, uma tabela, uma URL sem espaço) para
 * que a coluna inteira estoure e TODOS os irmãos fiquem mais largos que a tela,
 * levando o corpo da página a rolar de lado.
 *
 * `min-width: 0` devolve à pilha o comportamento que se espera dela: empilhar
 * na largura disponível e deixar quem tem conteúdo largo resolver por dentro
 * (rolagem própria, quebra, ou `overflow-x: auto`).
 *
 * Medido em /style: 9px de transbordo na página inteira, causados por uma única
 * faixa de abas em outra seção. A olho não dava para ver de onde vinha — todos
 * os blocos ficam 24px mais largos ao mesmo tempo, então nada parece o culpado.
 */
.pilha > *,
.pilha-curta > * {
  min-width: 0;
}

/* =============================================================================
   ESTADO
   ============================================================================= */
.selo {
  display: flex;
  align-items: center;
  min-height: 28px;
  padding: 0 var(--e-3);
  background: var(--aco-800);
  border: 1px solid var(--linha);
  border-radius: var(--r-full);
  color: var(--aco-200);
  font-size: var(--t-sm);
  font-weight: 600;
  /**
   * Sem `nowrap`, pelo mesmo motivo do `.estado`: o selo carrega uma FRASE
   * ("6 meses de acesso"), não uma palavra. Indivisível, ela virava a largura
   * mínima do card — e como o card é item de grid, a coluna inteira crescia com
   * ela: a 200% de fonte num aparelho de 320px isso empurrava a página em 95px.
   *
   * O texto só quebra quando não cabe, então a 100% nada muda de aparência. Com
   * duas linhas o raio `--r-full` continua dando a forma de pílula.
   */
  text-align: center;
}

/**
 * PAINEL — a ficha quando ela carrega a leitura principal da tela.
 *
 * Mais padding, elevação um degrau acima e hairline entre os blocos: é a
 * superfície do instrumento propriamente dito, distinta do card de lista.
 * Uma tela tem no máximo um painel; dois painéis significam que nenhum é o
 * principal.
 */
.painel {
  padding: var(--e-5) var(--e-4);
  /* glow violeta vem ANTES da sombra preta: o CSS pinta a primeira sombra
     por baixo, e a última por cima, então a sombra preta continua sendo o
     que recorta o painel do fundo. Sem essa ordem, o glow violeta cobriria
     o contact-shadow e o painel pareceria flutuar sem chão. */
  box-shadow: var(--glow-card), var(--elev-2);
}

.painel > * + * {
  margin-top: var(--e-4);
  padding-top: var(--e-4);
  border-top: 1px solid var(--linha);
}

/**
 * A VIRADA — a chegada do conteúdo da confirmação.
 *
 * O painel NÃO anima: ele é o mesmo objeto, na mesma posição, e mover a
 * moldura destruiria a única coisa que a tela tem a dizer ("não recomeçou,
 * virou"). Quem chega é o que está dentro dele, um bloco de cada vez, subindo
 * poucos pixels — o suficiente para o olho registrar a troca sem a tela dar um
 * salto.
 *
 * O `<h1>` entra junto porque o texto dele também mudou. O rodapé fica de
 * fora: a ação primária aparecendo devagar é a ação primária chegando atrasada.
 */
.tela[data-virada="1"] > h1,
.tela[data-virada="1"] .painel > * {
  animation: assentar var(--dur-tela) var(--curva) backwards;
}

.tela[data-virada="1"] .painel > *:nth-child(2) {
  animation-delay: calc(var(--dur-tela) / 4);
}

.tela[data-virada="1"] .painel > *:nth-child(3) {
  animation-delay: calc(var(--dur-tela) / 2);
}

@keyframes assentar {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

/* a ficha técnica já traz a própria hairline de topo; dentro do painel quem
   manda é a régua do painel, senão sobram 32px de vão e duas linhas no mesmo
   lugar */
.painel > .specs {
  margin-top: var(--e-4);
  border-top: 1px solid var(--linha);
}

/**
 * O BATIMENTO. Única animação em laço do produto — e existe porque o estado
 * que ela representa também é um laço: a tela está de fato perguntando ao
 * servidor a cada 3s. Animação que não corresponde a nada acontecendo é
 * decoração; esta corresponde.
 *
 * Só o ponto pulsa, nunca o texto: texto que pisca é ilegível e é o padrão
 * clássico de gatilho para quem tem sensibilidade a movimento. E o
 * `prefers-reduced-motion` do fim do arquivo desliga isto — a frase sozinha
 * já diz tudo.
 */
.batendo::before {
  animation: bater 1.8s var(--curva) infinite;
}

@keyframes bater {
  0%,
  70%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  35% {
    opacity: 0.35;
    transform: scale(0.8);
  }
}

/* nunca só cor: ponto + palavra, sempre (mas sem ponto — ver .estado::before { display: none }) */
.estado {
  display: flex;
  align-items: center; /* alinha o conteúdo (só texto agora) no centro */
  gap: var(--e-2);
  /* `margin: 0` porque isto vira `<p>` com frequência, e a margem herdada do
     parágrafo abria um vão fantasma no rodapé do painel. */
  margin: 0;
  font-size: var(--t-sm);
  line-height: var(--lh-nota);
  color: var(--aco-400);
  /**
   * SEM `white-space: nowrap`.
   *
   * Ele existia para o ponto não se separar da palavra — o que já é garantido
   * pelo `::before` como flex item com `flex: 0 0 auto`, que nunca quebra para dentro
   * do texto. O que o `nowrap` fazia de verdade era impedir a FRASE de
   * quebrar, e `.estado` nem sempre é uma palavra: na tela do PIX ele carrega
   * "esperando o PIX cair — esta tela vira sozinha".
   */
}

/* filho direto de painel: precisa ocupar a largura inteira, senão a hairline
   que o separa do bloco de cima acompanha o texto e para no meio da tela */
.painel > .estado {
  display: flex;
  width: 100%;
}

.estado::before {
  /* PONTO REMOVIDO.
   *
   * A frase do estado ("esperando o PIX cair — esta tela vira sozinha",
   * "recebido e acesso liberado", "não conseguimos falar com o servidor")
   * já carrega o estado na própria palavra. O ponto amarelo era polimento
   * visual — bonito quando posicionado certo, mas custou 5 sprints de
   * tentativa (sprint 9 grid, 10 sem display:flex, 11 componente, 12 1lh)
   * sem chegar a um resultado que funcionasse em todos os viewports.
   *
   * O Operador e o dono decidiram: em vez de gastar mais tempo brigando
   * com CSS frágil (pseudo-elemento sem baseline, ::before com content: ""
   * não tem baseline, navegador cai em comportamento imprevisível),
   * remover o ponto. A frase fala por si. O `display: none` aqui é a
   * versão conservadora: o HTML/JSX dos 13+ lugares que usam .estado
   * nao precisa mudar — so escondemos o ::before.
   *
   * Se um dia o sistema visual permitir um ponto real (com <span> em vez
   * de ::before, ou SVG), essa regra volta. Por agora: estado é a FRASE. */
  display: none;
}

/* Cores do ponto (por tipo) — desabilitadas. Ver .estado::before { display: none }. */
/* Mantidas como referencia historica; removiveis na proxima varredura. */
.estado[data-tipo="vazio"]::before {
  background: var(--aco-500);
}
.estado[data-tipo="espera"]::before {
  background: var(--espera);
}
.estado[data-tipo="erro"]::before {
  background: var(--erro);
}

.aviso {
  display: block;
  padding: var(--e-3) var(--e-4);
  border: 1px solid var(--espera);
  border-left-width: 3px;
  border-radius: var(--r-2);
  background: var(--espera-fraco);
  color: var(--aco-200);
  font-size: var(--t-sm);
  line-height: var(--lh-nota);
}

.aviso[data-tipo="erro"] {
  border-color: var(--erro);
  background: var(--erro-fraco);
}

/* Info (CPF ainda não digitado ou válido) — menos proeminente que erro */
.aviso[data-tipo="info"] {
  border-color: var(--aco-600);
  background: transparent;
}

/**
 * Só o PRIMEIRO `strong` é o título do aviso. A regra valia para qualquer um, e
 * um `<strong>` no meio da frase virava bloco: "Se ele vencer, / nada é cobrado
 * / e o pedido encerra" — a sentença partida em três linhas. Ênfase dentro do
 * texto continua sendo ênfase.
 */
.aviso > strong:first-child {
  display: block;
  margin-bottom: var(--e-1);
  color: var(--aco-050);
}

.aviso strong {
  color: var(--aco-050);
}

/**
 * CONVERSA DE SUPORTE — os dois lados têm que se distinguir de relance.
 *
 * Antes eram cards idênticos: mesma cor, mesma sangria, mesmo tudo, com a
 * autoria escondida numa linha de texto. Numa conversa isso é o defeito
 * central — quem lê rápido não sabe quem falou.
 *
 * A distinção usa a gramática que o sistema já tem: a barra à esquerda do
 * `.aviso`, que aqui significa "veio de fora". Nada de balão colorido; isto é
 * um instrumento, não um app de mensagem.
 */
.fala {
  border-left: 3px solid transparent;
}

.fala[data-de="suporte"] {
  /*
   * Borda usa --aco-600 (hairline), NAO --sinal.
   *
   * A regra do laranja (CLAUDE.md:305) diz que --sinal tem 3 papéis e
   * nenhum quarto: tempo (mostrador), decisao (botao primario), localizacao
   * (aba ativa). Borda de identificacao visual nao e' nenhum desses.
   *
   * A distincao "isto e' do suporte" ja existe em:
   *   - .fala-autor (texto "suporte")
   *   - data-de="suporte" (leitor de tela)
   *   - estrutura da lista
   *
   * Auditoria 2026-10-09 (docs/auditoria/suporte-auditoria-2026-10-09.md,
   * ACHADO-1).
   */
  border-left-color: var(--aco-600);
}

/**
 * Mensagem otimista (1.4). A conversa adiciona a fala localmente antes
 * do servidor confirmar, com id `tmp-...`. Marcamos via `data-enviando`
 * para:
 *   1. distinguir visualmente — opacidade reduzida + "Enviando…" no
 *      lugar do horário. O cliente sabe que está a caminho e não vê
 *      "minha mensagem está visível mas o servidor não respondeu"
 *      como bug;
 *   2. leitor de tela continua lendo como mensagem normal — não há
 *      mudança de papel nem região.
 *
 * Quando o GET oficial volta, o id muda para o UUID real e o atributo
 * some, devolvendo o visual definitivo.
 */
@media (prefers-reduced-motion: no-preference) {
  .fala[data-enviando="1"] {
    opacity: 0.6;
  }
}
.fala[data-enviando="1"] .nota {
  font-style: italic;
}

.fala-autor {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--e-3);
  margin: 0 0 var(--e-2);
}

.fala-corpo {
  margin: 0;
  white-space: pre-wrap;
  /* Quebra palavras longas (URLs, identificadores) sem estourar o layout.
   * `anywhere` quebra em qualquer ponto, nao so em espacos/hifens.
   * Auditoria 2026-10-09 ACHADO-3. */
  overflow-wrap: anywhere;
}

/* =============================================================================
   FORMULÁRIO
   ============================================================================= */
.form {
  display: grid;
  grid-gap: var(--e-3);
  gap: var(--e-3);
}

/* .form label: estilos delegados a .campo-rotulo quando dentro de .campo */

/**
 * INPUT/TEXTAREA STANDALONE — fora de .campo.
 *
 * O `.campo` é o container escuro com sombra interna. Quando o input está
 * DENTRO dele, é só um filho transparente — a borda/fundo pertencem ao pai.
 *
 * Quando o input está SOLTO (sem .campo), esta regra aplica o estilo
 * completo: fundo escuro, borda hairline, padding interno.
 */
.form > input:not(.campo input):not(.campo input *),
.form > textarea:not(.campo textarea):not(.campo textarea *) {
  display: block;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  box-sizing: border-box;
  min-height: var(--toque);
  padding: 0 var(--e-3);
  font-family: var(--f-texto);
  font-size: var(--t-base);
  color: var(--aco-050);
  background: var(--aco-800);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-2);
  box-shadow: var(--elev-dentro);
  transition: border-color var(--dur-micro) var(--curva);
}

.form textarea {
  padding: var(--e-3) var(--e-4);
  min-height: calc(var(--toque) * 2);
  line-height: var(--lh-corpo);
  resize: vertical;
}

.form input::placeholder,
.form textarea::placeholder {
  color: var(--aco-400);
}

/**
 * SELECT no formulário: reset de aparência + seta SVG inline.
 * - appearance: none remove a seta nativa do navegador em todos os engines.
 * - background-image SVG posicionado à direita com padding-right.
 * - option: fundo escuro + texto claro para legibilidade em qualquer OS.
 */
.form select {
  -moz-appearance: none;
       appearance: none;
  -webkit-appearance: none;
  width: 100%;
  min-height: var(--toque);
  padding: 0 40px 0 var(--e-3);
  font-family: var(--f-texto);
  font-size: var(--t-base);
  color: var(--aco-050);
  background-color: var(--aco-800);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--e-3) center;
  background-size: 16px 16px;
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-2);
  box-shadow: var(--elev-dentro);
  cursor: pointer;
  transition: border-color var(--dur-micro) var(--curva);
}

.form select:focus {
  outline: 2px solid var(--acao);
  outline-offset: 2px;
  border-radius: var(--r-2);
}

.form select option {
  background-color: var(--aco-900);
  color: var(--aco-050);
}

.linha-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  /* o SEXTO caso da mesma regra: fileira `space-between` sem `wrap` é
     indivisível, vira a largura mínima da coluna e empurra a PÁGINA de lado.
     A 200% de fonte o total do checkout transbordava 38px — e a tela nunca
     tinha sido medida porque `/checkout` não estava nas rotas do layout.spec */
  flex-wrap: wrap;
  gap: var(--e-3);
  padding-top: var(--e-3);
  border-top: 1px solid var(--linha);
}

.pedido-cabecalho {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--e-3);
  margin-bottom: var(--e-2);
}

.pedido-cabecalho h1,
.pedido-cabecalho .nota {
  margin: 0;
}

.lista-linhas {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  margin-top: var(--e-4);
}

.lista-linhas > li {
  padding: var(--e-3) 0;
  border-bottom: 1px solid var(--linha);
}

.lista-linhas > li:last-child {
  border-bottom: none;
}

.lista-linhas-total {
  padding-top: var(--e-4);
}


/* =============================================================================
   NAVEGAÇÃO
   ============================================================================= */
.abas {
  display: flex;
  gap: var(--e-2);
  overflow-x: auto;
  /**
   * Sangria negativa + padding interno: a faixa de abas encosta nas bordas
   * do container (rolável de ponta a ponta) mas o CONTEÚDO dela mantém o
   * mesmo respiro lateral do resto da coluna. É o oposto de "esticar para
   * dentro", que era o defeito medido: sem isto, a aba ativa encostava
   * direto na borda do padding do `.container`, e a primeira aba à esquerda
   * e a última à direita liam como "soltas". Com isto, a faixa de abas se
   * comporta como uma faixa E o texto se comporta como texto.
   *
   * `min-width: 0` é o que faz o `overflow-x` acima funcionar de verdade.
   *
   * Sem ele, a faixa é item de flex/grid e herda `min-width: auto`, que resolve
   * para o min-content — a soma de todas as abas, que são `flex: 0 0 auto`.
   * Resultado: a caixa se recusa a encolher, nunca rola, e empurra a PÁGINA
   * INTEIRA para o lado. O corpo passa a rolar horizontalmente, que é
   * exatamente o que o sistema proíbe.
   *
   * Estava latente: com as quatro categorias do seed a soma cabia em 412px e
   * nada aparecia. Só apareceu quando o guia vivo ganhou uma seção de navegação
   * e as abas de exemplo passaram de 412px — 9px de transbordo na página toda,
   * medidos com `scrollWidth`, invisíveis a olho.
   */
  min-width: 0;
  margin-inline: calc(var(--e-4) * -1);
  padding-inline: var(--e-4);
  /**
   * `--e-4` (não `--e-2`): a faixa de abas precisa respirar tanto quanto os
   * cards entre si. Sem isto, em `/conta` os botões colavam no primeiro card
   * de assinatura — o `padding-bottom` da `.abas` somava só 8px ao
   * `padding-top` do `.ficha` (16px) = 24px, enquanto entre dois `.ficha`
   * consecutivos o vão é `padding-bottom` + `padding-top` = 32px.
   *
   * Mexer aqui (e não no JSX) porque a faixa aparece em mais de uma tela e
   * o comportamento certo é "toda faixa de abas respira igual a card".
   */
  padding-bottom: var(--e-4);
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /**
   * SEM máscara de desvanecimento.
   *
   * Ela existia para "avisar que há mais à direita sem desenhar seta", e o
   * preço era alto demais: quando a aba ATIVA é a última — que é o caso toda
   * vez que o cliente abre Suporte — ela ficava esmaecida e cortada, ilegível
   * justamente no item que diz onde a pessoa está.
   *
   * Item cortado pela metade na borda já comunica "tem mais", e é o mecanismo
   * natural de qualquer lista rolável. `scroll-padding` garante que a aba ativa
   * nunca encoste na borda ao ser trazida para a tela.
   */
  scroll-padding-inline: var(--e-4);
}

.abas::-webkit-scrollbar {
  display: none;
}

/**
 * `.abas-wrap` existe como ponto de ancora para a faixa de abas quando ela
 * não mora dentro de uma `.pilha` (ex.: `/conta`, onde as abas são filhas
 * diretas de `<main.container>`). A margem lateral negativa está na própria
 * `.abas`, então este wrap só precisa garantir um contexto de
 * posicionamento caso um dia a faixa ganhe um indicador sticky ou algo
 * assim — por ora é uma âncora vazia, e a regra é literalmente essa.
 */
.abas-wrap {
  position: relative;
}

.aba {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  min-height: var(--toque-min);
  /**
   * `--e-3` e não `--e-4`: as QUATRO abas do portal não cabiam em 412px.
   *
   * Medido no ARTE-09 (achado 5): `clientWidth` 380, `scrollWidth` 406 — 26px
   * cortados. A página não transbordava (o `min-width: 0` acima faz o trabalho
   * dele), mas "Suporte" aparecia como "Suport", cortado no meio da palavra e
   * sem nada indicando que aquilo rola. Palavra cortada ao meio não lê como "há
   * mais à direita"; lê como defeito de renderização. E a aba escondida era
   * justamente Suporte — quem tem problema é quem menos achava o caminho.
   *
   * Doze pixels a menos por lado devolvem 32px nas quatro abas, com folga sobre
   * os 26 que faltavam. O alvo de toque não muda: ele é vertical (`min-height`
   * de 44px logo acima), e o rótulo continua dando largura de sobra.
   *
   * Isto conserta o caso MEDIDO, não a classe inteira: a faixa de categorias do
   * catálogo usa nomes vindos do banco, de comprimento arbitrário, e lá o
   * transbordo volta a ser questão de quantas categorias existem. Para aquele
   * caso o que falta é afordância de rolagem, que é trabalho separado.
   */
  padding: 0 var(--e-3);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-full);
  background: transparent;
  color: var(--aco-400);
  font: inherit;
  font-size: var(--t-sm);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--dur-micro) var(--curva);
}

.aba[aria-current="true"] {
  background: rgb(var(--glow-sinal) / 15%);
  color: #ffffff;
  border-color: transparent;
  font-weight: 700;
}

.link {
  background: none;
  border: 0;
  padding: var(--e-2) 0;
  /**
   * `inline-flex` porque `min-height` NÃO SE APLICA a elemento inline.
   *
   * `.link` funcionava em `<button>` (que é inline-block) e falhava calado em
   * `<a>`: na página de produto o alvo media **32px**, bem abaixo do piso de 44
   * que o sistema promete. O CSS estava lá, correto, e não valia — do tipo de
   * defeito que print nenhum mostra e que só aparece medindo caixa por caixa.
   */
  display: flex;
  align-items: center;
  min-height: var(--toque-min);
  /**
   * `--link`, não `--sinal` (ARTE-08 §1). O quente tem duas funções neste
   * sistema — tempo e a decisão da tela — e link era a terceira, no seletor, ou
   * seja em todas as telas. Quem carrega a afordância aqui é o sublinhado.
   */
  color: var(--link);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  /**
   * `<button>` centraliza o texto e, como filho de `.tela` (que é flex column),
   * ainda estica para a largura toda — o "Voltar aos chamados" aparecia
   * centralizado no meio da tela como se fosse título. Alinhar e encolher para
   * o conteúdo devolve o comportamento de link.
   */
  text-align: left;
  align-self: flex-start;
  width: -moz-fit-content;
  width: fit-content;
}

/**
 * O quente volta no TOQUE. Ali ele deixa de competir por atenção e passa a
 * confirmar intenção — o mesmo papel que `.copiar:active` já dá a ele. Sem esta
 * regra, `--link-hover` seria token que ninguém lê, que é o que este sistema
 * chama de token não calibrado.
 */
.link:hover,
.link:active {
  color: var(--link-hover);
}

/* Breadcrumb minimalista: categoria acima do título na página de produto.
   Sem underline, com chevron à esquerda e slide no hover. */
.voltar-para {
  display: flex;
  align-items: center;
  gap: var(--e-1); /* ~4px */
  color: var(--aco-400);
  font-size: var(--t-sm);
  font-weight: 500;
  text-decoration: none;
  transition: color var(--dur-micro) var(--curva), transform var(--dur-micro) var(--curva);
}

.voltar-para svg {
  transition: transform var(--dur-micro) var(--curva);
}

.voltar-para:hover {
  color: var(--aco-050);
  transform: translateX(-4px);
}

.voltar-para:hover svg {
  transform: translateX(-4px);
}

/* =============================================================================
   ESTRUTURA
   ============================================================================= */
.container {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 var(--e-4);
}

/**
 * A COLUNA ALARGA SÓ ONDE HÁ GRADE.
 *
 * 640px é medida de LEITURA — a largura em que uma linha de texto ainda se lê
 * sem o olho se perder no retorno. Vale para checkout, espera do PIX, conta:
 * ali alargar pioraria.
 *
 * Lista de produto e lista de plano são outra coisa: existem para COMPARAR, e
 * comparar melhora com mais itens no campo de visão. O `:has()` deixa a própria
 * página decidir — quem tem grade ganha largura, quem não tem continua em
 * medida de leitura. Sem prop, sem rota, sem duas classes de container.
 */
@media (min-width: 900px) {
  main.container:has(.card-list[data-grade]) {
    max-width: 1040px;
  }

  /**
   * O cabeçalho acompanha a largura MAIOR, sempre.
   *
   * Ele não tem como saber se a página abaixo tem grade (`:has()` não atravessa
   * irmãos), e das duas assimetrias possíveis esta é a que não parece defeito:
   * cabeçalho largo com coluna de leitura estreita é um padrão que se lê como
   * intenção; cabeçalho estreito com conteúdo largo se lê como bug — os itens
   * ficam boiando por cima do conteúdo, sem alinhar com nada.
   */
  .site-header .container {
    max-width: 1040px;
  }
}

.site-header {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  z-index: 10;
  background: transparent;
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--linha);
}

.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 56px;
}

.site-header a {
  text-decoration: none;
  font-family: var(--f-display);
  font-weight: 700;
  letter-spacing: var(--tr-display);
  padding: var(--e-3) 0;
}

main.container {
  padding-top: var(--e-4);
  padding-bottom: var(--e-5);
}

.card-list {
  display: grid;
  grid-gap: var(--e-3);
  gap: var(--e-3);
  padding: 0;
  margin: 0;
  list-style: none;
}

/**
 * CONVERSA DE SUPORTE (Fase 1 do redesign, 2026-09).
 *
 * A `.card-list` padrão usa `--e-3` (8px) entre itens — folgado
 * para uma lista de pedidos, mas apertado para uma conversa em
 * que cada turno é uma decisão. O olho precisa distinguir UM
 * turno do próximo sem esforço, e 8px entre duas fichas de borda
 * viva fica parecendo colado.
 *
 * Aqui o `data-tipo="conversa"` sobe o gap para `--e-4` (16px).
 * E o ÚLTIMO item ganha `padding-bottom` generoso para o form de
 * resposta (logo abaixo) não "encostar" na última mensagem —
 * problema recorrente na revisão visual do 2.3 (mensagem otimista
 * com screenshot seguida imediatamente do input).
 *
 * Mantemos `data-tipo` no `<ul>` em vez de criar uma classe
 * `.conversa-lista` porque `card-list` também é usado em
 * pedidos/entregas (com gap apertado) — mexer no global
 * prejudicaria lá.
 */
.card-list[data-tipo="conversa"] {
  gap: var(--e-4);
  /* colchão embaixo do último item: afasta o form de resposta,
     que vinha grudado na última mensagem. */
  padding-bottom: var(--e-7);
}
.card-list[data-tipo="conversa"] > li:last-child {
  margin-bottom: var(--e-4);
}

/**
 * SEÇÕES DA CONVERSA (Fase 1) — respiro entre blocos.
 *
 * O detalhe da conversa é uma sucessão de "regiões" semânticas:
 * cabeçalho do ticket → lista de mensagens → ação binária
 * (resolver/reabrir) → form de resposta → ação binária (enviar).
 * Sem margem entre elas, o olho lê tudo como uma coisa só — que
 * é como a tela ficou antes do redesign (cards de mensagem
 * tocando o card de "marcar como resolvido", tocando o form).
 *
 * Aqui o wrapper `.conversa-bloco` aplica a margem superior
 * com `:not(:first-child)` para que só os blocos depois do
 * primeiro sejam afastados — economiza uma classe
 * `.conversa-primeiro-bloco`.
 */
.conversa-bloco {
  margin-top: var(--e-5);
}
.conversa-bloco:first-child {
  margin-top: 0;
}

/**
 * R.3 do refinamento (2026-09): subheading do bloco de digitação.
 *
 * É um `<h3>` na árvore semântica (leitor de tela anuncia o
 * "heading level 3, Sua resposta"), mas o estilo é discreto —
 * `--t-sm` (14px) em vez de `--t-lg` (20px) do `<h3>` comum.
 *
 * Razão: dentro do detalhe do suporte, o card de status binário
 * tem título forte (botão + nota em peso 2), e o título da
 * conversa (`ficha-titulo`) está logo acima com `--t-xl` (24px).
 * Um `<h3>` de 20px aqui viraria "terceiro título grande" na
 * mesma tela — o olho não saberia qual é o principal.
 *
 * A função do `.conversa-subtitulo` é só FRONTEIRA entre blocos,
 * não hierarquia de conteúdo. Caps + cor `--aco-400` herdam do
 * `.campo-rotulo` (mesma gramática dos labels de formulário).
 *
 * Espaçamento: `margin-top: 0` (herdado) porque o `.conversa-bloco`
 * já dá o gap entre o card acima e este subtítulo. Margem inferior
 * 8px para o `<form>` "respirar" antes de começar.
 */
.conversa-subtitulo {
  margin: 0 0 var(--e-2);
  font-size: var(--t-sm);
  line-height: var(--lh-corpo);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  font-weight: 600;
  color: var(--aco-400);
}

/**
 * Mesma regra da `.pilha`, pelo mesmo motivo: item de grid nasce com
 * `min-width: auto` e a coluna é dimensionada pelo item mais largo. Basta um
 * card com uma palavra que não quebra — nome de produto comprido, um preço em
 * `nowrap` com a fonte do sistema em 200% — para TODOS os cards ficarem mais
 * largos que a tela ao mesmo tempo. Foi assim que a página de produto passou a
 * transbordar 3px a 200% sem que nenhum elemento isolado aparecesse como
 * culpado.
 */
.card-list > li,
.card-list > li > .ficha {
  min-width: 0;
}

/**
 * O catálogo em GRADE quando cabe.
 *
 * Só o catálogo: uma lista de produtos é para COMPARAR, e comparar melhora com
 * mais itens no campo de visão. Conversa de suporte, lista de pedidos e planos
 * de um produto continuam em coluna única — ali a ordem importa e duas colunas
 * fariam o olho pular.
 *
 * `auto-fill` com piso de 260px decide sozinho: uma coluna no celular, duas a
 * partir de ~580px. Sem media query, porque a pergunta não é "que aparelho é" e
 * sim "cabe outro card?".
 */
.card-list[data-grade="produtos"] {
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}

/**
 * Os PLANOS de um produto lado a lado quando cabe. Escolher plano é comparar
 * preço contra duração, e comparar em coluna obriga a rolar entre os dois
 * candidatos. Piso maior que o do catálogo (300px) porque o card tem leitura de
 * 32px dentro.
 */
.card-list[data-grade="planos"] {
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}

.card-list[data-grade="planos"] > li {
  display: flex;
}

.card-list[data-grade="planos"] > li > .ficha {
  flex: 1 1;
  display: flex;
  flex-direction: column;
}

/* Caso 1-plano: o <li> e a .ficha ocupam a largura inteira do
   .produto-checkout (max-width: 560px), não a célula de 280px do grid
   auto-fill. Sem isso o cartão fica estreito e descentrado. */
.produto-checkout .card-list[data-grade="planos"] > li {
  width: 100%;
  max-width: none;
}
.produto-checkout .card-list[data-grade="planos"] > li > .ficha {
  max-width: none;
}

/* a ação alinha na base entre colunas, mesmo com nomes de planos de alturas
   diferentes */
.card-list[data-grade="planos"] > li > .ficha > .campo-acoes {
  margin-top: auto;
}

/**
 * Respiro entre o preço e a nota "equivale a R$X por mês" no card de plano.
 *
 * O card usa `flex-direction: column` com `margin-top: auto` no último
 * filho (`.campo-acoes`) para empurrar o botão Comprar para o rodapé e
 * alinhar a base entre colunas. Isso joga TODO o espaço sobrante antes
 * do botão — e quando o card é curto (sem texto adicional nem estado),
 * o preço fica colado na nota, que por sua vez fica colada no botão.
 *
 * A nota é uma legenda do preço: ela precisa estar perto do R$ mas não
 * encostada. `e-4` (16px) garante esse respiro sem precisar mexer no
 * `margin-top: auto` do botão, que continua fazendo seu trabalho de
 * alinhamento da base entre colunas.
 */
.card-list[data-grade="planos"] > li > .ficha > .preco-bloco + .nota {
  margin-top: var(--e-4);
}

/**
 * E respiro entre a nota "equivale a" e o botão Comprar.
 *
 * Sem esta margem, o `margin-top: auto` do `.campo-acoes` empurra TUDO
 * para baixo mas deixa a nota colada nele (porque ela é filha direta
 * anterior). `e-4` separa o "isso é uma legenda" do "isso é a ação",
 * sem quebrar o alinhamento entre colunas.
 */
.card-list[data-grade="planos"] > li > .ficha > .nota + .campo-acoes {
  margin-top: var(--e-4);
}

/**
 * Cards da mesma linha com a MESMA ALTURA. O `<li>` estica com o grid, mas a
 * `.ficha` dentro dele não — e a linha ficava com a base irregular, um card
 * curto ao lado de um alto, que é o tipo de desalinho que só aparece quando a
 * lista deixa de ser uma coluna.
 */
.card-list[data-grade="produtos"] > li {
  display: flex;
}

.card-list[data-grade="produtos"] > li > .ficha {
  flex: 1 1;
  /* a ficha técnica vai para o rodapé do card: com títulos de alturas
     diferentes, é ela que precisa alinhar entre colunas — é o que se compara */
  display: flex;
  flex-direction: column;
}

.card-list[data-grade="produtos"] > li > .ficha > .specs {
  margin-top: auto;
}

/**
 * O copia-e-cola é CÓDIGO, e código pede monoespaçado: o cliente confere o
 * final da string contra o app do banco, e proporcional embaralha `l`, `1` e
 * `I` exatamente onde a conferência acontece. Não usa `--f-num` porque aquele
 * subset só tem dígitos — aqui é o mono do sistema, que custa zero byte.
 */
.pix-codigo {
  display: block;
  padding: var(--e-4);
  background: var(--aco-800);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-2);
  color: var(--aco-050);
  box-shadow: var(--elev-dentro);
  font-family: var(--f-mono);
  font-size: var(--t-sm);
  line-height: var(--lh-corpo);
  color: var(--aco-200);
  word-break: break-all;
  max-height: 7rem;
  overflow-y: auto;
}

.qr {
  display: block;
  width: min(58vw, 220px);
  height: auto;
  margin: 0 auto;
  padding: var(--e-3) var(--e-4);
  background: #fff; /* QR exige branco para leitura — exceção justificada */
  border-radius: var(--r-2);
  box-shadow: var(--elev-2);
}

/* =============================================================================
   CONTROLES DO CATALOGO - buscar
   ============================================================================= */
/**
 * Barra da busca, ocupando 100% da largura.
 *
 * `min-width: 0` libera o encolhimento do `<input>` (que e `width: 100%`);
 * sem isto o flex item para de encolher no min-content do placeholder.
 * Agora que so existe a busca, `flex: 1 1 100%` garante que ela
 * preenche tudo que o container oferecer.
 */
.controles {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
}

.busca {
  flex: 1 1 100%;
  min-width: 0;
}

.busca input {
  display: block;
  width: 100%;
  min-height: var(--toque);
  margin-top: var(--e-2);
  padding: 0 var(--e-3);
  font-family: var(--f-texto);
  font-size: var(--t-base); /* PISO: menos de 16px e o iOS da zoom */
  color: var(--aco-050);
  /* campo REBAIXADO, como todo campo do sistema: superfice que afunda pega
     menos luz. E a mesma fisica do `.campo` e do input do formulario. */
  background: var(--aco-800);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-2);
  color: var(--aco-050);
  box-shadow: var(--elev-dentro);
  transition: border-color var(--dur-micro) var(--curva);
}

.busca input:focus {
  border-color: var(--aco-500);
}

.busca input::placeholder {
  color: var(--aco-400);
}

/* ============================================
   RENEWAL BANNER - Banner proativo de renovacao
   Usa tokens do sistema: --erro (critico), --espera (alerta), --ok (info).
   Animacao de entrada segue --dur-padrao (200ms).
   ============================================ */
.renewal-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e-3);
  padding: var(--e-3) var(--e-4);
  margin-bottom: var(--e-4);
  border-radius: var(--r-3);
  border: 1px solid;
  animation: slideIn var(--dur-padrao) var(--curva) both;
}

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.renewal-banner[data-urgency="critico"] {
  background: color-mix(in srgb, var(--erro) 20%, var(--aco-900));
  border-color: var(--erro);
  color: var(--aco-050);
}

.renewal-banner[data-urgency="alerta"] {
  background: color-mix(in srgb, var(--espera) 18%, var(--aco-900));
  border-color: var(--espera);
  color: var(--aco-050);
}

.renewal-banner[data-urgency="info"] {
  background: color-mix(in srgb, var(--ok) 15%, var(--aco-900));
  border-color: var(--ok);
  color: var(--aco-050);
}

.renewal-banner-conteudo {
  display: flex;
  align-items: center;
  gap: var(--e-3);
  flex: 1 1;
  min-width: 0;
}

.renewal-banner-icone {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  color: currentColor;
  opacity: 0.9;
}

.renewal-banner-icone svg {
  width: 100%;
  height: 100%;
}

.renewal-banner-texto {
  min-width: 0;
}

.renewal-banner-texto strong {
  display: block;
  font-size: var(--t-base);
  font-weight: 600;
}

.renewal-banner-texto p {
  font-size: var(--t-sm);
  margin-top: var(--e-1);
  opacity: 0.8;
}

.renewal-banner-cta {
  flex-shrink: 0;
  padding: var(--e-2) var(--e-3);
  border-radius: var(--r-1);
  font-weight: 600;
  font-size: var(--t-sm);
  text-decoration: none;
  transition:
    background-color var(--dur-micro) var(--curva),
    transform var(--dur-micro) var(--curva),
    box-shadow var(--dur-micro) var(--curva);
}

.renewal-banner-cta:hover {
  transform: scale(1.03);
}

.renewal-banner-cta:active {
  transform: scale(0.98);
}

/* CTA adapta cor ao contexto de urgencia */
.renewal-banner[data-urgency="critico"] .renewal-banner-cta {
  background: var(--erro);
  color: var(--aco-050);
}

.renewal-banner[data-urgency="critico"] .renewal-banner-cta:hover {
  background: color-mix(in srgb, var(--erro) 85%, white);
  box-shadow: 0 0 12px color-mix(in srgb, var(--erro) 50%, transparent);
}

.renewal-banner[data-urgency="alerta"] .renewal-banner-cta {
  background: var(--espera);
  color: var(--aco-900);
}

.renewal-banner[data-urgency="alerta"] .renewal-banner-cta:hover {
  background: color-mix(in srgb, var(--espera) 85%, white);
  box-shadow: 0 0 12px color-mix(in srgb, var(--espera) 50%, transparent);
}

.renewal-banner[data-urgency="info"] .renewal-banner-cta {
  background: var(--ok);
  color: var(--aco-900);
}

.renewal-banner[data-urgency="info"] .renewal-banner-cta:hover {
  background: color-mix(in srgb, var(--ok) 85%, white);
  box-shadow: 0 0 12px color-mix(in srgb, var(--ok) 50%, transparent);
}

/* ============================================
   RENEWAL CARD - Card de renovacao no dashboard
   Usa tokens do sistema e segue a gramatica visual.
   Animacao de entrada segue --dur-padrao (200ms).
   ============================================ */
.renewal-card {
  padding: var(--e-4);
  margin-bottom: var(--e-4);
  border-radius: var(--r-3);
  border: 1px solid var(--linha-forte);
  background: var(--aco-700);
  animation: slideIn var(--dur-padrao) var(--curva) both;
  box-shadow: var(--glow-card);
}

.renewal-card[data-urgency="critico"] {
  border-color: var(--erro);
  background: color-mix(in srgb, var(--erro) 18%, var(--aco-700));
  box-shadow: 0 0 20px color-mix(in srgb, var(--erro) 20%, transparent);
}

.renewal-card[data-urgency="alerta"] {
  border-color: var(--espera);
  background: color-mix(in srgb, var(--espera) 15%, var(--aco-700));
  box-shadow: 0 0 16px color-mix(in srgb, var(--espera) 15%, transparent);
}

.renewal-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--e-3);
  gap: var(--e-3);
}

.renewal-card-header h3 {
  font-size: var(--t-lg);
  font-weight: 600;
  margin: 0;
}

.renewal-card-badge {
  flex-shrink: 0;
  padding: var(--e-1) var(--e-2);
  border-radius: var(--r-1);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  background: var(--aco-600);
  color: var(--aco-200);
}

.renewal-card-badge[data-urgency="critico"] {
  background: var(--erro);
  color: var(--aco-050);
}

.renewal-card-badge[data-urgency="alerta"] {
  background: var(--espera);
  color: var(--aco-900);
}

.renewal-card-badge[data-urgency="normal"] {
  background: var(--aco-600);
  color: var(--aco-200);
}

.renewal-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--e-3);
}

.renewal-card-info {
  display: flex;
  flex-direction: column;
  gap: var(--e-1);
}

.renewal-card-info .nota {
  font-size: var(--t-sm);
  color: var(--aco-400);
  margin: 0;
}

/* Detalhes com icone inline */
.renewal-card-detalhe {
  display: flex;
  align-items: center;
  gap: var(--e-2);
  font-size: var(--t-sm);
  color: var(--aco-400);
}

.renewal-card-icon {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  color: var(--aco-500);
}

.renewal-card-icon svg {
  width: 100%;
  height: 100%;
}

/* Valor em destaque */
.renewal-card-valor {
  display: flex;
  flex-direction: column;
  gap: var(--e-1);
  padding: var(--e-3);
  background: color-mix(in srgb, var(--aco-900) 50%, transparent);
  border-radius: var(--r-2);
}

.renewal-card-valor .rotulo {
  font-size: var(--t-xs);
  color: var(--aco-500);
  text-transform: uppercase;
  letter-spacing: var(--tr-caps);
}

/* Acao com icone */
.renewal-card-actions .acao {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--e-2);
}

.renewal-card-actions .acao-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.renewal-card-actions .acao-icon svg {
  width: 100%;
  height: 100%;
}

.renewal-card-valor {
  display: flex;
  align-items: baseline;
  gap: var(--e-2);
}

.renewal-card-valor .rotulo {
  font-size: var(--t-sm);
  color: var(--aco-400);
}

.renewal-card-actions {
  margin-top: var(--e-4);
  padding-top: var(--e-3);
  border-top: 1px solid var(--linha);
}

.renewal-card-actions .acao {
  width: 100%;
  padding: var(--e-3);
}

/* Estado de sucesso do botao de renovacao */
.renewal-card .acao.botao-sucesso {
  background: var(--ok);
  color: var(--aco-900);
  pointer-events: none;
}

.check-icon, .renewal-card .acao.botao-sucesso .check-icon {
  width: 18px;
  height: 18px;
  margin-right: var(--e-2);
  animation: checkPop 300ms var(--curva) both;
}

@keyframes checkPop {
  0% {
    transform: scale(0);
    opacity: 0;
  }
  50% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

/* Spinner inline para estado loading */
.renewal-card .acao[data-loading="1"]::before {
  content: "";
  width: 16px;
  height: 16px;
  margin-right: var(--e-2);
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--r-full);
  animation: girar 700ms linear infinite;
}

/* Mobile adjustments - breakpoint de toque (48px polegar) */
@media (max-width: 480px) {
  .renewal-banner {
    flex-direction: column;
    align-items: stretch;
    gap: var(--e-3);
  }

  .renewal-banner-cta {
    text-align: center;
    min-height: var(--toque);
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .renewal-card {
    padding: var(--e-3);
  }

  .renewal-card-header {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--e-2);
  }

  .renewal-card-badge {
    align-self: flex-start;
  }
}

/* =============================================================================
   RENEWAL CARD — alinhamento responsivo
   ============================================================================= */

.renewal-card {
  display: flex;
  flex-direction: column;
  gap: var(--e-3);
}

.renewal-card-header {
  display: flex;
  flex-direction: column;
  gap: var(--e-2);
}

@media (min-width: 480px) {
  .renewal-card-header {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

.renewal-card-header h3 {
  margin: 0;
  font-size: var(--t-lg);
}

.renewal-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--e-3);
}

.renewal-card-info {
  display: flex;
  flex-direction: column;
  gap: var(--e-2);
}

@media (min-width: 480px) {
  .renewal-card-info {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--e-3) var(--e-4);
  }
}

.renewal-card-valor {
  display: flex;
  flex-direction: column;
  gap: var(--e-1);
}

@media (min-width: 480px) {
  .renewal-card-valor {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

.renewal-card-actions {
  margin-top: var(--e-2);
}

.renewal-card-actions .acao {
  width: 100%;
}

@media (min-width: 480px) {
  .renewal-card-actions .acao {
    width: auto;
    min-width: 200px;
  }
}

/* =============================================================================
   RENEWAL BANNER — alinhamento responsivo
   ============================================================================= */

.renewal-banner {
  display: flex;
  flex-direction: column;
  gap: var(--e-3);
  padding: var(--e-3);
}

@media (min-width: 480px) {
  .renewal-banner {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--e-4);
    padding: var(--e-3) var(--e-4);
  }
}

.renewal-banner-conteudo {
  display: flex;
  align-items: flex-start;
  gap: var(--e-3);
}

@media (min-width: 480px) {
  .renewal-banner-conteudo {
    align-items: center;
  }
}

.renewal-banner-texto {
  display: flex;
  flex-direction: column;
  gap: var(--e-1);
}

.renewal-banner-texto strong {
  font-size: var(--t-base);
}

.renewal-banner-texto p {
  margin: 0;
  font-size: var(--t-sm);
}

.renewal-banner-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--e-2) var(--e-4);
  min-height: var(--toque);
  white-space: nowrap;
}

@media (min-width: 480px) {
  .renewal-banner-cta {
    width: auto;
  }
}

/* =============================================================================
   MOSTRADOR — alinhamento responsivo
   ============================================================================= */

.mostrador-legenda {
  flex-wrap: wrap;
}

@media (min-width: 480px) {
  .mostrador-legenda {
    flex-wrap: nowrap;
  }
}

.mostrador-leitura {
  gap: var(--e-1);
}

.rotulo {
  white-space: nowrap;
}

/* =============================================================================
   FORM — alinhamento responsivo
   ============================================================================= */

.form {
  display: flex;
  flex-direction: column;
  gap: var(--e-4);
}

.form > .campo {
  width: 100%;
}

/* =============================================================================
   NOTAS E LABELS — alinhamento
   ============================================================================= */

.nota {
  margin: 0;
}

.rotulo {
  font-size: var(--t-sm);
}

/* =============================================================================
   FICHAS — alinhamento interno consistente
   ============================================================================= */

.ficha {
  padding: var(--e-4);
}

@media (min-width: 480px) {
  .ficha {
    padding: var(--e-4) var(--e-5);
  }
}

/* Garantir que elementos internos não estourem */
.ficha > * {
  min-width: 0;
}

/* =============================================================================
   PAINEL — alinhamento interno
   ============================================================================= */

.painel {
  padding: var(--e-4);
}

@media (min-width: 480px) {
  .painel {
    padding: var(--e-5);
  }
}

/* =============================================================================
   ANEXOS DE SUPORTE (2.3 Sprint 3)
   =============================================================================

   Estilo dos thumbnails dentro de uma mensagem. Imagens aparecem
   inline (clica → nova aba, abre o original); outros MIMEs caem
   no link de download. Mantemos `max-width` baixo para não
   estourar a largura do cartão em mobile.
   ============================================================================= */

.anexos-mensagem {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  grid-gap: var(--e-2);
  gap: var(--e-2);
  margin-top: var(--e-2);
}

.anexo {
  display: flex;
  flex-direction: column;
  gap: var(--e-1);
  background: rgba(255, 255, 255, 0.04);
  border-radius: var(--e-2);
  padding: var(--e-2);
  border: 1px solid var(--linha);
}

.anexo a {
  display: block;
  border-radius: var(--e-2);
  overflow: hidden;
}

.anexo img {
  display: block;
  max-width: 100%;
  height: auto;
  max-height: 200px;
  object-fit: cover;
  border-radius: var(--e-2);
}

.anexo-link {
  padding: var(--e-2);
  background: rgba(124, 58, 237, 0.2);
  border-radius: var(--e-2);
  color: inherit;
  text-decoration: none;
  font-size: 0.9em;
}

.anexo-meta {
  font-size: 0.75em;
  color: var(--texto-suave);
  word-break: break-all;
}

/**
 * FASE 4.2 (2026-09) — placeholder de upload otimista.
 *
 * Quando o cliente clica "Enviar resposta" com anexos, eles já
 * aparecem na conversa como placeholders ENQUANTO o POST está
 * em voo (antes do servidor devolver o id real do attachment).
 *
 * O quadrado é desenhado com a mesma silhueta do `<img>` real
 * (mesma `max-height: 200px`, mesma `border-radius`) para que a
 * "troca" otimista → real aconteça sem pulo de layout. Mas a
 * cor é hairline (não fundo) — diferencia "ainda subindo" de
 * "pronto pra ver".
 *
 * Animação: `pulsar` (já existe em globals.css, usada pelo
 * `.esqueleto`). 1.6s é o sweet spot — rápido demais parece
 * travado, lento demais distrai. O pulse é fraco (60% → 100%
 * opacity) para não competir com a leitura do texto da fala.
 */
.anexo-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--e-1);
  min-height: 120px;
  max-height: 200px;
  padding: var(--e-3);
  border: 1px dashed var(--linha-forte);
  border-radius: var(--e-2);
  background: var(--aco-800);
  animation: pulsar 1.6s ease-in-out infinite;
}

.anexo-placeholder-numero {
  font-size: var(--t-base);
  font-weight: 600;
  color: var(--aco-300);
  text-transform: lowercase;
  letter-spacing: var(--tr-corpo);
}

.anexo-placeholder-arquivo {
  font-size: var(--t-xs);
  color: var(--aco-500);
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* O `.anexo` todo fica opaco também quando é placeholder —
   o cliente lê "isso ainda não está pronto" pela combinação
   do quadrado pulsante + transparência geral. */
.anexo[data-placeholder="1"] {
  opacity: 0.7;
}

/**
 * FASE 5 (2026-09) — preview local do anexo durante upload.
 *
 * O `<img>` aqui é o bitmap REAL que o cliente acabou de
 * selecionar, servido via `URL.createObjectURL(file)` (URL `blob:`).
 * O quadrado da Fase 4 (`[imagem N]` genérico) só aparece
 * quando não há blobUrl — fallback raro (anexo não-imagem,
 * navegador sem suporte a blob).
 *
 * Visual: o `<img>` ocupa EXATAMENTE a mesma silhueta do
 * `<img>` real quando o upload retornar — `max-width: 100%`,
 * `max-height: 200px`, `object-fit: cover`. Sem pulo de layout
 * na transição otimista → oficial.
 *
 * O pai `.anexo[data-placeholder="1"]` mantém `opacity: 0.7`
 * acima — comunica "ainda subindo" sem esconder a imagem.
 * A meta (.anexo-meta) abaixo ganha " · subindo…" para
 * reforço.
 *
 * Não há overlay porque o cliente precisa ver o conteúdo
 * (escolheu aquele print de propósito). A cor de border sutil
 * distingue "temporário" de "definitivo".
 */
.anexo-preview {
  display: block;
  width: 100%;
  max-height: 200px;
  object-fit: cover;
  border-radius: var(--e-2);
  border: 1px solid var(--linha-forte);
}

.rotulo-anexos {
  display: block;
  margin-top: var(--e-2);
  font-weight: 600;
  font-size: 0.9em;
}

.lista-anexos {
  margin: 0;
  padding: 0; /* era var(--e-4) — lista com botão à direita não
                 deve ter bullet/indentação de <ul>; o filho direto
                 é uma linha inteira */
  list-style: none;
  font-size: var(--t-sm); /* era 0.85em hardcoded — alinhar ao
                              sistema de tipos (-t-sm) usado nos
                              labels e notas do app */
  color: var(--texto-suave);
}

/* Fase 4 (2026-09) — item vira linha de file-list:
   nome à esquerda, botão Remover à direita. `gap: var(--e-2)`
   é o respiro mínimo — em mobile (340px) o nome já está curto
   (geralmente "shot.png") e o botão fica naturalmente à direita. */
.lista-anexos li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--e-2);
  padding: var(--e-1) 0;
}

/* Separador weak entre anexos: o nome cresce, o botão também,
   e eles ficam "grudados" sem um hairline. Hairline 1px em
   top é melhor que margin porque não soma espaço vertical
   extra entre os itens. */
.lista-anexos li + li {
  border-top: 1px solid var(--linha);
}

/* O tamanho do arquivo em KG/KB é informação secundária.
   Não escondo (cliente quer saber quanto pesa antes de
   enviar), mas dou cor mais fraca. */
.lista-anexos .anexo-tamanho {
  color: var(--aco-500);
}


/* =============================================================================
   MODAL CAMPO (2026-10-08) — isolar campo do checkout quando teclado sobe
   =============================================================================

   Substitui o hook `useMantemCampoVisivel` (apps/web/app/ui/teclado.ts). A
   ideia: quando o cliente toca em um campo do checkout, ABRE um modal
   com aquele campo, em overlay, fixo no centro da viewport visível. O
   teclado fica embaixo, o modal fica centralizado acima. Sem scroll,
   sem getBoundingClientRect, sem recalcular nada durante a animação do
   teclado.

   Por que position: fixed e não <dialog>: <dialog> nativo tem bugs de
   focus trap em iOS Safari < 16 e não respeita o safe-area-inset-bottom
   do iOS em landscape. position: fixed com padding-bottom manual é mais
   previsível.

   O overlay é `position: fixed; inset: 0` (cobre tudo) e o modal é
   `position: fixed; bottom: ...` no rodapé. Em landscape, o modal
   centraliza verticalmente porque o teclado ocupa metade da tela.
   ============================================================================= */


.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  z-index: 2147483647;
  animation: aparecer var(--dur-tela) var(--curva);
  padding: var(--e-3);
  padding-left: max(var(--e-3), env(safe-area-inset-left));
  padding-right: max(var(--e-3), env(safe-area-inset-right));
  padding-top: max(var(--e-3), env(safe-area-inset-top));
  padding-bottom: max(var(--e-3), env(safe-area-inset-bottom));
  overscroll-behavior: contain;
  touch-action: none;
  transform: none;
}

@keyframes aparecer {
  from { opacity: 0; }
  to { opacity: 1; }
}

.modal-campo {
  /* Visual elegante com profundidade - igual ao cartao .ficha */
  background: var(--aco-700);
  border: 1px solid rgb(var(--glow-sinal) / 35%);
  border-radius: var(--r-3);
  padding: var(--e-5);
  box-shadow: var(--elev-1), 0 0 24px rgb(var(--glow-sinal) / 15%);
  text-align: center;
  /* Posicionamento no topo para nao ser coberto pelo teclado */
  margin-top: max(60px, env(safe-area-inset-top) + 16px);
  width: 100%;
  max-width: 480px;
  /*
   * max-height respeita a viewport visivel menos o teclado, com
   * folga para a transicao do teclado terminar.
   */
  max-height: max(
    calc(100dvh - var(--altura-teclado, 0px) - var(--folga-teclado, 60px) - 2 * var(--e-3) - env(safe-area-inset-top) - env(safe-area-inset-bottom)),
    calc(100vh - var(--altura-teclado, 0px) - var(--folga-teclado, 60px) - 2 * var(--e-3))
  );
  display: flex;
  flex-direction: column;
  /* Rola se conteudo nao couber */
  overflow-y: auto;
  touch-action: auto;
  overscroll-behavior: contain;
  animation: subir var(--dur-tela) var(--curva);
}

@keyframes subir {
  from { transform: translateY(20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

/*
 * Flex-shrink: 0 apenas no título (que precisa de espaço fixo para
 * não colapsar). Campo e ações encolhem se necessário.
 */
.modal-campo > .modal-campo-titulo {
  flex-shrink: 0;
}

.modal-campo-titulo {
  font-size: var(--t-xl);
  font-weight: 700;
  line-height: 1.2;
  margin: 0;
  color: var(--texto);
}

/* Subtítulo do modal */
.modal-campo-subtitulo {
  font-size: var(--t-sm);
  color: var(--aco-400);
  margin: var(--e-1) 0 0;
  line-height: 1.4;
}

/* Header do modal */
.modal-campo-header {
  margin-bottom: var(--e-4);
}

/* Wrapper do input com ícone */
.modal-campo-input-wrapper {
  position: relative;
  display: flex;
  align-items: center;
  margin-bottom: var(--e-2);
}

.modal-campo-input-wrapper .modal-campo-input {
  margin: 0;
  flex: 1 1;
}

/* Ícones de validação */
.modal-campo-icone-ok,
.modal-campo-icone-erro {
  position: absolute;
  right: var(--e-3);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.modal-campo-icone-ok {
  color: var(--sinal);
}

.modal-campo-icone-erro {
  color: var(--erro);
}

/* Wrapper com estados */
.modal-campo-input-wrapper.input-com-valor .modal-campo-input,
.modal-campo-input-wrapper.input-com-erro .modal-campo-input {
  padding-right: calc(var(--e-3) + 28px);
}

/* Mensagem de erro moderna */
.modal-campo-mensagem-erro {
  font-size: var(--t-sm);
  color: var(--erro);
  margin: 0 0 var(--e-3);
  display: flex;
  align-items: flex-start;
  gap: var(--e-1);
}

/* Footer com botão */
.modal-campo-rodape {
  margin-top: auto;
  padding-top: var(--e-3);
  border-top: 1px solid var(--aco-700);
}

.modal-campo-dica {
  color: var(--texto-2, var(--aco-500));
  font-size: var(--t-sm, 13px);
  line-height: 1.4;
  margin: 0 0 var(--e-3);
  /* Dica limitada a 3 linhas. Em mobile pequeno, mesmo com fonte
   * grande do sistema, o usuario ve pelo menos 2 linhas. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.modal-campo .campo {
  margin: 0 0 var(--e-3);
  /* Input e label nascem com min-width: auto, que impede o campo
   * de participar do reflow. Sem isso, um input longo pode
   * forçar o modal a crescer alem do max-height disponivel. */
  min-width: 0;
}

.modal-campo-input {
  width: 100%;
  font-size: var(--t-lg);
  font-weight: 500;
  letter-spacing: 0.04em;
  padding: var(--e-3) var(--e-5);
  min-height: 60px;
  background: linear-gradient(
    to bottom,
    var(--aco-800),
    var(--aco-900)
  );
  border: 1.5px solid var(--aco-600);
  border-radius: var(--r-2, 12px);
  color: var(--texto);
  text-align: center;
  box-shadow:
    inset 0 2px 4px rgb(0 0 0 / 25%),
    inset 0 1px 0 rgb(255 255 255 / 3%);
  transition:
    border-color 0.2s var(--curva),
    box-shadow 0.25s var(--curva),
    background 0.25s var(--curva);
}

.modal-campo-input::placeholder {
  color: var(--aco-400);
  font-weight: 400;
  letter-spacing: 0.02em;
  opacity: 1;
}

.modal-campo-input:hover:not(:focus) {
  border-color: var(--aco-500);
  background: linear-gradient(
    to bottom,
    var(--aco-750, var(--aco-800)),
    var(--aco-850, var(--aco-900))
  );
}

/* Estado preenchido (tem valor, sem foco, sem erro) */
.modal-campo-input-wrapper.input-com-valor .modal-campo-input {
  border-color: var(--aco-500);
}

.modal-campo-input:focus {
  outline: none;
  border-color: var(--primaria-500);
  background: var(--aco-900);
  box-shadow:
    inset 0 2px 8px rgb(var(--glow-sinal) / 18%),
    0 0 0 4px rgb(var(--glow-sinal) / 15%);
}

/* Mobile: previne zoom no iOS (font-size < 16px causa zoom) */
@media (max-width: 480px) {
  .modal-campo-input {
    font-size: 16px;
    padding: var(--e-3) var(--e-4);
    min-height: 56px;
    letter-spacing: 0.02em;
  }
}

.modal-campo-nota {
  margin: 0 0 var(--e-2);
}

.modal-campo-acoes {
  display: flex;
  flex-direction: column;
  gap: var(--e-2);
  margin-top: auto; /* gruda no fundo quando ha espaco sobrando */
  padding-top: var(--e-3);
  /*
   * Borda superior sutil no lugar do box-shadow: o shadow criava
   * a ilusao de profundidade mas, dentro de um flex container,
   * o `position: sticky` que era necessario para manter o shadow
   * visivel conflituava com o `margin-top: auto` que posiciona
   * as acoes no fundo. Sem sticky, o shadow ficava "flutuando"
   * no topo das acoes sem contexto visual.
   *
   * Borda hairline resolve: e' fina, discreta, e nao depende de
   * posicao sticky.
   */
  border-top: 1px solid var(--linha);
}

.modal-campo-acoes .acao {
  width: 100%;
  min-height: var(--toque, 48px);
}

/* BOTAO UNICO DO MODAL (design redesenhado) */
.botao-unico {
  width: 100%;
  min-height: 56px;
  padding: var(--e-3) var(--e-4);
  font-size: var(--t-base);
  font-weight: 600;
  border-radius: var(--r-2, 12px);
  border: none;
  cursor: pointer;
  transition: all 0.2s var(--curva);
}

/* Vazio: Cancelar */
.botao-unico.botao-vazio {
  background: var(--aco-600);
  color: var(--aco-200);
}

.botao-unico.botao-vazio:hover {
  background: var(--aco-500);
}

/* Válido: Continuar - mesmo estilo do botao Comprar com PIX */
.botao-unico.botao-valido {
  background: var(--sinal);
  color: var(--sinal-texto, white);
  font-weight: 700;
  box-shadow: 0 4px 12px rgba(109, 77, 255, 0.25);
}

.botao-unico.botao-valido:hover {
  filter: brightness(1.1);
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(109, 77, 255, 0.3);
}

.botao-unico.botao-valido:active {
  transform: translateY(0);
  box-shadow: 0 2px 8px rgba(109, 77, 255, 0.2);
}

/* Inválido: Corrigir */
.botao-unico.botao-invalido {
  background: var(--erro);
  color: white;
  font-weight: 700;
}

.botao-unico.botao-invalido:hover {
  filter: brightness(1.1);
}

/* Input com erro */
.modal-campo-input-wrapper.input-com-erro .modal-campo-input {
  border-color: var(--erro);
  background: rgba(220, 38, 38, 0.05);
}

.modal-campo-input-wrapper.input-com-erro .modal-campo-input:focus {
  box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.15);
}

/* Botão voltar (legacy, pode não ser usado mais) */
.modal-campo-voltar {
  text-align: center;
  /* Botao secundario - sem borda cheia, mas mantem a borda hairline
   * para nao virar titulo de secao. */
  border: 1px solid var(--linha);
  border-radius: var(--raio-md, 8px);
  padding: var(--e-2) var(--e-3);
  min-height: var(--toque, 48px);
  background: transparent;
  color: var(--texto);
}

/* prefers-reduced-motion: desliga as animações de entrada */
@media (prefers-reduced-motion: reduce) {
  .modal-overlay,
  .modal-campo {
    animation: none;
  }
}

/* =============================================================================
   CAMPO-TRIGGER (2026-10-08) — botão que parece campo de input
   =============================================================================

   Substitui o <input readOnly> que abria o modal de campo no checkout.
   Por que não input readOnly: iOS Safari mantém foco em input readOnly,
   e o foco preso em coordenadas do campo interfere com scroll/snap e
   pode bloquear touch subsequente na mesma região.

   Um <button type="button"> com classe `.campo-trigger` tem a aparência
   do input (.campo > input) e o comportamento de abrir modal ao clicar,
   sem participar do tab order como input nem receber foco permanente.
   ============================================================================= */

.campo-trigger {
  display: block;
  width: 100%;
  text-align: left;
  min-height: var(--toque, 48px);
  padding: var(--e-2) var(--e-3);
  background: var(--aco-800);
  border: 1px solid var(--linha);
  border-radius: var(--raio-md, 8px);
  color: var(--texto);
  font: inherit;
  font-size: var(--t-base);
  line-height: 1.4;
  cursor: pointer;
  transition: border-color var(--dur-micro) var(--curva);
}

.campo-trigger:hover {
  border-color: var(--linha-2, var(--aco-500));
}

.campo-trigger:focus-visible {
  outline: 2px solid var(--sinal);
  outline-offset: 2px;
}

.campo-trigger-preenchido {
  color: var(--texto);
}

.campo-trigger-placeholder {
  color: var(--aco-500);
}

