/* ==========================================================================
   ROLAGEM SINCRONIZADA — prefixo sync-
   Quatro blocos de texto altos rolando de um lado, um painel de foto pinado
   do outro, e um fio de progresso entre os dois.

   Tokens usados:
     --nav-h                 de onde sai o topo do painel pinado
     --ease                  curva única do projeto
     --display --sans        tipografia (número e título / texto)
     --accent                trilho preenchido, ponto ativo, número do passo ativo
     --fg --fg-2 --fg-muted  passo ativo / corpo do ativo / passos em repouso
     --rule --rule-2         trilho vazio e ponto em repouso; fundo do painel
     --bg                    halo que separa o ponto do fio
     --dark-rgb              véu da legenda sobre a foto
     --light                 texto da legenda (ilha escura) e trilho sobre foto
     --radius-media          canto do painel
     --radius-pill           os pontos do trilho

   O COMPONENTE NÃO DECLARA ESCOPO DE TOM. A seção declara (.sec-dark no
   markup, .sec-deep ou .sec-paper valem igual) e tudo aqui herda. A única
   coisa que não herda é o que está DENTRO da foto: a legenda lê --light, como
   manda o §6 do contrato para texto sobre imagem, porque ali a ilha é escura
   independentemente do tom da seção.

   O QUE FAZ O PAINEL GRUDAR NÃO É SÓ O position:sticky. Sticky move o
   elemento DENTRO do bloco que o contém, nunca para fora dele — então o
   painel precisa ter altura MENOR que a área de grid dele. Quem garante isso
   são duas coisas juntas, o align-items:start e a altura explícita do painel,
   e as quatro combinações foram medidas nesta dobra, a 1440x900, comparando o
   topo do painel em scrollY 1200 e 1600:

     start   + altura   ->  108px e 108px       cravado, é o que se quer
     stretch + altura   ->  108px e 108px       a altura definida já vence o
                                                stretch; funciona por sorte
     start   + sem altura -> 108px, mas 0px de altura: as fotos são absolutas
                                                e o painel colapsa
     stretch + sem altura -> -807px e -1207px, painel de 2 520px: ele vira a
                                                coluna inteira e desce junto
                                                com a página

   Ou seja: o alinhamento sozinho não basta e a altura sozinha não basta. As
   duas ficam, e a segunda linha da tabela é o motivo — quem for adaptar mexe
   numa e não na outra, e a que sobra tem de segurar.

   O QUE MATA O STICKY é overflow diferente de visible em QUALQUER ancestral:
   ele vira o contêiner de rolagem do painel, e como esse contêiner não rola,
   o painel nunca gruda. Medido nesta dobra, nos mesmos dois pontos de
   rolagem: um overflow:hidden na seção derruba o painel para -807px e
   -1207px; no .sync-grid, para -699px e -1099px. Por isso não há
   overflow:hidden em lugar nenhum daqui — nem para conter o blur das fotos,
   que já é contido pelo próprio painel.

   O overflow-x:hidden que o core/base.css põe no <body> NÃO conta, e é bom
   saber por quê antes de tentar "consertar": com o <html> em visible, o
   navegador propaga o overflow do <body> para a viewport e o próprio <body>
   volta a se comportar como visible. Medido: computed style do body sai
   hidden/auto e o painel continua cravado em 108px.
   ========================================================================== */

