:root {
  /* Largura da janela: 1vw até 1600px; a partir daí fica parada (a página tem no máximo 1600px) */
  --vw-window: min(1vw, 16px);

  /* Margem lateral extra em ecrãs largos: 0 até 1280px de janela, cresce em linha reta até aos
     1600px e fica aí. Aos 1600px a margem lateral total (--side) chega a 224px = 5× a de hoje
     (2,8% de 1600). Os números saem de: extra(1600) = (224 − 0,028 × 1600) / 0,944 = 189,83px,
     a crescer 189,83 / 320 = 0,59322 px por px de janela a partir dos 1280. */
  --side-extra: clamp(0px, calc(59.322 * var(--vw-window) - 759.32px), 189.83px);

  /* Unidade de tamanho do site: a de uma janela mais estreita (a largura menos a margem extra).
     Com a coluna mais estreita, a composição encolhe toda por igual — senão o "Portfolio" e os
     textos, medidos pela janela, ficavam grandes demais para ela. Até 1280px é igual a 1vw. */
  --vw: calc(var(--vw-window) - var(--side-extra) / 50);

  --bg: #ffffff;
  --card: #f0f0f0;
  --ink: #1a1a1a;
  --ink-scramble: rgba(26, 26, 26, 0.45);
  --placeholder: #dcdcdc;

  --page-pad: clamp(14px, calc(2.8 * var(--vw)), 56px);
  /* Margem lateral da página (o --page-pad continua a ser o espaço entre blocos) */
  --side: calc(var(--page-pad) + var(--side-extra));
  --card-pad: clamp(22px, calc(2.2 * var(--vw)), 44px);
  --radius: clamp(28px, calc(4 * var(--vw)), 80px);
  /* O título é o nome em duas linhas ("Ricardo" / "Sequeira", 22/09/2026), com a foto das duas
     linhas ao lado. A linha mais larga precisa de ~5,825 × a letra ("Sequeira" 4,06 + foto 1,645 +
     espaço 0,12). Mede-se pela largura interior do cartão (cqw: o .hero__inner é o contentor) e
     o bloco ocupa no máximo **2/3** dela (pedido do utilizador): 2/3 ÷ 5,825 = 11,44cqw. No telemóvel
     ficaria pequeno demais (~20px a 360), por isso lá ocupa a largura toda com folga (ver abaixo). */
  --title-size: clamp(36px, 11.44cqw, 270px);
  /* O mínimo é 23px para o título mais longo ("Identidade Alimentar") caber numa só
     linha no telemóvel mais estreito que o site serve (360px: precisa de 236px e sobram
     7px). Com os 30px de antes quebrava em duas linhas abaixo de ~425px. */
  --project-title: clamp(23px, calc(4.2 * var(--vw)), 84px);

  --dark: #333333;
  --cream: #fffee0;
  /* Amarelo do Brixx — único apontamento de cor do site (só no botão do footer) */
  --brixx: #e9e93b;

  --font: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
}

body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

h1,
p,
figure {
  margin: 0;
}

img {
  display: block;
  max-width: 100%;
}

/* ---------- Layout ---------- */

.page {
  position: relative;
  max-width: 1600px;
  margin-inline: auto;
  padding: clamp(40px, calc(9 * var(--vw)), 180px) var(--side) var(--page-pad);
}

/* ---------- Seletor de língua (EN / PT) ---------- */

/* No espaço acima do header, alinhado à direita com a margem do cartão */
.lang {
  position: absolute;
  top: calc(clamp(40px, calc(9 * var(--vw)), 180px) / 2);
  right: var(--side);
  display: flex;
  align-items: baseline;
  gap: 0.4em;
  font-size: clamp(13px, calc(1.1 * var(--vw)), 22px);
  font-style: italic;
  font-weight: 500;
  letter-spacing: 0.01em;
  transform: translateY(-50%);
}

.lang__current {
  font-weight: 700;
}

.lang__sep {
  opacity: 0.4;
}

.lang__link {
  color: inherit;
  text-decoration: none;
  opacity: 0.55;
  transition: opacity 0.2s ease;
}

.lang__link:hover,
.lang__link:focus-visible {
  opacity: 1;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.hero {
  position: relative;
  isolation: isolate;
  /* Os glifos aleatórios podem ser mais largos que o texto final — não deixar sair do cartão */
  overflow: hidden;
  border-radius: var(--radius);
}

.hero__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: var(--radius);
  overflow: hidden;
}

/* O cinzento fica 2px para dentro, por baixo do vídeo, para não fazer linha nas bordas
   (mesma razão do .project__bg) */
.hero__bg::before {
  content: "";
  position: absolute;
  inset: 2px;
  background: var(--card);
  border-radius: calc(var(--radius) - 2px);
}

/* ---------- Vídeo de fundo (hover) ---------- */

.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1.06);
  transition:
    opacity 0.6s ease,
    transform 1.4s cubic-bezier(0.22, 1, 0.36, 1);
}

.hero.is-video .hero__video {
  opacity: 1;
  transform: scale(1);
}

/* Retângulo preto a 10% em multiply por cima do vídeo; aparece e desaparece com ele */
.hero__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #000000;
  mix-blend-mode: multiply;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s ease;
}

.hero.is-video .hero__bg::after {
  opacity: 0.1;
}

/* O texto passa a branco ao mesmo ritmo do fade do vídeo */
.hero__inner {
  transition: color 0.6s ease;
}

.hero.is-video .hero__inner {
  color: #ffffff;
}

/* Ecrã tátil: manter o dedo no header mostra o vídeo — sem selecionar texto nem abrir o menu do sistema */
@media (hover: none) {
  .hero {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
}

.hero__inner {
  /* Contentor para o tamanho do título (--title-size em cqw = largura interior do cartão) */
  container-type: inline-size;
  padding: var(--card-pad) var(--card-pad) calc(var(--card-pad) * 2.2);
  /* Sistema de distâncias do header (maquete do utilizador, 22/09/2026):
     A = recuo da borda do cartão (topo das tags = esquerda da foto);
     B = tags→nome (vertical) = foto→nome (horizontal); o espaço foto→frase é 2×B. */
  --A: var(--card-pad);
  --B: calc(var(--card-pad) * 0.6);
}

/* No telemóvel o nome ocupa a largura toda (1 ÷ 6,1 = 16,4cqw, ~3% de folga), não 2/3 */
@media (max-width: 700px) {
  :root {
    --title-size: clamp(36px, 16.4cqw, 270px);
  }
}

/* ---------- Roles ---------- */

.hero__roles {
  display: flex;
  /* Sempre numa linha (pedido do utilizador, 22/09/2026): no telemóvel a letra encolhe até caber.
     Os três ocupam 23,6 × a letra em EN e 24,7 em PT (texto medido + 2 espaços de 0,8em); com ~3%
     de folga a letra não passa de 4,11cqw (EN) / 3,94cqw (PT) — cqw do .hero__inner, o cartão. */
  flex-wrap: nowrap;
  gap: 0.4em 0.8em; /* menos espaço entre eles (era 18–52px) */
  /* Sem padding próprio: o recuo do topo é o padding do .hero__inner (= A), igual à esquerda da foto */
  padding-top: 0;
  /* Um pouco maiores (eram 13–22px, 1,1 × vw) desde que ficaram só três (21/09/2026) */
  font-size: min(clamp(15px, calc(1.3 * var(--vw)), 26px), 4.11cqw);
  white-space: nowrap;
  font-style: italic;
  font-weight: 700; /* a negrito desde 22/09/2026 (era 500) */
  letter-spacing: 0.01em;
}

:lang(pt) .hero__roles {
  font-size: min(clamp(15px, calc(1.3 * var(--vw)), 26px), 3.94cqw);
}

/* ---------- Title ---------- */

.hero__title-row {
  display: flex;
  align-items: baseline;
  /* Foto → nome = B (maquete do utilizador, 22/09/2026) */
  gap: var(--B);
  /* Tags → nome = B: ao B soma-se 0,05 × título, o que a maiúscula sobe acima da caixa
     por a linha ser 0,9 — assim o espaço visível fica mesmo B, igual ao horizontal */
  margin-top: calc(var(--B) + 0.05 * var(--title-size));
  font-size: var(--title-size);
}

.hero__photo {
  flex: none;
  /* Das duas linhas do nome (22/09/2026): do topo das maiúsculas de "Ricardo" à linha de base de
     "Sequeira" = uma linha (0,9em, o line-height do título) + a altura das maiúsculas (0,745em).
     Começa onde começam as maiúsculas: 0,16em abaixo do topo da linha com line-height 1, menos
     metade do que a linha encolheu (0,05em). */
  width: 1.645em;
  height: 1.645em;
  margin-top: 0.11em;
  overflow: hidden;
  background: var(--placeholder);
  align-self: flex-start;
}

.hero__photo:has(img) {
  background: none; /* o cinzento é só para quando falta a foto: por baixo dela faria linha */
}

.hero__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1);
  transition: filter 0.6s ease;
}

