/* ==========================================================================
   XCLUSIVE EVENTS · M1 BLACKOUT · Secções
   ========================================================================== */

/* ---- Cold open (M1) ------------------------------------------------------ */

.coldopen {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--void);
  display: grid;
  place-items: center;
}
.coldopen .co-line {
  width: 120px;
  height: 1px;
  background: var(--bone);
  transform: scaleX(0);
}
body.co-done .coldopen { display: none; }

/* ---- Hero scroll-scrub (M2) ---------------------------------------------- */

.hero {
  height: 160svh; /* pista de scroll do scrub */
  position: relative;
}
.hero-stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
}
.hero-media, .hero-media video, .hero-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* O push-in é feito aqui, por transform, e o js/scrub.js escreve o scale.
   transform-origin ao centro porque o travelling fecha sobre a cara do felino,
   que está ao centro do enquadramento. */
.hero-media img {
  transform-origin: 50% 50%;
  will-change: transform;
  backface-visibility: hidden;
}
/* scrim para garantir contraste do texto (spec §8.4). Uma camada só, a linear
   de sempre. Houve aqui uma elipse por cima da caixa do título, posta quando o
   material do #WILD entrava com a exposição de origem; com as sombras
   esmagadas do material do hero e com o subtítulo a sair por wipe em vez de
   por opacidade, ela deixou de fazer falta. ⚠️ A fotografia do hero mudou a
   2026-09-01 e é mais clara do que o MP4 que substituiu, portanto se o título
   voltar a ficar difícil de ler é aqui que se mexe, e mede-se, não se estima.

   ⚠️ O MEIO DO GRADIENTE SUBIU de 0.08/0.12 para 0.30/0.34 a 2026-09-01, com a
   troca da fotografia. A nova é bem mais clara e o subtítulo cai justamente
   sobre a cara do felino, que é a zona de maior luminância do enquadramento.
   O valor foi PROCURADO medindo, não estimado: quatro candidatos corridos nas
   três larguras, percentagem de pixeis do subtítulo abaixo de 4.5:1.

     véu no meio    390x844   768x1024   1440x900
     0.08 / 0.12     4.58%     13.47%      3.10%
     0.18 / 0.22     1.61%      7.14%      1.07%
     0.24 / 0.28     0.54%      3.55%      0.38%
     0.30 / 0.34     0.02%      0.99%      0.02%   ← escolhido

   Controlo, medido no site publicado que ainda servia o MP4: 8.62% / 14.43% /
   0.73%. Ou seja o escolhido é melhor do que o estado anterior nas TRÊS
   larguras, e não apenas melhor do que a versão intermédia desta mudança.
   ⚠️ O subtítulo já estava mal a 768x1024 antes disto (14.43%): não era
   regressão da fotografia nova, era dívida que só apareceu por se ter medido. */
.hero-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(0,0,0,0.42) 0%, rgba(0,0,0,0.42) 34%, rgba(0,0,0,0.46) 62%, rgba(0,0,0,0.66) 100%);
  pointer-events: none;
}
/* 2026-09-01, segunda passagem: as duas paragens do meio subiram de 0.30/0.34
   para 0.42/0.46. Medido a 1440x900 sobre hero-wild.jpg, no traço do logótipo
   contra o que lhe fica à volta: o texto branco abaixo de 4.5:1 desce de
   13.8% para 8.5%, e com o halo abaixo desce a 2.6% — o mesmo que se obtém
   pintando o palco de preto puro, mas sem perder a fotografia.
   ⚠️ Subir só o véu tem tecto BAIXO nesta foto: contra preto puro o "X" do
   logótipo chega a 2.99:1 e o texto a 2.5%. Não vale a pena ir além daqui
   pelo véu — o que falta resolve-se no logótipo, não no fundo. */

.hero-title {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  z-index: 2;
  text-align: center;
  /* sem isto o subtítulo encostava às duas bordas a 390 e a 360 */
  padding-inline: var(--margin);
}
.hero-title .ht-word {
  font-family: var(--serif);
  font-variation-settings: 'opsz' 96;
  font-weight: 400;
  font-size: clamp(64px, 14vw, 240px);
  line-height: 0.9;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  will-change: transform, opacity;
}
.hero-title .ht-word img {
  width: clamp(260px, 42vw, 620px);
  height: auto;
  margin-inline: auto;
  /* Halo colado ao contorno, em vez de uma sombra difusa única.
     A sombra anterior (0 4px 32px) era larga e deslocada para baixo: protegia
     o rodapé das letras e deixava o TOPO a descoberto, que é onde bate a
     folhagem clara da cara do felino. Três anéis concêntricos sem
     deslocamento escurecem o fundo imediato dos quatro lados do traço, e só
     depois vem uma projecção curta para o logótipo não ficar colado ao plano.
     Alcance total 37px, mais curto do que os 44px que substitui.
     Raios proporcionais: o logótipo vive entre 260px e 620px de largura, e os
     coeficientes vw aterram em 1.5 / 4 / 8 / 16px a 1440 (estilo computado).
     Medido a 1440x900 sobre hero-wild.jpg: o texto branco abaixo de 4.5:1 vai
     de 8.5% (só véu) para 2.6%. NÃO mexe na cor do logótipo — o realce de
     brilho e saturação que subia o "X" de 2.90 para 3.62:1 ficou de fora, por
     ser decisão de marca. */
  /* DUAS declarações, e os clamp() vão DIRECTOS — sem var() e sem calc().
     A razão está medida, e é o contrário do que parece:
       · um valor inválido que venha de var() é "invalid at computed-value
         time": a propriedade vai a `unset` e NÃO cai para a declaração
         anterior. Medido: dá `filter: none`, ou seja o logótipo ficaria sem
         sombra nenhuma — pior do que antes desta mudança.
       · um valor inválido NO PARSE (o caso de clamp() num motor que não o
         conheça) faz o browser descartar só essa declaração, e a anterior
         fica de pé. É o único recuo que funciona aqui.
     Por isso a primeira linha leva px fixos (os valores que o clamp dá a
     1440) e a segunda leva os clamp() escritos por extenso.
     O codex review apanhou o risco; o recuo que ele sugeriu, com var(), não
     segurava — verificado no Chrome antes de escrever isto. */
  filter:
    drop-shadow(0 0 1.5px  rgba(0, 0, 0, 0.96))
    drop-shadow(0 0 4px    rgba(0, 0, 0, 0.88))
    drop-shadow(0 0 8px    rgba(0, 0, 0, 0.80))
    drop-shadow(0 3px 16px rgba(0, 0, 0, 0.50));
  filter:
    drop-shadow(0 0 clamp(0.8px, 0.11vw, 1.5px) rgba(0, 0, 0, 0.96))
    drop-shadow(0 0 clamp(2px,   0.30vw, 4px)   rgba(0, 0, 0, 0.88))
    drop-shadow(0 0 clamp(4px,   0.58vw, 8px)   rgba(0, 0, 0, 0.80))
    drop-shadow(0 clamp(1.5px, 0.225vw, 3px) clamp(8px, 1.15vw, 16px) rgba(0, 0, 0, 0.50));
}
.hero-title .ht-sub {
  margin-top: 22px;
  font-size: var(--fs-label);
  letter-spacing: 0.42em;
  text-transform: uppercase;
  /* --bone e nao --bone-dim: esta linha assenta em cima do video do hero, nao
     sobre o preto do corpo. Os 5.84:1 do --bone-dim so valem contra o #0A0A0B;
     medido por pixel de tinta cheia contra o frame do video que esta mesmo por
     baixo dela, o --bone-dim deixava 34.7% dos pixeis abaixo de 4.5:1 a 1440,
     25.0% a 768 e 44.9% a 390, com minimo de 1.50:1. Com o --bone: 1.8% / 0.0%
     / 0.0%, mediana 16.25:1. Ficam tres pixeis a 1440 sobre o ponto mais claro
     do video; isso ja e o veu do hero a decidir, nao a cor. */
  color: var(--bone);
  /* repouso do wipe de saída (js/scrub.js fecha-o para inset(0 100% 0 0) nos
     primeiros 20% da pista, encolhendo da direita para a esquerda). Fica aqui
     para o GSAP partir de um valor do mesmo formato, sem salto, e para o hero
     estático (telemóvel, reduced-motion), onde o scrub nunca corre, nunca
     aparecer cortado. */
  clip-path: inset(0 0% 0 0%);
}

