/* =============================================================================
   TAKA DESIGN — 6ª DOBRA: PORTFÓLIO / PROJETOS
   -----------------------------------------------------------------------------
   Carregue DEPOIS de passage.css (consome .spectrum-rule--v) e de process.css,
   que é a dobra anterior no fluxo.

   Tudo aqui vem do Design System PRISMA. Nenhum token novo de cor, tipografia,
   raio, sombra, espaçamento ou motion é criado:
     · .spectrum-rule / .spectrum-rule--v  → a costura e a régua do rodapé
     · .badge badge-pill badge-amber       → o cabeçalho numerado do DS
                                             ("0X · Nome da seção"); âmbar é o
                                             próximo da sequência do
                                             --ds-spectrum depois do índigo
                                             (Sobre), do violeta (Serviços) e do
                                             rosa (Processo)
     · .badge badge-pill badge-neutral     → a categoria de cada case
     · .card / .card-neon / .spotlight / .tilt → os cinco cases
     · .aurora aurora-warm                 → a atmosfera, na temperatura do
                                             âmbar do cabeçalho
     · .t-h2 / .t-h3 / .t-body / .t-body-lg / .t-caption / .t-label
     · --ds-void / --ds-surface / --ds-r-* / --ds-ease / --ds-ease-spring

   O RITMO DA DOBRA
   ----------------
   Hero = impacto · Confiança = precisão · Sobre = humano · Serviços = produto ·
   Processo = método · Portfólio = PROVA.

   REVISÃO 2026-09-06 (leque em experimento) — do baralho preso para um leque
   estático, hover-driven
   -------------------------------------------------------------------------
   O baralho preso (coverflow scroll-driven, mecanismo de services.js) funcionava,
   mas exigia GSAP + ScrollTrigger só para EXISTIR: sem JS, a composição caía
   para uma lista empilhada — um estado bem diferente do "ao vivo". Esta revisão
   testa uma composição inspirada num leque de screenshots sobrepostos: TODOS os
   cinco cases visíveis ao mesmo tempo, sem pin, sem scrub, sem scroll-jacking.
   O destaque de cada case agora é HOVER (desktop) ou o próprio scroll horizontal
   (mobile/tablet, ver a seção "Composição — leque (desktop) / carrossel
   (mobile)" abaixo).

   A consequência arquitetural é a mais importante: a composição em si — leque
   OU carrossel — não depende mais de JS/GSAP para existir. CSS puro (nth-child
   para a posição de cada card no leque, :hover/:focus-within para revelar a
   ficha, scroll-snap nativo para o carrossel) entrega o layout final em
   qualquer estado — sem JS, com o CDN fora do ar, ou com movimento reduzido.
   GSAP volta a ser só coreografia de ENTRADA (a costura, o cabeçalho, o próprio
   leque aparecendo ao entrar na tela, o rodapé) — nunca a razão de a
   composição existir. Ver portfolio.js.

   O QUE SAIU desta revisão, e por quê:
     · --pf-travel / min-height do trilho    → sem pin não há curso de scroll
                                               extra a consumir.
     · .portfolio__stage (sticky + overflow) → nada mais gruda no topo nem
                                               precisa conter sangramento 3D
                                               (o leque é 2D: translateX +
                                               rotate + translateY, sem
                                               perspective/rotateY).
     · .portfolio__card-veil (--pf-veil)     → existia para o vizinho no
                                               coverflow não competir com o
                                               texto do card ativo. Nesta
                                               revisão o texto do vizinho não
                                               aparece de jeito nenhum — a
                                               ficha é um overlay escondido por
                                               padrão — então não há mais nada
                                               para velar.
     · indicador de progresso (barra+contador) → descrevia POSIÇÃO no scroll.
                                               Sem scroll a comandar o foco,
                                               "0X / 05" não tem mais referente.

   DOIS ESTADOS DE LAYOUT — trocam por MEDIA QUERY, não por JS
   ----------------------
   leque (≥900px)      Os cinco cases numa fileira sobreposta (leque), cada um
                       com um deslocamento/rotação fixos por posição
                       (nth-child). A ficha de texto (título, tag, descrição,
                       "Ver projeto") fica escondida por padrão sobre o print,
                       e só aparece no card em :hover/:focus-within.
   carrossel (≤899px)  Sem hover para revelar nada, o toque não teria como
                       destacar um case. Os cinco cases viram uma faixa de
                       scroll horizontal nativo (scroll-snap), um card por vez,
                       ficha SEMPRE visível (não há o que revelar).

   Estado inicial das timelines de entrada continua escrito por JS, nunca por
   CSS: só quem confirma que vai animar tem o direito de esconder ou deslocar
   alguma coisa. O layout em si (leque/carrossel) é CSS puro e nunca começa
   escondido.
   ============================================================================= */

