/* =============================================================================
   SMART PEÇAS I9 — camada de AJUSTE (carregada DEPOIS do styles.css)

   Por que este arquivo existe: o `styles.css` do pacote é mantido BYTE A BYTE
   igual ao que o Samuel entregou. Assim, quando chegar uma versão nova do
   pacote, é só substituir o arquivo — nada de garimpar edições no meio dele.
   Todo ajuste que a integração exige mora AQUI, isolado e comentado.

   São dois tipos de ajuste, e só esses:

   (A) REGRAS PRESAS AO ELEMENTO <button>
       O pacote era uma SPA: a navegação era feita por <button> + JavaScript.
       Aqui a navegação é do SERVIDOR, então esses viraram <a> de verdade —
       melhor pro cliente (abre em nova aba, o navegador mostra o destino) e
       pro Google. Só que o CSS dele mira `button` como ELEMENTO, e regra assim
       simplesmente PARA DE VALER quando o elemento muda. Foi o que apagou a
       barra inferior no primeiro teste: os ícones ficaram deitados em vez de
       empilhados. As regras abaixo são CÓPIA FIEL das dele, com a linha de
       origem anotada — ao atualizar o pacote, conferir se mudaram.

   (B) COMPONENTES QUE A SPA NÃO PRECISAVA TER
       Paginação e chips de filtro ativo: o protótipo carregava 19 produtos de
       exemplo num array, então nunca precisou paginar. O catálogo real tem
       milhares e pagina de 24 em 24. Estilo usando os MESMOS tokens dele.
   ========================================================================== */

/* ─── (A) BARRA INFERIOR — espelha styles.css 584-585 e 823-837 ───────────── */
@media (max-width: 900px) {
  .mobile-nav a {
    position: relative; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 3px;
    color: #7c8ba5; background: transparent; font-size: 8px; font-weight: 700;
  }
  .mobile-nav a.active { color: var(--blue-700); }
}
@media (max-width: 640px) {
  .mobile-nav a { isolation: isolate; min-height: 52px; border-radius: 16px; }
  .mobile-nav a::before {
    content: ""; position: absolute; inset: 2px 7px; z-index: -1; opacity: 0;
    border-radius: 14px; background: #eaf4ff; transform: scale(.88);
    transition: .2s ease;
  }
  .mobile-nav a.active::before { opacity: 1; transform: scale(1); }
  .mobile-nav a.active { color: var(--blue-800); }
  .mobile-nav a.active svg { stroke-width: 2.4; }
  /* Alvo de toque de 46px do painel de filtros (styles.css 816) — o "Limpar"
     virou <a> e perderia a altura mínima. */
  .filter-sheet-actions a { min-height: 46px; display: grid; place-items: center; }
}

/* ─── (B) OPÇÕES DE FILTRO como LINK ──────────────────────────────────────
   No protótipo eram <input type="checkbox"> marcados por JavaScript. Aqui cada
   opção é um link que recarrega a lista filtrada pelo servidor — é assim que o
   Glivvo filtra hoje, e é o que faz o filtro sobreviver a um F5 e poder ser
   mandado por WhatsApp. Herda o desenho de `.filter-group label` (styles.css
   365) pra ficar idêntico ao painel dele. */
.filter-radio {
  padding: 6px 0; display: flex; align-items: center; gap: 8px;
  color: #5d6d89; font-size: 10px; text-decoration: none;
}
.filter-radio::before {
  content: ""; width: 13px; height: 13px; flex: none;
  border: 1.5px solid #c6d2e4; border-radius: 50%; background: white;
}
.filter-radio.is-on { color: var(--blue-800); font-weight: 700; }
.filter-radio.is-on::before {
  border-color: var(--blue-600);
  box-shadow: inset 0 0 0 3px var(--blue-600);
}
@media (max-width: 640px) { .filter-radio { padding: 10px 0; font-size: 12px; } }

/* ─── (B) CHIPS DO QUE ESTÁ FILTRADO ──────────────────────────────────────
   Mostram o que está aplicado e permitem tirar um filtro por vez. Sem isso o
   cliente fica preso num resultado vazio sem entender por quê. */
.active-filters { display: flex; flex-wrap: wrap; gap: 8px; }
.active-filters:empty { display: none; }
.filter-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 10px; border-radius: 999px;
  background: #eaf4ff; color: var(--blue-800);
  font-size: 10px; font-weight: 700;
}
.filter-chip a {
  display: grid; place-items: center; width: 16px; height: 16px;
  border-radius: 50%; color: var(--blue-700); background: rgba(255,255,255,.75);
  font-size: 12px; line-height: 1; text-decoration: none;
}
.filter-chip a:hover { background: white; }

/* ─── (B) PAGINAÇÃO ───────────────────────────────────────────────────────── */
.catalog-pagination {
  margin-top: 26px; display: flex; flex-wrap: wrap; gap: 6px;
  align-items: center; justify-content: center;
}
.catalog-pagination a, .catalog-pagination span {
  min-width: 38px; height: 38px; padding: 0 10px;
  display: grid; place-items: center; border-radius: 11px;
  border: 1px solid #dce5f1; background: white;
  color: #5d6d89; font-size: 12px; font-weight: 700; text-decoration: none;
}
.catalog-pagination span { border-color: transparent; background: transparent; }
.catalog-pagination a:hover { border-color: var(--blue-600); color: var(--blue-700); }
.catalog-pagination a.is-atual {
  border-color: var(--blue-600); background: var(--blue-600); color: white;
}
/* Alvo de toque confortável no celular (WCAG 2.2 / checklist do pacote). */
@media (max-width: 640px) {
  .catalog-pagination a, .catalog-pagination span { min-width: 44px; height: 44px; }
}

/* ─── (B) SEÇÃO SEM PRODUTOS na home ──────────────────────────────────────
   O protótipo sempre tinha destaques e novidades. No sistema real a loja pode
   não ter nenhum produto marcado — e uma faixa vazia com título parece defeito.
   Aqui a seção inteira some (o template decide), e esta é a mensagem quando faz
   sentido explicar em vez de sumir. */
.section-empty {
  padding: 22px; border-radius: 14px;
  border: 1px dashed #dce5f1; background: #f7fafd;
  color: #7c8ba5; font-size: 11px; text-align: center;
}

/* ─── (B) PRODUTO SEM FOTO ────────────────────────────────────────────────
   Fica sobre o gradiente do próprio `.product-visual` (styles.css 305). */
.product-sem-foto {
  position: absolute; inset: 0; z-index: 1;
  display: grid; place-items: center;
  color: var(--blue-600); opacity: .28;
}
.product-sem-foto svg { width: 46px; height: 46px; }

/* ─── (B) BUSCA EM FUNIL ──────────────────────────────────────────────────
   O protótipo tinha 19 produtos de exemplo, então bastava listar. O catálogo
   real tem 4000 e o sistema já resolvia isso com um funil (marca -> tipo de
   peça -> refino). Estilizado com os tokens e o raio de canto dele, pra ficar
   parte do mesmo desenho. */
.cascade-picker { padding: 4px 0 10px; }
/* CENTRALIZADO (Samuel 30/07): com uma marca só, o card ficava encostado na
   esquerda e sobrava meia tela vazia — parecia defeito. Cabeçalho e cartões
   passam a nascer no meio. */