.hero-hint {
  position: absolute;
  bottom: 26px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  color: var(--bone-faint);
  font-size: 10px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
}
.hero-hint::after {
  content: '';
  width: 1px;
  height: 42px;
  /* --bone-ghost e nao --bone-faint: este risco e decorativo, nao e texto. O
     --bone-faint subiu de alfa 0.24 para 0.50 para o texto de meta passar AA, e
     nesse alfa este risco de 1px ficava com o dobro da intensidade que a
     direcao de arte lhe deu. O --bone-ghost guarda o 0.24 original. */
  background: linear-gradient(180deg, var(--bone-ghost), transparent);
}

/* mobile / reduced motion: hero de altura de ecrã, sem push-in conduzido pelo
   scroll. A imagem está SEMPRE visível (o hero deixou de ter vídeo), por isso
   aqui já não se liga nem desliga nada: só muda a altura e entra o kenburns. */
.hero.is-static { height: 100svh; }
.hero.is-static.kenburns .hero-media img {
  animation: kenburns 24s var(--ease-both) infinite alternate;
}
@keyframes kenburns {
  from { transform: scale(1); }
  to   { transform: scale(1.08); }
}
@media (prefers-reduced-motion: reduce) {
  .hero.is-static.kenburns .hero-media img { animation: none; }
}

/* ---- Manifesto (frase única) --------------------------------------------- */

.manifesto { text-align: left; }
/* line-height acima do 0.96 global: com a frase de 02/09 ("Uma visao. Uma
   producao integrada.") esta caixa passou a ter descendentes, e a 1440 a cauda
   do "p" de producao ficava a 5px do ponto do "i" de integrada, medido no
   render. Com 1.02 (124.848px a 1440) a folga passa a 12px, tambem medida.
   Seletor so desta seccao da home. */
.manifesto .display-xl { max-width: 14ch; line-height: 1.02; }

/* Cada frase do <h1> numa linha so sua, em TODAS as larguras: quatro linhas,
   "Uma visao." / "Uma equipa." / "Uma producao" / "integrada.".
   O .nb impede que uma frase parta ao meio. Sozinho, porem, ele corta:
   medido no render a 48px (o minimo do clamp de base.css), a frase mais larga,
   "Uma producao", ocupa 396.3px e pede 437px de ecra contando o padding de
   20px de cada lado; a 390px saia 46.3px para fora da caixa, e como o body tem
   overflow-x:hidden nao empurrava a pagina nenhuma - ficava cortada em
   silencio, sem erro de consola e sem scroll horizontal. Apanhou-o o codex
   review a 02/09; o portao de render nao o via, porque a caixa do h1 continua
   dentro do contentor mesmo com o texto a sair dela.
   A alternativa era deixar partir abaixo dos 450px, e isso dava 5 linhas a
   390px e 7 a 320px, com "Uma" orfao. Encolher a fonte custa menos: o
   min(48px, 10.2vw) mantem sempre as mesmas quatro linhas. Medido: a fonte
   necessaria e (largura - 40) / 8.256, ou seja 29.1px a 280, 33.9px a 320 e
   42.4px a 390; o 10.2vw da 28.6px, 32.6px e 39.8px, os tres com folga.
   O 10.5vw que estava aqui antes cobria de 320px para cima mas falhava a 280px
   por 2.7px, medidos. Aos 470.6px o 10.2vw ja chega aos 48px do clamp de base,
   por isso a media query fecha aos 470px e nao ha degrau nenhum na fronteira.
   Seletor so do <h1> desta seccao da home. */
.manifesto .display-xl .nb { white-space: nowrap; }
@media (max-width: 470px) {
  .manifesto .display-xl { font-size: min(48px, 10.2vw); }
}
.manifesto .m-sig {
  margin-top: 40px;
  display: flex;
  align-items: center;
  gap: 18px;
}
.manifesto .m-sig .rule { width: 96px; }

/* ---- Pilares (stack sticky) ----------------------------------------------- */

.pillars { position: relative; }
.pillar {
  position: sticky;
  top: 0;
  height: 86svh;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: var(--ink);
}
.pillar video, .pillar .p-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.6;
}
.pillar::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.72) 100%);
}
.pillar .p-word {
  position: relative;
  z-index: 2;
  font-family: var(--serif);
  font-variation-settings: 'opsz' 96;
  font-size: clamp(48px, 10vw, 170px);
  line-height: 1;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.pillar .p-index {
  position: absolute;
  z-index: 2;
  top: clamp(80px, 12vh, 130px);
  left: var(--margin);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.3em;
  color: var(--p-acc, var(--acc));
  text-transform: uppercase;
}
.pillar .p-word { color: var(--p-acc, var(--bone)); text-shadow: 0 0 46px color-mix(in srgb, var(--p-acc, var(--bone)) 55%, transparent); }
/* O índice de cada pilar é a cor do próprio pilar. O 01/04 tinha um override
   só dele (ciano, depois periwinkle) que punha na home uma matiz que não era de
   nenhum dos quatro pilares nem do acento da página. */
.pillar-musica     { --p-acc: var(--pilar-musica); }
.pillar-danca      { --p-acc: var(--pilar-danca); }
.pillar-espetaculos{ --p-acc: var(--pilar-espetaculos); }
.pillar-decoracao  { --p-acc: var(--pilar-decoracao); }

/* ---- Atos (M4) ------------------------------------------------------------ */

.atos-list { position: relative; }

.ato {
  display: grid;
  grid-template-columns: minmax(90px, 160px) 1fr auto;
  align-items: baseline;
  gap: var(--gutter);
  padding-block: clamp(28px, 4.5vh, 48px);
  position: relative;
  transition: opacity 400ms var(--ease-out), transform var(--dur-norm) var(--ease-out);
}
.ato::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  background: var(--hair);
}
.ato .a-line {
  position: absolute;
  inset: auto 0 0 0;
  height: 2px;
  background: var(--acc);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 500ms var(--ease-out);
}

.ato .a-num {
  font-family: var(--serif);
  font-size: clamp(22px, 2.4vw, 42px);
  line-height: 1;
  white-space: nowrap;
  color: var(--bone-dim);
  transition: font-style var(--dur-micro) linear, color 400ms linear;
}
.ato .a-name {
  font-family: var(--serif);
  font-variation-settings: 'opsz' 96;
  font-size: var(--fs-display-m);
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: -0.01em;
}
.ato .a-meta {
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--bone-faint);
  text-align: right;
}

/* estado hover — as luzes baixam nos outros */
.atos-list.is-hovering .ato { opacity: 0.28; }
.atos-list.is-hovering .ato.is-lit {
  opacity: 1;
  transform: translateX(24px);
}
.ato.is-lit .a-line { transform: scaleX(1); }
.ato.is-lit .a-num { color: var(--acc); }

/* preview que segue o cursor */
.ato-preview {
  position: fixed;
  z-index: 50;
  width: 320px;
  height: 200px;
  pointer-events: none;
  clip-path: circle(0% at 50% 50%);
  transition: clip-path 520ms var(--ease-out);
  overflow: hidden;
}
.ato-preview.is-visible { clip-path: circle(75% at 50% 50%); }
.ato-preview img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 300ms linear;
}
.ato-preview img.is-current { opacity: 1; }

@media (hover: none), (pointer: coarse) {
  .ato-preview { display: none; }
  .atos-list.is-hovering .ato { opacity: 1; transform: none; }
}

/* ---- Marquee (M8) ---------------------------------------------------------- */

.clients {
  padding-block: clamp(48px, 8vh, 110px);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.marquee { display: flex; width: max-content; animation: marquee 40s linear infinite; }
.clients:hover .marquee { animation-play-state: paused; }
.marquee-track {
  display: flex;
  align-items: center;
  gap: clamp(48px, 7vw, 120px);
  padding-right: clamp(48px, 7vw, 120px);
}
.marquee-track span {
  font-size: clamp(13px, 1.3vw, 18px);
  font-weight: 500;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--bone-faint);
  white-space: nowrap;
}
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .marquee { animation: none; flex-wrap: wrap; }
}

/* ---- Experiências (teaser + página) ----------------------------------------- */

.exp-list .ato .a-name { text-transform: none; }