.sync{
  /* Altura de cada bloco de texto. É o que dá tempo de leitura antes da
     próxima troca de foto — e é o que garante que o passo seja mais alto que
     a faixa de 10% do observador (ver --sync-band). Abaixo de ~20vh a dobra
     vira um flip-book. */
  --sync-step-h:70vh;

  /* Altura do painel. O teto em px existe para telas muito altas: 70vh de um
     monitor de 1440 seriam 1008px de foto, mais alto que qualquer retrato
     enquadrado bem. */
  --sync-panel-h:min(70vh,640px);

  /* Onde o painel gruda. Lê a altura do menu fixo do projeto e acrescenta um
     respiro. A conta que precisa fechar é --nav-h + 24px + --sync-panel-h <=
     altura da tela; com os padrões daqui isso vale para telas a partir de
     360px de altura. */
  --sync-top:calc(var(--nav-h) + 24px);

  /* Duração da fusão entre uma foto e a próxima. A escala leva 1,5x isso,
     para a imagem continuar assentando depois de já estar nítida. */
  --sync-fade:.9s;

  /* Espessura do fio, e também a largura da coluna do meio no grid — os dois
     saem daqui para não poderem divergir. Em 0px a dobra fica sem trilho e o
     que sobra é um corredor de dois gaps entre as colunas (ver a variação
     "sem trilho" na ficha). */
  --sync-rail-w:2px;

  /* Onde COMEÇA a faixa de leitura do observador, em % da altura da tela.
     A faixa tem sempre 10% de altura: 45 quer dizer de 45% a 55%, o meio
     exato da tela. Lido pelo script — número puro, sem unidade. No celular o
     painel ocupa o topo, e a faixa desce (ver a media query do fim). */
  --sync-band:45;

  /* Fatia do trilho por passo: 100% dividido pelo número de passos. O script
     reescreve a partir da contagem real do DOM, então este valor só vale
     enquanto o JS não rodou (ou se ele não estiver na página). Multiplicação
     e não divisão de propósito: calc(N * 25%) é sempre válido, calc(100% /
     var(--x)) depende de o navegador aceitar divisão por variável. */
  --sync-slot:25%;

  /* Progresso do trilho, de 0 a 1. Escrito pelo script a cada quadro de
     rolagem. Sem JS fica em 0 e o trilho aparece vazio — o que é honesto:
     não há progresso medido. */
  --sync-p:0;

  /* Os dois de baixo só valem abaixo de 900px, onde o painel vai para o topo.
     Ficam aqui, na raiz, para serem ajustáveis sem entrar na media query.
     Site com menu fixo também no celular sobe --sync-top-m para a altura
     dele; --sync-panel-m é o quanto da tela a foto ocupa lá. */
  --sync-panel-m:38vh;
  --sync-top-m:0px;
}

/* ---------------------------------------------------------------- cabeçalho */
/* Alinhado à esquerda, e não .sec-head do core: .sec-head é cabeçalho
   CENTRADO, e aqui ele abre uma coluna de leitura que também é à esquerda.
   Centrar o título e alinhar o texto à esquerda logo abaixo faz a dobra
   começar torta. */
.sync-head{margin-bottom:clamp(40px,6vh,72px)}

/* ---------------------------------------------------------------- grid */
/* Três colunas: texto, o fio de 2px, painel. O gap vale dos dois lados do
   fio, então ele nasce no meio de um corredor de 2*gap + 2px — é o que o faz
   ler como divisória e não como borda de uma das colunas.
   minmax(0,1fr) e não 1fr: sem o minmax, uma palavra longa no parágrafo
   empurra a coluna de texto e o painel encolhe. */
.sync-grid{
  display:grid;
  grid-template-columns:minmax(0,1fr) var(--sync-rail-w) minmax(0,1fr);
  gap:clamp(28px,4vw,64px);
  align-items:start;
}

/* Inverter os lados: painel à esquerda, texto à direita. O fio fica onde
   está — só as duas pontas trocam de ordem. Mesmo gancho e mesma convenção
   do conteudo/apresentacao-foto-alternada e do conteudo/imagem-lista-numerada,
   para as dobras de uma mesma página virarem todas pelo mesmo nome. */
.sync.is-flipped .sync-panel{order:-1}
.sync.is-flipped .sync-steps{order:1}

/* ---------------------------------------------------------------- passos */
/* É um <ol>: a ordem é conteúdo, não decoração. O reset do core zera as duas. */
.sync-steps{min-width:0}