/* -----------------------------------------------------------------------------
   Trilho da seção
   -------------------------------------------------------------------------- */
.portfolio {
  /* Vão que a costura atravessa antes do cabeçalho. */
  --pf-lead: clamp(3rem, 7vh, 5.5rem);
  /* Respiro entre o cabeçalho e a composição. */
  --pf-gap: clamp(2rem, 5vh, 4rem);
  --pf-tail: clamp(4rem, 9vh, 6.5rem);

  /* Quanto a costura sobe ACIMA da borda superior da seção.
     Deliberadamente independente de --pr-tail do Processo: copiar o valor da
     outra dobra criaria acoplamento silencioso, em que mexer numa quebraria a
     outra sem aviso. A subida só precisa ser suficiente para que a linha
     claramente anteceda a fronteira. Ver a nota grande em .portfolio__seam. */
  --pf-seam-rise: clamp(2.5rem, 6vh, 4.5rem);

  /* Eixo da costura. Central por padrão; o compacto o move para a esquerda,
     ver o bloco responsivo — no modo linear o Processo também está à esquerda,
     e duas linhas em eixos diferentes na fronteira são o defeito corrigido no
     lote anterior (ver a nota em process.css sobre o fio e o trilho). */
  --pf-axis: 50%;

  /* Largura do card no leque/carrossel. Mesmo valor já medido de
     --svc-card-w em services.css — reaproveitar evita medir do zero um
     problema que o site já resolveu. */
  --pf-card-w: clamp(19rem, 34vw, 38rem);

  position: relative;
  isolation: isolate;
  background: var(--ds-void);
  padding-bottom: var(--pf-tail);
  scroll-margin-top: var(--header-h);

  /* NÃO declarar overflow: clip/hidden aqui.
     -----------------------------------------------------------------------
     Serviços e Processo recortam na própria seção. Se esta fizesse o mesmo, a
     costura seria impossível: o trecho que sobe acima da borda superior seria
     cortado antes de aparecer, e o fio voltaria a NASCER na fronteira — que é
     exatamente o defeito que esta dobra existe para não repetir.

     O recorte desce um nível, para .portfolio__atmosphere. O sangramento
     horizontal do leque em si (translateX + margem negativa) já é contido
     globalmente por `body { overflow-x: clip }`, declarado em app.css — esta
     seção não precisa recortar nada além da atmosfera. */
}

/* --- Profundidade da dobra inteira ------------------------------------------
   Mesma política das dobras anteriores: teto de .05 de alfa, campos largos e
   sem borda perceptível, e cada parada final escrita como `rgba(cor, 0)` e não
   `transparent` — pelo motivo documentado em passage.css, para que nenhum
   navegador passe por um cinza no meio da interpolação.

   Nenhuma cor nova: âmbar e índigo são os mesmos do --ds-spectrum, e o âmbar
   ecoa o badge do cabeçalho desta dobra.

   O campo NÃO é centrado atrás da costura, pelo motivo medido em services.css:
   o Chromium aplica dither anti-banding mais forte perto do centro de
   gradientes radiais grandes, e centrar um campo exatamente atrás de um fio de
   1px semitransparente expõe esse ruído através dele. */
.portfolio::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(120% 58% at 50% 26%,
      rgba(251, 191, 36, .034), rgba(251, 191, 36, 0) 68%),
    radial-gradient(100% 50% at 16% 64%,
      rgba(99, 102, 241, .042), rgba(99, 102, 241, 0) 70%),
    radial-gradient(80% 42% at 88% 78%,
      rgba(168, 85, 247, .038), rgba(168, 85, 247, 0) 70%);
}

/* --- Atmosfera --------------------------------------------------------------
   O invólucro que assume o recorte que a seção deixou de fazer. É ele, e não
   .portfolio, que contém o blur de 90px da .aurora. */
.portfolio__atmosphere {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: clip;
  pointer-events: none;
}

/* BUG MEDIDO — recorte reto de fundo na fronteira Processo → Portfólio.
   Capturado em 2560×1440: a .aurora do rodapé do Processo (aurora-cool) é
   clipada por overflow:clip de .process no exato ponto em que esta .aurora
   (aurora-warm) nasce, clipada por .portfolio__atmosphere. São dois blobs
   INDEPENDENTES, animados (aurora-shift, sem sincronia entre si) e de
   paletas diferentes (frio/quente) — o valor de cada um bem na linha do
   corte nunca combina com o do outro, em nenhum instante da animação.

   Reconciliar as duas cores não escala (são paletas diferentes de
   propósito, cada uma ecoando o âmbar/ciano do próprio badge da dobra) — a
   correção é fazer as DUAS sumirem antes de chegar na linha, deixando um
   respiro de --ds-void dos dois lados. É isso que garante "mesma cor de
   fundo" no corte em si, sem apagar a profundidade — que continua cheia
   um pouco mais para dentro de cada dobra. Medido: o clip acontece por
   volta de 41% da altura PRÓPRIA deste blob (topo cortado); a máscara
   zera um pouco antes disso, de propósito (ver process.css para o espelho
   do outro lado). */
