/* ============================================================================
   MUNDOS — a tela partida, a névoa e os componentes flutuantes.
   Fonte de verdade: 01-roteiro-cena-a-cena.md §2/S07, §3.1, §3.2, §3.4, §3.5
                     02-design-system.md §4.6

   Vale para os Atos I (S07–S09), II (S10, a tomada da tela) e IV (S21–S22).

   DOM que este arquivo pressupõe — o parcial de cena precisa respeitar:

     <section class="scene" id="S07">
       <div class="board">
         <section class="world world--med">
           <p class="world__tag">A assistência</p>
           <div class="world__field">
             <article class="float float--med float--lg" data-z="3"
                      style="--x:12%; --y:18%">…</article>
             …
           </div>
         </section>
         <section class="world world--ai"> … idem … </section>
         <svg class="conn-layer"><!-- conectores do Ato IV --></svg>
       </div>

       <div class="world-dim"  aria-hidden="true"></div>   <!-- S09 -->
       <div class="fog-veil"   aria-hidden="true"></div>   <!-- camada A -->
       <canvas class="fog-noise" width="640" height="1080" aria-hidden="true"></canvas>
       <div class="fog-fb"     aria-hidden="true">         <!-- fallback sem WebGL -->
         <i></i><i></i><i></i>
       </div>
       <div class="fog-edge"   aria-hidden="true"></div>   <!-- camada C -->
     </section>

   Ordem de empilhamento dentro do palco (nenhum z-index avulso fora desta
   escala, senão a névoa deixa de comer as bordas dos componentes):
     10 mundos · 15 escurecimento da S09 · 20 véu · 21 ruído · 22 fio vermelho
     30 conectores · 40 texto de cena
   ========================================================================== */

/* --- Os três tons de fundo local dos mundos ------------------------------
   #0A1220, #0B1030 e #06091A são exigidos literalmente pela S07 e não têm
   token em tokens.css: são pontos de parada de gradiente, não superfícies de
   componente. Ficam declarados aqui, no único lugar que os usa, até que o
   design system decida promovê-los (ver resumo de entrega). */
.world--med { --med-bg-deep: #0A1220 }
.world--ai  { --ai-bg-near:  #0B1030; --ai-bg-far: #06091A }

/* ============================================================================
   1 · A TELA PARTIDA (§3.2)
   O corte é feito por clip-path dirigido pela custom property registrada
   --split. Registrada = animável pela Web Animations API; e `contain: paint`
   é o que permite ao navegador rasterizar cada lado em sua própria camada.
   ========================================================================== */
.board {
  position: absolute; inset: 0;
  overflow: hidden;
}

.world {
  position: absolute; inset: 0;
  contain: paint;
  z-index: 10;
}
.world--med { clip-path: inset(0 calc(100% - var(--split)) 0 0) }
.world--ai  { clip-path: inset(0 0 0 var(--split)) }

/* Durante o takeover, o JS marca o palco e o lado que sai deixa de existir
   para o leitor de tela também — não só para o olho. */
.stage[data-takeover="ai"]  .world--med { display: none }
.stage[data-takeover="med"] .world--ai  { display: none }

/* --- Fundos locais — a S07 especifica os dois, ponto a ponto ------------- */
.world--med::before,
.world--ai::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
}
.world--med::before {
  background:
    repeating-linear-gradient(0deg,
      transparent 0 2.4375rem, var(--med-rule) 2.4375rem 2.5rem),
    linear-gradient(180deg, var(--med-bg) 0%, var(--med-bg-deep) 100%);
}
.world--ai::before {
  background:
    linear-gradient(var(--ai-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--ai-grid) 1px, transparent 1px),
    radial-gradient(90% 120% at 100% 0%, var(--ai-bg-near) 0%, var(--ai-bg-far) 100%);
  background-size: 4rem 4rem, 4rem 4rem, cover;
}

/* --- O campo de posicionamento ------------------------------------------
   Os percentuais --x/--y da tabela da S07 são relativos à FAIXA de cada
   mundo (médico x 0–880, IA x 1040–1920), não ao palco inteiro. Sem esta
   caixa intermediária, um componente com --x:82% cairia do outro lado da
   névoa. 55rem = 880px. */
.world__field {
  position: absolute; top: 0; bottom: 0;
  width: 55rem;
}
.world--med .world__field { left:  0 }
.world--ai  .world__field { right: 0 }

/* Quando um mundo toma a tela inteira (Ato II), o campo acompanha. */
.stage[data-takeover="ai"]  .world--ai  .world__field,
.stage[data-takeover="med"] .world--med .world__field { width: 100% }

/* --- Rótulo de cada mundo -----------------------------------------------
   A S07 pede 28px; a escala do design system não tem 28 e o piso de 32px
   só é dispensado em caixa alta (§4.3). Fica em --fs-label, caixa alta,
   com o tracking .14em que a cena pede. */
.world__tag {
  position: absolute; top: var(--margin-v); z-index: 40;
  font: 600 var(--fs-label)/1.2 var(--font-body);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--txt-low); margin: 0;
}
.world--med .world__tag { left:  var(--margin-h); color: var(--med-ink) }
.world--ai  .world__tag { right: var(--margin-h); color: var(--ai-glow) }

