:root {
	--kinetic-ink: #282040;
	--kinetic-ink-soft: #3b3158;
	--kinetic-paper: #f0e8e0;
	--kinetic-paper-soft: #e4dce8;
	--kinetic-lilac: #aaa0c4;
	/* Cor de destaque nova (auditoria de design, 2026-09-03) — amarelo
	   mostarda envelhecido, não fluorescente, para não competir com o
	   indigo/lilás já estabelecido. Usado com moderação (2-3 pontos no
	   template): números dos serviços, hover dos ícones sociais do footer,
	   e o acento da citação nova na secção 3. */
	--kinetic-accent: #c9a24c;
	--kinetic-line: rgb(40 32 64 / 34%);
	--kinetic-font: 'Montserrat Variable', 'Montserrat', system-ui, sans-serif;
	--kinetic-gutter: clamp(1.25rem, 4.7vw, 5.25rem);
	--kinetic-max: 88rem;
	/* PatternBackground.astro (componente partilhado com o site principal) usa
	   --color-paper-on-ink para pintar o padrão de símbolos sobre fundo tinta.
	   KineticLayout.astro não importa global.css, por isso a variável tem de ser
	   redefinida aqui — mesmo valor de global.css:49. */
	--color-paper-on-ink: #f8f6f2;

	/* Escala de camadas fixa para conteúdo que precisa de se sobrepor a mais
	   do que a própria secção (ex.: a colagem de imagens da secção 3, que
	   deve atravessar para cima de secções vizinhas sem ser cortada). Isto é
	   uma escala à parte do z-index de cada `<section>`/`-stage` (esses
	   continuam com números avulsos — 5, 3, 4 — porque controlam a ordem das
	   costuras diagonais entre secções consecutivas, um mecanismo diferente).
	   Um elemento com --kinetic-z-media só ganha na prática se nenhum
	   ancestral seu tiver o próprio z-index mais baixo a criar um contexto de
	   empilhamento novo pelo caminho — isso prende o valor alto lá dentro,
	   como aconteceu com a colagem antes desta correção (ver
	   .kinetic-work__grid abaixo). Confirmar sempre isto ao adicionar um novo
	   elemento "flutuante". */
	--kinetic-z-bg: 0;    /* fundos e padrões presos à própria secção */
	--kinetic-z-text: 10; /* texto, botões, conteúdo normal de cada secção */
	--kinetic-z-media: 20; /* imagens decorativas que atravessam secções livremente, sempre acima de tudo o resto do conteúdo (não da navegação fixa) */
}

.kinetic-document {
	scroll-behavior: smooth;
	scroll-padding-top: 5.25rem;
	background: var(--kinetic-ink);
}

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

.kinetic-body {
	margin: 0;
	min-width: 0;
	background: var(--kinetic-ink);
	color: var(--kinetic-paper);
	font-family: var(--kinetic-font);
	line-height: 1.5;
}

.kinetic-body img { display: block; max-width: 100%; }
.kinetic-body a { color: inherit; }
.kinetic-body button { font: inherit; }
.kinetic-body :focus-visible { outline: 3px solid var(--kinetic-lilac); outline-offset: 4px; }

.kinetic-shell {
	width: min(calc(100% - (2 * var(--kinetic-gutter))), var(--kinetic-max));
	margin-inline: auto;
}

.kinetic-skip {
	position: fixed;
	top: .5rem;
	left: .5rem;
	z-index: 100;
	padding: .75rem 1rem;
	background: rgb(239 232 222 / 22%);
	color: var(--kinetic-ink);
	font-weight: 800;
	transform: translateY(-150%);
	transition: transform 180ms ease;
}

.kinetic-skip:focus { transform: translateY(0); }

.kinetic-header {
	position: fixed;
	top: 0;
	left: 0;
	z-index: 40;
	display: grid;
	grid-template-columns: auto 1fr;
	align-items: center;
	width: 100%;
	height: 5.25rem;
	padding-inline: max(var(--kinetic-gutter), calc((100vw - var(--kinetic-max)) / 2));
	background: rgb(40 32 64 / 52%);
	backdrop-filter: blur(7px);
	color: var(--kinetic-paper);
	box-shadow: 0 1px 0 rgb(240 232 224 / 12%);
	transition: background 350ms ease, backdrop-filter 350ms ease, box-shadow 350ms ease;
}

.kinetic-header.is-scrolled {
	background: rgb(40 32 64 / 68%);
	backdrop-filter: blur(14px);
	box-shadow: 0 1px 0 rgb(240 232 224 / 20%), 0 .75rem 2rem rgb(16 10 34 / 14%);
}

.kinetic-brand {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-self: start;
	width: clamp(11.75rem, 15vw, 14rem);
	padding: .25rem .45rem;
	overflow: visible;
}

.kinetic-brand img {
	width: 100%;
	height: auto;
	object-fit: contain;
	filter: brightness(0) invert(1);
}

.kinetic-header nav { justify-self: end; }
.kinetic-header nav { min-width: 0; }
.kinetic-header nav ul { display: flex; justify-content: flex-end; gap: clamp(1.6rem, 3vw, 3.35rem); margin: 0; padding: 0; list-style: none; }
.kinetic-header nav a { position: relative; display: inline-flex; align-items: center; gap: .42rem; font-size: .64rem; font-weight: 700; letter-spacing: .055em; text-decoration: none; text-transform: uppercase; }
.kinetic-nav-icon { width: .92rem; height: .92rem; flex: 0 0 auto; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; opacity: .78; transition: opacity 180ms ease, transform 180ms ease; }
.kinetic-header nav a:hover .kinetic-nav-icon, .kinetic-header nav a:focus-visible .kinetic-nav-icon { opacity: 1; transform: translateY(-1px); }

.kinetic-header nav a::after,
.kinetic-text-link::after {
	display: block;
	width: 100%;
	height: 1px;
	margin-top: .28rem;
	background: currentColor;
	content: '';
	transform: scaleX(0);
	transform-origin: right;
	transition: transform 240ms cubic-bezier(.16, 1, .3, 1);
}

.kinetic-header nav a::after { position: absolute; bottom: -.38rem; left: 0; margin-top: 0; }

.kinetic-header nav a:hover::after,
.kinetic-header nav a:focus-visible::after,
.kinetic-text-link:hover::after,
.kinetic-text-link:focus-visible::after {
	transform: scaleX(1);
	transform-origin: left;
}

.kinetic-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: .72rem;
	width: max-content;
	min-height: 2.65rem;
	padding: .62rem .78rem .62rem 1.15rem;
	border: 1px solid currentColor;
	border-radius: 999px;
	font-size: .62rem;
	font-weight: 800;
	letter-spacing: .05em;
	line-height: 1;
	text-decoration: none;
	text-transform: uppercase;
	white-space: nowrap;
	box-shadow: 0 0 0 rgb(16 10 34 / 0%);
	transition: transform 220ms cubic-bezier(.16, 1, .3, 1), box-shadow 220ms cubic-bezier(.16, 1, .3, 1), background 220ms ease, color 220ms ease;
}

.kinetic-button img { width: 1.3rem; height: 1.75rem; object-fit: contain; transition: transform 240ms cubic-bezier(.16, 1, .3, 1); }
/* translateY + sombra a crescer: só o translateY (como estava) lê-se como
   "mexe"; com a sombra lê-se como "levanta-se do papel" — mesma família de
   sombra do marquee e da colagem, para não introduzir uma linguagem nova. */
.kinetic-button:hover, .kinetic-button:focus-visible { transform: translateY(-2px); box-shadow: 0 .7rem 1.6rem rgb(16 10 34 / 22%); }
.kinetic-button:hover img, .kinetic-button:focus-visible img { transform: rotate(10deg) scale(1.08); }
.kinetic-button:active { transform: translateY(1px) scale(.98); box-shadow: 0 .1rem .4rem rgb(16 10 34 / 14%); }
.kinetic-button--outline:hover, .kinetic-button--outline:focus-visible { background: var(--kinetic-paper); color: var(--kinetic-ink); }
.kinetic-body .kinetic-button--cream { background: var(--kinetic-paper); color: var(--kinetic-ink); }
.kinetic-button--cream:hover, .kinetic-button--cream:focus-visible { background: var(--kinetic-lilac); }
.kinetic-button--outline img { filter: brightness(0) invert(1); }
.kinetic-button--outline:hover img, .kinetic-button--outline:focus-visible img { filter: none; }

.kinetic-page { --kinetic-seam-rise: 6.55vw; position: relative; background: var(--kinetic-paper); color: var(--kinetic-ink); isolation: isolate; overflow: clip; }
/* Página do portfólio: secções de projeto ~35% da altura das secções do
   resto do site (ver .kinetic-portfolio-project) — --kinetic-seam-rise é
   vw, não escala com a altura da própria secção. A mesma diagonal cheia
   (6.55vw) corta quase 2x94.32px de uma secção com só 248px, sobrando
   ~59px do lado curto — lê-se como cunha fechada, não como banda.
   Ciclo completo (2026-09-04/05): raso (2.4vw) → uniforme com o hero
   (6.55vw, "os restantes projectos têm de ficar com o mesmo angulo") →
   apanhado no resultado ("está demasiado apertado nas pontas... a
   extremidade mais estreita tem de abrir mais") → de volta ao raso, mas
   já não em toda a pilha às cegas: só o TOPO do 1º projeto continua a
   6.55vw (a única costura que tem de bater com o hero — ver --first,
   mais abaixo); todas as OUTRAS costuras da pilha (fundo do 1º, e
   topo+fundo de todos os seguintes) voltam a 2.4vw. É precisamente o que
   o cliente pediu: só as diagonais de BAIXO (entre projetos) abrem mais;
   a de cima (a que se vê primeiro, a que tinha o vinco) mantém-se igual
   ao resto do site. */
.kinetic-page.kinetic-portfolio-page { --kinetic-seam-rise: 2.4vw; }

/* Estado de repouso antes do JS (kinetic-template.ts) correr: evita um frame com
   o conteúdo do hero/reveals já visível antes da animação de entrada. O próprio
   script volta a garantir isto via gsap.set() no arranque; esta regra é só o
   backstop para o instante entre parse do CSS e execução do JS. Reduced-motion
   e <noscript> repõem a visibilidade (ver media query no fim do ficheiro / KineticLayout.astro). */
[data-hero-line], [data-hero-media], [data-kinetic-reveal], [data-value-item] { opacity: 0; }
/* Faixa de imagens do portfólio: cada célula começa fora da JANELA (não só
   fora da própria célula), do lado indicado por data-entry no elemento
   pai — CSS puro (seletor de atributo), sem depender do JS correr a tempo
   de evitar um frame com tudo já na posição final. Era translateX(100%),
   que desloca cada célula só pela SUA PRÓPRIA largura (1/3 da secção) —
   a 1ª e a 2ª célula ficavam deslocadas para a posição da célula
   seguinte, ainda dentro da secção/janela, só a 3ª saía mesmo (bug real,
   apanhado pelo utilizador: "apenas a terceira imagem está escondida, a
   primeira e a segunda já estão à vista"). 100vw garante que qualquer
   célula, seja qual for a sua largura, começa sempre para lá da borda da
   janela. */
[data-entry="right"] [data-portfolio-cell] { transform: translateX(100vw); }
[data-entry="left"] [data-portfolio-cell] { transform: translateX(-100vw); }
.kinetic-section { position: relative; }
.kinetic-pattern { position: absolute; inset: 0; pointer-events: none; }
.kinetic-pattern::before { position: absolute; inset: 0; background-image: url('../images/symbol-pattern.svg'); background-position: center; background-repeat: repeat; background-size: 2.25rem 4.4rem; content: ''; opacity: .055; }
.kinetic-pattern::after { position: absolute; inset: 0; background: url('../images/logo-lockup.png') 74% 48% / min(35rem, 44vw) auto no-repeat; content: ''; filter: brightness(0) invert(1); opacity: .095; }

/* Hero: os pontos-e-vírgulas em mosaico (.kinetic-pattern::before) trocados
   pelo campo de símbolos do PatternBackground.astro (mesmo componente do
   footer) — mantém o logótipo grande atrás (::after, intocado) mas passa a
   ter símbolos individuais em vez de uma textura só. O ::before continua a
   existir/ser usado normalmente na secção 3 (.kinetic-pattern--work), por
   isso é escondido só aqui, com o seletor com âmbito de .kinetic-hero, não
   na regra base. */
.kinetic-hero .kinetic-pattern::before,
.kinetic-portfolio-hero .kinetic-pattern::before { display: none; }
/* Logótipo grande atrás do título (::after) estava demasiado apagado no
   hero — mais visível só aqui (0.095 -> 0.2), sem tocar na base nem na
   secção 3 (.kinetic-pattern--work::after, mais discreta ainda: 0.04).
   .kinetic-contact-hero e .kinetic-portfolio-hero partilham esta regra de
   propósito (pedido explícito: mesmas medidas/posição do logótipo da
   home) — ver nota junto a .kinetic-contact-hero .kinetic-pattern::before.
   Portfólio já teve uma opacidade própria mais baixa (.16) para evitar
   colidir com o título — revertido a pedido explícito ("o logotipo,
   quando não está em colisão tem de perder transparência, tal como nas
   outras páginas"): o título passou a quebrar em 2 linhas curtas fixas
   ("Trabalho que" / "fala por si.", ver portfolio.astro) em vez de
   depender do wrap automático, o que já evita a colisão sem precisar de
   opacidade reduzida (confirmado por varrimento de 340–2200px). */
.kinetic-hero .kinetic-pattern::after,
.kinetic-contact-hero .kinetic-pattern::after,
.kinetic-portfolio-hero .kinetic-pattern::after { opacity: .64; }

/* Um símbolo amarelo no fundo do hero (pedido do cliente, 2026-09-03).
   Tentei primeiro pintar um dos símbolos do PatternBackground (via
   :nth-child), mas todo o campo herda --pattern-opacity (0.16) do
   .pattern-layer — a essa diluição, QUALQUER cor lida como o mesmo
   cinza-lilás baço dos outros, o amarelo desaparecia por completo. Por
   isso este é um símbolo à parte, fora do .pattern-layer, com a sua
   própria opacidade — só assim fica realmente visível sem deixar de ser
   discreto (0.5, mais forte que os 0.16 dos outros mas longe de opaco). */
/* Removido de todos os banners a pedido do cliente (2026-09-04) — display:none
   em vez de apagar a marcação de cada página (header/hero de cada
   template continua a chamar ponto_virgula_mark()/Mark() aqui, inofensivo
   e reversível de um sítio só). */
.kinetic-hero__accent-mark { display: none; position: absolute; top: 20%; left: 68%; z-index: 1; width: 2.5rem; color: var(--kinetic-accent); opacity: .5; pointer-events: none; }
/* !important necessário aqui: Mark.astro define o seu próprio `.mark {
   width: 0.6em }` com scoping do Astro, que adiciona um atributo
   data-astro-cid-* ao seletor — a especificidade final fica igual à desta
   regra (duas classes vs. uma classe + um atributo, ambas contam 2 no
   mesmo nível). Num empate de especificidade, quem ganha é quem vem depois
   no CSS final, e essa ordem entre um ficheiro simples importado
   (kinetic.css) e o `<style>` com scope de um componente não é garantida
   pelo Vite — history real: o mesmo código já renderizou a 48px (3rem,
   correto) e a ~9.6px (0.6em de 16px, minúsculo) sem nenhuma alteração
   funcional, só a ordem de bundling a mudar. Sem isto, o tamanho do ícone
   fica à sorte. */