.portfolio__aurora {
  inset: -18% -10% auto -10%;
  height: 460px;
  opacity: .34;
  -webkit-mask-image: linear-gradient(180deg, transparent 45%, #000 72%, #000 100%);
          mask-image: linear-gradient(180deg, transparent 45%, #000 72%, #000 100%);
}

/* -----------------------------------------------------------------------------
   A costura — o fio que CONTINUA a partir do Processo
   -----------------------------------------------------------------------------
   O SINTOMA QUE ESTA GEOMETRIA RESOLVE
   Um fio que nasce exatamente na borda superior da seção lê como QUEBRA, não
   como continuidade: a linha simplesmente aparece quando a dobra começa. É o
   mesmo defeito da ponte Serviços → Processo corrigido no lote anterior, onde
   uma linha terminava sem destino geométrico.

   A CORREÇÃO
   O elemento sobe --pf-seam-rise acima da borda superior e cresce a mesma
   medida, de modo que a linha já esteja descendo quando o Portfólio começa. O
   trecho que sobe cai sobre a cauda VAZIA do Processo (--pr-tail), sem cobrir
   conteúdo nenhum: .portfolio é irmão posterior de .process, com position:
   relative e isolation: isolate, então pinta acima dele.

   O `background: var(--ds-void)` da seção continua preso à caixa dela e NÃO
   sobe junto — nenhum fundo do Portfólio invade a área do Processo, e não há
   recorte nem degrau de background em lugar nenhum.

   POR QUE UM ELEMENTO SÓ, E NÃO DOIS
   Um elemento só significa um gradiente só e uma máscara só. Duas peças — ou
   uma peça mais um pseudo-elemento empilhado — produziriam emenda visível,
   degrau de cor e glow interrompido exatamente no ponto em que a linha cruza a
   fronteira entre as duas seções, que é o pior lugar possível para isso.

   A MÁSCARA PADRÃO FICA
   .spectrum-rule--v dissipa as DUAS pontas. Aqui as duas dissipações são
   desejadas: a de cima faz a linha MATERIALIZAR dentro da cauda do Processo,
   em vez de começar com uma aresta; a de baixo a entrega ao cabeçalho.

   NENHUMA VARIÁVEL DO PROCESSO É LIDA
   Nem --pr-tail, nem --pr-node, nem nenhuma outra. O alinhamento no compacto
   usa --app-gutter, que as duas seções já compartilham por serem filhas do
   mesmo .container.
   -------------------------------------------------------------------------- */
.portfolio__seam {
  position: absolute;
  inset-block-start: calc(-1 * var(--pf-seam-rise));
  left: var(--pf-axis);
  z-index: 1;
  width: 1px;
  height: calc(var(--pf-lead) + var(--pf-seam-rise));
  translate: -50% 0;
  transform-origin: 50% 0%;
  pointer-events: none;
}

/* -----------------------------------------------------------------------------
   Cabeçalho editorial
   -------------------------------------------------------------------------- */
.portfolio__head {
  position: relative;
  z-index: 2;
  padding-top: var(--pf-lead);
}

.portfolio__eyebrow { margin-bottom: 1.25rem; }

.portfolio__headline {
  max-width: 18ch;
  text-wrap: balance;
}

/* Cada linha do H2 é uma janela; o <span> de dentro é quem se desloca. Mesmo
   gesto de .split-line do DS, aqui conduzido pelo scroll. */
.portfolio__line {
  display: block;
  overflow: hidden;
}
.portfolio__line > span { display: block; }

.portfolio__lede {
  max-width: 52ch;
  margin-top: 1.5rem;
  color: rgba(255, 255, 255, .58);
  text-wrap: pretty;
}

/* -----------------------------------------------------------------------------
   A composição — leque (desktop) / carrossel (mobile)
   -----------------------------------------------------------------------------
   .portfolio__gallery  o trilho — só respiro vertical (--pf-gap), nada mais.
   .portfolio__cases    os cinco cases, numa <ol>: a perspectiva é
                        apresentação, não estrutura. Em QUALQUER largura os
                        cinco existem inteiros no DOM, com headings coerentes.
   -------------------------------------------------------------------------- */
.portfolio__gallery {
  position: relative;
  z-index: 2;
  margin-top: var(--pf-gap);
}

/* O reset de app.css cobre `ul`, não `ol`. Sem isto os marcadores "1." a "5."
   apareceriam ao lado dos cards. A <ol> é semântica (a ordem de leitura dos
   cases importa), a numeração visível é o .portfolio__index.

   REPETE A GEOMETRIA DE .container EM VEZ DE SER EMBRULHADA POR UM — mesma
   razão de .services__deck: um .container aqui em cima somaria uma caixa a
   mais entre a seção e os cases. */
.portfolio__cases {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--app-container-max);
  padding-inline: var(--app-gutter);
  margin: 0 auto;
  list-style: none;
}

/* --- LEQUE (≥900px) ----------------------------------------------------------
   Uma fileira só (flex), sobreposta por margem negativa uniforme — a margem
   negativa é o que de fato ocupa menos espaço horizontal (transform não
   afeta layout, só a pintura). Cada posição (nth-child) recebe seu próprio
   ângulo/deslocamento vertical/z-index via variáveis; o CENTRO (case 03) é o
   mais alto e o mais reto, os extremos rodam para fora e descem um pouco —
   o mesmo arco que uma mão de cartas faz ao abrir em leque. O número de
   regras nth-child é o preço de não depender de JS para o layout: cresce
   junto com o portfólio, mas em troca a composição inteira funciona sem
   nenhuma linha de JavaScript.

   TENTATIVA (revertida) — uma versão mais fechada, "pilha de arquivos"
   (72% de sobreposição, ângulo só numa direção, z-index 1→5, sombra
   permanente) foi testada e descartada: o usuário preferiu este leque mais
   aberto. Não reintroduzir sem pedido explícito.

   BUG MEDIDO em 1280×800 — canto do card vizinho "furando" por cima da
   fileira. --pf-y era um px FIXO (26/10), mas a rotação empurra o canto
   mais alto do card por uma distância proporcional à LARGURA dele
   (--pf-card-w), que muda com o viewport (clamp de 19rem a 38rem). Um
   deslocamento fixo só absorve essa subida numa faixa estreita de largura;
   fora dela (e 1280px cai bem no meio da faixa fluida, 34vw), a rotação
   vence o deslocamento e o canto do card aparece por cima do vizinho mais
   reto, como um chip flutuante sem conexão com o resto do card.

   A correção é medir --pf-y em função de --pf-card-w (calc), não em px
   fixo, com folga: os fatores abaixo (.11 e .065) cobrem com margem a
   subida real (~.083×largura para 11°, ~.0485×largura para 6°, medido
   trigonometricamente a partir da altura do card) em toda a faixa do
   clamp — não só na largura em que o bug foi flagrado.

   HOVER/POINTER, NÃO SÓ LARGURA (auditoria 2026-09-08) — tablets touch em
   paisagem batem 900px+ de largura CSS sem ter hover preciso; sem esta
   condição extra eles recebiam o leque preso ao :hover, sem nenhum jeito
   de revelar a ficha antes de abrir o link. `(hover: hover) and
   (pointer: fine)` restringe o leque a quem realmente tem cursor —
   mouse/trackpad. Ver o bloco `@media (max-width: 899px), (hover: none),
   (pointer: coarse)` logo abaixo: as duas condições são complementares
   (nunca as duas batem, nunca nenhuma bate) para qualquer largura ≥900px;
   abaixo de 900px o carrossel sempre vence, como já era. */
@media (min-width: 900px) and (hover: hover) and (pointer: fine) {
  .portfolio__cases {
    display: flex;
    justify-content: center;
    align-items: flex-start;
  }

  .portfolio__case {
    flex: none;
    width: var(--pf-card-w);
  }
  .portfolio__case + .portfolio__case {
    margin-inline-start: calc(var(--pf-card-w) * -0.42);
  }

  .portfolio__case:nth-child(1) { --pf-rot: -11deg; --pf-y: calc(var(--pf-card-w) * 0.11);  --pf-z: 1; }
  .portfolio__case:nth-child(2) { --pf-rot: -6deg;  --pf-y: calc(var(--pf-card-w) * 0.065); --pf-z: 2; }
  .portfolio__case:nth-child(3) { --pf-rot: 0deg;   --pf-y: 0px;                            --pf-z: 3; }
  .portfolio__case:nth-child(4) { --pf-rot: 6deg;   --pf-y: calc(var(--pf-card-w) * 0.065); --pf-z: 2; }
  .portfolio__case:nth-child(5) { --pf-rot: 11deg;  --pf-y: calc(var(--pf-card-w) * 0.11);  --pf-z: 1; }
}

/* --- CARROSSEL (≤899px) -------------------------------------------------------
   Sem hover no toque, um leque sobreposto não teria como revelar nada — o
   case de baixo ficaria permanentemente inacessível. scroll-snap nativo em
   vez disso: nenhum JS, nenhum GSAP, o próprio navegador cuida do arraste,
   da inércia e do snap. A ficha fica SEMPRE visível aqui (ver
   .portfolio__meta mais abaixo): não existe hover para escondê-la atrás.

   SEM touch-action AQUI DE PROPÓSITO — BUG MEDIDO E REVERTIDO. Uma
   tentativa anterior fixou `touch-action: pan-x` pensando em garantir o
   arraste horizontal, mas isso DESLIGA pan-y (rolagem vertical) para
   qualquer toque que comece sobre o carrossel: o usuário reportou que
   rolar a página pra baixo empurrava os cards pro lado. touch-action:
   auto (o padrão, sem declarar nada) já resolve os dois eixos
   corretamente — é o mesmo mecanismo que qualquer carrossel horizontal
   nativo da web usa: o navegador decide pelo ângulo inicial do gesto se é
   a página que rola ou o carrossel.

   `, (hover: none), (pointer: coarse)` (lista OR, não só max-width) —
   mesma correção da auditoria 2026-09-08 documentada no bloco do leque:
   um tablet touch em paisagem (largura CSS ≥900px, sem hover preciso)
   também precisa cair aqui, não no leque. */
@media (max-width: 899px), (hover: none), (pointer: coarse) {
  .portfolio__cases {
    display: flex;
    gap: 1rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--app-gutter);
    -webkit-overflow-scrolling: touch;
    /* Nenhuma barra de rolagem própria: o gesto de arrastar já comunica que
       há mais conteúdo, e uma barra correndo sob o leque competiria com o
       rodapé desta dobra. */
    scrollbar-width: none;
  }
  .portfolio__cases::-webkit-scrollbar { display: none; }

  .portfolio__case {
    flex: none;
    width: min(84vw, var(--pf-card-w));
    scroll-snap-align: center;
  }
}

