/* =============================================================================
   Alexandra Almeida — portfólio
   -----------------------------------------------------------------------------
   Ordem do ficheiro:
     1. Fontes
     2. Tokens          ← é aqui que se mexe para mudar cores, espaços e tipos
     3. Base
     4. Primitivas      ← .wrap .inner .stack .grid .frame .media
     5. Tipografia      ← .display .lede .body .eyebrow .label .note
     6. Componentes     ← .pill .entry .spec .card .btn .ref .flink .cv
     7. Secções         ← .section .hero .site-header .site-footer
     8. Interações      ← índice lateral, lightbox, revelação, foco
     9. Movimento reduzido
   ========================================================================== */


/* 1. FONTES ================================================================ */
/* Variáveis (300–700). O mesmo ficheiro serve os pesos 300 e 400 usados no site. */

@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 300 400;
  font-display: swap;
  src: url('../fonts/cormorant-garamond-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 300 400;
  font-display: swap;
  src: url('../fonts/cormorant-garamond-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300 400;
  font-display: swap;
  src: url('../fonts/cormorant-garamond-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300 400;
  font-display: swap;
  src: url('../fonts/cormorant-garamond-italic-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 300 400;
  font-display: swap;
  src: url('../fonts/jost-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 300 400;
  font-display: swap;
  src: url('../fonts/jost-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}


/* 2. TOKENS ================================================================ */

:root {
  /* Cor */
  --paper:        #FAF6F1;   /* fundo geral */
  --paper-hi:     #FBF7F2;   /* branco do hero, meio tom acima do papel */
  --paper-warm:   #F1E9E1;   /* fundo de Roquete e Mustard Crush */
  --paper-cool:   #EFEBE6;   /* fundo de Superfície Viva */
  --ink:          #211D1A;   /* texto principal e fundo do rodapé */
  --accent:       #A9825E;   /* latão — numeração, datas, hover */
  --accent-soft:  #D8B694;   /* hover sobre fundo escuro */
  --muted:        #6E645B;   /* corpo de texto */
  --muted-2:      #8A7F74;   /* etiquetas, códigos, legendas */
  --line:         #E0D3C5;   /* filete da citação */
  --selection:    #E6D6C4;

  /* Filetes — todos derivam da tinta, só muda a opacidade */
  --rule:         rgba(33, 29, 26, .12);
  --rule-card:    rgba(33, 29, 26, .14);
  --rule-strong:  rgba(33, 29, 26, .16);
  --rule-pill:    rgba(33, 29, 26, .2);
  --rule-btn:     rgba(33, 29, 26, .28);
  --rule-inv:     rgba(250, 246, 241, .2);

  /* Tipo — a cadeia de recurso tem de ficar como está: alguns sinais (⌄, ↓)
     não existem nas duas famílias e são desenhados pela fonte de sistema. */
  --font-display: 'Cormorant Garamond', serif;
  --font-ui:      'Jost', sans-serif;

  /* Medida e espaço */
  --maxw:      1280px;
  --pad-x:     clamp(20px, 4vw, 56px);
  --section-y: clamp(80px, 13vh, 170px);
  --gap-img:   12px;

  /* Movimento */
  --ease:      cubic-bezier(.22, .61, .36, 1);
  --dur:       .35s;
}


/* 3. BASE ================================================================== */

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  -webkit-font-smoothing: antialiased;
}

.page { overflow-x: clip; }

img { display: block; }

a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--accent); }

::selection { background: var(--selection); }

@keyframes riseIn   { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
@keyframes cueDrift { 0%, 100% { transform: translateY(0); opacity: .5; } 50% { transform: translateY(9px); opacity: 1; } }
@keyframes drawRule { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* Gémea do riseIn para quem entra por cima — a revelação passou a saber de que
   lado vem o bloco. O `to { opacity: 1 }` não é decorativo: quem repõe a
   opacidade dos blocos escondidos por [data-reveal-group] é o fim desta
   animação, e não uma regra à parte. Uma keyframe que o esqueça deixa as
   grelhas invisíveis para sempre. */
@keyframes dropIn   { from { opacity: 0; transform: translateY(-18px); } to { opacity: 1; transform: none; } }

/* Só transform: a palavra sobe dentro de um título que já está a fazer o seu
   próprio fundido. Dois fundidos sobrepostos leriam a papa. Pela mesma razão,
   o dealIn das etiquetas e das linhas de percurso também não toca em
   opacidade — quem se apaga é sempre o quadro, uma vez só. */
@keyframes riseWord { from { transform: translateY(.3em); } to { transform: none; } }
@keyframes dealIn   { from { transform: translateY(14px); } to { transform: none; } }


/* 4. PRIMITIVAS ============================================================ */
/* Parametrizadas por custom property — em vez de uma classe nova por variação,
   ajusta-se o valor no HTML: <div class="grid" style="--grid-min: 170px">
   Cada primitiva tem o seu prefixo (--grid-*, --stack-*, --frame-*) porque as
   custom properties são herdadas: um --gap genérico posto num contentor
   passaria para todas as grelhas lá dentro sem se dar por isso.               */

/* Coluna centrada com a medida do site. .wrap traz o respiro lateral,
   .inner não (usa-se quando a secção já tem padding horizontal). */
.inner { width: 100%; max-width: var(--maxw); margin-inline: auto; }
.wrap  { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--pad-x); }

/* Empilhamento vertical. --stack-gap controla o espaço. */
.stack { display: flex; flex-direction: column; gap: var(--stack-gap, 20px); }

/* Grelha fluida. --grid-min é a largura mínima de cada coluna antes de quebrar. */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--grid-min, 240px), 1fr));
  gap: var(--grid-gap, var(--gap-img));
}
.grid--fixed  { grid-template-columns: repeat(var(--grid-cols, 3), 1fr); }
.grid--start  { align-items: start; }
.grid--end    { align-items: end; }
.grid--center { align-items: center; }

/* Linha horizontal flexível. */
.row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--row-gap, 16px); }

/* Imagem recortada numa proporção fixa. --frame-ar muda a proporção.
   O `height: auto` não é decorativo: os atributos width/height do HTML entram
   como height fixo e, sem isto, ganhavam ao aspect-ratio — a imagem saía com a
   altura natural em vez da proporção pedida. */
.frame {
  width: 100%;
  height: auto;
  aspect-ratio: var(--frame-ar, 3 / 4);
  object-fit: cover;
}
.frame--contain { object-fit: contain; }

