/* ==========================================================================
   NEXUS theme-final / 01-tokens.css
   Fonte única de toda decisão visual do site.
   Nenhum outro CSS pode escrever hex, raio em px ou família de fonte.
   Formato de cor: HSL em três números soltos (H S% L%), padrão shadcn,
   para permitir hsl(var(--primary) / 0.4) e derivação de paleta pelo estúdio.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fontes locais (assets/fonts/), todas variáveis.
   Zero rede: nenhuma Google Font, nenhum CDN. Um @font-face por família,
   com a faixa real do eixo wght declarada no §2.4 do contrato.

   font-display: optional (não swap). Fonte é local e cada HTML faz preload
   dos dois arquivos críticos (display + body da pele daquela página) — o
   arquivo já está em cache do disco/preload quando o primeiro parágrafo
   pinta, então o período de bloqueio de ~100ms do "optional" quase sempre
   é suficiente e o texto nasce direto na fonte final, sem período de troca
   depois. "swap" pinta com o fallback IMEDIATAMENTE (bloqueio 0ms) e troca
   sempre que a fonte terminar de carregar — mesmo poucos ms bastam para
   gerar um reflow visível no título/CTA do hero (medido: ~57px de pulo
   vertical no botão primário, ver docs/shots/provas/provas-2026-08-05.md
   §H3/adendo). "optional" elimina esse reflow tardio; como a fonte é local
   e pré-carregada, o risco de "preso no fallback" é desprezível. */