.kinetic-hero__accent-mark .mark { width: 100% !important; height: auto !important; margin: 0; }

/* Par de ícones flutuantes extra por hero (pedido, 2026-09-03: "acrescentar
   icons flutuantes, sem abusar") — cor papel a opacidade baixa, para não
   competir com o acento amarelo (esse continua a ser o único ponto de cor
   de destaque). [data-symbol] é o mesmo mecanismo do PatternBackground
   (deriva lenta em x/y via GSAP, kinetic-template.ts) — aqui isolado, só 2
   por secção, não a densidade do padrão de fundo. */
.kinetic-hero__drift { position: absolute; z-index: 1; color: var(--kinetic-paper); opacity: .24; pointer-events: none; }
.kinetic-hero__drift .mark { width: 100% !important; height: auto !important; margin: 0; }

.kinetic-kicker {
	margin: 0 0 2.1rem;
	font-size: .64rem;
	font-weight: 800;
	letter-spacing: .09em;
	text-transform: uppercase;
}

.kinetic-kicker--ink { color: var(--kinetic-ink); }

.kinetic-hero h1,
.kinetic-section h2,
.kinetic-footer h2 {
	margin: 0;
	font-family: var(--kinetic-font);
	font-weight: 800;
	letter-spacing: -.065em;
	line-height: .9;
}

.kinetic-hero {
	position: relative;
	/* 40rem -> 32.5rem (520px): pedido explícito do cliente, 2026-09-05,
	   depois de confirmado por medição que o banner tinha folga a mais em
	   todas as páginas (conteúdo real da Home, a mais cheia, só precisa de
	   ~512px, contra os 597px que o min-height antigo impunha) e que o
	   corte da diagonal (6.55vw, valor fixo em px) não arrisca ficar
	   "cunha" a esta altura (94px/520px ≈ 18%, dentro da margem segura já
	   confirmada). Mesmo valor (520px) aplicado às 4 larguras/3 classes de
	   hero — ver as outras media queries deste ficheiro. */
	min-height: 32.5rem;
	padding-top: 5.25rem;
	background: radial-gradient(circle at 18% 45%, rgb(85 69 126 / 22%), transparent 36%), var(--kinetic-ink);
	color: var(--kinetic-paper);
	clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--kinetic-seam-rise)), 0 100%);
	overflow: clip;
}

.kinetic-hero__grid {
	position: relative;
	display: grid;
	grid-template-columns: minmax(20rem, .72fr) minmax(36rem, 1.28fr);
	align-items: center;
	min-height: 34.75rem;
}

.kinetic-hero__copy { position: relative; z-index: 4; max-width: 33rem; padding: 2rem 0 clamp(6.75rem, 8.5vw, 9rem); }
.kinetic-hero h1 { max-width: 7ch; font-size: clamp(3.6rem, 4.8vw, 5.1rem); line-height: .88; }
.kinetic-hero h1 span { display: block; }
.kinetic-hero__echo { color: var(--kinetic-lilac); }
.kinetic-hero__echo.kinetic-hero__echo--split { display: none; }
.kinetic-hero__lead { max-width: 29rem; margin: 1.4rem 0 1.35rem; font-size: clamp(.76rem, .82vw, .86rem); line-height: 1.55; }

/* Só em janela larga (>1100px, acima do breakpoint intermédio que já reduz a
   coluna do texto): título em 2 linhas em vez de partir "fronteiras."/"pontos
   finais." a meio, coluna do texto mais larga para preencher o vazio antes do
   símbolo flutuante, e a secção mais baixa já que o título deixa de precisar
   de tanta altura. Isolado numa media query própria em vez de mudar os
   valores base — a 1100px/767px o título continua a quebrar normalmente
   (colunas mais estreitas nesses breakpoints não têm espaço para uma frase
   inteira numa linha), por isso nowrap aí cortava/estourava o texto. */
@media (min-width: 1101px) {
	.kinetic-hero { min-height: 32.5rem; }
	.kinetic-hero__grid { grid-template-columns: minmax(24rem, 1.1fr) minmax(28rem, .9fr); min-height: 27rem; }
	.kinetic-hero__copy { max-width: 46rem; padding-bottom: clamp(4rem, 6vw, 6rem); }
	.kinetic-hero h1 { max-width: none; }
	.kinetic-hero h1 span { white-space: nowrap; }
}

/* Entre 1101 e ~1740px, "Sem pontos finais." (a segunda linha, nowrap
   desde o bloco acima) invade o logótipo em marca de água — medido com
   canvas measureText (largura real do texto, não a caixa do bloco, que dá
   falso-negativo aqui: um <span display:block> ocupa sempre a largura do
   contentor, o texto é que transborda por cima por causa do nowrap) contra
   a posição real do logótipo (background-position/size). Duas zonas de
   colisão, não uma só — apanhadas em duas rondas de medição:
   1) 1101→~1490px: a coluna de texto ainda não tem largura, o logótipo
      ainda não abriu espaço a caminhar para a direita.
   2) ~1660→~1720px: o `font-size` do título ainda está a crescer com
      `4.8vw` (só satura em 5.1rem aos 1700px) — o texto continua a
      alargar mais depressa do que o logótipo ganha margem, reabrindo a
      sobreposição pouco antes do texto parar de crescer. Só apanhada ao
      varrer até 2200px depois de a primeira zona "parecer resolvida" —
      not "1490 limpo" não significava limpo até ao fim.
   Nesta faixa (1101–1739px, as duas zonas cobertas com folga), troca para
   a variante "Sem / pontos / finais." (3 linhas, span próprio por
   palavra) em vez de "Sem pontos finais." inteiro — só a segunda frase
   muda, "Sem fronteiras." e o resto do hero ficam exatamente como estão. */
@media (min-width: 1101px) and (max-width: 1759px) {
	.kinetic-hero__echo:not(.kinetic-hero__echo--split) { display: none; }
	.kinetic-hero__echo.kinetic-hero__echo--split { display: block; }
	/* Altura da secção 1 igualada entre a home e os contactos (pedido:
	   "nota-se aquele corte ao mudar de página, fica feio"). Nesta faixa a
	   altura real da home NÃO é constante — cresce com a largura porque o
	   título de 4 linhas usa font-size: clamp(3.6rem, 4.8vw, 5.1rem) — por
	   isso um único valor fixo não chegava; medida a altura real da home em
	   1101px (535px) e em 1759px (657px) e ajustada uma reta entre os dois
	   pontos: 331px + 18.5vw. A regra para .kinetic-contact-hero está mais
	   abaixo, junto ao resto das suas media queries — não aqui: a base de
	   .kinetic-contact-hero só é definida mais tarde no ficheiro, e com a
	   mesma especificidade (uma classe), uma regra colocada aqui perdia
	   sempre para essa base, venha a media query que vier (quem vem depois
	   no ficheiro ganha um empate de especificidade — apanhado ao medir de
	   novo depois desta correção: os contactos não tinham mexido nada). */
	.kinetic-hero { min-height: 520px; }
}

/* A partir de 1760px o título da home volta a 2 linhas (fora da faixa da
   correção acima) e a secção estabiliza em 520px de altura REAL (medida,
   não só min-height). A correção de padding dos contactos para esta faixa
   está junto à regra base de .kinetic-contact-hero (linha ~793) — uma
   regra aqui, ANTES dessa base, perdia sempre o empate de especificidade
   (mesmo bug já documentado várias vezes neste ficheiro: quem vem depois
   no ficheiro ganha, não quem tem a media query "mais tarde" no fluxo de
   leitura). Confirmado por medição real (Playwright) antes de mover. */
@media (min-width: 1760px) {
	.kinetic-hero { min-height: 520px; }
	.kinetic-contact-hero { min-height: 520px; }
	/* .kinetic-portfolio-hero NÃO aqui — a sua regra base (min-height:24rem)
	   vem DEPOIS deste bloco no ficheiro (linha ~839) e ganhava sempre o
	   empate de especificidade a esta, venha a media query que vier (o
	   mesmo problema que o comentário de .kinetic-contact-hero, mais acima,
	   já documentava — apanhado ao medir, repetido aqui por descuido e
	   corrigido: a regra certa está depois da base, junto às outras media
	   queries de .kinetic-portfolio-hero). */
}

.kinetic-hero__media-float {
	position: absolute;
	top: -3rem;
	right: -5rem;
	z-index: 2;
	width: min(70vw, 65rem);
	height: 48rem;
	animation: kinetic-logo-float 6.5s ease-in-out infinite;
}

.kinetic-hero__media {
	position: relative;
	width: 100%;
	height: 100%;
	background: rgb(239 232 222 / 8%);
	-webkit-mask: url('../images/symbol.svg') center / contain no-repeat;
	mask: url('../images/symbol.svg') center / contain no-repeat;
	transform: rotate(5deg);
}
@keyframes kinetic-logo-float {
	0%, 100% { transform: translateY(-10px); }
	50% { transform: translateY(12px); }
}

.kinetic-marquee {
	position: relative;
	z-index: 12;
	width: 110%;
	/* margin-top corrigido (2026-09-05) — apanhado pelo cliente a olho
	   ("zoom-out 2x/3x, janela grande... à esquerda a faixa continua
	   certa, a linha do fundo azul faz um desvio para cima") e confirmado
	   a pixel: a fórmula antiga, "-1.35rem - 3.27vw", tentava alinhar
	   pelo CENTRO do elemento (rotate() roda por omissão à volta do
	   centro do próprio elemento, transform-origin: 50% 50%) — só batia
	   certo perto da largura em que foi afinada a olho (~1440px). Em
	   janelas muito mais largas (testado a 3840px com fundo/faixa em
	   cores sólidas para medir sem ambiguidade), o canto ESQUERDO da
	   faixa ficava até 100px acima da aresta real do hero, deixando uma
	   fatia de papel exposta ali — sempre a encolher para a direita até
	   desaparecer, nunca visível nas larguras mais pequenas onde este
	   template foi todo testado.
	   Corrigido com a MESMA técnica já usada em .kinetic-portfolio-hero::
	   after (a sombra do hero) e no antigo .kinetic-portfolio-seam-shadow:
	   transform-origin:0 0 (roda a partir do canto superior esquerdo do
	   PRÓPRIO elemento, não do centro) + skewY() em vez de rotate()
	   (desloca só o eixo Y, sem o encolhimento lateral de ~0.2% que uma
	   rotação verdadeira introduz). Com a origem fixa no canto, só é
	   preciso acertar o margin-top NESSE canto (onde margin-left o
	   coloca, -5vw) para bater com a extensão da diagonal do hero nesse
	   ponto — sem precisar de adivinhar onde fica o centro do elemento.
	   Conta: aresta do hero em pageX é heroHeight + pageX·tan(θ) (θ=
	   -3.75°, tan≈-0.0655); o canto da faixa fica em pageX=-5vw; logo
	   margin-top = (-5vw)·tan(θ) = (-5)·(-0.0655)vw ≈ 0.33vw. Isto vale
	   em QUALQUER largura de janela (só depende do declive e da posição
	   do canto, nunca da largura total) — ao contrário da fórmula
	   anterior, não há nenhuma largura de referência a que isto deixe de
	   bater certo. "-.5rem" extra é folga deliberada (mesmo raciocínio do
	   "-1px"/"-2px" usado noutras costuras do ficheiro): a faixa fica
	   SEMPRE ligeiramente por cima da aresta real, nunca a descoberto,
	   mesmo com arredondamentos diferentes consoante a largura. */
	margin: calc(.33vw - .5rem) 0 -1rem -5%;
	padding: .16rem 0 .16rem 1rem;
	background: var(--kinetic-paper);
	color: var(--kinetic-ink);
	overflow: hidden;
	transform-origin: 0 0;
	transform: skewY(-3.75deg);
	box-shadow: 0 1.2rem 2.4rem rgb(16 10 34 / 15%);
}

/* O loop assenta em duas metades idênticas do track a deslizarem -50%: a
   metade B entra exatamente onde a metade A começou. Isto só fica sem
   emendas se (a) cada metade for, sozinha, mais larga que a faixa visível —
   senão há um instante, perto do fim de cada ciclo, em que já saiu a
   metade A e ainda não chegou o resto da B, mostrando o fundo por baixo — e
   (b) as duas metades forem idênticas byte a byte, incluindo padding (por
   isso o inset de 1rem passou para o wrapper .kinetic-marquee acima, fora
   do que anima). KineticShowcase.astro repete a lista de palavras vezes
   suficientes para cada metade ultrapassar a largura máxima possível da
   faixa (~110% de --kinetic-max) com folga confortável. */
/* 29s -> 75s (2026-09-05, "a faixa está a passar demasiado rapido"): as
   palavras soltas de antes trocaram por tags reais dos serviços, bem mais
   compridas ("Gestão de Redes Sociais" vs "Marcas") — o texto por percorrer
   por loop quase duplicou (~4139px, medido, cada metade) mas a duração
   manteve-se, a velocidade efetiva subiu bastante. 75s dá ~55px/s (era
   ~143px/s), ritmo lento e legível. */
.kinetic-marquee__track { display: flex; align-items: center; gap: .58rem; width: max-content; animation: kinetic-marquee 75s linear infinite; }
.kinetic-marquee span { font-size: clamp(.98rem, 1.35vw, 1.42rem); font-weight: 800; letter-spacing: -.045em; line-height: 1; text-transform: uppercase; white-space: nowrap; }
.kinetic-marquee__word--accent { color: var(--kinetic-accent); }
.kinetic-marquee img { width: clamp(.78rem, 1vw, 1.05rem); height: clamp(1.3rem, 1.72vw, 1.8rem); object-fit: contain; }
@keyframes kinetic-marquee { to { transform: translateX(-50%); } }

.kinetic-intro {
	z-index: 5;
	margin-top: -1rem;
	padding: 5.5rem 0 6rem;
	background: var(--kinetic-paper);
	/* O corte inferior (0 91%, 100% 100%) é o único que desenha a costura com
	   a secção 3 — .kinetic-work::before/.kinetic-pattern--work já não têm
	   diagonal própria no topo (ver nota lá). Duas diagonais independentes,
	   cada uma em percentagem da ALTURA DO SEU PRÓPRIO elemento, quase nunca
	   coincidem (as duas secções mudam de altura em breakpoints diferentes)
	   — cruzavam-se e criavam um vinco visível a meio da linha. Fundido numa
	   só, a costura é sempre uma linha reta, seja qual for a altura de cada
	   secção. Profundidade 9% (era 4.5%) para manter a diagonal acentuada
	   pedida — antes a acentuação vinha do lado de baixo (work), agora vem
	   inteiramente daqui. */
	clip-path: polygon(0 6%, 100% 0, 100% 100%, 0 91%);
	overflow: clip;
}