/* Imagem à largura total, altura natural. */
.media { width: 100%; height: auto; }

/* --- Faixa a sangrar ------------------------------------------------------
   Num scroll rápido o site lê-se uniforme porque tudo tem a mesma medida: a
   coluna de 1002px. Esta faixa é a excepção — escapa às margens de lado a
   lado, com altura fixada ao ecrã, e as imagens derivam a ritmos diferentes
   enquanto passa. Três por site, nas filas de fotografia que fecham Roquete,
   Denim e Dot or Not: dão ao scroll três picos em vez de um plano contínuo.  */
.bleed-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 2px;
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  height: clamp(420px, 62vh, 680px);
}
.bleed-row > * {
  position: relative;
  overflow: hidden;
  margin: 0;
}
/* A imagem é mais alta que a janela que a mostra: a folga é o que permite a
   deriva sem nunca descobrir o fundo. --p vem do scroll (0 a entrar, 1 a
   sair) e --d é a amplitude, diferente por coluna. */
.bleed-row img {
  position: absolute;
  inset: -12% 0;
  width: 100%;
  height: 124%;
  object-fit: cover;
  transform: translate3d(0, calc((var(--p, .5) - .5) * var(--d, 7%) * -1), 0);
}
.bleed-row > :nth-child(odd)  img { --d: 9%; }
.bleed-row > :nth-child(3n)   img { --d: 5%; }

/* Legenda da faixa: pousa no canto, sobre a fotografia. */
.bleed-row__note {
  position: absolute;
  left: clamp(12px, 1.4vw, 20px);
  bottom: clamp(10px, 1.2vh, 16px);
  z-index: 1;
  font-size: 10px;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--paper-hi);
  text-shadow: 0 1px 14px rgba(20, 16, 13, .7);
}