@font-face {
  font-family: "Bricolage Grotesque";
  src: url("../fonts/bricolage-grotesque.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: optional;
}

@font-face {
  font-family: "Manrope";
  src: url("../fonts/manrope.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: optional;
}

@font-face {
  font-family: "Geist";
  src: url("../fonts/geist.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: optional;
}

@font-face {
  font-family: "Geist Mono";
  src: url("../fonts/geist-mono.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: optional;
}

@font-face {
  font-family: "Funnel Display";
  src: url("../fonts/funnel-display.woff2") format("woff2");
  font-weight: 300 800;
  font-style: normal;
  font-display: optional;
}

/* --------------------------------------------------------------------------
   1b. HAN (chinês simplificado): faixa emprestada da fonte do sistema
   --------------------------------------------------------------------------
   Medido em 06/08/2026, com fontTools, nos cinco arquivos acima:

     bricolage-grotesque  226 codepoints   funnel-display  217
     manrope              218              geist           225
     geist-mono           225

   As cinco famílias são LATINAS. Nenhuma tem um único ideograma, e nenhuma
   tem cirílico. Com o site em seis idiomas isso vira duas perguntas
   diferentes, e elas têm respostas diferentes.

   CIRÍLICO (/ru) NÃO PRECISA DE NADA, e é decisão medida, não omissão. A
   pilha de cada token já termina em `ui-sans-serif, system-ui, -apple-system,
   "Segoe UI", sans-serif`, e a fonte de interface de todo sistema operacional
   moderno traz cirílico completo e bem desenhado (SF Pro no macOS, Segoe UI
   no Windows, Roboto no Android). O russo cai UM degrau na pilha e para numa
   fonte boa. Nomear famílias cirílicas específicas aqui só serviria para
   correr o risco de acertar uma pior que a do sistema.

   HAN (/zh) PRECISA, e por um motivo que não é estético: sem uma família com
   ideograma na pilha, o navegador esgota a lista inteira e cai no seu
   CHUTE FINAL, que é comportamento interno de cada navegador e não do
   documento. Nesse chute, os caracteres han compartilhados entre chinês,
   japonês e coreano podem sair com a forma de outro idioma (o problema
   clássico de unificação han) e o resultado muda de máquina para máquina.

   A SOLUÇÃO ABAIXO NÃO BAIXA UM BYTE. Cada bloco declara o MESMO nome de
   família de uma das cinco fontes do projeto, com `src: local(...)` e
   `unicode-range` restrito às faixas han. O navegador MESCLA as duas
   declarações de mesmo nome numa família composta: latim continua vindo do
   .woff2 do repositório, ideograma passa a vir de uma fonte que já está
   instalada na máquina. Nenhum token muda, nenhuma pilha muda, e as quatro
   peles (06, 08, 09 e 10) herdam o comportamento sem uma linha a mais,
   porque elas trocam o NOME da família e o nome é justamente o que casa.
   Baixar uma CJK de verdade custaria de 4 a 10 MB, contra os 131 KB que hoje
   somam as cinco fontes inteiras, e o §2.4 do contrato não paga isso.

   ORDEM DAS `local()`: PingFang SC (macOS e iOS desde 2015), Microsoft YaHei
   UI e YaHei (Windows), Noto Sans CJK SC e Source Han Sans SC (Android,
   ChromeOS, Linux), Heiti SC (macOS antigo). Todas são sem serifa de
   SIMPLIFICADO, que é o registro de /zh; nenhuma é de tradicional. Se
   NENHUMA existir na máquina, esta declaração simplesmente não resolve e o
   navegador segue para o degrau seguinte da pilha, exatamente como antes
   dela existir: o bloco só pode melhorar o resultado, nunca piorá-lo.

   FAIXAS: U+2E80..U+303F (radicais e pontuação CJK), U+3400..U+4DBF
   (extensão A), U+4E00..U+9FFF (bloco unificado), U+F900..U+FAFF (formas de
   compatibilidade) e U+FF00..U+FFEF (largura total, que é a pontuação usada
   no texto de /zh). Nenhuma delas encosta em latim ou em cirílico, então
   nada do que já renderiza muda de fonte.

   `font-display` fica FORA de propósito: `local()` não faz requisição, a
   face resolve no mesmo quadro, e herdar o `optional` das declarações de
   cima poderia descartar a face num carregamento apertado. */

@font-face {
  font-family: "Bricolage Grotesque";
  src: local("PingFang SC"), local("Microsoft YaHei UI"), local("Microsoft YaHei"),
       local("Noto Sans CJK SC"), local("Noto Sans SC"), local("Source Han Sans SC"),
       local("Source Han Sans CN"), local("Heiti SC");
  font-weight: 200 800;
  font-style: normal;
  unicode-range: U+2E80-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF;
}

@font-face {
  font-family: "Manrope";
  src: local("PingFang SC"), local("Microsoft YaHei UI"), local("Microsoft YaHei"),
       local("Noto Sans CJK SC"), local("Noto Sans SC"), local("Source Han Sans SC"),
       local("Source Han Sans CN"), local("Heiti SC");
  font-weight: 200 800;
  font-style: normal;
  unicode-range: U+2E80-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF;
}

@font-face {
  font-family: "Geist";
  src: local("PingFang SC"), local("Microsoft YaHei UI"), local("Microsoft YaHei"),
       local("Noto Sans CJK SC"), local("Noto Sans SC"), local("Source Han Sans SC"),
       local("Source Han Sans CN"), local("Heiti SC");
  font-weight: 100 900;
  font-style: normal;
  unicode-range: U+2E80-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF;
}

/* O monoespaçado recebe uma fonte CJK de LARGURA FIXA quando existir (as han
   são naturalmente monoespaçadas em corpo, então a grade não quebra), com a
   mesma lista de reserva. Sem isso, um ideograma dentro de um bloco de código
   ou de um crachá em Geist Mono cairia numa família proporcional e a coluna
   se desalinharia só naquela linha. */
@font-face {
  font-family: "Geist Mono";
  src: local("PingFang SC"), local("Microsoft YaHei UI"), local("Microsoft YaHei"),
       local("Noto Sans Mono CJK SC"), local("Noto Sans CJK SC"), local("Source Han Sans SC"),
       local("Heiti SC");
  font-weight: 100 900;
  font-style: normal;
  unicode-range: U+2E80-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF;
}

@font-face {
  font-family: "Funnel Display";
  src: local("PingFang SC"), local("Microsoft YaHei UI"), local("Microsoft YaHei"),
       local("Noto Sans CJK SC"), local("Noto Sans SC"), local("Source Han Sans SC"),
       local("Source Han Sans CN"), local("Heiti SC");
  font-weight: 300 800;
  font-style: normal;
  unicode-range: U+2E80-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF;
}

/* --------------------------------------------------------------------------
   2. Tema claro (:root) e tema escuro ([data-theme="dark"]).
   O padrão de fábrica do site é o escuro: o HTML sai com data-theme="dark"
   e theme.js resolve o modo "sistema" gravando o atributo antes do primeiro
   paint. Os dois temas são completos e independentes.
   -------------------------------------------------------------------------- */

/* IMPORTANTE, cascata e estilo inline.
   Estilo inline vence qualquer folha, inclusive [data-skin="glass"] e as
   media queries de token do §7.3. Por isso o estado de fábrica mora AQUI e
   não no inline: assets/js/core/theme.js compara cada token com o próprio
   mapa de fábrica e usa removeProperty quando o valor volta ao padrão, de
   modo que uma carga limpa deixa ZERO custom property inline no <html> e a
   cascata continua viva. Nada neste arquivo pode depender de o estúdio ter
   rodado.

   IMPORTANTE, linguagem enterprise preto e branco.
   A paleta de fábrica é neutra pura: matiz 0, saturação 0%. A assinatura é a
   inversão da primária, botão BRANCO com texto preto no escuro e botão PRETO
   com texto branco no claro. Por isso --primary e --ring são literais por
   tema e não mais var(--brand-*): uma única tripla de marca não consegue ser
   quase branca no escuro e quase preta no claro ao mesmo tempo. --brand-* fica
   como semente neutra dos padrões de fundo e como o que o estúdio move.

   Cor cromática existe em um único token, --signal, e ocupa menos de 3% da
   tela: anel de foco de dado, pulso indicador, linha de progresso da timeline,
   número de destaque, sublinhado de link ativo. Nunca fundo de seção, nunca
   botão grande, nunca card inteiro. Ação continua sendo --primary. */

:root {
  color-scheme: light;

  /* --- semente neutra da marca (o estúdio move estes três) --- */
  --brand-h: 0;
  --brand-s: 0%;
  --brand-l: 50%;

  /* --- contrato shadcn --- */
  --background: 0 0% 100%;
  --foreground: 0 0% 6%;

  --card: 0 0% 100%;
  --card-foreground: 0 0% 6%;

  --popover: 0 0% 100%;
  --popover-foreground: 0 0% 6%;

  /* preto real sobre branco real: a inversão é intencional (ver cabeçalho) */
  --primary: 0 0% 9%;
  --primary-foreground: 0 0% 98%;

  --secondary: 0 0% 96%;
  --secondary-foreground: 0 0% 9%;

  --muted: 0 0% 96%;
  --muted-foreground: 0 0% 42%;

  --accent: 0 0% 94%;
  --accent-foreground: 0 0% 9%;

  --destructive: 0 72% 45%;
  --destructive-foreground: 0 0% 98%;

  --border: 0 0% 90%;

  /* --chip-neutral-bg / --chip-neutral-border (06/08/2026, pedido do dono
     sobre a cápsula de verticais do portfólio: "essa cor tem que seguir um
     padrão indiferente de qual modelo for" — a cápsula estava com contorno
     rosado num preset de marca). CAUSA: --border e --surface-2 (que a
     cápsula usava até aqui) são hue-tinted DE PROPÓSITO em todo o site —
     `derive()` (core/theme.js) deriva os dois de `nh`/`ns` (matiz/saturação
     do FUNDO do preset, não neutro puro), pensado pra coesão visual entre
     preset e superfície. Isso é correto para elevação de cartão e painel,
     mas um preset de marca saturada (nubank: matiz 279°, roxo) tinge um
     contorno fino o bastante pra ler como "rosado" em vez de "cinza".
     Estes dois tokens são a mesma dupla de valores (visualmente idênticos
     ao --border/--surface-2 de FÁBRICA, matiz 0/saturação 0%), mas NUNCA
     escritos inline por preset nenhum — nenhum campo de
     assets/js/data/presets.js os referencia, e core/theme.js não os inclui
     no mapa que `apply()` grava em `root.style` — então eles vêm sempre
     desta folha estática, o mesmo mecanismo de imunidade que já protege
     --muted-foreground/--faint-foreground (TINTA_DE_PELE, core/theme.js).
     Uso: só a cápsula de verticais (decorativa, não card de produto — os
     cards continuam tingidos por preset de propósito). */
  --chip-neutral-bg: 0 0% 98%;
  --chip-neutral-border: 0 0% 90%;
  /* RESOLVIDO §8 (era PENDENTE até a rodada de agosto de 2026).
     --input é borda de componente interativo e o mínimo é 3:1. O valor antigo
     (0 0% 86%) vinha fechado da escada neutra shadcn, que carrega o mesmo
     problema na origem, e media 1,38:1 no claro e 1,57:1 no escuro contra o
     fundo. O dono decidiu corrigir: não há exceção assinada, o token passa.

     O contrato calculou 0 0% 58% e 0 0% 36.5% medindo contra --background. O
     valor final é um passo além, e a razão foi medida: o consumidor de
     --input que pousa na superfície MAIS CLARA não está na página, está no
     estúdio. O botão de ícone de .nx-studio__head fica sobre --surface-2, e
     contra --surface-2 (não contra --background) 58%/36.5% davam 2,88:1 e
     2,80:1, ou seja, o mínimo do contrato passava na página e reprovava no
     painel. O piso tem que ser a superfície mais clara onde o token aparece.

     Medido no documento, contraste efetivo (fundo realmente pintado atrás da
     borda, alfa por alfa ao longo da cadeia de ancestrais), Chrome 141:
       claro   0 0% 56%  sobre 0 0% 100% = 3,24:1 · sobre --surface-2 = 3,11:1
       escuro  0 0% 39.5% sobre 0 0% 4%  = 3,37:1 · sobre --surface-2 = 3,03:1
     Consumidores do token: .btn--outline e .chip[aria-pressed="false"] na
     página, e campo, seletor, faixa e botão de ícone no estúdio. Todos com
     preenchimento transparente ou quase: a borda é a ÚNICA coisa que desenha
     o limite do componente, então ela atende 3:1 contra a superfície onde o
     componente de fato pousa.

     --border NÃO acompanha: é divisor estático, não borda de componente
     interativo, e o §8 não pede 3:1 dele. Quem precisa de mais tinta que o
     divisor pede --input, que é exatamente para isso.

     Nota de cascata: a pele surface pousa componente dentro de célula de
     0 0% 9.5% no escuro, mais clara que este --background, e por isso
     recalcula o próprio --input em 08-skin-surface.css. O foco continua
     coberto por --ring, que passa com folga (7,86:1 claro, 9,93:1 escuro). */
  --input: 0 0% 56%;
  --ring: 0 0% 32%;

  /* --- elevação: card, painel, overlay --- */
  --surface-1: 0 0% 100%;
  --surface-2: 0 0% 98%;
  --surface-3: 0 0% 96%;

  /* --- único token cromático do site: aço frio muito dessaturado ---
     Uso pontual, abaixo de 3% da tela. Medido contra --background,
     --card, --surface-2 e --surface-3: 5.22, 5.22, 5.00 e 4.78, todos
     acima de 4.5 (§8), então também serve como texto de dado. */
  --signal: 205 32% 42%;
  --signal-foreground: 0 0% 100%;

  /* --- tinta de véu sobre mídia ---
     Scrim é sempre tinta escura, nos dois temas: usar --background aqui
     clareia a foto no tema claro e derruba o contraste do texto por cima
     (§8). Quem escreve sobre o véu usa --scrim-foreground, que é claro nos
     dois temas por construção. */
  --scrim: 0 0% 4%;
  --scrim-foreground: 0 0% 98%;
  --scrim-muted-foreground: 0 0% 82%;
  /* Terceiro degrau do par do véu, para o que é deliberadamente subordinado
     por cima da mídia (numeral do cartão de #modelos). Existe pelo mesmo
     motivo que --faint abaixo: o antipadrão era hsl(var(--scrim-foreground)
     / 0.45), que sobre foto não compõe com nada previsível e mediu 1,10:1.
     Medido contra o prato de véu do numeral (alfa 0.82 de --scrim) sobre o
     pior pixel possível da foto, branco puro: 6,38:1; sobre preto puro,
     10,6:1. Opaco por construção: NUNCA receber alfa. */
  --scrim-faint-foreground: 0 0% 74%;

  /* --- tinta fraca: par --faint / --faint-foreground -------------------
     ESTE TOKEN EXISTE EXATAMENTE PARA SUBSTITUIR O ANTIPADRÃO
     `hsl(var(--muted-foreground) / <alfa>)` e o seu primo
     `hsl(var(--primary) / <alfa>)` em propriedade `color`, e também o uso de
     `--border` como tinta de glifo. Não escreva alfa sobre tinta de texto:
     alfa não é uma escala de ênfase, é uma aposta sobre o que está pintado
     atrás, e a aposta falha sempre que a pele, o padrão de fundo ou a
     elevação mudam o que está lá. Foi o que reprovou a rodada de agosto de
     2026 em oito elementos de uma vez (índice de #capacidades a 2,43:1,
     separador de #portfolio a 1,00:1, nota de #provas a 3,19:1, rótulo NOTA
     a 3,52:1, numeral de #tese a 3,41:1).

     Ênfase mais fraca que --muted-foreground é uma DECISÃO DE LUMINOSIDADE
     tomada uma vez, aqui, com o piso do §8 embutido, não um alfa escolhido
     por seletor.

     --faint             superfície de referência do par. É o próprio fundo da
                         página por tema: fecha o par shadcn do §2.1 (todo
                         -foreground tem a sua superfície) e deixa explícito
                         contra o que a tinta foi calibrada.
     --faint-foreground  a tinta. Um degrau só, e o degrau é o PIOR CASO.

     UM DEGRAU SÓ, E O PORQUÊ. A primeira versão desta correção tinha dois,
     --faint-foreground para texto sobre --background e --faint-strong para
     texto sobre superfície elevada. A sonda derrubou a ideia: medido em pixel
     renderizado nas três peles, o fundo que estes oito consumidores realmente
     pisam vai de 0 0% 100% a rgb(228) no claro e de rgb(24) a rgb(60) no
     escuro, e a tinta que atende o pior desses fundos atende todos os outros
     com folga. O segundo degrau não teria consumidor nenhum, e token sem
     consumidor é exatamente o defeito que esta mesma rodada está apagando de
     03-ui.css (.badge--success). Se um dia um consumidor real precisar de um
     segundo degrau, ele nasce aqui, medido, com o consumidor junto.

     CALIBRAÇÃO, por luminância relativa real (WCAG), matiz 0, saturação 0%,
     medida em PIXEL RENDERIZADO (scripts/contraste.mjs), não token contra
     token:

       tema claro   0 0% 34%
         sobre --background 0 0% 100% ................... 7,29:1
         sobre --surface-3 / --muted / --secondary (96%) . 6,65:1
         sobre --accent (94%) ........................... 6,39:1
         sobre o painel de #contratacao, rgb(226) ....... 5,62:1
         sobre o campo mais escuro que a pele de vidro
           produz no claro, rgb(204) em 1280x800 ........ 4,54:1

       tema escuro  0 0% 66%
         sobre --background 0 0% 4% .................... 8,37:1
         sobre --surface-3 0 0% 12% ................... 7,01:1
         sobre --card 0 0% 6.5% ....................... 8,00:1

     Todos passam o piso de 4,5:1 como TEXTO DE CORPO: nenhum consumidor
     precisa alegar tamanho de fonte para passar, o que é o que fazia
     .tese-block__num passar em 414px e reprovar em 390px.

     O FATO DESCONFORTÁVEL, registrado porque é o que explica o valor. Em
     paleta neutra pura, tinta AA mais fraca que --muted-foreground não
     existe: --muted-foreground já mede 5,32:1 sobre o branco real, e qualquer
     véu, elevação ou ambiente de pele come essa folga inteira. Por isso
     --faint-foreground acaba um passo mais TINTA que --muted-foreground
     (34% contra 42% no claro, 66% contra 63% no escuro), e não um passo mais
     fraco. A subordinação destes elementos é real e continua existindo, mas
     vem do tamanho, do peso, da caixa alta e do espaço em volta, nunca da
     tinta. É a mesma conclusão que 08-skin-surface.css já tinha escrito para
     o próprio quintal: a tinta mais escura é o preço do ambiente.

     --faint-foreground NUNCA recebe alfa. Se parecer que precisa, o que falta
     é outro degrau declarado aqui, medido.

     REGRA DE PELE (§12). Este token é um -foreground e obedece à mesma regra
     que reprovou a rodada de agosto de 2026: pele que redefine a SUPERFÍCIE
     é obrigada a redefinir o -foreground na mesma regra. A pele de vidro já
     recalcula --muted-foreground para o próprio ambiente (42% vira 31% no
     claro, 63% vira 72% no escuro) e precisa fazer o mesmo com este token
     quando mover o ambiente de novo. Medição desta rodada, para não haver
     dúvida sobre de quem é o quintal: na varredura de 24 execuções desta
     rodada (três páginas, dois temas, quatro viewports, 12.474 nós de texto e
     528 pseudo-elementos com glifo), as ÚNICAS 78 ocorrências abaixo do piso
     estão todas dentro de [data-skin="glass"], e 65 delas não têm nenhuma
     relação com este token: são o próprio --muted-foreground da pele
     (a.header__nav-link 3,19:1, p.card__desc 3,64:1, dt.case-block__fact-label
     3,82:1, p.capacidades__text 4,06:1, p.portfolio__meta 3,94:1 e afins,
     60 ocorrências) e o próprio --foreground (5 ocorrências). O cartão de
     vidro no escuro sobe até rgb(87) e nenhuma tinta desta rampa atende 4,5:1
     ali: a tinta que fecharia as 13 ocorrências restantes seria 0 0% 72%, que
     é exatamente o valor que a pele já dá ao --muted-foreground dela, e que
     no tema escuro da pele PADRÃO deixaria a tinta subordinada mais clara que
     o texto de corpo. É defeito de ambiente de 06-skin-glass.css, não deste
     arquivo, e não se corrige daqui sem estragar as outras duas peles. Nas
     peles padrão e superfície a varredura fecha em ZERO nos dois temas e nos
     quatro viewports. */
  --faint: var(--background);
  --faint-foreground: 0 0% 34%;

  /* --- famílias (par 1 do estúdio: Bricolage Grotesque / Manrope) --- */
  --font-display: "Bricolage Grotesque", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body: "Manrope", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* --- multiplicadores controlados pelo estúdio --- */
  --scale-user: 1;          /* o estúdio escreve aqui, nunca em --scale */
  --scale: var(--scale-user);
  --density: 0.99;          /* 0.85 .. 1.2 */
  --shadow-strength: 1;     /* 0 .. 1 */
  --motion: 1;              /* 1 animado, 0 estático */
  --pattern: aurora;        /* none | grid | dots | noise | aurora | mesh */

  /* --- vidro (consumido apenas por 06-skin-glass.css) --- */
  --glass-blur: 18px;
  --glass-sat: 160%;
  --glass-alpha: 0.62;

  /* --- raio: --radius é a raiz, o resto deriva --- */
  --radius: 0.75rem;                          /* faixa do estúdio: 0rem .. 1.5rem */
  --radius-sm: calc(var(--radius) * 0.5);
  --radius-md: var(--radius);
  --radius-lg: calc(var(--radius) * 1.5);
  --radius-xl: calc(var(--radius) * 2);
  --radius-full: 9999px;                      /* só pill de status e avatar */

  /* --- escala tipográfica (fluida, multiplicada por --scale) --- */
  --fs-mono: calc(0.6875rem * var(--scale));
  --fs-xs: calc(0.75rem * var(--scale));
  --fs-sm: calc(0.875rem * var(--scale));
  --fs-base: calc(1rem * var(--scale));
  --fs-lead: calc(clamp(1.0625rem, 0.98rem + 0.35vw, 1.3rem) * var(--scale));
  --fs-h3: calc(clamp(1.25rem, 1.1rem + 0.8vw, 1.75rem) * var(--scale));
  --fs-h2: calc(clamp(1.85rem, 1.35rem + 2.2vw, 3.25rem) * var(--scale));
  --fs-h1: calc(clamp(2.4rem, 1.5rem + 4.2vw, 5.25rem) * var(--scale));
  --fs-mega: calc(clamp(3rem, 1.6rem + 6vw, 8rem) * var(--scale));

  /* --- ritmo tipográfico --- */
  --lh-body: 1.65;
  --lh-display: 1.02;
  --lh-tight: 1.15;
  --tracking-display: -0.03em;
  --tracking-mono: 0.08em;
  --measure: 52ch;

  /* --- espaçamento: 4px * n * density, em rem --- */
  --space-1: calc(0.25rem * var(--density));
  --space-2: calc(0.5rem * var(--density));
  --space-3: calc(0.75rem * var(--density));
  --space-4: calc(1rem * var(--density));
  --space-5: calc(1.25rem * var(--density));
  --space-6: calc(1.5rem * var(--density));
  --space-7: calc(1.75rem * var(--density));
  --space-8: calc(2rem * var(--density));
  --space-9: calc(2.25rem * var(--density));
  --space-10: calc(2.5rem * var(--density));
  --space-11: calc(2.75rem * var(--density));
  --space-12: calc(3rem * var(--density));
  --space-13: calc(3.25rem * var(--density));
  --space-14: calc(3.5rem * var(--density));
  --space-15: calc(3.75rem * var(--density));
  --space-16: calc(4rem * var(--density));

  /* --- grid e container --- */
  --section-y: calc(clamp(4.5rem, 3rem + 6vw, 9rem) * var(--density));
  --gutter: clamp(1.25rem, 0.9rem + 2vw, 3rem);
  --container: 1280px;
  --container-wide: 1600px;
  --grid-min: 280px;         /* mínimo do repeat(auto-fit, minmax(...)) */

  /* --- medidas de chassi --- */
  --header-h: 4.5rem;
  --tap-min: 44px;           /* alvo de toque mínimo no mobile */
  --hairline: 1px;

  /* --- sombra, modulada por --shadow-strength (0 = sem sombra) --- */
  --shadow-sm: 0 1px 2px hsl(var(--foreground) / calc(0.05 * var(--shadow-strength)));
  --shadow-md: 0 12px 32px -16px hsl(var(--foreground) / calc(0.18 * var(--shadow-strength)));
  --shadow-lg: 0 32px 64px -24px hsl(var(--foreground) / calc(0.24 * var(--shadow-strength)));

  /* --- motion --- */
  --ease-out: cubic-bezier(0.65, 0, 0.35, 1);   /* fábrica de agosto de 2026: curva "io", ver DEFAULTS.easing em core/theme.js */
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-fast: 160ms;
  --dur-base: 420ms;
  --dur-slow: 820ms;

  /* --- intensidade dos padrões de fundo, por tema --- */
  --pattern-boost: 0.85;
}

[data-theme="dark"] {
  color-scheme: dark;

  /* Padrão de fábrica do site. Preto real como base, elevação resolvida por
     cor mais borda de 1px, e primária BRANCA com texto preto: é a assinatura
     da linguagem, não um engano. Neutro puro, matiz 0 e saturação 0%. */
  --brand-h: 0;
  --brand-s: 0%;
  --brand-l: 50%;

  --background: 0 0% 4%;
  --foreground: 0 0% 98%;

  --card: 0 0% 6.5%;
  --card-foreground: 0 0% 98%;

  --popover: 0 0% 7%;
  --popover-foreground: 0 0% 98%;

  --primary: 0 0% 98%;
  --primary-foreground: 0 0% 6%;

  --secondary: 0 0% 14%;
  --secondary-foreground: 0 0% 96%;

  --muted: 0 0% 11%;
  --muted-foreground: 0 0% 63%;

  --accent: 0 0% 16%;
  --accent-foreground: 0 0% 98%;

  --destructive: 0 62% 47%;
  --destructive-foreground: 0 0% 98%;

  --border: 0 0% 16%;
  /* --chip-neutral-bg / --chip-neutral-border: ver a nota completa no bloco
     claro (mesma dupla, imune a preset). */
  --chip-neutral-bg: 0 0% 9%;
  --chip-neutral-border: 0 0% 16%;
  --input: 0 0% 39.5%;        /* ver RESOLVIDO §8 no bloco claro: 3,37:1 e 3,03:1 */
  --ring: 0 0% 72%;

  --surface-1: 0 0% 6.5%;
  --surface-2: 0 0% 9%;
  --surface-3: 0 0% 12%;

  /* mesmo aço, um passo mais claro para segurar 4.5:1 sobre o preto:
     medido 7.41, 7.09, 6.72 e 6.20 contra background, card, surface-2 e
     surface-3 */
  --signal: 205 24% 62%;
  --signal-foreground: 0 0% 6%;

  /* no escuro o véu já cai sobre um fundo escuro: mesma tinta, um pouco
     mais fechada, e os mesmos primeiros planos claros. --scrim-foreground,
     --scrim-muted-foreground e --scrim-faint-foreground não mudam por tema
     porque o véu é escuro nos dois. */
  --scrim: 0 0% 2%;

  /* tinta fraca no escuro: a mesma decisão do bloco claro, invertida.
     Números medidos no comentário de --faint, ali em cima. */
  --faint-foreground: 0 0% 66%;

  --glass-alpha: 0.5;

  /* fábrica de agosto de 2026 (pedido do dono): sombra em força total nos
     dois temas. FACTORY_SHADOW.dark em core/theme.js espelha este valor. */
  --shadow-strength: 1;

  --pattern-boost: 1;
}

/* --------------------------------------------------------------------------
   3. Escala em telas muito grandes.
   Acima de 1900px o container não cresce; quem cresce é a tipografia.
   O estúdio controla --scale-user; --scale é sempre o valor efetivo.
   -------------------------------------------------------------------------- */

@media (min-width: 1900px) {
  :root { --scale: calc(var(--scale-user) * 1.06); }
}

@media (min-width: 2400px) {
  :root { --scale: calc(var(--scale-user) * 1.14); }
}

@media (min-width: 3200px) {
  :root { --scale: calc(var(--scale-user) * 1.28); }
}

/* --------------------------------------------------------------------------
   4. Padrões de fundo.
   Cada padrão é um pacote de custom properties; 02-base.css consome esse
   pacote em body::before (camada fixa, atrás do conteúdo).
   Receitas cruas primeiro, mapeamento depois, para não duplicar imagem.
   -------------------------------------------------------------------------- */

:root {
  /* grade técnica.
     Tamanho e opacidade já levam embutido o multiplicador de fábrica de
     agosto de 2026 (DEFAULTS.patOpacity 0.4, DEFAULTS.patScale 2.45 em
     core/theme.js): 0.75 * 0.4 = 0.3; round(64 * 2.45) = 157px. knobs()
     escreve os dois inline com a MESMA fórmula sempre que o dono muda um
     dos dois multiplicadores; aqui fica só a fábrica, para quando o estado
     está limpo e o inline é removido. */
  --pat-grid-image:
    linear-gradient(to right, hsl(var(--border) / 0.95) var(--hairline), transparent var(--hairline)),
    linear-gradient(to bottom, hsl(var(--border) / 0.95) var(--hairline), transparent var(--hairline));
  --pat-grid-size: 157px 157px;
  --pat-grid-opacity: 0.3;
  --pat-grid-mask: radial-gradient(125% 88% at 50% 0%, hsl(0 0% 0%) 0%, transparent 76%);

  /* pontilhado. Mesma fórmula: 0.6 * 0.4 = 0.24; round(22 * 2.45) = 54px. */
  --pat-dots-image: radial-gradient(hsl(var(--foreground) / 0.24) 1px, transparent 1.2px);
  --pat-dots-size: 54px 54px;
  --pat-dots-opacity: 0.24;
  --pat-dots-mask: radial-gradient(120% 92% at 50% 0%, hsl(0 0% 0%) 0%, transparent 80%);

  /* granulado: SVG inline em data URI, sem rede, sem imagem externa.
     Mesma fórmula: 0.22 * 0.4 = 0.088; round(180 * 2.45) = 441px. */
  --pat-noise-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  --pat-noise-size: 441px 441px;
  --pat-noise-opacity: 0.088;
  --pat-noise-mask: none;

  /* aurora: três manchas largas. A versão antiga girava a matiz da marca em
     +48 e -64 com saturação fixa em 78% e 76%, o que produzia um arco-íris
     que a linguagem preto e branco não aceita. Agora a única cor é --signal,
     as outras manchas são a própria semente neutra e a tinta do texto, e
     nenhuma saturação é escrita solta: uma marca neutra dá um aurora
     cinza-aço, e um preset contido dá o mesmo desenho com um sussurro de
     matiz. */
  --pat-aurora-image:
    radial-gradient(46% 38% at 18% 10%, hsl(var(--brand-h) var(--brand-s) 58% / 0.26), transparent 64%),
    radial-gradient(40% 34% at 84% 6%, hsl(var(--signal) / 0.18), transparent 66%),
    radial-gradient(54% 46% at 62% 92%, hsl(var(--foreground) / 0.07), transparent 68%);
  --pat-aurora-size: 100% 100%;
  /* fábrica de agosto de 2026: 1 * DEFAULTS.patOpacity (0.4) = 0.4. Aurora
     não tem 's' em PAT_BASE (core/theme.js), então --pat-aurora-size nunca
     é escrito por knobs(): patScale não afeta este padrão, só a opacidade. */
  --pat-aurora-opacity: 0.4;
  --pat-aurora-mask: none;

  /* mesh: quatro manchas menores, mais espalhadas e mais discretas */
  --pat-mesh-image:
    radial-gradient(38% 42% at 10% 18%, hsl(var(--brand-h) var(--brand-s) 60% / 0.2), transparent 62%),
    radial-gradient(34% 38% at 90% 24%, hsl(var(--signal) / 0.14), transparent 62%),
    radial-gradient(44% 40% at 28% 84%, hsl(var(--foreground) / 0.06), transparent 64%),
    radial-gradient(40% 36% at 78% 76%, hsl(var(--signal) / 0.1), transparent 64%);
  --pat-mesh-size: 100% 100%;
  /* mesma fórmula do aurora logo acima: 1 * 0.4 = 0.4, sem 's' em PAT_BASE */
  --pat-mesh-opacity: 0.4;
  --pat-mesh-mask: none;

  /* pacote efetivo, valor de fábrica: nenhum padrão */
  --pattern-image: none;
  --pattern-size: auto;
  --pattern-position: 0 0;
  --pattern-repeat: repeat;
  --pattern-opacity: 0;
  --pattern-blend: normal;
  --pattern-mask: none;
  --pattern-anim: none;      /* nome do keyframe; a duração é gated por --motion */
}

/* Seleção do padrão pelo token --pattern, que é o que o estúdio escreve.
   Consulta de estilo em container: html é container implícito de body. */
@container style(--pattern: grid) {
  body {
    --pattern-image: var(--pat-grid-image);
    --pattern-size: var(--pat-grid-size);
    --pattern-opacity: var(--pat-grid-opacity);
    --pattern-mask: var(--pat-grid-mask);
  }
}

@container style(--pattern: dots) {
  body {
    --pattern-image: var(--pat-dots-image);
    --pattern-size: var(--pat-dots-size);
    --pattern-opacity: var(--pat-dots-opacity);
    --pattern-mask: var(--pat-dots-mask);
  }
}

@container style(--pattern: noise) {
  body {
    --pattern-image: var(--pat-noise-image);
    --pattern-size: var(--pat-noise-size);
    --pattern-opacity: var(--pat-noise-opacity);
    --pattern-mask: var(--pat-noise-mask);
  }
}

@container style(--pattern: aurora) {
  body {
    --pattern-image: var(--pat-aurora-image);
    --pattern-size: var(--pat-aurora-size);
    --pattern-repeat: no-repeat;
    --pattern-opacity: var(--pat-aurora-opacity);
    --pattern-mask: var(--pat-aurora-mask);
    --pattern-anim: nx-drift;
  }
}

@container style(--pattern: mesh) {
  body {
    --pattern-image: var(--pat-mesh-image);
    --pattern-size: var(--pat-mesh-size);
    --pattern-repeat: no-repeat;
    --pattern-opacity: var(--pat-mesh-opacity);
    --pattern-mask: var(--pat-mesh-mask);
    --pattern-anim: nx-drift;
  }
}

/* Mesma seleção por atributo, para quem preferir marcar no HTML
   (data-pattern no <html>) e como rede de segurança onde a consulta de
   estilo em container não estiver disponível. */
[data-pattern="grid"] {
  --pattern-image: var(--pat-grid-image);
  --pattern-size: var(--pat-grid-size);
  --pattern-opacity: var(--pat-grid-opacity);
  --pattern-mask: var(--pat-grid-mask);
}

[data-pattern="dots"] {
  --pattern-image: var(--pat-dots-image);
  --pattern-size: var(--pat-dots-size);
  --pattern-opacity: var(--pat-dots-opacity);
  --pattern-mask: var(--pat-dots-mask);
}

[data-pattern="noise"] {
  --pattern-image: var(--pat-noise-image);
  --pattern-size: var(--pat-noise-size);
  --pattern-opacity: var(--pat-noise-opacity);
  --pattern-mask: var(--pat-noise-mask);
}

[data-pattern="aurora"] {
  --pattern-image: var(--pat-aurora-image);
  --pattern-size: var(--pat-aurora-size);
  --pattern-repeat: no-repeat;
  --pattern-opacity: var(--pat-aurora-opacity);
  --pattern-mask: var(--pat-aurora-mask);
  --pattern-anim: nx-drift;
}

[data-pattern="mesh"] {
  --pattern-image: var(--pat-mesh-image);
  --pattern-size: var(--pat-mesh-size);
  --pattern-repeat: no-repeat;
  --pattern-opacity: var(--pat-mesh-opacity);
  --pattern-mask: var(--pat-mesh-mask);
  --pattern-anim: nx-drift;
}

[data-pattern="none"] {
  --pattern-image: none;
  --pattern-opacity: 0;
  --pattern-anim: none;
}