/* O texto fica CENTRADO na altura do passo, e não no topo dele. É o que faz
   o bloco cruzar a faixa de leitura no meio da tela ao mesmo tempo em que a
   pessoa está lendo, em vez de trocar a foto uma tela antes. */
.sync-step{
  min-height:var(--sync-step-h);
  display:flex;align-items:center;
}

.sync-step-body{
  display:flex;flex-direction:column;align-items:flex-start;
  gap:clamp(12px,1.4vw,18px);
}

/* Os três elementos do passo nascem em tinta de apoio e acendem juntos. A
   transição é de COR e só de cor: não há deslocamento aqui, e é por isso que
   ela continua valendo com movimento reduzido — apagar e acender texto não é
   movimento, é o próprio estado do componente. */
.sync-num{
  font-family:var(--display);font-weight:700;
  font-size:12px;letter-spacing:.16em;line-height:1;
  text-transform:uppercase;
  color:var(--fg-muted);
  transition:color .5s var(--ease);
}
.sync-title{
  font-family:var(--display);font-weight:700;
  font-size:clamp(24px,2.4vw,34px);line-height:1.18;letter-spacing:-.022em;
  color:var(--fg-muted);
  transition:color .5s var(--ease);
}
/* 54ch segura a linha antes de a leitura cansar, mesmo em tema de coluna
   larga; na coluna padrão (~490px) o parágrafo enche a metade e para. */
.sync-text{
  font-family:var(--sans);
  font-size:17px;line-height:1.62;
  max-width:54ch;
  color:var(--fg-muted);
  transition:color .5s var(--ease);
}

/* O passo ativo não é um bloco monocromático em --fg: o título vai a --fg, o
   corpo a --fg-2 e o número à cor de marca. É a mesma hierarquia do
   h2.sec-title + .sec-lead do core — texto longo em --fg cheio pesa. */
.sync-step.is-active .sync-num{color:var(--accent)}
.sync-step.is-active .sync-title{color:var(--fg)}
.sync-step.is-active .sync-text{color:var(--fg-2)}

/* ---------------------------------------------------------------- trilho */
/* align-self:stretch vence o align-items:start do grid, e é o único item que
   quer a linha inteira: o fio tem de ser tão alto quanto a pilha de passos,
   porque é a régua deles. */
.sync-rail{
  position:relative;
  width:var(--sync-rail-w);
  align-self:stretch;
  background:var(--rule);
}

/* Preenchimento contínuo. Sem transition de propósito: o valor é reescrito a
   cada quadro de rolagem, e uma transição por cima faria a barra chegar
   atrasada em relação ao dedo — um indicador de posição que mente. */
.sync-rail-fill{
  position:absolute;inset:0;
  background:var(--accent);
  transform:scaleY(var(--sync-p,0));
  transform-origin:top;
  will-change:transform;
}

/* As estações. Cada ponto no centro da fatia do seu passo, que é exatamente
   onde o preenchimento chega quando aquele passo entra na faixa de leitura —
   os dois indicadores concordam por construção, não por ajuste.
   O halo em --bg é o que separa o ponto do fio; sem ele o ponto em repouso
   some dentro da parte já preenchida do trilho. */
.sync-dot{
  position:absolute;left:50%;
  top:calc((var(--sync-i,0) + .5) * var(--sync-slot));
  width:7px;height:7px;
  border-radius:var(--radius-pill);
  background:var(--rule-2);
  box-shadow:0 0 0 3px var(--bg);
  transform:translate(-50%,-50%);
  transition:background-color .45s var(--ease),transform .45s var(--ease);
}
.sync-dot.is-on{background:var(--accent);transform:translate(-50%,-50%) scale(1.55)}
/* O ponto muda de tamanho, e tamanho é deslocamento: some com movimento
   reduzido. A cor fica — é ela que diz onde a pessoa está. */