@media (max-width: 760px) {
  .bleed-row {
    grid-auto-flow: row;
    grid-auto-columns: auto;
    grid-auto-rows: 46vh;
    height: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bleed-row img { transform: none; inset: 0; height: 100%; }
}

/* Faixa de paleta de cor — recortada numa fita fina. */
.palette { width: 100%; height: 46px; object-fit: cover; }

/* Fundos de suporte para imagens com transparência ou margens brancas. */
.on-paper { background: var(--paper); }
.on-warm  { background: var(--paper-warm); }
.on-white { background: #fff; }


/* 5. TIPOGRAFIA ============================================================ */

/* Títulos em serifa. Escala fixa — cada modificador corresponde a um lugar:
     --hero     nome no topo          --xl   Superfície Viva, Mustard Crush
     --feature  Roquete               --l    Fungus, Denim
     --s        título do Sobre       --m    Pop-up
     --thanks   "obrigada" do rodapé                                          */
.display {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 300;
  line-height: .96;

  /* Espacejamento ótico: um título a 46px quer ar entre as letras, um título a
     210px quer densidade. Como `em` se mede contra o tamanho da própria letra,
     este calc é uma rampa contínua — abre nos tamanhos pequenos e aperta à
     medida que o título cresce, sem saltos e sem uma media query por classe:

         46px → +0.010em        118px → −0.010em
         92px → −0.007em        210px → −0.016em

     O clamp trava as pontas: nunca mais solto do que hoje nos tamanhos
     pequenos, nunca colado nos muito grandes. */
  letter-spacing: clamp(-3.4px, calc(1.53px - .0233em), .46px);
}
.display em { font-style: italic; }

.display--hero    { font-size: clamp(46px, 11.5vw, 210px); line-height: .88; }
.display--feature { font-size: clamp(52px, 12vw, 190px);   line-height: .92; }
.display--xl      { font-size: clamp(40px, 7.5vw, 118px); }
.display--l       { font-size: clamp(40px, 7vw, 108px); }
.display--m       { font-size: clamp(36px, 6vw, 92px);     line-height: .98; }
.display--s       { font-size: clamp(30px, 3.6vw, 54px);   line-height: 1.14; letter-spacing: .01em; max-width: 22ch; }
.display--thanks  { font-size: clamp(44px, 9vw, 140px);    line-height: .92; }

/* Frase de abertura em serifa, dentro dos projetos. */
.lede {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(19px, 2vw, 27px);
  font-weight: 300;
  line-height: 1.5;
  text-wrap: pretty;
}

/* Corpo de texto. --body-mw controla a medida (52ch por omissão). */
.body {
  margin: 0;
  max-width: var(--body-mw, 52ch);
  font-size: 16px;
  font-weight: 300;
  line-height: 1.8;
  color: var(--muted);
  text-wrap: pretty;
}

/* Variante do Sobre: itálico com filete à esquerda. */
.body--quote {
  font-style: italic;
  border-left: 1px solid var(--line);
  padding-left: 20px;
}

/* Numeração de secção — "01 — Sobre". */
.eyebrow {
  margin: 0;
  font-size: 10px;
  font-weight: 400;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Etiqueta de bloco — "Line-up", "Fichas técnicas". */
.label {
  margin: 0;
  font-size: 10px;
  font-weight: 400;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Legenda discreta. --wide e --wider abrem o espacejamento, --light alivia o peso. */
.note {
  margin: 0;
  font-size: 10px;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--muted-2);
}
.note--wide  { letter-spacing: .24em; }
.note--wider { letter-spacing: .3em; }
.note--light { font-weight: 300; }


/* 6. COMPONENTES =========================================================== */

/* Etiqueta oval — tags dos projetos e idiomas. */
.pills { display: flex; flex-wrap: wrap; gap: 8px 10px; }
.pill {
  padding: 7px 15px;
  border: 1px solid var(--rule-pill);
  border-radius: 999px;
  font-size: 10px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Etiqueta + filete que atravessa a largura (+ etiqueta opcional à direita). */
.rule-head { display: flex; align-items: center; gap: 16px; }
.hairline  { flex: 1; height: 1px; background: var(--rule-strong); }

/* Cabeçalho de um desenho técnico: nome à esquerda, código à direita.
   Em ecrãs estreitos os dois não cabem na mesma linha — em vez de deixar o
   nome partir a meio, o código desce e o filete sai. */
@media (max-width: 680px) {
  .drawing__head { flex-wrap: wrap; row-gap: 4px; }
  .drawing__head .hairline { display: none; }
  .drawing__head .ref { margin-left: auto; }
}

/* Linha de percurso — formação, experiência, competências. */
.entry {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  justify-content: space-between;
  padding: 15px 0;
  border-bottom: 1px solid var(--rule);
}
.entry--bare { padding: 0; border-bottom: 0; gap: 4px 24px; }
.entry__name { font-size: 13px; font-weight: 400; letter-spacing: .06em; }
.entry__year { font-size: 12px; font-weight: 300; letter-spacing: .14em; color: var(--accent); }
.entries     { display: flex; flex-direction: column; border-top: 1px solid var(--rule); margin-top: 8px; }

/* Lista de traços dentro do CV. */
.bullets {
  margin: 8px 0 0;
  padding-left: 17px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  font-size: 14px;
  font-weight: 300;
  line-height: 1.7;
  color: var(--muted);
}

/* Par termo/definição em duas colunas — competências e fichas técnicas. */
.spec {
  margin: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 11px 22px;
  font-size: 13px;
}
.spec dt {
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted-2);
  padding-top: 4px;
}
.spec dd { margin: 0; font-weight: 300; line-height: 1.65; color: var(--muted); }

.spec--tight    { gap: 9px 22px; font-size: 12px; }
.spec--tight dt { padding-top: 0; align-self: center; }
.spec--tight dd { font-weight: 400; line-height: normal; color: inherit; }

/* Ficha técnica. */
.card {
  background: var(--paper);
  padding: clamp(22px, 3vw, 34px);
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  border-bottom: 1px solid var(--rule-card);
  padding-bottom: 14px;
}
.card__title { margin: 0; font-family: var(--font-display); font-weight: 400; font-size: 27px; }
.card__text  { margin: 0; font-size: 14px; font-weight: 300; line-height: 1.75; color: var(--muted); }

/* Código de referência da peça. Aparece no cabeçalho da ficha técnica e,
   igual, no cabeçalho do respetivo desenho — é o que os liga. */
.ref {
  flex: none;
  font-size: 10px;
  letter-spacing: .16em;
  color: var(--muted-2);
  transition: color var(--dur) var(--ease);
}

/* Apontar (ou dar foco) a uma ficha acende o código da ficha e o do seu
   desenho ao mesmo tempo, mesmo que estejam longe um do outro. */
.is-paired .ref { color: var(--accent); }

/* Botão de contorno. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 13px 24px;
  border: 1px solid var(--rule-btn);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 400;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--ink);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.btn:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* Seta que acompanha um rótulo — não deve herdar o espacejamento do texto. */
.arrow { letter-spacing: 0; font-size: 12px; line-height: 1; }

/* Ligações do cabeçalho. Sem cápsula: a legibilidade sobre a fotografia vem
   da cor mudar com o que está por trás, não de uma caixa opaca por cima. */
.site-header__link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ink);
  transition: color var(--dur) var(--ease);
}
.site-header__link:hover { color: var(--accent); }

/* O nome. Fica escondido enquanto o hero o está a dizer a 210px logo por
   baixo, e entra quando o hero sai de trás do cabeçalho — é aí que passa a
   servir para alguma coisa. visibility e não só opacity: escondido também
   sai da ordem de tabulação e da árvore de acessibilidade. */
.brand {
  letter-spacing: .3em;
  opacity: 0;
  visibility: hidden;
  transition: opacity .5s var(--ease), color var(--dur) var(--ease), visibility 0s .5s;
}
.site-header.past-hero .brand {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

/* Currículo completo — bloco expansível. */
.cv summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 15px 0;
  border-bottom: 1px solid var(--rule);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: .06em;
  color: var(--ink);
  list-style: none;
  cursor: pointer;
}
.cv summary::-webkit-details-marker { display: none; }
.cv summary::marker { content: ""; }
.cv summary:hover { color: var(--accent); }
.cv .chev { flex: none; font-size: 13px; line-height: 1; color: var(--accent); transition: transform var(--dur) var(--ease); }
.cv[open] .chev { transform: rotate(180deg); }
.cv .panel {
  display: flex;
  flex-direction: column;
  gap: 30px;
  padding: 28px 0 32px;
  border-bottom: 1px solid var(--rule);
}
.cv[open] .panel { animation: riseIn .5s var(--ease) both; }

/* Ligação do rodapé. */
.flink {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 15px 0;
  border-top: 1px solid var(--rule-inv);
  font-size: 14px;
  font-weight: 300;
  letter-spacing: .04em;
  color: var(--paper);
  transition: color var(--dur) var(--ease);
}
.flink:last-child { border-bottom: 1px solid var(--rule-inv); }
.flink:hover { color: var(--accent-soft); }
.flink__label {
  font-size: 10px;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: rgba(250, 246, 241, .5);
}

/* Linha de ligações do rodapé — em coluna no telemóvel, em banda no desktop.
   Cada uma fecha com filete próprio porque deixam de ser uma pilha. */
.flinks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  column-gap: clamp(24px, 4vw, 64px);
}
.flinks .flink { border-bottom: 1px solid var(--rule-inv); }

/* Formulário de contacto. Vive só no rodapé, sobre fundo escuro: sem caixas,
   apenas filetes — a mesma gramática das fichas técnicas e das ligações. */
.form  { display: flex; flex-direction: column; gap: 20px; }
.field { display: flex; flex-direction: column; gap: 8px; }
.field__label {
  font-size: 10px;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: rgba(250, 246, 241, .5);
}
.field input,
.field textarea {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule-inv);
  padding: 8px 0;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 300;
  line-height: 1.7;
  color: var(--paper);
  transition: border-color var(--dur) var(--ease);
}
.field textarea { resize: vertical; min-height: 88px; }
.field input:focus,
.field textarea:focus { outline: none; border-bottom-color: var(--accent-soft); }
.form__foot { display: flex; align-items: center; flex-wrap: wrap; gap: 18px; margin-top: 4px; }

/* Botão de contorno sobre fundo escuro. */
.btn--inv { border-color: var(--rule-inv); color: var(--paper); }
.btn--inv:hover { background: var(--paper); color: var(--ink); border-color: var(--paper); }


/* 7. SECÇÕES =============================================================== */

.section { padding: var(--section-y) var(--pad-x); }
.section--tall      { padding-block: clamp(80px, 14vh, 180px); }   /* Sobre */
.section--short-end { padding-bottom: clamp(60px, 9vh, 110px); }   /* Fungus */
.section--flush     { padding: clamp(70px, 11vh, 140px) 0; }       /* Roquete: cada bloco traz o seu respiro */
.section--warm      { background: var(--paper-warm); }
.section--cool      { background: var(--paper-cool); }