/* A cores com o vídeo (rato por cima do header ou dedo pousado), ao ritmo do texto a passar a branco */
.hero.is-video .hero__photo img {
  filter: grayscale(0);
}

.hero__title {
  font-size: 1em;
  font-weight: 700;
  /* 0,9 (era 1): "Ricardo" e "Sequeira" mais juntos, a pedido do utilizador (22/09/2026). Ao mudar
     este valor, mudam com ele a foto, o topo da linha, o espaço até à frase e o 2/3 (ver lá). */
  line-height: 0.9;
  letter-spacing: -0.035em;
}

/* ---------- Name + degree ---------- */

.hero__degree {
  margin-top: 0.5em;
  font-size: clamp(13px, calc(1 * var(--vw)), 20px);
  font-style: italic;
  font-weight: 400;
  line-height: 1.4;
}

/* ---------- Skills ---------- */

/* ---------- Impressão digital ---------- */

/* Só existe em ecrã tátil: é lá que o vídeo do header aparece com o dedo pousado.
   Com rato basta passar por cima, e a instrução "press and hold" não se aplicaria. */
.hero__print {
  display: none;
}

@media (hover: none) {
  .hero__print {
    display: block;
    position: absolute;
    right: var(--card-pad);
    bottom: calc(var(--card-pad) * 1.2);
    width: clamp(72px, calc(19 * var(--vw)), 124px);
    transform-origin: 50% 50%;
    height: auto;
    /* Não rouba o toque ao header, que é quem mostra o vídeo */
    pointer-events: none;
  }
}

/* Abaixo disto não cabem as duas coisas: em PT, "Liderança de Equipas" chega onde a
   impressão começa. Sem largura para ambos, fica o texto — o gesto continua a funcionar. */
@media (hover: none) and (max-width: 339px) {
  .hero__print {
    display: none;
  }
}

/* A banda clara atravessa as linhas na diagonal, em ciclo. O passo é exatamente um
   período do gradiente medido no eixo a 45° (95 unidades → 95/√2 em cada eixo),
   por isso o fim do ciclo coincide com o início e não se vê o salto. */
.print__sweep {
  animation: print-sweep 3.2s linear infinite;
}

@keyframes print-sweep {
  to {
    transform: translate(67.18px, -67.18px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .print__sweep {
    animation: none;
  }

  .hero__photo img {
    transition: none;
  }
}

/* Por baixo do nome: a lista de disciplinas (invisível, guarda a altura) e por cima dela, na mesma
   célula, a frase e as áreas. --below-base é a distância que põe o topo das maiúsculas de baixo
   ao mesmo espaço visual da linha de base do nome que o nome tem do "Portfolio". Pelas métricas
   da fonte: 0,095 × título (abaixo da base do "P") + margem do nome + 0,063 × nome (o que o nome
   tem a mais por cima da maiúscula do que por baixo da base); cada bloco tira depois o que tem
   por cima da própria maiúscula. */
.hero__below {
  --below-base: calc(
    0.045 * var(--title-size) + clamp(14px, calc(1.6 * var(--vw)), 32px)
    + 0.063 * clamp(26px, calc(2.3 * var(--vw)), 46px)
  );
  display: grid;
  align-items: start;
}

.hero__below > * {
  grid-area: 1 / 1;
}

/* Fora da grelha, a margem de cima do curso já não se funde com a de baixo da lista: tira-se */
.hero__below + .hero__degree {
  margin-top: 0;
}

.hero__tagline {
  /* Da base da foto (≈ base de "Sequeira") ao topo das maiúsculas da frase = 2 × B (maquete do
     utilizador, 22/09/2026). Ao 2B tira-se 0,045 × título (o quanto a célula .hero__below começa
     abaixo da base) e o que a frase tem por cima da maiúscula (0,229 × a letra dela, linha 1,3,
     + 1,3px medidos) */
  margin-top: calc(2 * var(--B) - 0.045 * var(--title-size) - 0.229 * var(--lede-size) - 1.3px);
  /* "Think. Shape. Build." mede 9,6 × a letra: com ~3% de folga cabe numa linha até 10cqw (cqw do
     .hero__inner, o cartão). O limite só serve de rede — no telemóvel manda o mínimo de 17px */
  --lede-size: min(clamp(17px, calc(2 * var(--vw)), 38px), 10cqw);
}

/* "Think. Shape. Build.": negrito itálico, igual nas duas línguas (22/09/2026) */
.hero__lede {
  font-size: var(--lede-size);
  font-style: italic;
  font-weight: 700;
  line-height: 1.3;
  white-space: nowrap;
}

/* Escondido mas a ocupar o espaço (hoje: a lista de disciplinas, o curso e os papéis antigos),
   para o retângulo do header manter a altura de antes */
.hero .is-reserved {
  visibility: hidden;
}

.hero__skills {
  display: flex;
  flex-direction: column;
  /* Mais pequenas (eram 18–28px, gap 8–22px), a pedido do utilizador (21/09/2026) */
  gap: clamp(6px, calc(0.8 * var(--vw)), 16px);
  /* − 4,3px: o que a lista tem por cima da maiúscula, com a letra fixa nos 14px */
  --skills-top: calc(var(--below-base) - 4.3px);
  margin-top: var(--skills-top);
  /* O que a lista perdeu volta como espaço por baixo, para o retângulo do header manter a
     altura de antes (pedido do utilizador): 10 linhas × (22,5 − 17,5px; a linha é 1,25 × a
     letra, que é sempre 18 → 14px) + 9 × (espaço antigo − espaço novo) + (margem de cima
     antiga − nova). O curso invisível vem a seguir e guarda o espaço dele. */
  margin-bottom: calc(
    54px /* 50 + 4px de arredondamentos, medidos contra a versão anterior: fica a ±3px */
    + 9 * (clamp(8px, calc(1.1 * var(--vw)), 22px) - clamp(6px, calc(0.8 * var(--vw)), 16px))
    + clamp(40px, calc(4 * var(--vw)), 80px) - var(--skills-top)
  );
  font-size: clamp(14px, calc(1.05 * var(--vw)), 21px);
  font-style: italic;
  font-weight: 700;
  letter-spacing: 0.01em;
}

/* ---------- Projetos ---------- */

.work {
  /* A compensação do scroll ao fechar painéis é feita em JS (evita correção a dobrar do browser) */
  overflow-anchor: none;
}

.project {
  --gap: var(--page-pad);
  --plus-size: clamp(30px, calc(2.6 * var(--vw)), 52px);
  position: relative;
  margin-top: var(--page-pad);
  aspect-ratio: 1610 / 1145;
  /* Esconde o painel enquanto está fora, à direita */
  overflow: hidden;
  overflow: clip;
  border-radius: var(--radius);
}

/* Projeto escondido por agora (hidden no <article>): nenhum display do CSS o pode anular */
.project[hidden] {
  display: none;
}

/* Retângulo claro por trás do painel: ocupa a coluna do painel do topo ao fundo do cartão, por isso
   quando o painel preto (de altura dinâmica) não chega ao fundo, continua a haver um bloco alinhado
   com o cartão. Entra e sai com o painel, ao mesmo ritmo. */
.project::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  right: -40%; /* = largura do painel (40% − gap) + gap, como no .project__panel */
  z-index: 0;
  width: calc(40% - var(--gap));
  border-radius: var(--radius);
  background: #b0b0b0;
  transition: right 0.9s cubic-bezier(0.76, 0, 0.24, 1);
}

.project.is-open::after {
  right: 0;
}

/* Cartão cinzento (fica por cima do painel, que se revela por trás quando encolhe) */
.project__card {
  position: relative;
  z-index: 2;
  isolation: isolate;
  width: 100%;
  height: 100%;
  border-radius: var(--radius);
  overflow: hidden;
  transition: width 0.9s cubic-bezier(0.76, 0, 0.24, 1);
}

.project.is-open .project__card {
  width: 60%;
}

.project__bg {
  position: absolute;
  /* 2px para dentro: as imagens e os vídeos ficam sempre maiores que o cinzento. Se fossem do
     mesmo tamanho, o antialiasing das bordas (sobretudo nos cantos redondos) misturava o cinzento
     com a imagem e via-se uma linha clara à volta. Os media não podem ser eles a crescer: o
     <video> e o <model-viewer> não são recortados pelo cartão e ficariam 2px de fora. */
  inset: 2px;
  z-index: -2;
  background: var(--card);
  border-radius: calc(var(--radius) - 2px);
}

.project__media {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
}

/* Slides da galeria: empilhados; só o ativo (e o que está a entrar) fica visível */
.project__slide {
  position: absolute;
  inset: 0;
  overflow: hidden;
  visibility: hidden;
  border-radius: inherit;
}

.project__slide.is-active {
  visibility: visible;
}

.project__media :is(img, video) {
  width: 100%;
  height: 100%;
  object-fit: cover;
  will-change: transform; /* movimento com o rato (js) */
  /* Um <video> (e o <model-viewer>) é pintado em camada própria e NÃO é recortado pelo
     overflow arredondado do cartão: tem de trazer o raio consigo, ou fica com os cantos
     a 90°. O `inherit` sobe a cadeia .project__card → __media → __slide e acompanha
     sozinho a animação dos cantos de baixo ao abrir no telemóvel. */
  border-radius: inherit;
}

/* Preto e branco em repouso, cor com o rato por cima (só em dispositivos com rato) */
@media (hover: hover) {
  .project__media :is(img, video) {
    filter: grayscale(1);
    transition: filter 0.6s ease;
  }

  /* .is-hover é gerida em JS: também se atualiza quando o cartão encolhe/cresce sem o rato mexer */
  .project__card.is-hover .project__media :is(img, video),
  .project__card:has(:focus-visible) .project__media :is(img, video) {
    filter: none;
  }
}

/* Vídeo por cima da imagem de um slide: em repouso vê-se a imagem, com o rato sobre o cartão
   o vídeo aparece e toca (o JS trata do play/pause). Em ecrã tátil fica sempre escondido. */
.slide__video {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.6s ease;
}

/* Está à vista sempre que o seu slide é o ativo — não depende do rato nem do JS.
   Com movimento reduzido fica visível na mesma, parado no primeiro frame, em vez de
   deixar o cartão vazio nos projetos que só têm vídeo. */
.project__slide.is-active .slide__video {
  opacity: 1;
}

/* Com movimento reduzido o JS não toca nada: o vídeo vê-se, parado no primeiro frame. */
@media (prefers-reduced-motion: reduce) {
  .slide__video {
    transition: none;
  }
}

.project__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2em clamp(14px, calc(1.6 * var(--vw)), 32px);
  padding: var(--card-pad);
  /* Espaço para o botão + */
  padding-right: calc(var(--card-pad) * 2 + var(--plus-size));
}