@media(prefers-reduced-motion:reduce){
  .sync-dot{transition:background-color .2s linear}
  .sync-dot.is-on{transform:translate(-50%,-50%)}
}

/* ---------------------------------------------------------------- painel */
/* margin:0 porque é um <figure> e o navegador dá 1em de cada lado.
   overflow:hidden AQUI é obrigatório e é seguro: recorta o blur das fotos
   que entram (que sangra ~3x o raio para fora da caixa) sem virar contêiner
   de rolagem de nenhum ancestral — o sticky é DESTE elemento, e o overflow
   de um elemento não afeta a própria fixação dele.
   O fundo em --rule evita o buraco de meio segundo entre o layout e a
   decodificação da primeira foto: as quatro são absolutas e não sustentam
   altura nenhuma. */
.sync-panel{
  position:sticky;top:var(--sync-top);
  height:var(--sync-panel-h);
  margin:0;
  overflow:hidden;
  border-radius:var(--radius-media);
  background:var(--rule);
  z-index:2;
}

/* As quatro no mesmo quadro; só a .is-on aparece. A que entra carrega blur e
   um resto de escala indo a zero, a que sai faz o caminho inverso: em nenhum
   quadro existem duas fotos nítidas ao mesmo tempo (contrato §5, nada entra
   seco). O recorte vem por foto, em --sync-pos no style — retrato de rua tem
   o assunto num terço, foto de mesa no meio, e cravar um valor único
   decapitaria uma das duas famílias. */
.sync-img{
  position:absolute;inset:0;
  width:100%;height:100%;object-fit:cover;
  object-position:var(--sync-pos,50% 42%);
  opacity:0;filter:blur(12px);transform:scale(1.04);
  transition:opacity var(--sync-fade) var(--ease),
             filter var(--sync-fade) var(--ease),
             transform calc(var(--sync-fade) * 1.5) var(--ease);
}
.sync-img.is-on{opacity:1;filter:blur(0);transform:scale(1)}
/* Com movimento reduzido sobra a fusão de opacidade, curta e sem curva: sem
   blur (que é uma escala disfarçada) e sem escala. Trocar em corte seco seria
   pior — um piscar é mais agressivo que um esmaecer de .3s. */
@media(prefers-reduced-motion:reduce){
  .sync-img{filter:none;transform:none;transition:opacity .3s linear}
  .sync-img.is-on{filter:none;transform:none}
}

/* Legenda DENTRO da foto, ancorada embaixo, sobre gradiente com blur
   mascarado — contrato §6. A máscara não é enfeite: sem ela o backdrop-filter
   termina em borda dura no meio da imagem e a faixa lê como adesivo. */