.kinetic-intro__grid { display: grid; grid-template-columns: minmax(20rem, .9fr) minmax(13rem, .7fr) minmax(24rem, 1.1fr); align-items: center; gap: clamp(2rem, 4vw, 4rem); }
/* max-width solto (era 8.2ch, para o texto quebrar sozinho) — as quebras
   agora são explícitas (3 spans, um por linha: "Criamos" / "marcas com" /
   "presença.", pedido do utilizador), por isso a coluna já não precisa de
   forçar o embrulho automático — só teria de o impedir de quebrar ainda
   mais uma das 3 linhas (nowrap trata disso). */
.kinetic-intro h2 { font-size: clamp(2.7rem, 3.7vw, 4rem); }
.kinetic-intro h2 span { display: block; white-space: nowrap; }
.kinetic-intro__copy { min-width: 0; }
.kinetic-intro__copy > p:not(.kinetic-kicker) { max-width: 30rem; margin: 1.25rem 0 1.55rem; font-size: .76rem; line-height: 1.62; }
.kinetic-text-link { display: inline-block; font-size: .64rem; font-weight: 800; letter-spacing: .05em; text-decoration: none; text-transform: uppercase; }
.kinetic-text-link--marked { display: inline-flex; align-items: center; gap: .75rem; }
.kinetic-text-link--marked { position: relative; }
.kinetic-text-link--marked span { white-space: nowrap; }
.kinetic-text-link--marked::after { position: absolute; bottom: -.35rem; left: 0; width: calc(100% - 2.1rem); margin: 0; }
.kinetic-text-link--marked img { width: 1.35rem; height: 2.15rem; object-fit: contain; }
.kinetic-intro__mark { display: grid; place-items: center; }
.kinetic-intro__mark img { width: clamp(18rem, 26vw, 28rem); max-width: none; height: auto; filter: opacity(.36) saturate(.6); }

.kinetic-values { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
.kinetic-values li { display: grid; grid-template-columns: 3.25rem 1fr; align-items: center; gap: .9rem; min-height: 5.35rem; padding: .72rem 0; border-bottom: 1px solid var(--kinetic-line); }
.kinetic-values li:first-child { border-top: 1px solid var(--kinetic-line); }
.kinetic-values li > div { display: grid; gap: .32rem; }
.kinetic-values strong { font-size: .59rem; font-weight: 800; letter-spacing: .025em; text-transform: uppercase; }
/* .kinetic-values span { color: rgb(40 32 64 / 72%); font-size: .66rem }
   (a descrição por baixo de cada título) aplica-se a QUALQUER span dentro
   de .kinetic-values, incluindo este — com mais especificidade do que uma
   classe sozinha (uma classe+um elemento vence uma classe). "Design"
   ficava cinzento E maior (.66rem herdado dessa regra, em vez de .59rem,
   o tamanho do resto do título, .kinetic-values strong) do que o resto da
   frase — dois sintomas da mesma causa, a cor corrigida primeiro sem
   reparar que o tamanho tinha o mesmo problema. Os dois qualificados com
   .kinetic-values strong à frente, para ganhar sem precisar de
   !important. */
.kinetic-values strong .kinetic-values__accent { color: var(--kinetic-accent); font-size: inherit; }
.kinetic-values span { color: rgb(40 32 64 / 72%); font-size: .66rem; }

.kinetic-work {
	z-index: auto;
	margin-top: -5.5rem;
	/* Simétrico de propósito: com o conteúdo (heading+lead+botão) a ocupar
	   toda a altura da linha do grid (é a coluna mais alta, a colagem fica
	   mais baixa e centra-se sozinha dentro dela via align-items:center),
	   padding-top = padding-bottom é o que centra o texto entre os limites
	   da secção. Reduzido de 9.5rem: essa medida existia só para a colagem
	   nunca tocar a secção 2/4 acima/abaixo — o cliente confirmou que a
	   colagem invadir secções vizinhas fica bem, até é o efeito desejado
	   (overflow:visible mais abaixo já permitia isto, faltava dar-lhe espaço
	   para acontecer). Reduzido outra vez, de 5rem para 2.75rem, a pedido. */
	padding: 2.75rem 0;
	background: transparent;
	color: var(--kinetic-paper);
	overflow: visible;
}

/* Diagonal do topo acentuada (era 100% 4.5%, quase reta) para ficar visível
   como as outras costuras do template, não uma linha praticamente plana. */
/* Topo achatado (0 0, 100% 0) de propósito: a diagonal de cima já é
   inteiramente desenhada por .kinetic-intro (z-index mais alto, fica por
   cima nesta faixa) — ver o comentário lá. Duas diagonais competindo aqui
   criavam um vinco visível onde as suas inclinações se cruzavam. O fundo
   (100% 97%, 0 100%) continua diagonal, essa costura (com a secção 4) não
   tem esse problema, é a única a desenhá-la. */
.kinetic-work::before { position: absolute; inset: 0; z-index: 0; background: var(--kinetic-ink); clip-path: polygon(0 0, 100% 0, 100% 97%, 0 100%); content: ''; pointer-events: none; }
.kinetic-pattern--work { z-index: 1; clip-path: polygon(0 0, 100% 0, 100% 97%, 0 100%); }
.kinetic-pattern--work::after { background-position: 32% 50%; opacity: .04; }
/* Sem z-index próprio de propósito: um valor aqui criava um contexto de
   empilhamento novo que prendia o z-index alto da colagem lá dentro,
   impedindo-a de atravessar para cima da secção 2 apesar do seu próprio
   valor (var(--kinetic-z-media)) ser maior — ver nota em :root. */
.kinetic-work__grid { position: relative; display: grid; grid-template-columns: minmax(20rem, .56fr) minmax(0, 1.44fr); align-items: center; gap: clamp(3rem, 5vw, 5rem); }
.kinetic-work__heading { position: relative; z-index: var(--kinetic-z-text); align-self: center; }
.kinetic-work__heading h2 { max-width: 7ch; margin-bottom: 1.65rem; font-size: clamp(2.9rem, 4vw, 4.25rem); }
.kinetic-work__heading h2 span { display: block; }
.kinetic-work__lead { max-width: 26rem; margin: 0 0 1.75rem; font-size: clamp(.76rem, .82vw, .86rem); line-height: 1.55; }
/* font-size/font-weight explícitos como "inherit", não confiados por
   omissão — lição das secções 21/23 (.kinetic-values span já tinha
   provado que uma regra genérica "span dentro de X" pode aterrar em cima
   de um span novo sem se notar até alguém reportar). Aqui não há regra
   concorrente conhecida, mas o custo de o garantir é zero. */
.kinetic-text-dash { color: var(--kinetic-accent); font-size: inherit; font-weight: inherit; }
.kinetic-text-link--light { color: var(--kinetic-paper); }
/* Mosaico de molduras (quinta passada, 2026-09-03): as versões anteriores
   espalhavam as fotos em posição absoluta com rotações fortes; o cliente
   pediu antes uma composição arrumada, tipo moodboard — peças alinhadas em
   grelha, encostadas/sobrepostas a pares, com sombra a dar profundidade.
   Grelha em vez de posição absoluta porque a altura de cada peça passa a
   depender do rácio da própria imagem (ver --socket-ratio): com absoluto,
   uma imagem mais alta do que o previsto saía do contentor. */
.kinetic-collage { position: relative; z-index: var(--kinetic-z-media); display: grid; grid-template-columns: repeat(12, 1fr); align-items: start; gap: .75rem; min-width: 0; }
/* A moldura toma o rácio EXATO da imagem (escrito em runtime pelo JS); o
   4/5 é só o valor de arranque, para não haver salto de layout antes de a
   imagem carregar. Sem isto, uma foto 16:9 numa moldura 4:5 ficava cortada
   ou com barras — era a "salvaguarda" pedida. */
.kinetic-collage__item { position: relative; aspect-ratio: var(--socket-ratio, 4 / 5); margin: 0; background: var(--kinetic-ink-soft); border-radius: .2rem; box-shadow: 0 1.4rem 2.8rem rgb(8 5 18 / 45%); overflow: hidden; pointer-events: none; will-change: transform; }
/* object-fit: cover é seguro aqui exatamente porque a moldura já tem o rácio
   da imagem — não há nada para cortar. Fica cover (e não contain) para
   aguentar o caso limite de o rácio ainda não ter sido medido. */
.kinetic-collage__item img { width: 100%; height: 100%; object-fit: cover; }
/* Duas linhas de três peças em 12 colunas. As colunas de início sobrepõem-se
   de propósito (a acaba na 5, b começa na 5) e as margens negativas cruzam
   também as linhas — é daí que vem a sobreposição. O z-index decide quem
   fica por cima; as linhas são explícitas (grid-row) para a composição não
   se reorganizar sozinha quando uma imagem vier mais alta do que as outras. */
/* Sem "grid-row" fixo (só "grid-column", com início explícito) — o
   auto-placement do grid já resolve sozinho qual a próxima linha livre
   para cada intervalo de colunas (colide com o que já lá está e avança),
   por isso o mesmo ciclo de 6 molduras se repete sozinho para qualquer
   número de imagens (ver .kinetic-project-mosaic, single-projeto.php) —
   não só as 6 fixas da home. Confirmado sem alteração visual na home
   (mesmas 6 imagens continuam a cair exatamente nas mesmas duas linhas). */
.kinetic-collage__item--a { grid-column: 1 / span 4; z-index: 2; transform: rotate(-2.2deg); }
.kinetic-collage__item--b { grid-column: 5 / span 3; z-index: 4; margin-top: 1.5rem; }
.kinetic-collage__item--c { grid-column: 8 / span 4; z-index: 3; margin-top: -.5rem; }
.kinetic-collage__item--d { grid-column: 2 / span 3; z-index: 4; margin-top: -4.5rem; transform: rotate(1.6deg); }
.kinetic-collage__item--e { grid-column: 5 / span 4; z-index: 5; margin-top: -2.5rem; }
.kinetic-collage__item--f { grid-column: 9 / span 4; z-index: 3; margin-top: -5.5rem; transform: rotate(-1.3deg); }


/* O símbolo gigante (.kinetic-services__mask, top:-60%) sobe bem mais alto do
   que os -5.5rem de costura desta secção com a 3 — por isso pinta por cima da
   secção 3 (ela é z-index 0/auto no contexto partilhado de .kinetic-page,
   abaixo do z-index:3 desta stage). Baixar o z-index da stage não dá: o resto
   do conteúdo (título, lista de serviços) tem de continuar acima da secção 3
   nesses -5.5rem de costura, só o símbolo é que não pode. clip-path é
   aplicado à stage (sem transform própria) e corta em coordenadas de ecrã —
   por isso a rotação de 30deg do próprio símbolo não desalinha o corte.
   Um inset() plano (corte horizontal) não chega: o vinco visível entre a
   secção 3 e esta é a diagonal do PRÓPRIO fundo de papel desta secção
   (.kinetic-services::before, clip-path 0% 3% -> 100% 0%) — mais baixa à
   esquerda, mais alta à direita — porque nessa faixa de -5.5rem é o papel
   desta secção que ganha ao fundo tinta da secção 3, não uma linha reta. Um
   corte reto deixava um triângulo do símbolo à mostra do lado esquerdo,
   onde o papel ainda não tinha começado mas o corte plano já tinha deixado
   passar. O polygon() abaixo segue exatamente essa mesma diagonal (mais
   0.15rem de margem de segurança contra arredondamento de subpíxel),
   prolongada com folga enorme (%) para nunca cortar mais nada por engano. */
.kinetic-services-stage { position: relative; z-index: 3; margin-top: -5.5rem; overflow: visible; clip-path: polygon(0% calc(3% + 0.15rem), 100% 0.15rem, 100% 300%, -100% 300%, -100% calc(3% + 0.15rem)); }

.kinetic-services {
	z-index: auto;
	margin-top: 0;
	min-height: 36rem;
	/* Padding-bottom > padding-top de propósito, não um erro de simetria: o
	   footer sobrepõe-se -5.5rem (88px) ao fundo desta secção e ganha essa
	   faixa (z-index mais alto, mesmo mecanismo de costura das outras
	   secções) — por isso 88px do fundo da caixa "oficial" desta secção
	   nunca chegam a pintar-se em papel, ficam sempre cobertos de tinta do
	   footer. Título e lista estão centrados na caixa toda (incluindo essa
	   faixa invisível); sem compensar aqui, ficam a parecer 44px abaixo do
	   centro visível. +88px aqui no padding-bottom cancela exatamente essa
	   perda, sem tocar no padding-top nem no espaçamento da secção 3 acima.
	   Ver .kinetic-footer para a redução de altura do footer em si. */
	padding: 4.5rem 0 10rem;
	background: transparent;
	overflow: visible;
}

.kinetic-services::before { position: absolute; inset: 0; z-index: 0; background: var(--kinetic-paper); clip-path: polygon(0 3%, 100% 0, 100% 100%, 0 99%); content: ''; pointer-events: none; }

.kinetic-services__grid { position: relative; z-index: 3; display: grid; grid-template-columns: clamp(5.5rem, 8vw, 8rem) minmax(28rem, 42rem) minmax(14rem, 1fr); align-items: center; justify-content: center; gap: clamp(2rem, 3.5vw, 4rem); min-height: 28rem; }
.kinetic-services__title { position: relative; z-index: 2; align-self: stretch; min-height: 28rem; }
.kinetic-services__title h2 { position: absolute; top: 50%; left: 50%; font-size: clamp(2.7rem, 2.75vw, 3.25rem); white-space: nowrap; transform: translate(-50%, -50%) rotate(-90deg); transform-origin: center; }
.kinetic-service-list { position: relative; z-index: 3; width: 100%; max-width: 42rem; border-top: 1px solid var(--kinetic-line); }
.kinetic-service { border-bottom: 1px solid var(--kinetic-line); }
.kinetic-service__trigger { display: grid; grid-template-columns: 1.6rem 1fr auto; align-items: center; gap: .8rem; width: 100%; padding: 1.02rem 0; border: 0; background: transparent; color: var(--kinetic-ink); font-size: clamp(1.1rem, 1.5vw, 1.55rem); font-weight: 800; letter-spacing: -.035em; text-align: left; text-transform: uppercase; cursor: pointer; }
.kinetic-service__plus { position: relative; display: block; width: 1.1rem; height: 1.1rem; }
.kinetic-service__plus::before, .kinetic-service__plus::after { position: absolute; top: 50%; left: 50%; width: 100%; height: 1px; background: currentColor; content: ''; transform: translate(-50%, -50%); transition: transform 220ms cubic-bezier(.16, 1, .3, 1); }
.kinetic-service__plus::after { transform: translate(-50%, -50%) rotate(90deg); }
.kinetic-service__trigger[aria-expanded='true'] .kinetic-service__plus::after { transform: translate(-50%, -50%); }
.kinetic-service__body { display: grid; grid-template-rows: 0fr; padding-left: 3.2rem; transition: grid-template-rows 320ms cubic-bezier(.16, 1, .3, 1); }
.kinetic-service__content { position: relative; min-height: 0; overflow: hidden; }
/* Número gigante decorativo atrás do texto, só visível com o painel aberto
   (auditoria de design, 2026-09-03, mesmo espírito do símbolo mostarda do
   hero) — vive dentro de .kinetic-service__content de propósito, não como
   irmão solto em .kinetic-service__body: é esse overflow:hidden que o
   esconde quando o acordeão fecha (o truque grid-template-rows:0fr só
   colapsa este item, um segundo item fora dele criava uma linha implícita
   à parte que não colapsava). Posição em insets só positivos (top/right a
   partir do canto, nunca negativos) — a primeira versão usava top/right
   negativos para "empurrar" o número para fora do canto, mas isso é
   exatamente o que ficava cortado pelo overflow:hidden acima (o "0" sem a
   curva de cima, o resto sem o lado direito). */
.kinetic-service__bignum { position: absolute; top: 1.4rem; right: 2.75rem; z-index: 0; color: var(--kinetic-accent); font-size: 5.5rem; font-weight: 800; letter-spacing: -.04em; line-height: 1; opacity: .16; pointer-events: none; }
.kinetic-service__content > p { position: relative; z-index: 1; margin: 0; padding-right: 2rem; color: rgb(40 32 64 / 72%); font-size: .75rem; line-height: 1.55; }
.kinetic-service__tags { display: flex; flex-wrap: wrap; gap: .42rem; margin: .85rem 2rem 0 0; padding: 0; list-style: none; }
.kinetic-service__tags li { padding: .3rem .55rem; border: 1px solid rgb(40 32 64 / 28%); border-radius: 999px; color: rgb(40 32 64 / 78%); font-size: .58rem; font-weight: 700; line-height: 1.2; }
.kinetic-service__note { margin-top: .75rem !important; font-size: .65rem !important; font-style: italic; }
.kinetic-service.is-open .kinetic-service__body { grid-template-rows: 1fr; }
.kinetic-service.is-open .kinetic-service__content { padding: .3rem 0 1.15rem; }
/* filter: brightness(0) porque o próprio symbol-solid-soft.svg é #ded5c9 —
   um bege quase idêntico ao --kinetic-paper (#f0e8e0) de fundo, baixo
   contraste por natureza do ficheiro, não por opacidade CSS (já estava a
   1). Trocar de asset para uma versão outline (stroke fino) não servia —
   a este tamanho (100vw) um traço de 2px fica um fio invisível; manter a
   forma sólida e escurecer via filter preserva o desenho "silhueta
   gigante" já estabelecido, só com mais presença (auditoria de design,
   2026-09-03). */
.kinetic-services__mask { position: absolute; top: -60%; left: 12%; z-index: 2; width: 100vw; height: 220%; background: url('../images/symbol-solid-soft.svg') center / contain no-repeat; filter: brightness(0) opacity(.11); pointer-events: none; transform: rotate(30deg); transform-origin: center; }

.kinetic-footer {
	position: relative;
	z-index: 4;
	margin-top: -5.5rem;
	padding: 1rem 0 .75rem;
	background: radial-gradient(circle at 60% 0, rgb(86 68 126 / 25%), transparent 44%), var(--kinetic-ink);
	color: var(--kinetic-paper);
	clip-path: polygon(0 0, 100% 1%, 100% 100%, 0 100%);
	overflow: hidden;
}

.kinetic-footer__inner { position: relative; z-index: 2; }
.kinetic-footer__base { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 2.5rem; padding: .5rem 4%; font-size: .68rem; }
.kinetic-footer__brand { position: relative; display: block; width: clamp(9rem, 13vw, 11rem); padding: .3rem .5rem; overflow: visible; }
.kinetic-footer__brand img { width: 100%; height: auto; filter: brightness(0) invert(1); }
.kinetic-footer__socials { --social-hover-icon: var(--kinetic-ink); margin: 0; padding: 0; }
/* Toque de amarelo no hover dos ícones sociais — só aqui, sem tocar no
   componente partilhado SocialLinks.astro (usado também no site principal
   fora deste protótipo). kinetic.css é uma folha de estilo simples,
   importada, não passa pelo scoping do Astro — o seletor de classe chega
   ao componente sem precisar de :global(). O componente já expõe :hover a
   preencher com currentColor; aqui sobrepomos só a cor do círculo,
   mantendo o ícone claro por cima (--social-hover-icon já tratado disso). */
.kinetic-footer__socials .social-links a:hover { background: var(--kinetic-accent); border-color: var(--kinetic-accent); }
.kinetic-footer__legal { margin: 0; padding: .75rem 4% 0; border-top: 1px solid rgb(240 232 224 / 18%); color: rgb(240 232 224 / 52%); font-size: .55rem; text-align: center; text-transform: uppercase; }

/* Página de contactos (src/pages/template-cinetico/contactos.astro),
   2026-09-03 — segunda página do template, depois da home. Reaproveita os
   tokens/padrões já estabelecidos (kicker, kinetic-button, diagonal de
   costura, kinetic-pattern) em vez de inventar uma linguagem nova; não
   reutiliza .kinetic-hero inteira porque essa classe traz consigo a
   grelha/símbolo flutuante do hero da home, que aqui não fazem sentido. */
.kinetic-contact-hero {
	position: relative;
	/* z-index para garantir que fica sempre por cima do símbolo colossal da
	   secção 2 (.kinetic-contact-panel__mask, z-index: 0) — esse símbolo,
	   agora com tamanho fixo em px, pode invadir esta secção; sem isto,
	   competiam pela ordem do DOM (a secção 2 vem depois, ganhava). */
	z-index: 2;
	min-height: 24rem;
	padding: 8.5rem 0 4.5rem;
	background: radial-gradient(circle at 18% 45%, rgb(85 69 126 / 22%), transparent 36%), var(--kinetic-ink);
	color: var(--kinetic-paper);
	clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--kinetic-seam-rise)), 0 100%);
	overflow: clip;
}

