/* ==========================================================================
   NEXUS theme-final / 03-ui.css
   Primitivos de interface, no espírito shadcn/ui, em CSS puro com classes
   semânticas. Nenhum layout de seção específica mora aqui: isso é território
   de 04-sections.css (transversal) ou assets/css/sections/<id>.css (por id).

   Só consome tokens de 01-tokens.css. Nenhum hex, nenhum px de raio, nenhuma
   família de fonte literal. Motion: só transform e opacity animam; toda
   duração de transição usa calc(var(--dur-*) * var(--motion)) para o
   interruptor do estúdio conseguir zerar tudo, no mesmo espírito do
   body::before de 02-base.css.

   Índice:
   1. Botão (.btn)
   2. Card (.card)
   3. Badge (.badge)
   4. Chip de filtro (.chip)
   5. Campos de formulário (.field, .input, .textarea, .select, .switch,
      .slider, .color-field, .segmented)
   6. Separador (.separator)
   7. Tooltip por atributo ([data-tooltip])
   8. Tecla (.kbd)
   9. Avatar (.avatar)
   10. Skeleton (.skeleton)
   11. Área de rolagem (.scroll-area)
   12. Link (.link)
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Botão

   RECEITA DE ESTADO EM PALETA PRETO E BRANCO (H 0, S 0%): sem matiz para
   diferenciar hover/active/focus, os três instrumentos disponíveis são
   luminosidade, opacidade e transform.

   - .btn--primary e .btn--destructive (preenchimento extremo, quase-branco
     no escuro / quase-preto no claro): hover e active usam hsl(var(--x) /
     alpha). Funciona porque, dentro de um mesmo tema, background, card e
     todas as --surface-* moram no mesmo polo de luminosidade (extremo
     oposto ao de --primary); reduzir o alfa sempre mistura na direção certa,
     qualquer que seja a superfície de baixo.
   - .btn--secondary/--outline/--ghost (preenchimento médio ou nenhum): hover
     troca para --accent (token sólido, não opacidade, porque --secondary e
     --accent já são vizinhos próximos na rampa neutra e um blend por alfa
     mal se percebe) e reforça a borda com --input, o token calibrado para
     3:1 de componente interativo; nunca --border, reservado para divisor de
     superfície estática (crachá do parágrafo 3 abaixo).
   - Toda variante ganha translateY(-1px) no hover e translateY(1px) no
     active: pista de profundidade por transform, imune a qualquer paleta.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-block-size: var(--tap-min);
  padding-block: var(--space-3);
  padding-inline: var(--space-5);
  border-radius: var(--radius-md);
  border: var(--hairline) solid transparent;
  background-color: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  user-select: none;
  transition:
    background-color calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    border-color calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    color calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    transform calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.btn:focus-visible {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

/* seta interna: só transform desloca, nunca margin/left */
.btn__arrow {
  display: inline-flex;
  flex: none;
  transition: transform calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

.btn:hover .btn__arrow,
.btn:focus-visible .btn__arrow {
  transform: translateX(var(--space-1));
}

.btn--primary {
  background-color: hsl(var(--primary));
  border-color: transparent;
  color: hsl(var(--primary-foreground));
}

.btn--primary:hover {
  background-color: hsl(var(--primary) / 0.88);
  transform: translateY(-1px);
}

.btn--primary:active {
  background-color: hsl(var(--primary) / 0.78);
  transform: translateY(1px);
}

/* Preenchimento extremo (branco no escuro, preto no claro): o --ring do
   estado de foco pode se aproximar demais do próprio tom do botão. Reforça
   com um traço interno na cor oposta, --primary-foreground, que já inverte
   sozinha por tema (preto no escuro, branco no claro), então a separação
   fica garantida em qualquer superfície por trás, sem depender de matiz. */
.btn--primary:focus-visible {
  box-shadow: inset 0 0 0 2px hsl(var(--primary-foreground) / 0.85);
}

.btn--secondary {
  background-color: hsl(var(--secondary));
  border-color: hsl(var(--border));
  color: hsl(var(--secondary-foreground));
}

.btn--secondary:hover {
  background-color: hsl(var(--accent));
  border-color: hsl(var(--input));
  color: hsl(var(--accent-foreground));
  transform: translateY(-1px);
}

.btn--secondary:active {
  background-color: hsl(var(--accent));
  border-color: hsl(var(--input));
  transform: translateY(1px);
}

.btn--outline {
  background-color: transparent;
  border-color: hsl(var(--input));
  color: hsl(var(--foreground));
}

.btn--outline:hover {
  background-color: hsl(var(--accent));
  border-color: hsl(var(--ring));
  color: hsl(var(--accent-foreground));
  box-shadow: inset 0 0 0 1px hsl(var(--ring) / 0.35);
  transform: translateY(-1px);
}

.btn--outline:active {
  background-color: hsl(var(--accent));
  transform: translateY(1px);
}

.btn--ghost {
  background-color: transparent;
  border-color: transparent;
  color: hsl(var(--foreground));
}

.btn--ghost:hover {
  background-color: hsl(var(--accent));
  color: hsl(var(--accent-foreground));
  transform: translateY(-1px);
}

.btn--ghost:active {
  background-color: hsl(var(--accent));
  transform: translateY(1px);
}

.btn--link {
  position: relative;
  min-block-size: auto;
  padding: 0;
  background-color: transparent;
  border-color: transparent;
  color: hsl(var(--primary));
  font-weight: 600;
}

.btn--link::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-end: -0.2em;
  inline-size: 100%;
  block-size: max(1px, 0.06em);
  background-color: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

.btn--link:hover::after,
.btn--link:focus-visible::after {
  transform: scaleX(1);
}

.btn--destructive {
  background-color: hsl(var(--destructive));
  border-color: transparent;
  color: hsl(var(--destructive-foreground));
}

.btn--destructive:hover {
  background-color: hsl(var(--destructive) / 0.9);
  transform: translateY(-1px);
}

.btn--destructive:active {
  transform: translateY(1px);
}

.btn--sm {
  min-block-size: calc(var(--tap-min) * 0.8);
  padding-block: var(--space-2);
  padding-inline: var(--space-4);
  font-size: var(--fs-xs);
}

.btn--lg {
  padding-block: var(--space-4);
  padding-inline: var(--space-7);
  font-size: var(--fs-base);
}

.btn--icon {
  min-inline-size: var(--tap-min);
  min-block-size: var(--tap-min);
  padding: var(--space-3);
}

.btn--icon.btn--sm {
  min-inline-size: calc(var(--tap-min) * 0.8);
  min-block-size: calc(var(--tap-min) * 0.8);
  padding: var(--space-2);
}

/* pointer coarse (touch): garante 44px mesmo em variantes pequenas */
@media (pointer: coarse) {
  .btn--sm,
  .btn--icon.btn--sm {
    min-block-size: var(--tap-min);
    min-inline-size: var(--tap-min);
  }
}

/* Mesma garantia por largura de tela (contrato §7.5: "no mobile", uma
   classe de viewport, não uma capacidade de input). Telas estreitas sem
   ponteiro grosso reportado (comum em automação e em alguns hibridos)
   continuam precisando do alvo de 44px. */
@media (max-width: 767px) {
  .btn--sm,
  .btn--icon.btn--sm {
    min-block-size: var(--tap-min);
  }
}

/* --------------------------------------------------------------------------
   2. Card
   -------------------------------------------------------------------------- */

.card {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  border: var(--hairline) solid hsl(var(--border));
  background-color: hsl(var(--card));
  color: hsl(var(--card-foreground));
  box-shadow: var(--shadow-sm);
  transition:
    border-color calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    transform calc(var(--dur-base) * var(--motion)) var(--ease-out),
    filter calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

/* Estado de toque/ativo genérico (item novo da rodada de 06/08/2026: ver
   02-base.css, `html { -webkit-tap-highlight-color: transparent }`, mesmo
   comentário — o realce nativo do navegador desligou, e este é o
   substituto). QUALQUER `.card` clicável que ainda não tenha o próprio
   `:active` ganha este aqui de graça — inclusive cartões fora do
   território desta folha que só compõem `.card` mais uma classe própria
   de seção (ex.: `.portfolio__card`, que só herdava `:hover`/
   `:focus-within` locais, nunca `:active`; em toque puro `:hover` nunca
   dispara, então não havia retorno visual nenhum ali). `filter` pinta a
   PRÓPRIA caixa do elemento — respeita `border-radius` de qualquer raio,
   em qualquer pele, sem receita nenhuma, ao contrário do retângulo nativo
   que acabou de ser desligado. E não depende de `:hover` disparar antes:
   funciona sozinho no toque, que é exatamente o caso que faltava.
   `.card--interactive:active` (abaixo) continua com o próprio `transform`
   — propriedades diferentes, os dois efeitos somam em vez de brigar. */
.card:active {
  filter: brightness(0.96);
}

.card__header {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-6) var(--space-6) 0;
}

.card__header + .card__body {
  padding-block-start: var(--space-4);
}

.card__title {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 600;
  line-height: var(--lh-tight);
  color: hsl(var(--card-foreground));
}

.card__desc {
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: hsl(var(--muted-foreground));
}

.card__body {
  display: grid;
  align-content: start;
  gap: var(--space-4);
  padding: var(--space-6);
  flex: 1;
}

.card__body + .card__footer {
  padding-block-start: 0;
}

.card__footer {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-6);
}