/* --- Cabeçalho ----------------------------------------------------------- */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* O mesmo respiro lateral do conteúdo, para o texto cair na mesma linha
     vertical que o "Alexandra" do hero e que todas as secções. Os 25px em
     cima e em baixo repõem a altura que a cápsula dava: a linha de base do
     texto fica exactamente onde estava. */
  padding: 25px var(--pad-x);
  transition: transform .45s var(--ease);
}
.site-header.is-hidden { transform: translateY(calc(-100% - 8px)); }

/* Sobre o conteúdo o cabeçalho precisa de uma base: sem ela o texto fixo
   cruza-se com o texto que lhe passa por baixo e não se lê nem um nem outro.
   É a mesma matéria translúcida das cápsulas antigas — mas em barra à largura
   toda, que é arquitectura da página, e não em oval, que é autocolante.
   Sobre o hero não existe: aí a fotografia fica limpa. */
.site-header::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(250, 246, 241, .82);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
  opacity: 0;
  transition: opacity .4s var(--ease);
}
.site-header.past-hero::after { opacity: 1; }

/* Sobre o hero e sobre o rodapé o texto inverte. A sombra não é decorativa:
   sobre o painel, em ecrãs estreitos, o recorte da fotografia deixa o canto
   claro e o texto ficava a 3,5:1. Assim assenta a leitura sem pousar uma
   caixa por cima da imagem — lê-se como atmosfera, não como sombra. */
.site-header.on-dark .site-header__link {
  color: var(--paper-hi);
  text-shadow: 0 0 2px rgba(20, 16, 13, .42), 0 1px 18px rgba(20, 16, 13, .62);
}
.site-header.on-dark .site-header__link:hover { color: var(--accent-soft); }

/* Em ecrãs estreitos o `cover` da fotografia pode recortar para uma zona
   clara e o "CV" descia a 4,2:1. Um véu muito suave no topo — que se lê como
   vinheta e não como caixa — repõe a leitura. A partir de 900px não é preciso
   (mede 5,1:1 e 6,4:1), por isso aí a imagem fica intocada. */
@media (max-width: 900px) {
  .site-header.on-dark::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 104px;
    z-index: -1;          /* atrás do texto, à frente da fotografia */
    background: linear-gradient(180deg, rgba(20, 16, 13, .34), rgba(20, 16, 13, 0));
    pointer-events: none;
  }
}

/* --- Hero ---------------------------------------------------------------- */
/* O hero é o único sítio do site onde entra uma fotografia de fundo. Em vez
   de escrever por cima dela — que é o que qualquer página faz —, divide-se a
   página: o papel do resto do site à esquerda, com o nome a tinta, e o
   trabalho à direita como matéria. A dobra é a mesma que separa texto e
   imagem em todas as secções. */
.hero {
  position: relative;
  display: grid;
  grid-template-columns: 1.06fr 1fr;
  height: 100vh;
  min-height: 620px;
  width: 100%;
  background: var(--paper);
  overflow: hidden;
}

/* --- Painel de papel ----------------------------------------------------- */
.hero__body {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 24px;
  padding: clamp(64px, 10vh, 120px) clamp(24px, 3.4vw, 60px) clamp(40px, 6vh, 76px) var(--pad-x);
}

.hero__top {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.6vw, 22px);
  animation: riseIn 1.2s var(--ease) both;
}
/* O filete abre caminho entre a designação e o lugar, logo a seguir à linha
   entrar — é o primeiro dos filetes do site a desenhar-se, e dá o tom aos
   outros todos. */
.hero__rule {
  flex: 1;
  height: 1px;
  background: var(--rule-strong);
  transform-origin: left center;
  animation: drawRule .8s var(--ease) .5s both;
}

/* O nome deixa de ser um cartaz de página inteira: agora vive numa coluna,
   e a escala é a da coluna, não a do ecrã. */
.hero__title {
  color: var(--ink);
  font-size: clamp(40px, 6.2vw, 104px);
  animation: riseIn 1.1s var(--ease) both;
}
.hero__title em { padding-left: .08em; }

.hero__kicker {
  margin: 0;
  font-size: 10px;
  font-weight: 400;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}
.hero__place {
  margin: 0;
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 300;
  color: var(--muted-2);
}

.hero__foot { display: flex; flex-direction: column; gap: clamp(16px, 2.6vh, 28px); }

.hero__lede {
  margin: 0;
  max-width: 40ch;
  font-size: clamp(14.5px, 1.05vw, 16px);
  font-weight: 300;
  line-height: 1.75;
  color: var(--muted);
  text-wrap: pretty;
  animation: riseIn 1.4s var(--ease) both;
}

/* Índice dos trabalhos: em coluna, cada um com o seu filete, como as ligações
   do rodapé. Diz o que há no site antes do primeiro scroll. */
.hero__index {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--rule);
  animation: riseIn 1.6s var(--ease) both;
}
.hero__index a {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: clamp(6px, .9vh, 10px) 0;
  border-bottom: 1px solid var(--rule);
  font-size: 12.5px;
  font-weight: 300;
  letter-spacing: .06em;
  color: var(--ink);
  transition: color var(--dur) var(--ease);
}
.hero__index a:hover,
.hero__index a.is-live { color: var(--accent); }

/* A seta só aparece na linha activa: diz que aquela linha está a comandar o
   painel ao lado, e não apenas a ser apontada. */
.hero__index-cue {
  margin-left: auto;
  font-size: 11px;
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.hero__index a:hover .hero__index-cue,
.hero__index a.is-live .hero__index-cue { opacity: 1; transform: none; }
.hero__index .num {
  font-size: 10px;
  letter-spacing: .2em;
  color: var(--muted-2);
  transition: color var(--dur) var(--ease);
}
.hero__index a:hover .num { color: var(--accent); }

/* --- Painel de imagem ---------------------------------------------------- */
.hero__frame { position: relative; overflow: hidden; }
.hero__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 45%;
  /* Sem will-change: manteria a imagem numa camada de composição permanente,
     o que altera a filtragem em repouso. O translate3d do parallax já promove
     a camada quando ela é de facto precisa. */
}
/* Só o suficiente para o topo assentar sob o cabeçalho. */
/* --- Pré-visualizações do painel ----------------------------------------
   A primeira coleção é a base; as seguintes entram por cima em fundido, uma
   sobre a outra (z-index 1 e 2, postos pelo site.js). É isto que dá razão de
   ser à divisão da capa: o índice não é uma lista, é o comando do painel. */