/* Altura igualada com a home entre 1101–1759px — mesma fórmula, mesmo
   valor, ver o comentário completo junto a .kinetic-hero na media query
   partilhada (linha ~305). Colocada aqui, depois da regra base acima, de
   propósito: uma regra com a mesma especificidade posta ANTES dessa base
   perdia sempre para ela, venha a media query que vier (apanhado ao medir
   — os contactos não respondiam a nenhuma tentativa de override colocada
   mais cedo no ficheiro). */
@media (min-width: 1101px) and (max-width: 1759px) {
	.kinetic-contact-hero { min-height: 520px; }
	/* .kinetic-portfolio-hero NÃO aqui, pela mesma razão do bloco ≥1760px
	   acima — a regra própria está depois da base (linha ~880), ver lá. */
}

/* ≥1760px: min-height:520px (regra na media query lá em cima) não chegava
   sozinho — o padding próprio desta secção (8.5rem/4.5rem, regra base
   acima) empurrava a altura REAL para 570px, medida com Playwright, 50px
   acima da home (520px). Corrigido reduzindo o padding só nesta faixa —
   medido por iteração até bater com a home (pedido do cliente, 2026-09-07:
   "o banner dos contactos parece maior que o da home"). Fica DEPOIS da
   base de propósito (mesmo bug de especificidade documentado à exaustão
   neste ficheiro: uma regra igual posta antes da base nunca lhe ganha). */
@media (min-width: 1760px) {
	.kinetic-contact-hero { padding: 5.25rem 0 4.65rem; }
}

/* Mesmo tratamento do hero da home (secção 1): marca amarela isolada
   (.kinetic-hero__accent-mark, classe partilhada — não é preciso duplicá-la
   aqui) e logótipo em marca de água mais visível. A base de
   .kinetic-pattern::after (0.095) fica demasiado apagada nos heroes de
   tinta; a home já resolve isto com .kinetic-hero, esta é a mesma regra
   para .kinetic-contact-hero. */
.kinetic-contact-hero .kinetic-pattern::before { display: none; }
/* Pedido explícito (2026-09-03): preservar as MESMAS medidas e posição do
   logótipo da secção 1 da home aqui — nada de posição própria. As duas
   tentativas anteriores (posição partilhada 74%/48%, depois um canto
   próprio 92%/55%) resolviam a sobreposição empurrando o logótipo; o
   título mais curto (secção 17) já resolveu a sobreposição pela raiz, por
   isso a posição/tamanho de casa (home) voltam a fazer sentido aqui — ver
   a regra partilhada `.kinetic-hero .kinetic-pattern::after,
   .kinetic-contact-hero .kinetic-pattern::after` mais abaixo (mesmo ponto
   onde a home define os seus 0.64/74%/48%) e a variante de mobile, também
   partilhada. */
.kinetic-contact-hero__inner { position: relative; z-index: 1; }
.kinetic-contact-hero h1 { margin: 0; font-family: var(--kinetic-font); font-weight: 800; letter-spacing: -.065em; line-height: .92; font-size: clamp(2.9rem, 6vw, 5.5rem); }
.kinetic-contact-hero h1 span { display: block; }
.kinetic-contact-hero__lead { max-width: 34rem; margin: 1.5rem 0 0; font-size: clamp(.85rem, 1vw, 1rem); line-height: 1.6; color: rgb(240 232 224 / 78%); }
.kinetic-contact-hero__socials { --social-hover-icon: var(--kinetic-ink); margin: 2.25rem 0 0; padding: 0; }
.kinetic-contact-hero__socials .social-links a:hover { background: var(--kinetic-accent); border-color: var(--kinetic-accent); }

/* overflow removido de propósito — nunca cortar o símbolo da marca. Se for
   maior que o painel, invade as secções vizinhas (mesmo princípio já
   usado na home: hierarquia de z-index, nunca clip). */
.kinetic-contact-panel { position: relative; padding: 7rem 0 10rem; background: var(--kinetic-paper); }
.kinetic-contact-panel__grid { position: relative; z-index: 1; display: grid; grid-template-columns: minmax(18rem, .62fr) minmax(20rem, 1fr); gap: clamp(3rem, 6vw, 6rem); align-items: start; }
.kinetic-contact-panel__info h2 { margin: 0; font-family: var(--kinetic-font); font-weight: 800; letter-spacing: -.05em; line-height: 1; font-size: clamp(1.9rem, 3vw, 2.6rem); }

/* Símbolo colossal a preencher o fundo — a mesma peça decorativa da secção
   4 da home (.kinetic-services__mask: mesmo SVG, mesmo filtro para o tom
   discreto), pedida aqui para a secção 2 dos contactos.
   Regra fixa (nunca cortar o design da marca): símbolo inteiro (ponto +
   vírgula, `background-size: contain`, sem recorte) e
   `.kinetic-contact-panel` sem overflow — se for maior que o painel,
   invade as secções vizinhas na íntegra, exactamente como a colagem de
   imagens da home.
   Tamanho e posição fixos em px (1000px, confirmado com o utilizador) —
   não reage à largura da janela, ao contrário das versões anteriores
   (vw/rem). Continua centrado (top/left 50% + translate) porque isso não
   é "reagir à janela", é manter-se no mesmo sítio conceptual (o centro do
   painel) seja qual for o tamanho desse painel.
   Hierarquia: a mesma técnica já usada na home (giant mark da secção 4
   fica sempre atrás do footer, z-index 4) — aqui, `.kinetic-contact-hero`
   ganhou `z-index: 2` (só nesta regra, não aqui) e `.kinetic-footer` já
   tinha `z-index: 4` — as duas ficam sempre por cima da marca onde quer
   que ela invada o seu espaço, sem cortar a marca em lado nenhum. */
.kinetic-contact-panel__mask { position: absolute; top: 50%; left: 50%; z-index: 0; width: 1000px; height: 1000px; background: url('../images/symbol-solid-soft.svg') center / contain no-repeat; filter: brightness(0) opacity(.11); transform: translate(-50%, -50%) rotate(20deg); pointer-events: none; }

.kinetic-contact-pills { display: grid; gap: .6rem; margin: 1.75rem 0 0; padding: 0; list-style: none; }
.kinetic-contact-pills__row { display: flex; flex-wrap: wrap; gap: .6rem; }
.kinetic-contact-pill {
	display: inline-flex;
	align-items: center;
	gap: .65rem;
	padding: .75rem 1.1rem;
	border: 1.5px solid var(--kinetic-line);
	border-radius: 999px;
	color: var(--kinetic-ink);
	font-size: .78rem;
	font-weight: 700;
	text-decoration: none;
	transition: border-color 220ms ease, background 220ms ease, color 220ms ease, transform 220ms ease;
}
.kinetic-contact-pill:hover, .kinetic-contact-pill:focus-visible { border-color: var(--kinetic-ink); background: var(--kinetic-ink); color: var(--kinetic-paper); transform: translateY(-2px); }
.kinetic-contact-pill__icon { width: 1.05rem; height: 1.05rem; flex-shrink: 0; }

.kinetic-contact-form { display: grid; gap: 1.1rem; }
.kinetic-contact-form label { display: grid; gap: .4rem; font-size: .64rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: rgb(40 32 64 / 68%); }
.kinetic-contact-form input, .kinetic-contact-form textarea {
	padding: .75rem .9rem;
	border: 1.5px solid var(--kinetic-line);
	border-radius: .4rem;
	background: var(--kinetic-paper);
	font: inherit;
	font-size: .85rem;
	color: var(--kinetic-ink);
	transition: border-color 200ms ease;
}
.kinetic-contact-form input:focus-visible, .kinetic-contact-form textarea:focus-visible { outline: none; border-color: var(--kinetic-accent); }
.kinetic-contact-form textarea { resize: vertical; min-height: 7rem; }
/* Invertido a pedido do cliente (2026-09-04): fundo tinta, texto quase-
   branco — antes era contorno (fundo transparente/creme, texto tinta),
   ficava pouco destacado ao lado dos campos do formulário (mesmo tom do
   painel). */
.kinetic-contact-form .kinetic-button { justify-self: start; margin-top: .4rem; background: var(--kinetic-ink); border-color: var(--kinetic-ink); color: var(--color-paper-on-ink); }
.kinetic-contact-form .kinetic-button:hover, .kinetic-contact-form .kinetic-button:focus-visible { background: var(--kinetic-ink-soft); }
.kinetic-contact-form .kinetic-button img { filter: brightness(0) invert(1); }
.kinetic-contact-form__status { margin: 0; min-height: 1.1em; font-size: .72rem; color: rgb(40 32 64 / 68%); opacity: 0; transition: opacity 400ms ease; }
.kinetic-contact-form__status.is-visible { opacity: 1; }

/* Página de portfólio (src/pages/template-cinetico/portfolio.astro),
   segunda tentativa — a primeira (grelha de cartões estática) foi
   rejeitada por simples de mais. Cada projeto é agora a sua própria
   secção de largura total, tinta/papel alternado como o resto do
   template: só as secções de tinta precisam de clip-path (diagonal contra
   o papel da página, que já é a cor de fundo por omissão — papel sobre
   papel não mostra corte nenhum, por isso essas ficam sem clip). */