.project__title {
  margin: 0;
  font-size: var(--project-title);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
}

/* Cor do título fixada à mão: o `data-head` no <article> escolhe-a e o setupHeadContrast
   não mexe nesses projetos (ver secção 6.9 do CLAUDE.md). Serve para as capas em que a
   medição automática não acerta, ou onde se quer uma cor independentemente da imagem. */
[data-head='light'] .project__head {
  color: #ffffff;
}

[data-head='light'] .project__head .scr__glyph {
  color: rgba(255, 255, 255, 0.5);
}

[data-head='dark'] .project__head {
  color: var(--ink);
}

[data-head='dark'] .project__head .scr__glyph {
  color: var(--ink-scramble);
}

/* No cartão de telemóvel (4/5) o `cover` corta noutro sítio e a zona por baixo do título
   pode ser bem mais clara: o `data-head-mobile` permite inverter a cor só aí.
   Tem de vir depois das regras acima — mesma especificidade, ganha a última. */
@media (max-width: 700px) {
  [data-head-mobile='dark'] .project__head {
    color: var(--ink);
  }

  [data-head-mobile='dark'] .project__head .scr__glyph {
    color: var(--ink-scramble);
  }

  [data-head-mobile='light'] .project__head {
    color: #ffffff;
  }

  [data-head-mobile='light'] .project__head .scr__glyph {
    color: rgba(255, 255, 255, 0.5);
  }
}

/* Cor decidida pela luminância da imagem por baixo do título (WCAG) */
.project__head.is-light {
  color: #ffffff;
}

.project__head.is-light .scr__glyph {
  color: rgba(255, 255, 255, 0.5);
}

.project__head.is-dark {
  color: var(--ink);
}

.project__head.is-dark .scr__glyph {
  color: var(--ink-scramble);
}

.project__plus {
  position: absolute;
  /* Centrado verticalmente com o título */
  top: calc(var(--card-pad) + (var(--project-title) - var(--plus-size)) / 2);
  right: var(--card-pad);
  display: grid;
  place-items: center;
  width: var(--plus-size);
  height: var(--plus-size);
  padding: 0;
  border: 0;
  background: none;
  /* Branco porque o traço é misturado com `difference` (ver .project__plus svg) */
  color: #ffffff;
  cursor: pointer;
}

.project__plus svg {
  width: 72%;
  height: 72%;
  fill: none;
  stroke: currentColor;
  stroke-width: 3.4;
  /* Mesmo contraste automático do título: traço branco invertido sobre a capa.
     O blend vive no SVG e não no <button>, senão a bola branca e o "+" branco
     misturavam-se dentro do mesmo grupo e o "+" desaparecia. Assim o "+" vê por
     baixo dele a bola (quando está a seguir o cursor, sai preto sobre o branco,
     como antes) ou a própria capa. */
  mix-blend-mode: difference;
  /* Posicionado para pintar depois da .plus__ball, que vem antes no HTML */
  position: relative;
}

/* Cor do "+" fixada à mão, como o `data-head` faz com o título: `data-plus="light"`
   no <article> desliga a mistura e deixa o traço branco. Serve para as capas escuras em
   que o `difference` daria branco quase por todo o lado, mas onde as manchas de cinzento
   médio (128 invertido ≈ 128) fariam o "+" desaparecer. Hoje: Liga Portugal, Luzo e Mascotes. */
[data-plus='light'] .project__plus svg {
  mix-blend-mode: normal;
}

/* Exceção da exceção: com a bola branca por baixo (o "+" a seguir o cursor), um traço
   branco sem mistura ficaria invisível — aí volta o `difference`, que o põe preto. */
[data-plus='light'] .project__card.is-plus-follow .project__plus svg {
  mix-blend-mode: difference;
}