.hero__shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .8s var(--ease);
}
.hero__shot.is-on { opacity: 1; }

/* Nome da coleção em curso, no pé do painel. */
.hero__caption {
  position: absolute;
  right: clamp(16px, 2vw, 28px);
  bottom: clamp(14px, 1.8vh, 22px);
  z-index: 4;
  margin: 0;
  overflow: hidden;
  font-size: 10px;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--paper-hi);
  text-shadow: 0 1px 16px rgba(20, 16, 13, .75);
}
.hero__caption span {
  display: block;
  transform: translateY(110%);
  transition: transform .5s var(--ease);
}
.hero__caption.is-on span { transform: none; }

.hero__veil {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: linear-gradient(180deg, rgba(46, 34, 25, .3) 0%, rgba(46, 34, 25, 0) 34%);
}

/* O sinal de scroll vive sobre o painel, não sobre o papel: no papel chocaria
   com a última linha do índice. */
.hero__cue {
  position: absolute;
  left: 50%;
  bottom: 16px;
  z-index: 4;
  width: 1px;
  height: 42px;
  background: linear-gradient(180deg, rgba(251, 247, 242, 0), rgba(251, 247, 242, .9));
  animation: cueDrift 2.6s ease-in-out infinite;
}

/* Em ecrãs estreitos a dobra passa a horizontal: painel em cima, papel a seguir. */
@media (max-width: 900px) {
  .hero {
    grid-template-columns: 1fr;
    grid-template-rows: 38vh 1fr;
    height: auto;
    min-height: 100vh;
  }
  .hero__frame { grid-row: 1; }
  .hero__body { padding-top: clamp(32px, 5vh, 48px); }
  .hero__cue { display: none; }
}

/* --- Retrato do Sobre ---------------------------------------------------- */
.portrait {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  background: var(--paper-warm);
}
.portrait img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* --- Rodapé -------------------------------------------------------------- */
.site-footer {
  background: var(--ink);
  color: var(--paper);
  padding: clamp(70px, 12vh, 150px) var(--pad-x) 44px;
}
.site-footer__dot { color: var(--accent); }
/* O rodapé inverte o fundo, por isso as primitivas de texto trocam de tom. */
.site-footer .lede { color: rgba(250, 246, 241, .82); }
.site-footer .body { color: rgba(250, 246, 241, .62); }
.site-footer .note { color: rgba(250, 246, 241, .5); }
.site-footer__base {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 24px;
  justify-content: space-between;
  font-size: 10px;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: rgba(250, 246, 241, .45);
}


/* 8. INTERAÇÕES ============================================================ */

/* --- Foco visível -------------------------------------------------------- */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}
.hero :focus-visible,
.site-footer :focus-visible { outline-color: var(--accent-soft); }

/* O rodapé recebe o foco quando se salta para o formulário, mas não é um
   controlo: contorná-lo de latão desenharia uma caixa à volta de meio ecrã.
   Quem indica que se chegou é a própria página ter ido lá ter. */
.site-footer:focus-visible { outline: none; }

/* --- Cortina de abertura -------------------------------------------------- */
/* Só na primeira visita de cada sessão. Além do efeito, tapa a troca de fontes
   (font-display: swap) que de outro modo se vê no primeiro carregamento.
   Tudo está atrás da classe .has-overture, posta pelo script inline do <head>.
   A classe do <html> e a do painel TÊM de ter nomes diferentes: com o mesmo
   nome, a regra .overture{display:none} apanhava também o <html> e apagava
   a página inteira.
   sem JavaScript o painel fica em display:none e nunca cobre nada.           */

/* Ancorado em `body >` de propósito: assim esta regra nunca pode apanhar o
   elemento <html> e apagar a página inteira, mesmo que alguém volte a pôr
   uma classe com este nome na raiz. */
body > .overture { display: none; }

html.has-overture .overture {
  display: grid;
  position: fixed;
  inset: 0;
  z-index: 100;
  place-content: center;
  justify-items: center;
  gap: 22px;
  background: var(--paper);
  visibility: visible;
  transform: translateY(0);
  transition: transform .62s cubic-bezier(.76, 0, .24, 1), visibility 0s .62s;
}

/* O painel nunca é removido do DOM — sai de cena e deixa de existir para o
   rato e para o teclado. Assim funciona mesmo que só a rede de segurança
   do script inline chegue a correr. */
html.has-overture.has-overture-done .overture {
  transform: translateY(-100%);
  visibility: hidden;
  pointer-events: none;
}

.overture__name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(20px, 2.4vw, 34px);
  letter-spacing: .18em;
  text-indent: .18em;      /* compensa o espaço que o tracking deixa no fim */
  color: var(--ink);
  animation: riseIn .55s var(--ease) .15s both;
}

.overture__rule {
  width: clamp(120px, 14vw, 190px);
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  animation: drawRule .5s var(--ease) .4s both;
}

/* O nome e o filete saem à frente do painel, não com ele. */
html.has-overture.has-overture-done .overture__name,
html.has-overture.has-overture-done .overture__rule {
  opacity: 0;
  transition: opacity .2s linear;
}

/* O hero já anima sozinho ao carregar. Por trás da cortina essa entrada
   passaria despercebida, por isso fica em pausa até o painel começar a subir
   — o nome sobe *com* a revelação. */
/* O .hero__rule está aqui por uma razão que não é óbvia: o animation-play-state
   não é herdado. Sem o nomear, o filete desenhava-se atrás da cortina e chegava
   ao ecrã já feito, enquanto tudo à volta ainda estava por entrar. */
html.has-overture:not(.has-overture-done) .hero__title,
html.has-overture:not(.has-overture-done) .hero__top,
html.has-overture:not(.has-overture-done) .hero__rule,
html.has-overture:not(.has-overture-done) .hero__lede,
html.has-overture:not(.has-overture-done) .hero__index { animation-play-state: paused; }

/* --- Saltar para o conteúdo ---------------------------------------------- */
.skip-link {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 90;
  padding: 11px 18px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  transform: translateY(-200%);
  transition: transform .3s var(--ease);
}
.skip-link:focus-visible { transform: none; color: var(--paper); }