.kinetic-portfolio-hero {
	position: relative;
	/* --kinetic-hero-seam-rise PRÓPRIA (não var(--kinetic-seam-rise) da
	   página) — 2026-09-04, uniformização pedida pelo cliente: o hero desta
	   página (e o da página de projeto individual, que reutiliza esta
	   mesma classe) tem de ter o MESMO tamanho e a MESMA diagonal dos
	   heroes da home/contactos (6.55vw), independentemente do
	   --kinetic-seam-rise mais raso (2.4vw) que a PÁGINA usa para as
	   secções de projeto mais abaixo (esse fica como está — reduzido de
	   propósito porque essas secções são ~35% mais baixas; a mesma
	   diagonal de 6.55vw nelas ficava uma cunha, não uma faixa suave, ver
	   comentário em .kinetic-page.kinetic-portfolio-page). Min-height
	   também copiado ponto por ponto de .kinetic-contact-hero (já igualado
	   com a home em todas as larguras) — ver as mesmas media queries mais
	   abaixo no ficheiro. */
	--kinetic-hero-seam-rise: 6.55vw;
	min-height: 24rem;
	padding: 8.5rem 0 6rem;
	background: radial-gradient(circle at 82% 30%, rgb(85 69 126 / 22%), transparent 40%), var(--kinetic-ink);
	color: var(--kinetic-paper);
	clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--kinetic-hero-seam-rise)), 0 100%);
	overflow: clip;
}
/* Exceção SÓ na página de listagem (2026-09-07, pedido do cliente): o
   1º projeto da pilha (--first, ver comentário completo mais abaixo) tinha
   o topo a 6.55vw (para bater com este hero) e o fundo a 2.4vw (para bater
   com o 2º projeto) — os dois lados cortados em ângulos diferentes davam
   uma lateral mais curta que a dos outros projetos, um trade-off geométrico
   já provado e aceite antes (ver histórico junto a --first). Em vez de
   mexer no corte do projeto, a ideia do cliente foi mexer no hero: se o
   PRÓPRIO hero cortar ao mesmo ângulo raso da pilha (var(--kinetic-seam-rise),
   2.4vw/4vw mobile) em vez de 6.55vw, o 1º projeto pode ter os dois lados
   iguais a esse mesmo ângulo — fica geometricamente idêntico aos restantes.
   Custo aceite: o hero desta página deixa de ter a mesma diagonal íngreme
   da home/contactos (violando a "uniformização" pedida em 2026-09-04) — é
   o troco consciente pedido agora. A página de projeto individual (que
   reutiliza a mesma classe .kinetic-portfolio-hero) NÃO está dentro de
   .kinetic-portfolio-page, por isso mantém o hero íngreme original,
   igual à home/contactos, sem esta exceção. */
.kinetic-page.kinetic-portfolio-page .kinetic-portfolio-hero { --kinetic-hero-seam-rise: var(--kinetic-seam-rise); }
/* Sombra no fundo do hero — mesma técnica da sombra das secções de
   projeto (.kinetic-portfolio-project__filmstrip::after), corrigida
   depois de apanhada errada pelo cliente (2026-09-04):
   1) Sinal do skewY estava trocado. O corte do hero é
      `100% 0, 100% calc(100% - seam-rise), 0 100%` — ao contrário da
      variante --a (corta o canto INFERIOR ESQUERDO, sobe à direita), o
      hero corta o canto INFERIOR DIREITO: em x=0 o fundo está em 100%
      (cheio), em x=100% sobe para 100%-seam-rise. Isso é skewY NEGATIVO
      (desloca para CIMA ao crescer x), não positivo — copiei o sinal da
      --a sem voltar a verificar a direção real do hero.
   2) "top" também estava errado — para a variante --a o ponto de arranque
      (x=0 do skew) já está subido (100%-seam-rise); para o hero o ponto
      de arranque em x=0 é o fundo CHEIO (100%), sobe só a caminho de
      x=100% (é o skew que faz essa subida, não o "top"). */
.kinetic-portfolio-hero::after {
	content: '';
	position: absolute;
	inset: auto 0 auto 0;
	top: calc(100% - .8rem);
	height: .8rem;
	z-index: 2;
	transform-origin: 0 0;
	transform: skewY(-3.75deg);
	background: linear-gradient(180deg, rgb(8 5 18 / 0%) 0%, rgb(8 5 18 / 55%) 100%);
	pointer-events: none;
}
@media (max-width: 767px) {
	.kinetic-portfolio-hero::after { transform: skewY(-4.64deg); } /* atan(8.12/100) — par móvel de --kinetic-hero-seam-rise. */
}
/* Sombra do hero ajustada ao MESMO ângulo raso só na listagem (ver exceção
   de --kinetic-hero-seam-rise, junto à regra base do hero) — sem isto a
   sombra continuava inclinada a 3.75deg/-4.64deg (o ângulo antigo, 6.55vw)
   enquanto o corte real já estava a 1.4deg/2.3deg (2.4vw/4vw), ficando
   visivelmente desalinhada da diagonal (era exatamente o risco que o
   cliente avisou: "esta alteração pode causar problema na sombra").
   -1.4deg/-2.3deg (sinal negativo, não 1.4/2.3) porque este hero corta o
   canto INFERIOR DIREITO (ver nota de sinal na regra base do ::after, mais
   acima) — mesma direção de sempre, só o ângulo mudou. */
.kinetic-page.kinetic-portfolio-page .kinetic-portfolio-hero::after { transform: skewY(-1.4deg); }
@media (max-width: 767px) {
	.kinetic-page.kinetic-portfolio-page .kinetic-portfolio-hero::after { transform: skewY(-2.3deg); }
}
/* Overrides de min-height por largura, copiados ponto por ponto de
   .kinetic-contact-hero (já igualada com a home em toda a gama de
   larguras) — POSTOS DEPOIS da regra base acima, de propósito: a mesma
   armadilha de especificidade já documentada várias vezes neste ficheiro
   (regra igual posta ANTES da base perde sempre, venha a media query que
   vier). Confirmado com medição real (getBoundingClientRect), não só a
   olho — a 1ª tentativa desta correção pôs estas regras demasiado cedo no
   ficheiro e continuavam sem efeito nenhum. */
@media (min-width: 1101px) and (max-width: 1759px) {
	.kinetic-portfolio-hero { min-height: 520px; }
}
@media (min-width: 1760px) {
	.kinetic-portfolio-hero { min-height: 520px; }
}
.kinetic-portfolio-hero__inner { position: relative; z-index: 1; }
/* Quebra fixa em 2 linhas ("Trabalho que" / "fala por si.", cada uma o
   seu próprio <span data-hero-line> em portfolio.astro) — pedido
   explícito, em vez de depender do wrap automático de max-width/ch, que
   variava consoante a largura da janela. */
.kinetic-portfolio-hero h1 { margin: 0; max-width: 13ch; font-family: var(--kinetic-font); font-weight: 800; letter-spacing: -.06em; line-height: .98; font-size: clamp(2.6rem, 5vw, 4.4rem); }
.kinetic-portfolio-hero h1 span { display: block; }
.kinetic-portfolio-hero__lead { max-width: 30rem; margin: 1.5rem 0 0; font-size: clamp(.85rem, 1vw, 1rem); line-height: 1.65; color: rgb(240 232 224 / 78%); }

/* Terceira tentativa desta secção. Cada projeto é uma faixa de imagens de
   largura total (não uma imagem só ao lado de texto) — o título vive numa
   cápsula sobre a faixa. `overflow: clip` aqui é o que garante que as
   imagens de uma secção nunca aparecem na secção vizinha (pedido
   explícito) — contém tanto a forma do clip-path como a animação de
   entrada, que começa fora da caixa. */
.kinetic-portfolio-project {
	position: relative;
	/* Tem de ser exatamente a mesma medida que --kinetic-seam-rise (usada no
	   clip-path abaixo), não um rem fixo aproximado. --kinetic-seam-rise é
	   vw (escala com a largura da janela); um -5.5rem fixo só cancelava a
	   costura à volta dos 1344px de largura (onde 6.55vw == 5.5rem) — abaixo
	   disso sobrepunha de mais, acima abria um afastamento visível entre
	   secções que crescia com a janela. Corrigido a 2026-09-03 (apanhado
	   pelo utilizador: "as próprias secções em janelas maiores ficam com
	   afastamento"). Com calc() a ligar as duas, a costura fecha
	   perfeitamente em qualquer largura, incluindo o breakpoint mobile que
	   redefine --kinetic-seam-rise para 8.12vw. "-1px" extra de propósito:
	   o browser arredonda o valor de --kinetic-seam-rise (vw) de forma
	   independente para o margin-top e para o clip-path (duas propriedades
	   diferentes, podem arredondar o mesmo calc() para sub-pixels
	   diferentes) — um afastamento de meio pixel já expõe uma linha fina do
	   fundo da página por trás das imagens (pedido explícito: "não há
	   espaçamento entre secção... dá para notar o fundo branco"). 1px extra
	   de sobreposição é impercetível e garante que nunca há falha, mesmo
	   com arredondamento diferente entre as duas propriedades. */
	margin-top: calc(-1px - var(--kinetic-seam-rise));
	min-height: clamp(9rem, 27vh, 15.5rem);
	overflow: clip;
}
/* Duas variantes, cada uma amarra geometria + tom + lado de entrada das
   imagens num só pacote, para não ficarem a variar independentemente sem
   critério. A direção de cada corte tem de CONTINUAR a diagonal da secção
   anterior (o hero, ou a secção da outra variante) — não é uma escolha
   livre por secção. O hero corta o canto inferior direito (fica mais alto,
   expondo o que vem a seguir) e mantém o esquerdo cheio até ao fundo; por
   isso o topo da secção seguinte tem de ter o esquerdo cortado (para não
   sobrepor a tinta do hero que ainda lá está) e o direito cheio (para tapar
   o canto que o hero deixou exposto). Foi ao contrário nesta variante (--a)
   até 2026-09-03 — corrigido trocando os polígonos entre --a e --b:
   --a: topo/fundo ESQUERDO cortado, DIREITO cheio — título fica à direita
        (é aí que a secção nunca é cortada); fundo papel; imagens entram
        pela direita.
   --b: espelhado — ESQUERDO cheio, DIREITO cortado, título à esquerda,
        fundo tinta, imagens entram pela esquerda.
   O "canto maior" é sempre onde a secção NÃO foi cortada pela diagonal —
   por isso é aí que a cápsula do título tem garantidamente espaço cheio,
   nunca a ficar em cima do próprio corte. */
.kinetic-portfolio-project--a {
	background: var(--kinetic-paper);
	clip-path: polygon(0 var(--kinetic-seam-rise), 100% 0, 100% 100%, 0 calc(100% - var(--kinetic-seam-rise)));
}
/* .kinetic-portfolio-project--first — reintroduzida (2026-09-05, depois de
   removida mais cedo no mesmo dia). Histórico completo, para não se
   repetir o ciclo:
   1) Corte de topo do 1º projeto = 6.55vw (igual ao hero) → ângulo
      perfeito na costura com o banner, mas a lateral ESQUERDA deste
      projeto ficava mais curta que a do 3º.
   2) --kinetic-seam-rise da PÁGINA INTEIRA posto a 6.55vw (uniforme em
      toda a pilha, pedido explícito: "os restantes projectos têm de
      ficar com o mesmo angulo... isto é uma empresa de design") →
      resolvia a inconsistência entre projetos, mas cortava ~2×94.32px de
      secções com só 248px de altura — lateral curta de todos os
      projetos a ~59px, apanhado como "demasiado apertado nas pontas...
      a extremidade mais estreita tem de abrir mais".
   3) Esta versão: só a costura que TEM de bater com o hero (o topo do 1º
      projeto) fica a 6.55vw — voltou --first. Todas as OUTRAS costuras
      da pilha (fundo do 1º projeto, topo+fundo de todos os seguintes)
      usam de novo --kinetic-seam-rise da página (2.4vw, raso) — exatamente
      o pedido: "basta aumentar o angulo das diagonais inferiores dos
      projectos" [inferiores = entre projetos, não a que toca o hero].
      A prova geométrica do passo (1) mantém-se válida — a lateral
      esquerda do 1º projeto continua mais curta que a do 3º (só essa,
      não as restantes), é o único trade-off que resta e já estava aceite
      antes do passo (2). Sem overlap nesta costura (topo=6.55vw dos dois
      lados, ver nota no --first abaixo) — a sombra NATIVA do hero
      (.kinetic-portfolio-hero::after) chega sozinha, sem elemento extra. */
/* 2026-09-07: topo do --first deixou de ser 6.55vw fixo — passa a usar
   var(--kinetic-seam-rise), o MESMO ângulo do hero agora (ver exceção
   junto à regra base de .kinetic-portfolio-hero). Topo e fundo do --first
   ficam assim exatamente iguais aos dos outros projetos — geometricamente
   idêntico ao --a/--b sem --first (a prova do passo 1, lateral esquerda
   mais curta que a do 3º projeto, deixa de se aplicar: os dois ângulos que
   a causavam agora são o mesmo). As regras --first e margin-top abaixo
   ficam por questão de clareza/histórico (identificam explicitamente esta
   secção como a primeira da pilha), mas já não produzem nenhum corte
   diferente da regra base --a/--b — não apagadas para não obrigar a mexer
   no markup (portfolio.astro continua a marcar --first normalmente). */
.kinetic-portfolio-project--a.kinetic-portfolio-project--first {
	clip-path: polygon(0 var(--kinetic-seam-rise), 100% 0, 100% 100%, 0 calc(100% - var(--kinetic-seam-rise)));
}
.kinetic-portfolio-project--b.kinetic-portfolio-project--first {
	clip-path: polygon(0 0, 100% var(--kinetic-seam-rise), 100% calc(100% - var(--kinetic-seam-rise)), 0 100%);
}
.kinetic-portfolio-project.kinetic-portfolio-project--first {
	margin-top: calc(-1px - var(--kinetic-seam-rise));
}
.kinetic-portfolio-project--b {
	background: var(--kinetic-ink);
	clip-path: polygon(0 0, 100% var(--kinetic-seam-rise), 100% calc(100% - var(--kinetic-seam-rise)), 0 100%);
}

/* Faixa de imagens — enche a secção por completo (position:absolute,
   inset:0), por baixo da cápsula do título. Cada célula começa fora da
   secção (ver kinetic-template.ts, initPortfolioReveals) e desliza até à
   posição natural do flex — é o próprio layout de flexbox que as deixa
   "encostadas" uma às outras, o JS só anima a chegada, escalonada no
   tempo, não recalcula posições. */
.kinetic-portfolio-project__filmstrip { position: absolute; inset: 0; z-index: 0; display: flex; }
.kinetic-portfolio-project__cell { position: relative; flex: 1 1 0; height: 100%; overflow: hidden; }
/* Filtro de saturação (0.65) removido a pedido explícito, 2026-09-04 —
   a coerência entre as fotos fica só a cargo da tela de cor (regra
   seguinte), não de as desaturar. */