.exp-intro {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--gutter);
}
.exp-intro > .display-l { grid-column: 1 / span 8; }
.exp-intro > .lead { grid-column: 9 / span 4; align-self: end; }
@media (max-width: 900px) {
  .exp-intro { display: block; }
  .exp-intro > .lead { margin-top: 28px; }
}

.occasions {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 52px;
  margin-top: 40px;
  counter-reset: prog;
}
.occasions span {
  font-family: var(--serif);
  font-size: clamp(20px, 2.2vw, 32px);
  font-weight: 500;
  color: var(--bone-dim);
}
.occasions span::before {
  counter-increment: prog;
  content: counter(prog, decimal-leading-zero);
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--acc);
  margin-right: 10px;
  vertical-align: 0.6em;
}

/* ---- Journal teaser ----------------------------------------------------------- */

.journal-item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--gutter);
  align-items: baseline;
  padding-block: 28px;
  border-bottom: 1px solid var(--hair);
}
.journal-item .j-date { font-size: var(--fs-label); letter-spacing: 0.2em; color: var(--bone-faint); text-transform: uppercase; }
.journal-item .j-title { font-family: var(--serif); font-size: clamp(20px, 2.2vw, 34px); font-weight: 500; }
.journal-item .j-cat { font-size: var(--fs-label); letter-spacing: 0.2em; color: var(--bone-faint); text-transform: uppercase; }

/* ---- CTA final ------------------------------------------------------------------ */

.cta {
  min-height: 52svh;
  display: grid;
  align-content: center;
  justify-items: start;
  text-align: left;
}
.cta .display-l { max-width: 16ch; }
.cta-link {
  display: inline-block;
  margin-top: 40px;
  font-family: var(--serif);
  font-size: clamp(20px, 2vw, 32px);
  font-weight: 700;
  position: relative;
  padding-bottom: 10px;
}
.cta-link::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  background: var(--hair);
}
.cta-link::before {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 2px;
  background: var(--acc);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 600ms var(--ease-out);
  z-index: 1;
}
.cta-link:hover::before { transform: scaleX(1); }

/* ---- Página de evento ------------------------------------------------------------ */

.event-hero {
  height: 100svh;
  position: relative;
  overflow: hidden;
  display: grid;
  align-content: end;
}
.event-hero img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  will-change: transform;
}
/* O veu do hero. A paragem do fundo subiu de 0.78 para 0.92 e ganhou uma
   paragem intermedia a 74%, porque e ali que o bloco de texto do hero vive e a
   foto tem, nessa faixa, os leques amarelos e o vestido claro. Medido no render,
   sobre a tinta cheia dos glifos da etiqueta: com o veu antigo a luminancia
   mediana do fundo por baixo das letras era 0.141 a 1440 e nem o --bone passava
   (28.4% dos pixeis abaixo de 4.5:1); com este veu desce a 0.053 e o --bone da
   0.0% nos tres viewports, com minimo de 7.55:1. E cor, nao e maquetagem: as
   letras, as caixas e os tamanhos nao se mexem. */
.event-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0.32) 0%, rgba(0,0,0,0.06) 34%, rgba(0,0,0,0.70) 74%, rgba(0,0,0,0.92) 100%);
}
.event-hero .eh-content {
  position: relative;
  z-index: 2;
  padding: 0 var(--margin) clamp(40px, 7vh, 90px);
}
/* Unica etiqueta do site que assenta em cima de uma fotografia, e por isso a
   unica que nao pode ser o acento da pagina. Medido no render, sobre a tinta
   cheia dos glifos e ja com o veu novo: --logo-ember da 55.2% dos pixeis abaixo
   de 4.5:1 a 1440 (minimo 3.47:1), --bone da 0.0% nos tres viewports (minimo
   7.55:1 a 1440, 12.43:1 a 768, 7.74:1 a 390). Nenhuma das quatro matizes do
   logo aguenta este fundo sem esmagar a foto ate ela deixar de se ver. O acento
   da pagina continua a existir em todo o resto do evento-wild: nas etiquetas de
   seccao sobre preto, no sublinhado da navegacao, na barra de percurso e na
   barra de cookies. */
.event-hero .eh-label { margin-bottom: 18px; color: var(--bone); }
.event-hero .display-xl { font-size: clamp(48px, 9vw, 150px); }

.techsheet {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 36px;
  margin-top: 30px;
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--bone-dim);
}
.techsheet b { color: var(--bone-faint); font-weight: 500; margin-right: 8px; }

/* sequência editorial */
/* O .ed-full é a única caixa do site que sangra de bordo a bordo, por isso é a
   única em que o respiro (scale 1.06 do data-breathe) sai do viewport em vez de
   sair para uma goteira. Medido a 1440x900: a imagem ia de -43.2 a 1483.2 e o
   documento passava a 1483 de scrollWidth, ou seja 43px de scroll lateral em
   manifesto, experiencias e evento-wild. O recorte tem de acontecer aqui, como
   no .am-frame e no .gal-card, e não com overflow-x no body, que só tapa. */
.ed-full { margin-block: clamp(40px, 5.5vh, 72px); overflow: hidden; }
.ed-full img, .ed-full video { width: 100%; height: min(74svh, 760px); object-fit: cover; will-change: transform; }

.ed-text {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--gutter);
  margin-block: var(--section-gap);
}
/* Sem esta base, um .ed-text a que falte o modificador deixa os filhos
   auto-colocarem-se uma coluna cada, porque só as classes to-right e to-left
   recebem grid-column. Medido no experiencias.html a 390x844 antes da
   correção: documentElement.scrollWidth = 745 contra innerWidth = 390, com o
   h2 a ir de x=132.6 a x=423.8, ou seja fora do ecrã e por isso a nunca
   revelar (o IntersectionObserver não dispara para o que está fora do
   viewport, e o .fade-up fica em opacity 0 para sempre). */
.ed-text > * { grid-column: 1 / -1; }
.ed-text.to-right > * { grid-column: 6 / span 6; }
.ed-text.to-left > * { grid-column: 2 / span 6; }
@media (max-width: 900px) {
  .ed-text.to-right > *, .ed-text.to-left > * { grid-column: 1 / -1; }
}

.ed-pair {
  display: grid;
  grid-template-columns: 7fr 5fr;
  gap: var(--gutter);
  align-items: start;
  margin-block: var(--section-gap);
}
.ed-pair img { width: 100%; object-fit: cover; will-change: transform; }
.ed-pair img:first-child { height: min(72svh, 720px); }
.ed-pair img:last-child { height: min(54svh, 540px); margin-top: clamp(48px, 9vh, 120px); }
@media (max-width: 900px) {
  .ed-pair { grid-template-columns: 1fr; }
  .ed-pair img:last-child { margin-top: 0; }
}

.ed-quote {
  margin-block: var(--section-gap);
  text-align: left;
}
.ed-quote blockquote {
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(26px, 3.4vw, 52px);
  line-height: 1.15;
  max-width: 24ch;
}
.ed-quote cite {
  display: block;
  font-style: normal;
  margin-top: 24px;
  font-family: var(--sans);
  font-style: normal;
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--bone-faint);
}

/* elementos do evento: alinhamento de programa, sem caixas */
.elements {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 52px;
  counter-reset: prog;
}
.elements span {
  font-family: var(--serif);
  font-size: clamp(20px, 2.2vw, 32px);
  font-weight: 500;
  color: var(--bone-dim);
}
.elements span::before {
  counter-increment: prog;
  content: counter(prog, decimal-leading-zero);
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--acc);
  margin-right: 10px;
  vertical-align: 0.6em;
}

/* Navegação entre atos. Gémeo estrutural do .art-nav (ver ~l.1137): também é
   .container, portanto um border-top corria a largura do border-box e sangrava
   para fora do conteúdo que anuncia, x[0..1440] contra x[57.6..1382.4] dos
   próprios <a>. Em ::before recuada a var(--margin) fica no mesmo eixo. Esta
   nav não muda para coluna em nenhuma largura, por isso não há a régua de
   separação que obriga o .art-nav a suprimir o ::before a ≤720. */