/* Halo escuro por baixo do traço branco fixo: nas Mascotes, no telemóvel, o "+" calha
   sobre o holofote branco da imagem e desaparecia (22/09/2026). Só no "+" branco fixo —
   com o `difference` um filtro criaria contexto próprio e estragaria a mistura, por isso
   sai quando ele segue o cursor. */
[data-plus='light'] .project__card:not(.is-plus-follow) .project__plus svg {
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.55)) drop-shadow(0 2px 6px rgba(0, 0, 0, 0.3));
}

/* O círculo sai do viewBox do ícone, por isso o SVG não pode cortar */
.project__plus svg {
  overflow: visible;
}

/* Círculo do hover: nasce ao centro com ressalto, mesma grossura de traço do "+" */
.project__plus .plus__ring {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(0);
  opacity: 0;
  transition:
    transform 0.25s cubic-bezier(0.55, 0, 1, 0.45),
    opacity 0.2s ease;
}

@media (hover: hover) {
  .project__plus:hover .plus__ring {
    transform: scale(1);
    opacity: 1;
    transition:
      transform 0.55s cubic-bezier(0.34, 1.8, 0.64, 1),
      opacity 0.15s ease;
  }
}

.project__plus:focus-visible .plus__ring {
  transform: scale(1);
  opacity: 1;
}

/* Barra vertical do "+": recolhe no fim da rotação e o botão fica "−" com o painel aberto */
.project__plus .plus__v {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 0.3s cubic-bezier(0.65, 0, 0.35, 1);
}

.project__plus[aria-expanded="true"] .plus__v {
  transform: scaleY(0);
  transition-delay: 0.45s;
}

@media (prefers-reduced-motion: reduce) {
  .project__plus .plus__v,
  .project__plus[aria-expanded="true"] .plus__v,
  .project__plus .plus__ring,
  .project__plus:hover .plus__ring {
    transition: none;
  }
}

.project__plus:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
  border-radius: 50%;
}

/* ---------- Zona central: lupa + bola ---------- */

/* O cartão serve de referência de tamanho (cqmin = lado mais curto do cartão) */
.project__card {
  container-type: size;
}

/* Quadrado invisível ao centro; só com rato. Fica por baixo do título, do + e das setas */
.project__zoom {
  position: absolute;
  top: 50%;
  left: 50%;
  display: none;
  width: 54cqmin;
  height: 54cqmin;
  translate: -50% -50%;
}

@media (hover: hover) {
  .project__zoom {
    display: block;
    cursor: none; /* o cursor dá lugar à lupa */
  }
}

/* Lupa + bola no lugar do cursor (posição --zx/--zy definida em JS) */
.zoom__pointer {
  position: absolute;
  left: var(--zx, 50%);
  top: var(--zy, 50%);
  display: grid;
  place-items: center;
  translate: -50% -50%;
  pointer-events: none;
}

/* No comparador o centro é o círculo de arrastar; no 3D é para rodar o objeto */
.project__card.is-compare .project__zoom,
.project__card.is-model .project__zoom {
  display: none;
}

/* A lupa só aparece com o projeto aberto (fechado, quem vem até ao cursor é o "+") */
.project:not(.is-open) .zoom__pointer {
  display: none;
}

.zoom__ball,
.zoom__icon {
  grid-area: 1 / 1;
  pointer-events: none;
}

.zoom__ball {
  width: max(64px, 13cqmin);
  aspect-ratio: 1;
  border-radius: 50%;
  background: #ffffff;
  scale: 0;
  transition: scale 0.25s cubic-bezier(0.55, 0, 1, 0.45);
}

.zoom__icon {
  position: relative;
  width: max(24px, 4.8cqmin);
  height: auto;
  opacity: 0;
  scale: 0.6;
  transition:
    opacity 0.2s ease,
    scale 0.25s ease;
}

/* Primeiro a lupa, logo a seguir a bola com ressalto por baixo */
.project__zoom:hover .zoom__icon {
  opacity: 1;
  scale: 1;
  transition:
    opacity 0.25s ease,
    scale 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.project__zoom:hover .zoom__ball {
  scale: 1;
  transition: scale 0.6s cubic-bezier(0.34, 1.8, 0.64, 1) 0.08s;
}

@media (prefers-reduced-motion: reduce) {
  .zoom__ball,
  .zoom__icon,
  .project__zoom:hover .zoom__ball,
  .project__zoom:hover .zoom__icon {
    transition: none;
  }
}

/* ---------- Botão + a seguir o cursor (projeto fechado) ---------- */

/* Bola branca por baixo do "+", do mesmo tamanho da bola da lupa.
   Fica atrás por ordem de árvore (vem antes do SVG no HTML, que é `position: relative`),
   e não com `z-index: -1` + `isolation` no botão: o botão isolado impedia o "+" de se
   misturar com a capa do cartão. */
.plus__ball {
  position: absolute;
  top: 50%;
  left: 50%;
  width: max(64px, 13cqmin);
  aspect-ratio: 1;
  border-radius: 50%;
  background: #ffffff;
  translate: -50% -50%;
  scale: 0;
  pointer-events: none;
  transition: scale 0.25s cubic-bezier(0.55, 0, 1, 0.45);
}

.project__card.is-plus-follow .plus__ball {
  scale: 1;
  transition: scale 0.6s cubic-bezier(0.34, 1.8, 0.64, 1) 0.08s;
}

/* Enquanto anda (ida e volta) o botão não apanha o rato: o clique é tratado pela zona central */
.project__card.is-plus-moving .project__plus {
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .plus__ball,
  .project__card.is-plus-follow .plus__ball {
    transition: none;
  }
}

/* ---------- Comparador antes/depois (slide da galeria) ---------- */

/* ---------- Visualizador 3D ---------- */

.project__model .model__viewer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Tapa a <img> que está por baixo assim que o componente arranca */
  background-color: var(--card);
  /* Como o <video>, é composto em camada própria: traz o raio consigo */
  border-radius: inherit;
  /* O poster do model-viewer é a mesma imagem que está por baixo: sem véu por cima */
  --poster-color: transparent;
}

/* Sinal de que o objeto se roda, no canto superior esquerdo do slide 3D (pedido do utilizador,
   23/09/2026). O canto está livre: a este slide só se chega com o projeto aberto, e aí o título
   do cartão já saiu para a esquerda. O SVG traz a bola branca e as setas pretas, por isso lê-se
   sobre o cinzento do cartão e sobre o objeto. */
/* O seletor tem de ser mais específico que o `.project__media :is(img, video)` (largura e altura
   a 100%, `object-fit: cover` e `border-radius: inherit`), senão o ícone enchia o cartão. */
.project__media .model__rotate {
  position: absolute;
  top: var(--card-pad);
  left: var(--card-pad);
  z-index: 1; /* o <model-viewer> é composto em camada própria */
  /* Perto da bola da lupa (max(64px, 13cqmin)): ~57px no computador, 56 no telemóvel.
     `cqmin` mede o lado mais curto do cartão (container-type: size no .project__card). */
  width: max(56px, 8cqmin);
  height: max(56px, 8cqmin);
  border-radius: 0;
  object-fit: contain;
  filter: none; /* fica a cores mesmo com o preto e branco em repouso */
  pointer-events: none; /* não rouba o arrasto que roda o objeto */
  /* Entra como a lupa: aparece com ressalto quando o slide fica ativo */
  opacity: 0;
  scale: 0.6;
  transition:
    opacity 0.2s ease,
    scale 0.25s ease;
}

/* O atraso deixa o slide assentar primeiro (a troca demora 450 ms) */
.project__slide.is-active .model__rotate {
  opacity: 1;
  scale: 1;
  transition:
    opacity 0.25s ease 0.45s,
    scale 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.45s;
}

@media (prefers-reduced-motion: reduce) {
  .project__media .model__rotate,
  .project__slide.is-active .model__rotate {
    transition: none;
  }
}

