fix(navegacao): foco em âncora de outra página, vitrine acessível e 3D mais leve

Foco cai no destino ao chegar de outra página; âncoras ignoram ?tipo=; movimento reduzido salta em todos os cliques; cronômetro da janela ampliada; degradê no topo de Projetos e legendas do mergulho com intervalos testados; vitrine acima da barra de cookies e com 2 paradas por projeto; reel-list.css no global; 3D montado em tarefas curtas, limitado a ~30 qps e estático com WebGL por software; pôsteres da vitrine de 330 para 158 KB; SC Plus regravado; scripts sem funções acima de complexidade 10.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 19:14:48 -03:00
1 parent 9e2d65a926
commit 204ebd5fa5
49 files changed
+2197 -606

No files matched your search

+45 -25
View File
@@ -1,4 +1,8 @@
@import "tailwindcss";
/* A lista de projetos e o casco da vitrine (home): no mesmo arquivo que o global, para a home não carregar uma terceira
folha bloqueante. A vitrine fixa (pinned-reel.css) e a janela ampliada (lightbox.css) seguem sob demanda. As molduras
(showcase.css) vêm de app/layout.tsx, ao lado deste arquivo. */
@import "./components/showcase/reel-list.css";
@config "../tailwind.config.js";
@theme {
@@ -60,6 +64,9 @@
--fw-card: 600;
--font-display: var(--font-space-grotesk), sans-serif;
--font-mono: var(--font-geist-mono), monospace;
/* A monoespacada do sistema, para rótulos que aparecem na carga (primeira dobra da home, mesmo com opacidade 0):
a Geist Mono só é baixada quando algum texto a usa, e estes não valem um arquivo a mais na carga. */
--font-mono-system: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
--font-body: var(--font-display);
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
@@ -265,6 +272,12 @@ textarea {
max-width: 760px;
}
/* Empilhados (celular e telas estreitas), o título e a descrição respiram. Vem antes do layout largo, que a
desfaz (margin-top: 0): lá cada um tem a sua coluna e o espaço vem do alinhamento pela base. */
.ds-section-title + .ds-section-description {
margin-top: var(--sp-4);
}
/* Largo: título à esquerda, descrição à direita, alinhados pela base (some a faixa vazia ao lado do título). */
@container (min-width: 880px) {
.ds-section-meta {
@@ -289,6 +302,7 @@ textarea {
> .ds-section-description {
grid-area: lead;
max-width: 62ch;
margin-top: 0;
font-size: var(--fs-lead);
}
@@ -792,7 +806,7 @@ ol.ds-list {
.hero-caption-title {
display: block;
color: var(--slash);
font: 600 12px / 1.4 var(--font-mono);
font: 600 12px / 1.4 var(--font-mono-system);
letter-spacing: 0.08em;
text-transform: uppercase;
}
@@ -808,20 +822,41 @@ ol.ds-list {
.ds-main {
/* rolagem do mergulho: o progresso (--hero-p) vai de 0 a 1 nela */
--hero-dive: 110svh;
/* Fonte única da altura do mergulho: telas de hero além do mergulho (sem unidade). O CSS abaixo as usa na altura
do hero e na margem de Projetos, e HeroField.tsx lê esta variável para saber onde o progresso (--hero-p)
acaba: o mergulho é o resto da altura do hero. */
--hero-tail: 1.7;
}
@media (min-height: 700px) and (prefers-reduced-motion: no-preference) {
/* O palco (100svh, preso) fica parado durante o mergulho e por mais 70svh: nesse trecho Projetos sobe sobre ele, com
margin-top de -70svh. A cena acaba de frente para os dados, a última legenda fica até Projetos cobri-la, e
nunca há uma tela vazia entre o mergulho e a seção seguinte (HeroField.tsx usa a mesma conta: o progresso
acaba quando Projetos começa a subir, 1,7 tela antes do fim do hero). */
/* O palco (100svh, preso) fica parado durante o mergulho e por mais (--hero-tail - 1) tela: nesse trecho Projetos
sobe sobre ele, com a margem negativa dessa altura. A cena acaba de frente para os dados, a última legenda se
dissolve num degradê no topo de Projetos conforme ele a cobre, e nunca há uma tela vazia entre o mergulho e a
seção seguinte (o progresso acaba quando Projetos começa a subir, --hero-tail telas antes do fim do hero). */
.hero[data-dive] {
height: calc(170svh + var(--hero-dive));
height: calc(var(--hero-tail) * 100svh + var(--hero-dive));
& + .ds-section {
position: relative;
margin-top: -70svh;
margin-top: calc((1 - var(--hero-tail)) * 100svh);
box-shadow: 0 -32px 64px -24px rgba(0, 0, 0, 0.8);
/* Degradê acima da borda de Projetos: a legenda dos dados fica em cena até ele subir e, em vez de ser cortada
pela borda, some dentro do degradê à medida que ela se aproxima. Sem janela sem texto antes disso. */
&::before {
content: "";
position: absolute;
inset: auto 0 100%;
height: 22svh;
background: linear-gradient(
0deg,
var(--ink),
rgba(10, 10, 10, 0.85) 22%,
rgba(10, 10, 10, 0.45) 55%,
rgba(10, 10, 10, 0)
);
pointer-events: none;
}
}
/* Legendas do mergulho: cada uma aparece enquanto a câmera atravessa a sua camada (--hero-p) */
@@ -868,24 +903,9 @@ ol.ds-list {
translate: 0 calc((var(--hero-p) - var(--mid)) * -160px);
}
/* Intervalos em que a câmera enquadra cada camada (cameraFrames em HeroFieldScene.tsx), com folga: cada
legenda entra um pouco antes de a câmera chegar e sai um pouco depois de ela partir, para o intervalo sem
texto entre uma e outra ser de uns 80 px de rolagem. A última passa de 1 para ficar em cena até o fim. */
& li:nth-child(1) {
--from: 0.18;
--to: 0.49;
}
& li:nth-child(2) {
--from: 0.51;
--to: 0.75;
}
& li:nth-child(3) {
--from: 0.79;
--to: 1.07;
}
/* Os intervalos (--from e --to de cada item) vêm de app/components/hero-captions.ts, junto do texto: lá ficam
os trechos em que a câmera enquadra cada camada, com folga, e o teste que garante que não há janela sem
legenda entre uma e outra. */
& .hero-caption-title {
margin-bottom: var(--sp-3);
font-size: 12px;