.sync-cap{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  margin:0;
  display:flex;
  padding:60px 22px 20px;
  background:linear-gradient(to top,
    rgba(var(--dark-rgb),.9) 0%,
    rgba(var(--dark-rgb),.5) 48%,
    transparent 100%);
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  mask-image:linear-gradient(to top,#000 0%,rgba(0,0,0,.9) 44%,transparent 100%);
  -webkit-mask-image:linear-gradient(to top,#000 0%,rgba(0,0,0,.9) 44%,transparent 100%);
}

/* Reaproveita a .pill do core: forma, respiro e tracking vêm de lá. Só o tom
   muda, porque a pílula está sobre foto e não sobre a seção — é a mesma
   solução do midia/galeria-scroll-horizontal. */
.sync-cap-pill{
  color:var(--light);
  border-color:color-mix(in srgb,var(--light) 34%,transparent);
}

/* ---------------------------------------------------------------- mobile */
/* UMA COLUNA, e os três blocos na MESMA célula do grid (grid-area:1/1).
   A sobreposição não é economia de markup: é a única forma de o painel ter
   altura de sobra para grudar. Se ele tivesse linha própria, o bloco que o
   contém seria essa linha — 38vh — e o painel sairia de cena no primeiro
   gesto, porque sticky não sai do bloco que o contém.
   Quem empurra o texto para baixo do painel é o padding-top dos passos; quem
   fica por cima é o z-index. O texto passa POR TRÁS da foto, que é o efeito. */
@media(max-width:900px){
  /* A FAIXA DE LEITURA DESCE. No desktop a zona de leitura é a tela inteira e
     o meio dela é o meio da tela — daí 45 (de 45% a 55%). Aqui o painel come
     os 34-38% de cima, e o que sobra para ler vai de ~38% a 100%, cujo meio
     é ~69%. Com a faixa no meio da TELA a foto trocava quando o texto já
     tinha passado pela zona de leitura: medido em 390x844, a troca acontecia
     com o parágrafo a ~100px acima de onde o olho está. Em 58 (de 58% a 68%)
     a foto vira enquanto se lê o bloco, que é o ponto do componente.
     Quem lê este número é o script, uma vez na carga e de novo a cada resize
     — a decisão de layout mora aqui, não lá. */
  .sync{--sync-band:58}

  .sync-head{margin-bottom:clamp(28px,5vh,44px)}

  .sync-grid{grid-template-columns:minmax(0,1fr);gap:0}

  .sync-steps{
    grid-area:1/1;
    padding-top:calc(var(--sync-top-m) + var(--sync-panel-m) + clamp(20px,5vw,32px));
  }
  /* Sem altura de tela por passo: no celular a rolagem já é longa, e 70vh por
     bloco somariam quatro telas de nada entre um texto e o outro. O respiro
     passa a ser padding, e o piso continua sendo mais alto que a faixa de 10%
     do observador — medido em 390x844: 84px de faixa contra ~330px de passo. */
  .sync-step{min-height:0;padding-block:clamp(26px,6vh,54px)}

  .sync-panel{
    grid-area:1/1;
    align-self:start;
    top:var(--sync-top-m);
    height:var(--sync-panel-m);
  }

  /* O fio vira barra no pé do painel, e o preenchimento vira scaleX. Sobre
     foto o trilho não pode ler --rule: em foto escura ele desaparece e em
     foto clara ele fica sujo. Aqui ele é uma tinta clara translúcida, que é o
     que funciona sobre qualquer imagem — mesma lógica da legenda, que lê
     --light e não --fg. */
  .sync-rail{
    grid-area:1/1;
    align-self:start;
    position:sticky;
    /* 10px acima da base da foto, e não encostado nela: os pontos têm 7px e
       ficam centrados no fio, então na base exata eles avançavam 2,5px sobre a
       primeira linha de texto que sai de baixo do painel — medido, cortavam os
       ascendentes. Recuados para dentro da imagem, a barra lê como o
       marcador de um player e o texto passa limpo por baixo. */
    top:calc(var(--sync-top-m) + var(--sync-panel-m) - 10px);
    width:auto;height:var(--sync-rail-w);
    z-index:3;
    background:color-mix(in srgb,var(--light) 22%,transparent);
  }
  .sync-rail-fill{transform:scaleX(var(--sync-p,0));transform-origin:left}
  .sync-dot{
    top:50%;
    left:calc((var(--sync-i,0) + .5) * var(--sync-slot));
    background:color-mix(in srgb,var(--light) 45%,transparent);
    box-shadow:0 0 0 3px rgba(var(--dark-rgb),.5);
  }
  .sync-dot.is-on{background:var(--accent)}

  /* 26px embaixo, e não 14: é o que abre folga entre a pílula e a barra que
     agora corre por dentro da imagem. */
  .sync-cap{padding:44px 16px 26px}
}

@media(max-width:560px){
  .sync{--sync-panel-m:34vh}
  .sync-text{font-size:16px;line-height:1.6}
  .sync-step-body{gap:10px}
}