/* --- Índice lateral ------------------------------------------------------ */
.index {
  position: fixed;
  top: 50%;
  right: clamp(14px, 2vw, 26px);
  z-index: 35;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 0;
  margin: 0;
  list-style: none;
  opacity: 0;
  transition: opacity .5s var(--ease);
}
.index.is-ready { opacity: 1; }

.index__link {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  color: inherit;
  cursor: pointer;
  background: none;
  border: 0;
  padding: 3px 0;
  font: inherit;
}
/* O nome só aparece ao apontar ou ao receber foco — em repouso ficam apenas os
   traços. Vem numa cápsula igual à do cabeçalho para se ler sobre qualquer
   imagem por baixo. */
.index__label {
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(250, 246, 241, .92);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-size: 9px;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  opacity: 0;
  transform: translateX(6px);
  transition: opacity .3s var(--ease), transform .3s var(--ease);
  pointer-events: none;
}
.index__tick {
  display: block;
  width: 18px;
  height: 1px;
  background: currentColor;
  color: var(--ink);
  opacity: .28;
  transition: opacity .4s var(--ease), background-color .4s var(--ease);
}
.index__link:hover .index__label,
.index__link:focus-visible .index__label { opacity: 1; transform: none; }
/* A largura do traço passou a ser conduzida pelo scroll, em transform — ver
   «Índice lateral magnético», mais abaixo. Aqui só se mexe na cor. */
.index__link:hover .index__tick { opacity: .6; }

.index__item.is-active .index__tick { opacity: 1; background: var(--accent); }

/* Cada traço inverte consoante o que tem por trás, não a lista inteira.
   A proximidade continua a mandar na opacidade também aqui — sobre escuro
   parte-se é de um pouco mais alto, que é o que a inversão pede. */
.index__item.on-dark .index__tick { color: var(--paper); opacity: calc(.34 + var(--near, 0) * .56); }
.index__item.on-dark.is-active .index__tick { background: var(--accent-soft); }
.index__item.on-dark .index__link:hover .index__tick { opacity: 1; }

/* Barra de progresso — substitui o índice em ecrãs estreitos. */
.progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 100%;
  z-index: 45;
  transform: scaleX(var(--p, 0));
  transform-origin: 0 50%;
  background: var(--accent);
  opacity: 0;
  transition: opacity .4s var(--ease);
}
.progress.is-ready { opacity: .85; }

/* O índice só aparece quando há margem lateral que o acomode sem tocar no
   conteúdo; abaixo disso fica só a barra de progresso. */
@media (max-width: 1100px) {
  .index { display: none; }
}
@media (min-width: 1101px) {
  .progress { display: none; }
}

/* --- Imagens ampliáveis -------------------------------------------------- */
[data-zoom] {
  cursor: zoom-in;
  transition: opacity .45s var(--ease), filter .45s var(--ease);
}
[data-zoom]:hover { opacity: .88; }

/* --- Lightbox ------------------------------------------------------------ */
.lightbox {
  width: 100vw;
  max-width: 100vw;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  color: var(--paper);
  /* O véu vive no próprio diálogo (que ocupa o ecrã inteiro) e não só no
     ::backdrop — assim não depende do suporte do pseudo-elemento. */
  background: rgba(20, 16, 13, .94);
}
.lightbox::backdrop { background: rgba(20, 16, 13, .94); }

/* Três linhas: palco, legenda, fita. A fita e a legenda são linhas reais e
   não sobreposições, para a imagem nunca ficar por baixo delas.
   O [open] é obrigatório: sem ele este display:grid anularia o display:none
   que o browser dá a um <dialog> fechado, e o diálogo passaria a ocupar um
   ecrã inteiro de vazio no fim da página. */
.lightbox[open] {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto auto;
}

.lightbox__stage {
  position: relative;
  display: grid;
  /* minmax(0, 1fr) e não auto: com a linha a dimensionar-se ao conteúdo, o
     max-height: 100% da imagem media-se contra si próprio e não limitava nada. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  place-items: center;
  padding: clamp(44px, 6vh, 76px) clamp(16px, 8vw, 110px) clamp(8px, 1.6vh, 18px);
}
.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  cursor: zoom-out;
}

.lightbox__bar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding: 0 clamp(16px, 4vw, 40px) 12px;
  pointer-events: none;
}
.lightbox__caption {
  margin: 0;
  max-width: 60ch;
  font-size: 12px;
  font-weight: 300;
  line-height: 1.6;
  letter-spacing: .04em;
  color: rgba(250, 246, 241, .82);
}
.lightbox__count {
  flex: none;
  font-size: 10px;
  font-weight: 300;
  letter-spacing: .24em;
  color: rgba(250, 246, 241, .55);
  font-variant-numeric: tabular-nums;
}

.lightbox__btn {
  position: absolute;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border: 1px solid rgba(250, 246, 241, .22);
  border-radius: 999px;
  background: rgba(20, 16, 13, .35);
  color: var(--paper);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.lightbox__btn:hover { background: rgba(250, 246, 241, .14); border-color: rgba(250, 246, 241, .45); }
.lightbox__btn--close { position: fixed; top: clamp(14px, 3vh, 26px); right: clamp(16px, 4vw, 40px); }
.lightbox__btn--prev  { top: 50%; left: clamp(10px, 2vw, 28px); transform: translateY(-50%); }
.lightbox__btn--next  { top: 50%; right: clamp(10px, 2vw, 28px); transform: translateY(-50%); }

/* --- Fita de miniaturas --------------------------------------------------- */
/* Reutiliza o src que já está em cache — não custa um único pedido de rede. */
.lightbox__strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  scroll-behavior: smooth;
  padding: 0 clamp(16px, 4vw, 40px) clamp(14px, 2.6vh, 24px);
}
.lightbox__strip::-webkit-scrollbar { display: none; }
.lightbox__strip[hidden] { display: none; }

.lightbox__thumb {
  position: relative;
  flex: none;
  width: 52px;
  height: 66px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  opacity: .34;
  transition: opacity var(--dur) var(--ease);
}
.lightbox__thumb img { width: 100%; height: 100%; object-fit: cover; }
.lightbox__thumb:hover { opacity: .7; }
.lightbox__thumb.is-current { opacity: 1; }
.lightbox__thumb.is-current::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 1px;
  background: var(--accent-soft);
}

/* A moldura só entra depois de a imagem assentar: primeiro a peça, depois a
   ficha. A classe is-ready é posta pelo script quando a viagem termina. */