.kinetic-portfolio-project__cell img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Tela de cor da marca por cima da faixa, alternando por projeto — pedido
   explícito ("tela beje/azul com transparência... vai ajudar a perceber
   que imagens pertencem ao projecto"). Ligada à variante (--a/--b), que
   já alterna por projeto (ver .kinetic-portfolio-project--a/--b) — usa-se
   o mesmo alternar em vez de um contador à parte, para nunca poderem
   dessincronizar. --a (projetos 1, 3, 5, ...): papel/bege. --b (projetos
   2, 4, 6, ...): tinta/azul (a cor "azul" mais próxima nos tokens da
   marca, sem token azul dedicado — mesmo raciocínio da linha divisória
   já removida, secção 33/35). */
.kinetic-portfolio-project--a .kinetic-portfolio-project__filmstrip::before { background: rgb(240 232 224 / 30%); }
.kinetic-portfolio-project--b .kinetic-portfolio-project__filmstrip::before { background: rgb(40 32 64 / 35%); }
.kinetic-portfolio-project__filmstrip::before { content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none; }
/* Véu constante sobre toda a faixa — sem isto, uma imagem clara ao lado
   direito da cápsula ainda compete com o texto por contraste mesmo fora
   da própria cápsula. Por cima da tela de cor (mesmo z-index, mas depois
   no DOM — ::before pinta primeiro), para a legibilidade do título nunca
   depender da variante. */
.kinetic-portfolio-project__filmstrip::after { content: ''; position: absolute; inset: 0; z-index: 1; background: linear-gradient(180deg, rgb(8 5 18 / 0%) 55%, rgb(8 5 18 / 38%) 100%); pointer-events: none; }
/* Sombra no FUNDO de cada secção de projeto — dá a sensação de que a
   secção SEGUINTE está sempre à frente desta. Aplicada agora a TODAS as
   secções de projeto (pedido explícito, 2026-09-04: "aplicar sombra em
   todas as secções de projecto"), não só à "Vinha do Vale" — por isso já
   não depende de um atributo por projeto ([data-shadow-bottom] foi
   removido de portfolio.astro): a regra prende-se só à classe
   --a/--b, que TODO projeto tem sempre, incluindo os que o cliente vier
   a inserir pelo back-end (nada a replicar manualmente aqui).
   Histórico da geometria (porque é um skewY() e não um linear-gradient
   rodado) e das rondas de afinação de distância/intensidade em
   docs/kinetic-template-fixes.md §41-48 — resumo: a sombra vive em quem
   fica "por baixo" (nunca no topo da seguinte, que lia ao contrário); um
   linear-gradient (mesmo com o eixo rodado) posiciona as percentagens
   relativas à CAIXA INTEIRA da secção, não à forma diagonal recortada
   por cima — por isso a faixa é desenhada reta e só depois inclinada
   fisicamente com transform: skewY(atan(--kinetic-seam-rise / 100%)),
   ancorada em transform-origin: 0 0, com sinal oposto entre --a/--b
   (diagonais espelhadas) e ângulo recalculado no breakpoint mobile
   (--kinetic-seam-rise próprio da página, 2.4vw desktop / 4vw mobile). */
.kinetic-portfolio-project--a .kinetic-portfolio-project__filmstrip::after,
.kinetic-portfolio-project--b .kinetic-portfolio-project__filmstrip::after {
	inset: auto 0 auto 0;
	height: .80rem;
	transform-origin: 0 0;
	background: linear-gradient(180deg, rgb(8 5 18 / 0%) 0%, rgb(8 5 18 / 55%) 100%);
}
/* skewY de volta a 1.4deg — --kinetic-seam-rise da pilha voltou a 2.4vw
   (ver .kinetic-page.kinetic-portfolio-page, mais acima), atan(2.4/100)
   ≈ 1.4deg. Esta sombra é sempre entre DOIS projetos consecutivos, nunca
   toca o hero — segue sempre o ritmo raso da pilha, mesmo agora que o
   1º projeto tem um topo especial (--first, mais acima); o FUNDO do 1º
   projeto (de onde esta sombra "--a" nasce) continua a usar
   --kinetic-seam-rise normal, não o 6.55vw do --first. */
.kinetic-portfolio-project--a .kinetic-portfolio-project__filmstrip::after { top: calc(100% - var(--kinetic-seam-rise) - .80rem); transform: skewY(1.4deg); }
.kinetic-portfolio-project--b .kinetic-portfolio-project__filmstrip::after { top: calc(100% - .80rem); transform: skewY(-1.4deg); }

/* Cápsula do título — sempre no canto inferior "cheio" da variante (ver
   comentário acima). Véu escuro translúcido próprio, para o texto nunca
   depender do tom da imagem por baixo — por isso é sempre cor de papel,
   nas duas variantes, ao contrário do resto do template onde a cor do
   texto segue o tom da secção. */
/* Um título só, sem número — pedido explícito (o índice "01/02/03/04" foi
   removido do markup; ver portfolio.astro). */
.kinetic-portfolio-project__badge {
	position: absolute;
	z-index: 2;
	/* Bem mais afastado do fundo da secção do que antes (clamp(.6rem,
	   1.5vw, 1.1rem)) — pedido explícito: "os títulos... têm de subir
	   mais, estão muito perto do fundo das secções." */
	bottom: clamp(1.8rem, 4vw, 3rem);
	display: inline-block;
	max-width: min(30rem, 82vw);
	padding: .6rem 1.1rem;
	border-radius: 999px;
	/* rgb(8 5 18) era um preto quase puro, não a cor "azul" (tinta) da
	   marca — trocado por --kinetic-ink (#282040 = rgb(40 32 64)),
	   pedido explícito 2026-09-05. */
	background: rgb(40 32 64 / 58%);
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
	color: var(--kinetic-paper);
}
.kinetic-portfolio-project--a .kinetic-portfolio-project__badge { right: clamp(1.25rem, 4vw, 3rem); }
.kinetic-portfolio-project--b .kinetic-portfolio-project__badge { left: clamp(1.25rem, 4vw, 3rem); }

.kinetic-portfolio-project__badge h2 { margin: 0; font-family: var(--kinetic-font); font-weight: 800; letter-spacing: -.03em; line-height: 1.15; font-size: clamp(.85rem, 1.6vw, 1.15rem); white-space: normal; overflow-wrap: break-word; }
/* Só os projetos reais (com página individual, single-projeto.php) têm o
   título como link — os fictícios de demonstração ficam texto simples. */
/* Secção inteira é o link (não só o título) — "stretched link": <a> vazio,
   cobre a secção toda (position:absolute; inset:0), por cima da faixa de
   imagens e da cápsula do título, sem lhes tapar o conteúdo (sem
   background/texto próprios). Efeito de hover súbtil (zoom muito ligeiro
   na faixa) para se perceber que é clicável, sem sublinhado — pedido
   explícito do cliente (2026-09-04): "retirar o sublinhado". */
.kinetic-portfolio-project { position: relative; }
.kinetic-portfolio-project__link { position: absolute; inset: 0; z-index: 5; }
.kinetic-portfolio-project:has(.kinetic-portfolio-project__link):hover .kinetic-portfolio-project__cell img { transform: scale(1.025); }
.kinetic-portfolio-project__cell img { transition: transform 500ms cubic-bezier(.16, 1, .3, 1); }

/* Padding bottom maior que o topo de propósito: o rodapé sobrepõe-se
   sempre 5.5rem para cima (.kinetic-footer, margin-top: -5.5rem — a mesma
   costura usada no resto do site) — com a secção alta original havia folga
   de sobra; depois de reduzir a altura (pedido do utilizador) o botão
   ficava metade dentro do rodapé. O bottom fica sempre >5.5rem para o
   conteúdo nunca colidir com essa sobreposição, "subindo" o conteúdo em
   vez de baixar o rodapé. */
.kinetic-portfolio-cta { padding: clamp(2.5rem, 4vw, 3.5rem) 0 clamp(7.25rem, 8vw, 7.75rem); background: var(--kinetic-paper); text-align: center; }
.kinetic-portfolio-cta__inner { display: flex; flex-direction: column; align-items: center; gap: 1.25rem; }
/* Sem max-width: uma só linha (pedido explícito) em qualquer largura onde
   o texto couber; só quebra em ecrãs realmente estreitos, onde não há
   alternativa sem ficar minúsculo. */
.kinetic-portfolio-cta h2 { margin: 0; font-family: var(--kinetic-font); font-weight: 800; letter-spacing: -.045em; line-height: 1.02; font-size: clamp(1.05rem, 2.6vw, 1.9rem); color: var(--kinetic-ink); }
.kinetic-portfolio-cta__accent { color: var(--kinetic-accent); }
.kinetic-portfolio-cta .kinetic-button--cream { border-color: var(--kinetic-ink); color: var(--kinetic-ink); background: transparent; }
.kinetic-portfolio-cta .kinetic-button--cream img { filter: none; }
.kinetic-portfolio-cta .kinetic-button--cream:hover { background: var(--kinetic-ink); color: var(--kinetic-paper); }
.kinetic-portfolio-cta .kinetic-button--cream:hover img { filter: brightness(0) invert(1); }

/* ===== Página individual de projeto (single-projeto.php, WordPress) =====
   3ª versão (2026-09-04) — as duas primeiras foram rejeitadas por se
   afastarem do que já existe no resto do site. Esta versão não inventa
   NADA de novo: é a composição exata de 3 peças já existentes noutras
   páginas, pedida explicitamente pelo cliente:
     Secção 1 — o MESMO hero das outras páginas (.kinetic-portfolio-hero,
       reutilizado tal e qual, só com o título do projeto).
     Secção 2 — subtítulo + texto descritivo + tags, secção de texto
       simples com a mesma costura diagonal da .kinetic-intro (home).
     Secção 3 — o MESMO mosaico "Trabalho que fala por si" da home
       (.kinetic-collage/.kinetic-collage__item, ver acima) generalizado
       para qualquer número de imagens (a home tem sempre 6 fixas; um
       projeto pode ter mais) — o ciclo de 6 molduras repete-se sozinho
       via grid auto-placement (ver nota junto a --a.:--f, mais acima).
       Cada moldura é clicável (initLightbox já existente) e continua a
       flutuar como na home (initCollage já existente).
   Zero JS novo. */

/* Título do hero (secção 1) — herda a quebra de linha natural de
   .kinetic-portfolio-hero h1, mas com largura E tamanho de letra PRÓPRIOS
   (não os 13ch/4.4rem da listagem): medido com getBoundingClientRect() em
   vez de a olho (2026-09-04) — mesmo com a quebra de linha já ativa, a
   caixa do título (13ch ⇒ até 694px de largura a 1440px) continuava a
   ultrapassar o início real da marca-d'água do logótipo
   (.kinetic-pattern::after, calculado em 651px à mesma largura), texto e
   logótipo a cruzarem-se. Coluna mais estreita (min(30rem, 42%), sempre
   proporcional à largura do hero, não um ch fixo) + fonte mais pequena
   (3.4rem, não 4.4rem) dão uma margem real (~80px) antes do logótipo,
   confirmada por medição, não só visualmente. Seletor com ID
   (`h1#project-title`) para garantir que ganha à regra partilhada da
   listagem — uma classe sozinha já tinha perdido a esta por
   especificidade CSS antes (ver histórico no commit). */
.kinetic-portfolio-hero h1#project-title { max-width: min(30rem, 42%); font-size: clamp(1.9rem, 4vw, 3.4rem); overflow-wrap: break-word; }

/* Secção 2 — texto simples, mesma costura diagonal de .kinetic-intro
   (paper → a secção seguinte, escura, tem o topo achatado e não compete
   pela mesma linha — mesma regra "uma só secção desenha cada costura"
   usada em todo o resto do template).
   Corte de TOPO corrigido de "4%" (percentagem da própria altura da
   secção) para 6.55vw (a mesma inclinação do hero, --kinetic-hero-seam-
   rise) — um corte em % nunca acompanha um corte em vw: a % desenha uma
   inclinação diferente consoante a altura desta secção, quase nunca igual
   aos 6.55vw do hero por baixo, dando um VINCO visível na diagonal
   exatamente na costura (apanhado pelo cliente a pixel, 2026-09-04, "a
   diagonalidade do banner tem um vinco" — confirmado por screenshot,
   comparado com a mesma correção já feita para .kinetic-portfolio-project
   --first na listagem). Ao contrário da listagem, esta secção é ÚNICA
   (não faz parte de uma pilha alternada a/b) — sem constrangimento de
   "lateral igual a outra secção", por isso o corte podia bater exatamente
   com o hero (6.55vw nos dois lados). Essa versão funcionava (sem GAP/
   overlap), mas dependia de DOIS calc() independentes (aqui e no hero)
   acertarem ao pixel — exatamente a classe de bug que rebentou a seguir
   na listagem (.kinetic-portfolio-project--first, sombra 1-2px dentro da
   foto, 2026-09-04). Esta secção não tem NENHUMA razão para correr esse
   risco: ao contrário do 1º projeto da listagem, aqui não há foto a
   "empilhar" sobre o hero — é só texto centrado em fundo papel. Trocado
   pela técnica já usada em .kinetic-contact-panel (o painel equivalente
   nos Contactos): NENHUM corte próprio no topo, NENHUM margin-top — a
   secção fica em fluxo normal, mesmo cor (papel) da página por baixo do
   hero. A diagonal que se vê continua a ser só a do hero (a única a
   desenhá-la); o "entalhe" triangular que o corte do hero deixa exposto
   mostra o fundo da própria página, papel igual ao desta secção — sem
   coincidência nenhuma para acertar, zero risco de vinco OU sobreposição
   por 1px, em qualquer largura de janela. O corte de FUNDO (0 95%)
   mantém-se — essa é a costura com a secção 3, sem relação com o hero. */
.kinetic-project-info {
	position: relative;
	z-index: 3;
	/* padding-top reduzido de clamp(6rem, 9vw, 8.5rem) — sobrava do tempo em
	   que o clip-path tinha corte próprio no topo (4%) e este padding
	   também tinha de compensar esse corte a comer espaço vertical. Sem
	   corte próprio (ver comentário no clip-path abaixo), aquele valor só
	   deixava um vazio grande a mais entre a costura do hero e o
	   subtítulo (apanhado pelo cliente, 2026-09-04: "demasiado espaço
	   vazio até ao subtitulo e a descrição aparecerem"). */
	/* padding-bottom também reduzido (era clamp(5rem, 7vw, 6.5rem)) — pedido
	   explícito, 2026-09-04: "tudo o que está abaixo do banner... tem de
	   subir, está tudo muito despido". A secção 3 já sobe por cima desta
	   via margin-top negativo (ver .kinetic-project-showcase), mas com
	   tanto padding aqui embaixo o efeito ficava anulado — o mosaico
	   nunca chegava perto o suficiente do texto. */
	padding: clamp(3rem, 4.5vw, 4rem) 0 clamp(2.5rem, 3.5vw, 3.25rem);
	background: var(--kinetic-paper);
	text-align: center;
	clip-path: polygon(0 0, 100% 0, 100% 100%, 0 95%);
	overflow: clip;
}
.kinetic-project-info__inner { position: relative; z-index: 1; max-width: 46rem; margin: 0 auto; }
.kinetic-project-info h2 { margin: 0; font-family: var(--kinetic-font); font-weight: 800; letter-spacing: -.03em; line-height: 1.15; font-size: clamp(1.5rem, 2.6vw, 2.1rem); color: var(--kinetic-ink); }
.kinetic-project-prose { margin: 1.35rem 0 0; font-size: .92rem; line-height: 1.75; color: rgb(40 32 64 / 75%); }
.kinetic-project-prose p + p { margin-top: .85rem; }
.kinetic-project-tags { display: flex; flex-wrap: wrap; justify-content: center; gap: .5rem; list-style: none; margin: 1.75rem 0 0; padding: 0; }
.kinetic-project-tags li { font-size: .66rem; font-weight: 800; letter-spacing: .03em; padding: .34rem .85rem; border: 1.5px solid var(--kinetic-ink); border-radius: 999px; color: var(--kinetic-ink); transition: background 200ms ease, color 200ms ease; }
.kinetic-project-tags li:hover { background: var(--kinetic-ink); color: var(--kinetic-paper); }

/* Ícone colossal de fundo — pedido explícito, 2026-09-04 ("espeta um icon
   colossal na secção de subtítulo+texto descritivo, pode ser que
   ajuda"), mesma técnica já usada em .kinetic-services__mask (marca em
   symbol-solid-soft.svg, tamanho gigante, opacidade baixa, rodado) — não
   uma solução nova, reaproveita o que já existe no resto do template
   para esta secção deixar de parecer tão "despida". z-index abaixo do
   texto (.kinetic-project-info__inner, z-index:1) para nunca competir
   com a leitura. */
.kinetic-project-info__mark {
	position: absolute;
	top: 50%;
	left: 50%;
	z-index: 0;
	/* 1100px (herdado de .kinetic-services__mask, uma secção MUITO mais
	   alta) ficava quase todo fora da área visível desta secção — mais
	   baixa, só subtítulo+texto curto — sobrava só uma fatia do símbolo,
	   irreconhecível como marca (apanhado por screenshot, 2026-09-04).
	   Tamanho próprio, holgado o suficiente para "colossal" mas cabendo
	   inteiro (as duas formas, ponto+vírgula) dentro da altura típica
	   desta secção. */
	width: clamp(22rem, 30vw, 30rem);
	height: clamp(22rem, 30vw, 30rem);
	background: url('../images/symbol-solid-soft.svg') center / contain no-repeat;
	filter: brightness(0) opacity(.1);
	transform: translate(-50%, -50%) rotate(-16deg);
	pointer-events: none;
}

/* Secção 3 — mesmo tratamento de fundo/costuras de .kinetic-work (topo
   achatado, a diagonal de cima já vem da secção 2; fundo diagonal próprio
   para a costura com o rodapé). */
/* Mesmo padding/overflow de .kinetic-work (secção 3 da home) — pequeno de
   propósito, para a secção não crescer mais do que o necessário; o
   mosaico tem permissão para invadir a secção 2 (acima) e a secção 4
   (abaixo), overflow:visible deixa isso acontecer sem cortar nada. */
.kinetic-project-showcase { position: relative; z-index: auto; margin-top: -5.5rem; padding: 2.75rem 0; background: transparent; overflow: visible; }
.kinetic-project-showcase::before { position: absolute; inset: 0; z-index: 0; background: var(--kinetic-ink); clip-path: polygon(0 0, 100% 0, 100% 97%, 0 100%); content: ''; pointer-events: none; }
.kinetic-project-showcase .kinetic-pattern--work { z-index: 1; clip-path: polygon(0 0, 100% 0, 100% 97%, 0 100%); }
.kinetic-project-showcase__inner { position: relative; z-index: var(--kinetic-z-text); }
.kinetic-project-showcase h2 { position: relative; z-index: var(--kinetic-z-text); margin: 0 0 2.5rem; max-width: 12ch; font-family: var(--kinetic-font); font-weight: 800; letter-spacing: -.045em; line-height: 1; font-size: clamp(2rem, 3.2vw, 3rem); color: var(--kinetic-paper); text-align: center; }
/* Cada moldura passa a ser um <button> clicável (a home usa <figure>,
   decorativo) — precisa de pointer-events próprio, .kinetic-collage__item
   base tem pointer-events:none (correto para a home, onde não é
   clicável). */
.kinetic-project-mosaic .kinetic-collage__item { pointer-events: auto; cursor: zoom-in; border: none; padding: 0; background: var(--kinetic-ink-soft); }
.kinetic-project-mosaic .kinetic-collage__item img { transition: transform 500ms cubic-bezier(.16, 1, .3, 1); }
.kinetic-project-mosaic .kinetic-collage__item:hover img { transform: scale(1.045); }
/* Repetições do ciclo de 6 (imagem 7ª em diante): sem respiro extra de
   propósito — as imagens têm permissão para se sobrepor/invadir a linha
   anterior, tal como a própria secção invade a 2 e a 4 (pedido do
   cliente, 2026-09-04, "diminuir e deixar invadir"). */
.kinetic-project-mosaic .kinetic-collage__item:nth-child(6n + 1):not(:first-child) { margin-top: -2rem; }

/* Lightbox — display:none/.is-open o único trabalho do CSS; o fade/scale
   de entrada e saída é sempre feito pelo GSAP em kinetic-template.ts
   (initLightbox), nunca por transição CSS na mesma propriedade. */
.kinetic-lightbox { display: none; position: fixed; inset: 0; z-index: 300; align-items: center; justify-content: center; }
.kinetic-lightbox.is-open { display: flex; }
.kinetic-lightbox__backdrop { position: absolute; inset: 0; border: none; padding: 0; background: rgb(16 10 34 / 88%); backdrop-filter: blur(6px); cursor: zoom-out; }
.kinetic-lightbox__img { position: relative; z-index: 1; display: block; max-width: min(92vw, 78rem); max-height: 88vh; width: auto; height: auto; border-radius: .5rem; box-shadow: 0 1.9rem 5rem rgb(0 0 0 / 45%); }
.kinetic-lightbox__close, .kinetic-lightbox__nav {
	position: fixed;
	z-index: 2;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 2.75rem;
	height: 2.75rem;
	border-radius: 50%;
	border: 1.5px solid rgb(248 246 242 / 55%);
	background: rgb(40 32 64 / 60%);
	color: var(--color-paper-on-ink);
	cursor: pointer;
	transition: background 200ms ease, border-color 200ms ease, transform 200ms ease;
}
.kinetic-lightbox__close:hover, .kinetic-lightbox__nav:hover { border-color: var(--color-paper-on-ink); background: rgb(40 32 64 / 80%); }
.kinetic-lightbox__close svg, .kinetic-lightbox__nav svg { width: 1.15rem; height: 1.15rem; }
.kinetic-lightbox__close { top: 1.5rem; right: 1.5rem; }
.kinetic-lightbox__nav { top: 50%; transform: translateY(-50%); }
.kinetic-lightbox__nav:hover { transform: translateY(-50%) scale(1.06); }
.kinetic-lightbox__prev { left: 1.5rem; }
.kinetic-lightbox__next { right: 1.5rem; }

@media (max-width: 900px) {
	.kinetic-project-mosaic { grid-template-columns: repeat(6, 1fr); }
	.kinetic-project-mosaic .kinetic-collage__item--a, .kinetic-project-mosaic .kinetic-collage__item--d { grid-column: 1 / span 3; }
	.kinetic-project-mosaic .kinetic-collage__item--b, .kinetic-project-mosaic .kinetic-collage__item--e { grid-column: 4 / span 3; }
	.kinetic-project-mosaic .kinetic-collage__item--c, .kinetic-project-mosaic .kinetic-collage__item--f { grid-column: 1 / span 6; margin-top: 1rem; }
}

@media (max-width: 640px) {
	.kinetic-lightbox__close, .kinetic-lightbox__nav { width: 2.25rem; height: 2.25rem; }
	.kinetic-lightbox__close { top: .75rem; right: .75rem; }
	.kinetic-lightbox__prev { left: .5rem; }
	.kinetic-lightbox__next { right: .5rem; }
}

@media (max-width: 1100px) {
	.kinetic-header { grid-template-columns: 1fr auto; }
	.kinetic-hero__grid { grid-template-columns: minmax(20rem, .8fr) minmax(28rem, 1.2fr); }
	.kinetic-intro__grid { grid-template-columns: 1fr .42fr; }
	.kinetic-values { grid-column: 1 / -1; grid-template-columns: repeat(2, 1fr); }
	.kinetic-values li:nth-child(odd) { padding-right: 1.5rem; }
	.kinetic-values li:nth-child(even) { padding-left: 1.5rem; }
	.kinetic-services__grid { grid-template-columns: 5.5rem minmax(0, 1fr) minmax(10rem, .45fr); gap: 2rem; }
	.kinetic-footer__base { grid-template-columns: 1fr auto; }
	.kinetic-footer__socials { justify-self: end; }
	/* Altura da secção 1 igualada entre a home e os contactos (768–1100px:
	   a home usa aqui a base 40rem, sem override próprio — os contactos
	   ainda não tinham nenhum, ficavam mais curtos). Sobreposto pelo bloco
	   ≤767px mais abaixo no ficheiro (35rem), que é mais específico para
	   essa faixa. */
	.kinetic-contact-hero { min-height: 32.5rem; }
	.kinetic-portfolio-hero { min-height: 32.5rem; }
}

/* Secção 3 a uma coluna bem antes do mobile. Com texto e mosaico lado a lado,
   a coluna do mosaico ficava com 622px a 1101px e apenas ~390px a 834px — as
   peças caíam para 147px e 97px, tamanho de selo. Empilhado, o mosaico recebe
   a largura toda da shell e as peças não descem dos ~215px em lado nenhum
   abaixo deste limiar. */
@media (max-width: 1400px) {
	.kinetic-work__grid { grid-template-columns: 1fr; gap: 1.5rem; }
	/* Empilhado, o heading passa a ser o primeiro elemento a seguir ao
	   padding-top — sem folga extra aqui, "Trabalho" ficava dentro da cunha
	   creme da costura (0-9% da altura da secção 2, tipicamente 60-110px) e,
	   sendo texto da mesma cor do creme, o topo das letras ficava invisível
	   (creme sobre creme) — parecia cortado. Sobreposto abaixo dos 767px
	   pela regra própria dessa faixa (5.5rem, já suficiente). */
	.kinetic-work { padding: 6.5rem 0 2.75rem; }
}

@media (max-width: 767px) {
	:root { --kinetic-gutter: 1.25rem; }
	.kinetic-page { --kinetic-seam-rise: 8.12vw; }
	/* --kinetic-seam-rise da pilha de projetos volta a ter valor móvel
	   próprio, mais raso (4vw) — mesmo ciclo da versão desktop, ver
	   comentário completo em .kinetic-page.kinetic-portfolio-page (regra
	   base do ficheiro). skewY da sombra do fundo: atan(4/100) ≈ 2.3deg. */
	.kinetic-page.kinetic-portfolio-page { --kinetic-seam-rise: 4vw; }
	.kinetic-portfolio-project--a .kinetic-portfolio-project__filmstrip::after { transform: skewY(2.3deg); }
	.kinetic-portfolio-project--b .kinetic-portfolio-project__filmstrip::after { transform: skewY(-2.3deg); }
	.kinetic-portfolio-project { min-height: 12rem; }
	/* Sem left+right ao mesmo tempo (esticava a cápsula à largura toda,
	   ficando uma barra em vez de uma pílula à volta do título — pedido
	   explícito: "a mascara oval dos titulos fica demasiado esticada, tem
	   de ficar do tamanho dos titulos"). Cada variante mantém só o lado
	   próprio (--a direita, --b esquerda, herdado da regra base), a
	   cápsula volta a moldar-se ao texto. */
	.kinetic-portfolio-project__badge { max-width: min(18rem, 82vw); padding: .5rem .9rem; }
	.kinetic-portfolio-project__badge h2 { font-size: 1rem; }
	.kinetic-document { scroll-padding-top: 4.65rem; }
	.kinetic-header { height: 4.65rem; padding-inline: 1rem; }
	.kinetic-brand { width: 8.35rem; padding: .2rem .3rem; }
	.kinetic-brand img { height: auto; }
	.kinetic-header nav ul { gap: .72rem; }
	.kinetic-header nav a { gap: .28rem; font-size: .5rem; letter-spacing: .02em; }
	.kinetic-nav-icon { width: .76rem; height: .76rem; }
	/* Altura dos 3 heroes (home/contactos/portfolio) deixou de ser forçada
	   por min-height fixo no mobile (2026-09-07, pedido do cliente:
	   "o banner tem de ficar com tamanho em função do conteúdo"). Medido
	   sem nenhum min-height (Playwright, 390px): home 560px, contactos
	   428px, listagem 350px, página de projeto individual (mesma classe
	   .kinetic-portfolio-hero, ver single-projeto.php) só 230px — o antigo
	   min-height:32.5rem (520px) forçava até 290px de espaço vazio nessa
	   última, exatamente a queixa ("fica demasiado vazio"). 14rem (224px)
	   fica como piso de segurança (nunca vincula na prática, todos os 4
	   contextos medidos ficam acima disso) — não removido por completo,
	   para nunca colapsar por completo se um projeto futuro tiver um
	   título vazio ou quase. */
	.kinetic-hero { min-height: 14rem; padding-top: 4.65rem; }
	/* min-height própria apanhada depois do 1º ajuste mobile (2026-09-07) —
	   o utilizador viu no telemóvel real que ainda sobrava espaço vazio a
	   seguir ao botão. Esta era a segunda floor escondida, e pior do que
	   parecia: a regra mobile anterior (30.35rem/486px) já era mais baixa
	   que a base (34.75rem/556px, linha ~338) — ao simplesmente REMOVER a
	   regra mobile (1ª tentativa desta correção), a base maior passou a
	   aplicar-se sem nada a limitá-la, piorando o problema em vez de o
	   resolver (confirmado por medição: 630px, pior que os 560px
	   anteriores). min-height:0 explícito é o que corta a base de vez —
	   o conteúdo real (.kinetic-hero__copy) só precisa de 398px. */
	.kinetic-hero__grid { display: block; min-height: 0; }
	/* Padding de baixo reduzido (5.5rem -> 4.25rem, -20px): pedido "na home
	   está bom, pode encolher um pouco" — a home não estava a ser vinculada
	   pelo min-height (natural já dava 560px), por isso o encolhimento tem
	   de vir do padding, não do piso. */
	.kinetic-hero__copy { max-width: 100%; padding: 2.5rem 0 4.25rem; }
	.kinetic-kicker { margin-bottom: 1.6rem; font-size: .58rem; }
	.kinetic-hero h1 { max-width: 100%; font-size: clamp(2.6rem, 10.4vw, 3.3rem); line-height: .96; }
	.kinetic-contact-hero { min-height: 14rem; padding: 6.5rem 0 4rem; }
	/* Hero da listagem/página de projeto: a --kinetic-hero-seam-rise
	   própria continua a subir para 8.12vw aqui (o par móvel do 6.55vw
	   base, mesmo valor que .kinetic-page já usa por omissão) — continua
	   desligada do --kinetic-seam-rise mais raso da página (4vw), que
	   fica só para as secções de projeto. Altura já não forçada (ver nota
	   acima, mesmo piso de 14rem) — a página de projeto individual (mesma
	   classe partilhada) é quem mais precisava disto. */
	.kinetic-portfolio-hero { min-height: 14rem; padding: 8.5rem 0 4rem; --kinetic-hero-seam-rise: 8.12vw; }
	.kinetic-contact-hero h1 { font-size: clamp(2.6rem, 10.4vw, 3.3rem); }
	.kinetic-contact-panel { padding: 4.5rem 0 6rem; }
	.kinetic-contact-panel__grid { grid-template-columns: 1fr; gap: 3rem; }
	.kinetic-hero__lead { position: relative; z-index: 3; max-width: 20rem; margin-top: 1.45rem; font-size: .76rem; }
	.kinetic-hero__media-float { top: 11rem; right: -9rem; width: 29rem; height: 31rem; opacity: .45; }
	/* atan(8.12/100) ≈ 4.64deg — mesmo raciocínio da regra base. margin-top
	   recalculado com a mesma técnica corrigida (origem no canto, não no
	   centro — ver comentário completo na regra base): marginLeft=-8vw,
	   tan(-4.64°)≈-0.0812, margin-top = (-8)·(-0.0812) ≈ .65vw. */
	.kinetic-marquee { width: 116%; margin: calc(.65vw - .4rem) 0 -1rem -8%; padding-block: .12rem; transform: skewY(-4.64deg); }
	.kinetic-marquee span { font-size: .9rem; }
	.kinetic-marquee img { width: .7rem; height: 1.2rem; }
	.kinetic-intro { padding: 5.25rem 0 5.5rem; clip-path: polygon(0 5%, 100% 0, 100% 100%, 0 92%); }
	.kinetic-intro__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2rem; }
	.kinetic-intro h2, .kinetic-work h2 { font-size: clamp(2.65rem, 11.5vw, 3.55rem); }
	.kinetic-intro__copy { position: relative; z-index: 2; }
	/* Encolhido e muito mais transparente (2026-09-07, pedido do cliente:
	   "fica um vazio no meio e fica desenquadrado" + "o icon do logo... tem
	   de ficar um pouco transparente para se perceber o texto"). Medido: a
	   imagem do símbolo (19rem de largura, sem altura fixa, escala pela
	   proporção real do ficheiro) chegava a 304px de altura dentro de uma
	   caixa de só 176px (11rem) — a maior parte desse excesso é o espaço
	   TRANSPARENTE entre o ponto e a vírgula do próprio símbolo (não corta
	   nada ao encolher), mas ainda assim empurrava a lista para bem mais
	   longe do botão do que fazia sentido. Reduzido para 10rem de largura
	   (a caixa passa a ter altura suficiente para conter a imagem quase
	   sem sobra) e a lista puxada para cima com margin-top negativo — a
	   ideia é o símbolo ficar como uma marca de água POR TRÁS do início da
	   lista, não como um bloco à parte com espaço vazio a seguir; por isso
	   também desce a opacidade bastante (.72 -> .16), para continuar
	   legível por cima onde quer que a lista o cubra. */
	/* !important necessário aqui: [data-kinetic-reveal] (GSAP) fixa
	   opacity:1 inline no fim da animação de entrada e nunca a limpa
	   (clearProps não usado) — inline vence sempre uma regra externa
	   normal, mesmo vinda depois no ficheiro (lição já documentada neste
	   projeto: GSAP + estilo inline). !important é o único jeito de uma
	   regra de folha de estilo bater um inline sem tocar no JS. */
	.kinetic-intro__mark { position: relative; top: auto; right: auto; width: 100%; height: 7rem; opacity: .16 !important; }
	.kinetic-intro__mark img { width: 10rem; max-width: 92vw; margin-inline: auto; }
	.kinetic-values { margin-top: -6rem; }
	/* Grupo de imagens da página de projeto individual (secção 3) puxado
	   para baixo e centrado na secção (2026-09-07, pedido do cliente:
	   "puxar o grupo de imagens mais para baixo, centrar o grupo com a
	   secção em que estão") — o -5.5rem de sobreposição para cima (regra
	   base, pensada para desktop) deixava a legenda/imagens quase coladas
	   à costura diagonal no mobile, sem respiro. Menos sobreposição
	   (-2.5rem) + mais padding a abrir por cima do que por baixo do texto
	   e do grupo — lê-se como centrado dentro do trapézio tinta, não
	   encostado ao topo. */
	.kinetic-project-showcase { margin-top: -2.5rem; padding: 4.5rem 0 3.5rem; }
	.kinetic-values { grid-template-columns: 1fr; }
	.kinetic-values li:nth-child(n) { min-height: 5.25rem; padding-inline: 0; }
	/* -3.5rem (56px) não chegava para cobrir a costura em toda a faixa
	   ≤767px — apanhado pelo cliente a print (janela estreita): "não é um
	   vinco, é o conteúdo da secção anterior a sobrepor-se". Causa: o
	   corte de .kinetic-intro é em % (relativo à SUA PRÓPRIA altura, que
	   varia bastante dentro deste breakpoint — de ~687px a ~1050px,
	   conforme o texto quebra), mas este overlap é um valor FIXO em rem —
	   só cobre por acaso na largura em que foi medido. Medido o pior caso
	   real (480px de largura, altura da intro ~1051px): o lado raso do
	   corte da intro (8% = 100%-92%) precisa de ~84px de overlap; -3.5rem
	   só dava 56px, faltavam ~28px — exatamente a fatia da intro que
	   aparecia por baixo da diagonal. -6rem (96px) cobre o pior caso medido
	   com folga (~12px), sem tapar o último item da lista de valores (que
	   acaba bem antes da costura em qualquer largura testada). */
	.kinetic-work { margin-top: -6rem; padding: 5.5rem 0; }
	.kinetic-work::before, .kinetic-pattern--work { clip-path: polygon(0 0, 100% 0, 100% 97.5%, 0 100%); }
	.kinetic-work__grid { grid-template-columns: 1fr; gap: 1.25rem; }
	.kinetic-work__heading h2 { max-width: 6.2ch; }
	.kinetic-collage { gap: .5rem; }
	.kinetic-services-stage { margin-top: -3.5rem; }
	.kinetic-services { min-height: 44rem; margin-top: 0; padding: 5.5rem 0; }
	.kinetic-services::before { clip-path: polygon(0 2.5%, 100% 0, 100% 100%, 0 99.25%); }
	.kinetic-services__grid { grid-template-columns: 1fr; gap: 2.5rem; min-height: 33rem; }
	.kinetic-services__title { align-self: auto; min-height: 0; }
	.kinetic-services__title h2 { position: static; font-size: clamp(2.7rem, 12vw, 3.65rem); white-space: normal; transform: none; }
	.kinetic-services__mask { top: -60%; left: -125%; width: 350vw; height: 220%; margin: 0; background-position: center; background-size: contain; opacity: .9; transform: rotate(30deg); }
	.kinetic-service__trigger { font-size: 1.05rem; }
	/* Seletor com o mesmo nível de especificidade de .kinetic-hero
	   .kinetic-pattern::after (duas classes + pseudo) — a regra base
	   .kinetic-pattern::after (uma classe) nunca ganhava contra ela aqui,
	   por mais tarde que viesse no ficheiro; o logótipo ficava sempre a
	   0.64 em mobile, sobreposto ao título.
	   Contactos partilha esta regra (pedido explícito, 2026-09-03: mesmas
	   medidas/posição do logótipo da home, incluindo em mobile) — o título
	   mais curto (secção 17) já resolveu a sobreposição pela raiz, deixou
	   de ser preciso um comportamento próprio aqui. Aprovado pelo
	   utilizador: "janela curta, fica mais transparente, está OK". */
	/* Eliminada por completo no mobile (2026-09-07, pedido do cliente) —
	   mesmo a 0.06 de opacidade continuava a sobrepor-se visualmente ao
	   parágrafo do hero em ecrãs estreitos (confirmado por screenshot).
	   Substitui a versão anterior "mais transparente" (0.06) por opacity:0
	   — mantém a regra (não apagada) para não perder o histórico de
	   especificidade já documentado (uma classe sozinha nunca lhe ganhava). */
	.kinetic-hero .kinetic-pattern::after,
	.kinetic-contact-hero .kinetic-pattern::after,
	.kinetic-portfolio-hero .kinetic-pattern::after { opacity: 0; }
	.kinetic-footer { margin-top: -4.5rem; padding: 1rem 0 .75rem; clip-path: polygon(0 0, 100% .75%, 100% 100%, 0 100%); }
	/* Grid "1fr auto" (regra base, >767px) parte em ecrãs de telemóvel
	   estreitos: um item de grid com largura própria definida (a marca,
	   11.75rem) continua a impor essa largura à faixa mesmo sem espaço —
	   min-width:auto do grid não encolhe abaixo do "specified size
	   suggestion". Nunca acontecia nos ecrãs onde isto foi desenhado/
	   testado (tablet+), só ficou visível em telemóveis estreitos
	   (confirmado <390px: marca 188px + ícones 238px + gap 20px = 446px
	   nun contentor de 350px — os ícones sociais saíam do ecrã, cortados,
	   sem barra de scroll horizontal porque .kinetic-footer os recortava
	   silenciosamente). Troca para flex+wrap aqui: quando não cabem lado a
	   lado, os ícones passam para a linha de baixo em vez de serem
	   cortados — nenhuma das duas peças precisa de encolher. */
	/* Centrado (2026-09-07, pedido do cliente: "alinhar ao centro os
	   elementos/objectos") — antes ficava tudo à esquerda (marca, ícones
	   sociais, texto legal), cada linha do wrap encostada à margem. Com
	   justify-content:center cada linha resultante do wrap (marca sozinha,
	   depois os ícones sozinhos) centra-se por si, sem precisar de regra
	   própria por elemento. margin-left:auto removido dos socials — era
	   o que os empurrava para a direita antes. */
	.kinetic-footer__base { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 1.25rem; padding: .5rem 0; }
	.kinetic-footer__brand { width: 11.75rem; padding: .3rem .5rem; flex: 0 0 auto; }
	.kinetic-footer__socials { justify-self: auto; }
	.kinetic-footer__legal { padding: .75rem 0 0; text-align: center; }
}