.event-nav {
  display: flex;
  justify-content: space-between;
  gap: var(--gutter);
  position: relative;
  margin-top: var(--section-gap);
}
.event-nav::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--margin);
  right: var(--margin);
  height: 1px;
  background: var(--hair);
}
.event-nav a {
  flex: 1;
  padding: clamp(32px, 5vh, 56px) 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  transition: opacity var(--dur-micro) linear;
}
.event-nav a:last-child { text-align: right; align-items: flex-end; }
.event-nav a:hover .en-name { font-style: italic; }
.event-nav .en-name { font-family: var(--serif); font-size: clamp(20px, 2.4vw, 36px); font-weight: 500; }

/* ---- Responsivo --------------------------------------------------------------- */

@media (max-width: 900px) {
  .site-nav { display: none; }
  .ato { grid-template-columns: minmax(64px, 90px) 1fr; }
  .ato .a-meta { grid-column: 2; text-align: left; }
  .journal-item { grid-template-columns: 1fr; gap: 8px; }
  .progress .p-label { display: none; }
}

/* ---- Galeria de eventos (eventos.html) ----------------------------------- */

.gal-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--gutter);
  grid-auto-rows: clamp(120px, 16vh, 200px);
  grid-auto-flow: dense;
}
.gal-card {
  position: relative;
  overflow: hidden;
  display: block;
  background: var(--void);
}
.gal-card img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.82;
  transform: scale(1.02);
  transition: transform 900ms var(--ease-out), opacity 500ms linear;
  will-change: transform;
}
.gal-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 42%, rgba(0,0,0,0.78) 100%);
}
.gal-card:hover img { transform: scale(1.08); opacity: 1; }
.gal-card .gc-info {
  position: absolute;
  z-index: 2;
  inset: auto 0 0 0;
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.gal-card .gc-name {
  font-family: var(--serif);
  font-size: clamp(18px, 1.8vw, 28px);
  font-weight: 500;
  line-height: 1.1;
}
.gal-card:hover .gc-name { font-style: italic; }
.gal-card .gc-meta {
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--bone-dim);
}
/* spans do mosaico */
.gal-card.s-a { grid-column: span 5; grid-row: span 3; }
.gal-card.s-b { grid-column: span 4; grid-row: span 2; }
.gal-card.s-c { grid-column: span 3; grid-row: span 2; }
.gal-card.s-d { grid-column: span 4; grid-row: span 3; }
.gal-card.s-e { grid-column: span 5; grid-row: span 2; }
.gal-card.s-f { grid-column: span 3; grid-row: span 3; }
@media (max-width: 900px) {
  .gal-grid { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 34vw; }
  .gal-card.s-a, .gal-card.s-b, .gal-card.s-c,
  .gal-card.s-d, .gal-card.s-e, .gal-card.s-f { grid-column: span 1; grid-row: span 2; }
}

/* ---- Strip horizontal de fotos (marquee de imagens) ----------------------- */

.photo-strip {
  overflow: hidden;
  padding-block: clamp(32px, 5vh, 64px);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.photo-strip .ps-track {
  display: flex;
  gap: var(--gutter);
  width: max-content;
  animation: marquee 70s linear infinite;
}
.photo-strip:hover .ps-track { animation-play-state: paused; }
.photo-strip img {
  height: clamp(200px, 32vh, 340px);
  width: auto;
  opacity: 0.85;
  transition: opacity 300ms linear;
}
.photo-strip img:hover { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .photo-strip .ps-track { animation: none; flex-wrap: wrap; }
}

/* ---- Formulário (contactos) ------------------------------------------------ */

.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(32px, 5vh, 56px) var(--gutter);
  max-width: 880px;
}
.form-grid .full { grid-column: 1 / -1; }
@media (max-width: 720px) { .form-grid { grid-template-columns: 1fr; } }

.field { display: flex; flex-direction: column; gap: 12px; }
/* O rótulo diz de que campo se trata: é texto de interface, não decoração, e a
   13px o mínimo é 4.5:1. Medido no render sobre o fundo do formulário:
   --bone-faint dava 1.95:1, reprovado; --bone-dim dá 5.84:1. Token que já
   existia, sem valor cru e sem paleta nova. */
.field label {
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--bone-dim);
}
/* A régua de baixo é a única fronteira visível do campo: conta como componente
   de interface e o mínimo é 3:1. Medido no render sobre o mesmo fundo: --hair
   dava 1.30:1 e --bone-faint 1.95:1, ambos reprovados; o degrau seguinte que a
   paleta tem, --bone-dim, dá 5.84:1 e transformava o fio num traço. 42% de
   --bone dá 3.72:1: cumpre com folga e mantém o fio. Sai do token, por isso
   acompanha a paleta quando ela mudar. O --hair fica como está em todo o resto
   do site: este override é só dos campos. */
.field input, .field select, .field textarea {
  background: transparent;
  border: none;
  border-bottom: 1px solid color-mix(in srgb, var(--bone) 42%, transparent);
  color: var(--bone);
  font-family: var(--serif);
  font-size: clamp(19px, 1.8vw, 26px);
  padding: 8px 0 14px;
  border-radius: 0;
  transition: border-color 400ms var(--ease-out);
}
.field select { appearance: none; cursor: pointer; }
.field select option { background: var(--ink); color: var(--bone); font-family: var(--sans); font-size: 16px; }
.field textarea { resize: vertical; min-height: 110px; font-size: clamp(17px, 1.5vw, 22px); line-height: 1.5; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none;
  border-bottom-color: var(--glow);
}
.form-submit {
  margin-top: clamp(32px, 5vh, 56px);
}

/* ---- Logo real ------------------------------------------------------------- */

.brand img { height: 38px; width: auto; display: block; }
.site-foot .foot-logo img { height: 44px; width: auto; opacity: 0.9; }

/* ---- Journal strip (cartões a passar) -------------------------------------- */

.journal-strip {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
}
.journal-strip .js-track {
  display: flex;
  gap: var(--gutter);
  width: max-content;
  animation: marquee 55s linear infinite;
}
.journal-strip:hover .js-track { animation-play-state: paused; }
.j-card {
  width: clamp(260px, 26vw, 380px);
  display: flex;
  flex-direction: column;
  background: #101012;
  border: 1px solid var(--hair);
}
.j-card img { width: 100%; height: clamp(170px, 15vw, 230px); object-fit: cover; }
.j-card .jc-body { padding: 20px 22px 24px; display: flex; flex-direction: column; gap: 10px; }
.j-card .jc-tag {
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--acc);
}
.j-card .jc-title {
  font-family: var(--serif);
  font-size: clamp(17px, 1.5vw, 21px);
  font-weight: 700;
  line-height: 1.25;
}
.j-card .jc-meta { font-size: 12px; color: var(--bone-faint); letter-spacing: 0.08em; text-transform: uppercase; }
.j-card:hover .jc-title { color: var(--acc); }
@media (prefers-reduced-motion: reduce) {
  .journal-strip .js-track { animation: none; flex-wrap: wrap; }
}

/* Teaser do Journal na home. Com dois artigos reais uma faixa a passar só
   repetia os mesmos dois títulos lado a lado, e parava recortada com
   prefers-reduced-motion. Dois cartões estáticos, no eixo do .container. */
.journal-teaser {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gutter);
}
.journal-teaser .j-card { width: auto; }
.journal-teaser .j-card img { height: clamp(200px, 20vw, 300px); }
/* O slot .jc-meta deixou de ser uma data decorativa e passou a informação de
   leitura, por isso tem de se ler. Sobre o #101012 do cartão, --bone-faint dá
   2.02:1 e reprova AA a 12px; --bone-dim dá 5.81:1, medido. Token já existe, e o
   override é o mais estreito possível: só os cartões do teaser, para não mexer
   nas datas da faixa a passar. */
.journal-teaser .j-card .jc-meta { color: var(--bone-dim); }
@media (max-width: 720px) {
  .journal-teaser { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Equipa (manifesto) ------------------------------------------------------ */

.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 380px));
  gap: var(--gutter);
}
.team-card { background: #101012; border: 1px solid var(--hair); }
.team-card .tc-photo {
  height: clamp(280px, 30vw, 420px);
  display: grid;
  place-items: center;
  background:
    radial-gradient(ellipse at 50% 30%, color-mix(in srgb, var(--acc) 16%, transparent), transparent 70%),
    #0D0D0F;
}
.team-card .tc-photo span {
  font-family: var(--serif);
  font-size: clamp(56px, 6vw, 96px);
  font-weight: 800;
  color: var(--acc);
  text-shadow: 0 0 42px color-mix(in srgb, var(--acc) 60%, transparent);
}
.team-card .tc-body { padding: 20px 22px 24px; }
.team-card .tc-name { font-family: var(--serif); font-weight: 700; font-size: clamp(19px, 1.8vw, 26px); }
.team-card .tc-role { margin-top: 6px; font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--bone-faint); }