.lightbox__bar,
.lightbox__strip,
.lightbox__btn { opacity: 0; transition: opacity .32s var(--ease); }
.lightbox.is-ready .lightbox__btn { opacity: 1; }
.lightbox.is-ready .lightbox__bar,
.lightbox.is-ready .lightbox__strip { opacity: 1; transition-delay: .1s; }
.lightbox.is-ready .lightbox__btn[disabled] { opacity: .25; cursor: default; }

@media (max-width: 640px) {
  .lightbox__btn--prev,
  .lightbox__btn--next { display: none; }   /* no telemóvel navega-se com o dedo */
  .lightbox__stage { padding: 58px 12px 10px; }
  .lightbox__thumb { width: 42px; height: 54px; }
}

/* --- Revelação ao scroll -------------------------------------------------- */
/* A classe .js-reveal só é posta no <html> pelo próprio script. Sem JS,
   nada disto se aplica e a página aparece inteira, como sempre.             */
.js-reveal [data-reveal],
.js-reveal [data-reveal-group] > * { opacity: 0; }

.js-reveal [data-reveal].is-in,
.js-reveal [data-reveal-group] > .is-in {
  animation: riseIn .85s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 65ms);
}

/* Um bloco alcançado a subir entrava na mesma de baixo, contra o sentido da
   leitura — era o que denunciava a animação como enlatada. Agora vem do lado
   de onde o olho vem. */
.js-reveal [data-reveal].is-in.from-above,
.js-reveal [data-reveal-group] > .is-in.from-above {
  animation-name: dropIn;
}


/* --- Deriva das imagens --------------------------------------------------- */
/* As faixas a sangrar já derivavam; o resto da página não. O que lhes faltava
   era folga: uma .frame é a própria caixa, sem nada por onde deslizar. O
   panes() do site.js embrulha cada uma num .pane que reproduz exactamente o
   que a .bleed-row faz — recorte fixo por fora, imagem maior por dentro.
   Sem JavaScript nenhum .pane chega a existir e as imagens ficam como estavam. */
/* display:block não é detalhe: um <span> inline ignora o aspect-ratio, e o
   recorte inteiro deixava de existir fora de uma grelha. */
.pane {
  display: block;
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--frame-ar, 3 / 4);
}
/* O recorte passou para o .pane: a imagem larga a proporção que tinha e volta
   a ser só conteúdo, mais alta do que a janela que a mostra. 110% chega: a
   deriva aqui conta-se em pixéis, não em ecrãs como nas faixas, e folga a mais
   é área descodificada a troco de nada. */
.pane > .frame,
.js-drift .portrait img {
  position: absolute;
  inset: -5% 0;
  width: 100%;
  height: 110%;
  aspect-ratio: auto;
  transform: translate3d(0, calc((var(--p, .5) - .5) * var(--travel, 12px) * var(--lean, 1) * -1), 0);
}
/* Atrás de .js-drift porque o .portrait existe no HTML: sem JavaScript a folga
   seria um recorte a mais, sem nada que o justificasse. Os .pane não precisam
   da guarda — só existem se este ficheiro correr. */
.js-drift .portrait { overflow: hidden; }

/* A amplitude vem em pixéis, escrita pelo panes() a partir da altura de cada
   moldura. Em percentagem, uma moldura de 420px derivava o dobro de uma de
   210px — e o que se pediu foi uma amplitude, não uma proporção.
   O desencontro entre colunas fica aqui, que é onde se lê a composição: a
   uníssono lia-se o ecrã a abanar. */
.pane:nth-child(odd) > .frame { --lean: 1.3; }
.pane:nth-child(3n)  > .frame { --lean: .72; }

/* A imagem passou a transbordar o recorte, e o contorno de foco dela ficava
   cortado ao meio. Quem o mostra passa a ser o recorte, que é afinal a forma
   que se vê. */
.pane:has(:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.pane > .frame:focus-visible { outline: none; }


/* --- Filetes que se desenham ---------------------------------------------- */
/* «A estrutura é expressa inteiramente por filetes de 1px» — e o keyframe para
   os desenhar já existia, a servir só a cortina de abertura. Aqui ganha o
   alcance que merecia: cada filete traça-se ao entrar, como a régua de uma
   prova a ser puxada.
   Ancorado sempre na cadeia .is-in, nunca no estado base: um filete que venha
   a nascer fora de um [data-reveal] tem de aparecer na mesma, não ficar
   invisível para sempre à espera de uma classe que nunca chega. */
.js-reveal .is-in .hairline,
.js-reveal .is-in.hairline {
  transform-origin: left center;
  animation: drawRule .7s var(--ease) .18s both;
}

/* Os filetes que são bordas não se animam — uma borda não tem transform. A
   borda fica no sítio a ocupar o espaço (por isso o layout não se mexe) e
   perde a cor; quem se vê é um traço de 1px por cima, esse sim desenhado. */
.js-reveal .entries,
.js-reveal .entry,
.js-reveal .card__head,
.js-reveal .flink { position: relative; }

.js-reveal .entries    { border-top-color: transparent; }
.js-reveal .entry,
.js-reveal .card__head { border-bottom-color: transparent; }
.js-reveal .flink      { border-top-color: transparent; border-bottom-color: transparent; }

.js-reveal .entries::before,
.js-reveal .entry::after,
.js-reveal .card__head::after,
.js-reveal .flink::before,
.js-reveal .flink::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  transform: scaleX(0);
  transform-origin: left center;
}
.js-reveal .entries::before { top: -1px;    background: var(--rule); }
.js-reveal .entry::after    { bottom: -1px; background: var(--rule); }
.js-reveal .card__head::after { bottom: -1px; background: var(--rule-card); }
.js-reveal .flink::before   { top: -1px;    background: var(--rule-inv); }
.js-reveal .flink::after    { bottom: -1px; background: var(--rule-inv); }

.js-reveal .is-in .entries::before,
.js-reveal .is-in .entry::after,
.js-reveal .is-in .card__head::after,
.js-reveal .is-in .flink::before,
.js-reveal .is-in .flink::after,
.js-reveal .is-in.entry::after,
.js-reveal .is-in.flink::before,
.js-reveal .is-in.flink::after {
  animation: drawRule .8s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 70ms + 120ms);
}


