
html,body{margin:0;padding:0}
html{touch-action:manipulation}
/* =========================================================================
   Delivery — layout estilo iFood (padrão de UX de delivery), marca do tenant
   (nome/logo/cores aplicados em runtime via /delivery/config → applyMarca()).
   Tema claro, barra de endereço no topo, categorias redondas, trilhos
   horizontais, abas inferiores (Início/Busca/Pedidos/Perfil).
   Sem clonar marca do iFood. Estado simulado em JS. Escopo: .af-app
   ========================================================================= */
.af-app{
  --red:#e30613; --red-2:#c40511; --gold:#ffb703; --store-name:"";
  --red-ink:#ffffff; --gold-ink:#1c1c1c; /* fonte de contraste — recalculada por applyMarca() */
  --header-1:#e30613; --header-2:#c40511; --header-ink:#ffffff; /* pacote Header (T1) */
  /* canais RGB p/ efeitos (box-shadow) seguirem a cor real — Fase A1, 2026-08-09.
     Fallback só: o marcador #storecolors sempre sobrescreve no HTML servido.
     ⚠️ NUNCA escrever aqui dentro o marcador completo (com os delimitadores de
     comentário), nem a tag de fechamento de estilo literal. app_render.py substitui o
     marcador por regex em TODO o documento, inclusive dentro de comentário CSS, e o
     bloco injetado traz um fechamento de estilo que encerraria o CSS neste ponto — o
     parser HTML não enxerga comentário CSS, só procura a string de fechamento. Todo o
     CSS abaixo viraria texto puro na tela. Já aconteceu (2026-08-09, 3 tenants). */
  --red-rgb:227,6,19; --gold-rgb:255,183,3; --header-1-rgb:227,6,19;
  /* Fase A2 (2026-08-09): cores por PAPEL — herdam de --red/--gold, NUNCA
     auto-referência (ciclo = CSS inteiro guaranteed-invalid, já queimamos com isso).
     Elemento não reclassificado (Fase A3) continua em --red direto; estes tokens só
     importam pros poucos seletores que passarem a usar var(--c-x). */
  --c-cta:var(--red); --c-cta-rgb:var(--red-rgb); --c-cta-ink:var(--red-ink);
  --c-preco:var(--red);
  --c-badge:var(--gold); --c-badge-ink:var(--gold-ink);
  --c-sacola:var(--red); --c-sacola-rgb:var(--red-rgb); --c-sacola-ink:var(--red-ink);
  --c-icone:var(--red);
  --bg:#f4f4f6; --surf:#ffffff; --ink:#3f3e3e; --ink-2:#717171; --ink-3:#a9a9a9;
  --line:#ededed; --line-2:#f1f1f1; --green:#50a773;
  /* Tokens de superfície criados na tokenização do tema escuro (2026-08-31).
     Valores IDÊNTICOS aos literais que substituíram — no claro nada muda; é o
     bloco .dark que os inverte depois. Ver docs/modulos/delivery-tema-dark-plano.md.
     ⚠️ NEM todo #fff virou token: branco em cima de cor (banner, badge de preço,
     bolha do WhatsApp, ícone de sucesso) continua literal de propósito, e o fundo
     do QR code do Pix TEM que seguir branco ou a câmera não lê o código. */
  --surf-2:#f4f4f4;                                    /* fundo neutro (miniatura, favorito) */
  --ph-bg:linear-gradient(160deg,#fafafa,#f0f0f0);     /* placeholder de foto (card/linha)   */
  --ph-bg-hero:linear-gradient(160deg,#fafafa,#ececec);/* placeholder grande (tela produto)  */
  --ph-flat:#fafafa;                                   /* fundo da <img> do produto          */
  --sel-bg:#fde8e9;                                    /* escolhido (peso) + halo do passo   */
  --sel-bg-2:#fff5f5;                                  /* escolhido (opção/endereço/ETA)     */
  --warn-bg:#fff7e6; --warn-track:#f0e0bb;             /* barra de frete grátis              */
  --neutral-bg:#eef0f2; --neutral-ink:#4a4f57;         /* selo neutro (produto por peso)     */
  --surf-3:#f2f2f4;                                    /* faixa neutra (cabeçalho de opção)  */
  --ok-bg:#eafaf0;                                     /* selo de status concluído           */
  --tab-on-bg:#fdeaec;                                 /* aba ativa (só no layout de desktop)*/
  /* pastilha do favorito/compartilhar sobre a FOTO do produto. Era branco cravado
     (rgba(255,255,255,.92)) em 3 lugares — e branco cravado não é editável nem
     acompanha tema: no escuro virava uma bola branca em cima do produto. */
  --pill-bg:rgba(255,255,255,.92);
  /* Tema escuro — fallback dos tokens que o servidor emite em #storecolors (a versão
     CLAREADA de cada cor, legível sobre fundo escuro). Se o marcador sair cru
     (container com Python velho), estes valores seguram o app: mesma cautela do
     fail-safe de #storemodoclass. NUNCA auto-referência aqui: --x:var(--x,...) é
     ciclo, e o CSS inteiro vira guaranteed-invalid (já queimamos com isso na F1d). */
  --red-lit:#f90412; --red-lit-rgb:249,4,18;
  --c-preco-lit:var(--red-lit); --c-icone-lit:var(--red-lit);
  --c-cta-lit:var(--red-lit); --c-cta-lit-rgb:var(--red-lit-rgb); --c-cta-lit-ink:#ffffff;
  --c-sacola-lit:var(--red-lit); --c-sacola-lit-rgb:var(--red-lit-rgb); --c-sacola-lit-ink:#ffffff;
  --store-bg:none; /* fundo da loja — só existe se o Gestor subir uma foto em Configurações › Aparência */
  --sh:0 1px 3px rgba(0,0,0,.06),0 8px 24px -12px rgba(0,0,0,.12);
  --ease:cubic-bezier(.22,1,.36,1); --w:480px;
  font-family:'Figtree',system-ui,sans-serif;color:var(--ink);background:#111;
  line-height:1.4;-webkit-font-smoothing:antialiased;
}
.af-app *{margin:0;padding:0;box-sizing:border-box}
.af-app img{display:block;max-width:100%}
.af-app a{color:inherit;text-decoration:none}
.af-app button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
.af-app ::selection{background:var(--red);color:var(--red-ink)}

.af-app .stage{max-width:var(--w);margin:0 auto;min-height:100vh;position:relative;background:var(--bg);box-shadow:0 0 80px rgba(0,0,0,.5);overflow:hidden}
/* NIVEL: ambos [palco-fluido] — de 481 a 899px (totem, tablet) o palco ocupa a TELA TODA. Antes ficava a
   coluna de 480px com o #111 dos lados (totem do showdosbiscoitos, 30/09). Tudo o que acompanha o palco
   lê var(--w): fundo, barra de baixo, sacola, botão fixo, janelas de baixo, mapa. >= 900px é o desktop. */
@media (max-width:899px){ .af-app{--w:100%} }
/* Fundo da loja: camada FIXA atrás do conteúdo (parallax) — não usar
   background-attachment:fixed, que é quebrado no Safari iOS. */
.af-app .af-bg{position:fixed;top:0;left:50%;transform:translateX(-50%);width:100%;max-width:var(--w);height:100vh;z-index:1;pointer-events:none;background-image:linear-gradient(rgba(244,244,246,.72),rgba(244,244,246,.72)),var(--store-bg);background-size:cover;background-position:top center;background-repeat:no-repeat}
.af-app .screen{position:relative;z-index:2}
.af-app .screen{display:none;min-height:100vh;padding-bottom:78px}
.af-app.hasfab .screen{padding-bottom:150px}
.af-app .screen.active{display:block}
/* NIVEL: completo [tipo-nota] — escolha da compra, chips de nota no checkout (13/10/2026) */
.af-app .tnota{padding:16px 14px;display:flex;flex-direction:column;gap:12px}
.af-app .tnota-op{display:flex;align-items:center;gap:14px;text-align:left;padding:16px;border:1.5px solid var(--line);border-radius:14px;background:var(--surf)}
.af-app .tnota-op.destaque{border:2px solid var(--red)}
.af-app .tnota-op b{display:block;font-size:16px}
.af-app .tnota-op small{display:block;color:var(--ink-2);font-size:13px;margin-top:3px;line-height:1.35}
.af-app .tnota-ic{font-size:28px;width:40px;flex:0 0 40px;text-align:center}
.af-app .tnota-entrar{margin-top:4px;padding:12px;color:var(--red);font-weight:700}
.af-app .nf-chips{display:flex;gap:8px;padding:0 14px 12px;flex-wrap:wrap}
.af-app .nf-chip{padding:9px 16px;border:1.5px solid var(--line);border-radius:999px;font-weight:700;font-size:14px}
.af-app .nf-chip.sel{border-color:var(--red);color:var(--red)}
.af-app .nf-aviso{margin:12px 14px;padding:10px 12px;border-radius:10px;background:var(--surf);border:1px solid var(--line);font-size:13px}
.af-app .fade{animation:fade .25s var(--ease)}
@keyframes fade{from{opacity:0}to{opacity:1}}

/* ===== TOP: endereço ===== */
/* .tophero = wrapper único que carrega o gradiente do header (endereço + busca) como UMA
   peça só, igual à .prof-top. Evita a faixa chapada que surgia quando a .searchbar tinha
   fundo sólido var(--header-2). */
.af-app .tophero{background:linear-gradient(135deg,var(--header-1),var(--header-2));position:sticky;top:0;z-index:20;box-shadow:0 8px 20px rgba(var(--header-1-rgb),.22);padding-top:env(safe-area-inset-top)}
.af-app .addrbar{background:transparent;color:var(--header-ink);padding:10px 14px;position:static}
.af-app .addrbar .r{display:flex;align-items:center;gap:10px}
.af-app .addrbar .brand{flex:1;min-width:0;display:flex;align-items:center;gap:9px}
.af-app .addrbar .brand .blogo{width:44px;height:44px;border-radius:11px;object-fit:contain;flex:none;background:transparent}
/* nome + endereço empilhados AO LADO da logo (antes o endereço era irmão da .r e caía
   embaixo da linha inteira, criando um header alto e espalhado — pedido do cliente 2026-08-01) */
.af-app .addrbar .brand .btxt{min-width:0;display:flex;flex-direction:column;justify-content:center}
.af-app .addrbar .brand .bname{font-weight:800;font-size:17px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Letreiro (2026-08-10): ativado via JS só quando o nome não cabe (updateBrandMarquee).
   .bname-track leva 2 cópias do texto lado a lado; anima -50% pra loop sem "salto". */
.af-app .addrbar .brand .bname.marquee{text-overflow:clip}
.af-app .addrbar .brand .bname.marquee .bname-track{display:inline-flex;width:max-content;animation:bname-marquee linear infinite;animation-duration:var(--bname-dur,9s)}
.af-app .addrbar .brand .bname .bname-seg{display:inline-block;white-space:nowrap}
.af-app .addrbar .brand .bname.marquee .bname-seg{padding-right:36px}
@keyframes bname-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.af-app .addrbar .addr{font-size:11.5px;opacity:.85;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  border:none;background:none;padding:0;font-family:inherit;color:inherit;display:flex;align-items:center;gap:4px;cursor:pointer;max-width:100%}
.af-app .addrbar .addr .addrIc{flex:none;width:16px;height:16px}
/* Selo "Aberto até / Fechado" (2026-09-24) — faixa própria, CENTRALIZADA, abaixo da busca e
   do WhatsApp (pedido do dono). Fora da .addrbar de propósito: lá dentro o logo grande do
   Catálogo (.catalogo.logo-xl) vira grade de áreas nomeadas e o selo precisaria de faixa
   própria em cada variante dela. Pílula com fundo PRÓPRIO: o topo tem a cor de cada loja, e
   verde/vermelho em cima de um topo verde ou vermelho sumiria. A faixa nasce `hidden` e só
   aparece quando atualizarSelo() tem certeza do estado (ver lá).
   NIVEL: ambos [selo-horario] */
.af-app .selowrap{display:flex;justify-content:center;padding:0 14px 10px;margin-top:-8px}
.af-app .selowrap[hidden]{display:none}
/* Tela grande (27/10/2026, dono: "na tela grande tá muito junto, no celular está certo"): o recuo de −8px que cola o selo na busca
   no celular fazia ele SOBREPOR a busca em 8 px e encostar no fim do topo. A partir de 900 px: respiro em cima e embaixo. */
@media (min-width:900px){ .af-app .selowrap{margin-top:0;padding:10px 14px 16px} }
.af-app .lstatus{display:inline-flex;align-items:center;gap:5px;padding:2px 9px 2px 7px;border-radius:999px;
  font-size:11px;font-weight:700;line-height:1.4;white-space:nowrap;max-width:100%;overflow:hidden;
  text-overflow:ellipsis;box-shadow:0 2px 8px rgba(0,0,0,.08)}
/* Cores personalizáveis em Cores da loja (--selo-txt/--selo-bg, emitidas no 1º quadro por
   _selo_css em app_render.py); sem a variável, o verde/vermelho de sempre. A BOLINHA não
   segue a personalização: é ela que diz aberto × fechado quando o texto tem cor da marca.
   `.selo-sem-fundo` (classe do servidor) tira SÓ a pílula: o texto segue a cor do estado
   (a escolhida, ou verde/vermelho) — dono, 24/09: "quando removo o fundo o texto fica preto". */
.af-app .lstatus i{flex:none;width:6px;height:6px;border-radius:50%}
.af-app .lstatus.on{background:var(--selo-bg,#e6f6ea);color:var(--selo-txt,#12803a)}
.af-app .lstatus.off{background:var(--selo-f-bg,#fdecec);color:var(--selo-f-txt,#c62828)}
.af-app .lstatus.on i{background:#1fa54a}
.af-app .lstatus.off i{background:#e53935}
.af-app.selo-sem-fundo .lstatus{background:none;box-shadow:none;padding-left:0;padding-right:0}
/* NIVEL: ambos [loja-fechada] — faixa de "estamos fechados" no checkout e na sacola.
   Usa as MESMAS cores do selo fechado (o lojista escolhe em Cores da loja): duas paletas
   diferentes para o mesmo recado é o tipo de detalhe que denuncia remendo. */
.af-app .fechado-aviso{margin:10px 14px;padding:10px 12px;border-radius:12px;font-size:13.5px;font-weight:600;
  line-height:1.35;background:var(--selo-f-bg,#fdecec);color:var(--selo-f-txt,#c62828)}
.af-app .addrbar .pic{width:34px;height:34px;flex:none;border-radius:50%;background:rgba(255,255,255,.18);display:grid;place-items:center;font-weight:800;font-size:15px}
.af-app .addrbar .pic .pic-label{display:none}

/* ===== HEADER logo grande — 3 faixas (2026-08-11) =====
   Exclusivo de .catalogo.logo-xl (classe nasce no servidor, ver _storemodoclass_html em
   app_render.py — logo_tamanho>72 SÓ é possível no modo Catálogo, ver admin_config.py).
   Delivery completo nunca aplica este bloco: nem a classe chega, nem este seletor bate.

   display:contents em .brand/.btxt dissolve as duas caixas como layout (sem tirar nada
   do DOM — mesma árvore do layout padrão, só reorganizada por grid-area) para que logo,
   nome e endereço virem itens diretos do grid de 3 faixas abaixo. */
/* coluna do logo é minmax(0,1fr) e a do WhatsApp é auto (tamanho do próprio conteúdo)
   — invertido do "auto 1fr" de antes. Antes o teto de largura do logo vinha só do
   maxWidth em vw (marca.js), uma ESTIMATIVA de tela cheia que satura cedo demais pra
   logo em formato de faixa/retangular (medido: acima de ~95px de altura pedida a
   imagem já não crescia mais). Com a coluna flexível, .blogo (abaixo) usa max-width:100%
   contra o espaço REAL que sobra ao lado do botão — sempre o máximo que cabe, sem
   estourar em cima do WhatsApp nem depender de adivinhar a largura da tela. */
/* NIVEL: catalogo [logo-xl] */
.af-app.catalogo.logo-xl .addrbar .r{
  display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:12px;row-gap:10px;align-items:center;
  grid-template-areas:"name name" "logo pic" "addr addr"}
/* NIVEL: catalogo [logo-xl] */
.af-app.catalogo.logo-xl .addrbar .brand,
.af-app.catalogo.logo-xl .addrbar .btxt{display:contents}
.af-app.catalogo.logo-xl .addrbar .blogo{grid-area:logo;border-radius:16px;max-width:100%}
/* NIVEL: catalogo [logo-xl] */
.af-app.catalogo.logo-xl .addrbar .bname{grid-area:name;font-size:20px}
.af-app.catalogo.logo-xl .addrbar .addr{grid-area:addr;font-size:15px;opacity:1;font-weight:600;white-space:normal}
.af-app.catalogo.logo-xl .addrbar .addr .addrIc{width:20px;height:20px}
/* NIVEL: catalogo [logo-xl] */
.af-app.catalogo.logo-xl .addrbar .pic{
  grid-area:pic;justify-self:end;width:auto;height:auto;min-width:64px;border-radius:14px;
  flex-direction:column;gap:3px;padding:10px 12px}
/* NIVEL: catalogo [logo-xl] */
.af-app.catalogo.logo-xl .addrbar .pic svg{width:38px;height:38px}
.af-app.catalogo.logo-xl .addrbar .pic .pic-label{display:block;font-size:10.5px;font-weight:700;white-space:nowrap}

/* Loja SEM nome cadastrado (2026-08-11): o logo sobe pro topo do header. Não basta
   deixar o texto vazio — o segmento é inline-block, então o span vazio ainda gera uma
   linha de ~23px, e o row-gap de 10px continua valendo entre as faixas. Some a caixa
   (display:none) E a faixa do grid (grid-template-areas sem "name"): as duas coisas,
   porque `grid-area:name` apontando pra área inexistente criaria uma linha implícita.
   A classe nasce em applyMarca (nome vazio é cenário previsto, ver comentário lá). */
.af-app.sem-nome .addrbar .bname{display:none}
/* NIVEL: catalogo [logo-xl] */
.af-app.catalogo.logo-xl.sem-nome .addrbar .r{grid-template-areas:"logo pic" "addr addr"}
.af-app.wpp-inline.catalogo.logo-xl.sem-nome .addrbar .r{grid-template-areas:"logo" "addr"}

/* ===== WhatsApp: cor oficial + descida pra linha da busca (2026-08-11) =====
   Cor: verde #25D366 com glifo branco (padrão da marca) no lugar do branco translúcido
   genérico. A classe .wpp entra em atualizarIconePerfilHeader() SÓ quando há grupo
   configurado — sem grupo o MESMO #picBtn é a bolha de Perfil, que nunca pode ficar
   verde. Seletor sem .addrbar de propósito: o botão troca de pai (ver .wpp-inline). */
.af-app .pic.wpp{background:#25D366;color:#fff}

/* .wpp-inline: a logo já não cabe ao lado do botão — MEDIDO em posicionarWpp(), não
   deduzido de logo_tamanho (logo quadrada de 120px nunca encosta no botão; logo em
   faixa, tipo 256x78, encosta bem antes). O botão desce pro fim da linha da busca e a
   faixa 2 vira 1 coluna só: a logo passa a crescer até a borda da tela.
   O .searchbar é escopado em [data-scr="home"] porque a tela de busca monta um
   .searchbar próprio (renderBusca) que nunca recebe este botão. */
/* NIVEL: catalogo [logo-xl] */
.af-app.wpp-inline.catalogo.logo-xl .addrbar .r{
  grid-template-columns:minmax(0,1fr);grid-template-areas:"name" "logo" "addr"}
.af-app.wpp-inline .screen[data-scr="home"] .searchbar{display:flex;align-items:stretch;gap:8px}
.af-app.wpp-inline .screen[data-scr="home"] .searchbar .search{flex:1;min-width:0}
/* min-width:0 no INPUT, não só no .search: input tem largura intrínseca (size padrão) e
   não encolhe sozinho num flex — o campo branco encolhia mas o texto seguia em frente e
   vazava por baixo do botão. Com o ellipsis o placeholder longo ("Buscar na
   DISTRIBUIDORA DE BEBIDAS…") termina DENTRO do campo. */
.af-app.wpp-inline .screen[data-scr="home"] .searchbar .search input{min-width:0;text-overflow:ellipsis}
/* ao trocar de pai o botão sai do escopo de `.addrbar .pic` — forma/tamanho redefinidos
   aqui do zero, sem herdar nada daquele bloco (lá ele é o bloco alto de 2 andares, com
   ícone de 38px em cima do rótulo). Aqui é compacto e deitado: a linha é dividida com a
   busca, então cada px do botão sai do campo de pesquisa. Sem background: quem pinta é
   .pic.wpp acima (o modo inline só existe com WhatsApp, nunca com a bolha de Perfil). */
.af-app.wpp-inline .searchbar .pic{
  flex:none;display:flex;align-items:center;gap:7px;padding:0 13px;border:none;
  border-radius:10px;font-family:inherit;font-weight:800;color:#fff;cursor:pointer}
.af-app.wpp-inline .searchbar .pic svg{width:25px;height:25px;flex:none}
.af-app.wpp-inline .searchbar .pic .pic-label{display:block;font-size:13px;font-weight:700;white-space:nowrap}

/* busca */
.af-app .searchbar{padding:12px 14px 16px;background:transparent}
.af-app .search{display:flex;align-items:center;gap:9px;background:var(--surf);border-radius:10px;padding:11px 13px}
.af-app .search input{flex:1;border:none;outline:none;font-size:16px;color:var(--ink);font-family:inherit}
.af-app .search input::placeholder{color:var(--ink-3)}
.af-app .search svg{color:var(--c-icone);flex:none}
/* NIVEL: ambos [app-vitrine] [conta-cliente] — busca e telas de senha em <form> próprio (21/10/2026): sem form o Chrome
   juntava a página inteira num formulário só e escrevia o usuário salvo ("admin") na busca. type=search sem a cara de busca do navegador. */
.af-app form.search,.af-app form.fcred{margin:0}
.af-app #buscaInput{-webkit-appearance:none;appearance:none;min-width:0}
.af-app #buscaInput::-webkit-search-cancel-button,.af-app #buscaInput::-webkit-search-decoration{-webkit-appearance:none;display:none}
/* 2026-09-17 — efeito ao tocar na busca da HOME (pedido do dono): a barra afunda, ganha um
   anel na cor da loja, uma faixa de luz atravessa e a lupa gira; a tela de busca abre
   220ms depois (tocarBuscaHome). Com "reduzir movimento" vai direto, sem efeito. */
.af-app .screen[data-scr="home"] .searchbar .search{position:relative;overflow:hidden;cursor:pointer;
  transition:transform .18s ease,box-shadow .18s ease}
.af-app .screen[data-scr="home"] .searchbar .search.tocando{transform:scale(.97);
  box-shadow:0 0 0 3px rgba(var(--red-rgb),.35),0 8px 20px -8px rgba(var(--red-rgb),.55)}
.af-app .screen[data-scr="home"] .searchbar .search.tocando::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(100deg,transparent 15%,rgba(var(--red-rgb),.18) 50%,transparent 85%);
  animation:busca-onda .22s ease-out forwards}
.af-app .screen[data-scr="home"] .searchbar .search.tocando svg{animation:busca-lupa .22s ease-out}
@keyframes busca-onda{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
@keyframes busca-lupa{50%{transform:scale(1.25) rotate(-14deg)}}
@media (prefers-reduced-motion:reduce){
  .af-app .screen[data-scr="home"] .searchbar .search,.af-app .screen[data-scr="home"] .searchbar .search.tocando{transition:none;transform:none}
  .af-app .screen[data-scr="home"] .searchbar .search.tocando::after,.af-app .screen[data-scr="home"] .searchbar .search.tocando svg{animation:none}
}

/* ===== FILTROS/entrega toggle ===== */
.af-app .modebar{display:flex;gap:8px;padding:12px 14px 6px;background:transparent;overflow-x:auto;scrollbar-width:none}
.af-app .modebar::-webkit-scrollbar{display:none}
.af-app .mode{flex:none;display:flex;align-items:center;gap:7px;padding:9px 14px;border-radius:100px;background:var(--surf);box-shadow:var(--sh);font-size:13px;font-weight:600;color:var(--ink)}
.af-app .mode.on{background:var(--red);color:var(--red-ink)}
.af-app .mode small{opacity:.8;font-weight:500}
/* NIVEL: ambos [busca-atalhos] — atalhos da busca (dono, 20/10/2026): blocos de canto reto, borda fina, colados um no outro
   (a borda do vizinho se sobrepõe: -1px). Espaço IGUAL acima e abaixo da fileira (12 px): a barra de busca já tem 16 px
   embaixo, por isso -4px; abaixo, 6 px da fileira + 6 px da lista. A flutuante fica PRESA NO TOPO DA TELA (mesmo molde do
   .tabbar: fixed + centrada na largura do app) — o sticky da barra de busca não segura dentro do .af-app (medido em 21/10). */
.af-app .modebar.batl{gap:0;padding:0 14px 6px;margin-top:-4px}
.af-app .batl .mode{border-radius:0;box-shadow:none;border:1px solid #d9d9de;border:1px solid color-mix(in srgb,var(--ink) 15%,transparent);padding:8px 12px}
.af-app .batl .mode+.mode{margin-left:-1px}
/* pontas levemente arredondadas (dono, 21/10: "uma borda bem leve apenas para remover as pontas") — só nas pontas da fileira,
   para os blocos continuarem encaixados; atalho sozinho = os 4 cantos */
.af-app .batl .mode:first-child{border-top-left-radius:5px;border-bottom-left-radius:5px}
.af-app .batl .mode:last-child{border-top-right-radius:5px;border-bottom-right-radius:5px}
.af-app .batl .mode.on{position:relative;z-index:1;border-color:var(--red)}
.af-app .modebar.batl.batl-flut{position:fixed;top:0;left:50%;width:100%;max-width:var(--w);z-index:30;margin:0;
  padding:calc(10px + env(safe-area-inset-top)) 14px 10px;background:var(--bg);box-shadow:0 8px 14px -10px rgba(0,0,0,.35);
  transform:translateX(-50%) translateY(-100%);visibility:hidden;pointer-events:none;transition:transform .18s ease,visibility 0s linear .18s}
.af-app .modebar.batl.batl-flut.on{transform:translateX(-50%) translateY(0);visibility:visible;pointer-events:auto;transition:transform .18s ease,visibility 0s}

/* ===== carrossel banners ===== */
.af-app .banners{padding:8px 0 4px}
.af-app .btrack{display:flex;gap:12px;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;padding:6px 14px}
.af-app .btrack::-webkit-scrollbar{display:none}
/* NIVEL: ambos [palco-fluido] — faixa com no máximo 400px: no palco fluido ela esticava (~700x132) e a
   foto (cover) cortava a arte. No celular nada muda (86% < 400px); no totem cabem 2 lado a lado. */
.af-app .banner{flex:0 0 min(86%,400px);scroll-snap-align:center;border-radius:16px;padding:20px;min-height:132px;position:relative;overflow:hidden;color:#fff;display:flex;flex-direction:column;justify-content:center;box-shadow:var(--sh)}
.af-app .banner.b1{background:linear-gradient(120deg,var(--red),var(--red-2))}
.af-app .banner.b2{background:linear-gradient(120deg,#1f1f28,#3a3a48)}
.af-app .banner.b3{background:linear-gradient(120deg,#ffb703,#e08a00);color:#3a2a00}
.af-app .banner .k{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.9}
.af-app .banner h3{font-size:21px;font-weight:800;margin:6px 0 3px;line-height:1.05}
.af-app .banner p{font-size:13px;opacity:.92}
.af-app .banner .mega{position:absolute;right:14px;bottom:-6px;font-size:64px;font-weight:800;opacity:.16}
/* ===== galeria do banner (F30 — delivery-banner-galeria-plano.md) ===== */
.af-app .gal{padding:0 14px 24px}
.af-app .gal .galsub{color:var(--ink-2);font-size:13px;margin:2px 0 14px}
/* 1 coluna no celular: foto/vídeo de loja é conteúdo, não miniatura — grade de 2
   deixaria o vídeo pequeno demais pra tocar. No desktop cabem 2. */
.af-app .galgrid{display:grid;grid-template-columns:1fr;gap:14px}
.af-app .galitem{border-radius:16px;overflow:hidden;background:#0000000d;box-shadow:var(--sh);position:relative}
.af-app .galitem img.galimg{width:100%;display:block;aspect-ratio:16/10;object-fit:cover;background:#00000014}
.af-app .galitem .gallegenda{padding:10px 12px;font-size:13px;color:var(--ink-2)}
/* facade do vídeo: capa + play. O <iframe> só nasce no toque (ver playGaleria) —
   sem isso cada vídeo carrega os scripts do YouTube no boot da tela. */
.af-app .galvideo{position:relative;cursor:pointer;aspect-ratio:16/9;background:#000}
.af-app .galvideo img{width:100%;height:100%;display:block;object-fit:cover;opacity:.85}
.af-app .galvideo .galplay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none}
.af-app .galvideo .galplay span{width:64px;height:64px;border-radius:50%;background:#000000a6;color:#fff;display:flex;align-items:center;justify-content:center;font-size:26px;padding-left:4px}
/* sem capa (Instagram/Facebook): cartão nomeia a plataforma em vez de quadro preto */
.af-app .galvideo .galsemcapa{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;color:#fff;background:linear-gradient(135deg,#2b2b36,#14141b)}
.af-app .galvideo .galsemcapa b{font-size:15px}
.af-app .galvideo .galsemcapa small{font-size:12px;opacity:.7}
/* vídeo vertical (reel) não pode ocupar a tela toda em altura */
.af-app .galvideo[style*="9/16"]{max-height:78vh;margin:0 auto;width:auto;aspect-ratio:9/16}
.af-app .galvideo iframe{width:100%;height:100%;display:block;border:0}
.af-app .galempty{padding:28px 4px;text-align:center;color:var(--ink-2);font-size:14px}
@media(min-width:720px){ .af-app .galgrid{grid-template-columns:1fr 1fr} }

/* ===== categorias redondas ===== */
.af-app .cats{padding:14px 0 6px;background:transparent}
.af-app .cats h4,.af-app .rail h4,.af-app .listwrap h4{font-size:16px;font-weight:800;padding:0 14px 12px;color:var(--ink)}
.af-app .catscroll{display:flex;align-items:flex-start;gap:16px;overflow-x:auto;padding:0 14px 6px;scrollbar-width:none}
.af-app .catscroll::-webkit-scrollbar{display:none}
.af-app .catitem{flex:none;width:80px;text-align:center}
.af-app .catitem .ic{position:relative;overflow:hidden;width:60px;height:60px;border-radius:16px;background:var(--surf);box-shadow:var(--sh);display:grid;place-items:center;font-size:28px;margin:0 auto 7px;transition:transform .2s var(--ease)}
.af-app .catitem .ic img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.af-app .catitem:active .ic{transform:scale(.92)}
/* --catfs: fonte do rótulo. Encolhe SÓ no item cuja maior palavra não cabe nos 80px
   (ver ajustarLabelsCat) — quebra continua acontecendo apenas em espaço. height em px,
   não em em, senão a caixa encolheria junto com a fonte e desalinharia a fileira. */
.af-app .catitem span{font-size:var(--catfs,13px);font-weight:700;color:var(--ink);line-height:1.15;
  word-break:normal;overflow-wrap:normal;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;height:30px}

/* ===== trilho horizontal de produtos ===== */
.af-app .rail{padding:16px 0 6px;background:transparent}
.af-app .rail .rh{display:flex;align-items:center;justify-content:space-between;padding:0 14px 12px}
.af-app .rail .rh h4{padding:0}
.af-app .rail .rh a{color:var(--red);font-size:13px;font-weight:700}
/* Compartilhar o trilho (2026-08-26) — fica à ESQUERDA do "Ver mais", a pedido do
   dono. O .rh é flex com space-between: o par [compartilhar][Ver mais] precisa de um
   wrapper próprio (.rhr), senão o h4 e os dois vira(ria)m 3 colunas espalhadas. */
.af-app .rail .rh .rhr{display:flex;align-items:center;gap:10px}
.af-app .rail .rh .rhshare{background:none;border:none;padding:0;cursor:pointer;color:var(--red);display:grid;place-items:center;line-height:0}
.af-app .rail .rh .rhshare svg{width:17px;height:17px;display:block}
.af-app .railscroll{display:flex;gap:12px;overflow-x:auto;padding:0 14px 6px;scrollbar-width:none}
.af-app .railscroll::-webkit-scrollbar{display:none}
/* Vitrine por categoria carregando aos poucos na horizontal: o marcador vira só o giro, no fim
   da fileira. NIVEL: ambos [vitrines-categoria] */
.af-app .railcat .railscroll .lista-mais{flex:0 0 auto;align-self:center;font-size:0;padding:0 22px;border:0;background:none}
.af-app .pcard{flex:none;width:150px;background:var(--surf);border-radius:14px;box-shadow:var(--sh);overflow:hidden;position:relative;
  display:flex;flex-direction:column;container-type:inline-size}
.af-app .pcard .ph{height:100px;display:grid;place-items:center;font-size:46px;background:var(--ph-bg)}
.af-app .pcard .pb{padding:10px 11px 12px;flex:1;display:flex;flex-direction:column}
.af-app .tagoff{position:absolute;top:8px;left:8px;background:var(--c-badge);color:var(--c-badge-ink);font-size:10px;line-height:1;font-weight:700;padding:3px 7px;border-radius:6px;z-index:2}
.af-app .promotag{display:inline-block;margin-top:4px;background:var(--gold,#ffb703);color:var(--gold-ink,#1c1c1c);font-size:10.5px;font-weight:700;padding:3px 7px;border-radius:6px;line-height:1}
/* F14 — selo neutro (não é oferta) do produto por peso, mesmo padrão do .promotag */
.af-app .pesotag{display:inline-block;margin-top:4px;background:var(--neutral-bg);color:var(--neutral-ink);font-size:10.5px;font-weight:700;padding:3px 7px;border-radius:6px;line-height:1}
/* Vencimento (2026-08-19) — vermelho literal (#e30613), mesmo motivo do B1: var(--red)
   é a cor PRIMÁRIA da loja (preta no tocomsede), o selo sairia invisível. */
.af-app .pesotag.venc{background:#e30613;color:#fff}
.af-app .pesowarn{font-size:11.5px;font-weight:600;color:var(--ink-2);margin-top:2px}
/* NIVEL: ambos [sacola] — F6.4: item sem estoque fica MEIO APAGADO na sacola até o cliente resolver
   (dono, 26/10/2026). Vermelho fixo de erro (--red é a cor da loja). Aviso, botão e quantidade ficam inteiros. */
.af-app .citem.esg .th,.af-app .citem.esg .ci>b,.af-app .citem.esg .cp,.af-app .citem.esg .cops{opacity:.35}
.af-app .esgwarn{font-size:12px;font-weight:700;color:#c40511;margin-top:4px;line-height:1.35}
.af-app .esgrm{margin-left:4px;border:1.5px solid #c40511;color:#c40511;background:#fff;border-radius:8px;padding:3px 10px;font-weight:800;font-size:12px;cursor:pointer}
.af-app .pesopick{display:flex;gap:8px;flex-wrap:wrap;margin:10px 0}
.af-app .pesopick button{padding:9px 14px;border-radius:9px;border:1px solid var(--line);background:var(--surf);font-weight:700;font-size:13px}
.af-app .pesopick button.sel{border-color:var(--red);color:var(--red);background:var(--sel-bg)}
/* Personalização (2026-08-16) — grupos de escolha na tela de detalhe.
   Visual espelha o padrão do setor (cabeçalho cinza com a pergunta + selo
   OBRIGATÓRIO, opções em linha com preço extra à esquerda e controle à direita). */
.af-app .opcg{margin:14px 0;border-top:1px solid var(--line)}
.af-app .opch{display:flex;align-items:center;justify-content:space-between;gap:10px;background:var(--surf-3);padding:11px 14px}
.af-app .opch b{font-size:14.5px}
.af-app .opcs{font-size:11.5px;color:var(--ink-2);margin-top:2px}
.af-app .opcs .okc{color:var(--ok,#1a9e57)}
.af-app .obrig{background:#1c1c1c;color:#fff;font-size:9.5px;font-weight:800;letter-spacing:.4px;padding:4px 7px;border-radius:4px;white-space:nowrap}
.af-app .orow{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--line);cursor:pointer}
.af-app .orow .oin{min-width:0}
/* NIVEL: completo [opcoes-produto] — opção sem saldo (estoque por opção, 13/10/2026): esmaecida e sem clique */
.af-app .orow.esgot{opacity:.55;cursor:default}
.af-app .ods.opouco{color:var(--red);font-weight:600}
.af-app .lrow .limg{flex:none;width:56px;height:56px;border-radius:12px;overflow:hidden;background:var(--surf-3);display:flex;align-items:center;justify-content:center;font-size:28px;cursor:pointer}
.af-app .lrow .limg img{width:100%;height:100%;object-fit:cover}
.af-app .lrow .oin{flex:1;cursor:pointer}
.af-app .lrow .lpr{flex:none;font-weight:700;font-size:14px;white-space:nowrap}
.af-app .ligatual{margin:16px 14px 2px}
.af-app .ligatual-t{font-size:11.5px;font-weight:800;color:var(--ink-2);text-transform:uppercase;letter-spacing:.5px;margin:0 2px 6px}
.af-app .ligatual .lrow{border:2px solid var(--c-sacola,var(--red));border-radius:14px;background:var(--surf)}
.af-app .lrow.latual .oin{cursor:default}
.af-app .lrow.lind{opacity:.55}
.af-app .lrow .lpr2{display:none;font-weight:700;color:var(--ink);font-size:13.5px}
/* Celular estreito (≤ 400 px): com o "− N +" aberto o preço em coluna própria ficava POR CIMA do nome (medido em 320 px, 20/10/2026) —
   ele desce para baixo do nome. */
@media (max-width:400px){.af-app .lrow .lpr{display:none}.af-app .lrow .lpr2{display:block}.af-app .lrow .oin{min-width:0;overflow-wrap:break-word}
  .af-app .lrow{gap:8px;padding:12px 10px}.af-app .lrow .limg{width:44px;height:44px;font-size:22px}
  .af-app .lrow .oqt{gap:6px}.af-app .lrow .oqt .oqn{min-width:36px}}
.af-app #addp.lbloq{opacity:.55}
.af-app .ods.oesg-sel{color:var(--red);font-weight:700}
.af-app .oesg{flex:none;font-size:11px;font-weight:800;letter-spacing:.3px;color:#b91c1c;background:#fee2e2;padding:4px 9px;border-radius:999px}
.af-app .orow .oin b{font-weight:600;font-size:14px}
.af-app .ods{font-size:11.5px;color:var(--ink-2);margin-top:2px}
.af-app .oex{font-size:12.5px;font-weight:700;color:var(--red);margin-top:3px}
/* Bolinha (escolha única/múltipla) — sem <input>, para o clique ser sempre da linha. */
.af-app .obl{flex:none;width:21px;height:21px;border-radius:50%;border:2px solid var(--line);background:var(--surf)}
.af-app .obl.on{border-color:var(--red);background:var(--red);box-shadow:inset 0 0 0 3.5px var(--surf)}
/* Botões + e − dos sabores 1,5× o original (dono: 17/10/2026 "muito pequeno" → 2×; 18/10 "diminua 0,5" → 1,5×) */
.af-app .oqt{flex:none;display:flex;align-items:center;gap:10px;font-weight:700}
.af-app .oqt span{min-width:20px;text-align:center;font-size:17px}
.af-app .oqt button{width:44px;height:44px;border-radius:50%;border:2px solid var(--line);background:var(--surf);color:var(--red);font-size:25px;font-weight:700;line-height:1;touch-action:manipulation}
/* NIVEL: completo [opcoes-produto] [produtos-ligados] — número do sabor tocável + teclado numérico próprio (20/10/2026) */
.af-app .oqt .oqn{min-width:44px;height:36px;padding:0 6px;display:inline-flex;align-items:center;justify-content:center;border:2px dashed var(--line);border-radius:10px;cursor:pointer;touch-action:manipulation}
.af-app .tqk-fundo{position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.45);display:flex;align-items:flex-end;justify-content:center}
.af-app .tqk{width:100%;max-width:420px;background:var(--surf);color:var(--ink);border-radius:18px 18px 0 0;padding:14px 16px calc(16px + env(safe-area-inset-bottom));box-shadow:0 -6px 24px rgba(0,0,0,.18)}
.af-app .tqk-tit{display:flex;justify-content:space-between;align-items:center;gap:10px;font-weight:700;font-size:15px}
.af-app .tqk-x{border:0;background:none;color:var(--ink-2);font-size:15px;font-weight:700;padding:8px}
.af-app .tqk-vis{margin:10px 0 12px;height:56px;border:2px solid var(--line);border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:30px;font-weight:800}
.af-app .tqk-vis.vazio{color:var(--ink-3)}
.af-app .tqk-g{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.af-app .tqk-g button{height:56px;border-radius:12px;border:2px solid var(--line);background:var(--surf);color:var(--ink);font-size:24px;font-weight:700;touch-action:manipulation}
.af-app .tqk-g .tqk-ok{background:var(--red);border-color:var(--red);color:#fff;font-size:18px}
/* Escolhas mostradas na linha da sacola. */
.af-app .cops{font-size:11.5px;color:var(--ink-2);margin-top:3px;line-height:1.35}
.af-app .pedit{background:none;border:0;padding:0;margin-top:4px;font-size:11.5px;font-weight:700;color:var(--red)}
.af-app .ribbon{position:absolute;top:8px;right:8px;font-size:9.5px;line-height:1;font-weight:700;padding:3px 7px;border-radius:6px;z-index:2;text-transform:uppercase;letter-spacing:.02em}
.af-app .big:disabled{opacity:.45;pointer-events:none}
.af-app .freebar{margin:0 14px 12px;padding:10px 12px;border-radius:10px;background:var(--warn-bg);font-size:13px;color:var(--ink)}
/* Código de confirmação de entrega (2026-08-29). Os 4 dígitos são o elemento que o
   cliente vai LER EM VOZ ALTA na porta, muitas vezes no escurinho do corredor: fonte
   grande, tabular (para 1 e 7 não se confundirem) e espaçada. Cor da loja via token,
   nunca cravada — ver _storecolors_html(). */
.af-app .confcode{margin:0 14px 14px;padding:12px;border-radius:12px;text-align:center;background:rgba(var(--cor-primaria-rgb,227,6,19),.07);border:1px dashed rgba(var(--cor-primaria-rgb,227,6,19),.35)}
.af-app .confcode small{display:block;font-size:11.5px;color:var(--ink-2);text-transform:uppercase;letter-spacing:.06em}
.af-app .confcode b{display:block;margin:4px 0 2px;font-size:34px;line-height:1.1;font-weight:800;letter-spacing:.18em;font-variant-numeric:tabular-nums;color:var(--cor-primaria,#e30613)}
.af-app .confcode span{display:block;font-size:12.5px;color:var(--ink-2)}
.af-app .freebar-track{margin-top:6px;height:6px;border-radius:4px;background:var(--warn-track);overflow:hidden}
.af-app .freebar-fill{height:100%;background:var(--gold,#ffb703);border-radius:4px;transition:width .3s}
.af-app .pcard .favbtn{position:absolute;bottom:6px;left:6px;width:26px;height:26px;border-radius:50%;background:var(--pill-bg);box-shadow:0 2px 6px rgba(0,0,0,.12);border:none;font-size:14px;line-height:26px;text-align:center;padding:0;cursor:pointer;z-index:2}
.af-app .prow .favbtn{background:var(--surf-2);border:none;font-size:16px;width:32px;height:32px;border-radius:50%;margin-right:10px;cursor:pointer;color:var(--ink-2);display:grid;place-items:center;padding:0}
.af-app .favbtn.on{color:var(--c-icone)}
.af-app .pimg{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;z-index:1;background:var(--ph-flat)}
.af-app .pcard .ph,.af-app .prow .th,.af-app .phero,.af-app .citem .th{position:relative;overflow:hidden}
.af-app .pcard h5{font-size:13px;font-weight:600;line-height:1.25;height:33px;overflow:hidden;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.af-app .pcard .pr{margin-top:6px;position:relative;height:36px;padding-right:36px;
  display:flex;flex-direction:column;justify-content:flex-end;align-items:flex-start;gap:1px}
.af-app .pcard .pr .now{color:var(--c-preco);font-weight:800;font-size:15px;line-height:20px;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.af-app .pcard .pr .old{color:var(--ink-3);font-size:11px;text-decoration:line-through;line-height:13px;height:13px;white-space:nowrap}
/* 2026-09-05 — o -X% saiu do canto SUPERIOR-ESQUERDO da foto e foi pro
   INFERIOR-DIREITO. Motivo: no card de 150px o .ribbon do lojista ("QUEIMA DE
   ESTOQUE", 18 caracteres) cresce da direita pra esquerda, passa do meio do card
   e cobria o -X% — os dois eram absolutos no mesmo `top:8px`, e quem vem depois
   no DOM pinta por cima. Censo dos cantos da foto: SE livre, SD = .ribbon (só
   quando há selo), IE = ♡ .favbtn (sempre), ID livre. Fica no ID, diagonal ao
   coração, sem nunca disputar espaço com o selo.
   ⚠️ É filho do .ph (como o ♡), não do .pcard: a foto tem 100px no celular e
   118px no desktop (:912) — ancorar no card cravaria a posição errada na tela
   grande. A base .af-app .tagoff (usada no .phero da tela de detalhe) fica
   intacta; aqui só viram os âncoras. */
.af-app .pcard .ph .tagoff{top:auto;left:auto;right:6px;bottom:6px}
/* 2026-09-17 — EXPERIMENTO só no tenant `teste` (dono: "efeito de fogo", "card em chamas", "é no
   card inteiro e faz ele se mexer levemente"). Cards do trilho "Ofertas 🔥" envolvidos em
   `.chamas`: o fogo fica POR TRÁS do card (z -1 dentro do invólucro) — halo de brasa em volta,
   labaredas saindo por cima e pelas laterais — e card + fogo balançam juntos. O invólucro existe
   porque o .pcard tem overflow:hidden e cortaria o fogo. Nome e preço continuam legíveis porque o
   fundo do card cobre o miolo. Só transform/opacity animados. O padding do trilho abre espaço
   para as labaredas (o scroll horizontal corta na vertical). Desliga com "reduzir movimento".
   NIVEL: ambos [app-loja]
   2026-10-05 (dono: "no leve mais e pague menos, a mesma animação de fogo de ofertas"): o mesmo
   fogo nos cards do trilho "Leve Mais Pague Menos" (#railPromo) — esse trilho só existe no Delivery
   completo (some no Catálogo por `.af-app.catalogo #railPromoWrap`). NIVEL: completo [trilho-promocoes] */
.af-app #railOfertas,.af-app #railPromo{padding-top:30px;padding-bottom:12px}
.af-app #railOfertas .chamas,.af-app #railPromo .chamas{position:relative;flex:none;isolation:isolate;transform-origin:50% 100%;
  animation:oferta-balanca 2.8s ease-in-out infinite}
.af-app #railOfertas .chamas:nth-child(2n),.af-app #railPromo .chamas:nth-child(2n){animation-duration:3.2s;animation-delay:-.9s}
.af-app #railOfertas .chamas:nth-child(3n),.af-app #railPromo .chamas:nth-child(3n){animation-duration:2.5s;animation-delay:-1.7s}
@keyframes oferta-balanca{0%,100%{rotate:-.9deg;translate:0 0}50%{rotate:.9deg;translate:0 -2px}}
.af-app #railOfertas .chamas .pcard,.af-app #railPromo .chamas .pcard{box-shadow:0 0 0 1.5px rgba(255,122,0,.9),0 6px 18px -6px rgba(255,70,0,.6)}
.af-app #railOfertas .fogo,.af-app #railPromo .fogo{position:absolute;left:-9px;right:-9px;top:-20px;bottom:-7px;z-index:-1;pointer-events:none}
.af-app #railOfertas .fogo::before,.af-app #railPromo .fogo::before{content:"";position:absolute;left:3px;right:3px;top:14px;bottom:0;border-radius:20px;
  background:linear-gradient(to top,#ff3d00,#ff7a00 45%,#ffb300 80%,#ffd54f);filter:blur(7px);
  animation:fogo-brasa 1.1s ease-in-out infinite alternate}
.af-app #railOfertas .fogo i,.af-app #railPromo .fogo i{position:absolute;opacity:0;border-radius:50% 50% 45% 45% / 65% 65% 35% 35%;transform-origin:50% 100%;
  background:radial-gradient(ellipse at 50% 88%,rgba(255,246,190,.95) 0%,rgba(255,184,0,.92) 24%,rgba(255,90,0,.8) 50%,rgba(255,40,0,0) 72%);
  animation:fogo-lingua 1.05s ease-out infinite}
/* labaredas de cima */
.af-app #railOfertas .fogo i:nth-child(-n+5),.af-app #railPromo .fogo i:nth-child(-n+5){top:0;width:30%;height:46px}
.af-app #railOfertas .fogo i:nth-child(1),.af-app #railPromo .fogo i:nth-child(1){left:-3%;animation-delay:-.2s}
.af-app #railOfertas .fogo i:nth-child(2),.af-app #railPromo .fogo i:nth-child(2){left:17%;height:54px;animation-duration:1.25s;animation-delay:-.65s}
.af-app #railOfertas .fogo i:nth-child(3),.af-app #railPromo .fogo i:nth-child(3){left:36%;height:58px;animation-delay:-.4s}
.af-app #railOfertas .fogo i:nth-child(4),.af-app #railPromo .fogo i:nth-child(4){left:55%;height:50px;animation-duration:1.15s;animation-delay:-.9s}
.af-app #railOfertas .fogo i:nth-child(5),.af-app #railPromo .fogo i:nth-child(5){left:74%;animation-delay:-.1s}
/* labaredas das laterais (metade escondida atrás do card) */
.af-app #railOfertas .fogo i:nth-child(n+6),.af-app #railPromo .fogo i:nth-child(n+6){width:24px;height:36%}
.af-app #railOfertas .fogo i:nth-child(6),.af-app #railPromo .fogo i:nth-child(6){left:-3px;bottom:4%;animation-delay:-.5s}
.af-app #railOfertas .fogo i:nth-child(7),.af-app #railPromo .fogo i:nth-child(7){left:-3px;bottom:44%;animation-duration:1.3s;animation-delay:-.15s}
.af-app #railOfertas .fogo i:nth-child(8),.af-app #railPromo .fogo i:nth-child(8){right:-3px;bottom:12%;animation-duration:1.2s;animation-delay:-.8s}
.af-app #railOfertas .fogo i:nth-child(9),.af-app #railPromo .fogo i:nth-child(9){right:-3px;bottom:50%;animation-delay:-.35s}
@keyframes fogo-lingua{0%{transform:translateY(30%) scale(.7,.5);opacity:0}25%{opacity:1}
  60%{transform:translateY(0) scale(.9,1);opacity:.9}100%{transform:translateY(-18%) scale(.5,1.15);opacity:0}}
@keyframes fogo-brasa{from{opacity:.55;transform:scale(.99)}to{opacity:.9;transform:scale(1.01)}}
@media (prefers-reduced-motion:reduce){
  .af-app #railOfertas .chamas,.af-app #railPromo .chamas,.af-app #railOfertas .fogo::before,.af-app #railPromo .fogo::before{animation:none}
  .af-app #railOfertas .fogo i,.af-app #railPromo .fogo i{animation:none;opacity:.75;transform:none}
}
/* 2026-09-17 — EXPERIMENTO só no tenant `teste` (dono: "o mais pedido com um formato metálico de
   ouro como se fosse uma medalha"). Cards do trilho "Mais pedidos" envolvidos em `.medalha`:
   moldura de ouro metálico, brilho de metal polido atravessando a cada ~4s e uma medalha com fita
   pendurada no canto livre da foto (superior-esquerdo). A medalha mostra a POSIÇÃO de venda
   (mp_rank, top automático da semana); destaque manual do lojista mostra ★ — não tem posição
   real e um número ali seria mentira. O invólucro existe porque o .pcard tem overflow:hidden e
   cortaria a medalha. Brilho desliga com "reduzir movimento". NIVEL: ambos [app-loja] */
.af-app #railTop{padding-top:12px}
.af-app #railTop .medalha{position:relative;flex:none;isolation:isolate;padding:3px;border-radius:17px;
  background:linear-gradient(135deg,#8a5a00 0%,#f7d774 17%,#fff3b8 29%,#d4a017 44%,#9c6b00 58%,#f5d061 75%,#fff1a8 86%,#b8860b 100%);
  box-shadow:0 7px 18px -7px rgba(150,100,0,.6),inset 0 0 0 1px rgba(255,255,255,.35)}
.af-app #railTop .medalha .pcard{border-radius:14px;box-shadow:inset 0 0 0 1px rgba(160,110,0,.25)}
.af-app #railTop .medalha-luz{position:absolute;inset:0;border-radius:17px;overflow:hidden;pointer-events:none;z-index:3}
.af-app #railTop .medalha-luz::before{content:"";position:absolute;top:-10%;bottom:-10%;left:-60%;width:45%;
  background:linear-gradient(100deg,rgba(255,255,255,0) 0%,rgba(255,252,230,.55) 50%,rgba(255,255,255,0) 100%);
  transform:skewX(-14deg);animation:ouro-luz 4.2s ease-in-out infinite}
.af-app #railTop .medalha:nth-child(2n) .medalha-luz::before{animation-delay:-1.4s}
.af-app #railTop .medalha:nth-child(3n) .medalha-luz::before{animation-delay:-2.8s}
@keyframes ouro-luz{0%{transform:translateX(0) skewX(-14deg)}38%,100%{transform:translateX(520%) skewX(-14deg)}}
.af-app #railTop .medalha-selo{position:absolute;top:-9px;left:-7px;width:34px;height:34px;z-index:4;pointer-events:none}
.af-app #railTop .medalha-selo b{position:absolute;inset:0;border-radius:50%;display:grid;place-items:center;
  font-size:14px;font-weight:900;line-height:1;color:#6b4300;text-shadow:0 1px 0 rgba(255,255,255,.65);
  background:radial-gradient(circle at 34% 28%,#fffbe0 0,#ffe27a 18%,#f0b92b 46%,#c08400 74%,#8a5a00 100%);
  box-shadow:inset 0 0 0 2px rgba(255,236,160,.95),inset 0 0 0 4px rgba(150,95,0,.5),0 3px 8px rgba(110,70,0,.45)}
.af-app #railTop .medalha-selo i{position:absolute;top:21px;width:11px;height:19px;z-index:-1;background:var(--red,#e30613);
  clip-path:polygon(0 0,100% 0,100% 100%,50% 76%,0 100%);box-shadow:inset -3px 0 0 rgba(0,0,0,.18)}
.af-app #railTop .medalha-selo i:first-child{left:5px;transform:rotate(14deg)}
.af-app #railTop .medalha-selo i:last-child{right:5px;transform:rotate(-14deg)}
@media (prefers-reduced-motion:reduce){ .af-app #railTop .medalha-luz::before{animation:none;opacity:0} }
/* Efeitos animados DESLIGADOS pelo lojista (2026-09-17, Configurações › Personalizar cores). A
   classe `sem-anim` nasce no HTML servido (_storemodoclass_html). Ofertas voltam ao card normal
   (fogo é animação por natureza); a medalha fica, parada, sem o brilho; luzes do tema escuro
   param e somem. Spinner de carregamento e letreiros de texto continuam. NIVEL: ambos [cores] */
.af-app.sem-anim #railOfertas,.af-app.sem-anim #railPromo{padding-top:0;padding-bottom:6px}
.af-app.sem-anim #railOfertas .chamas,.af-app.sem-anim #railPromo .chamas{animation:none}
.af-app.sem-anim #railOfertas .fogo,.af-app.sem-anim #railPromo .fogo{display:none}

/* ==================== VITRINE DE FESTA — NIVEL: ambos [vitrine-festa] (2026-09-30) ====================
   Fileira entre Ofertas e Mais pedidos com o nome escolhido pela loja. Clima de festa: FOGOS ao lado do
   título, CONFETE caindo no alto da fileira e BALÕES subindo por trás de cada card. Molde do .chamas: o
   invólucro .festa-card existe porque o .pcard tem overflow:hidden e cortaria os balões. Só
   transform/opacity. Nome, preço e o "+" ficam livres: o que é decorativo fica ATRÁS ou ACIMA do card.
   `sem-anim` e prefers-reduced-motion: some o movimento, fica o visual (moldura dourada + 🎉). */
.af-app #railFestaWrap .rh{position:relative;isolation:isolate}
.af-app #railFestaWrap .rh h4{position:relative;z-index:1}
.af-app #railFesta{padding-top:40px;padding-bottom:10px}
.af-app #railFesta .festa-card{position:relative;flex:none;isolation:isolate}
.af-app #railFesta .festa-card .pcard{position:relative;z-index:1;box-shadow:0 0 0 2px rgba(255,196,0,.6),var(--sh)}
.af-app #railFesta .baloes{position:absolute;left:0;right:0;top:0;height:100%;z-index:0;pointer-events:none}
.af-app #railFesta .baloes i{position:absolute;top:14%;width:20px;height:24px;border-radius:50% 50% 46% 46%/58% 58% 42% 42%;
  background:radial-gradient(circle at 32% 28%,rgba(255,255,255,.75) 0 12%,transparent 13%),var(--bal,#ff3d6e);
  opacity:0;animation:festa-balao 6.5s ease-in-out infinite}
.af-app #railFesta .baloes i::after{content:"";position:absolute;left:50%;top:100%;width:1px;height:16px;background:rgba(0,0,0,.25)}
.af-app #railFesta .baloes i:nth-child(1){left:8%;--bal:#ff3d6e;animation-delay:-.4s}
.af-app #railFesta .baloes i:nth-child(2){left:44%;--bal:#ffc400;animation-delay:-2.6s;animation-duration:7.2s}
.af-app #railFesta .baloes i:nth-child(3){right:8%;--bal:#2f80ff;animation-delay:-4.8s;animation-duration:6s}
.af-app #railFesta .festa-card:nth-child(2n) .baloes i:nth-child(1){--bal:#22c55e}
.af-app #railFesta .festa-card:nth-child(3n) .baloes i:nth-child(2){--bal:#a855f7}
.af-app #railFesta .festa-card:nth-child(2n+1) .baloes i:nth-child(3){--bal:#ff7a1a}
@keyframes festa-balao{
  0%{transform:translate(0,0) rotate(-6deg);opacity:0}
  15%{opacity:1}
  50%{transform:translate(5px,-38px) rotate(5deg)}
  82%{opacity:1}
  100%{transform:translate(-4px,-76px) rotate(-4deg);opacity:0}}
.af-app #railFestaWrap .festa-confete{position:absolute;left:0;right:0;top:-6px;height:78px;z-index:2;pointer-events:none;overflow:hidden}
.af-app #railFestaWrap .festa-confete i{position:absolute;top:-10px;width:6px;height:10px;border-radius:1px;
  background:var(--cf,#ffc400);opacity:0;animation:festa-confete 3.6s linear infinite}
.af-app #railFestaWrap .festa-confete i:nth-child(1){left:4%;--cf:#ff3d6e;animation-delay:-0.0s;animation-duration:3.2s}
.af-app #railFestaWrap .festa-confete i:nth-child(2){left:12%;--cf:#ffc400;animation-delay:-0.4s;animation-duration:3.7s}
.af-app #railFestaWrap .festa-confete i:nth-child(3){left:20%;--cf:#2f80ff;animation-delay:-0.7s;animation-duration:4.2s}
.af-app #railFestaWrap .festa-confete i:nth-child(4){left:28%;--cf:#22c55e;animation-delay:-1.1s;animation-duration:3.2s}
.af-app #railFestaWrap .festa-confete i:nth-child(5){left:36%;--cf:#a855f7;animation-delay:-1.5s;animation-duration:3.7s}
.af-app #railFestaWrap .festa-confete i:nth-child(6){left:44%;--cf:#ff7a1a;animation-delay:-1.9s;animation-duration:4.2s}
.af-app #railFestaWrap .festa-confete i:nth-child(7){left:52%;--cf:#ff3d6e;animation-delay:-2.2s;animation-duration:3.2s}
.af-app #railFestaWrap .festa-confete i:nth-child(8){left:60%;--cf:#ffc400;animation-delay:-2.6s;animation-duration:3.7s}
.af-app #railFestaWrap .festa-confete i:nth-child(9){left:68%;--cf:#2f80ff;animation-delay:-3.0s;animation-duration:4.2s}
.af-app #railFestaWrap .festa-confete i:nth-child(10){left:76%;--cf:#22c55e;animation-delay:-3.3s;animation-duration:3.2s}
.af-app #railFestaWrap .festa-confete i:nth-child(11){left:84%;--cf:#a855f7;animation-delay:-0.1s;animation-duration:3.7s}
.af-app #railFestaWrap .festa-confete i:nth-child(12){left:92%;--cf:#ff7a1a;animation-delay:-0.5s;animation-duration:4.2s}
@keyframes festa-confete{
  0%{transform:translate(0,0) rotate(0);opacity:0}
  10%{opacity:1}
  85%{opacity:1}
  100%{transform:translate(14px,84px) rotate(540deg);opacity:0}}
.af-app #railFestaWrap .festa-fogos{position:absolute;left:0;right:0;top:-16px;height:60px;z-index:0;pointer-events:none}
.af-app #railFestaWrap .festa-fogos i{position:absolute;width:4px;height:4px;border-radius:50%;opacity:0;
  box-shadow:0 -22px 0 #ffc400,16px -16px 0 #ff3d6e,22px 0 0 #2f80ff,16px 16px 0 #22c55e,0 22px 0 #a855f7,
    -16px 16px 0 #ff7a1a,-22px 0 0 #ffc400,-16px -16px 0 #ff3d6e;
  animation:festa-fogo 3.9s ease-out infinite}
.af-app #railFestaWrap .festa-fogos i:nth-child(1){left:62%;top:26px}
.af-app #railFestaWrap .festa-fogos i:nth-child(2){left:78%;top:14px;animation-delay:-1.3s}
.af-app #railFestaWrap .festa-fogos i:nth-child(3){left:91%;top:32px;animation-delay:-2.6s}
@keyframes festa-fogo{
  0%{transform:scale(.1);opacity:0}
  8%{opacity:1}
  35%{transform:scale(1.15);opacity:.9}
  55%{transform:scale(1.3);opacity:0}
  100%{transform:scale(1.3);opacity:0}}
/* Festa DENTRO do card (dono, 01/10) — só na área da foto (.ph): confete caindo, 3 brilhos piscando e uma
   faixa de luz atravessando. pointer-events:none: o toque vai para o card/coração como sempre. */
.af-app #railFesta .pcard .ph{position:relative;overflow:hidden}
/* 🔴 z-index 2, não 1 (01/10): a FOTO do produto (.pimg) é absoluta, de fundo sólido, z-index 1 e vem
   DEPOIS no HTML — com 1 ela cobria a festa inteira. Só aparecia no `teste`, onde os produtos da festa
   não têm foto; no combas (todos com foto) sumia. Coração e selo (-X%) também são 2 e vêm depois:
   continuam por cima da festa. */
.af-app #railFesta .festa-dentro{position:absolute;inset:0;z-index:2;pointer-events:none;overflow:hidden}
.af-app #railFesta .festa-dentro::before{content:"";position:absolute;top:-20%;bottom:-20%;left:-60%;width:40%;
  background:linear-gradient(105deg,transparent,rgba(255,255,255,.55),transparent);transform:translateX(0) skewX(-12deg);
  animation:festa-luz 3.8s ease-in-out infinite}
.af-app #railFesta .festa-card:nth-child(2n) .festa-dentro::before{animation-delay:-1.9s}
@keyframes festa-luz{0%,55%{transform:translateX(0) skewX(-12deg)}100%{transform:translateX(420%) skewX(-12deg)}}
.af-app #railFesta .festa-dentro i{position:absolute;top:-10px;width:5px;height:8px;border-radius:1px;background:var(--cf,#ffc400);
  opacity:0;animation:festa-confete-dentro 2.8s linear infinite}
.af-app #railFesta .festa-dentro i:nth-child(1){left:6%;--cf:#ff3d6e;animation-delay:-0.0s;animation-duration:2.6s}
.af-app #railFesta .festa-dentro i:nth-child(2){left:18%;--cf:#ffc400;animation-delay:-0.5s;animation-duration:3.1s}
.af-app #railFesta .festa-dentro i:nth-child(3){left:30%;--cf:#2f80ff;animation-delay:-1.1s;animation-duration:3.5s}
.af-app #railFesta .festa-dentro i:nth-child(4){left:42%;--cf:#22c55e;animation-delay:-1.6s;animation-duration:2.6s}
.af-app #railFesta .festa-dentro i:nth-child(5){left:54%;--cf:#a855f7;animation-delay:-2.1s;animation-duration:3.1s}
.af-app #railFesta .festa-dentro i:nth-child(6){left:66%;--cf:#ff7a1a;animation-delay:-2.7s;animation-duration:3.5s}
.af-app #railFesta .festa-dentro i:nth-child(7){left:78%;--cf:#ffc400;animation-delay:-0.2s;animation-duration:2.6s}
.af-app #railFesta .festa-dentro i:nth-child(8){left:90%;--cf:#ff3d6e;animation-delay:-0.7s;animation-duration:3.1s}
@keyframes festa-confete-dentro{
  0%{transform:translate(0,0) rotate(0);opacity:0}
  12%{opacity:.95}
  85%{opacity:.95}
  100%{transform:translate(-8px,118px) rotate(420deg);opacity:0}}
.af-app #railFesta .festa-dentro b{position:absolute;width:10px;height:10px;background:#fff6c2;opacity:0;
  clip-path:polygon(50% 0,62% 38%,100% 50%,62% 62%,50% 100%,38% 62%,0 50%,38% 38%);
  filter:drop-shadow(0 0 3px rgba(255,196,0,.9));animation:festa-brilho 2.4s ease-in-out infinite}
.af-app #railFesta .festa-dentro b:nth-of-type(1){left:14%;top:18%}
.af-app #railFesta .festa-dentro b:nth-of-type(2){right:16%;top:30%;animation-delay:-.8s}
.af-app #railFesta .festa-dentro b:nth-of-type(3){left:46%;bottom:14%;animation-delay:-1.6s}
@keyframes festa-brilho{0%,100%{transform:scale(.2) rotate(0);opacity:0}45%{transform:scale(1.15) rotate(45deg);opacity:1}70%{opacity:0}}
.af-app.sem-anim #railFesta{padding-top:6px}
.af-app.sem-anim #railFestaWrap .festa-confete,.af-app.sem-anim #railFestaWrap .festa-fogos,
.af-app.sem-anim #railFesta .baloes,.af-app.sem-anim #railFesta .festa-dentro{display:none}
@media (prefers-reduced-motion:reduce){
  .af-app #railFesta{padding-top:6px}
  .af-app #railFestaWrap .festa-confete,.af-app #railFestaWrap .festa-fogos,.af-app #railFesta .baloes,
  .af-app #railFesta .festa-dentro{display:none}
}
.af-app.sem-anim #railOfertas .chamas .pcard,.af-app.sem-anim #railPromo .chamas .pcard{box-shadow:var(--sh)}
.af-app.sem-anim #railTop .medalha-luz{display:none}
.af-app.sem-anim.dark .tophero::after,.af-app.sem-anim.dark .banner::after,
.af-app.sem-anim.dark .freebar-fill::after,.af-app.sem-anim.dark .cartfab button::after{animation:none;display:none}
/* Rede de segurança do selo do lojista: texto longo não atravessa o card. */
.af-app .pcard .ribbon{max-width:calc(100% - 16px);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.af-app .pcard .addc{position:absolute;right:0;bottom:0;width:30px;height:30px;border-radius:9px;background:var(--c-sacola);color:var(--c-sacola-ink);font-size:19px;font-weight:700;display:grid;place-items:center;box-shadow:0 4px 10px rgba(var(--c-sacola-rgb),.3)}
/* 2026-09-17 — etiquetas numa faixa de ALTURA FIXA (2 etiquetas), para o card nunca mudar
   de tamanho nem empurrar nada. O botão + mora na linha de preço (canto fixo), então a
   faixa usa a largura inteira. Substitui o rodapé flex de 2026-08-24 (.pfoot/.com-msg),
   que movia o + conforme o número de etiquetas. */
.af-app .pcard .pmsgs{margin-top:6px;height:35px;display:flex;flex-direction:column;align-items:flex-start;gap:3px;overflow:hidden}
.af-app .pcard .pmsgs .promotag,.af-app .pcard .pmsgs .pesotag{margin-top:0;max-width:100%;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;font-size:10px;line-height:10px;padding:3px 6px}
/* Preço oculto ocupa o lugar do preço: até 2 linhas dentro da própria pílula, ancorada
   embaixo da linha de preço (o preço antigo não aparece nesse caso). */
.af-app .pcard .pr .now:has(.precotag){white-space:normal;overflow:visible;line-height:1}
.af-app .pcard .pr .precotag{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;white-space:normal;
  text-overflow:clip;font-size:10.5px;line-height:1.15;padding:2px 8px;border-radius:10px}
/* Card estreito (grade de 5 colunas numa coluna apertada, ex.: "Costuma ir junto"): só a
   FONTE do preço diminui — a posição continua a mesma. */
@container (max-width:140px){
  .af-app .pcard .pr .now{font-size:13px}
  .af-app .pcard .pmsgs .promotag,.af-app .pcard .pmsgs .pesotag{font-size:9.5px}
}

/* ===== lista vertical (busca / categoria) ===== */
.af-app .listwrap{padding:14px 0 6px;background:transparent}
/* Marcador da lista incremental (2026-09-17): ocupa a linha inteira também na grade de 2
   colunas do computador. */
.af-app .lista-mais{grid-column:1/-1;display:flex;align-items:center;justify-content:center;gap:10px;
  padding:18px 0 22px;font-size:13px;color:var(--ink-3)}
.af-app .lista-spin{width:18px;height:18px;border-radius:50%;border:2.5px solid var(--line);
  border-top-color:var(--c-icone,var(--red));animation:luz-spin .7s linear infinite}
.af-app .prow{display:flex;align-items:center;gap:12px;padding:13px 14px;background:var(--surf);border-bottom:1px solid var(--line);min-width:0}
.af-app .ribbon-inline{display:inline-block;font-size:9.5px;line-height:1;font-weight:700;padding:2px 6px;border-radius:5px;text-transform:uppercase;letter-spacing:.02em;margin-right:6px;vertical-align:middle}
.af-app .prow .th{width:64px;height:64px;flex:none;border-radius:12px;display:grid;place-items:center;font-size:30px;background:var(--ph-bg)}
.af-app .prow .pi{flex:1;min-width:0;align-self:stretch}
/* 2026-09-17 — linha padronizada: nome e descrição em 1 linha (selo ANTES do nome, para
   nunca ser cortado), linha de preço e faixa de etiquetas com altura fixa. */
.af-app .prow .pi h5{font-size:14px;font-weight:600;line-height:18px;height:18px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.af-app .prow .pi .d{font-size:12px;color:var(--ink-2);margin-top:2px;line-height:16px;height:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.af-app .prow .pi .pr{margin-top:4px;height:34px;display:flex;flex-direction:column;justify-content:flex-end;align-items:flex-start;gap:1px}
.af-app .prow .pi .pmsgs{margin-top:4px;height:16px;display:flex;gap:4px;overflow:hidden}
.af-app .prow .pi .pmsgs .promotag,.af-app .prow .pi .pmsgs .pesotag{margin-top:0;flex:0 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:10px;line-height:10px;padding:3px 6px}
.af-app .prow .pi .pr .now{color:var(--c-preco);font-weight:800;font-size:15px;line-height:20px;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.af-app .prow .pi .pr .old{color:var(--ink-3);font-size:11px;text-decoration:line-through;line-height:13px;height:13px;white-space:nowrap}
/* Preço oculto na linha: a pílula é mais alta que os 20px do preço — sem isto o overflow do
   .now cortava a base de "LIGAR P/ TELEVENDAS" (visto a olho em 17/09). */
.af-app .prow .pi .pr .now:has(.precotag){overflow:visible;line-height:1}
.af-app .prow .act{display:flex;align-items:center;flex:none}
.af-app .prow .addc{width:32px;height:32px;border-radius:9px;background:var(--c-sacola);color:var(--c-sacola-ink);font-size:20px;font-weight:700;display:grid;place-items:center;box-shadow:0 4px 10px rgba(var(--c-sacola-rgb),.25)}
.af-app .prow .stp{display:none;align-items:center;gap:9px;background:var(--surf);border:1px solid var(--c-sacola);border-radius:9px;padding:3px 5px}
.af-app .prow.in .addc{display:none}
.af-app .prow.in .stp{display:flex}
.af-app .prow .stp button{width:24px;height:24px;border-radius:6px;background:var(--surf);color:var(--c-sacola);font-size:16px;font-weight:800;display:grid;place-items:center}
.af-app .prow .stp .q{font-weight:800;color:var(--c-sacola);min-width:14px;text-align:center;font-size:14px}

/* ===== abas inferiores ===== */
.af-app .tabbar{position:fixed;bottom:0;left:50%;transform:translateX(-50%);width:100%;max-width:var(--w);z-index:40;background:var(--surf);border-top:1px solid var(--line);display:flex;padding:8px 0 calc(8px + env(safe-area-inset-bottom))}
.af-app .tab{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;color:var(--ink-3);font-size:10.5px;font-weight:600}
.af-app .tab.on{color:var(--red)}
.af-app .tab svg{width:22px;height:22px}
.af-app .tab .badge{position:absolute;transform:translate(12px,-4px);min-width:16px;height:16px;background:var(--red);color:var(--red-ink);font-size:10px;font-weight:700;border-radius:100px;display:grid;place-items:center;padding:0 4px}
/* Badge DENTRO de uma linha de lista (Perfil → "Falar com a loja"), 2026-09-02.
   🔴 Antes reusava `.nbadge`, que só tem estilo em `.notifbell .nbadge` (notifbell.css):
   fora do sino o número saía como texto solto, sem a bolinha. `.tab .badge` também não
   servia — depende do ancestral `.tab` e é `position:absolute`. Mesmas variáveis de cor
   do badge das abas, nada cravado. */
.af-app .prow2 .rowbadge{margin-left:8px;min-width:16px;height:16px;background:var(--red);color:var(--red-ink);font-size:10px;font-weight:700;border-radius:100px;display:inline-grid;place-items:center;padding:0 5px;vertical-align:middle}

/* ===== barra sacola flutuante ===== */
.af-app .cartfab{position:fixed;bottom:78px;left:50%;transform:translateX(-50%) translateY(16px);width:calc(100% - 28px);max-width:calc(var(--w) - 28px);z-index:45;opacity:0;visibility:hidden;pointer-events:none;transition:transform .3s var(--ease),opacity .3s var(--ease),visibility 0s linear .3s}
.af-app .cartfab.show{transform:translateX(-50%) translateY(0);opacity:1;visibility:visible;pointer-events:auto;transition:transform .3s var(--ease),opacity .3s var(--ease),visibility 0s linear 0s}
.af-app .cartfab button{width:100%;background:var(--c-sacola);color:var(--c-sacola-ink);border-radius:12px;padding:14px 16px;display:flex;align-items:center;justify-content:space-between;box-shadow:0 10px 26px rgba(var(--c-sacola-rgb),.35);font-weight:700}
.af-app .cartfab .l{display:flex;align-items:center;gap:10px;font-size:14px}
.af-app .cartfab .n{width:22px;height:22px;border-radius:7px;background:rgba(255,255,255,.22);display:grid;place-items:center;font-size:12px}
.af-app .cartfab .tot{font-size:15px}
/* NIVEL: completo [reserva-sacola] — relógio da reserva no botão e na sacola + aviso do topo (26/10/2026, dono) */
.af-app .cartfab .rt{font-size:12.5px;font-weight:800;padding:3px 8px;border-radius:999px;background:rgba(255,255,255,.2);font-variant-numeric:tabular-nums;white-space:nowrap}
.af-app .cartfab .rt.urg{background:#ffd54d;color:#1c1c1c}
.af-app .cartfab .rt.fim{background:rgba(0,0,0,.22)}
.af-app .res-sac{margin:0 16px 10px;padding:9px 12px;border-radius:10px;background:rgba(127,127,127,.12);font-size:13px;font-weight:700;text-align:center;font-variant-numeric:tabular-nums}
.af-app .res-sac.urg{background:#fff4d6;color:#8a5a00}
.af-app .res-sac.fim{opacity:.75}
.af-app .res-aviso{position:fixed;top:calc(8px + env(safe-area-inset-top));left:50%;transform:translate(-50%,-180%);width:calc(100% - 24px);max-width:520px;z-index:200;display:flex;gap:10px;align-items:center;padding:11px 12px 11px 14px;border-radius:12px;background:#1c1c1c;color:#fff;font-size:13.5px;line-height:1.35;box-shadow:0 10px 30px rgba(0,0,0,.3);transition:transform .25s ease;visibility:hidden}
.af-app .res-aviso.on{transform:translate(-50%,0);visibility:visible}
.af-app .res-aviso button{flex:none;background:#fff;color:#1c1c1c;border:0;border-radius:8px;padding:7px 14px;font-weight:800}

/* ===== topbar sub-telas ===== */
.af-app .tb{position:sticky;top:0;z-index:20;background:var(--surf);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:8px;height:54px;padding:0 8px}
.af-app .tb .bk{width:40px;height:40px;display:grid;place-items:center;border-radius:50%;color:var(--ink);flex:none}
/* Título centralizado (2026-08-26) — position:absolute independe do que existe à
   direita (ex.: .notifbell em 5 telas), então nunca precisa recalcular o espaço livre.
   max-width com ellipsis evita que um título comprido cubra o botão de voltar OU o
   sino/ação à direita — os dois ficam por cima (.bk e .notifbell têm z-index maior
   implícito por virem depois no DOM/stacking do flex). */
.af-app .tb b{font-size:16px;font-weight:800;position:absolute;left:50%;top:50%;
  transform:translate(-50%,-50%);max-width:60%;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;pointer-events:none}
.af-app .tb .notifbell{margin-left:auto;position:relative;z-index:1}
.af-app .tb .tbaction{margin-left:auto;position:relative;z-index:1;background:none;border:0;
  color:var(--red);font-weight:700;font-size:14px;padding:8px 10px}
/* Visitante sem conta não tem notificação nenhuma pra ver — o sino (e o badge) só faziam
   prometer algo que não existe. Via classe no root, e não escondendo elemento a elemento,
   porque tb() recria o sino a cada render de tela. Ver syncAnonUI(). */
.af-app.anon [data-bell]{display:none}

/* Nível Catálogo (2026-08-09) — a classe `catalogo` é decidida NO SERVIDOR e já vem no
   HTML (marcador #storemodoclass, ver app_render.py). Antes disso, estas 4 ocultações
   rodavam só no callback de /delivery/config: como o painel volta pra loja com
   location.href='/' (reload completo), o cliente via a barra de Entrega, o trilho de
   promoção, o sino e a aba Pedidos por alguns instantes ANTES de sumirem — o flash que
   o usuário reportou 3x. CSS servido no mesmo documento não perde corrida pra fetch.
   O JS (syncModoUI) mantém a classe sincronizada, como rede de segurança e para o caso
   de troca de nível sem reload. Mesmo padrão de `.af-app.anon` acima. */
/* B1 (2026-08-14) — selo que substitui o preço. #e30613 LITERAL de propósito: var(--red)
   é a cor primária da loja (preta no tocomsede) e o selo sumiria. O usuário pediu
   vermelho — vermelho sai em qualquer loja. */
/* B3 — '−' esmaecido no piso. (O 🗑️ .rmitem saiu em 2026-08-26 a pedido do dono; a
   remoção agora é digitar 0 no campo de quantidade ou descer no '−'.) */
.af-app .stp button[disabled]{opacity:.35;pointer-events:none}
/* '−' em modo REMOVER (2026-08-26): quando o próximo toque some com o item, o botão
   vira lixeirinha. Fonte menor porque emoji ocupa mais que o traço, e um tom de
   alerta para o toque não parecer "diminuir". */
.af-app .stp button.mm.rm,.af-app .stp button.pmm.rm{font-size:13px;color:var(--red)}
.af-app .qbar button[disabled]{opacity:.4;pointer-events:none}
.af-app .precotag{display:inline-block;background:#e30613;color:#fff;border-radius:100px;
  padding:2px 10px;font-size:12px;font-weight:800;letter-spacing:.2px;white-space:nowrap;
  max-width:100%;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}
/* Tela de detalhe tem espaço de sobra — nunca corta lá, só nas telas apertadas
   (card, lista, sacola). */
.af-app .prc .precotag{font-size:15px;padding:4px 14px;max-width:none}
/* NIVEL: completo [modebar] — `.batl` (atalhos da busca, 20/10/2026) fica de fora: as 2 regras eram da barra Entrega/Retirar
   da home, mas pegavam toda `.modebar` — e os atalhos (classe emprestada para o desenho) sumiam no Catálogo e em toda loja
   com a barra desligada (atacadão, combas, alitem). Os chips de categoria/marcador da busca continuam como estavam. */
.af-app.catalogo .modebar:not(.batl){display:none}
/* Delivery completo (2026-08-13): mesma barra, agora desligável por config
   (modebar_ativa) fora do catálogo — ver syncModoUI() e _storemodoclass_html(). */
.af-app.sem-modebar .modebar:not(.batl){display:none}
/* Chat com a loja — desenho em _shared/chatui.css (01/10/2026, plano
   delivery-chat-interface-plano.md). Aqui só o que é do app: a coluna ocupa a tela até o
   menu de baixo (altura medida em chatuiCompositor), a pílula flutua ACIMA da tabbar (40)
   e abaixo das folhas (95), na largura do app. NIVEL: ambos [atendimento-chat] */
.af-app{--cu-z:45;--cu-maxw:var(--w)}
.af-app .chatwrap{display:flex;flex-direction:column;min-height:0}
.af-app .chatwrap .cu-lista{flex:1;min-height:0}
.af-app .chathist{margin:12px 14px;border:1px solid var(--line);border-radius:16px;background:var(--surf-2);overflow:hidden}
.af-app .chathist .cu-lista{padding:6px 12px 12px;animation:none}
/* Card de pedido expansível (Frente G, 2026-09-02) — status+itens+chat dentro do próprio
   card de "Meus pedidos", substituindo a tela separada de detalhe. Reusa as MESMAS classes
   do chat de tela cheia (.chatmsgs/.chatstatus/.chatbar) — são compartilhadas porque a
   conversa é ÚNICA por cliente: chatPintarMsgs() atualiza TODAS as cópias na tela ao mesmo
   tempo, cheia ou embutida. Só o tamanho muda aqui (a versão cheia usa 100vh; embutida
   precisa de um teto, senão o card cresce até engolir a tela). */
.af-app .ocard .oh{cursor:pointer;user-select:none}
.af-app .ocard .oh .oc-chev{margin-left:8px;color:var(--ink-2);font-size:12px;flex:none}
.af-app .oc-sec{padding:12px 14px 6px;font-size:12px;font-weight:800;color:var(--ink-2);
  text-transform:uppercase;letter-spacing:.03em;border-top:1px solid var(--line)}

/* Modalidades (2026-09-02): os chips existem sempre no HTML estático e quem decide quais
   aparecem é a classe injetada pelo servidor (_storemodoclass_html) — só assim o primeiro
   frame já mostra as opções certas. "Agendar" é entrega com horário: cai junto do motoboy. */
.af-app.sem-motoboy .modebar [data-mode="entrega"],
.af-app.sem-motoboy .modebar [data-mode="agendar"]{display:none}
.af-app .modebar [data-mode="app"]{display:none}
.af-app.com-coleta-app .modebar [data-mode="app"]{display:flex}
/* NIVEL: completo [agendamento] — a loja desligou o agendamento (09/10/2026): o "Agendar" da
   barra some. Classe vinda do servidor (_storemodoclass_html) e confirmada em syncModoUI. */
.af-app.sem-agendar .modebar [data-mode="agendar"]{display:none}
/* Letreiro de fornecedores (Bloco C, 2026-08-11) — ocupa o espaço que a .modebar deixa
   vazio no modo Catálogo. Mesma mecânica de .bname.marquee: 2 cópias lado a lado,
   translateX(-50%) em loop. display:none por padrão — só liga via JS quando há
   fornecedor ativo (nunca deixar a faixa vazia, ver renderFornecedores). */
.af-app .forn-letreiro{display:none;overflow:hidden;padding:12px 14px 6px}
.af-app .forn-letreiro.on{display:block}
.af-app .forn-track{display:inline-flex;width:max-content;animation:forn-marquee linear infinite;animation-duration:var(--forn-dur,16s)}
.af-app .forn-seg{display:inline-flex;gap:22px;padding-right:22px}
.af-app .forn-seg img{height:40px;width:auto;max-width:120px;object-fit:contain;flex:none;border-radius:8px;background:var(--surf);box-shadow:var(--sh);padding:4px}
@keyframes forn-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){
  .af-app .forn-track{animation:none;overflow-x:auto;flex-wrap:nowrap}
  .af-app .forn-seg:nth-child(2){display:none}
}
/* NIVEL: completo [trilho-promocoes] */
.af-app.catalogo #railPromoWrap{display:none}
/* NIVEL: completo [notif-cliente] */
.af-app.catalogo [data-bell]{display:none}
/* NIVEL: completo [pedidos-cliente] */
.af-app.catalogo #tabbar .tab[data-go="pedidos"]{display:none}

/* ===== sino de notificações (header fixo, não flutuante) — ver _shared/notifbell.css ===== */
/* _shared/tokens.css — aliases canônicos usados pelos componentes de _shared/.
   Cada app já define --red, --ink, --line, --header-*, etc no seu próprio :root/.af-app.
   Este arquivo só acrescenta os nomes que faltam em admin.html/moto.html (--ink-2,
   --ink-3, --surf), apontando pro token equivalente que cada app já tem (--muted, #fff),
   para que _shared/notifbell.css sirva aos 3 sem duplicar cor.
   Ver docs/modulos/delivery-padronizacao-e-ajustes-plano.md §5.2/§5.3.

   ⚠️ NUNCA escrever `--x: var(--x, fallback)` aqui: uma custom property que referencia
   a si mesma no mesmo escopo é CICLO, e o CSS a trata como guaranteed-invalid — a
   variável deixa de resolver e quem usa `var(--x)` fica SEM valor. Foi o que aconteceu
   entre a F1 e a F1d: `--surf` não resolvia, o `.notifpanel` ficou com fundo
   transparente no /admin e no /moto (a loja escapou porque define --surf própria) e o
   conteúdo de trás aparecia através do painel. Valores literais abaixo são os mesmos
   que a loja já usa, então os 3 apps ficam idênticos. */
:root{
  --ink-2: #717171;
  --ink-3: #a9a9a9;
  --surf: #ffffff;
}

/* _shared/notifbell.css — sino + painel dropdown de notificações.
   Fonte canônica: app-ifood.html (confirmado pelo cliente: "o da loja é a correta").
   Usa var(--ink-2)/var(--ink-3)/var(--surf) resolvidos por tokens.css nos apps que não
   os definem nativamente (admin/moto). NÃO redefinir estes seletores fora daqui —
   tools/check_shared_delivery.py falha se detectar duplicata.
   Ver docs/modulos/delivery-padronizacao-e-ajustes-plano.md §5.2/§5.3. */
.notifbell{position:relative;width:40px;height:40px;display:grid;place-items:center;border-radius:50%;background:transparent;color:inherit;border:0;font-size:19px;line-height:1;cursor:pointer}
.notifbell .nbadge{position:absolute;top:2px;right:2px;background:var(--red);color:var(--red-ink);border-radius:50%;min-width:16px;height:16px;font-size:10px;font-weight:800;line-height:16px;display:none}
.notifbell .nbadge.show{display:block}
.notifpanel{position:absolute;top:100%;right:8px;z-index:60;background:var(--surf);color:var(--ink);border-radius:12px;box-shadow:0 8px 28px rgba(0,0,0,.22);width:290px;max-width:calc(100vw - 16px);font-size:13px;overflow:hidden}
.notifpanel .np-top{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--line)}
.notifpanel .np-top b{font-size:14px}
.notifpanel .np-marktodas{color:var(--red);font-weight:700;font-size:12px;background:none;border:0;cursor:pointer}
.notifpanel .np-list{max-height:290px;overflow-y:auto}
.notifpanel .np-item{display:flex;gap:8px;padding:11px 12px;border-bottom:1px solid var(--line);cursor:pointer}
.notifpanel .np-item:hover{background:rgba(0,0,0,.03)}
.notifpanel .np-item .np-dot{flex:none;width:8px;height:8px;border-radius:50%;margin-top:5px;background:var(--ink-3)}
.notifpanel .np-item .np-dot.critico{background:var(--red)}
.notifpanel .np-item .np-body{flex:1;min-width:0}
.notifpanel .np-item b{display:block;font-size:13px}
.notifpanel .np-item small{color:var(--ink-2);font-size:11.5px}
.notifpanel .np-item .np-lida{flex:none;color:var(--ink-3);font-size:11px;background:none;border:0;cursor:pointer;padding:2px 4px}
.notifpanel .np-empty{padding:20px 12px;text-align:center;color:var(--ink-2)}
.notifpanel .np-sys{display:flex;gap:8px;padding:12px;border-top:1px solid var(--line)}
/* color:var(--surf) e não #fff (2026-08-31): o fundo deste botão é var(--ink), que no
   tema escuro é CLARO — texto branco sumiria dentro dele. Com --surf o par sempre se
   inverte junto. No tema claro os dois valem #ffffff, então admin e moto não mudam. */
.notifpanel .np-sys button{margin-top:6px;background:var(--ink);color:var(--surf);border:0;border-radius:8px;padding:6px 10px;font-size:12px}
.notifpanel .np-sys .np-x{cursor:pointer;color:var(--ink-3);flex:none}
.notifpanel .np-foot{display:flex;gap:8px;padding:10px 12px;border-top:1px solid var(--line);background:rgba(0,0,0,.02)}
.notifpanel .np-foot button{flex:1;background:none;border:0;color:var(--red);font-weight:700;font-size:12px;cursor:pointer;padding:4px}
.notifpanel .np-foot button:disabled{color:var(--ink-3);cursor:default;font-weight:400}

/* _shared/notifhist.css — corpo do histórico de notificações (F2.1), usado pelos
   3 apps. Cada app mantém seu próprio "shell" de navegação (tela roteada na loja,
   #body/det no moto, areaShell no admin) — só o cartão de cada notificação, que
   estava divergindo sem motivo (.prow2/.card/.li), virou único aqui.
   Ver docs/modulos/delivery-padronizacao-e-ajustes-plano.md §6 F2. */
.nh-sec{font-size:12.5px;font-weight:800;padding:14px 2px 8px;color:var(--ink-2);text-transform:uppercase;letter-spacing:.03em}
.nh-card{display:flex;gap:10px;align-items:flex-start;background:var(--surf);border-radius:12px;padding:13px 14px;box-shadow:0 2px 10px rgba(0,0,0,.06);margin-bottom:10px;cursor:pointer}
.nh-dot{flex:none;width:8px;height:8px;border-radius:50%;margin-top:6px;background:var(--ink-3)}
.nh-dot.critico{background:var(--red)}
.nh-b{flex:1;min-width:0}
.nh-b b{display:block;font-size:14px;font-weight:700;color:var(--ink)}
.nh-b small{display:block;font-size:12px;color:var(--ink-2);margin-top:2px}
.nh-empty{text-align:center;color:var(--ink-2);padding:40px 20px;font-size:13.5px}
/* "Limpar lidas" (2026-08-19) — discreto e à direita: é ação destrutiva, não deve
   competir com a leitura das notificações. Aparece só quando existe lida. */
.nh-top{display:flex;justify-content:flex-end;padding:6px 0 0}
.nh-limpar{background:transparent;border:1px solid var(--line,rgba(0,0,0,.12));color:var(--ink-2);
  font-size:12.5px;font-weight:700;padding:6px 12px;border-radius:999px;cursor:pointer}
.nh-limpar:active{opacity:.7}

/* ── Lida × não lida (2026-08-19) ──────────────────────────────────────────────
   Antes os dois cartões eram IGUAIS: mesma sombra, mesmo peso, mesmo ponto cinza —
   a única pista era a seção ("Não lidas"/"Lidas"), que some da vista assim que a
   lista cresce e o usuário rola. Agora o estado se lê pelo próprio cartão.

   ⚠️ Só variáveis que os TRÊS apps definem (--red é a cor PRIMÁRIA da loja, não
   vermelho; --ink/--line/--surf/--ink-2/--ink-3 idem via tokens.css). Nada de
   `--x: var(--x, ...)`, que é ciclo e some com a variável. */
.nh-card{transition:opacity .22s ease, box-shadow .22s ease, transform .12s ease}
.nh-card:active{transform:scale(.994)}

/* NÃO LIDA — pesa mais: faixa na cor da loja (inset, não border: não empurra o
   conteúdo nem muda a altura do cartão), sombra um pouco mais presente, título forte. */
.nh-card.nh-nova{box-shadow:inset 3px 0 0 var(--red), 0 3px 14px rgba(0,0,0,.10)}
.nh-card.nh-nova .nh-b b{font-weight:800}
.nh-card.nh-nova .nh-dot{background:var(--red);animation:nh-pulse 2.4s ease-out infinite}
.nh-card.nh-nova .nh-dot.critico{background:var(--red)}

/* LIDA — recua: esmaecida e plana. Continua CLICÁVEL (abre o destino), por isso
   volta ao normal no toque — cartão apagado que não responde parece quebrado. */
.nh-card.nh-lida{opacity:.62;box-shadow:0 1px 4px rgba(0,0,0,.05)}
.nh-card.nh-lida .nh-b b{font-weight:600;color:var(--ink-2)}
.nh-card.nh-lida .nh-dot{background:var(--ink-3)}
.nh-card.nh-lida:hover,.nh-card.nh-lida:active{opacity:1}

/* Halo que respira no ponto da não lida. `box-shadow` em vez de escalar o elemento:
   não reflui o layout do cartão. */
@keyframes nh-pulse{
  0%{box-shadow:0 0 0 0 rgba(0,0,0,.18)}
  70%{box-shadow:0 0 0 7px rgba(0,0,0,0)}
  100%{box-shadow:0 0 0 0 rgba(0,0,0,0)}
}
/* Quem pediu menos movimento no sistema não recebe pulso — o contraste de peso,
   faixa e opacidade já distingue os dois estados sozinho. */
@media (prefers-reduced-motion: reduce){
  .nh-card{transition:none}
  .nh-card.nh-nova .nh-dot{animation:none}
}

/* _shared/toast.css — O BALÃO DE MENSAGEM DO SISTEMA (2026-08-26).
   Fonte ÚNICA dos 3 apps (loja, painel, entregador). Decisão do dono, 2026-08-26:
   "todas as mensagens devem ser nesse padrão e nada de coisas pretas ridículas" —
   não existe mais variante escura/neutra, TODA mensagem é esta faixa vermelha.

   O que existia antes e por que foi trocado (print do dono):
     • `white-space:nowrap` → texto longo ("CORONA LATAO 473ml - atacado removido ·
       mínimo de 10 unidades") não quebrava linha e VAZAVA pelas laterais da tela;
     • `bottom:150px` (loja) / `bottom:26px` (painel/moto) → posição arbitrária que
       colidia com a barra "Adicionar" fixa e com o tabbar;
     • fundo #222/#2a2a2a → o "preto ridículo".

   🔴 VERMELHO LITERAL, nunca var(--red): aquela variável é a COR PRIMÁRIA DA LOJA
   (preta no tocomsede) — usá-la aqui recriaria exatamente o balão preto recusado.
   Mesma regra que precoLabel() já segue. Ver feedback_delivery_var_red_e_cor_primaria.

   ⚠️ NÃO redefinir estes seletores dentro dos apps — tools/check_shared_delivery.py
   falha se detectar duplicata. O visual muda AQUI e vale nos três de uma vez. */
.mtoast{position:fixed;top:calc(14px + env(safe-area-inset-top,0px));left:50%;
  transform:translateX(-50%) translateY(-16px);
  display:flex;align-items:flex-start;gap:10px;
  width:calc(100% - 24px);max-width:520px;box-sizing:border-box;
  padding:13px 15px;border-radius:12px;
  background:#15803d;color:#fff;text-align:left;   /* VERDE = deu certo (13/10/2026); vermelho só em .mtoast-err */
  /* 🔴 Pilha de fontes EXPLÍCITA: o balão é filho de <body>, e na loja a tipografia
     ('Figtree') está declarada em `.af-app`, não no body — sem isto o texto saía em
     SERIFA (fallback do navegador), medido no navegador em 2026-08-26. Figtree só
     existe na loja; admin e moto caem no system-ui da própria pilha deles. */
  font-family:'Figtree',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  box-shadow:0 10px 30px rgba(0,0,0,.28);
  /* acima de QUALQUER coisa: modal do painel (z 80), folha de opções da loja (z 95),
     galeria em tela cheia. Uma mensagem que aparece atrás de um modal não existe. */
  z-index:2147483000;
  opacity:0;pointer-events:none;
  transition:opacity .22s ease,transform .22s ease}
.mtoast.show{opacity:1;transform:translateX(-50%) translateY(0)}
/* Pastilha branca com "!" — o ícone do balão do iFood que o dono mandou de referência. */
.mtoast .mtoast-i{flex:none;width:20px;height:20px;border-radius:50%;
  background:#fff;color:#15803d;font-size:13px;font-weight:900;line-height:20px;
  text-align:center;margin-top:1px}
/* Remoção, erro e aviso: faixa vermelha (vermelho LITERAL, nunca var(--red)) com o "!" — decisão do dono, 13/10/2026. */
.mtoast.mtoast-err{background:#e30613}
.mtoast.mtoast-err .mtoast-i{color:#e30613}
/* overflow-wrap:anywhere além do normal: nome de produto sem espaço (código de SKU,
   URL colada pelo lojista) estouraria a caixa mesmo com white-space:normal. */
.mtoast .mtoast-t{white-space:normal;overflow-wrap:anywhere;
  font-size:14px;line-height:1.35;font-weight:600;color:#fff}
/* `mtoast-err` continua sendo aplicada pelo JS (a assinatura toast(msg, isErr) não
   mudou, são ~270 chamadas nos 3 apps) mas NÃO tem mais efeito visual — por decisão do
   dono, erro e aviso usam a mesma faixa. Fica no DOM porque os testes se apoiam nela.
   🔴 O nome tem o prefixo do componente de propósito: `.err` sozinha já é a CAIXA DE
   ERRO DE FORMULÁRIO do admin/moto (#fff0f0 + borda) e pintava o balão de rosa. */

/* NIVEL: ambos [formas-pagamento] — ícone da forma de pagamento (F7): grande no checkout e nas listas de forma, pequeno ao lado de um valor (.pgicos). */
.pgico{display:inline-grid;place-items:center;flex:none}
.pgicos{display:inline-flex;align-items:center;gap:3px;vertical-align:middle}
.pgicos .pgico svg{width:20px;height:20px;display:block}
/* NIVEL: ambos [formas-pagamento] — resumo do pagamento (dono, 01/11/2026): uma forma por linha, com ícone e valor. A mesma caixa na janela do pedido, no "Combinar pagamentos", no checkout e no motoboy. */
.pgres{border:1px solid var(--line,#e9e9ec);border-radius:12px;background:var(--surf-4,#f7f7f9);overflow:hidden;text-align:left}
.pgres-t{padding:10px 12px 0;font-size:11.5px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;color:var(--muted,#7a7a7a)}
.pgres-l{display:flex;align-items:center;gap:10px;padding:10px 12px}
.pgres-t+.pgres-l{padding-top:8px}
.pgres-l+.pgres-l{border-top:1px solid var(--line,#e9e9ec)}
.pgres .pgico svg{width:26px;height:26px;display:block}
.pgres-n{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;font-size:14px;font-weight:600;line-height:1.3;color:var(--ink,#1c1c1c);overflow-wrap:anywhere}
.pgres-n small{font-size:12px;font-weight:400;line-height:1.35;color:var(--muted,#7a7a7a)}
.pgres-n small b{font-weight:700;white-space:nowrap;color:var(--ink,#1c1c1c)}
.pgres-v{flex:none;font-size:14.5px;font-weight:800;white-space:nowrap;font-variant-numeric:tabular-nums;color:var(--ink,#1c1c1c)}
.pgres-tot{display:flex;justify-content:space-between;gap:12px;padding:10px 12px;border-top:1px dashed var(--line,#d4d4d8);font-size:15px;font-weight:800;color:var(--ink,#1c1c1c)}
.pgres-tot span:last-child{white-space:nowrap;font-variant-numeric:tabular-nums}
.pgnw{white-space:nowrap}
.pgres-ok{font-weight:600;color:#166534}
body.dark .pgres-ok{color:#86efac}
.pgres-erro{color:var(--red,#e30613);font-size:13px;font-weight:600}
.pgres-aviso{margin-top:8px;font-size:12.5px;line-height:1.35;color:var(--muted,#7a7a7a)}
/* Campo de valor: o R$ fica AO LADO do número, dentro da moldura, e não por cima do campo. Ao clicar, nada o cobre (dono, 01/11/2026). */
.field label.pgval,.pgval{display:flex;align-items:center;gap:8px;margin:0;padding:0 13px;border:1px solid var(--line,#e9e9ec);border-radius:10px;background:var(--surf-2,#fafafa);font-size:inherit;font-weight:700;color:inherit;cursor:text}
.field label.pgval:focus-within,.pgval:focus-within{border-color:var(--red,#e30613);background:var(--surf,#fff)}
.pgval>span{flex:none;color:var(--muted,#7a7a7a)}
.field .pgval input,body.dark .field .pgval input:not([type="checkbox"]):not([type="radio"]){flex:1;min-width:0;width:auto;padding:11px 0;border:0;border-radius:0;background:transparent;box-shadow:none;outline:none;font-weight:700}

/* _shared/dialogo.css — visual do diálogo próprio (confirmação, pergunta e lista de opções).
   Ver _shared/dialogo.js. Classes com prefixo `mdlg`/`msel` (componente compartilhado não usa
   nome genérico — ver o caso do `.err` em toast.js). */
.mdlg-mask{position:fixed;inset:0;z-index:2000;display:flex;align-items:center;justify-content:center;
  padding:20px;background:rgba(0,0,0,.5);-webkit-tap-highlight-color:transparent}
.mdlg{width:100%;max-width:380px;max-height:86vh;overflow:auto;background:var(--surf,#fff);color:var(--ink,#18181b);
  border-radius:16px;padding:20px;box-shadow:0 18px 50px rgba(0,0,0,.28);font-family:inherit;
  animation:mdlgIn .14s ease-out}
@keyframes mdlgIn{from{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.mdlg,.msel{animation:none}}
.mdlg h3{margin:0 0 8px;font-size:17px;font-weight:700;line-height:1.3}
.mdlg p{margin:0;font-size:14px;line-height:1.5;color:var(--muted,#52525b);white-space:pre-line;overflow-wrap:anywhere}
.mdlg h3:empty,.mdlg p:empty{display:none}
.mdlg-campo{width:100%;box-sizing:border-box;margin-top:14px;padding:12px 13px;border:1px solid var(--line,#d4d4d8);
  border-radius:10px;background:var(--surf-2,#fafafa);color:var(--ink,#18181b);font:inherit;font-size:15px}
.mdlg-campo:focus{outline:none;border-color:var(--red,#e30613);background:var(--surf,#fff)}
.mdlg-campo.mdlg-falta{border-color:#c62828;outline:2px solid rgba(198,40,40,.25)}
.mdlg-b{display:flex;gap:10px;margin-top:18px}
.mdlg-b button{flex:1;min-height:44px;padding:10px 14px;border-radius:10px;font:inherit;font-size:14px;font-weight:700;cursor:pointer}
.mdlg-nao{border:1px solid var(--line,#d4d4d8);background:var(--surf,#fff);color:var(--ink,#18181b)}
.mdlg-sim{border:1px solid var(--red,#e30613);background:var(--red,#e30613);color:var(--red-ink,#fff)}
.mdlg-b button:focus-visible{outline:2px solid var(--ink,#18181b);outline-offset:2px}

/* Lista de opções no lugar da lista nativa do <select>. O <select> continua na tela como campo
   fechado (mesmo visual de sempre); só não abre mais a lista do navegador. */
select:not([data-nativo]){pointer-events:none}
.msel-mask{position:fixed;inset:0;z-index:2000;background:transparent;-webkit-tap-highlight-color:transparent}
.msel{position:fixed;box-sizing:border-box;min-width:180px;max-width:calc(100vw - 16px);max-height:min(60vh,420px);overflow:auto;
  background:var(--surf,#fff);color:var(--ink,#18181b);border:1px solid var(--line,#e4e4e7);border-radius:12px;
  padding:6px;box-shadow:0 14px 40px rgba(0,0,0,.22);font-family:inherit;animation:mdlgIn .12s ease-out}
.msel-t{padding:8px 10px 6px;font-size:12px;font-weight:700;color:var(--muted,#71717a)}
.msel-t:empty{display:none}
.msel-o{display:flex;align-items:center;gap:10px;width:100%;min-height:42px;padding:9px 10px;border:0;border-radius:8px;
  background:transparent;color:inherit;font:inherit;font-size:14px;text-align:left;cursor:pointer}
.msel-o::before{content:"";flex:none;width:16px;height:16px;border-radius:50%;border:2px solid var(--line,#c4c4cc);box-sizing:border-box}
.msel-o:hover,.msel-o:focus{outline:none;background:var(--surf-3,#f1f1f4)}
.msel-o.on{font-weight:700}
.msel-o.on::before{border-color:var(--red,#e30613);background:radial-gradient(circle,var(--red,#e30613) 0 4px,transparent 5px)}
.msel-o:disabled{opacity:.45;cursor:default}
@media (max-width:560px){
  .mdlg-mask{align-items:flex-end;padding:0}
  .mdlg{max-width:none;border-radius:18px 18px 0 0;padding:20px 18px calc(18px + env(safe-area-inset-bottom))}
  .msel-mask{background:rgba(0,0,0,.45)}
  .msel{left:0!important;right:0;top:auto!important;bottom:0;width:100%!important;max-width:none;max-height:70vh;
    border-radius:18px 18px 0 0;border:0;padding:10px 10px calc(12px + env(safe-area-inset-bottom))}
  .msel-o{min-height:48px;font-size:15px}
}

/* _shared/toque.css — EFEITO DE TOQUE PADRÃO dos 3 apps (loja, painel, entregador) — 2026-09-17.
   Decisão do dono: o efeito da barra de busca da home ("gostei desse efeito") vira o padrão de
   TUDO que é clicável no Delivery, na cor primária da loja (Configurações › Cores). Quem desenha é
   _shared/toque.js; aqui fica só o anel. Plano: docs/modulos/delivery-efeito-toque-padrao-plano.md.
   NIVEL: ambos [app-loja] [painel]
   O anel é uma camada POR CIMA do elemento (position:fixed no <body>), não um ::after nele: não
   briga com os ::before/::after que o tema escuro já usa e não some quando a tela redesenha o
   elemento no meio do toque. A cor chega por --toque-rgb, lida do próprio elemento tocado. */

/* A "marcação azul" do toque (print do dono no menu Início, 17/09): é o destaque nativo do
   Chrome/Android (-webkit-tap-highlight-color) — o painel já o desligava, a loja e o
   entregador não. Quem mostra o toque agora é o anel abaixo. */
*{-webkit-tap-highlight-color:transparent}
/* Anel de foco azul que o navegador deixa no botão depois do toque/clique. Foco por TECLADO
   (:focus-visible) continua visível, só que na cor da loja. :where() tem especificidade zero:
   qualquer regra de foco que a tela já tenha (ex.: admin.html) continua valendo. */
:where(button,a,summary,[role="button"],[role="tab"],[tabindex]):focus:not(:focus-visible){outline:none}
:where(button,a,summary,[role="button"],[role="tab"]):focus-visible{outline:2px solid rgba(var(--red-rgb,227,6,19),.6);outline-offset:2px}
/* Foco de CAMPO (print do dono, 17/09: busca de Pedidos & Entregas com a borda preta do navegador).
   Campo sem estilo de foco próprio ganha o contorno na cor da loja no lugar do preto/azul nativo.
   Só mexe em `outline`: campo que já tem foco próprio (.field input:focus → outline:none + borda
   vermelha, .search input → outline:none) continua exatamente como está. */
:where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]),textarea,select):focus-visible{outline:2px solid rgba(var(--red-rgb,227,6,19),.6);outline-offset:0}

.toque-anel{position:fixed;left:0;top:0;pointer-events:none;z-index:2147483000;overflow:hidden;
  box-shadow:0 0 0 3px rgba(var(--toque-rgb,227,6,19),.35),0 8px 20px -8px rgba(var(--toque-rgb,227,6,19),.55);
  animation:toque-anel .34s ease-out forwards}
.toque-anel::after{content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,transparent 15%,rgba(var(--toque-rgb,227,6,19),.18) 50%,transparent 85%);
  animation:toque-onda .26s ease-out forwards}
.toque-anel.toque-saindo{animation:toque-sai .12s ease-out forwards}
@keyframes toque-onda{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
@keyframes toque-anel{0%{opacity:0}12%{opacity:1}70%{opacity:1}100%{opacity:0}}
@keyframes toque-sai{to{opacity:0}}
@media (prefers-reduced-motion:reduce){.toque-anel{display:none}}

/* _shared/chatui.css — o chat com cara de app de mensagem (01/10/2026).
   Usado pelo app do cliente (app-ifood.html) e pelo painel (admin.html), com o mesmo
   desenho dos dois lados. Plano: docs/modulos/delivery-chat-interface-plano.md.
   NIVEL: ambos [atendimento-chat]
   Só tokens que existem nos dois apps, no claro e no escuro (--surf, --surf-2, --ink,
   --ink-2, --ink-3, --line, --red, --red-ink) — nenhuma cor cravada que apague no escuro.
   Cada app define --cu-z (camada da barra flutuante: acima do menu de baixo, ABAIXO das
   janelas/folhas) e --cu-maxw (largura máxima, a mesma do app). */

/* ---------- cabeçalho da conversa ---------- */
.cu-cab{display:flex;align-items:center;gap:11px;padding:10px 14px;background:var(--surf);
  border-bottom:1px solid var(--line);flex:none}
.cu-avw{position:relative;flex:none}
.cu-av{width:42px;height:42px;border-radius:50%;display:grid;place-items:center;overflow:hidden;
  font-weight:800;font-size:15px;color:var(--red-ink);background:var(--red);text-transform:uppercase}
.cu-av img{width:100%;height:100%;object-fit:cover;background:var(--surf)}
.cu-dot{position:absolute;right:-1px;bottom:-1px;width:13px;height:13px;border-radius:50%;
  border:2px solid var(--surf);background:var(--ink-3)}
.cu-dot.on{background:#1fb866;animation:cuPulso 2.2s ease-out infinite}
.cu-cabtx{min-width:0;flex:1}
.cu-tit{font-weight:800;font-size:15px;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cu-sub{font-size:12px;color:var(--ink-2);margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ---------- lista de mensagens ---------- */
.cu-lista{display:flex;flex-direction:column;overflow-y:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;padding:12px 12px 96px;animation:cuSurge .3s ease both}
.cu-dia{align-self:center;margin:14px 0 6px;padding:4px 12px;border-radius:100px;font-size:11.5px;
  font-weight:700;color:var(--ink-2);background:var(--surf-2);border:1px solid var(--line)}
.cu-b{position:relative;max-width:min(80%,540px);margin-top:8px;padding:8px 12px 5px;border-radius:18px;
  font-size:14.5px;line-height:1.42;overflow-wrap:anywhere;box-shadow:0 1px 2px rgba(0,0,0,.07)}
.cu-b.cont{margin-top:2px}
.cu-b.eu{align-self:flex-end;background:var(--red);color:var(--red-ink)}
.cu-b.ele{align-self:flex-start;background:var(--surf);color:var(--ink);border:1px solid var(--line)}
/* Agrupamento: bolhas seguidas do mesmo autor se juntam, e só a ÚLTIMA ganha o canto do
   "rabinho" — como em qualquer app de mensagem. */
.cu-b.eu.cont{border-top-right-radius:6px}
.cu-b.ele.cont{border-top-left-radius:6px}
.cu-b.eu.ult{border-bottom-right-radius:5px}
.cu-b.ele.ult{border-bottom-left-radius:5px}
.cu-b.eu:not(.ult){border-bottom-right-radius:6px}
.cu-b.ele:not(.ult){border-bottom-left-radius:6px}
.cu-nome{display:block;font-size:11.5px;font-weight:800;color:var(--red);margin-bottom:1px}
.cu-tx a{color:inherit;text-decoration:underline}
.cu-meta{display:flex;justify-content:flex-end;align-items:center;gap:4px;margin-top:2px;
  font-size:10.5px;opacity:.72;white-space:nowrap}
.cu-ok{font-weight:800;letter-spacing:-2px}
.cu-b.novo{animation:cuEntra .34s cubic-bezier(.2,.8,.2,1) both}
.cu-b.eu.novo{transform-origin:100% 100%}
.cu-b.ele.novo{transform-origin:0 100%}
.cu-b.enviando{opacity:.78}
.cu-env{animation:cuPisca 1.1s ease-in-out infinite}
.cu-b.falhou{background:var(--surf);color:var(--ink);border:1.5px dashed var(--red);opacity:1}
.cu-b.falhou .cu-meta{opacity:1;color:var(--red)}
.cu-retry{border:none;background:none;padding:0;font:inherit;font-weight:800;color:var(--red);
  text-decoration:underline;cursor:pointer}
/* Conversa vazia / carregando */
.cu-vazio{margin:auto;text-align:center;color:var(--ink-2);padding:30px 24px;font-size:14px;line-height:1.5}
.cu-vazio .cu-ic{display:block;font-size:44px;margin-bottom:8px;animation:cuFlutua 3.2s ease-in-out infinite}
.cu-sk{height:40px;border-radius:18px;margin-top:9px;width:62%;
  background:linear-gradient(90deg,var(--surf-2) 0,var(--line) 50%,var(--surf-2) 100%);
  background-size:320px 100%;animation:cuBrilho 1.15s linear infinite}
.cu-sk.eu{align-self:flex-end;width:48%}
.cu-sk.ele{align-self:flex-start}

/* ---------- compositor FLUTUANTE ----------
   Pílula fixa acima do menu de baixo (ou do teclado). `bottom` e a sobra da lista são
   calculados em chatuiCompositor() — CSS sozinho não sabe a altura do menu nem do teclado. */
.cu-comp{position:fixed;left:50%;bottom:12px;transform:translateX(-50%);z-index:var(--cu-z,45);
  width:calc(100% - 20px);max-width:calc(var(--cu-maxw,720px) - 20px);box-sizing:border-box;
  display:flex;align-items:flex-end;gap:8px;padding:6px 6px 6px 16px;background:var(--surf);
  border:1px solid var(--line);border-radius:26px;
  box-shadow:0 12px 30px rgba(0,0,0,.18),0 2px 6px rgba(0,0,0,.07);
  animation:cuSobe .4s cubic-bezier(.2,.8,.2,1) both;transition:bottom .2s ease,box-shadow .2s ease}
.cu-comp:focus-within{box-shadow:0 14px 34px rgba(0,0,0,.22),0 0 0 2px var(--red)}
.cu-comp textarea{flex:1;min-width:0;border:none;outline:none;background:transparent;color:var(--ink);
  font:inherit;font-size:15px;line-height:1.4;resize:none;padding:9px 0;max-height:120px;height:40px}
.cu-comp textarea::placeholder{color:var(--ink-3)}
/* O campo é "transparente" dentro da pílula: o tema escuro do app pinta todo textarea com
   fundo/borda próprios (regra de mesma força, vinha depois) — visto a olho em 01/10 como um
   retângulo claro dentro da pílula. Reset explícito só destas 4 propriedades. */
.cu-comp textarea{background:transparent!important;border:none!important;box-shadow:none!important;border-radius:0!important}
.cu-envia{width:42px;height:42px;flex:none;border:none;border-radius:50%;background:var(--red);
  color:var(--red-ink);display:grid;place-items:center;cursor:pointer;
  transition:transform .15s ease,opacity .2s ease}
.cu-envia:disabled{opacity:.32;cursor:default}
.cu-envia:not(:disabled):active{transform:scale(.9)}
.cu-envia.toque{animation:cuToque .38s ease}
.cu-envia svg{width:19px;height:19px;margin-left:2px}
/* "↓ N novas": aparece quando chega mensagem com a pessoa lendo lá em cima. */
.cu-novas{position:fixed;left:50%;transform:translateX(-50%);z-index:var(--cu-z,45);display:none;
  border:none;border-radius:100px;padding:8px 15px;font:inherit;font-size:13px;font-weight:800;
  background:var(--red);color:var(--red-ink);box-shadow:0 8px 20px rgba(0,0,0,.2);cursor:pointer}
.cu-novas.on{display:block;animation:cuQuica .5s cubic-bezier(.2,1.4,.4,1) both}

@keyframes cuEntra{from{opacity:0;transform:translateY(12px) scale(.96)}to{opacity:1;transform:none}}
@keyframes cuSurge{from{opacity:0}to{opacity:1}}
@keyframes cuSobe{from{opacity:0;transform:translate(-50%,24px)}to{opacity:1;transform:translate(-50%,0)}}
@keyframes cuQuica{from{opacity:0;transform:translate(-50%,10px) scale(.9)}to{opacity:1;transform:translate(-50%,0) scale(1)}}
@keyframes cuPulso{0%{box-shadow:0 0 0 0 rgba(31,184,102,.55)}70%{box-shadow:0 0 0 8px rgba(31,184,102,0)}100%{box-shadow:0 0 0 0 rgba(31,184,102,0)}}
@keyframes cuBrilho{from{background-position:-160px 0}to{background-position:160px 0}}
@keyframes cuPisca{0%,100%{opacity:.45}50%{opacity:1}}
@keyframes cuToque{0%{transform:scale(1)}35%{transform:scale(.82) rotate(-12deg)}70%{transform:scale(1.08) rotate(4deg)}100%{transform:none}}
@keyframes cuFlutua{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
/* Quem desligou animação no sistema não recebe nenhuma (acessibilidade). */
@media (prefers-reduced-motion:reduce){
  .cu-lista,.cu-b.novo,.cu-comp,.cu-novas.on,.cu-dot.on,.cu-sk,.cu-env,.cu-envia.toque,.cu-vazio .cu-ic{animation:none!important}
  .cu-comp{transition:none}
}


/* ===== sacola / checkout ===== */
.af-app .sec-t{font-size:15px;font-weight:800;padding:16px 14px 8px;color:var(--ink)}
.af-app .citem{display:flex;gap:12px;padding:13px 14px;background:var(--surf);border-bottom:1px solid var(--line);align-items:center}
.af-app .citem .th{width:46px;height:46px;flex:none;border-radius:10px;display:grid;place-items:center;font-size:24px;background:var(--surf-2)}
.af-app .citem .ci{flex:1;min-width:0}
.af-app .citem .ci b{font-size:14px;font-weight:600;display:block}
.af-app .citem .ci .cp{color:var(--c-preco);font-weight:800;font-size:13px;margin-top:2px}
.af-app .citem .stp{display:flex;align-items:center;gap:9px;border:1px solid var(--line);border-radius:9px;padding:3px 5px}
.af-app .citem .stp button{width:24px;height:24px;border-radius:6px;color:var(--c-sacola);font-size:16px;font-weight:800;display:grid;place-items:center;background:var(--surf)}
/* A quantidade da sacola é TOCÁVEL (2026-08-26): abre o teclado pra digitar o número.
   Pedido do dono — a loja vende atacado (mínimo de 10, pedidos de 1.500 unidades) e
   chegar nesse número no '+' é inviável. Área de toque generosa e sublinhado tracejado
   para o número não parecer um rótulo morto. */
/* width/height auto anulam o `.stp button{width:24px;height:24px}` acima — sem isto o
   número vira um quadradinho de 24px e o toque fica difícil justamente onde ele passou
   a ser a interação principal. */
.af-app .citem .stp .q{font-weight:800;color:var(--ink);width:auto;height:auto;min-width:34px;
  text-align:center;font-size:13px;cursor:pointer;padding:5px 7px;border-radius:6px;
  border:0;border-bottom:1px dashed var(--line);background:none}
.af-app .citem .stp .q:active{background:#0000000d}
/* Sheet de digitar quantidade */
.af-app .qtdsheet input{width:100%;font-size:30px;font-weight:800;text-align:center;padding:12px;
  border:1px solid var(--line);border-radius:12px;margin:4px 0 2px}
.af-app .qtdsheet .qhint{font-size:12.5px;color:var(--ink-2);text-align:center;margin-bottom:10px;min-height:16px}
.af-app .qtdsheet .qatalhos{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-bottom:12px}
.af-app .qtdsheet .qatalhos button{flex:1;min-width:58px;padding:9px 0;border:1px solid var(--line);
  border-radius:9px;background:var(--surf);font-weight:700;font-size:13px;color:var(--ink)}
.af-app .opt{display:flex;align-items:center;gap:12px;padding:14px;background:var(--surf);border:1px solid var(--line);border-radius:12px;margin:0 14px 10px}
.af-app .opt.sel{border-color:var(--red);background:var(--sel-bg-2)}
.af-app .opt .radio{width:20px;height:20px;flex:none;border-radius:50%;border:2px solid var(--ink-3);display:grid;place-items:center}
.af-app .opt.sel .radio{border-color:var(--red)}
.af-app .opt.sel .radio::after{content:"";width:10px;height:10px;border-radius:50%;background:var(--red)}
.af-app .opt .oi{flex:1}.af-app .opt .oi b{display:block;font-size:14px}.af-app .opt .oi small{color:var(--ink-2);font-size:12px}
.af-app .opt .opr{color:var(--red);font-weight:800;font-size:13.5px}
.af-app .field{margin:0 14px 12px}
.af-app .field label{display:block;font-size:12px;color:var(--ink-2);margin-bottom:6px;font-weight:600}
.af-app .field input,.af-app .field select,.af-app .field textarea{width:100%;background:var(--surf);border:1px solid var(--line);border-radius:10px;padding:12px;font-size:16px;font-family:inherit;color:var(--ink);outline:none}
.af-app .field input:focus{border-color:var(--red)}
.af-app .field.two{display:flex;gap:10px}
.af-app .coupon{display:flex;align-items:center;gap:8px;margin:0 14px 12px;background:var(--surf);border:1px solid var(--line);border-radius:10px;padding:4px 4px 4px 12px}
.af-app .coupon .ctag{flex:none;font-size:15px;line-height:1;color:var(--ink-3)}
.af-app .coupon input{flex:1;background:transparent;border:none;padding:10px 4px;font-size:16px;font-family:inherit;color:var(--ink);outline:none}
.af-app .coupon input::placeholder{color:var(--ink-3)}
.af-app .coupon button{flex:none;padding:9px 16px;border-radius:8px;background:var(--red);border:none;color:var(--red-ink);font-weight:700;font-size:13px}
.af-app .geobusca{position:relative}
.af-app .geosug{display:none;margin-top:6px;background:var(--surf);border:1px solid var(--line);border-radius:10px;box-shadow:var(--sh);overflow:hidden}
.af-app .geosug-item{display:block;width:100%;text-align:left;padding:11px 13px;font-size:13px;color:var(--ink);border-bottom:1px solid var(--line);background:var(--surf)}
.af-app .geosug-item:last-child{border-bottom:none}
.af-app .geosug-item:active{background:var(--surf)}
.af-app .geosug-loading{display:block;padding:11px 13px;font-size:13px;color:var(--ink-3)}
.af-app .summ{margin:8px 14px 0;padding:14px;border-radius:12px;background:var(--surf);box-shadow:var(--sh)}
.af-app .summ .sr{display:flex;justify-content:space-between;font-size:13.5px;padding:5px 0;color:var(--ink-2)}
.af-app .summ .sr.tot{border-top:1px dashed var(--line);margin-top:6px;padding-top:11px;color:var(--ink);font-weight:800;font-size:16px}
.af-app .summ .sr .rr{color:var(--red);font-weight:700}
.af-app .stickybtn{position:sticky;bottom:0;padding:14px;background:linear-gradient(0deg,rgba(var(--bg-rgb),.92) 70%,transparent)}
/* "Adicionar" fixo na tela de produto (2026-08-26) — ⚠️ classe NOVA (.fixo), nunca
   alterar .stickybtn direto: ela é compartilhada por 10 telas (sacola, entrega,
   checkout, pix, login, cadastro, endereço...) e virar `fixed` ali afetaria telas que
   ninguém pediu. Mesma técnica de centralização que .tabbar já usa (left:50% +
   translateX + max-width:var(--w)) — sem isso o botão ficaria colado na borda
   esquerda da tela em vez de acompanhar a largura centralizada do app no desktop. */
.af-app .stickybtn.fixo{position:fixed;left:50%;transform:translateX(-50%);width:100%;
  max-width:var(--w);bottom:0;z-index:25;
  padding-bottom:calc(14px + env(safe-area-inset-bottom));
  display:flex;align-items:center;gap:10px;
  /* fundo sólido: a barra flutua SOBRE o conteúdo que rola por baixo — o gradiente
     translúcido de .stickybtn deixava texto aparecendo através dela. */
  background:var(--bg);border-top:1px solid var(--line)}
/* Quantidade DENTRO da barra fixa, ao lado do botão (2026-08-26) — padrão iFood.
   Antes a .qbar ficava solta no meio do conteúdo e o botão numa barra separada:
   funcionava, mas não era o layout pedido. Compacta aqui (botões 34px, gap 10) porque
   divide a linha com o botão; a .qbar fora da barra segue com o tamanho original. */
.af-app .stickybtn.fixo .qbar{padding:0;gap:10px;flex:none;border:1px solid var(--line);
  border-radius:12px;background:var(--surf);height:48px;padding:0 6px}
.af-app .stickybtn.fixo .qbar button{width:34px;height:34px;font-size:19px;border:0;background:none}
.af-app .stickybtn.fixo .qbar .qn{font-size:16px;min-width:22px}
.af-app .stickybtn.fixo .big{flex:1;min-width:0}
/* No desktop a rolagem não é o problema (tela alta, conteúdo cabe) e a nav fica no
   topo — então a barra volta a ser sticky, no fluxo, com a largura da coluna de
   conteúdo. Fixa com max-width:480px ali ficaria uma tarja estreita solta no rodapé
   de uma página de 1200px. */
@media (min-width:900px){
  .af-app .stickybtn.fixo{position:sticky;left:auto;transform:none;max-width:none;
    width:auto;border-top:0;background:linear-gradient(0deg,rgba(244,244,246,.92) 70%,transparent)}
}
/* Sem isto, o fim do conteúdo (rail "Costuma ir junto") fica ATRÁS da barra fixa —
   96px cobre a altura real do botão+padding com folga; safe-area cobre o iPhone com
   home indicator. */
.af-app .screen[data-scr="produto"]{padding-bottom:calc(96px + env(safe-area-inset-bottom))}
/* Menu inferior escondido na tela de produto (2026-08-26) — o rodapé fixo do
   "Adicionar" ocupa o lugar dele; ter os dois fixos empilhados sobraria pouca tela.
   🔴 SÓ ABAIXO DE 900px. No desktop o .tabbar NÃO é o menu de baixo: ele vira a NAV
   DO TOPO, com a marca da loja e todos os links (ver o bloco @media). Escondê-lo lá
   tirava a navegação inteira do cliente ao abrir um produto — o pedido era esconder o
   menu do celular, não decapitar a versão desktop. */
@media (max-width:899px){ .af-app.sem-tabbar .tabbar{display:none} }
.af-app .devby{display:block;text-align:center;padding:18px 14px;font-size:12px;color:var(--ink-2);text-decoration:none}
.af-app .big{width:100%;padding:15px;border-radius:12px;background:var(--c-cta);color:var(--c-cta-ink);font-weight:800;font-size:15px;box-shadow:0 10px 24px rgba(var(--c-cta-rgb),.3);display:flex;align-items:center;justify-content:center;gap:8px}
.af-app .big:disabled{opacity:.5;box-shadow:none}
.af-app .pedcontato{display:flex;gap:8px}   /* NIVEL: completo [pedidos-cliente] — chat + WhatsApp lado a lado (21/10/2026) */
.af-app .pedcontato .big{width:auto;flex:1 1 auto;min-width:0;padding:14px 8px;font-size:14px;white-space:nowrap}
.af-app .pedcontato .pedwa{flex:0 0 auto;gap:6px;padding:14px 12px;color:#128c4a}
@media (max-width:340px){.af-app .pedcontato .big{font-size:13px}.af-app .pedcontato .pedwa{padding:14px 10px}}
.af-app.dark .pedcontato .pedwa{color:#25d366}
.af-app .pedcontato .pedwa svg{flex:none}
.af-app .big.ghost{background:var(--surf);color:var(--ink);border:1px solid var(--line);box-shadow:none}
/* G3 — variante compacta para botões dentro de faixas (.freebar) e listas. Existia
   `class="btn sm"` em 2 lugares, mas `.btn`/`.sm` são vocabulário do admin.html e NÃO
   existem aqui: renderizavam como botão cru do navegador. */
.af-app .big.mini{padding:9px 12px;font-size:13px;border-radius:9px;box-shadow:none;width:auto}
.af-app .empty{margin:40px 14px;text-align:center;color:var(--ink-2);font-size:14px}

/* pedidos / tracking */
.af-app .segtabs{display:flex;background:var(--surf);border-bottom:1px solid var(--line)}
.af-app .segtabs button{flex:1;padding:14px;font-weight:700;font-size:13px;color:var(--ink-2);border-bottom:2px solid transparent}
.af-app .segtabs button.on{color:var(--red);border-bottom-color:var(--red)}
.af-app .ocard{margin:14px;background:var(--surf);border-radius:14px;box-shadow:var(--sh);overflow:hidden}
.af-app .ocard .oh{display:flex;justify-content:space-between;align-items:center;padding:14px;border-bottom:1px solid var(--line)}
.af-app .ocard .oh b{font-weight:800;font-size:14px}
/* 2026-08-17: era SEMPRE --red (a cor primária da loja, não "erro") pra qualquer
   status — pedido "Entregue" saía com a mesma pílula visual de alerta que "Falhou".
   Neutro por padrão; .bd.ok (sucesso) e .bd.falhou (alerta) sobrescrevem via classe. */
.af-app .ocard .oh .bd{font-size:11px;font-weight:700;color:var(--ink-2);background:var(--surf-3);padding:5px 10px;border-radius:100px}
.af-app .ocard .oh .bd.ok{color:var(--green);background:var(--ok-bg)}
.af-app .ocard .oh .bd.falhou{color:#fff;background:var(--red)}
.af-app .steps{padding:16px 14px}
.af-app .steps .s{display:flex;gap:12px;padding-bottom:18px;position:relative}
.af-app .steps .s:last-child{padding-bottom:0}
.af-app .steps .s::before{content:"";position:absolute;left:12px;top:24px;bottom:0;width:2px;background:var(--line)}
.af-app .steps .s:last-child::before{display:none}
.af-app .steps .s .dot{width:24px;height:24px;flex:none;border-radius:50%;border:2px solid var(--line);display:grid;place-items:center;font-size:12px;background:#fff;color:var(--ink-3);z-index:1}
.af-app .steps .s.done .dot{background:var(--green);border-color:var(--green);color:#fff}
.af-app .steps .s.now .dot{background:var(--red);border-color:var(--red);color:#fff;box-shadow:0 0 0 4px var(--sel-bg)}
.af-app .steps .s .tx b{font-size:14px;display:block;font-weight:700}
.af-app .steps .s .tx small{color:var(--ink-2);font-size:12px}
.af-app .steps .s.pending .tx b{color:var(--ink-3);font-weight:600}
.af-app .oeta{margin:0 14px 14px;padding:13px;border-radius:11px;background:var(--sel-bg-2);text-align:center}
.af-app .oeta b{color:var(--red);font-weight:800;font-size:18px;display:block}
.af-app .oeta small{color:var(--ink-2);font-size:12px}
/* Badge do código de entrega (2026-08-29). Pílula centralizada entre os passos do status
   e o card do tempo — elemento próprio, fora do card do tempo, que fica intacto.
   Formato de pílula porque o código é um dado ATÔMICO: nem card, nem linha de tabela;
   uma etiqueta que o olho acha de primeira ao abrir a tela. Os 4 dígitos são para ler em
   voz alta na porta, então vão em tabular-nums (1 e 7 não se confundem) e bem espaçados. */
.af-app .codbadge-wrap{display:flex;justify-content:center;margin:0 14px 12px}
.af-app .codbadge{display:inline-flex;flex-direction:column;align-items:center;gap:1px;
  padding:7px 18px;border-radius:999px;
  background:rgba(var(--cor-primaria-rgb,227,6,19),.08);
  border:1px solid rgba(var(--cor-primaria-rgb,227,6,19),.28)}
.af-app .codbadge i{font-style:normal;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2)}
.af-app .codbadge b{font-size:22px;line-height:1.15;font-weight:800;letter-spacing:.2em;
  font-variant-numeric:tabular-nums;color:var(--cor-primaria,#e30613);
  /* o letter-spacing empurra o texto para a esquerda dentro da pílula; o padding
     compensa para os dígitos ficarem opticamente centrados. */
  padding-left:.2em}

/* perfil */
.af-app .prof-top{background:linear-gradient(135deg,var(--header-1),var(--header-2));color:var(--header-ink);padding:26px 18px 30px;display:flex;align-items:center;gap:14px;box-shadow:0 10px 26px rgba(var(--header-1-rgb),.30)}
.af-app .prof-top .av{width:58px;height:58px;border-radius:50%;background:rgba(255,255,255,.22);display:grid;place-items:center;font-size:24px;font-weight:800;box-shadow:0 3px 10px rgba(0,0,0,.18)}
.af-app .prof-top .av.avpick{position:relative;flex:none;color:inherit}
.af-app .prof-top .avcam{position:absolute;right:-2px;bottom:-2px;width:22px;height:22px;border-radius:50%;background:var(--surf);color:var(--ink);display:grid;place-items:center;font-size:11px;box-shadow:0 2px 6px rgba(0,0,0,.25)}
.af-app .photosheet-bd{position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:95;display:flex;align-items:flex-end}
.af-app .photosheet{background:var(--surf);width:100%;max-width:var(--w);margin:0 auto;border-radius:16px 16px 0 0;padding:8px 14px calc(14px + env(safe-area-inset-bottom))}
.af-app .photosheet button{width:100%;text-align:left;padding:14px 6px;font-size:15px;font-weight:600;border-bottom:1px solid var(--line)}
.af-app .photosheet button:last-child{border-bottom:none;color:var(--red)}
.af-app .revisao-bd{position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:95;display:flex;align-items:flex-end}
.af-app .revisao-sheet{background:var(--surf);width:100%;max-width:var(--w);margin:0 auto;border-radius:16px 16px 0 0;padding:18px 18px calc(14px + env(safe-area-inset-bottom))}
.af-app .revisao-sheet h3{margin:0 0 14px;font-size:17px;text-align:center}
.af-app .revisao-row{display:flex;gap:12px;align-items:flex-start;padding:9px 0;border-bottom:1px solid var(--line)}
.af-app .revisao-row:last-of-type{border-bottom:none;margin-bottom:10px}
.af-app .revisao-row span.ic{flex:none;font-size:17px;line-height:1.3}
.af-app .revisao-row b{display:block;font-size:14px}
.af-app .revisao-row small{color:var(--ink-2);font-size:12.5px}
.af-app .revisao-row .rv{margin-left:auto;font-weight:700;color:var(--red)}
/* NIVEL: ambos [acrescimo-pagamento] — valores do pedido na revisão (itens, entrega, cupom, acréscimo, total) */
.af-app .revisao-vals{margin:2px 0 12px;padding:10px 12px;border:1px solid var(--line);border-radius:12px}
.af-app .revisao-vals .sr{display:flex;justify-content:space-between;gap:12px;font-size:13.5px;padding:4px 0;color:var(--ink-2)}
.af-app .revisao-vals .sr.acr{color:var(--ink);font-weight:600}
.af-app .revisao-vals .sr.tot{border-top:1px dashed var(--line);margin-top:5px;padding-top:9px;color:var(--ink);font-weight:800;font-size:16px}
.af-app .revisao-vals .sr.tot span:last-child{color:var(--red)}
.af-app .prof-top b{font-size:18px;font-weight:800;display:block}
.af-app .prof-top small{opacity:.92;font-size:13px}
.af-app .rolebadge{display:inline-block;margin-top:6px;background:rgba(255,255,255,.22);color:#fff;font-size:11.5px;font-weight:700;padding:3px 10px;border-radius:100px;backdrop-filter:blur(2px)}
.af-app .plist{background:var(--surf)}
.af-app .prow2{display:flex;align-items:center;gap:14px;padding:16px 16px;border-bottom:1px solid var(--line);font-weight:600;font-size:14.5px}
.af-app .prow2 svg{color:var(--c-icone);flex:none}
.af-app .prow2 .rv{margin-left:auto;color:var(--ink-3)}

/* success */
.af-app .success{text-align:center;padding:56px 24px;background:var(--surf);min-height:100vh}
.af-app .success .ok{width:92px;height:92px;border-radius:50%;background:var(--green);display:grid;place-items:center;margin:0 auto 22px;font-size:46px;color:#fff;animation:pop .5s var(--ease)}
@keyframes pop{0%{transform:scale(.3);opacity:0}60%{transform:scale(1.1)}100%{transform:scale(1)}}
.af-app .success h2{font-size:23px;font-weight:800;margin-bottom:10px}
.af-app .success p{color:var(--ink-2);font-size:14px;margin-bottom:24px}
/* 27/10/2026 (dono: "tela torta, botões de tamanhos diferentes"): o bloco do Pix é um .field (margem lateral de 14 px) e os botões
   de baixo não — larguras diferentes. Uma coluna só, centralizada (na tela grande não estica), todos os botões com a mesma largura. */
.af-app .success>*{max-width:480px;margin-left:auto;margin-right:auto}
.af-app .success .field{margin-left:auto;margin-right:auto}
.af-app .success .big:not(.mini){width:100%;max-width:480px;margin-left:auto;margin-right:auto}

/* ===== detalhe do produto / combo ===== */
.af-app .phero{background:var(--ph-bg-hero);height:210px;display:grid;place-items:center;font-size:96px;position:relative}
/* Coração de favorito na imagem do produto (2026-08-26). Maior que o das listagens
   (.pcard .favbtn é 26px) porque aqui é elemento único sobre imagem grande, não
   miniatura — precisa ler bem e ter área de toque confortável.
   🔴 top:56px (rodada 3, 2026-08-26): o compartilhar assumiu o topo do canto direito
   ("acima do coração", pedido do dono). A coluna direita da foto agora é uma PILHA de
   3 elementos absolutos no MESMO canto — compartilhar (12) → coração (56) → selo
   (100, inline em renderProduto). Mexer em um exige reconferir os outros dois. */
.af-app .phero .favbtn{position:absolute;top:56px;right:12px;width:36px;height:36px;
  border-radius:50%;background:var(--pill-bg);box-shadow:0 2px 6px rgba(0,0,0,.16);
  border:none;font-size:19px;line-height:36px;text-align:center;padding:0;cursor:pointer;
  z-index:3;color:var(--c-icone)}
/* Compartilhar produto (2026-08-26) — mesma pastilha do coração, pra os dois lerem
   como um par. SVG e não emoji: não existe emoji de "compartilhar" que renderize
   igual em Android e iOS, e o currentColor deixa o ícone seguir a cor da loja. */
.af-app .phero .sharebtn{position:absolute;top:12px;right:12px;width:36px;height:36px;
  border-radius:50%;background:var(--pill-bg);box-shadow:0 2px 6px rgba(0,0,0,.16);
  border:none;padding:0;cursor:pointer;z-index:3;color:var(--c-icone);display:grid;place-items:center}
/* NIVEL: ambos [produtos] — Engrenagem do gestor (2026-10-13): mesma pastilha, no canto de
   BAIXO à esquerda (o de cima à esquerda é do "-X%"; a coluna direita é compartilhar →
   coração → selo). Só aparece com html.af-gestor, que vem do /delivery/me. */
.af-app .phero .admbtn{position:absolute;bottom:12px;left:12px;width:36px;height:36px;
  border-radius:50%;background:var(--pill-bg);box-shadow:0 2px 6px rgba(0,0,0,.16);
  z-index:3;color:var(--c-icone);display:none;place-items:center;text-decoration:none}
html.af-gestor .af-app .phero .admbtn{display:grid}
.af-app .phero .sharebtn svg{width:18px;height:18px;display:block}
/* .pdet ficava transparente sobre .af-bg (a foto de fundo que o lojista sobe em
   Configurações › Aparência, position:fixed atrás de tudo) — com fotos escuras ou
   de contraste médio, nome do produto e aviso de peso ficavam ilegíveis. Fundo
   sólido desacopla o texto de qualquer foto de fundo escolhida. */
.af-app .pdet{padding:16px 14px 16px;background:var(--surf);position:relative;z-index:2}
.af-app .pdet h2{font-size:20px;font-weight:800;line-height:1.2}
.af-app .pdet .dsc{color:var(--ink);font-weight:600;font-size:14px;margin-top:6px}
.af-app .pdet .prc{margin-top:12px;display:flex;flex-direction:column-reverse;align-items:flex-start;gap:2px}
.af-app .pdet .prc .now{color:var(--c-preco);font-weight:800;font-size:22px}
.af-app .pdet .prc .old{color:var(--ink-3);font-size:14px;text-decoration:line-through}
/* Riscado na linha da SACOLA (2026-08-26) — a classe .old existia só escopada em
   .pcard/.prow/.pdet, então na sacola não pegava nada. */
.af-app .citem .cp .old{color:var(--ink-3);font-size:12px;text-decoration:line-through;font-weight:600}
.af-app .qbar{display:flex;align-items:center;justify-content:center;gap:24px;padding:14px 0 4px}
.af-app .qbar button{width:42px;height:42px;border-radius:12px;background:var(--surf);border:1px solid var(--line);color:var(--c-sacola);font-size:22px;font-weight:800;display:grid;place-items:center}
.af-app .qbar .qn{font-size:20px;font-weight:800;min-width:26px;text-align:center}
.af-app .cstep{padding:4px 14px 0}
.af-app .cstep .prog{display:flex;gap:6px;margin:6px 0 16px}
.af-app .cstep .prog i{flex:1;height:4px;border-radius:3px;background:var(--line);transition:.3s var(--ease)}
.af-app .cstep .prog i.on{background:var(--red)}
.af-app .cstep .q{font-size:17px;font-weight:800;margin-bottom:4px}
.af-app .cstep .hint{font-size:12px;color:var(--ink-2);margin-bottom:14px}
.af-app .copt{display:flex;align-items:center;gap:12px;padding:14px;background:var(--surf);border:1px solid var(--line);border-radius:12px;margin-bottom:10px}
.af-app .copt.sel{border-color:var(--red);background:var(--sel-bg-2)}
.af-app .copt .radio{width:20px;height:20px;flex:none;border-radius:50%;border:2px solid var(--ink-3);display:grid;place-items:center}
.af-app .copt.sel .radio{border-color:var(--red)}
.af-app .copt.sel .radio::after{content:"";width:10px;height:10px;border-radius:50%;background:var(--red)}
.af-app .copt b{font-size:14px;font-weight:600}

/* ===== endereços ===== */
.af-app .acard{display:flex;gap:12px;padding:14px;margin:0 14px 10px;background:var(--surf);border:1px solid var(--line);border-radius:12px;align-items:flex-start}
.af-app .acard.sel{border-color:var(--red);background:var(--sel-bg-2)}
.af-app .acard .radio{width:20px;height:20px;flex:none;margin-top:2px;border-radius:50%;border:2px solid var(--ink-3);display:grid;place-items:center}
.af-app .acard.sel .radio{border-color:var(--red)}
.af-app .acard.sel .radio::after{content:"";width:10px;height:10px;border-radius:50%;background:var(--red)}
.af-app .acard .ai{flex:1;min-width:0}
.af-app .acard .ai b{font-size:14px;font-weight:700;display:block}
.af-app .acard .ai small{color:var(--ink-2);font-size:12.5px;display:block;margin-top:2px}
.af-app .acard .aact{display:flex;flex-direction:column;gap:8px;flex:none}
.af-app .acard .aact button{font-size:12px;font-weight:700;color:var(--red)}
.af-app .addnew{display:flex;align-items:center;gap:10px;padding:14px;margin:0 14px 10px;border:1.5px dashed var(--red);border-radius:12px;color:var(--red);font-weight:700;font-size:14px;justify-content:center}

/* ===== rodapé de faixas (2026-08-12) =====
   Arte pronta do lojista, largura total, empilhada. Sem padding lateral de propósito:
   a faixa é uma imagem que já traz a própria margem desenhada — o padding de 14px do
   resto da home encolheria a arte e deixaria duas listras do fundo nas laterais.
   `hidden` precisa da regra explícita porque `display:block` de .storefoot img não
   basta: o atributo só vence enquanto ninguém declarar display no PRÓPRIO <footer>. */
.af-app .storefoot{margin-top:20px}
.af-app .storefoot[hidden]{display:none}
/* line-height:0 no container: <img> é inline por padrão e o navegador reserva espaço
   pra descida da linha de base — vira uma listra do fundo entre as faixas empilhadas.
   O display:block no img resolve a maior parte; o line-height fecha o caso das faixas
   dentro de <a>, onde a âncora reintroduz a caixa de linha. */
.af-app .storefoot .ffaixa{display:block;line-height:0}
.af-app .storefoot img{display:block;width:100%;height:auto}
/* 2026-08-13 — abaixo de 900px só existe a faixa de CELULAR. A do computador é escondida
   aqui, e não pelo JS, porque a decisão é de largura de tela: media query reavalia sozinha
   quando o usuário gira o aparelho ou redimensiona a janela, sem listener de resize e sem
   re-render. O [hidden] de cada footer continua valendo por cima disso (quem não tem
   imagem some nas duas larguras). */
.af-app .storefoot-d{display:none}

/* ===== footer com os dados da loja (rodada 2, 2026-08-12) =====
   Mobile: colunas empilhadas, sem teto de altura — cabe o quanto precisar. Cores
   custom (fundo/texto/destaque) chegam via CSS var setada em JS (renderFooterDados);
   o fallback aqui é só rede de segurança pra antes do JS rodar. */
.af-app .storedata{background:var(--ft-bg,#111);color:var(--ft-fg,#fff);margin-top:14px;padding:18px 20px 22px}
.af-app .storedata[hidden]{display:none}
.af-app .storedata .fdcol{margin-bottom:16px}
.af-app .storedata .fdcol:last-child{margin-bottom:0}
/* fallback --gold (cor de destaque), NUNCA --red: --red carrega a cor PRIMÁRIA da loja e
   deixava este título preto no preto. Ver renderFooterDados(). */
.af-app .storedata .fdcol-t{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;color:var(--ft-accent,var(--gold));margin-bottom:6px}
.af-app .storedata .fdcol-r{font-size:13px;line-height:1.55;opacity:.92}
/* Ícones clicáveis de Instagram/WhatsApp — MONOCROMÁTICOS por decisão do usuário
   (2026-08-24): só o contorno do símbolo, sem bolha e sem cor de marca.
   HISTÓRICO: em 2026-08-16 a 1ª versão herdava --ft-accent e foi REJEITADA (a marca
   mudava de cor conforme a loja); a correção de 08-17 fixou as cores oficiais. Em
   2026-08-24 o usuário pediu explicitamente a versão sem cor — confirmado com o
   histórico à vista. `color:inherit` herda o texto do rodapé, então o contraste
   acompanha o tema de cada loja em vez de depender de um valor fixo. */
.af-app .storedata .fdredes{display:flex;gap:12px;justify-content:center;margin-top:4px;padding-top:14px;border-top:1px solid rgba(255,255,255,.14)}
.af-app .storedata .fdico{display:flex;align-items:center;justify-content:center;width:40px;height:40px;color:inherit;opacity:.92;flex:0 0 auto}
.af-app .storedata .fdico:active{opacity:.75}

/* toast */
/* O balão de mensagem virou _shared/toast.css (.mtoast) em 2026-08-26 — faixa
   vermelha no TOPO, com quebra de linha. O `.toast` que morava aqui tinha
   white-space:nowrap (texto longo vazava pelas laterais) e bottom:150px (colidia com
   a barra "Adicionar" fixa). NÃO recriar `.toast` neste arquivo. */

/* ==================== DESKTOP (>=900px) — redesenho profissional (header claro, grades) ========= */
@media (min-width:900px){
  .af-app{background:var(--bg)}
  .af-app .stage{max-width:1200px;box-shadow:none;padding-top:64px}
  .af-app .af-bg{max-width:1200px}
  .af-app .screen{min-height:calc(100vh - 64px);padding-bottom:56px}
  .af-app .cats h4,.af-app .rail .rh,.af-app .listwrap h4,
  .af-app .catscroll,.af-app .railscroll,.af-app .banners,
  .af-app .modebar{padding-left:32px;padding-right:32px}

  /* ---- NAV fixa no topo: marca à esquerda, links à direita ---- */
  .af-app .tabbar{top:0;bottom:auto;max-width:1200px;flex-direction:row;justify-content:flex-end;
    align-items:center;gap:4px;padding:11px 32px;border-top:none;border-bottom:1px solid var(--line);
    box-shadow:0 1px 2px rgba(0,0,0,.04)}
  .af-app .tabbar::before{content:var(--store-name);margin-right:auto;font-weight:800;font-size:21px;
    color:var(--red);letter-spacing:-.02em}
  .af-app .tab{flex:0 0 auto;flex-direction:row;gap:8px;font-size:14px;padding:8px 15px;border-radius:10px;
    color:var(--ink-2);transition:background .15s,color .15s}
  .af-app .tab:hover{background:var(--line-2);color:var(--ink)}
  .af-app .tab.on{background:var(--tab-on-bg);color:var(--red)}
  .af-app .tab svg{width:19px;height:19px}
  .af-app .tab .badge{transform:translate(4px,-9px)}

  /* ---- HEADER: mantém a faixa colorida da marca (--header-1/--header-2) igual ao mobile,
     só ajusta espaçamento/tamanho pro layout largo do desktop ---- */
  .af-app .addrbar{padding:26px 32px 4px}
  .af-app .addrbar .val{font-size:19px;font-weight:800}
  .af-app .addrbar .brand .blogo{width:64px;height:64px}
  .af-app .addrbar .brand .bname{font-size:24px}
  .af-app .addrbar .addr{font-size:13px}
  .af-app .lstatus{font-size:12px}   /* NIVEL: ambos [selo-horario] */
  .af-app .addrbar .pic{display:none}           /* avatar redundante — Perfil já está na nav */
  /* ...MAS o #picBtn tem dois papéis: com grupo configurado ele deixa de ser o avatar e
     vira o botão do WhatsApp (classe .wpp, ver atualizarIconePerfilHeader). A regra de
     cima foi escrita pensando só no avatar e levava o WhatsApp junto — era esse o motivo
     de "o ícone do WhatsApp não aparece no desktop" (relatado pelo tocomsede, 2026-08-12).
     Só o botão verde volta; a bolha de Perfil (sem .wpp) segue oculta, porque Perfil
     continua na nav. display:grid = o mesmo do bloco base de .addrbar .pic. */
  .af-app .addrbar .pic.wpp{display:grid}
  /* mesmo botão, depois de descer pra linha da busca (.wpp-inline): segue oculto no
     desktop, exatamente como já era antes de ele poder mudar de pai. E agora ele nunca
     CHEGA aqui no desktop — posicionarWpp() trava o modo inline acima de 900px. Sem
     essa trava, a regra de cima faria o botão aparecer e sumir sozinho conforme a
     largura da logo, que é pior que o bug original. */
  .af-app.wpp-inline .searchbar .pic{display:none}
  /* ---- RODAPÉ no desktop (2026-08-12; reescrito em 2026-08-13) ----
     A tentativa anterior era conter a MESMA arte por altura (max-height:104px) e
     centralizá-la sobre um fundo escuro. Não resolveu: uma arte pensada pro celular
     encolhia até virar um cartãozinho no meio do fundo — "a imagem é pequena demais para
     o desktop". A causa é que não existe arte única boa em 480px e em 1200px.

     Agora cada largura tem a SUA arte (campo `dispositivo`), então aqui a de computador
     ocupa a largura toda e a altura acompanha a proporção — nada de recorte, nada de
     encolher. Medida recomendada ao lojista: 2400x400 (6:1), que em 1200px de palco
     renderiza ~200px de altura. Sem padding lateral: a faixa encosta nas bordas, como
     rodapé de site.

     margin-bottom negativo = o -56px do padding-bottom de `.screen` (ver regra acima):
     sem isso sobra uma faixa do fundo da página DEPOIS do rodapé e ele não encosta no
     fim da tela. Se aquele padding mudar, este valor acompanha. */
  .af-app .storefoot{background:#111;margin-top:32px;padding:0;margin-bottom:-56px}
  .af-app .storefoot img{width:100%;height:auto;margin:0}
  /* Inverte a regra de fora do @media: no desktop quem aparece é a faixa do computador,
     e a do celular sai. Cada uma continua sujeita ao próprio [hidden] — loja sem arte de
     computador simplesmente não tem rodapé de imagem aqui, e nenhuma tarja vazia sobra. */
  .af-app .storefoot-d{display:block}
  .af-app .storefoot-d[hidden]{display:none}
  .af-app #storefoot{display:none}
  /* Footer de dados no desktop (rodada 2, 2026-08-12): teto medido de 150px (padding
     18+16 + título 10px + até 3 linhas de 12.5px/1.45 ≈ 130px). Colunas viram grid —
     coluna sem conteúdo simplesmente não existe no DOM (renderFooterDados), então
     auto-fit nunca deixa buraco vazio. Mesmo truque de margin-bottom:-56px do
     .storefoot acima, mas só QUANDO é o elemento visualmente por último: se o rodapé
     de imagem TAMBÉM estiver ligado, é ele (não este) que fica colado nas bordas —
     por isso a regra seguinte zera o -56px do .storefoot quando um .storedata visível
     vem depois, senão os dois -56px se somariam e um cobriria o outro.

     2026-08-13: a cadeia de irmãos virou #storefoot → #storefootd → #storedata. A regra
     abaixo continua correta sem mudança porque no desktop o #storefoot está display:none
     e quem encosta em .storedata é o #storefootd — que também carrega a classe .storefoot.
     Se algum dia entrar um 4º bloco no meio, esta regra precisa ser revista junto. */
  .af-app .storefoot:has(+ .storedata:not([hidden])){margin-bottom:0}
  .af-app .storedata{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
    gap:16px 28px;margin-top:14px;padding:18px 32px 16px;margin-bottom:-56px}
  .af-app .storedata .fdcol{margin-bottom:0}
  .af-app .storedata .fdcol-r{font-size:12.5px;line-height:1.45}
  .af-app .searchbar{background:transparent;padding:14px 32px 4px}
  .af-app .searchbar .search{max-width:560px;background:var(--surf);border:1px solid var(--line);
    box-shadow:var(--sh);color:var(--ink)}
  .af-app .searchbar .search input{color:var(--ink)}
  .af-app .searchbar .search svg{color:var(--ink-3)}
  .af-app .modebar{padding-top:16px;padding-bottom:4px}

  /* ---- Banners: fileira de 3 iguais, sem sobra cortada ---- */
  .af-app .banners{padding-top:18px;padding-bottom:4px}
  .af-app .banners .btrack{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;overflow:visible}
  .af-app .banner{flex:initial;min-height:144px}

  /* ---- Categorias em grade que quebra linha ---- */
  .af-app .cats{padding-top:26px}
  .af-app .catscroll{flex-wrap:wrap;overflow:visible;gap:18px 20px}
  .af-app .catitem{width:84px}
  .af-app .catitem .ic{width:64px;height:64px}

  /* ---- Trilhos -> grade de 5 colunas, cards compactos com hover ---- */
  .af-app .rail{padding-top:26px}
  .af-app .railscroll{display:grid;grid-template-columns:repeat(5,1fr);gap:18px;overflow:visible}
  /* Vitrine por categoria SEM limite (até 201 produtos): no computador segue fileira que se
     arrasta — em grade viraria 40 linhas. 5 cartões por largura, barra fina à mostra (mouse).
     NIVEL: ambos [vitrines-categoria] */
  .af-app .railcat .railscroll{display:flex;overflow-x:auto;scrollbar-width:thin;padding-bottom:10px}
  .af-app .railcat .railscroll::-webkit-scrollbar{display:block;height:6px}
  .af-app .railcat .railscroll .pcard{flex:0 0 calc((100% - 72px)/5)}
  .af-app .pcard{width:auto;transition:transform .15s var(--ease),box-shadow .15s var(--ease)}
  .af-app .pcard:hover{transform:translateY(-3px);box-shadow:0 10px 28px -8px rgba(0,0,0,.18)}
  .af-app .pcard .ph{height:118px;font-size:46px}

  /* ---- "Tudo na loja" e busca: 2 colunas, com hover ---- */
  .af-app .listwrap{padding-top:8px}
  /* minmax(0,1fr), não 1fr (2026-09-17): com o nome da linha em 1 linha (nowrap), a largura
     MÍNIMA de cada item virou a do nome inteiro, e `1fr` não encolhe abaixo disso — a coluna
     com nome longo ficava mais larga e passava da borda (visto no combas e no showdosbiscoitos). */
  .af-app #listAll,.af-app #buscaList{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:0 18px}
  .af-app .prow{border-radius:14px;border-bottom:none;box-shadow:var(--sh);margin-bottom:14px;
    transition:transform .15s var(--ease),box-shadow .15s var(--ease)}
  .af-app .prow:hover{transform:translateY(-2px);box-shadow:0 10px 24px -10px rgba(0,0,0,.16)}

  /* ---- Telas de formulário/detalhe centralizadas (não esticam) ---- */
  .af-app .screen[data-scr="checkout"],.af-app .screen[data-scr="entrega"],.af-app .screen[data-scr="sacola"],
  .af-app .screen[data-scr="perfil"],.af-app .screen[data-scr="login"],
  .af-app .screen[data-scr="cadastro"],.af-app .screen[data-scr="esqueci"],
  .af-app .screen[data-scr="endform"],.af-app .screen[data-scr="enderecos"],
  .af-app .screen[data-scr="produto"],.af-app .screen[data-scr="pedido"],
  .af-app .screen[data-scr="pedidos"],.af-app .screen[data-scr="horarios"],
  .af-app .screen[data-scr="taxas"],.af-app .screen[data-scr="pagamento"],
  .af-app .screen[data-scr="contato"],.af-app .screen[data-scr="termos"],
  .af-app .screen[data-scr="favoritos"],.af-app .screen[data-scr="notificacoes"],
  .af-app .screen[data-scr="sucesso"],
  .af-app .screen[data-scr="enviado"]{max-width:780px;margin:0 auto;width:100%}

  /* ---- Sacola flutuante -> canto inferior direito ---- */
  .af-app .cartfab{left:auto;right:24px;transform:translateX(0) translateY(16px);
    width:min(420px,calc(100% - 48px));max-width:420px}
  .af-app .cartfab.show{transform:translateX(0) translateY(0)}
}

/* =========================================================================
   ================= TEMA ESCURO — 1ª metade: TOKENS =======================
   A classe `dark` nasce no SERVIDOR (_storemodoclass_html), nunca no JS: o app
   volta do painel com reload completo, então qualquer decisão tomada no callback
   de /delivery/config chega depois do primeiro frame e o cliente veria a loja
   branca piscando toda vez — mesma causa/cura do flash de cor e de nome (09/08).

   Nada aqui pinta componente: só troca o VALOR dos tokens que o CSS acima já
   consome, então tela que ninguém lembrou de revisar também nasce escura. É o
   oposto do que quebrou o tema de backend (1616 linhas listando seletor a
   seletor, e todo módulo fora da lista continuava branco) — ver
   docs/modulos/tema-refatoracao-variaveis-plano.md §1.
   ========================================================================= */
.af-app.dark{
  /* 🔴 2026-09-02 — os literais viraram FALLBACK do token servido pelo Python
     (_storecolors_html). Sem token (Python velho, marcador cru, campo vazio) o valor é
     EXATAMENTE o de 31/08; com token, é o que o lojista escolheu. Nunca trocar por uma
     segunda declaração em `.af-app.dark`: o <style> servido sai em `.af-app` e perderia
     a disputa de especificidade, calado. Ver delivery-tema-dark-detalhes-plano.md §4.2. */
  --bg:var(--dk-bg,#0a0a10); --surf:var(--dk-surf,#14141d); --surf-2:var(--dk-surf-2,#1a1a25);
  --ink:var(--dk-ink,#f2f2f7); --ink-2:var(--dk-ink-2,#a6a7b4); --ink-3:var(--dk-ink-3,#6f7080);
  --line:rgba(255,255,255,.085); --line-2:rgba(255,255,255,.05);
  --green:#42d392;
  --ph-bg:linear-gradient(160deg,rgba(255,255,255,.07),rgba(255,255,255,.02));
  --ph-bg-hero:linear-gradient(160deg,rgba(255,255,255,.08),rgba(255,255,255,.02));
  --ph-flat:var(--dk-ph-flat,#12121a);
  --sel-bg:rgba(var(--c-cta-rgb),.16); --sel-bg-2:rgba(var(--c-cta-rgb),.10);
  --warn-bg:rgba(var(--gold-rgb),.12); --warn-track:rgba(var(--gold-rgb),.22);
  --neutral-bg:rgba(255,255,255,.08); --neutral-ink:#c3c4cf;
  --surf-3:rgba(255,255,255,.06);
  /* estado NÃO segue a marca: erro é vermelho e sucesso é verde mesmo em loja
     dourada ou azul — quem lê precisa reconhecer o sinal, não a identidade. */
  --ok-bg:rgba(66,211,146,.14);
  --tab-on-bg:rgba(var(--red-rgb),.18);
  --pill-bg:rgba(18,18,26,.72);
  /* a sombra do claro é preta e some no escuro: aqui vira relevo (fio de luz em
     cima, poço embaixo) */
  --sh:inset 0 1px 0 rgba(255,255,255,.06),0 12px 30px -14px rgba(0,0,0,.9);
  /* MARCA: fundo continua na cor cheia; TEXTO/ÍCONE usam a clareada, senão marca
     escura (vinho, marinho) fica ilegível — medido: #4a1c2b dá 1.40:1 contra este
     fundo, e o mínimo do WCAG para texto é 4.5:1. */
  --c-preco:var(--c-preco-lit);
  --c-icone:var(--c-icone-lit);
  --c-cta:var(--c-cta-lit); --c-cta-rgb:var(--c-cta-lit-rgb); --c-cta-ink:var(--c-cta-lit-ink);
  --c-sacola:var(--c-sacola-lit); --c-sacola-rgb:var(--c-sacola-lit-rgb); --c-sacola-ink:var(--c-sacola-lit-ink);
  /* header deixa de ser a marca chapada e vira vidro escuro com halo da marca */
  --header-1:var(--dk-header-1,#191926); --header-2:var(--dk-header-2,#0d0d14);
  --header-ink:var(--dk-header-ink,#f2f2f7); --header-1-rgb:var(--dk-header-1-rgb,25,25,38);
  --fx:1;   /* intensidade das luzes — trocada pelas classes fx-* (painel do lojista) */
  background:var(--dk-fora,#07070c);
}
.af-app.dark.fx-sutil{--fx:.55}
.af-app.dark.fx-medio{--fx:1}
.af-app.dark.fx-neon{--fx:1.6}
.af-app.dark .stage{background:var(--bg);box-shadow:0 0 100px rgba(0,0,0,.9)}
/* o véu da foto da loja é claro por padrão: no escuro tem que escurecer, senão a
   foto do lojista lava a tela inteira de branco */
.af-app.dark .af-bg{background-image:
  radial-gradient(300px 300px at 12% 4%,rgba(var(--aurora-1-rgb,var(--red-rgb)),calc(.55 * var(--fx-aurora,1))),transparent 70%),
  radial-gradient(260px 260px at 95% 42%,rgba(var(--aurora-2-rgb,91,61,245),calc(.38 * var(--fx-aurora,1))),transparent 70%),
  radial-gradient(300px 300px at 22% 97%,rgba(var(--aurora-3-rgb,0,194,255),calc(.26 * var(--fx-aurora,1))),transparent 70%),
  linear-gradient(rgba(var(--dk-bg-rgb,10,10,16),.74),rgba(var(--dk-bg-rgb,10,10,16),.74)),var(--store-bg)}
.af-app.dark .banner.b2{background:linear-gradient(120deg,#181826,#26263a)}

/* =========================================================================
   ================= TEMA ESCURO — 2ª metade: CAMADA DE LUZ ================
   Só efeito. Tudo em opacidade/box-shadow/filtro — NUNCA posição — para poder
   desligar inteiro (fx-sutil) sem mexer em layout. Todo brilho puxa
   rgba(var(--red-rgb)...), então acompanha a cor real de cada loja.
   ========================================================================= */
/* 1. auroras — PINTADAS NO PRÓPRIO .af-bg (2026-08-31).
   Estavam em dois pseudo-elementos do tamanho da tela. Medido com a CPU 6x mais
   lenta: 51,6 FPS contra 60,3 do tema claro — e continuava assim mesmo com elas
   PARADAS, porque o custo não era a animação: era compor duas camadas extras de tela
   cheia a cada frame de rolagem. Como `background-image` do elemento que já existe,
   é uma pintura só, na camada que já era pintada de qualquer jeito. */
/* 2. vinheta: profundidade. Fraca de propósito — .55 apagava o botão de compra,
   que vive no .screen (z-index 2), abaixo desta camada. */
.af-app.dark .stage::before{content:"";position:fixed;inset:0;max-width:var(--w);margin:0 auto;
  pointer-events:none;z-index:3;opacity:calc(.9 * var(--fx-aurora,var(--fx)));
  background:radial-gradient(130% 92% at 50% 8%,transparent 52%,rgba(0,0,0,.30) 100%)}
/* 3. spotlight seguindo o dedo (--mx/--my vêm do JS; sem JS fica parado no topo) */
/* o spotlight que seguia o dedo foi REMOVIDO (2026-08-31): repintava a tela inteira a
   cada pointermove, e era o efeito que menos aparecia. */
/* 4. header: vidro escuro + halo da marca + varredura */
.af-app.dark .tophero{
  background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,0)),linear-gradient(135deg,var(--header-1),var(--header-2));
  box-shadow:0 18px 46px -22px rgba(var(--halo-topo-rgb,var(--red-rgb)),calc(.9 * var(--fx-header,var(--fx))));
  overflow:hidden}
.af-app.dark .tophero::before{content:"";position:absolute;top:-120px;left:-10%;width:120%;height:260px;pointer-events:none;
  background:radial-gradient(50% 60% at 22% 100%,rgba(var(--halo-topo-rgb,var(--red-rgb)),.55),transparent 70%);opacity:var(--fx-header,var(--fx))}
.af-app.dark .tophero::after{content:"";position:absolute;top:0;left:0;width:45%;height:100%;pointer-events:none;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.14),transparent);
  transform:skewX(-18deg);opacity:var(--fx-header,var(--fx));animation:luz-sweep 7s cubic-bezier(.22,1,.36,1) infinite;will-change:transform}
/* PERFORMANCE (2026-08-31): animar `left` obriga o navegador a refazer LAYOUT a cada
   frame, na thread principal — foi uma das causas do app "travado". transform é
   composited: roda na GPU sem tocar no layout. Mesma aparência, custo ~zero. */
@keyframes luz-sweep{0%{transform:translateX(-140%) skewX(-18deg)}
  60%,100%{transform:translateX(340%) skewX(-18deg)}}
.af-app.dark .searchbar{position:relative}
.af-app.dark .searchbar::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(var(--halo-topo-rgb,var(--red-rgb)),.9),rgba(255,255,255,.5),rgba(var(--halo-topo-rgb,var(--red-rgb)),.9),transparent);
  opacity:calc(.85 * var(--fx-header,var(--fx)));box-shadow:0 0 12px rgba(var(--red-rgb),calc(.8 * var(--fx-header,var(--fx))))}
.af-app.dark .addrbar .blogo{box-shadow:0 0 0 1px rgba(255,255,255,.10),0 6px 20px -6px rgba(var(--red-rgb),calc(.9 * var(--fx-header,var(--fx))))}
.af-app.dark .addrbar .bname{text-shadow:0 0 18px rgba(var(--red-rgb),calc(.55 * var(--fx-header,var(--fx))))}
.af-app.dark .addrbar .pic,.af-app.dark .notifbell{background:rgba(255,255,255,.07);box-shadow:inset 0 0 0 1px rgba(255,255,255,.09)}
.af-app.dark .search{background:rgba(255,255,255,.07);box-shadow:inset 0 0 0 1px rgba(255,255,255,.09)}
.af-app.dark .search svg{filter:drop-shadow(0 0 6px rgba(var(--red-rgb),calc(.9 * var(--fx-header,var(--fx)))))}
/* 5. pílula de modo ativa vira lâmpada */
.af-app.dark .mode{box-shadow:inset 0 0 0 1px rgba(255,255,255,.08)}
.af-app.dark .batl .mode{box-shadow:none}  /* atalhos da busca: só a borda fina (21/10/2026) */
.af-app.dark .mode.on{background:linear-gradient(135deg,rgb(var(--red-rgb)),var(--red-2));
  box-shadow:0 0 0 1px rgba(255,255,255,.14),0 8px 24px -6px rgba(var(--red-rgb),calc(.85 * var(--fx-header,var(--fx))))}
/* 6. banners: bokeh + brilho diagonal + moldura acesa */
.af-app.dark .banner{box-shadow:0 18px 40px -20px rgba(0,0,0,.9),0 0 0 1px rgba(255,255,255,.08)}
.af-app.dark .banner.b1{box-shadow:0 18px 44px -18px rgba(var(--red-rgb),calc(.75 * var(--fx-card,var(--fx)))),0 0 0 1px rgba(255,255,255,.10)}
.af-app.dark .banner::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;opacity:var(--fx-card,var(--fx));
  background:radial-gradient(60px 60px at 18% 22%,rgba(255,255,255,.28),transparent 70%),
             radial-gradient(90px 90px at 82% 78%,rgba(255,255,255,.18),transparent 70%),
             radial-gradient(40px 40px at 62% 18%,rgba(255,255,255,.22),transparent 70%)}
/* banner só com imagem: as manchas de luz são enfeite do gradiente — sobre a arte do lojista viram defeito */
.af-app.dark .banner.so-img::before{display:none}
.af-app.dark .banner::after{content:"";position:absolute;top:0;left:0;width:55%;height:100%;z-index:1;pointer-events:none;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.22),transparent);transform:skewX(-16deg);
  opacity:var(--fx-card,var(--fx));animation:luz-sweep 5.5s cubic-bezier(.22,1,.36,1) infinite 1s}
/* 7. categorias: anel de luz girando por trás do ícone */
.af-app.dark .catitem .ic{background:rgba(255,255,255,.05);isolation:isolate}
.af-app.dark .catitem .ic::before{content:"";position:absolute;inset:-40%;z-index:-1;opacity:calc(.75 * var(--fx-card,var(--fx)));
  background:conic-gradient(from 0deg,transparent 0deg,rgba(var(--red-rgb),.95) 40deg,transparent 120deg,transparent 240deg,rgba(120,90,255,.8) 290deg,transparent 340deg);
}
.af-app.dark .catitem .ic::after{content:"";position:absolute;inset:1px;border-radius:15px;z-index:-1;
  background:linear-gradient(160deg,rgba(255,255,255,.12),rgba(255,255,255,.02) 45%,transparent);
  background-color:var(--surf);box-shadow:inset 0 0 0 1px rgba(255,255,255,.10)}
@keyframes luz-spin{to{transform:rotate(360deg)}}
/* a rotação (luz-spin) saiu por performance: eram 8 gradientes cônicos girando ao
   mesmo tempo. O anel de luz continua, parado — e cada item recebe um ângulo
   diferente, então a fileira não fica repetitiva. */
.af-app.dark .catitem:nth-child(2n) .ic::before{transform:rotate(120deg)}
.af-app.dark .catitem:nth-child(3n) .ic::before{transform:rotate(240deg)}
/* 8. cards: vidro + moldura em gradiente (máscara, porque border-image não respeita
   o raio) + preço em neon */
.af-app.dark .pcard{background:linear-gradient(180deg,rgba(255,255,255,.06),rgba(255,255,255,.02));
  box-shadow:var(--sh),0 0 0 1px rgba(255,255,255,.07)}
.af-app.dark .pcard::before{content:"";position:absolute;inset:0;border-radius:14px;padding:1px;pointer-events:none;z-index:4;
  opacity:calc(.9 * var(--fx-card,var(--fx)));
  background:linear-gradient(140deg,rgba(var(--red-rgb),.85),rgba(255,255,255,.10) 35%,transparent 60%,rgba(120,90,255,.55));
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude}
.af-app.dark .pcard .pr .now,.af-app.dark .prow .pi .pr .now{text-shadow:0 0 16px rgba(var(--red-rgb),calc(.6 * var(--fx-card,var(--fx))))}
.af-app.dark .pcard .addc,.af-app.dark .prow .addc{
  box-shadow:0 6px 18px -4px rgba(var(--c-sacola-rgb),calc(.95 * var(--fx-card,var(--fx)))),inset 0 1px 0 rgba(255,255,255,.28),0 0 0 1px rgba(255,255,255,.10)}
.af-app.dark .tagoff{box-shadow:0 0 16px rgba(var(--gold-rgb),calc(.85 * var(--fx-card,var(--fx)))),inset 0 1px 0 rgba(255,255,255,.4)}
.af-app.dark .promotag{box-shadow:0 0 12px rgba(var(--gold-rgb),calc(.6 * var(--fx-card,var(--fx))))}
/* 9. lista: relevo nas miniaturas */
.af-app.dark .prow{background:linear-gradient(180deg,rgba(255,255,255,.035),rgba(255,255,255,.012))}
.af-app.dark .prow .th,.af-app.dark .pcard .ph,.af-app.dark .phero,.af-app.dark .citem .th{box-shadow:inset 0 0 0 1px rgba(255,255,255,.07)}
/* 10. frete grátis: luz correndo dentro da barra */
.af-app.dark .freebar{box-shadow:var(--sh),0 0 0 1px rgba(255,255,255,.07)}
.af-app.dark .freebar-fill{box-shadow:0 0 14px rgba(var(--gold-rgb),calc(.9 * var(--fx-card,var(--fx))));position:relative;overflow:hidden}
.af-app.dark .freebar-fill::after{content:"";position:absolute;inset:0;opacity:var(--fx-card,var(--fx));
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.75),transparent);
  transform:translateX(-100%);animation:luz-run 2.2s linear infinite}
@keyframes luz-run{to{transform:translateX(100%)}}
/* 11. abas: vidro + lâmpada acima do ícone ativo */
/* PERFORMANCE: sem backdrop-filter. A barra é fixa, mas o conteúdo passa POR TRÁS
   dela — o navegador teria de recompor o desfoque a cada frame de rolagem, e era um
   dos motivos do app parecer travado. Fundo quase sólido dá o mesmo ar de vidro. */
.af-app.dark .tabbar{background:rgba(12,12,18,.94);
  border-top:1px solid rgba(255,255,255,.08);box-shadow:0 -18px 40px -24px rgba(var(--red-rgb),calc(.9 * var(--fx-card,var(--fx))))}
.af-app.dark .tab{position:relative}
/* a barrinha luminosa acima do ícone da aba saiu a pedido do dono (31/08): virava
   uma linha atravessada em cima do ícone. O brilho do próprio ícone dá o destaque. */
.af-app.dark .tab.on svg{filter:drop-shadow(0 0 8px rgba(var(--red-rgb),calc(.95 * var(--fx-card,var(--fx)))))}
/* 12. sacola flutuante: pulso + brilho varrendo */
.af-app.dark .cartfab button{box-shadow:0 14px 34px -10px rgba(var(--c-sacola-rgb),calc(.95 * var(--fx-card,var(--fx)))),inset 0 1px 0 rgba(255,255,255,.25),0 0 0 1px rgba(255,255,255,.10);
  position:relative;overflow:hidden}
@keyframes luz-pulse{0%,100%{filter:brightness(1)}50%{filter:brightness(1.12)}}
.af-app.dark .cartfab button::after{content:"";position:absolute;top:0;left:0;width:45%;height:100%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.30),transparent);transform:skewX(-18deg);
  opacity:var(--fx-card,var(--fx));animation:luz-sweep 4.5s cubic-bezier(.22,1,.36,1) infinite}
/* 13. telas internas: topo de vidro, herói com feixe, CTA acesa */
.af-app.dark .tb{background:rgba(12,12,18,.95);
  border-bottom:1px solid rgba(255,255,255,.08)}
.af-app.dark .phero::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:var(--fx-card,var(--fx));
  background:radial-gradient(70% 60% at 50% 15%,rgba(var(--red-rgb),.35),transparent 70%)}
.af-app.dark .obl.on{box-shadow:inset 0 0 0 3.5px var(--surf),0 0 14px rgba(var(--red-rgb),var(--fx-card,var(--fx)))}
.af-app.dark .big:not(.ghost){box-shadow:0 12px 30px -8px rgba(var(--c-cta-rgb),calc(.9 * var(--fx-card,var(--fx)))),inset 0 1px 0 rgba(255,255,255,.22)}
/* 13b. CAMPOS DE FORMULÁRIO — input/textarea/select sem `background` no CSS herdam o
   branco da folha do NAVEGADOR (`background-color:field`). No tema claro ninguém nota,
   porque o pai também é branco; no escuro vira uma caixa branca acesa no meio da tela.
   Foi o campo de busca que denunciou, mas vale para todo formulário do app (login,
   cadastro, endereço, cupom), então a regra é genérica de propósito. Checkbox e radio
   ficam de fora: neles o fundo é o próprio controle desenhado pelo sistema. */
.af-app.dark input:not([type="checkbox"]):not([type="radio"]),
.af-app.dark textarea,
.af-app.dark select{background-color:rgba(255,255,255,.05);color:var(--ink);
  border-color:var(--line);caret-color:var(--c-preco)}
.af-app.dark ::placeholder{color:var(--ink-3);opacity:1}
/* a busca é a exceção: quem desenha a caixa é o .search em volta, o campo é só o texto.
   Os :not repetidos não são enfeite — sem eles a regra genérica acima tem
   especificidade MAIOR (cada :not([attr]) soma) e a exceção não pega. */
.af-app.dark .search input:not([type="checkbox"]):not([type="radio"]){background-color:transparent}
/* autofill do Chrome repinta o campo de branco por dentro — a sombra interna gigante é
   o único jeito de cobrir, porque o navegador ignora background nesse estado */
.af-app.dark input:-webkit-autofill{-webkit-text-fill-color:var(--ink);
  -webkit-box-shadow:0 0 0 1000px #1a1a25 inset;caret-color:var(--ink)}

/* 13c. pastilhas sobre a foto (favorito/compartilhar): no escuro a bola branca some e
   vira vidro. O ícone segue --c-icone, que no escuro já é a versão clareada, então lê
   bem tanto na pastilha quanto sobre foto clara. */
.af-app.dark .pcard .favbtn,
.af-app.dark .phero .favbtn,
.af-app.dark .phero .sharebtn{
  box-shadow:0 2px 10px rgba(0,0,0,.55),inset 0 0 0 1px rgba(255,255,255,.16)}

/* 14. quem pediu menos movimento fica com o brilho, sem a animação */
@media (prefers-reduced-motion:reduce){
  .af-app.dark .tophero::after,
  .af-app.dark .banner::after,.af-app.dark .cartfab button,.af-app.dark .cartfab button::after,
  .af-app.dark .catitem .ic::before,.af-app.dark .freebar-fill::after{animation:none}
}

/* ===== CHIP DE BANDEIRA DE PAGAMENTO (F3 — 2026-09-11) ======================
   Identifica a bandeira pelo NOME sobre a cor dela, sem embutir logotipo de
   terceiro: carrega junto com a pagina (nenhuma requisicao de rede, nenhum
   arquivo de marca no repositorio) e continua legivel no tema escuro, porque a
   cor de fundo e a do texto vem juntas do servidor, ja' pareadas por contraste. */
.af-app .pgchip{display:inline-block;padding:4px 10px;border-radius:6px;
  font-size:13px;font-weight:700;letter-spacing:.2px;line-height:1.3;
  box-shadow:0 1px 2px rgba(0,0,0,.18)}
/* F3.1 — variante com logo real (FORMA_LOGO): fundo branco fixo (nao depende do
   tema escuro nem da cor da marca), a imagem e' que carrega a identidade. */
.af-app .pgchip-logo{background:#fff;padding:5px 10px;box-shadow:0 1px 2px rgba(0,0,0,.18),inset 0 0 0 1px rgba(0,0,0,.06)}
.af-app .pgchip-logo img{display:block;height:20px;width:auto}
/* NIVEL: ambos [formas-pagamento] — PAGAMENTO NO ESTILO iFood (27/10/2026, dono: "ícones muito grandes, ocupando espaço"): abas
   "Pagar pelo app" × "Pagar na entrega/maquininha" e linhas compactas (ícone pequeno, nome, descrição, seleção à direita). */
.af-app .pgtabs{display:flex;margin:4px 14px 12px;border-bottom:1px solid var(--line)}
.af-app .pgtabs button{flex:1;padding:12px 6px;background:none;border:0;border-bottom:2px solid transparent;margin-bottom:-1px;
  font-size:14.5px;font-weight:700;color:var(--ink-2)}
.af-app .pgtabs button.on{color:var(--red);border-bottom-color:var(--red)}
.af-app .pgpane[hidden]{display:none}
.af-app .opt.pgrow{padding:12px 14px;gap:12px}
.af-app .opt.pgrow .pgico{width:38px;height:38px;flex:none;display:grid;place-items:center;border-radius:10px;overflow:hidden}
.af-app .opt.pgrow .pgico img{max-width:30px;max-height:30px;object-fit:contain}
.af-app .opt.pgrow .pgico i{font-style:normal;font-size:12px;font-weight:800;color:#fff;width:30px;height:30px;display:grid;place-items:center;border-radius:8px}
.af-app .opt.pgrow .oi{flex:1;min-width:0}
.af-app .opt.pgrow .oi b{display:block;font-size:15px;font-weight:700;color:var(--ink)}
.af-app .opt.pgrow .oi small{display:block;font-size:12.5px;color:var(--ink-2);margin-top:2px}
.af-app .opt.pgrow .radio{order:3}
.af-app .opt.pgcomb .seta{font-size:20px;color:var(--ink-2);order:3}
.af-app .pgcomb-sheet{position:fixed;inset:0;z-index:300;background:rgba(0,0,0,.45);display:flex;align-items:flex-end;justify-content:center}
.af-app .pgcomb-sheet>div{background:var(--surf);width:100%;max-width:560px;max-height:88vh;overflow:auto;border-radius:16px 16px 0 0;padding:16px 0 calc(16px + env(safe-area-inset-bottom))}
.af-app .pgcomb-sheet h3{margin:0 16px 2px;font-size:16px;font-weight:800;text-align:center}
.af-app .pgcomb-sheet p{margin:0 16px 12px;text-align:center;color:var(--ink-2);font-size:13px}
.af-app .pgcomb-sheet .opt input{width:20px;height:20px;margin:0;order:3;accent-color:var(--red)}
.af-app .pgcomb-sheet .acts{padding:8px 14px 0;display:flex;flex-direction:column;gap:8px}
/* NIVEL: ambos [formas-pagamento] — os campos da forma escolhida abrem colados nela, como um dropdown (dono, 01/11/2026) */
.af-app .pgdet{margin:-11px 14px 10px;padding:12px 12px 2px;border:1px solid var(--red);border-top:0;border-radius:0 0 12px 12px;background:var(--surf)}
.af-app .pgdet[hidden]{display:none}
.af-app .opt.det-aberto{border-bottom-left-radius:0;border-bottom-right-radius:0}
.af-app .pgdet .field{margin:0 0 12px}
.af-app .pgdet .opt{margin:0 0 8px}
.af-app .opt .pgchev{order:2;flex:none;color:var(--ink-2);font-size:13px;line-height:1;transition:transform .15s}
.af-app .opt.det-aberto .pgchev{transform:rotate(180deg)}
/* campo de valor: o R$ fica fixo ao lado do número, dentro da moldura (o mesmo do painel, `_shared/formas.css`) */
.af-app .field label.pgval{display:flex;align-items:center;gap:8px;margin:0;padding:0 12px;border:1px solid var(--line);border-radius:10px;background:var(--surf);font-size:16px;font-weight:700;color:var(--ink);cursor:text}
.af-app .field label.pgval:focus-within{border-color:var(--red)}
.af-app .field label.pgval>span{color:var(--ink-2)}
.af-app .field .pgval input,.af-app .field .pgval input:focus{flex:1;min-width:0;width:auto;border:0;border-radius:0;padding:12px 0;background:transparent;font-weight:700}
.af-app .opt.pg .oi b{display:flex;align-items:center;gap:8px}

/* ===== MAPA DE CONFIRMAÇÃO DO ENDEREÇO (F1 — 2026-09-11) ======================
   POR QUE ESTA TELA EXISTE. Medido em 10/09 contra os 6 endereços reais do combas,
   a geocodificação por TEXTO acertou ZERO: três pararam no centroide do bairro,
   dois caíram em bairro DIFERENTE do informado ("Rua Mississipe" casa com a xará de
   Santa Cruz da Serra, 3,7 km fora, dentro do raio e com preço plausível) e um não
   existe no OSM. Nem com o CEP dos Correios em mãos o geocoder acerta — testado
   ViaCEP, OpenCEP, BrasilAPI, Nominatim (texto e estruturado) e Photon.
   Quem sabe onde o cliente mora é o cliente. Esta tela pergunta a ele.

   O PINO FICA FIXO NO CENTRO e o mapa se move sob ele (padrão iFood/Uber/99): no
   celular, acertar um alvo de 30px com o dedo em cima dele é muito pior do que
   arrastar o mapa inteiro. O ponto escolhido é sempre o centro da viewport.

   🔴 TODA cor aqui tem FALLBACK literal — `var(--bg,#f4f4f6)`, nunca `var(--bg)`.
   As variáveis do app vivem em `.af-app`, não em `:root`: na 1ª versão o overlay
   foi anexado ao <body>, fora desse escopo, e TODO `var()` caiu em vazio. Fundo
   transparente = o formulário de endereço aparecendo through o overlay, com os
   textos embaralhados (relatado 11/09). O overlay agora é filho do `.af-app` E
   carrega fallback: um overlay sem fundo opaco é ilegível, então o fundo não pode
   depender de o escopo estar certo.                                              */
.mapaOv{position:fixed;inset:0;z-index:200;display:flex;flex-direction:column;
  background:var(--bg,#f4f4f6);max-width:var(--w,480px);margin:0 auto;
  color:var(--ink,#3f3e3e);
  font-family:inherit;box-shadow:var(--sh,0 1px 3px rgba(0,0,0,.2))}
.mapaOv .mapaHead{display:flex;align-items:center;gap:8px;padding:10px 14px;
  background:var(--surf,#fff);border-bottom:1px solid var(--line,#ededed);flex:0 0 auto}
.mapaOv .mapaHead button{background:none;border:0;font-size:21px;line-height:1;
  color:var(--ink,#3f3e3e);padding:0 4px;cursor:pointer}
.mapaOv .mapaHead b{font-size:15.5px;color:var(--ink,#3f3e3e)}
/* Compactado a pedido (11/09): endereço e aviso em 1 linha cada sempre que couber,
   para sobrar altura de mapa no celular sem cortar informação. */
.mapaOv .mapaEnd{padding:7px 14px;background:var(--surf,#fff);
  border-bottom:1px solid var(--line,#ededed);font-size:13.5px;line-height:1.3;
  color:var(--ink,#3f3e3e);flex:0 0 auto}
.mapaOv .mapaEnd small{display:block;color:var(--ink-2,#717171);font-size:12px;margin-top:1px}
.mapaOv .mapaAviso{padding:7px 14px;font-size:12.5px;line-height:1.35;flex:0 0 auto}
.mapaOv .mapaAviso.ok{background:var(--ok-bg,#eafaf0);color:var(--ink,#3f3e3e)}
.mapaOv .mapaAviso.warn{background:var(--warn-bg,#fff7e6);color:var(--ink,#3f3e3e)}
/* 🔴 z-index 5 + contexto proprio: o Leaflet cria panes com z-index 400-1000
   DENTRO da area do mapa. Com o dropdown em z-index:3 e o mapa depois no DOM, a
   lista de sugestoes aparecia e sumia atras dos tiles no 1o repaint (relatado
   11/09: "aparece mas some rapido sem dar pra clicar"). A dupla .mapaWrap{z-index:0}
   + .mapaBuscaWrap{z-index:5} CONFINA os z-index internos do Leaflet: eles passam a
   competir so' dentro do mapa, nunca com a barra de busca. */
.mapaOv .mapaBuscaWrap{padding:7px 14px;background:var(--surf,#fff);
  border-bottom:1px solid var(--line,#ededed);position:relative;z-index:5;flex:0 0 auto}
.mapaOv .mapaBuscaWrap input{width:100%;padding:8px 11px;border:1px solid var(--line,#ededed);
  border-radius:9px;background:var(--bg,#f4f4f6);color:var(--ink,#3f3e3e);font-size:13.5px;
  font-family:inherit;box-sizing:border-box}
.mapaOv .mapaSug{position:absolute;left:14px;right:14px;top:calc(100% - 3px);z-index:6;
  background:var(--surf,#fff);border:1px solid var(--line,#ededed);border-radius:10px;
  overflow-y:auto;max-height:46vh;box-shadow:0 8px 28px rgba(0,0,0,.28);display:none}
.mapaOv .mapaSug button{display:block;width:100%;text-align:left;padding:12px 11px;min-height:44px;
  background:none;border:0;border-bottom:1px solid var(--line-2,#f1f1f1);font-size:13px;
  color:var(--ink,#3f3e3e);font-family:inherit;cursor:pointer}
.mapaOv .mapaSug button:last-child{border-bottom:0}
.mapaOv .mapaWrap{position:relative;z-index:0;flex:1 1 auto;min-height:180px;
  background:var(--line-2,#f1f1f1)}
.mapaOv .mapaCanvas{position:absolute;inset:0}
/* O pino é irmão do canvas e NÃO filho do Leaflet: dentro do mapa ele seria
   arrastado junto com os tiles e deixaria de marcar o centro.
   Desenhado em CSS (gota girada 45°) em vez de <img> com data URI: um SVG inline
   depende da CSP dos estáticos, e um pino que não pinta deixa a tela sem o único
   elemento que diz onde o ponto está. Forma pura não tem como falhar. */
/* 🔴 z-index 1200/1199, nao 2/1. Confinar o Leaflet em .mapaWrap{z-index:0}
   consertou o dropdown de busca, mas jogou os panes dele (400-1000) para DENTRO do
   mesmo contexto do pino: com z-index:2 o pino nascia visivel e AFUNDAVA no instante
   em que os tiles pintavam — o sintoma relatado em 11/09 foi "pisca o ponto e logo
   sai". O pino tem que ficar acima de TODA camada do Leaflet, controles inclusive
   (o maior deles e' 1000), e continua confinado pelo .mapaWrap, entao 1200 aqui nao
   disputa nada com o resto do app. */
.mapaOv .mapaPino{position:absolute;left:50%;top:50%;width:22px;height:22px;
  margin:-30px 0 0 -11px;background:var(--c-cta,#e30613);border:3px solid #fff;
  border-radius:50% 50% 50% 0;transform:rotate(-45deg);
  box-shadow:0 2px 6px rgba(0,0,0,.45);pointer-events:none;z-index:1200}
.mapaOv .mapaPinoSombra{position:absolute;left:50%;top:50%;width:12px;height:4px;
  margin:-2px 0 0 -6px;border-radius:50%;background:rgba(0,0,0,.35);
  pointer-events:none;z-index:1199}
.mapaOv .mapaAcoes{padding:10px 14px calc(10px + env(safe-area-inset-bottom,0px));
  background:var(--surf,#fff);border-top:1px solid var(--line,#ededed);flex:0 0 auto;
  display:flex;flex-direction:column;gap:7px}
.mapaOv .mapaAcoes button{width:100%;padding:12px;border-radius:11px;font-size:14.5px;
  font-weight:600;font-family:inherit;border:0;cursor:pointer}
.mapaOv .mapaGps{background:var(--bg,#f4f4f6);color:var(--ink,#3f3e3e);
  border:1px solid var(--line,#ededed) !important}
.mapaOv .mapaOk{background:var(--c-cta,#e30613);color:var(--c-cta-ink,#fff)}
.mapaOv .mapaOk:disabled{opacity:.45;cursor:not-allowed}
.mapaOv .mapaCarregando{position:absolute;inset:0;display:flex;align-items:center;
  justify-content:center;font-size:13.5px;color:var(--ink-2,#717171);text-align:center;
  padding:20px;z-index:3}
/* Atribuição do OpenStreetMap: obrigatória pela licença dos tiles (ODbL). */
.mapaOv .leaflet-control-attribution{font-size:9px;background:rgba(255,255,255,.75)}
/* Tela baixa (celular pequeno, teclado aberto): o mapa nunca some — ele encolhe,
   e quem cede espaço é o texto auxiliar. */
@media (max-height:620px){
  .mapaOv .mapaBuscaWrap{padding:5px 14px}
  .mapaOv .mapaEnd,.mapaOv .mapaAviso{padding:5px 14px;font-size:12px}
  .mapaOv .mapaAcoes{padding:8px 14px calc(8px + env(safe-area-inset-bottom,0px))}
  .mapaOv .mapaAcoes button{padding:10px;font-size:14px}
}