.card--interactive {
  cursor: pointer;
}

.card--interactive:hover,
.card--interactive:focus-visible {
  transform: translateY(-2px);
  border-color: hsl(var(--primary) / 0.5);
}

.card--interactive:active {
  transform: translateY(0);
}

.card--interactive:focus-visible {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

/* brilho radial que acompanha o cursor: só transform e opacity animam.
   --px e --py são percentuais escritos por JS a cada movimento do ponteiro.
   Cor: --signal, o único token cromático do site (aço frio, uso pontual
   abaixo de 3% da tela), no lugar de --primary. Um brilho branco/preto puro
   fica achatado; um sussurro de --signal é o toque "caro" da linguagem
   Vercel/Linear sem virar decoração colorida. */
.card--glow {
  position: relative;
  overflow: hidden;
  --px: 50%;
  --py: 0%;
}

.card--glow::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(28rem circle at 50% 50%, hsl(var(--signal) / 0.16), transparent 70%);
  transform: translate3d(calc(var(--px) - 50%), calc(var(--py) - 50%), 0);
  opacity: 0;
  transition: opacity calc(var(--dur-base) * var(--motion)) var(--ease-out);
  will-change: transform;
}

.card--glow:hover::before,
.card--glow:focus-within::before {
  opacity: 1;
}