/* Enquanto o script não carregar, o elemento ainda não existe para o browser:
   não pode ocupar espaço nem tapar a imagem de baixo. */
.project__model model-viewer:not(:defined) {
  display: none;
}

.project__compare {
  --pos: 50%;
  cursor: ew-resize;
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}

.project__compare img {
  position: absolute;
  inset: 0;
  pointer-events: none;
  -webkit-user-drag: none;
}

/* "Depois" por cima, visível só à direita da linha */
.compare__after {
  position: absolute;
  inset: 0;
  clip-path: inset(0 0 0 var(--pos));
}

.compare__handle {
  --knob-size: clamp(36px, calc(3 * var(--vw)), 60px);
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--pos);
  width: 0;
  outline: none;
}

.compare__handle::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -3px;
  width: 6px;
  background: #ffffff;
}

/* Círculo branco no mesmo estilo das setas da galeria */
.compare__knob {
  position: absolute;
  top: 50%;
  left: 0;
  display: grid;
  place-items: center;
  width: var(--knob-size);
  height: var(--knob-size);
  border-radius: 50%;
  background: #ffffff;
  color: var(--ink);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
  transform: translate(-50%, -50%);
  transition: transform 0.45s cubic-bezier(0.34, 1.8, 0.64, 1);
}

.compare__knob svg {
  width: 56%;
  height: 56%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (hover: hover) {
  .project__compare:hover .compare__knob {
    transform: translate(-50%, -50%) scale(1.08);
  }
}

.project__compare.is-dragging .compare__knob {
  transform: translate(-50%, -50%) scale(0.94);
  transition-duration: 0.12s;
}

.compare__handle:focus-visible .compare__knob {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* Etiquetas nos cantos inferiores, alinhadas com as setas da galeria */
.compare__label {
  position: absolute;
  bottom: calc(clamp(14px, calc(1.4 * var(--vw)), 28px) + clamp(36px, calc(3 * var(--vw)), 60px) / 2);
  transform: translateY(50%);
  font-size: clamp(11px, calc(1.05 * var(--vw)), 20px);
  font-style: italic;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--ink);
  pointer-events: none;
}

.compare__label--before {
  left: var(--card-pad);
}

.compare__label--after {
  right: var(--card-pad);
}

@media (prefers-reduced-motion: reduce) {
  .compare__knob {
    transition: none;
  }
}

/* ---------- Galeria: botões anterior/seguinte ---------- */

/* Deixa o scroll vertical ao browser e os gestos horizontais ao swipe da galeria */
.project__card {
  touch-action: pan-y;
}

/* Centrados no fundo do cartão (acompanham o cartão quando encolhe) */
.project__nav {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(14px, calc(1.4 * var(--vw)), 28px);
  display: flex;
  justify-content: center;
  gap: clamp(16px, calc(2 * var(--vw)), 40px);
  pointer-events: none;
}

.project__arrow {
  --arrow-size: clamp(36px, calc(3 * var(--vw)), 60px);
  display: grid;
  place-items: center;
  width: var(--arrow-size);
  height: var(--arrow-size);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #ffffff;
  color: var(--ink);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  cursor: pointer;
  pointer-events: auto;
  transition: transform 0.45s cubic-bezier(0.34, 1.8, 0.64, 1);
}

.project__arrow svg {
  width: 48%;
  height: 48%;
  fill: none;
  stroke: currentColor;
  stroke-width: 4;
  stroke-linejoin: round;
  stroke-linecap: round;
}

@media (hover: hover) {
  .project__arrow:hover {
    transform: scale(1.12);
  }
}

.project__arrow:active {
  transform: scale(0.92);
  transition-duration: 0.12s;
}

.project__arrow:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* Setas só com o projeto aberto: entram depois de o cartão encolher, saem logo ao fechar */
.project__nav {
  visibility: hidden;
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 0.25s ease,
    transform 0.25s ease,
    visibility 0s linear 0.25s;
}

.project.is-open .project__nav {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition:
    opacity 0.5s ease 0.65s,
    transform 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.65s,
    visibility 0s linear 0.65s;
}

/* Título: ao abrir corre para a esquerda até sair do cartão (a info passa para o painel) */
.project__head {
  transition:
    transform 0.9s cubic-bezier(0.76, 0, 0.24, 1),
    visibility 0s linear,
    /* a cor acompanha a troca de imagem da galeria */ color 0.4s ease;
}

.project.is-open .project__head {
  visibility: hidden;
  transform: translateX(-100%);
  transition:
    transform 0.9s cubic-bezier(0.76, 0, 0.24, 1),
    visibility 0s linear 0.9s;
}

@media (prefers-reduced-motion: reduce) {
  .project__arrow,
  .project__nav,
  .project.is-open .project__nav,
  .project__head,
  .project.is-open .project__head {
    transition: none;
  }
}

/* Painel preto com a descrição */
.project__panel {
  position: absolute;
  top: 0;
  width: calc(40% - var(--gap));
  /* Altura dinâmica: só desce até onde há texto (e o botão "View case", se houver), como no
     telemóvel. Se o texto passar a altura do cartão, fica nela e ganha scroll. */
  max-height: 100%;
  overflow-y: auto;
  padding: calc(var(--card-pad) * 1.4) calc(var(--card-pad) * 1.5);
  background: var(--dark);
  color: var(--cream);
  border-radius: var(--radius);
  font-size: clamp(14px, calc(1.3 * var(--vw)), 24px);
  line-height: 1.48;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 254, 224, 0.3) transparent;
  z-index: 1; /* por cima do retângulo claro (.project::after) e por baixo do cartão */
  /* Fora de vista à direita; entra "puxado" pela margem do cartão (mesma duração e curva).
     Anima "right" e não "transform": o transform corre numa thread à parte e, com a página
     ocupada (a carregar imagens/vídeos), o painel adiantava-se à largura do cartão, que só
     anda na thread principal. Com os dois na mesma thread, andam sempre juntos.
     -40% = largura do painel (40% − gap) + gap. */
  right: -40%;
  transition: right 0.9s cubic-bezier(0.76, 0, 0.24, 1);
}

.project.is-open .project__panel {
  right: 0;
}

.panel__title {
  margin: 0;
  font-size: clamp(22px, calc(2.5 * var(--vw)), 48px);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.015em;
}

.panel__client {
  margin-top: 0.2em;
  font-size: clamp(15px, calc(1.45 * var(--vw)), 28px);
  font-style: italic;
  font-weight: 500;
  line-height: 1.3;
}

.panel__question {
  margin-top: 1.1em;
  font-size: clamp(18px, calc(1.9 * var(--vw)), 36px);
  font-style: italic;
  font-weight: 400;
  line-height: 1.25;
}

.panel__body {
  margin-top: 1.5em;
}

.panel__body p {
  /* 1em (era 1,4em): com os textos definitivos, o Expofacic em PT só cabe na altura do cartão
     assim (a partir de 1280px). Abaixo disso o painel ganha scroll por dentro. */
  margin: 0 0 1em;
}

.panel__body p:last-child {
  margin-bottom: 0;
}

/* Vários trabalhos no mesmo painel (Motion Graphics: "1. Labialfarma", "2. ACERT"), cada um com
   o seu título numerado. Duas linhas em branco a separá-los; o primeiro fica logo a seguir
   ao título do painel, só com o espaço normal do .panel__body. */
.panel__body .panel__sub {
  margin-top: 2em;
  margin-bottom: 0.4em;
}

.panel__body .panel__sub:first-child {
  margin-top: 0;
}

/* Links no texto (ex.: o site do Luzo): na cor do texto, sublinhados */
.panel__body a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
}

.panel__body a:hover {
  text-decoration-thickness: 2px;
}

.panel__body a:focus-visible {
  outline: 2px solid var(--cream);
  outline-offset: 3px;
  border-radius: 2px;
}

.panel__body strong {
  font-weight: 700;
}