.cascade-head { margin-bottom: 26px; text-align: center; }
.cascade-head h1 { margin: 6px auto 4px; max-width: 20ch; }
.cascade-head p { color: #5d6d89; font-size: 12px; }
/* O "voltar" continua à esquerda: é navegação, não conteúdo — no meio ele
   competiria com o título. */
.cascade-head .back-link { display: block; width: fit-content; margin: 0 0 14px; text-align: left; }
.cascade-grid {
  display: grid; gap: 12px;
  /* ⚠️ O MÁXIMO do minmax tem de ser `1fr`, não um valor fixo. Com um máximo
     fixo o navegador usa ESSE número pra decidir quantas colunas cabem — com
     250px em 351px de tela deu UMA coluna só, e a etapa 2 (tipo de peça)
     passou a listar tudo empilhado. Com `1fr` a conta usa o mínimo e as
     colunas voltam a ficar lado a lado.
     A centralização de quando há POUCOS cartões (pedido do Samuel) é resolvida
     no próprio cartão, com largura máxima + margem automática — não na
     definição das colunas. */
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  justify-content: center;
}
.cascade-card {
  align-items: center; text-align: center;
  /* `width:100%` é obrigatório junto da margem automática: sozinha, ela faz o
     cartão ENCOLHER até o conteúdo (medido: 88px numa coluna de 351px) em vez
     de preencher a coluna e só então centralizar. */
  width: 100%; max-width: 250px; margin-inline: auto;
}
.cascade-card .cascade-card-icon { margin-inline: auto; }
.cascade-card {
  padding: 18px 16px; border-radius: 16px;
  border: 1px solid #dce5f1; background: white;
  display: flex; flex-direction: column; gap: 4px;
  text-decoration: none; transition: .18s ease;
}
.cascade-card:hover {
  border-color: var(--blue-600);
  transform: translateY(-2px);
  box-shadow: 0 14px 26px rgba(3,40,110,.10);
}
.cascade-card-icon {
  width: 38px; height: 38px; margin-bottom: 8px;
  display: grid; place-items: center; border-radius: 12px;
  color: var(--blue-700); background: #eaf4ff;
}
.cascade-card-icon svg { width: 18px; height: 18px; }
.cascade-card strong { color: var(--blue-900); font-size: 13px; line-height: 1.3; }
.cascade-card small { color: #7c8ba5; font-size: 11px; }
.cascade-footer { margin-top: 22px; display: flex; justify-content: center; }
.cascade-footer .outline-btn { display: inline-flex; align-items: center; gap: 8px; }
.cascade-footer .outline-btn svg { width: 15px; height: 15px; }
@media (max-width: 640px) {
  .cascade-grid { grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 10px; }
  .cascade-card { max-width: 220px; }
  .cascade-card { padding: 15px 13px; min-height: 108px; }
}

/* ─── BARRA INFERIOR MAIS FINA (pedido do Samuel, 30/07) ──────────────────
   Medido no site em produção (smartpecasi9.com.br, celular): a barra de lá
   tem 64px. A do pacote estava rendendo 70px — 6px a mais em TODA rolagem,
   comendo vitrine. Igualada aos 64px, mantendo o alvo de toque confortável
   (item de 52px) e a pílula do item ativo, que é do desenho dele. */
@media (max-width: 640px) {
  .mobile-nav { height: 64px; padding: 4px 14px calc(4px + env(safe-area-inset-bottom)); }
  .mobile-nav a { min-height: 48px; }
}

/* ─── "TROCAR UNIDADE" — pílula flutuante idêntica à de produção ──────────
   Estilo extraído de `_trocar_unidade.html` (o componente que roda hoje no
   site). Lá o estilo é inline; aqui vira classe porque este arquivo já é o
   lugar dos ajustes — mesmos valores, medidos no ar. */
#trocarUnidadeBtn {
  position: fixed; bottom: 88px; right: 16px; z-index: 9998;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-radius: 999px;
  background: rgba(15,23,42,.92); color: #fff;
  font-size: 12px; font-weight: 600; text-decoration: none;
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
  border: 1px solid rgba(255,255,255,.12);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  transition: transform .15s ease, background .15s ease;
}
#trocarUnidadeBtn:hover {
  transform: translateY(-2px); background: rgba(37,99,235,.95);
}
#trocarUnidadeBtn:focus-visible { outline: 3px solid rgba(37,99,235,.5); outline-offset: 2px; }
/* Sem barra inferior no desktop — desce, como no original. */
@media (min-width: 641px) { #trocarUnidadeBtn { bottom: 20px; } }

/* Com um modal na tela, a pílula sai da frente — SÓ no celular.
   Ela é `position: fixed` com z-index 9998 contra os 100 do modal, então
   flutuava POR CIMA dele e caía quase em cima do "Adicionar ao carrinho".
   Medido no domínio próprio (é lá que este botão existe — `trocar_unidade_
   visivel` exige grupo com 2+ lojas em domínio próprio): sobrepõe o botão de
   comprar em 375, 500 e 640px, e a partir de 721px o modal já não chega
   perto dela. Daí o corte em 720 — o mesmo breakpoint em que o modal deixa
   de ser de uma coluna.
   A classe vem do `abrirModal`/`closeOverlay` em app-glivvo.js e é genérica:
   vale para qualquer modal, e qualquer flutuante novo some pelo mesmo
   caminho sem JS adicional. */
@media (max-width: 720px) {
  body.modal-aberto #trocarUnidadeBtn { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   CELULAR: barra inferior SAIU, faixa de marcas ficou no lugar dela
   (decisão Samuel 30/07)

   ⚠️ TUDO aqui está dentro de @media (max-width: 900px). O DESKTOP não é
   tocado em nenhuma linha — a faixa segue no corpo da home, do mesmo tamanho
   e no mesmo lugar. Foi condição explícita do pedido.

   Reverter é simples: apagar este bloco e restaurar o <nav class="mobile-nav">
   no _sp2_rodape.html (o comentário lá marca o ponto).
   ══════════════════════════════════════════════════════════════════════════ */
/* A faixa fixa é EXCLUSIVA do celular. Sem esta linha ela também renderiza no
   desktop (em fluxo, no fim da página) e a home passa a mostrar as marcas DUAS
   vezes — o oposto de "no desktop não muda nada". Pego na medição. */
@media (min-width: 901px) { .brand-marquee--fixa { display: none; } }

@media (max-width: 900px) {
  /* A faixa do corpo da home dá lugar à fixa — senão o celular mostraria as
     marcas DUAS vezes na home. */
  .brand-marquee--fluxo { display: none; }

  .brand-marquee--fixa {
    position: fixed; inset-inline: 0; bottom: 0; z-index: 55;
    margin: 0; border-radius: 0;
    border: 0; border-top: 1px solid #dce5f1;
    background: rgba(255,255,255,.97);
    backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
    box-shadow: 0 -2px 16px rgba(3,40,110,.10);
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  /* 56px de faixa: mais fina que os 70px que ela tinha e na mesma pegada da
     barra que acabou de emagrecer (64px). As marcas encolhem junto, senão
     ficariam apertadas contra as bordas. */
  .brand-marquee--fixa .brand-marquee-group { height: 56px; padding: 0 20px; gap: 20px; }
  .brand-marquee--fixa .marquee-brand { width: 104px; height: 34px; }

  /* Folga pro conteúdo não terminar embaixo da faixa. */
  .main-content, .site-footer { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }

  /* A pílula sobe pra não cobrir as marcas. */
  /* ─── "Trocar unidade" no CELULAR (pedido do Samuel, 30/07) ──────────────
     Metade do tamanho e pílula escura -> retângulo de cantos arredondados,
     fundo gelo com a escrita em azul. Só no celular: no computador ele segue
     igual (a regra de 641px+ acima não é tocada).
     Como fica claro sobre fundo claro, ganha borda e sombra fraca — sem elas
     o botão se dissolveria na página em vez de ler como controle. */
  #trocarUnidadeBtn {
    bottom: calc(72px + env(safe-area-inset-bottom));
    /* +50% sobre a 1a reducao (Samuel achou pequeno demais): 101x21 -> ~150x31.
       Ainda bem menor que o original de 140x38 em area. */
    padding: 7px 13px;
    gap: 7px;
    border-radius: 11px;           /* cantos arredondados, não pílula */
    font-size: 11px;
    background: #eef2f8;           /* gelo */
    color: var(--blue-700);        /* escrita azul */
    border: 1px solid #d7e2f0;
    box-shadow: 0 3px 10px rgba(3,40,110,.10);
    backdrop-filter: none; -webkit-backdrop-filter: none;
  }
  #trocarUnidadeBtn svg { width: 13px; height: 13px; }
  #trocarUnidadeBtn:hover { background: #e3ebf7; color: var(--blue-800); }
}

/* Ícone ativo no topo, agora que ele carrega a navegação inteira no celular. */
.icon-btn[aria-current="page"] { color: var(--blue-700); background: #eaf4ff; }

/* ═══════════════════════════════════════════════════════════════════════════
   HEADER NO CELULAR — a folga que o pacote tinha e a integração perdeu
   A caixa de busca é a 2ª linha do grid do header (68px fixos) e VAZA 36px
   pra fora dele em <=980px. O pacote compensava com `padding-bottom:48px`
   (styles.css:572) e ANULAVA logo depois (styles.css:694), porque quem passava
   a segurar a folga era a `.mobile-context-bar` (margin-top:48px) — barra que
   esta integração NÃO renderiza (_sp2_header.html:45-48). Sem nenhum dos dois,
   o header sticky come os primeiros 36px do conteúdo das 5 telas; no carrinho
   isso cortava "Continuar comprando" pela metade. Devolve a compensação
   original do pacote — o desktop não é tocado.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 980px) {
  .site-header { padding-bottom: 48px; }
}

/* "Continuar comprando" — alvo de toque de verdade.
   styles.css:901 já pede `min-height:38px`, mas o link computa `display:inline`
   e caixa inline IGNORA min-height: o alvo real media 14px de altura (WCAG 2.2
   SC 2.5.8 pede 24px). Mesmo remédio já aplicado no `.cascade-head .back-link`
   (linha 145 deste arquivo). */
.checkout-view .back-link { display: inline-flex; align-items: center; }

/* ═══════════════════════════════════════════════════════════════════════════
   CARRINHO / FINALIZAÇÃO
   O <script> da tela foi mantido intacto (606 linhas de lógica de entrega,
   trava, arredondamento do PIX e retry). Ele injeta HTML com classes do design
   system do Glivvo, que NÃO existem no pacote — sem as regras abaixo, a tela
   de "pedido enviado" apareceria crua justamente no momento mais importante
   da compra. Vestidas com os tokens do pacote.
   ══════════════════════════════════════════════════════════════════════════ */

/* Entrega: a estrutura `input:checked + label` ficou (o script e o estado
   visual dependem dela); o visual é o `.delivery-option` dele (styles.css
   390-397), inclusive o anel pontilhado do selecionado. */
.entrega-input { position: absolute; opacity: 0; pointer-events: none; }
.entrega-input:checked + .delivery-option {
  border: 1px solid #2da6ff;
  background: linear-gradient(145deg,#eef8ff,#f9fcff);
  box-shadow: 0 0 0 4px rgba(10,117,255,.07);
}
.entrega-input:checked + .delivery-option::before {
  content: ""; position: absolute; inset: -4px;
  border: 2px dotted #42d8ff; border-radius: 19px; pointer-events: none;
}
.entrega-input:checked + .delivery-option i { opacity: 1; transform: scale(1); }
.delivery-option i { opacity: 0; transform: scale(.7); transition: .18s ease;
  display: grid; place-items: center; width: 24px; height: 24px;
  border-radius: 50%; color: #fff; background: var(--blue-600); }
.delivery-option i svg { width: 13px; height: 13px; }
.entrega-input:focus-visible + .delivery-option { outline: 3px solid rgba(37,99,235,.5); outline-offset: 2px; }
/* Grupo com escolha pendente: o mesmo destaque que a tela atual dá. */
#pickEntrega.is-required .delivery-option { border-color: var(--blue-600); }
.delivery-note[hidden] { display: none; }
.delivery-note.pick-req { margin-top: 14px; }

/* Itens do carrinho */
.ck-itens { margin-top: 18px; display: flex; flex-direction: column; gap: 12px; }
.ck-item { display: grid; grid-template-columns: 62px 1fr auto auto; gap: 12px;
  align-items: center; padding: 12px; border: 1px solid #e6edf6; border-radius: 14px; }
.ck-item-foto { width: 62px; height: 62px; display: grid; place-items: center;
  border-radius: 11px; background: #f4f7fb; overflow: hidden; color: var(--blue-600); }
.ck-item-foto img { width: 100%; height: 100%; object-fit: contain; }
.ck-item-foto svg { width: 24px; height: 24px; opacity: .35; }
.ck-item-info { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ck-item-info strong { font-size: 12px; line-height: 1.3; }
.ck-item-info small { color: var(--muted); font-size: 10px; }
.ck-item-remover { width: 30px; height: 30px; display: grid; place-items: center;
  border-radius: 9px; color: var(--muted); background: transparent; }
.ck-item-remover:hover { color: var(--red, #dc2626); background: #fdecec; }
.ck-item-remover svg { width: 13px; height: 13px; }

/* Botões de fechamento do pedido */
.primary-btn.full, .outline-btn.full { width: 100%; justify-content: center; }
/* Mais "gordinhos" que o padrao de 47px do pacote — pedido Samuel 2026-07-31. */
.ck-pix, .ck-enviar { margin-top: 14px; flex-wrap: wrap; min-height: 58px; padding: 12px 18px; }
/* Rotulos maiores que os 12px do pacote (pedido Samuel): Enviar +50%,
   PIX +30%. A sub-linha nao acompanha (.ck-sub tem font-size proprio);
   o valor no PIX (.ck-pix-val) acompanha de proposito. */
.ck-enviar { font-size: 18px; }
.ck-pix { font-size: 15.6px; }
.ck-pix-val { margin-left: auto; font-weight: 800; }
.ck-sub { flex-basis: 100%; margin-top: 4px; font-size: 9px; font-weight: 600; opacity: .8; }
/* O PIX continua exatamente como estava. */
.ck-pix:disabled { opacity: .55; cursor: not-allowed; }

/* ─── "Enviar pedido": os 3 estados ────────────────────────────────────────
   O <script> da tela já alterna `is-armed` / `just-armed` / `disabled`
   (atualizarGate, linhas 234-256) e o comentário dele cita "a cascata das 3
   regras no CSS" — só que essas regras moram no site.css do design system, que
   este modelo NÃO carrega (_sp2_head.html:28,32). Resultado medido: liberado e
   travado eram IDÊNTICOS (branco com letra azul), separados só por opacity.

   Por que o preenchimento usa o `--green` do PRÓPRIO modelo (#19c37d, o mesmo
   do check da entrega) e a letra é escura: texto BRANCO sobre esse verde dá
   2,30:1 e reprovaria a AA (12px bold é texto normal). Com a tinta --blue-950
   dá 7,10:1 (AAA) — e o verde fica MAIS aceso, não menos. Por isso o token não
   foi escurecido.
     armado ....... #19c37d + #021b54 = 7,10:1  (sub-linha em .8 = 4,99:1)
     borda ........ #129c64 sobre branco = 3,52:1  (SC 1.4.11, não-texto)
     hover ........ #129c64 + #021b54 = 4,63:1
     anel de foco . --blue-900 sobre branco = 13,9:1 · sobre o verde = 6,05:1
     travado ...... #4d5c75 sobre #f1f5fb = 6,19:1
   ────────────────────────────────────────────────────────────────────────── */

/* (1) LIBERADO — verde aceso. É o estado de REPOUSO de quem já pode enviar
   (a tela nasce assim: catalogo.py:1358-1362 pré-seleciona a entrega).
   Visual por decisão do Samuel (2026-07-31, 2ª rodada): verde FORTE (não neon)
   que faça a letra branca estourar, sem contorno nenhum, rótulo grosso.
   Bônus: branco sobre #0b8552 dá 4,7:1 — voltou a passar AA. */
.ck-enviar.is-armed {
  --green-cta: #0b8552;
  color: #fff;
  background: var(--green-cta);
  border: 1px solid transparent;
  font-weight: 800;
  box-shadow: 0 10px 24px rgba(11,133,82,.35);
}
.ck-enviar.is-armed:hover {
  background: #096e44;
  border-color: transparent;
  transform: translateY(-2px);
  box-shadow: 0 15px 30px rgba(11,133,82,.40);
}
/* O verde escurece no hover e leva a sub-linha em .8 pra 3,61:1 (reprova AA);
   em opacity cheia ela volta a 4,63:1. */
.ck-enviar.is-armed:hover .ck-sub { opacity: 1; }
.ck-enviar.is-armed:active {
  transform: translateY(0);
  box-shadow: 0 6px 14px rgba(25,195,125,.30);
}
/* O anel azul global (styles.css:680) sobre verde fica sujo e some; aqui o
   foco é sólido e afastado 3px, então ele encosta no branco do cartão. */
.ck-enviar.is-armed:focus-visible {
  outline: 3px solid var(--blue-900);
  outline-offset: 3px;
}

/* (2) ENVIANDO — armado E disabled ao mesmo tempo (_setBusy, linha ~296).
   Segue COLORIDO: `opacity` aqui derrubaria o contraste (o fundo clareia
   contra o cartão branco, a letra não acompanha), então o "atenuado" vem da
   perda do relevo e do cursor, não da transparência. VEM DEPOIS do :hover de
   propósito — mesma especificidade, quem está por último ganha. */
.ck-enviar.is-armed:disabled {
  opacity: 1;
  cursor: progress;
  transform: none;
  box-shadow: none;
  /* Repete o verde de propósito: com o ponteiro parado sobre o botão o
     `:hover` continua valendo durante o envio e escurecia o fundo,
     mudando a cara do estado. Fixando o mesmo verde do repouso, o "enviando"
     só perde o relevo. */
  background: var(--green-cta);
}

/* (3) TRAVADO — falta escolher a entrega. Bloco cinza em vez de outline
   apagado: "Escolha como quer receber pra liberar" é a INSTRUÇÃO da tela e
   precisa continuar legível (em opacity .55 ela caía pra 3,3:1). */
.ck-enviar:not(.is-armed) {
  color: #4d5c75;
  background: #f1f5fb;
  border-color: #d5e0ef;
  box-shadow: none;
  cursor: not-allowed;
}
.ck-enviar:not(.is-armed) .ck-sub { opacity: 1; }

/* Pulso da virada travado -> liberado. O JS põe e tira `just-armed` em 450ms;
   a animação dura o mesmo. Na prática só aparece na recuperação de erro de
   entrega (a tela nasce liberada) — e é justamente aí que o cliente precisa
   ver que destravou. Só no Enviar: o PIX não ganha animação nova. */
@keyframes ck-armar {
  0%   { transform: scale(1);     box-shadow: 0 0 0 0 rgba(25,195,125,.55); }
  55%  { transform: scale(1.015); box-shadow: 0 0 0 10px rgba(25,195,125,0); }
  100% { transform: scale(1);     box-shadow: 0 10px 24px rgba(25,195,125,.32); }
}
.ck-enviar.is-armed.just-armed { animation: ck-armar .45s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .ck-enviar.is-armed.just-armed { animation: none; }
}

/* ─── Estouro horizontal do celular: nome de entrega sem espaço ───────────
   "ENTREGA/TRAMANDAI/IMBE/CENTRO" — 29 caracteres inquebráveis, 12px bold.
   Nome com barra sem espaço é PADRÃO da casa e vai se repetir, então o fix vem
   em duas camadas: quebrar a palavra (caso concreto) e permitir que as colunas
   encolham abaixo do conteúdo (blindagem — `1fr` é `minmax(auto,1fr)`, que
   nunca encolhe). Medido antes: documento com 403px numa tela de 375, a página
   inteira rolando de lado. */
/* O conserto inteiro vive DENTRO do `@media` de propósito — e a razão não é
   óbvia. A assimetria bonita do desktop (261/353px a 1366) não está escrita em
   lugar nenhum: ela NASCE do nome inquebrável. `1fr` é `minmax(auto,1fr)`, e
   esse `auto` é o min-content — o card que carrega "ENTREGA/TRAMANDAI/IMBE/
   CENTRO" pede mais largura e ganha. Só que `overflow-wrap:anywhere` **derruba
   o min-content** (é exatamente nisso que ele difere de `break-word`), então
   aplicar o conserto globalmente apagava o pedido de largura e os dois cards
   viravam 307/307 — mexer na entrega do desktop, que o Samuel mandou não
   mexer. Escopado, acima de 980px nada meu se aplica e o pacote fica intacto.

   O piso de `12ch` é o que separa "quebrar palavra" de "empilhar letra por
   letra": com `minmax(0,...)` puro a coluna colapsa a zero e o `anywhere`
   obedece ao pé da letra. `min(100%, 12ch)` segura o piso onde cabe e desiste
   dele quando a tela é estreita demais. */
.checkout-layout { grid-template-columns: minmax(0,1fr) 360px; }
@media (max-width: 980px) {
  .delivery-option { grid-template-columns: 44px minmax(min(100%, 12ch), 1fr) 24px; }
  .delivery-option > div { min-width: 0; overflow-wrap: anywhere; }
  .delivery-options { grid-template-columns: minmax(0,1fr) minmax(0,1fr); }
  .checkout-layout { grid-template-columns: minmax(0,1fr); }
}
@media (max-width: 720px) { .delivery-options { grid-template-columns: minmax(0,1fr) } }

.summary-line--pix { color: var(--muted); font-size: 10px; }
.summary-line--pix[hidden] { display: none; }

/* Estado final injetado pelo script (pedido enviado / PIX / erro) */
.conta-fin-ok { padding: 26px 20px; text-align: center;
  border: 1px solid #dfe8f4; border-radius: 20px; background: white;
  box-shadow: var(--shadow-sm); display: flex; flex-direction: column;
  align-items: center; gap: 10px; }
.conta-fin-ok-titulo { font-size: 16px; font-weight: 800; color: var(--blue-900); }
.conta-fin-ok-msg { color: var(--muted); font-size: 11px; max-width: 44ch; }
/* FORA DA RODADA do par --site-primary-solid. Este .conta-fin-pix e o BESPOKE
   do carrinho smartpecas (nao o do carrinho.html compartilhado, que ja migrou):
   branco sobre --blue-600 (#0a75ff) da 4.20:1, reprova AA por pouco. O :hover
   logo abaixo ja cai pro --blue-700 (6.18:1), entao so o REPOUSO e que falha.
   Nao corrigido agora porque trocar o azul de repouso e decisao de marca. */
.conta-fin-pix { display: inline-flex; align-items: center; justify-content: center;
  gap: 8px; margin-top: 6px; padding: 13px 20px; border-radius: 13px;
  color: #fff; background: var(--blue-600); font-size: 12px; font-weight: 700;
  text-decoration: none; border: 0; cursor: pointer; }
.conta-fin-pix:hover { background: var(--blue-700); }
.conta-fin-pix:disabled { opacity: .6; cursor: wait; }
.conta-fin-voltar { color: var(--blue-700); font-size: 11px; font-weight: 700; text-decoration: none; }
@keyframes ck-pulse { 50% { transform: scale(1.03); } }
.conta-fin-pulse { animation: ck-pulse 1.6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .conta-fin-pulse { animation: none; } }
/* O pacote não carrega a fonte Material Icons: sem isto o nome do ícone
   apareceria escrito ("check_circle") no meio da tela de sucesso. */
.conta-fin-ok .material-icons-round { display: none; }

/* Item do carrinho no CELULAR — medido em 375px: a grade de 4 colunas deixava
   o nome com 69px e ele quebrava em OITO linhas, virando uma tira ilegível.
   Aqui vira duas fileiras: foto + nome em cima (nome com a largura toda),
   quantidade e remover embaixo. */
@media (max-width: 640px) {
  .ck-item {
    grid-template-columns: 56px 1fr auto;
    grid-template-areas: "foto info remover" "qtd qtd qtd";
    row-gap: 10px;
  }
  .ck-item-foto    { grid-area: foto; width: 56px; height: 56px; }
  .ck-item-info    { grid-area: info; }
  .ck-item-remover { grid-area: remover; align-self: start; }
  .ck-item .qty-control { grid-area: qtd; justify-self: start; }
  .ck-item-info strong { font-size: 13px; }
  /* Entrega em coluna única: dois cards de 104px lado a lado ficam estreitos
     demais pro nome da opção. */
  .delivery-options { grid-template-columns: minmax(0,1fr); }
}

/* O MESMO remédio acima, na faixa do tablet-paisagem — mesmo sintoma, causa
   diferente. Aqui a tela é larga, mas a coluna do meio não: a sidebar de 250px
   e o resumo de 360px são fixos, então entre 981 e 1080px sobram ~241px por
   card. A fileira única precisaria de 320px (62 foto + 102 a maior palavra do
   nome + 90 quantidade + 30 remover + 36 de gaps), e os 79px que faltam não
   existem — daí o nome cair para UMA PALAVRA POR LINHA (12 linhas, cartão de
   266px a 981px). Dar piso à coluna de texto não resolve: o `minmax` não cria
   espaço, só escolhe quem é esmagado, e o esmagado vira o `+/−` (medido: 90px
   -> 2px, com o botão remover vazando 40px do cartão). Descer a quantidade
   para a segunda fileira devolve 102px ao nome e é o único arranjo que cabe.
   Abaixo de 981px o layout já é de coluna única e o cartão tem folga; acima de
   1080px a fileira única volta a caber — por isso a faixa é fechada dos dois
   lados. */
@media (min-width: 981px) and (max-width: 1080px) {
  .ck-item {
    grid-template-columns: 62px 1fr auto;
    grid-template-areas: "foto info remover" "qtd qtd qtd";
    row-gap: 10px;
  }
  .ck-item-foto    { grid-area: foto; }
  .ck-item-info    { grid-area: info; }
  .ck-item-remover { grid-area: remover; align-self: start; }
  .ck-item .qty-control { grid-area: qtd; justify-self: start; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ENTRADA (login por token) — PÁGINA, não modal
   O pacote tinha o login dentro de um modal. Aqui `/login` é rota de verdade,
   então o miolo dele (`login-mark`, `token-field`, botões) foi reaproveitado
   classe por classe e só o invólucro virou página centrada. Estas regras
   neutralizam o que vinha do `.modal` (posição fixa, duas colunas, invisível
   até receber `.active`) — sem elas a tela abriria em branco.
   ══════════════════════════════════════════════════════════════════════════ */
.sp2-entrada-body { min-height: 100dvh; background: var(--bg, #f4f7fc); }
.sp2-entrada {
  min-height: 100dvh; padding: 32px 16px calc(32px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 22px;
}
.sp2-entrada-marca img { width: 190px; max-width: 62vw; height: auto; }
/* Anula o `.modal`: estático, uma coluna, visível. */
.sp2-entrada-card {
  position: static; top: auto; left: auto; transform: none;
  opacity: 1; visibility: visible; max-height: none; overflow: visible;
  width: min(430px, 100%);
  box-shadow: 0 24px 60px rgba(1,18,52,.10);
}
.sp2-entrada-card form { text-align: left; }
.sp2-entrada-erro {
  display: flex; align-items: center; gap: 9px; margin-bottom: 14px;
  padding: 11px 13px; border-radius: 12px; text-align: left;
  border: 1px solid #f4c7c7; background: #fdecec; color: #a52020;
  font-size: 11px; font-weight: 600;
}
.sp2-entrada-erro svg { width: 14px; height: 14px; flex: none; }
.sp2-entrada-voltar {
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--muted); font-size: 11px; font-weight: 700; text-decoration: none;
}
.sp2-entrada-voltar:hover { color: var(--blue-700); }
.sp2-entrada-voltar svg { width: 14px; height: 14px; transform: rotate(180deg); }
@media (max-width: 640px) {
  .sp2-entrada { justify-content: flex-start; padding-top: 26px; }
  .sp2-entrada-marca img { width: 165px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MEUS PEDIDOS — o que o pacote não trazia
   `account-hero`, `account-tabs`, `order-card` e `status-pill` são DELE. O
   protótipo mostrava um pedido de exemplo com 1 produto; a lista real tem
   itens, código, quantidade e as ações de PIX/repetir. Estas regras cobrem só
   esse miolo, com os tokens dele.
   ══════════════════════════════════════════════════════════════════════════ */
/* ─── CARTÃO DE PEDIDO — acordeão, igual à aba "Em aberto" ─────────────────
   Pedido do Samuel (30/07): "usar esse mesmo padrão em Meus Pedidos". Copiada
   a mecânica do /conta, inclusive o truque de abrir com
   `grid-template-rows: 0fr -> 1fr` — anima de verdade sem `max-height`
   chutado, que ou corta a lista ou deixa um vão embaixo.
   O cartão inteiro é o botão; as ações (PIX/repetir) ficam FORA dele. */
.order-toggle {
  display: block; width: 100%; padding: 0; margin: 0; text-align: left;
  background: none; border: 0; cursor: pointer; color: inherit;
  font-family: inherit; font-size: inherit;
  -webkit-tap-highlight-color: transparent;
}
.order-toggle:focus-visible { outline: 3px solid rgba(37,99,235,.5); outline-offset: 4px; border-radius: 10px; }
.order-card-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.order-num  { font-size: 14px; font-weight: 800; color: var(--blue-900); }
.order-num small { color: var(--muted); font-size: 11px; font-weight: 500; }
.order-valor {
  margin-top: 6px; display: flex; align-items: center; justify-content: space-between;
  gap: 8px; font-size: 18px; font-weight: 800; color: var(--blue-900);
}
.order-chev { flex: none; width: 18px; height: 18px; color: var(--muted);
  transform: rotate(90deg); transition: transform 160ms ease; }
.order-card.is-aberto .order-chev { transform: rotate(-90deg); }
/* Resumo numa linha; some quando abre, pra não repetir a lista logo abaixo. */
.order-teaser {
  display: block; margin-top: 6px; color: var(--muted); font-size: 11px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.order-card.is-aberto .order-teaser { display: none; }
.order-sub {
  display: grid; grid-template-rows: 0fr; visibility: hidden;
  transition: grid-template-rows 160ms ease, visibility 160ms;
}
.order-card.is-aberto .order-sub { grid-template-rows: 1fr; visibility: visible; }
.order-sub > div { overflow: hidden; min-height: 0; }
.order-lista { margin-top: 10px; border-top: 1px solid #eef3fa; }
.order-it {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid #f3f7fc; font-size: 12px;
}
.order-it:last-child { border-bottom: 0; }
.order-it-nome { flex: 1; min-width: 0; font-weight: 700; color: var(--ink); }
.order-it-nome small { display: block; color: var(--muted); font-size: 10px; font-weight: 500; }
.order-it-valor { white-space: nowrap; font-weight: 800; color: var(--blue-900); }

.order-card-footer { margin-top: 12px; flex-wrap: wrap; gap: 12px; align-items: center; }
.order-total-rot { display: block; color: var(--muted); font-size: 9px; font-weight: 700; }
.order-total-val { font-size: 17px; font-weight: 800; color: var(--blue-900); }
.order-acoes { margin-left: auto; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.order-btn { padding: 10px 15px; font-size: 11px; border-radius: 11px; }
.order-btn svg { width: 14px; height: 14px; }
.order-btn--solo { min-width: 176px; justify-content: center; }
.order-aviso {
  flex-basis: 100%; padding: 9px 11px; border-radius: 10px;
  border: 1px solid #f6dfae; background: #fdf6e7; color: #7a5b12;
  font-size: 10px; font-weight: 600; line-height: 1.5;
}

/* O pacote tem só duas pílulas (`delivered` verde e `processing` âmbar). Os
   status reais do sistema são oito, então mapeamos por `data-status` — e o
   DEFAULT já nasce neutro, para que um status novo apareça em cinza em vez de
   invisível. Mesma escolha da tela atual, que documenta ter sido mordida duas
   vezes por cor faltando. */
.status-pill { background: #eef2f8; color: #5d6d89; }
.status-pill[data-status="entregue"],
.status-pill[data-status="pago"],
.status-pill[data-status="expedicao_concluida"],
.status-pill[data-status="fiado"],
.status-pill[data-status="aberto"]            { background: #e7f8ee; color: #146c3a; }
.status-pill[data-status="pendente_pix"],
.status-pill[data-status="pendente_aprovacao"]{ background: #fdf3e0; color: #8a5a10; }
.status-pill[data-status="cancelado"]         { background: #fdecec; color: #a52020; }

/* Abas da conta: no desenho eram <button> trocados por JS; aqui são links pras
   rotas reais, então precisam do reset de link. */
.account-tab { text-decoration: none; }
.account-tabs { flex-wrap: wrap; }

@media (max-width: 640px) {
  .order-card-top { flex-wrap: wrap; gap: 8px; }
  .order-acoes { margin-left: 0; width: 100%; }
  .order-btn { flex: 1 1 auto; justify-content: center; }
  .order-item { grid-template-columns: 1fr auto; row-gap: 2px; }
  .order-item-preco { grid-column: 2; }
}

/* ─── CHIPS DO FILTRO ESCONDIDAS SOB A BUSCA (Samuel, 30/07) ──────────────
   A barra de busca TRANSBORDA abaixo do header por design: medido em 375px,
   o header acaba em 148 e a busca vai até 184 — 36px pendurados, pintados
   por cima do conteúdo porque o header tem z-index 50.

   Na navegação normal quem apara esse encosto é a barra de topo do catálogo
   (162→362): os primeiros 22px dela ficam por baixo, mas ali só há respiro.
   Ao escondê-la no resultado de busca, as chips (162→190) passaram a nascer
   INTEIRAS debaixo da busca — apareciam só os 6px de baixo, que era o
   "aparece só os redondinhos".

   A regra só vale quando as chips são o primeiro elemento da tela, ou seja,
   exatamente no estado de busca. Na navegação normal a barra de topo continua
   sendo a primeira e nada muda. */
@media (max-width: 900px) {
  #products-view > .active-filters:first-child { margin-top: 32px; }
}

/* Aba "Sair" — destructiva, então vai à direita e em vermelho. */
.account-tab--sair { margin-left: auto; color: #a52020; }
.account-tab--sair:hover { background: #fdecec; color: #8a1a1a; }
@media (max-width: 640px) { .account-tab--sair { margin-left: 0; } }

/* ─── TOKEN: mostrar o que a pessoa colou (Samuel, 30/07) ─────────────────
   O pacote força `text-transform: uppercase` no campo (styles.css 547). É só
   aparência — o valor enviado nunca muda, e o login é insensível a caixa
   (conferido: minúsculo, MAIÚSCULO, com espaço e com caractere invisível, os
   quatro entram). Só que quem copia um token minúsculo e o vê VIRAR maiúsculo
   conclui que o campo estragou o que ele colou, apaga e tenta de novo. O
   campo passa a mostrar exatamente o que recebeu. */
.token-field input { text-transform: none; }

/* ─── MARCA AO LADO DO CÓDIGO (Samuel, 30/07) ─────────────────────────────
   Substitui o selo que ficava no topo do card. O logo vem SEM a assinatura de
   baixo (arquivo `-simbolo.svg`): em 18px a assinatura vira borrão.
   `max-width` além da altura porque as três marcas têm proporções bem
   diferentes — WEFIX e WeKeep são faixas largas, NN é quase quadrado; sem o
   limite de largura a WeKeep empurraria o código. */
.product-code-linha {
  /* Mesmo padrão do celular (Samuel aprovou lá e pediu igual aqui): o logo tem
     FILEIRA PRÓPRIA centralizada, logo abaixo da foto, e o código vem embaixo.
     `column-reverse` porque no HTML o código vem primeiro (ordem de leitura e
     de acessibilidade), mas visualmente o logo é que encosta na foto. */
  display: flex; flex-direction: column-reverse;
  align-items: center; gap: 6px;
  min-height: 18px;
}
.product-marca-logo {
  /* Desktop tem 237px úteis contra ~137 do celular — o logo cresce junto.
     A ALTURA é quem manda; `max-width` só existe como trava de segurança para
     uma marca futura de proporção extrema, e é folgada de propósito: apertada,
     ela vira o teto e achata o logo (foi o que aconteceu com o WeKeep). */
  height: 24px; max-width: 170px; width: auto;
  object-fit: contain; flex: none;
  opacity: 1;
}
/* NN é quase quadrado (0,91:1); os outros são faixas de ~5:1. Na mesma altura
   ele ocupa um quinto da área — por isso ganha mais. */
.product-marca-logo[src*="nn-simbolo"] { height: 30px; max-width: 34px; }
/* Marca apontada para o próprio nome (ex.: "Original China"): retângulo de
   cantos arredondados, no mesmo peso visual do logo. */
.product-marca-tag {
  /* DISCRETA de propósito (Samuel, 30/07): quem tem logo é marca e merece
     destaque; a etiqueta escrita é para peça genérica, e não deve competir com
     as marcas na mesma vitrine. Cinza neutro no lugar do azul, peso menor,
     sem preenchimento. O TAMANHO ele aprovou — só a ênfase caiu. */
  flex: none; padding: 2px 7px; border-radius: 7px;
  border: 1px solid #dfe5ee; background: transparent;
  color: var(--muted); font-size: 8px; font-weight: 700;
  letter-spacing: .02em; white-space: nowrap;
  text-transform: uppercase;
}
@media (max-width: 640px) {
  /* ─── Logo da marca no celular (Samuel, 30/07) ─────────────────────────
     Sai da linha do código e ganha uma FILEIRA PRÓPRIA, centralizada, logo
     abaixo da foto — foi o que ele pediu ("mais no meio, bem embaixo da foto").
     Não é só estética: ao lado do código o logo tinha ~90px de teto, e o
     WeKeep (faixa de 5,9:1) batia nesse teto e renderizava a 15px em vez dos
     17 — parecia menor que o WEFIX sem motivo. Numa fileira só pra ele, a
     ALTURA passa a mandar e os três ficam com o mesmo peso. */
  .product-code-linha {
    flex-direction: column-reverse;   /* logo em cima, código embaixo */
    align-items: center;
    gap: 5px;
  }
  .product-marca-logo { height: 20px; max-width: 128px; opacity: 1; }
  /* O NN é quase quadrado (0,91:1); os outros são faixas de ~5:1. Na mesma
     altura ele ocupa um quinto da área — daí os +30% que o Samuel notou.
     Casado pelo ARQUIVO: o nome da marca varia por empresa (é o que o
     apontamento resolve), o arquivo não. */
  .product-marca-logo[src*="nn-simbolo"] { height: 26px; max-width: 30px; }
  .product-marca-tag { font-size: 7px; padding: 2px 6px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   BLOCO CARROSSEL — copiar íntegro ao derivar um template novo
   ═══════════════════════════════════════════════════════════════════════════
   Anda junto com a macro `catalogo/_carrossel_macros.html` e com o bloco de
   mesmo nome em site/app-glivvo.js. Os três formam uma peça só.

   A REGRA DE OURO é a altura: quem manda nela é o slide do hero padrão, SEMPRE.
   Os slides de imagem entram com a imagem em `position:absolute`, então não
   contribuem altura nenhuma — em flex row eles apenas se esticam até a altura
   do slide mais alto, que é o do hero. Consequência prática: trocar de banner
   NÃO empurra a página, independentemente da proporção da imagem que o lojista
   subir (16:9, faixa larga, quadrado — tanto faz). Sem isso, cada troca de
   slide mudaria a altura do topo e a página inteira daria um solavanco.
   ═══════════════════════════════════════════════════════════════════════════ */
/* `touch-action: pan-y` — a loja é mobile-first e o swipe é a navegação
   principal no celular. Sem isto o navegador pode assumir o gesto horizontal
   como rolagem/navegação e disparar `pointercancel` no meio, e o deslize que
   termina fora do carrossel (comum: o dedo sai pela borda) simplesmente se
   perdia. `pan-y` cede o eixo vertical ao navegador — rolar a página com o
   dedo sobre o banner continua funcionando — e reserva o horizontal pro
   carrossel. */
.gcar { position: relative; overflow: hidden; border-radius: 28px;
        touch-action: pan-y; }

/* ─── O QUADRO PASSA A SER 16:9 NO COMPUTADOR ───────────────────────────────
   O Samuel mandou uma arte em 1600×900 — exatamente o que a tela recomenda — e
   o topo saiu cortado. Medido, o motivo: o quadro NUNCA foi 16:9. A altura vinha
   fixa do `.hero` (min-height 520px) e a largura é fluida, então dava:

       viewport 1280 → 972×520  (1,87:1) → corta  27px  (4,9%)
       viewport 1366 → 1058×520 (2,04:1) → corta  75px  (12,6%)
       viewport ≥1600 → 1232×520 (2,37:1) → corta 173px (25%)

   Com `cover` num quadro mais largo que a arte, o que sobra é altura — e sai
   pelo topo e pela base. Um quarto da arte sumia na tela do escritório.

   `aspect-ratio` inverte a dependência: a altura passa a sair da LARGURA, e o
   quadro fica 16:9 em qualquer tela. A recomendação "1600×900" deixa de ser
   aproximação e vira verdade — zero corte.

   O `min-height` é o piso: abaixo de ~925px de largura útil o 16:9 daria um
   quadro mais baixo que o hero original, e o hero ficaria espremido. Ali o piso
   vence e o quadro deixa de ser 16:9 (fica mais alto que largo em proporção),
   o que traz de volta um corte pequeno — contra os 25% de antes.

   ⚠️ `max-width: 100%` NÃO é redundante com o bloco pai, e a falta dele foi um
   defeito EM PRODUÇÃO (corrigido em 01/08). `aspect-ratio` liga as duas
   dimensões nos DOIS sentidos: quando a altura derivada da largura ficaria
   abaixo do piso, o navegador passa a derivar a LARGURA da altura e o quadro
   trava em 924px (520 × 16/9) — largura FIXA, que estoura o container e põe
   BARRA DE ROLAGEM HORIZONTAL na home. Medido antes do conserto, com o `.gcar`
   em 924px dentro do pai: 721px de tela sobrava 219px de página; 1024px
   sobrava 156px. Faixa afetada: 721 a ~1180px — iPad em pé e deitado, e
   notebook pequeno. Com o `max-width` a largura volta a ser a do container e o
   piso age só na altura (768px: 924 → 736, sem barra); de 1180px pra cima nada
   muda, porque ali o 16:9 já dá altura acima do piso.
   Mesma armadilha, em sentido oposto, do `.product-modal-media` (lá um
   `max-height` ENCOLHIA a largura) — regra prática: `aspect-ratio` junto com
   qualquer limite de altura pede um limite de largura explícito.

   ⚠️ SÓ NO COMPUTADOR. No celular o quadro é retrato (0,78:1) e já está
   aprovado com a arte 4:5; o `aspect-ratio` aqui o quebraria.

   De quebra some o CLS: a altura é conhecida antes de a imagem carregar. */
@media (min-width: 721px) {
  .gcar { aspect-ratio: 16 / 9; min-height: 520px; max-width: 100%; }
  /* O hero deixa de DITAR a altura e passa a PREENCHER o quadro — senão sobra
     faixa de gradiente embaixo dele nas telas largas. */
  .gcar-track, .gcar-slide, .gcar-slide > .hero { height: 100%; }
}
.gcar-track {
  display: flex;
  transition: transform .55s cubic-bezier(.22, .61, .36, 1);
  will-change: transform;
}
/* `min-width` além do `flex-basis`: sem ele, um slide com conteúdo largo
   (o hero tem logo de 620px) estoura os 100% e dois slides aparecem juntos. */
.gcar-slide { position: relative; flex: 0 0 100%; min-width: 100%; }

/* ─── Slide fora de vista sai do alcance de verdade ─────────────────────────
   `aria-hidden` esconderia do leitor de tela mas deixaria o conteúdo TABULÁVEL:
   o hero fora de vista mantinha o botão "Falar no WhatsApp" no fluxo de foco, e
   o visitante de teclado parava num botão que não conseguia ver.
   `visibility:hidden` tira da tabulação E do leitor de tela de uma vez.

   ⚠️ O atributo `data-gcar-ativo` do PRIMEIRO slide vem do HTML servido (macro),
   não do JS: como o app-glivvo.js é `defer`, marcar só no JS deixaria todos os
   slides invisíveis no primeiro paint — o hero é o elemento LCP.

   ⚠️ `visibility` — e não `display:none` — porque ele PRESERVA O LAYOUT. É o que
   mantém o invariante de altura: o slide do hero continua ocupando espaço mesmo
   escondido, então a altura do carrossel segue sendo a dele, sempre.

   O atraso de .55s (a duração do transform) é o que preserva a animação: o slide
   que ENTRA fica visível na hora, o que SAI só some depois de terminar de
   deslizar. Sem isso, a troca seria um corte seco. */
.gcar-slide { visibility: hidden; transition: visibility 0s linear .55s; }
.gcar-slide[data-gcar-ativo] { visibility: visible; transition-delay: 0s; }
/* O hero traz o próprio `border-radius`; o wrapper já recorta, então zerar
   aqui evita o canto duplo (um arredondado dentro do outro). */
.gcar-slide > .hero { border-radius: 0; }
.gcar-img { position: absolute; inset: 0; width: 100%; height: 100%;
            object-fit: cover; display: block;
            /* Imagem é arrastável por padrão. Arrastar o banner tirava o
               `pointerup` do carrossel (o gesto termina no lugar onde o
               fantasma foi solto) e ainda mostra uma miniatura fantasma no
               meio de um swipe. `draggable="false"` no HTML cobre o resto. */
            -webkit-user-drag: none; user-select: none; }
/* `<picture>` é inline por padrão e geraria uma caixa vazia entre o slide e a
   imagem absoluta. `display:contents` tira o wrapper da árvore de layout e o
   `.gcar-img` segue posicionado pelo `.gcar-slide`. Vale nos dois tamanhos. */
.gcar-slide picture { display: contents; }
/* ⚠️ DUPLICAÇÃO CONSCIENTE — gradiente COPIADO de `.hero` (styles.css:231-233).
   É o fundo que aparece atrás da imagem quando ela não preenche o quadro no
   celular; sem ele a letterbox ficaria branca e pareceria erro de carregamento.

   Não dá pra referenciar o original: `styles.css` é o pacote do Samuel e é
   INTOCÁVEL byte a byte, então não há como extrair o gradiente pra uma custom
   property compartilhada. Consequência aceita: se um dia o `.hero` mudar de
   cor no styles.css, ESTA regra não acompanha e a letterbox fica destoando do
   hero. Quem mexer no gradiente do `.hero` tem que vir aqui também. */
.gcar-slide--imagem {
  background:
    radial-gradient(circle at 78% 18%, rgba(35,169,255,.42), transparent 30%),
    linear-gradient(135deg, #021c59 0%, #0644b7 68%, #0b72ef 100%);
}

/* ─── Link e botao do banner (05/09) ────────────────────────────────────────
   O slide inteiro vira `<a>` quando o lojista escolhe um destino, e o botao e
   um `<span>` DENTRO dele. Sem destino nada disto existe no HTML.

   O `<a>` e a CAMADA do slide: `position:absolute; inset:0` mantem o
   `.gcar-img` (que tambem e `absolute; inset:0`) posicionado, porque o `<a>`
   passa a ser o ancestral posicionado mais proximo. Sem isto a imagem
   escaparia para o `.gcar-slide` e o link nao cobriria a arte. */
.gcar-link { position: absolute; inset: 0; display: block;
             text-decoration: none; color: inherit; }
/* `outline-offset` NEGATIVO de proposito: o `.gcar` recorta com
   `overflow:hidden`, entao um anel para fora seria cortado. Branco porque o
   fundo e a arte do lojista — cor de marca perderia contraste sobre arte
   clara, e este e o unico indicador de orientacao de quem navega por teclado. */
.gcar-link:focus-visible { outline: 3px solid #ffffff; outline-offset: -5px; }

/* O BOTAO — semantica de `background-position`, e ela e o que evita transbordo.
     x=0   -> translate(0)      : borda esquerda do botao na borda do slide
     x=50  -> translate(-50%)   : centro no centro
     x=100 -> translate(-100%)  : borda direita na borda direita
   Resultado: para QUALQUER x em [0,100] o botao fica DENTRO do slide, por
   construcao. Com `translate(-50%,-50%)` fixo ele transbordaria nos extremos, e
   a correcao viraria um clamp arbitrario que o editor teria de repetir — duas
   copias da mesma regra, que e a bug-class deste repo.

   ⚠️ A COR e literal AQUI de proposito. Nos 5 modelos GERADOS o
   `tokens_engine` a quantiza em `var(--g-b-700)`/`var(--g-white)` na
   regeracao, entao o botao herda a identidade da loja e acompanha o slider de
   cor. No `smartpecas_i9`, que e CSS AUTORAL sem `origem` declarada
   (`tokens.suporta` = False), NAO existe nenhum `--g-*`: escrever `var()` la
   deixaria o `background` invalido em computed-value time e o botao sairia SEM
   FUNDO sobre a arte — texto branco sobre foto, ilegivel.

   ⚠️ `#0858d8` e nao `#0975ff` (o b-600): medido, b-600 com branco da 4,20:1 e
   REPROVA AA para texto normal — e justamente a unica entrada aberta em
   `EXCECOES_AA`. O b-700 da 6,18:1 e nao encosta naquela excecao.

   ⚠️ Contraste sobre FOTO nao e calculavel estaticamente: a arte e do lojista e
   muda sem deploy. A mitigacao e de DESENHO, nao de medicao — fundo SOLIDO
   (nunca translucido) e CONTORNO DUPLO (linha clara + linha escura adjacentes),
   que sobrevive a fundo claro e a fundo escuro sem depender de escolher um tom
   e torcer. */
.gcar-botao {
  position: absolute; z-index: 2;
  left: calc(var(--gcar-bx, 50) * 1%);
  top:  calc(var(--gcar-by, 50) * 1%);
  transform: translate(calc(var(--gcar-bx, 50) * -1%),
                       calc(var(--gcar-by, 50) * -1%));
  display: inline-flex; align-items: center; justify-content: center;
  /* WCAG 2.2 SC 2.5.8 pede 24px; 44 e o alvo confortavel de dedo. Vira altura
     REAL quando o texto quebra em duas linhas no celular. */
  min-height: 44px; padding: 10px 22px;
  /* ⚠️⚠️ `width: max-content` E O QUE MANTEM O BOTAO DO MESMO TAMANHO EM TODO
     x — sem ele o gesto que o Samuel pediu ("arrastar pra um lado e pro outro")
     ENTREGA UM BOTAO QUEBRADO na metade direita do curso.

     Por que: um elemento `position:absolute` com `left` definido, `right:auto`
     e `width:auto` e dimensionado por SHRINK-TO-FIT contra o espaco que sobra,
     ou seja `largura do slide - left`. Quanto mais a direita, menos espaco, e
     a pilula colapsa. MEDIDO (achado B-1 do Time 13, reproduzido aqui):

        sem `max-content`, celular:  x=0 -> 141px   x=100 -> 93px
        sem `max-content`, desktop:  x=0 -> 164px   x=100 -> 109px
        com `max-content`:           IGUAL em x = 0, 25, 50, 75, 88 e 100

     `max-content` e nao `right:0 + margin:auto`: com os dois lados presos o
     elemento fica sobre-restringido e a semantica de `background-position` do
     `transform` abaixo — que e o que garante o nao-transbordo — deixaria de
     valer. Aqui a largura vira INTRINSECA e o `transform` continua sendo a
     unica coisa que posiciona.

     ⚠️ O `max-width` continua mandando, e e ele que faz o texto longo quebrar:
     porcentagem de `max-width` resolve contra o SLIDE (estavel), nunca contra
     o espaco restante — entao a quebra acontece do MESMO jeito em todo x. Nao
     usar `white-space: nowrap` aqui: com texto acima do teto ele transbordaria
     em vez de quebrar. */
  width: max-content;
  max-width: min(78%, 420px);
  border-radius: 999px;
  font: 700 15px/1.2 inherit; text-align: center;
  background: #0858d8; color: #ffffff;
  box-shadow: 0 0 0 2px rgba(255,255,255,.92), 0 0 0 3.5px rgba(0,0,0,.32),
              0 6px 20px rgba(0,0,0,.28);
  transition: box-shadow .18s ease;
}
.gcar-link:hover .gcar-botao {
  box-shadow: 0 0 0 2px rgba(255,255,255,1), 0 0 0 3.5px rgba(0,0,0,.4),
              0 10px 26px rgba(0,0,0,.34);
}
/* ⚠️ `no-preference`, e NAO `reduce`. O `transform` ja carrega a POSICAO: se o
   hover animado morasse no ramo `reduce` como "sem scale", a translacao teria
   de ser repetida em dois lugares e um refactor os separaria. Assim o padrao e
   SEM movimento e o movimento e a excecao declarada — que e a leitura correta
   da preferencia, e a mesma decisao que o carrossel ja tomou (`animacaoOk`). */
@media (prefers-reduced-motion: no-preference) {
  .gcar-botao { transition: transform .18s ease, box-shadow .18s ease; }
  .gcar-link:hover .gcar-botao {
    transform: translate(calc(var(--gcar-bx, 50) * -1%),
                         calc(var(--gcar-by, 50) * -1%)) scale(1.03);
  }
}
/* No celular a arte e mais alta e o botao concorre com os dots da barra
   inferior (`z-index:3`, `bottom:14px`). Encolher o botao mantem o alvo de
   44px e tira a disputa. */
@media (max-width: 720px) {
  .gcar-botao { font-size: 14px; padding: 9px 18px; max-width: 84%; }
}

/* ─── Setas ─────────────────────────────────────────────────────────────────
   Só no desktop: no celular a navegação é por deslize e por dots, e uma seta
   sobre a imagem cobriria justamente o que o lojista quer mostrar. */
.gcar-seta {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 44px; height: 44px; display: none; place-items: center;
  border: 1px solid rgba(255,255,255,.28); border-radius: 50%;
  background: rgba(2,20,70,.42); backdrop-filter: blur(6px);
  color: #fff; cursor: pointer; transition: background .2s, border-color .2s;
}
.gcar-seta svg { width: 22px; height: 22px; fill: none; stroke: currentColor;
                 stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.gcar-seta:hover { background: rgba(2,20,70,.72); border-color: rgba(255,255,255,.5); }
.gcar-seta:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
.gcar-seta:active { transform: translateY(-50%) scale(.94); }
.gcar-seta--prev { left: 16px; }
.gcar-seta--next { right: 16px; }
@media (min-width: 981px) { .gcar-seta { display: grid; } }

/* ─── Barra inferior: dots + pausa ──────────────────────────────────────── */
.gcar-barra {
  position: absolute; left: 0; right: 0; bottom: 14px; z-index: 3;
  display: flex; align-items: center; justify-content: center; gap: 14px;
}
.gcar-dots { display: flex; align-items: center; gap: 4px; }
/* O alvo de toque tem 24px (padding transparente); a bolinha VISÍVEL tem 9px.
   Dot de 9px seria alvo bom só pra quem mira com mouse. */
.gcar-dot {
  width: 24px; height: 24px; padding: 0; border: 0; background: none;
  cursor: pointer; display: grid; place-items: center; border-radius: 50%;
}
.gcar-dot::before {
  content: ""; width: 9px; height: 9px; border-radius: 50%;
  background: rgba(255,255,255,.42);
  box-shadow: 0 1px 3px rgba(0,20,70,.45);
  transition: background .25s, width .25s, border-radius .25s;
}
.gcar-dot[aria-current="true"]::before { background: #fff; width: 22px; border-radius: 5px; }
.gcar-dot:focus-visible { outline: 3px solid #fff; outline-offset: -2px; }

.gcar-pausa {
  width: 30px; height: 30px; display: grid; place-items: center; padding: 0;
  border: 1px solid rgba(255,255,255,.28); border-radius: 50%;
  background: rgba(2,20,70,.42); backdrop-filter: blur(6px);
  color: #fff; cursor: pointer; transition: background .2s;
}
.gcar-pausa:hover { background: rgba(2,20,70,.72); }
.gcar-pausa:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
.gcar-pausa:active { transform: scale(.94); }
.gcar-pausa svg { width: 15px; height: 15px; fill: currentColor; }
/* O ícone reflete o que o botão FAZ agora: rodando mostra "pausar", pausado
   mostra "retomar". `aria-pressed` no botão diz o mesmo pro leitor de tela. */
.gcar-pausa .gcar-icone-play { display: none; }
.gcar-pausa[aria-pressed="true"] .gcar-icone-pausa { display: none; }
.gcar-pausa[aria-pressed="true"] .gcar-icone-play { display: block; }

@media (max-width: 720px) {
  .gcar { border-radius: 21px; }          /* acompanha `.hero` no mesmo breakpoint */
  /* `contain` no celular: a tela é estreita e alta, e `cover` mostraria uma
     tira do meio de uma arte pensada em paisagem — some justamente o texto do
     banner. Com `contain` o banner aparece inteiro, sobre o gradiente do hero. */
  .gcar-img { object-fit: contain; }
  /* ...MENOS quando o lojista cadastrou arte PRÓPRIA de celular (retrato). Aí
     a imagem já foi feita para este quadro: 1080×1350 (4:5) contra um hero de
     ~0,8:1 — `cover` preenche de ponta a ponta com corte mínimo, e `contain`
     é que deixaria faixas feias em cima e embaixo de uma arte correta. Quem
     decide é a macro, pela classe: o CSS não precisa saber de JS. */
  .gcar-img--m { object-fit: cover; }
  .gcar-barra { bottom: 10px; }
}

/* ─── FAIXA INTERMEDIÁRIA (iPad e notebook pequeno) — `contain` também aqui ──
   Extensão da regra logo acima, pelo MESMO motivo: quando o quadro não é 16:9,
   `cover` corta a arte pelas laterais e leva junto o texto do banner.

   Esta faixa caiu no `cover` por tabela, não por decisão. De 721 a ~1180px o
   piso de 520px governa a altura e o quadro fica mais "quadrado" que 16:9 —
   medido: 25% da arte cortada em 721px, 20% em 768 e em 1024, 6% em 900, 3%
   em 1180. De 1181px pra cima o 16:9 já rende altura acima do piso, o quadro
   volta a bater com a arte e o `cover` (que preenche de ponta a ponta) segue
   sendo o certo.

   Por que não baixar o piso, que era a saída óbvia: o piso protege o HERO, e
   isso foi verificado na tela, não deduzido. Com piso 420 o botão "Falar no
   WhatsApp" do hero é CORTADO pela borda e a barra de dots cobre o selo
   "REPRESENTANTE AUTORIZADO", em 721, 768 e 1024. Placar das três saídas nesta
   faixa:
       hoje `cover` + piso 520 ....... banner corta 20-25% | hero íntegro
       piso 420 ...................... banner corta 0-8%   | hero QUEBRADO
       `contain` aqui + piso 520 ..... banner INTEIRO      | hero íntegro
   Altura uniforme entre slides é invariante, então trocar o piso por slide
   não é opção — o que muda é só como a arte se encaixa no quadro.

   As faixas que sobram em cima e embaixo caem no gradiente de
   `.gcar-slide--imagem` (o mesmo do hero), então leem como fundo, não como
   tarja preta. */
@media (min-width: 721px) and (max-width: 1180px) {
  .gcar-img { object-fit: contain; }
}

@media (prefers-reduced-motion: reduce) {
  /* O JS já não roda sozinho nesse modo; aqui some também o deslize da troca
     manual, que continua funcionando (só que instantânea). */
  .gcar-track { transition: none; }
  /* Sem deslize não há o que esperar: o slide que sai some na hora. Manter o
     atraso deixaria dois slides visíveis por meio segundo sem motivo. */
  .gcar-slide { transition-delay: 0s; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   BLOCO AMPLIAR FOTO — copiar íntegro ao derivar um template novo
   ═══════════════════════════════════════════════════════════════════════════
   O modelo Clássico amplia a foto do produto desde sempre (`openImg` em
   catalogo/_scripts.html); o pacote bespoke nunca portou, e clicar na foto não
   fazia nada. Anda junto com o markup `#foto-zoom` de `_sp2_rodape.html` e o
   bloco de mesmo nome em site/app-glivvo.js.

   ⚠️ NÃO fecha em clique no fundo — invariante do projeto. O Clássico fecha, e
   é justamente o que não se copia: no celular, ampliar e arrastar para ver o
   detalhe termina num toque no fundo, e a foto sumiria na cara do cliente.
   ═══════════════════════════════════════════════════════════════════════════ */
.foto-zoom {
  position: fixed; inset: 0; z-index: 12000;
  display: flex; align-items: center; justify-content: center;
  padding: 18px;
  background: rgba(2, 12, 34, .92);
  -webkit-tap-highlight-color: transparent;
}
.foto-zoom[hidden] { display: none; }
.foto-zoom img {
  max-width: 100%; max-height: 100%;
  object-fit: contain; border-radius: 14px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, .55);
  /* O pinch-zoom do celular precisa deste eixo livre; sem isto o navegador
     entrega o gesto para a página e a foto não amplia com dois dedos. */
  touch-action: pinch-zoom;
}
/* Alvo de 44px (WCAG 2.2 / checklist do pacote) e afastado do canto, para não
   competir com o gesto de voltar do sistema na borda da tela. */
.foto-zoom-fechar {
  position: absolute; top: 16px; right: 16px;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border: 1px solid rgba(255, 255, 255, .3); border-radius: 50%;
  background: rgba(255, 255, 255, .14); color: #fff;
  cursor: pointer; backdrop-filter: blur(6px);
  transition: background .2s, border-color .2s;
}
.foto-zoom-fechar:hover { background: rgba(255, 255, 255, .26); }
.foto-zoom-fechar:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
.foto-zoom-fechar svg { width: 20px; height: 20px; stroke: currentColor;
                        fill: none; stroke-width: 2.2; }

/* Sinal de que a foto AMPLIA. Sem isto nada convida ao clique — que é
   exatamente o que fez o defeito passar despercebido até virar site principal. */
.is-ampliavel { cursor: zoom-in; }
.is-ampliavel:focus-visible { outline: 3px solid var(--blue-600); outline-offset: 2px; }

/* ═══════════════════════════════════════════════════════════════════════════
   PREÇO SÓ PRA CLIENTE LOGADO — copiar íntegro ao derivar um template novo
   ═══════════════════════════════════════════════════════════════════════════
   O "****" ocupa a MESMA caixa do preço real (`.product-price`), então o card
   não muda de altura entre visitante e cliente logado — trocar de estado não
   pode reflowar a grade inteira. O valor não é escondido por CSS: ele nem
   chega ao HTML (ver a macro). Isto aqui é só a aparência. */
.product-price--oculto strong { letter-spacing: .16em; }
.product-price--oculto { cursor: help; }
/* CTA do modal — discreto, para não competir com "Adicionar ao carrinho". */
.modal-login-cta {
  display: inline-block; margin-top: 6px;
  color: var(--blue-700); font-size: 11px; font-weight: 700;
  text-decoration: underline; cursor: pointer;
}
.modal-login-cta:hover { color: var(--blue-800); }
.modal-price strong.is-oculto { letter-spacing: .16em; }

/* ═══════════════════════════════════════════════════════════════════════════
   BOTÃO "PEDIR TOKEN PELO WHATSAPP" — copiar íntegro ao derivar um template
   ═══════════════════════════════════════════════════════════════════════════
   O caminho de quem AINDA NÃO é cliente. Era um `.text-link` de 11px e sumia na
   tela: quem já tem token digita e entra, quem não tem precisa ACHAR isto.

   Segue o contrato do `.act-btn` do ERP (style.css ~1698), reescrito aqui com
   os tokens do MODELO — o site não carrega o style.css do sistema, então copiar
   o arquivo não é opção; copia-se o COMPORTAMENTO:
     · outline no resting que PREENCHE na cor no hover ("fills on hover")
     · `:focus-visible` com anel de 3px
     · `:active` pressionado (translateY .5px)
     · `:disabled` com opacity .5 — estado coberto mesmo sem uso hoje
     · ícone SVG inline + rótulo em texto + `title`; nunca emoji

   Variante WhatsApp (espírito do `.act-btn--wa`): o verde já aparece no
   resting (borda e ícone) porque este é o CTA de captação — presença sem
   competir com o "Entrar", que continua sendo o herói azul da tela.
   ═══════════════════════════════════════════════════════════════════════════ */
.sp-btn-wa {
  --sp-wa: #25d366;
  /* Verde ESCURO medido, não escolhido no olho: `#128c4a` sobre o fundo verde a
     8% dá 4,05:1 e REPROVA o AA (o limite é 4,5). Este dá 5,1:1. */
  --sp-wa-escuro: #0f7a40;
  width: 100%; margin-top: 10px;
  min-height: 47px; padding: 12px 18px;
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  border: 1.5px solid var(--sp-wa);
  border-radius: 13px;
  background: rgba(37, 211, 102, .08);
  /* Texto em verde ESCURO, não no verde puro: #25d366 sobre este fundo claro
     dá ~1,9:1 e reprovaria AA de longe. Ver o token acima para o valor medido. */
  color: var(--sp-wa-escuro);
  font-family: inherit; font-size: 12.5px; font-weight: 700; line-height: 1.25;
  text-align: center; text-decoration: none;
  transition: background .18s ease, border-color .18s ease, color .18s ease,
              box-shadow .18s ease, transform .12s ease;
}
.sp-btn-wa svg {
  width: 17px; height: 17px; flex-shrink: 0;
  fill: none; stroke: currentColor; stroke-width: 2;
}
.sp-btn-wa strong { font-weight: 800; }
/* PREENCHE na cor — o gesto característico do padrão. */
.sp-btn-wa:hover {
  background: var(--sp-wa); border-color: var(--sp-wa); color: #04321a;
  box-shadow: 0 10px 22px rgba(37, 211, 102, .28);
}
.sp-btn-wa:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(37, 211, 102, .38);
}
.sp-btn-wa:active { transform: translateY(.5px); }
.sp-btn-wa[disabled], .sp-btn-wa.is-disabled {
  opacity: .5; cursor: not-allowed; pointer-events: none;
}
/* Celular: alvo de toque ≥44px e o rótulo em duas linhas cabendo inteiro —
   é onde este botão mais importa, porque é de lá que o cliente novo chega. */
@media (max-width: 640px) {
  .sp-btn-wa { min-height: 52px; padding: 14px 16px; font-size: 13px; gap: 10px; }
  .sp-btn-wa svg { width: 19px; height: 19px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MODAL DO PRODUTO — copiar íntegro ao derivar um template novo
   ═══════════════════════════════════════════════════════════════════════════
   Anda junto com o markup de três faixas em `catalogo/_sp2_rodape.html`
   (.product-modal-media / .product-modal-copy / .product-modal-foot).

   O QUE FOI MEDIDO ANTES (produto de nome mais longo do catálogo, 72 chars):
     conteúdo natural do modal ............ 810px
     tela visível de um celular real ...... ~650px (812 de layout − barra do
                                            navegador; ver `dvh` abaixo)
     "Adicionar ao carrinho" terminava em .. y=797  → 130px abaixo da dobra
     foto no desktop ...................... 300×300 solta numa caixa 380×511
                                            = 54% de sobra morta, 7% da tela

   TRÊS DECISÕES:

   1. `dvh`, não `vh`. No celular `100vh` é a tela COM a barra do navegador
      escondida — o modal podia medir 812px enquanto só 650px estavam à vista,
      e por isso o botão de comprar "sumia" mesmo com o modal cabendo na conta.
      `dvh` acompanha a barra. Navegador sem `dvh` descarta a declaração e cai
      no `max-height: calc(100vh − 28px)` do pacote — degradação automática,
      sem @supports.

   2. Rodapé fixo. O botão de comprar não pode disputar espaço com o texto:
      a faixa do meio é a única que rola, e ela rola em vez de empurrar o
      rodapé para fora. Assim o modal cabe em UMA tela em qualquer altura.

   3. Foto com caixa própria. O `img` não tinha NENHUMA regra aqui (a do pacote
      exige `.has-image`, classe que o app-glivvo.js nunca põe), então a foto
      aparecia no tamanho natural, encostada no topo da caixa. Agora a caixa
      tem proporção declarada e o `object-fit: contain` centraliza — enquadra
      igual para foto quadrada, retrato ou paisagem.
   ═══════════════════════════════════════════════════════════════════════════ */
#product-modal {
  /* `hidden`: quem rola é a faixa do meio, não o modal inteiro — senão o
     rodapé rolaria junto e a faixa fixa não seria fixa. */
  overflow: hidden;
  max-height: calc(100dvh - 24px);
}

/* ── FAIXA 1: a foto ─────────────────────────────────────────────────────── */
.product-modal-media {
  position: relative;          /* âncora do botão Fechar */
  overflow: hidden;
  display: grid;
  background: #f4f7fb;
}
.product-modal-visual {
  /* Anula o `min-height: 480px` do pacote: aqui quem manda na altura é a
     caixa (proporção no desktop, orçamento de tela no celular). */
  min-height: 0; height: 100%;
  padding: 20px;
  /* `minmax(0, 1fr)` não é enfeite: com a linha em `auto` o `height: 100%` da
     foto vira referência circular — a linha cresce até o tamanho natural da
     imagem, o `100%` resolve contra essa linha inflada, e a foto passa a
     estourar a caixa e ser cortada pelo `overflow: hidden`. Medido: caixa de
     280px com a imagem em 328px. Com a linha fixa em 1fr o `100%` resolve
     contra a caixa de verdade. */
  display: grid; grid-template-rows: minmax(0, 1fr);
  background: transparent;
}
/* Sem foto o produto ainda precisa de uma caixa apresentável. `:empty` é
   exato: o JS faz `innerHTML = ''` quando o produto não tem imagem. */
.product-modal-visual:empty { background: linear-gradient(145deg, #edf5ff, #d6e8ff); }
/* As formas decorativas do pacote não entram — a foto é o assunto. */
.product-modal-visual::before,
.product-modal-visual::after { display: none; }
.product-modal-visual img {
  width: 100%; height: 100%; max-height: none;
  object-fit: contain;         /* nunca corta a peça */
  mix-blend-mode: multiply;    /* funde o fundo branco da foto com a caixa */
}

/* Fechar: redondo e sólido para ler como controle por cima da imagem, 44px de
   alvo (WCAG 2.2). A POSIÇÃO muda por viewport, e de propósito — ver os dois
   blocos de media query no fim deste arquivo. */
#product-modal .modal-close {
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid #c9d8ea;
  background: rgba(255, 255, 255, .94);
  box-shadow: 0 6px 18px rgba(1, 18, 52, .16);
  backdrop-filter: blur(4px);
}
#product-modal .modal-close:hover { color: var(--blue-600); border-color: #a9ccff; }
#product-modal .modal-close:focus-visible { outline: 3px solid var(--blue-600); outline-offset: 2px; }
#product-modal .modal-close svg { width: 18px; height: 18px; }

/* ── FAIXA 2: o texto (única que rola) ───────────────────────────────────── */
.product-modal-copy { min-height: 0; overflow: auto; }
/* Nome longo não pode empurrar o resto: três linhas e o resto no `title`
   (posto pelo JS, porque o texto vem do card). */
#product-modal-title {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── FAIXA 3: comprar (sempre à vista) ───────────────────────────────────── */
.product-modal-foot {
  padding: 16px 36px 24px;
  border-top: 1px solid #edf2f9;
  background: white;
}
.product-modal-foot .modal-buy-row { margin: 0 0 14px; }

/* ── DESKTOP ─────────────────────────────────────────────────────────────── */
@media (min-width: 721px) {
  #product-modal {
    /* 760px deixava a coluna da foto com 380px para uma peça de 300px. */
    width: min(940px, calc(100vw - 40px));
    max-height: calc(100dvh - 48px);
    grid-template-columns: 1.15fr 1fr;
    grid-template-rows: minmax(0, 1fr) auto;
  }
  /* A foto ocupa a coluna esquerda INTEIRA (as duas linhas).
     `position: static` de propósito: com isso o Fechar (absolute) volta a se
     ancorar no MODAL e não na foto — ver a regra logo abaixo. */
  .product-modal-media { grid-area: 1 / 1 / 3 / 2; border-radius: 24px 0 0 24px; position: static; }
  /* No desktop não há polegar: o Fechar fica onde todo mundo procura, no canto
     superior direito do modal. O canto inferior esquerdo é solução de alcance
     de UMA MÃO, e só o celular tem esse problema. */
  #product-modal .modal-close { top: 14px; right: 14px; bottom: auto; left: auto; }
  .product-modal-copy  { grid-area: 1 / 2 / 2 / 3; padding: 34px 36px 18px; }
  .product-modal-foot  { grid-area: 2 / 2 / 3 / 3; }
  /* Piso de altura: sem ele um produto de nome curto encolheria o modal a
     ponto da foto virar uma tira. */
  .product-modal-media { min-height: 460px; }
}

/* ── CELULAR ─────────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
  #product-modal {
    width: min(430px, calc(100vw - 20px));
    max-height: calc(100dvh - 20px);
    grid-template-columns: 1fr;
    /* auto | o que sobrar | auto — o texto absorve a falta de espaço. */
    grid-template-rows: auto minmax(0, 1fr) auto;
  }
  .product-modal-media {
    border-radius: 24px 24px 0 0;
    /* Quadrada quando cabe; nunca mais que 42% da tela à vista, para o
       orçamento fechar com texto + rodapé numa única tela.
       ⚠️ `width: 100%` é OBRIGATÓRIO aqui, não é redundância com o stretch do
       grid: com a largura automática, o `max-height` puxa a LARGURA junto para
       preservar a proporção, e o painel encolhia para 280px dentro de um modal
       de 355px — sobrava uma faixa branca à direita e a foto saía torta. Com a
       largura definida, o clamp mexe só na altura. */
    width: 100%;
    aspect-ratio: 1 / 1;
    max-height: 42dvh; min-height: 150px;
  }
  /* Fechar no canto inferior ESQUERDO da foto — das três posições que o Samuel
     sugeriu, é a que custa zero altura (sobrepõe a foto em vez de empurrar o
     conteúdo) e mantém distância do "Adicionar ao carrinho", que fica no
     rodapé: sair do modal e comprar não podem ser toques vizinhos. */
  #product-modal .modal-close { top: auto; right: auto; bottom: 12px; left: 12px; }
  .product-modal-visual { padding: 14px; }
  .product-modal-copy { padding: 16px 20px 10px; }
  #product-modal-title { margin: 8px 0 8px; font-size: 19px; }
  /* A descrição é a MESMA frase em todo produto (texto institucional do
     pacote, não dado da peça). No desktop ela contextualiza; no celular
     custaria 33px do orçamento sem informar nada sobre aquela peça. */
  #product-modal .product-description { display: none; }
  /* Qualidade continua visível — é dado real — mas em uma linha (73px → ~34px). */
  #product-modal .quality-box {
    flex-direction: row; align-items: baseline; gap: 8px;
    padding: 9px 12px;
  }
  #product-modal .quality-box small { display: none; }
  .product-modal-foot { padding: 12px 20px 18px; }
  .product-modal-foot .modal-buy-row { margin: 0 0 10px; }
}


/* ─── RODAPÉ DO CARD DE PEDIDO NO CELULAR (17/08) ─────────────────────────
   COLISÃO DE NOME DE CLASSE, não decisão de layout: no mock do pacote
   `.order-card-footer` era um rodapé decorativo de 4 colunas, e o
   `styles.css` o esconde abaixo de 720px por isso. O template do Glivvo
   reusou a classe para a BARRA DE AÇÕES real — Total + "Pagar com PIX" +
   countdown do prazo + "Repetir Pedido". Resultado medido: no celular (a
   tela que o cliente da loja usa) o card não oferecia NENHUMA forma de
   pagar. Reexibir aqui, e não editar o pacote, mantém a camada de ajuste
   como o único lugar onde o Glivvo diverge do mock. */
@media (max-width: 720px) {
  .order-card-footer { display: flex; }
}

/* Relogio do prazo do PIX numa linha propria: ele muda a cada segundo e
   reflow no meio da frase faz o paragrafo inteiro tremer. */
.conta-fin-restante { display: block; margin-top: 3px; font-weight: 700; }