/* -----------------------------------------------------------------------------
   O card
   -----------------------------------------------------------------------------
   SUPERFÍCIE OPACA — o card do leque se sobrepõe ao vizinho (desktop) ou
   fica lado a lado no carrossel (mobile); em ambos os casos uma superfície
   translúcida deixaria o conteúdo de trás/do lado atravessar. Mesma
   composição de .services__card: o gradiente de .glass-panel sobre
   --ds-surface. */
.portfolio__card {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--ds-r-lg);

  /* BUG MEDIDO — print aparece PRETO SÓLIDO no leque (≥900px), mesmo com a
     imagem 100% carregada (complete:true, naturalWidth/Height corretos).
     Reproduzido de forma determinística até no card CENTRAL, sem rotação
     (--pf-rot:0deg) — não é o bug antigo do coverflow (filter + transform 3D
     via GSAP), que exigia perspective/rotateY; aqui não há nenhum dos dois.
     Forçar a promoção da camada (transform: translateZ(0)) no próprio
     elemento consertava instantaneamente — sinal de uma rasterização "fria"
     que o Chromium às vezes não repinta sozinho quando vários cards
     sobrepostos entram no layout de uma vez. will-change permanente (e não
     só durante hover, como o resto do site faz) é a correção: são só 5
     elementos, sempre a UM `transform` de distância de animar (a mesma
     propriedade já muda no hover), então o custo é desprezível perto do
     defeito que evita. */
  will-change: transform;

  background-color: var(--ds-surface);
  background-image: linear-gradient(165deg,
      rgba(255, 255, 255, .075) 0%,
      rgba(255, 255, 255, .03) 45%,
      rgba(255, 255, 255, .012) 100%);
  border-color: rgba(255, 255, 255, .08);
  border-top-color: rgba(255, 255, 255, .16);
  border-left-color: rgba(255, 255, 255, .12);

  cursor: pointer;
}