@media (min-width: 640px) and (max-width: 767px) {
	.kinetic-intro { padding-block: 6rem; }
	.kinetic-intro__grid { grid-template-columns: minmax(0, 1fr) minmax(10rem, .42fr); align-items: center; gap: 2.5rem; }
	.kinetic-intro__mark { height: auto; }
	.kinetic-intro__mark img { width: min(18rem, 32vw); }
	.kinetic-values { grid-column: 1 / -1; grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.kinetic-values li:nth-child(odd) { padding-right: 1.25rem; }
	.kinetic-values li:nth-child(even) { padding-left: 1.25rem; }
}

/* Mosaico: três linhas de duas peças, em vez das duas de três da versão
   larga. Só em mobile, onde não há largura para três. Tentei primeiro cortar
   aos 1450px, calibrando pela largura mínima de cada peça (~215px), mas isso
   estava a olhar para a métrica errada: a 1449px o mosaico de duas por linha
   ficava com 1786px de altura — uma torre, o oposto da composição larga e
   baixa da referência. O que interessa é o rácio do conjunto, e para isso
   três por linha ganha sempre que houver largura para isso. O limiar são os
   640px porque abaixo disso as três peças por linha ficam com menos de 150px
   e deixam de se ler como imagens. */
@media (max-width: 640px) {
	.kinetic-collage__item--a { grid-row: 1; grid-column: 1 / span 7; margin-top: 0; }
	.kinetic-collage__item--b { grid-row: 1; grid-column: 7 / span 6; margin-top: 1.1rem; }
	.kinetic-collage__item--c { grid-row: 2; grid-column: 1 / span 6; margin-top: -1.2rem; }
	.kinetic-collage__item--d { grid-row: 2; grid-column: 6 / span 7; margin-top: -2.2rem; }
	.kinetic-collage__item--e { grid-row: 3; grid-column: 1 / span 6; margin-top: -1.4rem; }
	.kinetic-collage__item--f { grid-row: 3; grid-column: 6 / span 7; margin-top: -2.6rem; }
}

@media (max-width: 520px) {
	.kinetic-header { grid-template-columns: auto minmax(0, 1fr); padding-inline: .75rem; }
	.kinetic-brand { width: 7.15rem; }
	.kinetic-header nav ul { gap: .48rem; }
	.kinetic-header nav a { gap: .2rem; font-size: .43rem; letter-spacing: 0; }
	.kinetic-nav-icon { width: .68rem; height: .68rem; }
	/* Altura fixa deixou de existir no mosaico — cada moldura mede-se pelo
	   rácio da imagem e a grelha soma-as. */
}

@media (max-width: 350px) {
	.kinetic-header { padding-inline: .65rem; }
	.kinetic-brand { width: 6.7rem; }
	.kinetic-brand img { height: auto; }
	.kinetic-header nav ul { gap: .45rem; }
	/* Nunca maior que o breakpoint anterior (.43rem, ≤520px) — este é ≤350px,
	   logo estritamente mais estreito e tem de ficar igual ou menor. */
	.kinetic-header nav a { font-size: .4rem; letter-spacing: 0; }
	.kinetic-intro h2 { font-size: clamp(2.45rem, 12vw, 2.9rem); }
}

@media (prefers-reduced-motion: reduce) {
	.kinetic-document { scroll-behavior: auto; }
	.kinetic-marquee__track { animation: none; }
	.kinetic-hero__media-float { animation: none; }
	[data-hero-line], [data-hero-media], [data-kinetic-reveal], [data-value-item] { opacity: 1; transform: none; }
	[data-entry="right"] [data-portfolio-cell], [data-entry="left"] [data-portfolio-cell] { transform: none; }
	.kinetic-button, .kinetic-button img, .kinetic-service__body { transition-duration: .01ms; }
}


/* ===== Estilos de componentes consolidados (Mark.astro, PatternBackground.astro,
   SocialLinks.astro, KineticServiceIcon.astro, KineticValueIcon.astro) =====
   Ver comentário em scripts/build-wp-theme-assets.mjs. */

.mark { width: 0.6em; height: 1.4em; margin-right: 0.5rem; vertical-align: middle; flex-shrink: 0; }
.mark-dot, .mark-tail { fill: currentColor; transform-box: fill-box; transform-origin: center; animation: mark-bounce 2.4s ease-in-out infinite; }
.mark-tail { animation-delay: 0.15s; }
@keyframes mark-bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8%); } }
@media (prefers-reduced-motion: reduce) { .mark-dot, .mark-tail { animation: none; } }

