/* ═══════════════════════════════════════════════════════════════════════════
   OS CARREGADORES DA MARCA E O VISOR DE PDF — a fonte ÚNICA das DUAS camadas
   ═══════════════════════════════════════════════════════════════════════════

   ## ⚠️⚠️ ISTO MORAVA SÓ NO `app.css`, E O PORTAL NÃO CARREGA O `app.css`

   Chamado #26 (15/09/2026): *"não é algo padronizado, tem mais de um tipo pra
   tela toda"* + *"isso no app inteiro"*.

   O app e o **portal do cliente** são duas camadas com layouts próprios:
   `layouts/base.html` carrega o `app.css`; `portal/base.html` tem folha
   INLINE e carregava **um único script** (`file_export.js`). Resultado medido:
   no portal, todo `Carregando…` ficava como TEXTO CRU — o "outro tipo" que ele
   viu —, e o PDF caía no visor de iframe, sem a saída que o #24 escreveu.

   Não bastava somar dois `<script>` lá: **componente é JS, CSS E MARKUP**, e o
   JS sem o CSS abriria o visor sem moldura e o spinner sem tamanho. Então o
   desenho saiu do `app.css` e virou este arquivo, que as DUAS camadas linkam.

   ⚠️ **Copiar pro `<style>` do portal seria o gêmeo que diverge**: o dia em que
   alguém mexesse no carregador, uma das camadas ficaria pra trás — sem erro,
   sem log, e só quem usa descobre. Uma fonte, dois `<link>`.

   ⚠️ Quem INJETA o SVG das facetas é o `brand_loaders.js`, e quem monta o visor
   é o `pdf_view.js`: os dois têm que viajar junto com esta folha em toda camada
   que a carregue. `tests/test_carregador_e_visor_em_toda_camada.py` cobra.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Vectra · loader branded "Facet cascade" ────────────────────────
   As 3 facetas do escudo (green → dark → teal) pulsam em cascata.
   SVG injetado por brand_loaders.js. Uso: <div class="vectra-loading">
   <span class="vectra-spinner"></span> Carregando…</div>.
   Ref: vectra_brand_kit/brand_showcase.html (.ld-cascade). */
.vectra-spinner { display:inline-block; width:60px; height:auto; line-height:0; vertical-align:middle; }
.vectra-spinner.sm { width:40px; }
.vectra-spinner.lg { width:88px; }
.vectra-spinner svg { width:100%; height:auto; display:block; }
.vectra-spinner .facet { transform-box:fill-box; transform-origin:center; }
.vectra-spinner .facet.green { animation:vectra-casc 1.4s ease-in-out infinite; }
.vectra-spinner .facet.dark  { animation:vectra-casc 1.4s ease-in-out .18s infinite; }
.vectra-spinner .facet.teal  { animation:vectra-casc 1.4s ease-in-out .36s infinite; }
@keyframes vectra-casc { 0%,80%,100% { opacity:.18; } 40% { opacity:1; } }
/* ## ⚠️⚠️ SOBRE FUNDO DE MARCA, O ESCUDO É BRANCO
   Chamado #28 (15/09/2026): *"além de disputar contraste com a própria cor do
   botão, então não serve pra nada"*. As três facetas são verde/grafite/ciano da
   marca, e o `btn-primary` tem como FUNDO o gradiente verde→ciano: a faceta
   verde ficava sobre verde e a ciano sobre ciano. O escudo estava lá e não se
   via — pior que não ter, porque ocupa espaço e não informa.
   Aqui as três viram BRANCO e a cascata passa a se ler por BRILHO em vez de
   matiz: o pulso continua sendo o mesmo `vectra-casc`, sem animação nova.
   ⚠️ `fill` no `path`, não no `<g>`: o SVG declara a cor em cada `path`, e
   pintar o grupo não vence o atributo do filho.
   ⚠️⚠️ E só o `fill` — **opacidade fixa aqui não faria NADA**: quem governa
   `opacity` das facetas é o `vectra-casc`, e animação ganha de declaração
   normal. Escrever `.on-brand .facet.green { opacity:.95 }` seria uma linha
   decorativa, da mesma família do `animation:none` que perdia pra regra de
   baixo. A cascata de brilho já vem da animação. */