.card--glow > * {
  position: relative;
  z-index: 1;
}

/* --------------------------------------------------------------------------
   3. Badge

   RESOLVIDO na rodada de agosto de 2026, quarta aparição do mesmo defeito.
   .badge--success e .badge--warning referenciavam var(--success) e
   var(--warning), que nunca existiram em 01-tokens.css: as seis declarações
   eram descartadas pelo parser e as duas classes rendiam um crachá sem
   preenchimento, sem borda e sem tinta. As três rodadas anteriores registraram
   a pendência em comentário aqui e em sections/portfolio.css §5 e seguiram em
   frente; comentário não é conserto.

   As duas classes foram REMOVIDAS, e a decisão tem duas pernas:

   1. Ninguém as usa. `grep -rn "badge--success\|badge--warning"` em
      assets/, partials/ e index*.html não devolve uma única marcação: as
      ocorrências eram a definição morta e os comentários que a lamentavam.
      O estado de produto do portfólio já se resolve sem elas, por
      .portfolio__badge[data-tone], com --primary e estilo de borda.
   2. Criar os tokens não resolveria, mascararia. A paleta de fábrica é
      neutra pura (matiz 0, saturação 0%, ver o cabeçalho de 01-tokens.css) e
      cor cromática existe em um token só, --signal, com teto de 3% da tela.
      Um --success e um --warning neutros seriam dois cinzas indistinguíveis
      de .badge--secondary, ou seja, um crachá que promete semântica de estado
      e entrega nenhuma. Um --success verde e um --warning âmbar comprariam a
      semântica quebrando a linguagem, e ainda dependeriam de par
      superfície/-foreground validado nos dois modos e nas três peles (§12).

   Quando existir uma necessidade real de estado semântico, ela entra pelo
   caminho inteiro: par de tokens em 01-tokens.css, medido nos dois modos,
   mais a variante aqui, mais um consumidor. Não pela metade, como estava.
   -------------------------------------------------------------------------- */

/* GEOMETRIA E PESO, revisto na rodada de agosto de 2026 a pedido do dono
   ("botões com cara de IA").

   O crachá era `--font-mono` + `text-transform: uppercase` +
   `--radius-full`. Os três juntos são exatamente o padrão que o detector do
   projeto marca como gerado: cápsula totalmente redonda, mono, caixa alta,
   igual em toda parte, independentemente do que a etiqueta diz.

   Havia também um defeito de contrato por baixo do estilo. O §3 reserva mono
   caixa alta para RÓTULO DE SEÇÃO e DADO TABULAR. Nenhum dos consumidores de
   .badge é uma dessas duas coisas: são nomes de produto (.govtech-chip),
   estado de produto (.portfolio__badge), sigla de modelo (.modelos__sigla),
   país (.govtech-inco__pais) e referência normativa (.govtech-legal__chip).
   Nome próprio em caixa alta não é ênfase, é ruído: "AQUANTA" e "PROJETO MEL"
   deixam de ser nomes e viram etiquetas de sistema.

   O que passa a valer:

   - Tipo do CORPO, caixa como o dado vier. As siglas que já nascem em caixa
     alta ("B2B", "A", "LGPD") continuam em caixa alta, porque o texto é
     assim; os nomes próprios voltam a ter forma de nome. Quem precisa
     genuinamente de mono tabular pede .badge--mono, que é opt-in e tem
     consumidor declarado (o código de motor de #govtech).
   - RAIO PEQUENO, derivado, nunca pill. `calc(var(--radius-sm) * 0.5)` é
     `calc(var(--radius) * 0.25)`: continua saindo da raiz do §2.3, continua
     obedecendo ao estúdio (em --radius: 0 o crachá fica reto, como o §2.3
     exige) e nunca vira cápsula, nem na pele de vidro, onde --radius é
     1.5rem e o pill anterior comia a forma da peça.
   - Peso 550 em vez de 500 mono: fonte variável, e o crachá em corpo precisa
     de um degrau a mais para se separar do texto ao redor sem gritar.

   O par de tinta não muda em nenhuma variante, então a medição de contraste
   do §8 é a mesma de antes desta rodada. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  /* O raio vira um botão nomeado em vez de um calc solto no meio da regra:
     quem quiser um crachá mais ou menos redondo mexe em --badge-radius, num
     escopo qualquer, sem reescrever a regra e sem tocar no raio de todo o
     resto. O padrão continua derivando de --radius, então o estúdio continua
     mandando nele. */
  --badge-radius: calc(var(--radius-sm) * 0.5);
  padding-block: calc(var(--space-1) * 0.9);
  padding-inline: var(--space-2);
  border-radius: var(--badge-radius);
  border: var(--hairline) solid transparent;
  background-color: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 550;
  line-height: 1.35;
  letter-spacing: 0;
  white-space: nowrap;
  text-decoration: none;
}