/* --- Leque: posição, rotação e o destaque no hover ---------------------------
   As variáveis (--pf-rot/--pf-y/--pf-z) são escritas por nth-child logo
   acima; :hover e :focus-within SOBRESCREVEM as mesmas variáveis, e é só
   por isso que a transição em `transform` anima de um estado pro outro —
   o navegador interpola a matriz resultante, não precisa que a variável em
   si seja animável.

   :focus-within (não :focus-visible) porque o card em si NÃO é mais
   focável — .portfolio__link agora é um <a href="…"> real (ver HTML), e é
   ELE quem recebe o foco. :focus-within no card só observa esse
   descendente; o anel de foco em si vem de graça da regra global
   :where(a, ...):focus-visible do DS, aplicada ao próprio link.

   Mesma condição hover/pointer do bloco do leque acima (auditoria
   2026-09-08) — este bloco SÓ faz sentido para quem tem cursor; em
   touch a ficha já fica sempre visível pelo carrossel. */
@media (min-width: 900px) and (hover: hover) and (pointer: fine) {
  .portfolio__case {
    --pf-rot: 0deg;
    --pf-y: 0px;
    --pf-z: 1;
    position: relative;
    z-index: var(--pf-z);
    transition: z-index 0s;
  }

  .portfolio__card {
    transform: translateY(var(--pf-y)) rotate(var(--pf-rot)) scale(1);
    transform-origin: 50% 100%;
    transition: transform .5s var(--ds-ease-spring), box-shadow .5s var(--ds-ease);
  }

  /* Foco sempre vence o hover, nunca empata com ele — BUG MEDIDO: com os dois
     no mesmo --pf-z (10), o mouse parado sobre um card enquanto o teclado foca
     outro produz empate de z-index, resolvido por ordem do DOM — o card mais
     à direita cobre o card focado, escondendo bem o que Tab deveria revelar.
     :focus-within fica mais alto (11) de propósito: quando as duas condições
     batem no MESMO card (hover e foco coincidem), 11 ainda vence por ordem de
     cascata — nunca um valor menor que o hover sozinho já alcança. */
  .portfolio__case:hover {
    --pf-z: 10;
  }
  .portfolio__case:focus-within {
    --pf-z: 11;
  }
  .portfolio__card:hover,
  .portfolio__card:focus-within {
    transform: translateY(calc(var(--pf-y) - 18px)) rotate(0deg) scale(1.07);
    box-shadow: 0 28px 60px -20px rgba(0, 0, 0, .55);
  }

  /* BUG MEDIDO em 1280×800 — os cases 01 e 05 nascem cortados pela borda da
     tela quando o hover deveria trazê-los ao foco.
     -----------------------------------------------------------------------
     A largura plana da fileira inteira (as 5 larguras de card, descontada a
     sobreposição de 42% entre vizinhas) é card_w + 4 × 0,58 × card_w =
     3,32 × card_w. Enquanto --pf-card-w é governado pelo termo fluido do
     clamp (34vw, ativo de 900px a 1788px de viewport), essa largura plana já
     vale 3,32 × 0,34 = 112,9% da viewport — sempre mais larga que a tela,
     por construção, não só num breakpoint específico. É esse excesso,
     dividido entre as duas pontas, que faz os cases 01/05 nascerem com uma
     fatia real fora da tela: no repouso isso é o efeito de leque pretendido
     (a ponta sangrando para fora não compete por atenção), mas no hover o
     card devia se tornar o protagonista legível — e sem correção ele SOME
     exatamente a mesma fatia, cortando título/descrição/"Ver projeto".

     Medido via getBoundingClientRect em viewport real de 1280px (card_w =
     34vw = 435px): o card 01 em hover (derrotacionado, scale 1.07) ficava
     de x = -105px a 360px — 105px do lado esquerdo (o texto inteiro da
     ficha) fora da tela. O card 05 espelhava, ~90px cortados à direita.
     O mesmo raciocínio vale para qualquer viewport abaixo de ~2061px
     lógicos: só acima disso a fileira sobra dentro da tela sem ajuda.

     A CORREÇÃO
     --pf-edge-guard mede exatamente quanto falta para a borda externa do
     card, já com scale 1.07 e rotação zerada (o estado do hover), encostar
     na viewport — e empurra só o suficiente para não estourar, com 2rem de
     folga. A conta (1,695 × --pf-card-w − 50vw): 1,16 é a metade da
     distância entre o centro do card 01/05 e o centro da fileira
     (= (3,32 − 1) ÷ 2, a mesma progressão geométrica da nota de --pf-y
     acima); 0,535 é a metade da largura do card já escalada (1,07 ÷ 2);
     50vw é o centro da viewport, que aqui coincide com o centro da fileira
     porque .portfolio__cases nasce centralizada por justify-content. Os
     dois fatores (1,16 e 0,535) dependem da sobreposição de -42% e do
     scale 1.07 usados acima — se qualquer um dos dois mudar, refazer esta
     conta. `max(0px, …)` é o que mantém os cases 02/03/04 (que já cabem
     sozinhos) e qualquer viewport ≳2061px intocados: o guard cai a zero
     e translateX(0) não move nada.

     Verificado por medição direta (mesmo getBoundingClientRect, mesma
     transform do hover) em 900/1024/1280/1366/1440/1536/1728/1920/2200/
     2560px: as duas pontas ficam inteiras na tela em toda a faixa, com
     folga ≥24px onde o guard age e guard = 0 exatamente onde ele não faz
     falta. Só os cases 01 e 05 recebem o translateX extra — o resto do
     leque (posição de repouso incluída) fica como estava. */
  .portfolio__cases {
    --pf-edge-guard: max(0px, calc(1.695 * var(--pf-card-w) - 50vw + 2rem));
  }
  .portfolio__case:nth-child(1) .portfolio__card:hover,
  .portfolio__case:nth-child(1) .portfolio__card:focus-within {
    transform: translateX(var(--pf-edge-guard)) translateY(calc(var(--pf-y) - 18px)) rotate(0deg) scale(1.07);
  }
  .portfolio__case:nth-child(5) .portfolio__card:hover,
  .portfolio__case:nth-child(5) .portfolio__card:focus-within {
    transform: translateX(calc(-1 * var(--pf-edge-guard))) translateY(calc(var(--pf-y) - 18px)) rotate(0deg) scale(1.07);
  }
}