/* --- Títulos palavra a palavra -------------------------------------------- */
/* Só transform, nunca opacidade: o quadro à volta já está a fazer o seu
   fundido e dois fundidos sobrepostos leriam a papa. A cadência é de
   composição tipográfica — as palavras assentam, não desfilam.
   Sem máscara de propósito: a esta escala, e com line-height abaixo de 1,
   um overflow cortaria descendentes e o remate do itálico do Cormorant. */
.js-words .word { display: inline-block; }

.js-words .is-in .display .word {
  animation: riseWord .8s var(--ease) both;
  animation-delay: calc(var(--w, 0) * 42ms);
}


/* --- Etiquetas e linhas de percurso em cascata ---------------------------- */
/* Entravam em bloco por serem filhas de um só [data-reveal]. Passam a ser
   dadas uma a uma, com o mesmo --i que a revelação já usa nos grupos.
   Só transform, pela mesma razão das palavras: o fundido é do quadro. */
.js-reveal .is-in .pill,
.js-reveal .is-in .entry {
  animation: dealIn .7s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 55ms);
}


/* --- Velocidade do scroll ------------------------------------------------- */
/* Um único valor global (--v, 0 a 1) escrito pelo cadence(). Usado com muita
   parcimónia: o suficiente para a página ter peso e inércia, nunca ao ponto
   de se conseguir apontar o efeito. Assenta sozinho quando o scroll pára. */
/* Declarar o tipo faz o --v valer 0 antes de o cadence() escrever a primeira
   vez — sem isto, as contas abaixo ficavam inválidas até ao primeiro scroll. */
@property --v { syntax: "<number>"; inherits: true; initial-value: 0; }

.eyebrow { letter-spacing: calc(.34em + var(--v, 0) * .012em); }
.label   { letter-spacing: calc(.3em + var(--v, 0) * .012em); }
.pane > .frame,
.js-drift .portrait img { scale: 1 calc(1 + var(--v, 0) * .014); }


/* --- Hero conduzido pelo ponteiro ----------------------------------------- */
/* O painel de imagem responde ao rato — mas quem se move é a fotografia, não
   a moldura: se fosse a moldura a derivar, o recorte ia com ela e descobria
   o papel na borda. Daí a folga negativa, a mesma ideia das faixas.
   Sem will-change, por decisão da casa (ver .hero__img): o translate3d já
   promove a camada enquanto ela é precisa. */
.js-pointer .hero__img,
.js-pointer .hero__shot {
  inset: -14px;
  width: calc(100% + 28px);
  height: calc(100% + 28px);
}

/* Uma só transformação para as duas derivas — a do scroll (--py/--ps, escritas
   pelo parallax) e a do ponteiro (--mx/--my). Compostas aqui, em vez de
   disputarem a propriedade uma à outra a partir de dois sítios. */
.hero__img,
.hero__shot {
  transform-origin: bottom center;
  transform: translate3d(calc(var(--mx, 0) * -10px),
                         calc(var(--py, 0px) + var(--my, 0) * -8px), 0)
             scale(var(--ps, 1));
}

/* O nome aperta-se à medida que o hero sai — é a mesma rampa óptica que o
   .display já faz com o tamanho, agora ao longo do scroll. letter-spacing e
   não transform: assim não disputa nada com o riseIn de entrada. */
.hero__title { letter-spacing: calc(clamp(-3.4px, calc(1.53px - .0233em), .46px) + var(--hero-track, 0em)); }


/* --- Índice lateral magnético --------------------------------------------- */
/* Os traços deixaram de ser ligado/desligado: cada um sabe a que distância
   está da leitura e estica-se na proporção disso. Por transform e não por
   width — é escrito a cada frame, e uma largura a mudar obrigaria o browser
   a refazer a conta do layout sessenta vezes por segundo. */
/* Sem transição na geometria: o --near é escrito a cada frame, e uma transição
   de 400ms por cima disso arrastaria sempre atrás do scroll, a borrar. A cor é
   que continua a precisar dela — muda por classe, não por frame. */
.index__tick {
  width: 32px;
  transform: scaleX(calc(.56 + var(--near, 0) * .44));
  transform-origin: right center;
  opacity: calc(.26 + var(--near, 0) * .64);
  transition: background-color .4s var(--ease);
}
.index__link:hover .index__tick { opacity: 1; }
.index__item.is-active .index__tick { width: 32px; background: var(--accent); }


/* --- Formulário ----------------------------------------------------------- */
/* O envio deixou de sair da página. Enquanto vai, o formulário afasta-se sem
   desaparecer — continua legível, só deixa de aceitar mãos. */
.form.is-sending .field { opacity: .5; pointer-events: none; }
.form.is-sending .field,
.form .field { transition: opacity var(--dur) var(--ease); }

/* A mensagem ocupa o lugar do formulário com a altura que ele tinha: o rodapé
   não pode encolher debaixo dos pés de quem acabou de escrever. */
.form__done {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 14px;
  min-height: var(--done-h, auto);
  animation: riseIn .6s var(--ease) both;
}
.form__done .display {
  font-size: clamp(30px, 3.4vw, 50px);
  line-height: 1.06;
  color: var(--paper);
}
.form__done .body { color: rgba(250, 246, 241, .68); }


/* 9. MOVIMENTO REDUZIDO ==================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .js-reveal [data-reveal],
  .js-reveal [data-reveal-group] > * { opacity: 1; }

  /* A folga que serve a deriva deixa de ter razão de ser: a imagem volta a
     assentar exactamente no seu recorte. */
  .pane > .frame,
  .js-drift .portrait img { transform: none; scale: none; inset: 0; height: 100%; }

  /* Os filetes que se desenham chegam feitos. O scaleX do estado base tem de
     ser desfeito à mão — sem isto ficavam a zero, ou seja, invisíveis. */
  .js-reveal .entries::before,
  .js-reveal .entry::after,
  .js-reveal .card__head::after,
  .js-reveal .flink::before,
  .js-reveal .flink::after { transform: none; }

  .hero__img,
  .hero__shot { transform: none; }
  .hero__title { letter-spacing: clamp(-3.4px, calc(1.53px - .0233em), .46px); }

  /* Sem velocidade de scroll a alimentar coisa nenhuma. */
  .eyebrow { letter-spacing: .34em; }
  .label   { letter-spacing: .3em; }
}


/* Sem isto o 100vw das faixas a sangrar inventa uma barra horizontal quando a
   vertical ocupa largura. clip em vez de hidden: não cria contexto de scroll,
   por isso o position: sticky do resto da página continua a funcionar. */
body { overflow-x: clip; }