.project__panel .scr__glyph {
  color: rgba(255, 254, 224, 0.45);
}

/* Coluna flexível para o botão "View case" poder ficar centrado (align-self).
   O display: flex anularia o atributo hidden — daí a regra explícita. */
.project__panel {
  display: flex;
  flex-direction: column;
}

.project__panel[hidden] {
  display: none;
}

/* Espaço entre o texto e o botão */
.panel__body:has(+ .panel__case) {
  margin-bottom: 1.6em;
}

/* Botão "View case": pílula branca, itálico forte, lupa à direita */
.panel__case {
  align-self: center;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  padding: 0.5em 0.6em 0.5em 1em;
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: var(--ink);
  font: inherit;
  font-size: clamp(16px, calc(1.45 * var(--vw)), 28px);
  font-style: italic;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.02em;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.panel__case img {
  width: 1.35em;
  height: auto;
}

/* Chamada de atenção: um "pulo" em tamanho, a partir do centro, a cada 4 s, só com o painel
   aberto. Cresce, encolhe um pouco abaixo do normal, volta a crescer ligeiramente e assenta.
   Anima a propriedade "scale" (não "transform") para não colidir com o scale do hover. */
.js-anim .project.is-open .panel__case {
  animation: case-bounce 4s ease-in-out 2s infinite;
}

@keyframes case-bounce {
  0%,
  20%,
  100% {
    scale: 1;
  }
  5% {
    scale: 1.12;
  }
  10% {
    scale: 0.95;
  }
  15% {
    scale: 1.04;
  }
}

@media (hover: hover) {
  .js-anim .project.is-open .panel__case:hover {
    animation: none;
  }
}

@media (hover: hover) {
  .panel__case:hover {
    transform: scale(1.06);
  }
}

.panel__case:active {
  transform: scale(0.96);
}

.panel__case:focus-visible {
  outline: 3px solid var(--cream);
  outline-offset: 4px;
}

/* ---------- Modal do estudo de caso (<dialog class="case">, criado em hero.js) ---------- */

/* Ocupa o ecrã todo e faz ela própria o scroll da imagem comprida.
   O escurecimento é o fundo do dialog (não o ::backdrop) para a transição ser igual em todos os browsers. */
.case {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: var(--page-pad);
  border: 0;
  background: rgba(20, 20, 20, 0);
  color: var(--ink);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.35) transparent;
  transition: background-color 0.45s ease;
}

.case::backdrop {
  background: transparent;
}

/* Opaco, sem transparência (pedido do utilizador, 23/09/2026; era 88%). Continua a entrar com
   fade, porque o estado fechado é a mesma cor a 0. */
.case.is-open {
  background: rgb(20, 20, 20);
}

/* A página por trás não faz scroll enquanto a modal está aberta */
html:has(.case[open]) {
  overflow: hidden;
}

.case__sheet {
  width: min(1200px, 100%);
  margin: 0 auto;
  border-radius: clamp(16px, calc(2 * var(--vw)), 40px);
  overflow: hidden;
  opacity: 0;
  transform: translateY(48px);
  transition:
    opacity 0.5s ease,
    transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}

.case.is-open .case__sheet {
  opacity: 1;
  transform: none;
}

/* Crédito no fundo de todas as modais (fixo, a seguir à folha; serve imagens, documento e "coming soon") */
.case__credit {
  width: min(1200px, 100%);
  margin: clamp(20px, calc(3 * var(--vw)), 48px) auto 0;
  /* Mais pequena e a 50% (pedido do utilizador, 23/09/2026; era 13–16px a 60%) */
  color: rgba(255, 255, 255, 0.5);
  font-size: clamp(11px, calc(0.85 * var(--vw)), 14px);
  font-style: italic;
  line-height: 1.5;
  text-align: center;
  opacity: 0;
  transition: opacity 0.5s ease;
}

.case.is-open .case__credit {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .case__credit {
    transition: none;
  }
}

/* Enquanto a imagem carrega, a folha ocupa o ecrã (senão abria com altura zero) */
.case__sheet.is-loading {
  min-height: calc(100vh - 2 * var(--page-pad));
  background: var(--card);
}

.case__img {
  display: block;
  width: 100%;
  height: auto;
  transition: opacity 0.5s ease;
}

.case__sheet.is-loading .case__img {
  opacity: 0;
}

/* Partes coladas umas às outras. Cada uma sobe 1px sobre a de cima: com alturas fracionárias,
   duas imagens encostadas deixam passar o fundo escuro no píxel da junção (risco fino). */
.case__img + .case__img {
  margin-top: -1px;
}

/* Um <video> é pintado em camada própria e não é recortado pelos cantos redondos da folha
   (a mesma armadilha dos cartões): a primeira e a última parte trazem o raio consigo */
.case__img:first-child {
  border-top-left-radius: inherit;
  border-top-right-radius: inherit;
}

.case__img:last-child {
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
}

/* ---------- Estudo de caso em texto + imagens (<template> aberto na modal) ---------- */

/* A folha passa a página: fundo cinzento claro e margens (nos estudos de imagens é só a imagem).
   #f1f1f1 escolhido pelo utilizador; os cartões das imagens (--card, #f0f0f0) ficam quase iguais
   e leem-se sem moldura — o que se vê é a imagem. */
.case__sheet--doc {
  padding: clamp(28px, calc(5.5 * var(--vw)), 96px) clamp(22px, calc(5.5 * var(--vw)), 96px);
  background: #f1f1f1;
}

.case-doc {
  font-size: clamp(15px, calc(1.25 * var(--vw)), 22px);
  line-height: 1.55;
}

.case-doc p {
  margin: 0 0 1em;
}

/* Abertura: tag como as do header, título como os dos cartões, frase de entrada */
.case-doc__tag {
  font-size: clamp(13px, calc(1.1 * var(--vw)), 22px);
  font-style: italic;
  font-weight: 500;
  letter-spacing: 0.01em;
}

.case-doc__title {
  margin: 0.15em 0 0;
  font-size: var(--project-title);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
}