/* --- Chapa/moldura de navegador ----------------------------------------------
   Diz "isto é um site no ar" sem virar mockup skeumórfico: uma faixa de vidro
   com três pontos e o rótulo do case. Nenhuma imagem de moldura. Sempre
   visível — não faz parte da ficha que se esconde. */
.portfolio__frame {
  position: relative;
  border-radius: var(--ds-r-lg) var(--ds-r-lg) 0 0;
  overflow: hidden;
}

.portfolio__chrome {
  display: flex;
  align-items: center;
  gap: .5rem;
  height: 2rem;
  padding-inline: .875rem;
  background: rgba(255, 255, 255, .045);
  border-bottom: 1px solid rgba(255, 255, 255, .07);
}

.portfolio__dots {
  display: inline-flex;
  gap: .3125rem;
  flex: none;
}
.portfolio__dots > i {
  width: .4375rem;
  height: .4375rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, .18);
}

.portfolio__url {
  color: rgba(255, 255, 255, .34);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* --- O print -----------------------------------------------------------------
   Caixa reservada em 16:10 pelo aspect-ratio: zero layout shift quando a
   imagem real entrar. O enquadramento é feito por object-fit/object-position,
   então a imagem não é deformada em nenhum breakpoint. */
.portfolio__shot {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--ds-surface);
}