.vectra-spinner.on-brand .facet path { fill: #fff; }
/* ## ⚠️⚠️ REDUZIR MOVIMENTO NÃO É CONGELAR O CARREGADOR
   Chamado #26 (15/09/2026): *"as animações de carregamento… sempre travam, ou
   seja, em vez de ficar em loop, travam num frame bem no começo sempre,
   ridículo"*. A causa mais provável está aqui: esta regra zerava a animação e
   deixava as três facetas em `opacity:1` — um escudo PARADO, que é exatamente a
   cara de defeito. Quem tem "Reduzir movimento" ligado no iPhone via isso em
   TODA tela, sempre.
   `prefers-reduced-motion` pede pra evitar movimento VESTIBULAR — translação,
   escala, parallax, o que embaralha o equilíbrio. Pulso de OPACIDADE não move
   nada; e um carregador que não indica atividade não é acessível, é quebrado.
   Então aqui a animação fica, mais lenta e mais suave. O que é movimento de
   verdade (o `vectra-breathe`, que dá `scale`) continua desligado. */
@media (prefers-reduced-motion: reduce) {
  .vectra-spinner .facet { animation-duration:2.6s; }
  @keyframes vectra-casc { 0%,80%,100% { opacity:.45; } 40% { opacity:1; } }
}
.vectra-loading { display:flex; flex-direction:column; align-items:center; gap:14px;
  color:#66778a; font-size:.9rem; padding:44px 0; letter-spacing:.02em; }

/* "respirando" — estado de processamento de um escudo estático */
.vectra-shield-pulse { animation:vectra-breathe 2.2s ease-in-out infinite; transform-origin:center; }
@keyframes vectra-breathe { 0%,100%{ transform:scale(1); filter:none; }
  50%{ transform:scale(1.06); filter:drop-shadow(0 4px 16px rgba(17,173,180,.5)); } }
/* ⚠⚠ E ESTE FICA DEPOIS DA DEFINIÇÃO, NÃO ANTES. Media query não soma
   especificidade: com o `animation:none` escrito lá em cima, junto com o do
   carregador, a regra de baixo (mesma especificidade, mais tarde) GANHAVA e o
   escudo continuava escalando — desligar sem efeito nenhum, calado. O
   `breathe` dá `scale`, que é movimento de verdade; o pulso de OPACIDADE do
   carregador fica (ver o bloco acima). */
@media (prefers-reduced-motion: reduce) { .vectra-shield-pulse { animation:none; } }

/* loader inline (auto-upgrade via brand_loaders.js) */
.vectra-inline-loading { display:inline-flex; align-items:center; gap:10px; color:#66778a; }
.vectra-inline-loading .vload-label { font-size:.9rem; letter-spacing:.01em; }
td.vectra-inline-loading, th.vectra-inline-loading { justify-content:center; }

/* ── Skeleton shimmer (placeholder de carregamento p/ tabelas/cards) ────── */
.vectra-skeleton { position:relative; overflow:hidden; background:#eef2f7; border-radius:8px; }
.vectra-skeleton::after { content:''; position:absolute; inset:0; transform:translateX(-100%);
  background:linear-gradient(90deg, transparent, rgba(143,203,58,.20) 42%, rgba(17,173,180,.20) 58%, transparent);
  animation:vSkel 1.3s ease-in-out infinite; }
@keyframes vSkel { 100% { transform:translateX(100%); } }
.skeleton-line { height:12px; border-radius:6px; margin:8px 0; background:#eef2f7; }
@media (prefers-reduced-motion: reduce){ .vectra-skeleton::after{ animation:none; } }


.vectra-nav-overlay {
  position: fixed; inset: 0; z-index: 99999;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  background: rgba(15, 23, 32, 0.55); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  opacity: 0; pointer-events: none; transition: opacity .15s ease;
}
.vectra-nav-overlay.on { opacity: 1; pointer-events: all; }
/* ⚠️⚠️ `position:fixed` NÃO TRAVA A ROLAGEM (chamado #26 da J&M, 4ª mensagem):
   o dedo continuava arrastando a página por baixo, e no iPhone o overlay ficava
   pra trás mostrando a tela de baixo. Mesmas travas do `partials/nav_loader.html`
   (que é o carregador do app; este vale no portal e no hub): gesto morto no
   overlay, rolagem presa no <html>, e SEM desfoque no celular — refazer o blur
   da página inteira a cada quadro é o que engasga o escudo pulsando. */
.vectra-nav-overlay { touch-action: none; overscroll-behavior: contain; }
html.vnav-travado, html.vnav-travado body { overflow: hidden !important; overscroll-behavior: none; }
@media (max-width: 680px) {
  .vectra-nav-overlay { -webkit-backdrop-filter: none; backdrop-filter: none; background: rgba(15, 23, 32, .72); }
}
.vectra-nav-overlay .vload-label { color: #fff; font-weight: 600; font-size: .95rem; letter-spacing: .01em; }


/* ══════════ O VISOR DE PDF (static/js/pdf_view.js) ════════════════════════
   ⚠️ Ele existe porque `window.open(pdf)` no app INSTALADO é um beco: não há
   barra de endereço nem botão de voltar, e a única saída é matar o app
   (chamado #24). Aqui o Fechar é um botão de verdade, sempre visível.
   ⚠️ `env(safe-area-inset-*)`: no iPhone em tela cheia a barra de ações cairia
   por baixo do risquinho do home — o botão existiria e não daria pra tocar. */
.pdfv { position: fixed; inset: 0; z-index: 10020; display: flex;
  align-items: center; justify-content: center; padding: 14px;
  background: rgba(6, 14, 22, .62); backdrop-filter: blur(2px); }
.pdfv-caixa { display: flex; flex-direction: column; width: min(920px, 100%);
  height: min(92vh, 100%); background: #fff; border-radius: 16px;
  overflow: hidden; box-shadow: 0 24px 70px rgba(0,0,0,.35); }
.pdfv-topo { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 12px 14px; border-bottom: 1px solid var(--line, #e2e8ed);
  padding-top: calc(12px + env(safe-area-inset-top, 0px)); }
.pdfv-titulo { font-size: .96rem; min-width: 0; overflow-wrap: anywhere; }
.pdfv-x { border: none; background: none; font-size: 26px; line-height: 1;
  cursor: pointer; color: #627385; padding: 0 6px; }
.pdfv-corpo { flex: 1 1 auto; min-height: 0; background: #f1f5f9; position: relative; }
.pdfv-frame { width: 100%; height: 100%; border: none; display: block; }
/* ⚠️ A espera fica POR CIMA do quadro, não ao lado: o iframe já ocupa 100% da
   altura desde que nasce, e um carregador em fluxo normal cairia embaixo dele —
   fora da vista, que é o mesmo branco de antes com um spinner escondido.
   (O `position: relative` que a ancora está na regra do `.pdfv-corpo` acima.) */
.pdfv-espera { position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; background: #f1f5f9; z-index: 1; }
.pdfv-espera .vectra-loading { padding: 0; }
/* ⚠️ As folhas desenhadas do celular (chamado #34 da J&M). A rolagem é do
   navegador (`pan-x pan-y`); a pinça e o toque duplo são DELE, visor — sem
   isto o iPhone ampliaria a página inteira, visor fixo junto, e o Fechar
   sairia da vista. */
.pdfv-folhas { position: absolute; inset: 0; overflow: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain; touch-action: pan-x pan-y; padding: 8px; }
.pdfv-miolo { display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  width: max-content; min-width: 100%; }
.pdfv-folha { display: block; margin: 0 auto; background: #fff;
  box-shadow: 0 1px 4px rgba(15, 23, 42, .18); }
.pdfv-nota { margin: 4px auto 12px; font-size: .82rem; color: var(--muted, #64748b); text-align: center; }
.pdfv-aviso { display: flex; flex-direction: column; gap: 8px; padding: 26px 22px;
  font-size: .92rem; color: var(--text, #0f172a); }
.pdfv-aviso span { color: var(--muted, #64748b); }
.pdfv-acoes { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap;
  padding: 12px 14px; border-top: 1px solid var(--line, #e2e8ed);
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); }
@media (max-width: 680px) {
  /* ⚠️ A BARRA RESERVA O CANTO DO WIDGET DE SUPORTE (chamado #34 da J&M, visto
     no print do ticket #660): o botão dele flutua ACIMA do visor (10040 ×
     10020) e caía em cima do "Abrir / salvar". Esconder não serve — é por ele
     que se pede ajuda justamente quando o papel não abre. Folga = 14px de canto
     + 52 de botão + 14; e só onde o widget existe (`partials/support_widget.html`),
     senão a camada sem widget perderia largura à toa. */
  body:has(.supw) .pdfv-acoes { padding-right: 80px; }
  .pdfv { padding: 0; }
  .pdfv-caixa { height: 100%; border-radius: 0; width: 100%; }
  .pdfv-acoes .btn { flex: 1 1 46%; }
}


/* ⚠️ A palavra do carregador num botão SÓ DE ÍCONE ("Enviando…" num botão de
   36px saía "Envian…"): fica pra leitor de tela, fora da vista. */
.vl-sr { position: absolute !important; width: 1px; height: 1px; padding: 0;
  margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