.case-doc__lede {
  max-width: 22ch;
  margin-top: 0.9em;
  font-size: clamp(22px, calc(2.6 * var(--vw)), 48px);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.case-doc__intro {
  max-width: 40ch;
  font-size: clamp(18px, calc(1.9 * var(--vw)), 36px);
  font-style: italic;
  line-height: 1.3;
}

/* Blocos: texto de um lado, imagem (maior) do outro, a alternar */
.case-doc__block {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  grid-template-areas: "text fig";
  gap: clamp(24px, calc(4 * var(--vw)), 72px);
  align-items: center;
  margin-top: clamp(48px, calc(7 * var(--vw)), 128px);
}

.case-doc__block--flip {
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  grid-template-areas: "fig text";
}

/* Bloco só com texto (o conceito, no Douro): uma coluna */
.case-doc__block:not(:has(figure)) {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "text";
}

.case-doc__text {
  grid-area: text;
}

.case-doc__text p {
  max-width: 46ch;
}

.case-doc__text p:last-child {
  margin-bottom: 0;
}

.case-doc__text h3 {
  margin: 0 0 0.6em;
  font-size: clamp(20px, calc(2 * var(--vw)), 38px);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.015em;
}

/* A tese, em destaque */
.case-doc__claim {
  max-width: 16ch;
  font-size: clamp(26px, calc(3.2 * var(--vw)), 60px);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.025em;
  text-wrap: balance;
}

/* Tradução da tese por baixo dela (Planalto: assinatura em PT, inglês por baixo) */
.case-doc__claim:has(+ .case-doc__claim-translation) {
  margin-bottom: 0.3em;
}

.case-doc__claim-translation {
  font-size: clamp(18px, calc(1.9 * var(--vw)), 36px);
  font-style: italic;
  line-height: 1.3;
}

/* As duas assinaturas do Planalto ficam numa só linha no telemóvel (pedido do utilizador,
   23/09/2026). A largura útil é ~100vw − 64px (o padding da modal mais o da folha). "O fim é
   apenas o começo." mede 12,07 × a letra e "Sou resto, mas ainda presto!" 13,27: o limite segue a
   mais comprida — (100vw − 64) / 13,67, com ~3% de folga — para as duas ficarem do mesmo tamanho.
   Sem isto faltavam 3px a 375 e as frases partiam em duas linhas. */
@media (max-width: 700px) {
  .case-doc__claim--oneline {
    max-width: none;
    white-space: nowrap;
    font-size: min(clamp(26px, calc(3.2 * var(--vw)), 60px), calc(7.32 * var(--vw) - 4.7px));
  }
}

.case-doc__block figure {
  grid-area: fig;
  margin: 0;
}

/* Imagem num cartão cinzento: enquanto o ficheiro não existir, o onerror tira a <img>
   e fica o cartão com a legenda do que lá vai. Slides 16:9. */
.case-doc__media {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: clamp(16px, calc(2.4 * var(--vw)), 48px);
  background: var(--card);
}

.case-doc__slot {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 1em;
  color: var(--ink-scramble);
  font-style: italic;
  font-weight: 500;
  text-align: center;
}

.case-doc__media :is(img, video) {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

.case-doc__block figcaption {
  margin-top: 0.8em;
  font-size: 0.85em;
  font-style: italic;
}

/* Painel das aplicações: imagem comprida a toda a largura da folha, antes da ficha */
.case-doc__board {
  margin: clamp(48px, calc(7 * var(--vw)), 128px) 0 0;
}

.case-doc__board img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: clamp(16px, calc(2.4 * var(--vw)), 48px);
}

/* Ficha final no cartão escuro, como o painel dos projetos */
.case-doc__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
  gap: 1.2em 2em;
  margin: clamp(48px, calc(7 * var(--vw)), 128px) 0 0;
  padding: calc(var(--card-pad) * 1.4) calc(var(--card-pad) * 1.5);
  border-radius: clamp(16px, calc(2.4 * var(--vw)), 48px);
  background: var(--dark);
  color: var(--cream);
}

.case-doc__facts dt {
  font-weight: 700;
}

.case-doc__facts dd {
  margin: 0.2em 0 0;
}

/* Planalto Beirão: as quatro cores da marca. São o assunto do caso (paleta e formas),
   por isso aparecem aqui apesar de o site ser monocromático — só nas bolas e nas formas. */
.case-doc--planalto {
  --pb-luz: #f4bd1e;
  --pb-biosfera: #76b82a;
  --pb-agua: #0069a9;
  --pb-terra: #bb282e;
}

.case-doc__palette {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5em 1.5em;
  max-width: 46ch;
  margin: 1.2em 0 0;
  padding: 0;
  list-style: none;
}

.case-doc__palette li {
  display: flex;
  align-items: center;
  gap: 0.6em;
  line-height: 1.3;
}

.case-doc__dot {
  flex: none;
  width: 0.9em;
  height: 0.9em;
  border-radius: 50%;
}

.case-doc__dot--luz { background: var(--pb-luz); }
.case-doc__dot--biosfera { background: var(--pb-biosfera); }
.case-doc__dot--agua { background: var(--pb-agua); }
.case-doc__dot--terra { background: var(--pb-terra); }

/* Bloco a toda a largura: título, banner e texto por baixo, numa coluna */
.case-doc__wide {
  margin-top: clamp(48px, calc(7 * var(--vw)), 128px);
}

.case-doc__wide h3 {
  margin: 0 0 0.9em;
  font-size: clamp(20px, calc(2 * var(--vw)), 38px);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.015em;
}

.case-doc__wide figure {
  margin: 0 0 1.2em;
}

.case-doc__wide p {
  max-width: 46ch;
  margin: 0;
}

.case-doc__wide p + p {
  margin-top: 1em;
}

/* Slogan a toda a largura (Planalto, "Sou resto, mas ainda presto!"), com a tradução por baixo */
.case-doc__wide .case-doc__claim {
  max-width: none;
}

.case-doc__wide .case-doc__claim-translation {
  margin: 0 0 0.9em;
}

/* Selo de prémio logo a seguir ao título (Liga, "Let it Roll": Silver Prize). Texto branco sobre
   um retângulo de cantos arredondados em prata — gradiente de cinzentos com um brilho na diagonal.
   Os cinzentos são escuros o bastante para o branco se ler; o prémio também está escrito no texto. */