/* A linha que nasce no clique da S07 — some por padrão, entra por batida. */
.world__tag-sub {
  display: block; margin-top: var(--sp-2);
  font: 500 var(--fs-body)/var(--lh-body) var(--font-body);
  letter-spacing: 0; text-transform: none; color: var(--txt-mid);
  opacity: 0; transform: translateY(.75rem);
  transition: opacity var(--dur-base) var(--ease-out-expo),
              transform var(--dur-base) var(--ease-out-expo);
}
.scene[data-beat="1"] .world__tag-sub { opacity: 1; transform: none }

/* --- Escurecimento da S09 (a névoa fica POR CIMA dele) ------------------- */
.world-dim {
  position: absolute; inset: 0; z-index: 15; pointer-events: none;
  background: rgba(5, 11, 24, .4);
  opacity: 0; transition: opacity var(--dur-base) var(--ease-in-out);
}
.world-dim[data-on="1"] { opacity: 1 }

/* ============================================================================
   2 · A NÉVOA (§3.1) — três camadas
   ========================================================================== */

/* --- Camada A · degradação óptica ---------------------------------------
   É esta camada que de fato SEPARA: o backdrop-filter borra, dessatura e
   escurece o que está sob ela. As outras duas só dão volume e tensão.  */
.fog-veil {
  position: absolute; inset-block: 0; left: 50%;
  width: var(--fog-w); transform: translateX(-50%);
  z-index: 20; pointer-events: none;
  backdrop-filter: blur(calc(var(--fog-d) * var(--fog-blur, 16px)))
                   saturate(calc(1 - var(--fog-d) * .72))
                   brightness(calc(1 - var(--fog-d) * .28));
  -webkit-backdrop-filter: blur(calc(var(--fog-d) * var(--fog-blur, 16px)))
                   saturate(calc(1 - var(--fog-d) * .72))
                   brightness(calc(1 - var(--fog-d) * .28));
  /* feather horizontal: névoa não tem borda dura */
  mask-image: linear-gradient(90deg,
      transparent 0%, #000 22%, #000 78%, transparent 100%);
  -webkit-mask-image: linear-gradient(90deg,
      transparent 0%, #000 22%, #000 78%, transparent 100%);
  /* rgb(160,173,192) é --fog (#A0ADC0). O roteiro escreve 140,153,173 no
     gradiente; usamos o token, que é o cinza canônico da névoa — a diferença
     é imperceptível e o valor deixa de existir em dois lugares. */
  background: linear-gradient(90deg,
      transparent,
      rgba(160, 173, 192, .10) 30%,
      rgba(160, 173, 192, .16) 50%,
      rgba(160, 173, 192, .10) 70%,
      transparent);
}

/* --- Camada B · volume (canvas WebGL, fbm de 5 oitavas) ------------------ */
.fog-noise {
  position: absolute; inset-block: 0; left: 50%;
  width: var(--fog-w); height: 100%;
  transform: translateX(-50%);
  z-index: 21; pointer-events: none;
  mix-blend-mode: screen; opacity: .9;
}

/* --- Camada C · fio vermelho difuso no eixo ------------------------------
   Subliminar: ninguém "vê" a linha, todo mundo sente a tensão do corte. */
.fog-edge {
  position: absolute; inset-block: 0; left: 50%;
  width: calc(var(--fog-w) + 10rem); transform: translateX(-50%);
  z-index: 22; pointer-events: none;
  background: linear-gradient(90deg,
      transparent 0 46%, rgba(230, 57, 70, .09) 50%, transparent 54% 100%);
  filter: blur(1.5rem);
  opacity: var(--fog-d);
}

/* --- Fallback sem WebGL --------------------------------------------------
   Acionado por `.fog--fallback` no .stage (fog.js, quando falta contexto GL)
   ou por [data-perf="2"] (PerfGuard, §3.7). Perde o orgânico e mantém a
   leitura. É o caminho que precisa ser testado na máquina do auditório. */
.fog-fb {
  position: absolute; inset-block: 0; left: 50%;
  width: var(--fog-w); transform: translateX(-50%);
  z-index: 21; pointer-events: none;
  overflow: hidden; display: none;
  mask-image: linear-gradient(90deg,
      transparent 0%, #000 26%, #000 74%, transparent 100%);
  -webkit-mask-image: linear-gradient(90deg,
      transparent 0%, #000 26%, #000 74%, transparent 100%);
  opacity: var(--fog-d);
}
.fog-fb > i {
  position: absolute; left: -10%; right: -10%; top: 0;
  height: 300%;                       /* a queda é um translate de -33,33% */
  display: block;
  filter: blur(1.75rem);
  animation: fogFall var(--fb-dur, 26s) linear infinite;
  will-change: transform;
}
.fog-fb > i:nth-child(1) {
  --fb-dur: 23s;
  background: repeating-linear-gradient(96deg,
    transparent 0 3.5rem, rgba(160,173,192,.10) 3.5rem 7rem);
}
.fog-fb > i:nth-child(2) {
  --fb-dur: 31s; animation-delay: -9s;
  background: repeating-linear-gradient(88deg,
    transparent 0 5rem, rgba(160,173,192,.08) 5rem 9rem);
}
.fog-fb > i:nth-child(3) {
  --fb-dur: 17s; animation-delay: -4s;
  background: repeating-linear-gradient(92deg,
    transparent 0 2.25rem, rgba(160,173,192,.06) 2.25rem 5.5rem);
}
@keyframes fogFall {
  from { transform: translate3d(0, -33.333%, 0) }
  to   { transform: translate3d(0, 0, 0) }
}

.stage.fog--fallback .fog-fb,
:root[data-perf="2"] .fog-fb   { display: block }
.stage.fog--fallback .fog-noise,
:root[data-perf="2"] .fog-noise { display: none }

/* No nível 2 o véu perde o blur (o token --fog-blur vai a 0 em tokens.css),
   então a dessaturação passa a ser o único trabalho da camada A. */

/* ============================================================================
   3 · COMPONENTES FLUTUANTES (§3.4 e §4.6)
   O HTML declara só a posição (--x, --y), a profundidade (data-z) e o
   tamanho. Amplitude, rotação, duração e defasagem vêm do drift.js.

   Posições canônicas da S07 — copiar daqui para o parcial de cena, sem
   arredondar: elas foram compostas para que nada encoste na névoa antes da
   hora e para que os três planos de profundidade não se sobreponham.

     MUNDO MÉDICO (.world--med .world__field)
     id            componente                       z   --x    --y   tamanho
     f-med-1       protocolo assistencial           3   12%    18%   lg
     f-med-2       POP + carimbo REV. 04            2   46%    10%   md
     f-med-3       checklist bundle 1h              3    8%    58%   md
     f-med-4       controle de sinais vitais        3   52%    52%   md
     f-med-5       prontuário — evolução            2   28%    76%   lg
     f-med-6       formulário de admissão           1   68%    30%   md
     f-med-7       declaração de óbito              1    4%    36%   md
     f-med-8       escala médica do setor           2   62%    72%   md
     f-med-9       passagem de plantão (SBAR)       1   34%    44%   sm

     MUNDO IA (.world--ai .world__field)
     id            componente                       z   --x    --y   tamanho
     f-ai-1        Attention(Q,K,V) — KaTeX         3   18%    14%   xl
     f-ai-2        rede neural (thumb)              2   62%    24%   md
     f-ai-3        agentes (loop de 4 ícones)       3    8%    62%   md
     f-ai-4        chip ChatGPT · OpenAI            2   48%     8%   sm
     f-ai-5        chip Claude · Anthropic          2   66%    10%   sm
     f-ai-6        chip Gemini · Google             1   82%    18%   sm
     f-ai-7        chip Copilot · Microsoft         1   40%    86%   sm
     f-ai-8        chip OpenEvidence                2   74%    60%   sm
     f-ai-9        embeddings / RAG                 3   22%    44%   md
     f-ai-10       cálculo d_model=512 · h=8        3   56%    70%   sm
   ========================================================================== */
.float {
  --z-blur: 0px; --z-op: 1; --z-scale: 1;
  --ax: .5rem; --ay: .875rem; --rot: 0deg; --del: 0s;
  /* --fw = largura do cartão. Vem das classes de tamanho abaixo; 0 aqui para
     que um .float sem classe de tamanho não invalide o min() do `left`. */
  --fw: 0px;
  /* --dur NÃO tem valor aqui de propósito: o padrão é de cada mundo (abaixo),
     e o drift.js sobrescreve por elemento, inline, dentro da faixa de §3.4. */
  position: absolute;
  /* A tabela canônica ancora o CANTO do cartão, não o centro, e não conhece a
     largura dele: um chip a --x:82% da faixa começa em 722px e termina em
     960px — 80px além do palco, e no telão "Gemini" vira "Gemin". O min()
     prende pela borda externa da faixa quem não caberia. Quem já cabia não
     sai do lugar, então a composição da tabela é preservada. --ax entra na
     conta porque o drift ainda vai empurrar o cartão até essa amplitude: sem
     reservá-la, quem fica rente à borda sai dela meio ciclo depois. */
  left: min(var(--x, 50%), calc(100% - var(--fw) - var(--ax))); top: var(--y, 50%);
  transform: translate3d(0, 0, 0) scale(var(--z-scale));
  filter: blur(var(--z-blur));
  opacity: var(--z-op);
  animation: drift var(--dur, 15s) var(--del) cubic-bezier(.37, 0, .63, 1) infinite alternate;
  will-change: transform;
}

/* Profundidade. O blur do fundo é o que faz a tela ter ar em vez de ter
   colagem — e é de graça, porque não anima. */
.float[data-z="1"] { --z-blur: 2.5px; --z-op: .42; --z-scale: .86 }
.float[data-z="2"] { --z-blur: 1px;   --z-op: .75; --z-scale: .94 }
.float[data-z="3"] { --z-blur: 0;     --z-op: 1;   --z-scale: 1   }

@keyframes drift {
  from { transform: translate3d(calc(var(--ax) * -1), calc(var(--ay) * -1), 0)
                    rotate(calc(var(--rot) * -1)) scale(var(--z-scale)) }
  to   { transform: translate3d(var(--ax), var(--ay), 0)
                    rotate(var(--rot)) scale(var(--z-scale)) }
}

/* Amplitude reduzida durante fala densa (S19, S23, S19B, S29, S25 — regra 11
   de §5.3). Desde 10/08 o gatilho existe: a cena declara data-amp="low" e o
   deck.js (syncChrome) copia para o palco a cada troca. Nota honesta: essas
   cenas não têm .float hoje (o tabuleiro só vive em S07–S09, S21–S22), então
   o efeito visível é nulo — o gancho fica real e reconciliado para o dia em
   que uma cena densa tiver elemento em drift. */
.stage[data-amp="low"] .float { animation-name: driftLow }
@keyframes driftLow {
  from { transform: translate3d(calc(var(--ax) * -.3), calc(var(--ay) * -.3), 0)
                    scale(var(--z-scale)) }
  to   { transform: translate3d(calc(var(--ax) * .3), calc(var(--ay) * .3), 0)
                    scale(var(--z-scale)) }
}

/* --- As duas físicas ----------------------------------------------------
   Papel não flutua: papel assenta. Vidro respira. É a mesma animação com
   temperamentos opostos — e é isso que um leigo em IA lê sem ler rótulo. */
.world--med .float {
  --dur: 19s;                                                /* faixa 16–22 s */
  animation-timing-function: cubic-bezier(.45, 0, .55, 1);   /* peso, inércia */
}
.world--ai .float {
  --dur: 10.5s;                                              /* faixa 8–13 s  */
  animation-timing-function: cubic-bezier(.37, 0, .63, 1);
}

/* O glow pulsante do lado IA mora numa pseudo-camada própria: box-shadow
   não está na lista de propriedades animáveis do projeto, opacity está. */
.world--ai .float::after {
  content: ''; position: absolute; inset: -1px; z-index: -1;
  border-radius: inherit; box-shadow: var(--glow-ai);
  animation: aiBreath calc(var(--dur) * .42) var(--del) ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes aiBreath { from { opacity: .45 } to { opacity: 1 } }

/* --- Tamanhos canônicos dos cartões (S07) ------------------------------- */
/* A largura passa por --fw porque o `left` do .float precisa dela para prender
   o cartão na borda da faixa. Declarar nos dois lugares abriria a porta para
   os dois valores divergirem. */
.float--xl { --fw: 46rem; width: var(--fw) }  /* 736px — a fórmula do attention */
.float--lg { --fw: 34rem; width: var(--fw) }  /* 544px — protocolo, prontuário  */
.float--md { --fw: 25rem; width: var(--fw) }  /* 400px — POP, formulário, DO    */
.float--sm { --fw: 17rem; width: var(--fw) }  /* 272px — SBAR, cálculo, chips   */

/* O chip de marca não tem largura de classe: a largura dele é o nome que
   carrega, de "Gemini · Google" a "Copilot · Microsoft". Não recebe `width` —
   continua intrínseco —, só o teto que o min() do `left` precisa conhecer.
   Literal local: 22rem = 352px, o mais largo medido (338px) arredondado para
   cima. Vem depois de .float--sm de propósito: os chips das S21/S22 herdam
   aquela classe, e 272px não bastariam para segurar o chip do Claude. */
.float.brand-chip { --fw: 22rem }

/* --- Chips minimizados (S13, fase 3: a fileira do rodapé) ---------------- */
/* `position: relative` e o par left/top em `auto` são o que faz a fileira
   existir. Sem eles o chip continua obedecendo ao posicionamento absoluto
   de .float — left do min(), top de --y — e os nove pousam empilhados no
   mesmo ponto, um por cima do outro e por cima da referência do rodapé. */
.float[data-min="1"] {
  position: relative; left: auto; top: auto;
  height: 4rem; width: auto; padding: 0 var(--sp-3);
  display: inline-flex; align-items: center; gap: var(--sp-2);
  opacity: .5; animation: none; filter: none;
  font: 600 var(--fs-label)/1 var(--font-body);
  letter-spacing: var(--tr-label); text-transform: uppercase;
  overflow: hidden; white-space: nowrap;
}
/* Minimizado, o chip carrega só o nome: a nota técnica do cartão de cálculo
   ("d_model · h · d_k") triplicaria a largura dele na fileira. */
.float[data-min="1"] .t-ref { display: none }

/* ============================================================================
   4 · O ENTRELAÇAMENTO DO ATO IV (§3.5) — layout `interleaved`
   As posições de destino são declarativas; o FLIP só interpola até elas.
   ========================================================================== */

/* Sem isto, um componente do mundo médico não consegue ir para a metade
   direita: ele está dentro de uma .world recortada por clip-path. Com
   display:contents os 19 componentes viram filhos diretos da grade. */
.stage[data-layout="interleaved"] .world { display: contents }

/* A altura da célula do par. Literal local, sem token: é a soma do que a
   célula precisa segurar — ícone (48) + rótulo de até duas linhas (58) +
   respiro + a metade de cima da pílula soldada na borda inferior. Com as
   trilhas em `1fr` a célula media 416px e ficava quase toda vazia: moldura
   demais para um ícone e uma palavra. */
.stage[data-layout="interleaved"] { --par-h: 13.5rem }

.stage[data-layout="interleaved"] .board {
  display: grid;
  /* O topo tem de livrar o "Seis encontros." (72 + 96 de altura); o bloco dos
     seis pares fica centrado no que sobra. */
  padding: calc(var(--margin-v) + var(--sp-7)) var(--margin-h);
  align-content: center;
  /* 96px entre as linhas: é onde mora a pílula do par de cima. */
  gap: var(--sp-7) var(--sp-4);
  /* três duplas por linha; as colunas estreitas são o respiro ENTRE duplas,
     o que faz o par ler como par mesmo antes do conector ser desenhado */
  grid-template-columns:
    1fr 1fr var(--sp-6) 1fr 1fr var(--sp-6) 1fr 1fr;
  grid-template-rows: repeat(2, auto);
  grid-template-areas:
    "p1-a p1-b .    p2-a p2-b .    p3-a p3-b"
    "p4-a p4-b .    p5-a p5-b .    p6-a p6-b";
}
.stage[data-layout="interleaved"] .world__field { position: static; width: auto; display: contents }
.stage[data-layout="interleaved"] .float {
  position: relative; left: auto; top: auto; width: auto;
  --z-blur: 0; --z-op: 1; --z-scale: 1;
  /* filter e will-change criam contexto de empilhamento, e aqui não servem a
     nada: a profundidade da S07 está desligada neste layout. Enquanto
     existiam, prendiam o z-index do .pair-label dentro do próprio cartão — e
     o cartão do lado da máquina, irmão posterior no DOM, imprimia por cima
     dele. No telão isso era "1 Reconhecime" e o resto da frase desaparecendo
     por baixo da caixa vizinha, nos seis pares. */
  filter: none; will-change: auto;
  /* Conteúdo ancorado no TOPO da célula, e não centrado. O conector do par
     sai da meia-altura do cartão: com o conteúdo centrado, o traço e a
     partícula passavam exatamente por cima do rótulo — foi o que apagou o
     "Citação" do par 3, cujo chip é inline-flex e centrava por conta própria. */
  display: grid; align-content: start; justify-items: start;
  gap: var(--sp-2); padding: var(--sp-3);
  min-height: var(--par-h);
}
/* A marca Q·K·V do cartão de fenótipo é o único texto em --fs-mono aqui, e
   nesta largura de célula ela quebrava depois do segundo ponto ("Q · K ·" /
   "V"). No piso de 32px cabe inteira, e é uma marca: não pode quebrar. */
.stage[data-layout="interleaved"] .float .t-mono {
  font-size: var(--fs-body); white-space: nowrap;
}
/* O rótulo do par nasce dentro do cartão do lado da assistência e atravessa a
   fresta até o cartão do lado da máquina. O cartão tem transform, então é um
   contexto de empilhamento: o z-index do rótulo não sai de dentro dele — ele
   vale o do cartão. Por isso o cartão inteiro sobe, e não o rótulo.
   31, e não 1: em 1 o cartão ficava ABAIXO da camada de conectores (30), e a
   partícula vermelha de cada par imprimia sobre o texto da pílula. Em 36
   passaria à frente da .infra-row (35) e comeria os wordmarks do rodapé. */
.stage[data-layout="interleaved"] .world--med .float { z-index: 31 }

/* Flutuação quase parada, pelo mesmo mecanismo do data-amp="low": a amplitude
   por elemento é escrita inline pelo drift.js, então quem a corrige é o
   keyframe, não uma variável. Na S07 cada peça vaga 8 a 22px e é isso que dá
   vida à tela; aqui elas estão em GRADE e aos PARES, e a mesma amplitude vira
   desalinho — os dois cartões de uma dupla sobem em fases diferentes, o
   conector (geometria congelada no instante em que foi desenhado) descola das
   bordas, e a pílula, ancorada no cartão da assistência, escorrega sobre o
   cartão da máquina. Um quarto da amplitude ainda respira e já alinha. */
.stage[data-layout="interleaved"] .float { animation-name: driftPaired }
@keyframes driftPaired {
  from { transform: translate3d(0, calc(var(--ay) * -.25), 0) scale(var(--z-scale)) }
  to   { transform: translate3d(0, calc(var(--ay) *  .25), 0) scale(var(--z-scale)) }
}

/* Quem não formou par desce para a fileira de infraestrutura do rodapé:
   "isto é encanamento, não é o assunto". */
.stage[data-layout="interleaved"] .float[data-pair="none"] {
  grid-row: 3; grid-column: 1 / -1;
  opacity: .35; animation: none;
}

/* --- Rótulo de cada dupla (batida 1 da S22) -----------------------------
   Soldado na BORDA DE BAIXO dos dois cartões, e não no meio deles: a pílula
   é mais larga que a fresta e sempre vai cobrir parte dos dois: a escolha é
   só qual parte. Na meia-altura ela cobria o conteúdo e o conector; na borda
   inferior ela cobre o vazio da célula, e passa a ser a solda visível entre
   a assistência e a máquina — que é exatamente o que a cena afirma. */
.pair-label {
  position: absolute; left: 50%; top: 100%;
  transform: translate(-50%, -50%) scale(.9);
  z-index: 35;
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-full);
  background: var(--bg-900); border: 1px solid var(--accent);
  color: var(--accent); white-space: nowrap;
  font: 500 var(--fs-body)/1.2 var(--font-body);
  opacity: 0; transition: opacity var(--dur-fast), transform var(--dur-fast) var(--ease-overshoot);
}
.scene[data-beat="1"] .pair-label { opacity: 1; transform: translate(-50%, -50%) scale(1) }

/* ============================================================================
   5 · CONECTORES (§3.5)
   O caminho é escrito pelo worlds.js (getTotalLength → --len, d → --p).
   ========================================================================== */
.conn-layer {
  position: absolute; inset: 0; z-index: 30;
  pointer-events: none; overflow: visible;
}
.conn {
  fill: none;
  stroke: var(--accent); stroke-width: 2.5; stroke-opacity: .75;
  stroke-dasharray: var(--len); stroke-dashoffset: var(--len);
  animation: connDraw .8s cubic-bezier(.65, 0, .35, 1) var(--d, 0s) forwards;
}
@keyframes connDraw { to { stroke-dashoffset: 0 } }

.conn-dot {
  fill: var(--accent);
  offset-path: path(var(--p));
  offset-distance: 0%;
  animation: connTravel 2s linear infinite var(--d, 0s);
  filter: drop-shadow(0 0 6px var(--accent));
}
@keyframes connTravel { to { offset-distance: 100% } }

/* ============================================================================
   6 · DEGRADAÇÕES
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .float, .fog-fb > i, .conn-dot,
  .world--ai .float::after { animation: none !important }
  .conn { stroke-dashoffset: 0; animation: none !important }
}
:root[data-motion="reduced"] .fog-fb > i,
:root[data-motion="reduced"] .conn-dot,
:root[data-motion="reduced"] .world--ai .float::after { animation: none !important }

/* Nível 2 do PerfGuard: sem blur de profundidade e sem glow pulsante — o
   custo de composição de 19 camadas borradas é o primeiro a cair. */
:root[data-perf="2"] .float { filter: none }
:root[data-perf="2"] .world--ai .float::after { animation: none; opacity: .7 }

/* --- Mobile (§4.9): o deck vira página de leitura ----------------------- */
@media (max-width: 900px) {
  .board, .world, .world__field {
    position: static; inset: auto; width: auto;
    clip-path: none; contain: none; display: block;
  }
  .world::before { display: none }
  .world--med { background: var(--med-bg) }
  .world--ai  { background: var(--bg-900) }
  .world__tag { position: static; margin-bottom: var(--sp-2) }
  /* A neutralização COMPLETA do float mora AQUI, e não no stage.css, por causa
     da cascata: stage.css carrega antes de worlds.css, e o `position: static`
     que ele declarava no bloco mobile perdia para o `position: absolute` da
     regra base deste arquivo — mesma especificidade, fonte posterior vence.
     Resultado em produção (11/08): os cartões da S07 espalhados por cima das
     cenas vizinhas no celular, com blur e opacidade de profundidade. Dentro
     do mesmo arquivo, este bloco vem depois da regra base e ganha. Os
     !important cobrem os seletores de profundidade [data-z], que têm
     especificidade maior que a classe pura. */
  .float {
    position: static !important; inset: auto !important;
    width: auto !important; margin: 0 0 1rem;
    transform: none !important; filter: none !important; opacity: 1 !important;
    animation: none !important; will-change: auto;
  }
  /* O carimbo REV. 04 é absoluto (base.css) e, com o .float estático, ancorava
     no .stage — os 4 carimbos (S07/S08/S09/S21) empilhavam no topo da PÁGINA,
     por cima da capa, cortados na borda. Em fluxo ele vira o selo do próprio
     cartão. Mesma especificidade da base (classe), arquivo posterior vence. */
  .stamp { position: static; transform: none; margin: 0 0 .75rem }
  .fog-fb, .world-dim, .conn-layer { display: none }
  /* Os seis eixos do convite são o CONTEÚDO da S22, não coreografia: no fluxo
     de leitura a pílula deixa de ser absoluta sobre a fresta do par e vira
     rótulo estático sob o próprio cartão. */
  .pair-label {
    display: inline-block; position: static; opacity: 1;
    transform: none; margin-top: .25rem;
  }
  .stage[data-layout="interleaved"] .board { display: block; padding: 0 }
  /* Na leitura, S08 e S09 são a S07 com camadas por cima: o tabuleiro repetido
     em cada <section> (necessário na projeção — cena inativa é display:none e
     o fog.js exige um canvas por cena) vira ~8.300px de conteúdo duplicado.
     Fica só a cópia da S07; o conteúdo próprio de cada cena (as quatro
     palavras da S08, a promessa da S09) é reexibido em scenes.css §mobile. */
  #S08 .board, #S09 .board { display: none }

  /* 13/08, revisão mobile (item D): os 9 flutuantes do tabuleiro da S12/S13
     viravam ~1.800px de caixas quase vazias por cena — no palco são a
     composição, no fluxo são inventário. Viram chips compactos em linha
     (o desenho da S15). O t-ref do card "Cálculo" (d_model · h · d_k) sai:
     é ornamento de palco. */
  #S12 .grid-fluid, #S13 .grid-fluid {
    display: flex; flex-wrap: wrap; gap: .625rem; margin: 1rem 0;
  }
  #S12 .grid-fluid .float, #S13 .grid-fluid .float {
    margin: 0; padding: .5rem .875rem; width: auto !important;
    min-height: 0; background-image: none;
  }
  #S12 .grid-fluid .float .t-ref, #S13 .grid-fluid .float .t-ref { display: none }

  /* 13/08 (item E): os SVGs-esqueleto dos documentos cenográficos (POP,
     admissão, escala, prontuário, DO) rolavam 2–3 telas sem informação na
     S07 e na S21. Em fluxo cada documento fica só com o cabeçalho. Restrito
     a .float--med: os glifos dos brand-chips (mundo IA) não são tocados. */
  /* !important porque os esqueletos carregam style="display:block" inline
     (medida de palco) — sem ele a regra perde para o estilo do elemento. */
  #S07 .float--med > svg[aria-hidden="true"],
  #S21 .float--med > svg[aria-hidden="true"],
  #S07 .float--med .do-form,
  #S21 .float--med .do-form { display: none !important }

  /* 13/08 (item F): os cards da S22 herdavam respiro de palco — metade da
     caixa era vazio no fluxo. */
  #S22 .float { min-height: 0; padding: var(--sp-3) }
}

@media print {
  .fog-fb, .world-dim { display: none }
  .conn { stroke-dashoffset: 0; animation: none }
  .conn-dot { display: none }
}