.portfolio__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0;
}

/* --- Ficha do case -------------------------------------------------------------
   No leque (≥900px COM hover preciso) é um overlay escondido por padrão,
   revelado no hover/foco do próprio card (ver o bloco de hover acima). No
   carrossel (≤899px OU sem hover/pointer fino — auditoria 2026-09-08) ela
   fica no fluxo normal, sempre visível — não existe hover pra escondê-la
   atrás de nada. */
.portfolio__meta {
  display: grid;
  gap: .75rem;
  padding: clamp(1.25rem, 2.5vw, 1.875rem);
}

@media (min-width: 900px) and (hover: hover) and (pointer: fine) {
  .portfolio__meta {
    position: absolute;
    inset: 0;
    z-index: 2;
    align-content: end;
    background: linear-gradient(to top,
      rgba(6, 6, 12, .94) 0%, rgba(6, 6, 12, .8) 42%, rgba(6, 6, 12, 0) 100%);
    opacity: 0;
    transform: translateY(10px);
    pointer-events: none;
    transition: opacity .35s var(--ds-ease), transform .35s var(--ds-ease);
  }
  .portfolio__card:hover .portfolio__meta,
  .portfolio__card:focus-within .portfolio__meta {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
  }
}

.portfolio__index {
  /* .3 dava ~2,5:1 contra #020205 (auditoria 2026-09-08); .49 cruza 4,5:1. */
  color: rgba(255, 255, 255, .49);
  letter-spacing: .22em;
}