/* Opt-in para o que é de fato DADO TABULAR ou CÓDIGO curto: o código de
   motor de #govtech ("A", "B", "C", "Base"), que é identificador e não
   nome. Mono, caixa alta e tracking do §3 ficam aqui, com consumidor
   declarado, em vez de valerem para todo crachá por herança. */
.badge--mono {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: 500;
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  padding-inline: var(--space-3);
}

a.badge,
button.badge {
  cursor: pointer;
  transition: opacity calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

a.badge:hover,
button.badge:hover {
  opacity: 0.85;
}

.badge:focus-visible {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

.badge--default {
  background-color: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
}

/* Preenchimento extremo, mesmo raciocínio do .btn--primary: o traço interno
   garante separação do próprio --ring em qualquer superfície por trás. */
.badge--default:focus-visible,
a.badge:focus-visible,
button.badge:focus-visible {
  box-shadow: inset 0 0 0 2px hsl(var(--primary-foreground) / 0.85);
}

/* --secondary e --card ficam a poucos pontos de luminosidade um do outro
   nesta rampa neutra (ver §3 do arquivo); sem uma borda própria a etiqueta
   se perde contra o cartão que a hospeda. */
.badge--secondary {
  background-color: hsl(var(--secondary));
  border-color: hsl(var(--border));
  color: hsl(var(--secondary-foreground));
}

/* --input, não --border: crachá sem preenchimento é componente interativo
   em potencial (a.badge/button.badge), pede o traço calibrado para 3:1. */
.badge--outline {
  background-color: transparent;
  border-color: hsl(var(--input));
  color: hsl(var(--foreground));
}

/* --------------------------------------------------------------------------
   4. Chip de filtro
   Estado ligado é aria-pressed="true"; NX/estúdio ou partial de portfólio
   alterna o atributo via JS.

   UMA GEOMETRIA SÓ PARA OS DOIS ESTADOS (rodada de agosto de 2026, print do
   dono sobre o filtro de #portfolio).

   A regra que passa a valer, e que vale em TODO consumidor de .chip, aqui e
   no cromo do estúdio: ligado e desligado compartilham forma, altura, raio,
   padding, tipo e peso. A ÚNICA diferença permitida entre os dois estados é
   PREENCHIMENTO e BORDA. Nenhuma pele, nenhuma seção e nenhum estado pode
   redeclarar `border-radius`, `padding` ou `min-block-size` só de um lado:
   dois raios diferentes na mesma fileira leem como dois componentes
   diferentes, e foi exatamente isso que o print mostrou.

   Junto com isso, o mesmo acerto de "cara de IA" do crachá acima: o raio
   deixa de ser cápsula (--radius-full) e passa a ser o degrau pequeno
   derivado da raiz, e a altura deixa de ser 44px fixos em todo lugar. O alvo
   de toque de 44px do §7.5 é requisito de MOBILE, não de desktop: fixá-lo em
   toda viewport engordava a fileira inteira e é metade do que fazia o filtro
   parecer gerado. Ele volta, inteiro, sob ponteiro grosso e em tela estreita,
   logo abaixo.
   -------------------------------------------------------------------------- */

.chip {
  /* Mesmo botão nomeado do crachá, e de propósito com nome próprio: chip e
     crachá podem divergir de raio sem que um arraste o outro. Quem redefinir
     --chip-radius redefine os DOIS estados de uma vez, que é exatamente a
     invariante deste bloco. */
  --chip-radius: calc(var(--radius-sm) * 0.5);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding-block: var(--space-2);
  padding-inline: var(--space-4);
  border-radius: var(--chip-radius);
  /* --input, não --border: chip é filtro clicável, o traço pede o mínimo
     de 3:1 de componente interativo, não o hairline de divisor estático. */
  border: var(--hairline) solid hsl(var(--input));
  background-color: hsl(var(--surface-2));
  color: hsl(var(--muted-foreground));
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 550;
  line-height: 1.25;
  cursor: pointer;
  transition:
    background-color calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    border-color calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    color calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    transform calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

/* Alvo de toque do §7.5, onde ele é requisito: dedo ou tela estreita.
   Vale para os DOIS estados, pela regra da geometria única acima. */
@media (pointer: coarse), (max-width: 47.9375rem) {
  .chip {
    min-block-size: var(--tap-min);
  }
}

.chip:hover {
  border-color: hsl(var(--primary) / 0.5);
  color: hsl(var(--foreground));
  transform: translateY(-1px);
}

.chip:focus-visible {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

.chip:active {
  transform: scale(0.97);
}

.chip[aria-pressed="true"] {
  background-color: hsl(var(--primary));
  border-color: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
}

.chip[aria-pressed="true"]:hover {
  background-color: hsl(var(--primary) / 0.9);
}

/* Preenchimento extremo quando pressionado: mesma dupla de anel do
   .btn--primary, pelo mesmo motivo (ver comentário lá em cima). */
.chip[aria-pressed="true"]:focus-visible {
  box-shadow: inset 0 0 0 2px hsl(var(--primary-foreground) / 0.85);
}

.chip:disabled,
.chip[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   5. Campos de formulário
   Usados pela página e pelo estúdio de tema: precisam caber num painel de
   320px e ter alvo de toque de 44px no mobile.
   -------------------------------------------------------------------------- */

.field {
  display: grid;
  gap: var(--space-2);
}

.field--row {
  grid-auto-flow: column;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.field__label {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: hsl(var(--foreground));
}

.field__hint {
  font-size: var(--fs-xs);
  line-height: 1.4;
  color: hsl(var(--muted-foreground));
}

.field__hint--error {
  color: hsl(var(--destructive));
}

.input,
.textarea,
.select {
  display: block;
  inline-size: 100%;
  min-block-size: var(--tap-min);
  padding-block: var(--space-2);
  padding-inline: var(--space-4);
  border-radius: var(--radius-md);
  border: var(--hairline) solid hsl(var(--input));
  background-color: hsl(var(--surface-1));
  color: hsl(var(--foreground));
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  line-height: 1.4;
  transition:
    border-color calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    background-color calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

.input::placeholder,
.textarea::placeholder {
  color: hsl(var(--muted-foreground));
}

.input:hover,
.textarea:hover,
.select:hover {
  border-color: hsl(var(--ring) / 0.5);
}

.input:focus-visible,
.textarea:focus-visible,
.select:focus-visible {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
  border-color: hsl(var(--ring));
}

.input:disabled,
.textarea:disabled,
.select:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  background-color: hsl(var(--muted));
}

.textarea {
  min-block-size: calc(var(--tap-min) * 2);
  padding-block: var(--space-3);
  resize: vertical;
}

/* select nativo: seta desenhada em CSS (borda rotacionada), sem imagem,
   sem data URI, cor sempre coerente com o tema */
.select-wrap {
  position: relative;
  display: block;
}

.select-wrap .select {
  padding-inline-end: var(--space-8);
  appearance: none;
  -webkit-appearance: none;
}

.select-wrap::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--space-4);
  inset-block-start: 40%;
  inline-size: 0.5em;
  block-size: 0.5em;
  border-inline-end: var(--hairline) solid hsl(var(--muted-foreground));
  border-block-end: var(--hairline) solid hsl(var(--muted-foreground));
  transform: translateY(-50%) rotate(45deg);
  pointer-events: none;
}

/* switch: dimensão fixada em --tap-min para o alvo de toque valer sempre,
   não só sob (pointer: coarse) */
.switch {
  --switch-h: var(--tap-min);
  --switch-w: calc(var(--tap-min) * 1.7);
  position: relative;
  display: inline-flex;
  align-items: center;
  inline-size: var(--switch-w);
  block-size: var(--switch-h);
  flex: none;
}

.switch input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.switch__track {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-full);
  background-color: hsl(var(--input));
  border: var(--hairline) solid hsl(var(--border));
  transition: background-color calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

.switch__thumb {
  position: absolute;
  inset-inline-start: var(--space-1);
  inset-block-start: 50%;
  inline-size: calc(var(--switch-h) - var(--space-2));
  block-size: calc(var(--switch-h) - var(--space-2));
  border-radius: var(--radius-full);
  background-color: hsl(var(--background));
  box-shadow: var(--shadow-sm);
  transform: translateY(-50%) translateX(0);
  transition: transform calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

.switch input:checked ~ .switch__track {
  background-color: hsl(var(--primary));
  border-color: hsl(var(--primary));
}

.switch input:checked ~ .switch__thumb {
  transform: translateY(-50%) translateX(calc(var(--switch-w) - var(--switch-h)));
}

.switch input:focus-visible ~ .switch__track {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

/* Ligado, o track é preenchimento extremo (--primary): mesmo traço interno
   em --primary-foreground dos outros controles preenchidos, mesma razão. */
.switch input:checked:focus-visible ~ .switch__track {
  box-shadow: inset 0 0 0 2px hsl(var(--primary-foreground) / 0.85);
}

.switch input:disabled ~ .switch__track {
  opacity: 0.5;
}

.switch:has(input:disabled) {
  cursor: not-allowed;
}

/* slider: trilho preenchido até --slider-fill (escrito por JS em %),
   0% se não informado */
.slider {
  appearance: none;
  -webkit-appearance: none;
  inline-size: 100%;
  block-size: var(--tap-min);
  background: transparent;
  cursor: pointer;
}

.slider::-webkit-slider-runnable-track {
  block-size: calc(var(--space-2) * 0.75);
  border-radius: var(--radius-full);
  background: linear-gradient(
    to right,
    hsl(var(--primary)) var(--slider-fill, 50%),
    hsl(var(--secondary)) 0
  );
}

.slider::-moz-range-track {
  block-size: calc(var(--space-2) * 0.75);
  border-radius: var(--radius-full);
  background-color: hsl(var(--secondary));
}

.slider::-moz-range-progress {
  block-size: calc(var(--space-2) * 0.75);
  border-radius: var(--radius-full);
  background-color: hsl(var(--primary));
}

.slider::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  inline-size: var(--space-6);
  block-size: var(--space-6);
  margin-block-start: calc((var(--space-2) * 0.75 - var(--space-6)) / 2);
  border-radius: var(--radius-full);
  background-color: hsl(var(--background));
  border: calc(var(--hairline) * 2) solid hsl(var(--primary));
  box-shadow: var(--shadow-sm);
  transition: transform calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

.slider::-moz-range-thumb {
  inline-size: var(--space-6);
  block-size: var(--space-6);
  border-radius: var(--radius-full);
  background-color: hsl(var(--background));
  border: calc(var(--hairline) * 2) solid hsl(var(--primary));
  box-shadow: var(--shadow-sm);
}

.slider:hover::-webkit-slider-thumb {
  transform: scale(1.08);
}

.slider:focus-visible::-webkit-slider-thumb,
.slider:focus-visible::-moz-range-thumb {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

.slider:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* color-field: swatch nativo + valor textual (usado pelo picker de --primary
   e afins no estúdio) */
.color-field {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: var(--tap-min);
  padding-inline: var(--space-2);
  border-radius: var(--radius-md);
  border: var(--hairline) solid hsl(var(--input));
  background-color: hsl(var(--surface-1));
  transition: border-color calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

.color-field:focus-within {
  border-color: hsl(var(--ring));
  outline: 2px solid hsl(var(--ring) / 0.35);
  outline-offset: 1px;
}

.color-field__swatch {
  appearance: none;
  -webkit-appearance: none;
  inline-size: var(--space-8);
  block-size: var(--space-8);
  padding: 0;
  border: var(--hairline) solid hsl(var(--border));
  border-radius: var(--radius-sm);
  background: none;
  cursor: pointer;
  flex: none;
}

.color-field__swatch::-webkit-color-swatch-wrapper {
  padding: 0;
}

.color-field__swatch::-webkit-color-swatch {
  border: 0;
  border-radius: var(--radius-sm);
}

.color-field__swatch::-moz-color-swatch {
  border: 0;
  border-radius: var(--radius-sm);
}

.color-field__value {
  flex: 1;
  min-inline-size: 0;
  border: 0;
  background: transparent;
  color: hsl(var(--foreground));
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
}

.color-field__value:focus-visible {
  outline: none;
}

/* segmented: grupo de 2 a 3 botões tipo radio (aria-pressed/aria-checked
   no botão ativo) */
.segmented {
  display: inline-flex;
  align-items: stretch;
  gap: calc(var(--hairline) * 3);
  padding: calc(var(--hairline) * 3);
  border-radius: var(--radius-md);
  border: var(--hairline) solid hsl(var(--border));
  background-color: hsl(var(--secondary));
  inline-size: 100%;
}

.segmented__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  flex: 1;
  min-block-size: var(--tap-min);
  padding-inline: var(--space-4);
  border-radius: var(--radius-sm);
  background-color: transparent;
  color: hsl(var(--muted-foreground));
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  transition:
    background-color calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    color calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

.segmented__btn:hover {
  color: hsl(var(--foreground));
}

.segmented__btn:focus-visible {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

.segmented__btn[aria-pressed="true"],
.segmented__btn[aria-checked="true"] {
  background-color: hsl(var(--surface-1));
  color: hsl(var(--foreground));
  box-shadow: var(--shadow-sm);
}

.segmented__btn:disabled,
.segmented__btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   6. Separador
   -------------------------------------------------------------------------- */

.separator {
  flex: none;
  inline-size: 100%;
  block-size: var(--hairline);
  background-color: hsl(var(--border));
  border: 0;
}

.separator--vertical {
  align-self: stretch;
  inline-size: var(--hairline);
  block-size: auto;
}

/* --------------------------------------------------------------------------
   7. Tooltip por atributo
   Uso: <button data-tooltip="Texto">…</button>. Opcional data-tooltip-pos
   ="bottom" para abrir para baixo (padrão: para cima).
   -------------------------------------------------------------------------- */

[data-tooltip] {
  position: relative;
}

[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  inset-inline-start: 50%;
  inset-block-end: calc(100% + var(--space-2));
  z-index: 40;
  padding-block: var(--space-1);
  padding-inline: var(--space-3);
  border-radius: var(--radius-sm);
  border: var(--hairline) solid hsl(var(--border));
  background-color: hsl(var(--popover));
  color: hsl(var(--popover-foreground));
  box-shadow: var(--shadow-md);
  font-size: var(--fs-xs);
  line-height: 1.3;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, var(--space-1)) scale(0.96);
  transform-origin: bottom center;
  transition:
    opacity calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    transform calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
}

[data-tooltip=""]::after,
[data-tooltip="false"]::after {
  content: none;
}

[data-tooltip][data-tooltip-pos="bottom"]::after {
  inset-block-end: auto;
  inset-block-start: calc(100% + var(--space-2));
  transform: translate(-50%, calc(var(--space-1) * -1)) scale(0.96);
  transform-origin: top center;
}

[data-tooltip][data-tooltip-pos="bottom"]:hover::after,
[data-tooltip][data-tooltip-pos="bottom"]:focus-visible::after {
  transform: translate(-50%, 0) scale(1);
}

/* --------------------------------------------------------------------------
   8. Tecla
   -------------------------------------------------------------------------- */

.kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: var(--space-6);
  padding-block: var(--space-1);
  padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  border: var(--hairline) solid hsl(var(--border));
  border-block-end-width: calc(var(--hairline) * 2);
  background-color: hsl(var(--muted));
  color: hsl(var(--muted-foreground));
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1;
}

/* --------------------------------------------------------------------------
   9. Avatar
   -------------------------------------------------------------------------- */

.avatar {
  position: relative;
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  inline-size: var(--space-10);
  block-size: var(--space-10);
  border-radius: var(--radius-full);
  background-color: hsl(var(--muted));
  color: hsl(var(--muted-foreground));
  font-size: var(--fs-xs);
  font-weight: 600;
}

.avatar img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.avatar__fallback {
  text-transform: uppercase;
}

.avatar--sm {
  inline-size: var(--space-8);
  block-size: var(--space-8);
}

.avatar--lg {
  inline-size: var(--space-13);
  block-size: var(--space-13);
}

/* --------------------------------------------------------------------------
   10. Skeleton
   -------------------------------------------------------------------------- */

.skeleton {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-md);
  background-color: hsl(var(--muted));
}

.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, hsl(var(--foreground) / 0.06), transparent);
  transform: translateX(-100%);
  animation: nx-skeleton calc(var(--dur-slow) * 2 * var(--motion)) var(--ease-io) infinite;
}

@keyframes nx-skeleton {
  to {
    transform: translateX(100%);
  }
}

/* --------------------------------------------------------------------------
   11. Área de rolagem
   scrollbar-width/scrollbar-color cobrem Firefox; ::-webkit-scrollbar cobre
   Chrome/Safari; onde nenhum dos dois existir, cai na scrollbar nativa do
   sistema, que já é o fallback aceitável.
   -------------------------------------------------------------------------- */

.scroll-area {
  position: relative;
  overflow: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: hsl(var(--border)) transparent;
  -webkit-overflow-scrolling: touch;
}

.scroll-area::-webkit-scrollbar {
  inline-size: var(--space-2);
  block-size: var(--space-2);
}

.scroll-area::-webkit-scrollbar-track {
  background: transparent;
}

.scroll-area::-webkit-scrollbar-thumb {
  background-color: hsl(var(--border));
  background-clip: padding-box;
  border: calc(var(--hairline) * 2) solid transparent;
  border-radius: var(--radius-full);
}

/* --input, não um alfa sobre --muted-foreground. O polegar da barra é limite
   de componente interativo, que é literalmente o que --input existe para
   pintar, e assim a varredura por "muted-foreground) / 0." fecha em zero no
   projeto inteiro: enquanto sobrar uma ocorrência, mesmo em background, o
   padrão continua disponível para ser copiado para dentro de um `color`. */
.scroll-area::-webkit-scrollbar-thumb:hover {
  background-color: hsl(var(--input));
}

.scroll-area--x {
  overflow-x: auto;
  overflow-y: hidden;
}

.scroll-area--y {
  overflow-x: hidden;
  overflow-y: auto;
}

/* --------------------------------------------------------------------------
   12. Link
   Filete sempre presente, que troca de tinta no hover.

   A TINTA NÃO É MAIS --primary (defeito 2 da décima primeira rodada).
   --primary é cor de PREENCHIMENTO e de LIMITE: os pisos que a paleta
   garante para ela são 4,5 contra --primary-foreground e 3 contra o fundo.
   Escrita como `color`, ela vira tinta de glifo, e aí o piso é 4,5 contra a
   superfície que hospeda o link, que é um par que ninguém media. Na paleta
   neutra o defeito não aparecia porque --primary de fábrica é 0 0% 9% no
   claro e 0 0% 98% no escuro, ou seja, praticamente o próprio --foreground
   (0 0% 6% e 0 0% 98%): este seletor JÁ renderizava como --foreground em
   todas as medições do projeto. Em preset cromático a primária cai para
   luminância média e o link descia para a faixa de 2,3 a 4,3.

   Então a correção não muda um pixel do estado de fábrica: ela só escreve o
   que já estava pintado, e passa a valer também quando o dono aplica um
   preset. Papel do elemento é TEXTO, então tinta é --foreground (§2.5), e a
   subordinação de .link--muted continua vindo de --muted-foreground.

   O QUE ENTRA NO LUGAR DA COR COMO AFFORDANCE. Como a cor nunca distinguiu
   este link do texto ao redor na paleta neutra, o sublinhado deixa de nascer
   em scaleX(0) e passa a estar SEMPRE presente, em --border, que é o papel
   de divisor para o qual esse token é calibrado. O hover troca a tinta do
   filete para currentColor em vez de fazê-lo crescer da esquerda: é uma
   transição de cor em um pseudo sem glifo, não animação contínua, e o
   ganho é um link que se anuncia como link em repouso, inclusive para quem
   não distingue matiz.
   -------------------------------------------------------------------------- */

.link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: hsl(var(--foreground));
  font-weight: 600;
  text-decoration: none;
}

/* Alvo de toque mínimo 44x44 (contrato §7.5). ".link" costuma ser só texto
   de uma linha ("ver produto"), então a caixa visual fica bem abaixo disso
   em altura. Em vez de crescer com padding (o que mudaria o layout do card
   e a posição do texto), um pseudo-elemento invisível, centralizado e fora
   do fluxo, estende só a área clicável. ::after já é o filete do sublinhado,
   por isso a extensão de alvo usa ::before. */
.link::before {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  inline-size: max(100%, var(--tap-min));
  block-size: max(100%, var(--tap-min));
  transform: translate(-50%, -50%);
}

/* O filete em repouso é DIVISOR, e é por isso que ele pode ser --border sem
   contrariar a proibição 2 do §2.5: aqui --border pinta `background-color`
   de um pseudo com `content: ""`, ou seja, zero glifo. O que aquela
   proibição reprova é --border escrito como `color`. */
.link::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-end: -0.15em;
  inline-size: 100%;
  block-size: max(1px, 0.06em);
  background-color: hsl(var(--border));
  transition: background-color calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}

.link:hover::after,
.link:focus-visible::after,
.link:active::after {
  background-color: currentColor;
}

/* Toque/ativo próprio (mesmo item novo de 02-base.css, `html {
   -webkit-tap-highlight-color: transparent }`): sem isto um `.link`
   tocado no celular perdia o único retorno visual que tinha, porque o
   sublinhado acima só reagia a `:hover`/`:focus-visible` — nenhum dos dois
   dispara em toque puro. `currentColor` já é a MESMA tinta usada em hover;
   a opacidade reduzida no texto é o que diferencia visualmente "pressionado"
   de "focado". */
.link:active {
  opacity: 0.72;
}

.link:focus-visible {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 3px;
}

.link--muted {
  color: hsl(var(--muted-foreground));
}

/* --------------------------------------------------------------------------
   Diálogo que cabe na janela

   17ª rodada, 06/08/2026, reprovação do dono: numa janela de ~640px de altura
   (notebook com a janela não maximizada) o diálogo de contato e o modal de
   produto ficavam com 270px para fora da tela. Rolar funcionava, mas o topo
   do painel comia a tela inteira e o conteúdo útil não aparecia.

   A política é uma só, por isso mora aqui e não em duas folhas de seção: o
   painel nunca passa da altura da janela, o cromo (cabeçalho, abas, carrossel,
   fechar) fica parado e QUEM ROLA é o corpo. Vale a mesma regra dos embeds:
   um rolador só, e ele é nosso.
   -------------------------------------------------------------------------- */

.dialog-fit {
  display: flex;
  flex-direction: column;
  max-block-size: calc(100dvh - var(--dialog-fit-respiro, var(--space-6)) * 2);
}

/* `min-block-size: 0` é o que faz a rolagem existir: sem ele um item de flex
   tem piso automático de altura no conteúdo e o corpo empurra o painel para
   fora em vez de rolar. */
.dialog-fit__corpo {
  flex: 1 1 auto;
  min-block-size: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.dialog-fit__fixo {
  flex: none;
}