.pattern-layer { position: absolute; inset: 0; overflow: hidden; opacity: var(--pattern-opacity, 0.16); pointer-events: none; z-index: 0; }
.symbol { position: absolute; width: 1.5rem; height: 3.6rem; margin: -1.8rem 0 0 -0.75rem; will-change: transform; }
.symbol-inner { width: 100%; height: 100%; fill: var(--color-paper-on-ink); }

.social-links { display: flex; gap: 0.6rem; list-style: none; }
.social-links a { display: flex; align-items: center; justify-content: center; width: 2.5rem; height: 2.5rem; border-radius: 50%; border: 1.5px solid currentColor; color: inherit; transition: transform 0.25s ease, background 0.25s ease, color 0.25s ease; }
.social-links svg { width: 1.15rem; height: 1.15rem; }
.social-links a:hover { transform: translateY(-3px); background: currentColor; }
.social-links a:hover svg { color: var(--social-hover-icon, var(--color-paper)); }

.kinetic-service-icon { display: grid; place-items: center; width: 1.6rem; height: 1.6rem; flex-shrink: 0; color: var(--kinetic-accent); }
.kinetic-service-icon svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

.kinetic-value-icon { display: grid; place-items: center; width: 3.25rem; height: 3.25rem; color: currentColor; }
.kinetic-value-icon svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.65; stroke-linecap: round; stroke-linejoin: round; }