.portfolio__title { color: #fff; }

.portfolio__tags {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
}
/* .42 dava ~3,95:1 contra #020205 (auditoria 2026-09-08); .6 cruza 4,5:1. */
.portfolio__segment { color: rgba(255, 255, 255, .6); }

.portfolio__desc {
  max-width: 46ch;
  color: rgba(255, 255, 255, .6);
  text-wrap: pretty;
}

/* --- "Ver projeto" -----------------------------------------------------------
   LINK REAL, E O CARD INTEIRO É A ÁREA DE CLIQUE — não só este texto/ícone.
   ::after estica a área clicável do link até cobrir o card inteiro (o
   "stretched link" pattern): um <a> só, semântico, focável uma vez só, mas
   com hit-area do tamanho do card. É por isso que o card não precisa mais
   de tabindex próprio (ver a nota grande no bloco de hover acima) — o real
   alvo de foco/clique é este link, não o <article>.

   O LINK EM SI fica sem position — de propósito. ::after usa inset:0 contra
   o ANCESTRAL POSICIONADO MAIS PRÓXIMO (.portfolio__meta no leque,
   .portfolio__card no carrossel; os dois cobrem o card inteiro); se o
   próprio link ganhasse position:relative, ele viraria essa referência e o
   ::after encolheria pro tamanho do texto "Ver projeto", não do card. */
.portfolio__link {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  margin-top: .25rem;
  /* .34 dava ~2,9:1 contra #020205 (auditoria 2026-09-08); .52 cruza 4,5:1,
     ainda claramente abaixo do #fff do hover/focus. */
  color: rgba(255, 255, 255, .52);
  text-decoration: none;
  cursor: pointer;
  transition: color .35s var(--ds-ease), gap .35s var(--ds-ease-spring);
}
.portfolio__link::after {
  content: '';
  position: absolute;
  inset: 0;
}
.portfolio__link svg {
  width: .875rem;
  height: .875rem;
  flex: none;
}

.portfolio__link:hover,
.portfolio__link:focus-visible {
  color: #fff;
  gap: .75rem;
}

/* -----------------------------------------------------------------------------
   Rodapé — a nota de crescimento
   -----------------------------------------------------------------------------
   Tratamento SECUNDÁRIO por definição: t-label, opacidade baixa, junto de uma
   régua espectral. Nunca um banner.
   -------------------------------------------------------------------------- */
.portfolio__foot {
  position: relative;
  z-index: 2;
  display: grid;
  justify-items: center;
  gap: 1.25rem;
  margin-top: clamp(3rem, 8vh, 5rem);
}

.portfolio__foot-rule {
  width: min(100%, 34rem);
  margin: 0;
  transform-origin: 50% 50%;
}

.portfolio__note {
  /* .3 dava ~2,5:1 contra #020205 (auditoria 2026-09-08); .49 cruza 4,5:1. */
  color: rgba(255, 255, 255, .49);
  text-align: center;
  text-wrap: balance;
}

/* -----------------------------------------------------------------------------
   Responsivo
   -----------------------------------------------------------------------------
   O leque e o carrossel já têm seus próprios blocos de media query, acima,
   junto da regra que descrevem — mais fácil de auditar do que um bloco
   responsivo único e distante. O que resta aqui é só a costura, que já tinha
   uma regra própria: no modo linear o Processo move sua narrativa do centro
   para a coluna dos nodes; uma costura do Portfólio subindo pelo eixo central
   apareceria ao lado do trilho esquerdo do Processo — duas linhas paralelas em
   eixos diferentes, o defeito capturado em 390 × 844 e corrigido no lote
   anterior. NENHUMA variável --pr-* do Processo é lida. */
@media (max-width: 899px) {
  .portfolio {
    --pf-axis: calc(max(0px, (100% - var(--app-container-max)) / 2) + var(--app-gutter));
  }

  .portfolio__headline { max-width: none; }
}

/* -----------------------------------------------------------------------------
   Sinalização de camada
   -----------------------------------------------------------------------------
   Mesma política de passage.css, about.css, services.css e process.css:
   will-change SÓ enquanto a timeline está de fato em curso (portfolio.js liga e
   desliga .portfolio-active no <html> pelo onToggle do ScrollTrigger).

   Só a costura precisa disto agora: ela é a única coisa nesta dobra ainda
   animada por SCRUB, e ainda CRUZA a fronteira entre duas seções — o caso de
   risco mais exposto do site inteiro (flicker medido e documentado em
   process.css). O leque/carrossel não passa mais por scrub nenhum — só
   :hover/:focus-within (instantâneo) ou a entrada única do baralho
   (play/reverse) — então não precisa de promoção de camada antecipada. */
.portfolio-active .portfolio__seam { will-change: transform; }

/* -----------------------------------------------------------------------------
   Movimento reduzido
   -----------------------------------------------------------------------------
   design-system.css já neutraliza animações e transições globalmente. O
   layout do leque/carrossel é CSS puro e não depende de JS para existir —
   só a ENTRADA (fade/slide da costura, cabeçalho, baralho, rodapé) some;
   os cinco cases continuam completos e no lugar. */
@media (prefers-reduced-motion: reduce) {
  .portfolio__card { transition: none; }
  .portfolio__seam > .spectrum-rule--v,
  .portfolio__foot-rule { animation: none; }
}