/* Newsletter */
.nl-box { max-width: 720px; }
.nl-form { display: flex; gap: 12px; margin-top: 28px; flex-wrap: wrap; }
/* A caixa da newsletter é toda a moldura do campo (não há régua nem rótulo
   visível), por isso 3:1 é o mínimo. O rgba(255,255,255,.25) que aqui estava
   dava 2.14:1 medido, e além disso era um valor cru: não sobrevivia a uma
   troca de paleta. Passa aos mesmos 42% de --bone da régua dos campos, 3.72:1
   medido. O texto era var(--fg, #fff) com um --fg que não existe em lado
   nenhum, ou seja, branco puro cru a 19.79:1; passa a --bone, o branco da
   marca, 17.54:1, que continua muito acima do mínimo. */
.nl-form input[type="email"] { flex: 1 1 280px; background: transparent; border: 1px solid color-mix(in srgb, var(--bone) 42%, transparent); color: var(--bone); padding: 14px 18px; font: inherit; }
.nl-form input[type="email"]:focus { outline: none; border-color: var(--acc); }
.nl-form button { background: var(--acc); color: var(--ink); border: 0; padding: 14px 26px; font: inherit; font-weight: 700; cursor: pointer; }
.nl-form button:hover { filter: brightness(1.1); }

/* FAQ */
.faq-list { max-width: 880px; }
.faq-item { border-bottom: 1px solid rgba(255,255,255,.14); }
.faq-item summary { cursor: pointer; padding: 18px 0; font-size: clamp(16px, 1.6vw, 20px); font-weight: 600; list-style: none; position: relative; padding-right: 36px; }
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after { content: "+"; position: absolute; right: 4px; top: 50%; transform: translateY(-50%); color: var(--acc); font-size: 22px; }
.faq-item[open] summary::after { content: "\2212"; }
.faq-item p { padding: 0 0 20px; max-width: 760px; opacity: .85; line-height: 1.6; }

/* Instagram mosaico */
.ig-mosaic { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-top: 36px; }
.ig-mosaic a { display: block; aspect-ratio: 4 / 5; overflow: hidden; }
.ig-mosaic img { width: 100%; height: 100%; object-fit: cover; transition: transform .5s ease; }
.ig-mosaic a:hover img { transform: scale(1.05); }
@media (max-width: 720px) { .ig-mosaic { grid-template-columns: repeat(2, 1fr); } }

/* Inclui lista */
.inclui-list { margin-top: 28px; padding: 0; list-style: none; max-width: 640px; }
.inclui-list li { padding: 10px 0 10px 26px; position: relative; border-bottom: 1px solid rgba(255,255,255,.08); }
/* O losango alinha à PRIMEIRA linha, não ao meio da caixa: "Licenciamentos,
   seguros e direitos de autor" é o único dos oito itens que quebra em duas
   linhas a 390 e a 360, e com top:50% o marcador caía no vão entre as duas
   (medido a 390: meio ótico da 1.ª linha a 23.5px do topo do <li>, losango a
   39.4px). Mesma gramática do .art-list li::before mais abaixo, que é por isso
   que os losangos do artigo assentam bem em itens de cinco linhas.
   O top:20px assenta o centro do losango a 24.0px do topo do <li>, meio pixel
   abaixo desse meio ótico, contra os 15.9px de desvio que dava o top:50%. */
.inclui-list li::before { content: ""; position: absolute; left: 2px; top: 20px; width: 8px; height: 8px; transform: rotate(45deg); background: var(--acc); }

/* Rodapé: links legais.
   Os seis links desta linha (Journal, e os cinco legais, um dos quais é o
   Livro de Reclamações) leem-se todos com o mesmo peso. O opacity .7 que aqui
   estava multiplicava-se pelo alpha herdado do .foot-meta e dava 1.44:1 em
   píxeis renderizados, contra os 4.5:1 mínimos do WCAG; a cor vem agora do
   .foot-meta (--bone-dim, medido a 5.8:1) sem segundo alpha por cima. O
   href="journal.html" deixou de precisar de exceção: era a exceção que fazia
   um item da linha ler-se e cinco não.
   Sublinhado, e não só cor: neste `.foot-meta` convivem seis <a> e um <span>
   de copyright, todos a 13px, weight 400, letter-spacing .14em, uppercase e
   rgba(244,241,234,.56). Medido: zero diferenças computadas entre link e
   não-link em repouso. A única pista era o anel do cursor personalizado
   (base.css faz `body.has-cursor a { cursor: none }`) e o hover, e nenhum dos
   dois existe em toque, que é onde as duas coisas ficam empilhadas a 27px uma
   da outra. O sublinhado em currentColor herda o --bone-dim e mede os mesmos
   5.84:1 do texto, é a mesma convenção do .legal-body a e do .art-body a, e
   não depende de rato. O hover continua a levar a cor a --acc. */
.foot-links a {
  color: inherit;
  opacity: 1;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  text-decoration-color: currentColor;
}
.foot-links a:hover,
.foot-links a:focus-visible { color: var(--acc); }

/* Mesma razão, mesma linha do rodapé de cima: na coluna "Seguir" os três <a>
   são `.body-copy`, exatamente como a morada não-clicável da coluna "Estúdio"
   ao lado. Sem sublinhado não havia nada a separar os dois papéis em toque.
   Os dois `.display-m` (telefone e email) ficam de fora de propósito: são a
   28-30px o único conteúdo da sua coluna, e sublinhar tipografia de display
   dessa dimensão lê-se como erro, não como afordância. */
.foot-grid .body-copy a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  text-decoration-color: currentColor;
}

/* Composição da linha, medida e não deixada ao acaso do flex-wrap.
   Larguras reais dos seis rótulos a 13px/.14em uppercase: Journal 74.8,
   Perguntas Frequentes 212.4, Termos e Condições 182.8, Privacidade e Cookies
   200.2, Livro de Reclamações 198.4, Desenvolvido por UpWeGo 237.9; somados,
   1106.5px de tinta. Com `flex-wrap` partia onde calhava (a 390, cinco linhas
   irregulares com passo de 40.1px) e lia-se como quebra acidental.

   A versão anterior corrigiu a quebra com DUAS colunas max-content para toda a
   banda 560-1339, e isso trocou um defeito por outro: duas colunas medem
   198.4 + 32 + 237.9 = 468.3px de tinta FIXA, e essa largura não muda em 780px
   de janela. Medido: 1024 -> 468.3 de tinta em 942.0 úteis (473.8 de preto
   vazio à direita, 50%); 1200 -> 468.3 em 1104.0 (635.7 vazios); 1280 -> 468.3
   em 1177.6 (709.3 vazios); 1339 -> 468.3 em 1232.0 (763.7 vazios, 62%). E a
   1340 saltava para 1196.4 de tinta: 764px de salto por 1px de janela, com a
   altura do rodapé a cair de 502 para 405. Em todo o portátil o bloco ficava
   encolhido no terço esquerdo, com o copyright sozinho à direita.

   Duas decisões, ambas medidas:

   1) O número de colunas segue a largura, e é sempre um DIVISOR DE SEIS
      (1, 2, 3, 6) para nenhuma linha ficar meia vazia. Com 4 colunas, a
      segunda linha levava só 2 dos 6 itens e acabava em x=558.7 numa janela
      de 1299, ou seja o mesmo defeito outra vez, uma linha abaixo. Escolhe-se
      o maior divisor cujas colunas max-content cabem no útil com o gutter
      mínimo da casa (--gutter, 24px) entre elas:
        6 colunas: 1106.5 + 5 x 12 = 1166.5 -> precisa de 1268px de janela;
                   fronteira em 1300 (útil 1196.0, folga 29.5). O piso de 12
                   e não 24 é o que baixa a fronteira de 1340 para 1300: numa
                   linha só os rótulos são vizinhos, não colunas.
        3 colunas: max-content por coluna 200.2 + 212.4 + 237.9 = 650.5, mais
                   2 x 24 = 698.5 -> precisa de 759px; fronteira em 768
                   (útil 706.6, folga 8.1), medida a dar gutters de 28.0/27.6.
        2 colunas: 198.4 + 237.9 + 24 = 460.3 -> precisa de 500px; fronteira
                   mantida em 560 (útil 515.2), como estava.
        1 coluna : abaixo de 560; o rótulo mais largo (237.9) cabe nos 280px
                   úteis de uma janela de 320.

   2) `justify-content: space-between` gasta a folga nos gutters em vez de a
      deixar toda à direita, e por isso a linha vai de margem a margem em
      TODAS as larguras. Medido depois: vazio à direita = 0.0px e cobertura
      100% de 560 a 1920 (era 50% a 1024, 42% a 1200, 38% a 1339 e 90% a 1440).
      Os gutters efetivos ficam 79.2 a 560, 28.0 a 768, 145.8 a 1024, 226.8 a
      1200, 18.1 a 1300, 43.7 a 1440, 115.0 a 1920 - o mesmo idioma do
      `.foot-meta`, que já distribui logo/copyright com space-between.

   As pistas são `minmax(0, max-content)` e não `max-content` seco: se a
   Archivo não carregar e os rótulos crescerem, as colunas encolhem e o texto
   quebra, em vez de a grelha transbordar para fora da janela. Medido em todas
   as 25 larguras: scrollWidth <= innerWidth, zero linhas de rótulo com mais de
   26px de altura. */