.case-doc__badge {
  display: inline-block;
  margin-left: 0.5em;
  padding: 0.4em 0.85em;
  border-radius: 0.45em;
  background: linear-gradient(120deg, #6f6f6f 0%, #9d9d9d 38%, #c4c4c4 50%, #9d9d9d 62%, #707070 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
  color: #ffffff;
  font-size: max(12px, 0.55em);
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1;
  text-transform: uppercase;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
  vertical-align: 0.2em;
  white-space: nowrap;
}

/* Subtítulo por baixo do título do bloco (3D: "Motion videos for the pharmaceutical industry") */
.case-doc__wide h3:has(+ .case-doc__sub) {
  margin-bottom: 0.2em;
}

.case-doc__wide .case-doc__sub {
  max-width: none;
  margin: 0 0 clamp(20px, calc(2.4 * var(--vw)), 40px);
  font-style: italic;
}

/* Dois vídeos lado a lado à mesma altura, um vertical (9:16) e um horizontal (16:9):
   as larguras ficam na razão 9/16 : 16/9 = 81 : 256. No telemóvel, um por baixo do outro. */
.case-doc__pair {
  display: grid;
  grid-template-columns: minmax(0, 81fr) minmax(0, 256fr);
  gap: clamp(12px, calc(1.6 * var(--vw)), 28px);
  align-items: start;
}

.case-doc__media--vertical {
  aspect-ratio: 9 / 16;
}

@media (max-width: 700px) {
  .case-doc__pair {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Texto em duas colunas a toda a largura, entre o título e a imagem (uma coluna no telemóvel) */
.case-doc__cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1em clamp(24px, calc(4 * var(--vw)), 72px);
  margin-bottom: clamp(24px, calc(3 * var(--vw)), 48px);
}

.case-doc .case-doc__cols p {
  max-width: none;
  margin: 0;
}

/* No cabeçalho, logo a seguir ao título (Motion Graphics: a descrição da Labialfarma) */
.case-doc__title + .case-doc__cols {
  margin: clamp(20px, calc(2.4 * var(--vw)), 40px) 0 0;
}

/* Coluna com mais de um parágrafo (os filhos da grelha são <div>) */
.case-doc .case-doc__cols > * > p + p {
  margin-top: 1em;
}

.case-doc__wide figure:last-child {
  margin-bottom: 0;
}

@media (max-width: 700px) {
  .case-doc__cols {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Imagem inteira, na proporção dela (Planalto: banner, logótipo, camião e posts têm cada uma a sua).
   O width/height da <img> reserva o espaço; se o ficheiro faltar, o cartão fica 16:9 com a legenda. */
.case-doc__media--fit {
  aspect-ratio: auto;
}

.case-doc__media--fit:not(:has(img)) {
  aspect-ratio: 16 / 9;
}

.case-doc__media--fit img {
  position: relative;
  height: auto;
}

/* As formas desenhadas em SVG, centradas no cartão cinzento (não há ficheiro para faltar) */
.case-doc__media--shapes {
  display: grid;
  place-items: center;
  padding: 0 12%;
}

.case-doc__media--shapes svg {
  display: block;
  width: 100%;
  height: auto;
}

@media (max-width: 700px) {
  .case-doc__palette {
    grid-template-columns: minmax(0, 1fr);
  }

  .case-doc__block,
  .case-doc__block--flip {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "text" "fig";
  }
}

/* Projeto ainda sem imagens de estudo de caso: folha com aviso, em vez de vazia */
.case__soon {
  display: grid;
  place-items: center;
  min-height: min(60vh, 520px);
  padding: var(--card-pad);
  background: var(--card);
  font-size: clamp(18px, calc(1.9 * var(--vw)), 36px);
  font-style: italic;
  text-align: center;
}

/* Fechar: círculo branco com cruz, preso ao canto do ecrã (mesmo desenho das setas da galeria) */
.case__close {
  position: fixed;
  top: calc(var(--page-pad) + 14px);
  right: calc(var(--page-pad) + 14px);
  z-index: 1;
  display: grid;
  place-items: center;
  width: clamp(44px, calc(3.6 * var(--vw)), 60px);
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #ffffff;
  color: var(--ink);
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.18);
  cursor: pointer;
  opacity: 0;
  transform: scale(0.6);
  transition:
    opacity 0.35s ease,
    transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.case.is-open .case__close {
  opacity: 1;
  transform: none;
}

@media (hover: hover) {
  .case.is-open .case__close:hover {
    transform: scale(1.12);
  }
}

.case.is-open .case__close:active {
  transform: scale(0.94);
}

.case__close:focus-visible {
  outline: 3px solid #ffffff;
  outline-offset: 4px;
}

.case__close svg {
  width: 42%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
}

@media (max-width: 700px) {
  .case {
    padding: 10px;
  }

  .case__close {
    top: 20px;
    right: 20px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .panel__case,
  .case,
  .case__sheet,
  .case__img,
  .case__close {
    transition: none;
  }
}

@media (max-width: 700px) {
  .project {
    aspect-ratio: auto;
  }

  .project__card,
  .project.is-open .project__card {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 5;
  }

  /* No telemóvel o painel não entra pela direita: sai de baixo do cartão e desce (WAAPI em hero.js).
     Começa por trás da parte de baixo do cartão (sem margem): o preto aparece nos cantos redondos
     do cartão e une-o ao painel, para se perceber que a descrição é desse cartão. */
  .project__panel {
    position: relative;
    width: 100%;
    height: auto;
    max-height: none;
    margin-top: calc(-1 * var(--radius));
    padding-top: calc(var(--radius) + var(--card-pad) * 1.4);
    /* Topo reto: os cantos de cima ficam por trás dos cantos redondos do cartão e têm de ser pretos */
    border-radius: 0 0 var(--radius) var(--radius);
    overflow: visible;
    right: auto;
    transition: none;
  }

  /* No telemóvel não há coluna ao lado: o painel desce por baixo do cartão, a toda a largura */
  .project::after {
    content: none;
  }

  /* Com o projeto aberto, os cantos de baixo do cartão ficam a 90°: cartão e painel formam uma peça.
     Anima ao mesmo tempo que a gaveta (is-open entra quando o painel começa a descer e sai quando recolhe). */
  .project__card,
  .project__bg {
    transition:
      border-bottom-left-radius 0.9s cubic-bezier(0.76, 0, 0.24, 1),
      border-bottom-right-radius 0.9s cubic-bezier(0.76, 0, 0.24, 1);
  }

  .project.is-open .project__card,
  .project.is-open .project__bg {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .project__card,
  .project__panel,
  .project::after {
    transition: none;
  }
}

/* ---------- Scramble ---------- */

.scr {
  position: relative;
}

/* O texto final fica invisível a reservar o espaço (e acessível a leitores de ecrã) */
.scr__ghost {
  opacity: 0;
}

/* Camada animada por cima, com a mesma largura, para as quebras de linha coincidirem */
.scr__live {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  white-space: nowrap;
  pointer-events: none;
}

/* Textos longos podem quebrar linha — aí a camada animada acompanha o texto final */
.hero__degree .scr__live,
.hero__title .scr__live,
.panel__client .scr__live,
.panel__question .scr__live {
  white-space: normal;
}

.scr__glyph {
  color: var(--ink-scramble);
}

/* ---------- Estado inicial (antes da animação) ---------- */

.js-anim .hero__bg {
  clip-path: inset(0 100% 100% 0);
}

.js-anim .project__bg {
  clip-path: inset(0 100% 100% 0);
}

.js-anim .project__media,
.js-anim .project__plus,
.js-anim .hero__print {
  opacity: 0;
}

.js-anim .hero__photo {
  clip-path: inset(100% 0 0 0);
}

.js-anim [data-scramble]:not(.scr):not(.scr-done) {
  opacity: 0;
}

/* ---------- Footer ---------- */

/* Alinhado com os cartões (mesma largura e margens laterais da .page); o painel escuro cola-se ao
   fundo da janela com cantos redondos só em cima. O espaço entre o último cartão e o footer é o
   padding-bottom da .page (= --page-pad, o espaço entre blocos). */
.footer {
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 var(--side);
}

.footer__panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* As distâncias medem-se na letra do nome (--f), como na maquete do utilizador (22/09/2026):
     ~1× entre blocos, 1,2× em cima e 1,5× em baixo. Eram 32–80px e 40–104px — "espaços gigantes". */
  --f: clamp(20px, calc(2 * var(--vw)), 34px);
  gap: var(--f);
  padding: calc(var(--f) * 1.2) var(--card-pad) calc(var(--f) * 1.5);
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--dark);
  color: #ffffff;
  text-align: center;
}

/* Foto + (nome · tagline) */
.footer__id {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: clamp(14px, calc(1.6 * var(--vw)), 28px);
}

/* Nome e tagline lado a lado; quebram juntos para uma linha própria em ecrãs estreitos */
.footer__idtext {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: clamp(8px, calc(1.4 * var(--vw)), 22px);
}

.footer__tagline {
  font-size: clamp(17px, calc(1.7 * var(--vw)), 30px);
  font-style: italic;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.footer__photo {
  flex: none;
  /* ~1,6× a letra do nome, como na maquete (era 48–72px, 2,5× o nome) */
  width: calc(var(--f) * 1.6);
  height: calc(var(--f) * 1.6);
  overflow: hidden;
  border-radius: calc(var(--f) * 0.4);
  background: var(--placeholder);
}

.footer__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1); /* p&b, como o resto do site */
}

.footer__name {
  font-size: var(--f);
  font-weight: 700;
  letter-spacing: -0.02em;
}

/* Contactos numa linha (com quebra no telemóvel), centrados */
.footer__contacts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(16px, calc(2.4 * var(--vw)), 52px);
  font-size: clamp(14px, calc(1.15 * var(--vw)), 20px);
  font-weight: 600;
}

.footer__contacts a {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  color: inherit;
  text-decoration: none;
  transition: opacity 0.2s ease;
}

.footer__contacts a:hover,
.footer__contacts a:focus-visible {
  opacity: 0.65;
}

.footer__icon {
  flex: none;
  height: 1.6em;
  width: auto;
}

@media (max-width: 520px) {
  /* No telemóvel os contactos empilham, alinhados à esquerda a partir do centro */
  .footer__contacts {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* Chamada para o Brixx (agente de IA): texto + botão */
.footer__cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--f) * 0.9);
}

.footer__lede {
  max-width: 34em;
  color: rgba(255, 255, 255, 0.6);
  font-size: clamp(14px, calc(1.25 * var(--vw)), 22px);
  font-style: italic;
  line-height: 1.5;
}

.footer__brixx {
  display: inline-block;
  padding: clamp(13px, calc(1.35 * var(--vw)), 21px) clamp(26px, calc(2.7 * var(--vw)), 46px);
  border-radius: 999px;
  background: var(--brixx);
  color: var(--ink);
  font-size: clamp(15px, calc(1.35 * var(--vw)), 24px);
  font-style: italic;
  font-weight: 700;
  text-decoration: none;
  transition: transform 0.2s ease;
}

.footer__brixx:hover,
.footer__brixx:focus-visible {
  transform: scale(1.04);
}

@media (prefers-reduced-motion: reduce) {
  .footer__brixx {
    transition: none;
  }
}