.foot-links {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, max-content));
  justify-content: space-between;
  gap: 12px;
  /* sem isto os <a> esticam-se ate a borda da coluna e "Journal", que mede
     74.8px de texto, ficava com uma caixa de clique de 237.9px: carregar no
     vazio a direita da palavra abria o Journal. Com `start` o alvo e
     exatamente a etiqueta que se ve. */
  justify-items: start;
  /* e a grelha ocupa a sua propria linha do .foot-meta, sempre. Sem isto,
     entre ~1100 e 1339 o bloco encolhia o suficiente para caber na mesma
     linha flex do logo e do copyright e o `justify-content: space-between`
     atirava-o para a direita: medido a 1200, os links acabavam em x=1152,
     encostados a margem direita, com tres linhas de altura ao lado de um
     logo de 44px. Acima de 1340 ja estava na sua linha, mas com largura de
     conteudo (1196.4 em 1324.8 uteis a 1440), e sem os 100% o
     `space-between` de dentro nao teria folga nenhuma para distribuir. */
  flex-basis: 100%;
}
@media (max-width: 1299px) {
  .foot-links {
    grid-template-columns: repeat(3, minmax(0, max-content));
    column-gap: var(--gutter);
  }
}
@media (max-width: 767px) {
  .foot-links { grid-template-columns: repeat(2, minmax(0, max-content)); }
}
@media (max-width: 559px) {
  .foot-links { grid-template-columns: minmax(0, max-content); justify-content: start; }
}
/* ...e têm de ser mesmo CLICÁVEIS, não só visíveis. A .cookie-bar é fixed no
   canto inferior esquerdo (191px de altura + 16px de folga, medidos a 390 e
   iguais a 1440 porque o max-width dela é fixo) e assentava por cima da última
   linha do rodapé: em desktop cortava o email a meio e o
   document.elementFromPoint no centro do link Journal devolvia o <button>
   Aceitar, não o <a>. A reserva estava fechada dentro de @media
   (max-width: 900px) e por isso nunca disparava em desktop, onde o rodapé
   media 32px de padding-bottom. Passa a valer em todas as larguras enquanto o
   aviso estiver de pé, e volta ao padding de base assim que o visitante decide.
   Não vai para o canto inferior direito porque é lá que vive o .progress,
   também fixo. 240px = 191 da barra + 16 de folga + 32 de padding, arredondado.
   O padding de base é agora 72px (base.css), a reserva do próprio .progress:
   é ele que segura o canto direito depois de o aviso de cookies desaparecer,
   e é por isso que a colisão com o .foot-links só se via depois de decidir. */
body:has(#cookieBar:not([hidden])) .site-foot { padding-bottom: 240px; }

/* Cookies */
.cookie-bar { position: fixed; left: 16px; bottom: 16px; z-index: 120; max-width: 260px; background: #141416; border: 1px solid rgba(255,255,255,.15); padding: 14px 16px; display: block; }
.cookie-bar[hidden] { display: none !important; }
.cookie-bar p { margin: 0 0 10px; font-size: 13px; opacity: .9; }
/* O unico link inline que TODA a gente ve, em todas as paginas, e este: a
   "Politica de Cookies" dentro do aviso. Ficara so a cor, exatamente o defeito
   que o `.legal-body a` (base.css) foi corrigido para nao ter, e com o mesmo
   numero: medido a 1440x900 com a cadeia de opacity multiplicada e composto
   sobre o fundo real da barra (rgb(20,20,22)), o link compunha para
   rgb(61,211,232) e o paragrafo que o rodeia para rgb(222,219,213), ou seja
   1.30:1 entre link e texto envolvente, contra os 3:1 que a tecnica G183 do
   WCAG exige quando a cor e a unica pista (1.4.1) - o mesmo 1.30:1 que
   justificou a correcao das paginas legais. Sublinhado a `currentColor` pela
   mesma razao que la: com --hair compunha para 1.25:1 contra o fundo e nao se
   via a 1x. O estado de hover/foco e a espessura, nao a cor. Estas quatro
   linhas sao as mesmas do `.legal-body a` e do `.art-body a`, para o site ter
   UMA convencao de link inline, nao duas. */
.cookie-bar a {
  color: var(--acc);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: currentColor;
  transition: text-decoration-thickness var(--dur-micro) var(--ease-out);
}
.cookie-bar a:hover,
.cookie-bar a:focus-visible { text-decoration-thickness: 2px; }
.cb-actions { display: flex; gap: 8px; }
.cookie-bar button { background: var(--acc); color: #0A0A0B; border: 0; padding: 8px 14px; font: inherit; font-size: 13px; font-weight: 700; cursor: pointer; }
.cookie-bar button.ghost { background: transparent; color: #fff; border: 1px solid rgba(255,255,255,.3); }

/* Páginas legais */
.legal-body { max-width: 820px; }
.legal-body h2 { font-size: clamp(20px, 2vw, 26px); margin: 40px 0 14px; }
.legal-body p, .legal-body li { line-height: 1.65; opacity: .88; }
.legal-body ul { padding-left: 22px; }

/* ---- Formulários: estados, erros e consentimento ---------------------------
   Só cor por token. Onde uso um token que pode desaparecer numa troca de
   paleta (--error) deixo o --bone como reserva dentro do próprio var(). */

/* etiqueta lida por leitores de ecrã, invisível no ecrã */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* campo-armadilha: fora do ecrã, fora da ordem de tabulação */
.hp-wrap { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }

/* foco por teclado visível: o .field :focus faz outline:none e matava o
   :focus-visible global do base.css */
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible,
.consent-label input[type="checkbox"]:focus-visible {
  outline: 1px solid var(--bone);
  outline-offset: 4px;
}
.nl-form input[type="email"]:focus-visible { outline: 1px solid var(--bone); outline-offset: 3px; }

/* o placeholder por defeito do navegador ficava a 4.3:1 */
.field input::placeholder,
.field textarea::placeholder,
.nl-form input[type="email"]::placeholder { color: var(--bone-dim); opacity: 1; }

/* erro por campo */
.field-error {
  font-size: var(--fs-label);
  line-height: 1.5;
  letter-spacing: 0.01em;
  color: var(--error, var(--bone));
}
.field-error[hidden] { display: none; }
.nl-form .field-error { flex: 1 0 100%; margin-top: 2px; }

.field input[aria-invalid="true"],
.field select[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-bottom-color: var(--error, var(--bone)); }
.nl-form input[type="email"][aria-invalid="true"] { border-color: var(--error, var(--bone)); }

/* consentimento RGPD (só no formulário de contacto) */
.consent-row { margin-top: 8px; }
.consent-label {
  display: flex; align-items: flex-start; gap: 14px;
  max-width: 620px; cursor: pointer;
  font-size: 15px; line-height: 1.55; color: var(--bone-dim);
}
/* A caixa do consentimento era o único controlo do site desenhado pelo
   navegador: sem color-scheme declarado saía branca (rgb(255,255,255)) sobre o
   #0A0A0B, o pixel mais claro do formulário inteiro. Passa a ser desenhada aqui,
   só com tokens que já existem. O color-scheme fica como rede: se um dia o
   appearance falhar, o navegador desenha-a escura em vez de branca. */
.consent-label input[type="checkbox"] {
  flex: 0 0 auto; width: 18px; height: 18px; margin-top: 4px;
  appearance: none; -webkit-appearance: none;
  color-scheme: dark;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid var(--bone-dim);
  border-radius: 0;
  cursor: pointer;
  transition: border-color 200ms var(--ease-out), background-color 200ms var(--ease-out);
}
/* o visto: dois lados de uma caixa rodada, para não trazer nenhum ficheiro
   nem nenhuma cor crua dentro de um SVG embebido */
.consent-label input[type="checkbox"]::after {
  content: "";
  width: 4px; height: 9px;
  margin-top: -2px;
  border: solid var(--ink);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
  opacity: 0;
}
.consent-label input[type="checkbox"]:checked {
  background: var(--acc);
  border-color: var(--acc);
}
.consent-label input[type="checkbox"]:checked::after { opacity: 1; }
.consent-label input[type="checkbox"]:hover:not(:checked) { border-color: var(--bone); }

/* estado inválido: era pixel a pixel igual ao válido. Passa a ter a mesma
   marca vermelha que as réguas dos outros campos, e um anel que a destaca
   mesmo para quem não distinga a cor pela espessura. */
.consent-label input[type="checkbox"][aria-invalid="true"] {
  border-color: var(--error, var(--bone));
  border-width: 2px;
  outline: 1px solid var(--error, var(--bone));
  outline-offset: 3px;
}
/* Com foco de teclado o anel volta a ser o claro do resto do site: a marca do
   erro fica na borda vermelha, e o contorno continua a dizer "estás aqui".
   Sem esta regra o estado inválido tapava o próprio indicador de foco, e a
   caixa focada ficava igual à não focada. */
.consent-label input[type="checkbox"][aria-invalid="true"]:focus-visible {
  outline: 1px solid var(--bone);
  outline-offset: 4px;
}
.consent-label:has(input[aria-invalid="true"]) { color: var(--bone); }
.consent-label a { color: var(--acc); text-decoration: underline; text-underline-offset: 3px; }
.consent-row .field-error { margin-top: 10px; }

/* zona de estado (aria-live) */
.form-status { font-size: 15px; line-height: 1.6; max-width: 52ch; }
.form-status:not(:empty) { margin-top: 22px; }
.form-status a { color: var(--acc); text-decoration: underline; text-underline-offset: 3px; }
.form-status.is-ok  { color: var(--acc); }
.form-status.is-bad { color: var(--error, var(--bone)); }
.form-status.is-info { color: var(--bone); }
.nl-form .form-status { flex: 1 0 100%; }
.nl-form .form-status:not(:empty) { margin-top: 16px; }

/* Em repouso a zona de estado está vazia e não pode gastar uma linha de flex:
   custava 12px de gap às três caixas de newsletter, numa secção que não é para
   ser redesenhada. Sai do fluxo em vez de ir a display:none, para continuar na
   árvore de acessibilidade: uma região aria-live só é anunciada com fiabilidade
   se já lá estiver antes de receber texto. */
.nl-form .form-status:empty {
  position: absolute;
  width: 1px; height: 1px;
  margin: 0; padding: 0;
  overflow: hidden; clip-path: inset(50%);
}

/* recebe o foco por script, para trazer a resposta ao ecrã; não é paragem de
   tabulação e por isso não mostra anel de foco */
.form-status:focus { outline: none; }

/* botão de envio do formulário de contacto: é um <button>, não um <a> */
button.cta-link { text-align: left; padding: 0 0 10px; }

/* estado a enviar: botão travado, sem hover a fingir que reage */
.form-submit button[disabled],
.nl-form button[disabled] { opacity: 0.5; cursor: progress; }
.nl-form button[disabled]:hover { filter: none; }
.form-submit button[disabled]:hover::before { transform: scaleX(0); }

/* microcópia legal da newsletter */
.nl-legal { margin-top: 16px; font-size: 13px; line-height: 1.6; color: var(--bone-dim); }
.nl-legal a { color: var(--acc); text-decoration: underline; text-underline-offset: 3px; }

/* contactos.html: quem chega a esta página para telefonar tem de encontrar o
   número DENTRO da página, e antes do formulário. Ao apagar a secção "Direto",
   que duplicava o rodapé inteiro, o telefone passou a existir só no rodapé,
   depois da caixa de newsletter: medido a 1440x900 com cookies aceites, o
   botão "Enviar pedido" ficava em y=1256 e o primeiro <a href="tel:"> em
   y=1882, 626px abaixo, numa página de 2146px; a 390x844 eram 672px, quase um
   ecrã inteiro. Isto não é o bloco antigo de volta: é uma linha só, ao lado do
   lead, com o telefone e o email e nada mais. A morada, as redes e os rótulos
   continuam a viver uma vez só, no rodapé. */
.direto {
  margin-top: 20px;
  max-width: 62ch;
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--bone-dim);
}
.direto a {
  color: var(--acc);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: currentColor;
  white-space: nowrap;
  transition: text-decoration-thickness var(--dur-micro) linear;
}
.direto a:hover,
.direto a:focus-visible { text-decoration-thickness: 2px; }

/* Quando o navegador ou um script trazem um campo ao ecrã, o cabeçalho fixo
   não pode ficar por cima dele. Só afeta elementos de formulário. */
.field, .consent-row, .form-submit, .nl-form { scroll-margin-top: 104px; scroll-margin-bottom: 32px; }

/* AQUI NÃO SE RESOLVE A BARRA DE COOKIES, e houve uma regra a tentá-lo que foi
   retirada: `body:has(#cookieBar:not([hidden])) .field { scroll-margin-bottom:
   224px }`. Era inerte no caso que dizia cobrir. O scroll-margin só governa
   scrolls que o NAVEGADOR faz, e a navegação sequencial do Chrome não rola
   nada a um elemento que já está inteiro dentro da janela: ao tabular, o
   elemento fica onde está, por baixo da barra fixa. Medido só com Tab, a
   390x844: #f-rgpd 100% tapado, elementFromPoint no centro a devolver
   DIV.cookie-bar. Falha o WCAG 2.2 SC 2.4.11 Focus Not Obscured (Minimum), AA.

   Quem resolve é o guarda de focusin do js/forms.js (função desobstruir()),
   que mede a faixa livre e rola o que for preciso. A barra em si não se mexe:
   já se tentou movê-la e estreitá-la e saiu pior (abaixo de 412px de largura
   os dois botões dela deixavam de caber e o "Recusar" saía do cartão, e em
   eventos.html passou a tapar 81% de um cartão da galeria em vez de 45%). */

/* O resumo da validação sai da linha do botão e vai para dentro da linha do
   primeiro campo por corrigir (ver a nota em estado(), js/forms.js). Aí o
   espaçamento já é feito pela própria linha: a .field é uma coluna flex com
   gap de 12px, e os 22px do repouso somavam-se a ele e descolavam o resumo do
   erro a que se refere. Na .consent-row, que não é flex, fica a mesma folga de
   10px que o .field-error de lá já usa.

   E dentro da linha o resumo passa à escala do .field-error, que é quem ele
   resume. Nos 15px do repouso ficava MAIOR do que o erro de 13px que estava
   logo por cima dele, o que punha o genérico tipograficamente acima do
   específico: lia-se o erro do campo a 13px e por baixo, em corpo maior, uma
   frase mais vaga sobre o mesmo assunto. Aqui é o erro do campo que manda e o
   resumo acompanha-o. Os valores são os mesmos que o .field-error já declara,
   não são valores novos. */
.field > .form-status:not(:empty),
.consent-row > .form-status:not(:empty) {
  font-size: var(--fs-label);
  line-height: 1.5;
}
.field > .form-status:not(:empty) { margin-top: 0; }
.consent-row > .form-status:not(:empty) { margin-top: 10px; }

/* ---- Journal: índice e páginas de artigo ---------------------------------- */

.art-head { padding-top: clamp(140px, 22vh, 260px); }
.art-kicker {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 30px;
  margin-bottom: clamp(22px, 3vh, 32px);
}
.art-kicker > * {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.art-kicker .ak-cat { color: var(--acc); }
.art-kicker .ak-read { color: var(--bone-dim); }
.art-head h1 { max-width: 17ch; }
.art-head .lead { margin-top: clamp(26px, 4vh, 42px); max-width: 54ch; }

/* Corpo de leitura longa: coluna estreita, medida confortável.
   O resto do site não precisa disto; por isso vive só em .art-body.

   ARMADILHA (medida, não presumida): `ch` resolve na font-size do PRÓPRIO
   elemento que o declara. Se o max-width viver num .art-body a 17px e o texto
   renderizar nos filhos a clamp(17px,1.25vw,19px), a coluna fica congelada em
   px e a medida real cresce. E `1ch` é a largura do glifo "0" da Archivo, que
   é ~22% mais largo do que a média do texto corrido em português: 64ch dava
   76 a 78 caracteres reais por linha, mais do que a `.body-copy` do resto do
   site. Por isso a font-size de leitura passa a viver no .art-body (é ela que
   define o ch) e os filhos herdam-na. */
.art-body {
  font-size: clamp(17px, 1.25vw, 19px);
  max-width: 54ch;
}
.art-body > p {
  margin-top: clamp(18px, 2.2vh, 26px);
  font-size: inherit;
  line-height: 1.78;
  color: color-mix(in srgb, var(--bone) 82%, var(--ink));
}
.art-body > p:first-child { margin-top: 0; }
.art-body > h2 + p { margin-top: clamp(20px, 2.8vh, 30px); }
.art-body strong { color: var(--bone); font-weight: 600; }
/* currentColor e nao --hair, pela mesma medicao que corrigiu o .legal-body a
   (base.css): --hair (alpha .12) compunha o sublinhado para rgb(35,34,35),
   1.25:1 contra o fundo, invisivel a 1x. Aqui o texto do paragrafo e um
   color-mix a 82% de --bone, portanto o link tambem so se distinguia por
   matiz. As duas regras mudam juntas para o site ter uma convencao de link
   inline, nao duas. */
.art-body a {
  color: var(--acc);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  text-decoration-color: currentColor;
  transition: text-decoration-thickness var(--dur-micro) linear;
}
.art-body a:hover,
.art-body a:focus-visible { text-decoration-thickness: 2px; }
.art-body > h2 {
  font-family: var(--serif);
  font-variation-settings: 'opsz' 96;
  font-weight: 700;
  font-size: clamp(24px, 2.4vw, 38px);
  line-height: 1.12;
  letter-spacing: -0.006em;
  margin-top: clamp(48px, 7vh, 84px);
  padding-top: clamp(20px, 2.6vh, 28px);
  border-top: 1px solid var(--hair);
}

.art-list { margin-top: clamp(18px, 2.4vh, 28px); padding: 0; list-style: none; }
.art-list li {
  position: relative;
  padding: 14px 0 14px 26px;
  font-size: inherit;
  line-height: 1.7;
  color: color-mix(in srgb, var(--bone) 82%, var(--ink));
  border-bottom: 1px solid var(--hair);
}
/* Filete duplo: a última linha da lista fechava com régua e o <h2> seguinte
   abria com outra, iguais em cor e extensão, sem nada entre as duas senão a
   faixa vazia do margin-top do <h2>. O intervalo É esse margin-top, portanto
   não é fixo: segue o clamp(48px, 7vh, 84px) declarado acima e varia com a
   altura do viewport (63px a 1440x900 no artigo-o-momento-em-que-a-sala-muda).
   A régua que fica é a do <h2>, que se repete em todo o artigo e é a que
   estrutura a leitura. */
.art-list li:last-child { border-bottom: 0; }
.art-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 27px;
  width: 8px;
  height: 8px;
  transform: rotate(45deg);
  background: var(--acc);
}

/* Imagens do artigo. O recorte do respiro (scale 1.06 do data-breathe) tem de
   acontecer numa caixa SEM padding, como no .gal-card: num .container o
   overflow recorta na caixa de padding e a imagem escalada derrama para dentro
   da goteira, saindo do eixo de alinhamento do texto. Daí o .am-frame. */
.art-media { margin-block: clamp(40px, 6vh, 76px); }
.art-media .am-frame { overflow: hidden; }
.art-media img {
  width: 100%;
  height: min(64svh, 640px);
  object-fit: cover;
  will-change: transform;
}

/* Fim de artigo. Sem itálico no hover: a Syne não tem itálico. */
/* Como .art-nav também é .container, o border-top corria a largura do
   border-box, x[0..1440] a 1440, contra x[57.6..1382.4] das réguas do
   journal.html e x[57.6..937.6] das do faqs.html. Desenhada em ::before
   recuada a var(--margin) fica no mesmo eixo do resto do conteúdo. A ≤720 as
   réguas vivem nos <a>, por isso lá este ::before é suprimido. O .event-nav
   do evento-wild.html é o gémeo estrutural desta nav e leva o mesmo desenho. */
.art-nav {
  display: flex;
  justify-content: space-between;
  gap: var(--gutter);
  position: relative;
  margin-top: var(--section-gap);
}
.art-nav::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--margin);
  right: var(--margin);
  height: 1px;
  background: var(--hair);
}
.art-nav a {
  flex: 1;
  max-width: 44ch;
  padding: clamp(32px, 5vh, 56px) 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.art-nav a:last-child { text-align: right; align-items: flex-end; }
.art-nav .an-title {
  font-family: var(--serif);
  font-size: clamp(19px, 2vw, 30px);
  font-weight: 700;
  line-height: 1.16;
  transition: color var(--dur-micro) linear;
}
.art-nav a:hover .an-title { color: var(--acc); }
@media (max-width: 720px) {
  /* Em coluna ficavam duas réguas de extensões diferentes a 139px uma da
     outra (medido a 390): a de topo vinha da caixa .container e sangrava de
     0 a 390, a que separa os dois itens vivia dentro do padding, de 20 a 370.
     Aqui as duas passam a viver nos <a>, portanto no mesmo recuo. */
  .art-nav { flex-direction: column; gap: 0; }
  .art-nav::before { content: none; }
  .art-nav a {
    max-width: none;
    padding-block: 30px;
    border-top: 1px solid var(--hair);
  }
  .art-nav a:last-child {
    text-align: left;
    align-items: flex-start;
  }
}

/* Índice do Journal (journal.html) */
.art-index { display: flex; flex-direction: column; }
.art-index .ai-item {
  display: grid;
  grid-template-columns: 5fr 7fr;
  gap: clamp(24px, 3vw, 56px);
  align-items: center;
  padding-block: clamp(32px, 5vh, 56px);
  border-top: 1px solid var(--hair);
}
.art-index .ai-item:last-child { border-bottom: 1px solid var(--hair); }
.art-index .ai-shot { overflow: hidden; }
.art-index .ai-shot img {
  width: 100%;
  height: clamp(200px, 26vw, 340px);
  object-fit: cover;
  opacity: 0.86;
  transform: scale(1.02);
  transition: transform 900ms var(--ease-out), opacity 500ms linear;
}
.art-index .ai-item:hover .ai-shot img { transform: scale(1.07); opacity: 1; }
.art-index .ai-title {
  display: block;
  font-family: var(--serif);
  font-variation-settings: 'opsz' 96;
  font-weight: 700;
  font-size: clamp(24px, 3vw, 46px);
  line-height: 1.08;
  letter-spacing: -0.008em;
  max-width: 18ch;
  transition: color var(--dur-micro) linear;
}
.art-index .ai-item:hover .ai-title { color: var(--acc); }
.art-index .ai-lead {
  display: block;
  margin-top: 18px;
  max-width: 52ch;
  font-size: var(--fs-body);
  line-height: 1.7;
  color: var(--bone-dim);
}
.art-index .ai-more {
  display: inline-block;
  margin-top: 22px;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--acc);
}
@media (max-width: 900px) {
  .art-index .ai-item { grid-template-columns: 1fr; gap: 20px; }
  .art-index .ai-shot img { height: clamp(180px, 46vw, 260px); }
}
