feat(hero): campo de particulas WebGL com mergulho guiado pela rolagem e transicao de secoes em CSS

Tres camadas de pontos ligadas por colunas de dados, animadas no vertex shader;
o hero fica fixo em telas grandes enquanto a camera atravessa as camadas.
Secoes sobem como cartoes via animation-timeline: view() (sem JS).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Evandro Fonseca JuniorandClaude Opus 5.5 committed 2026-10-05 00:22:18 -03:00
1 parent 3d47fe9cc7
commit 0ec0194441
7 files changed
+694 -1001

No files matched your search

+157 -10
View File
@@ -255,6 +255,7 @@ textarea {
.ds-section {
padding: clamp(72px, 9vw, 112px) clamp(24px, 6vw, 64px);
background: var(--paper);
border-bottom: 1px solid var(--border);
}
@@ -986,30 +987,176 @@ textarea {
radial-gradient(40% 50% at 6% 92%, rgba(204, 255, 0, 0.05), transparent 70%);
}
.hero-rise > * {
animation: hero-rise 0.8s var(--ease-out) both;
/* Hero: palco de 100svh; em telas grandes ele fica fixo enquanto a rolagem conduz o mergulho da cena 3D.
--hero-p (0 a 1) vem de cinematic/HeroField.tsx. */
.hero {
--hero-p: 0;
isolation: isolate;
}
&:nth-child(2) {
animation-delay: 0.08s;
}
.hero-stage {
position: relative;
display: flex;
flex-direction: column;
min-height: 100svh;
overflow: hidden;
}
&:nth-child(3) {
animation-delay: 0.16s;
/* Sem WebGL (ou antes de a cena carregar): malha de pontos estática no lugar das camadas */
.hero-glow::after {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(circle, rgba(244, 242, 237, 0.22) 1px, transparent 1.5px) 0 0 / 22px 22px;
mask: radial-gradient(38% 46% at 74% 50%, #000, transparent 75%);
transition: opacity 1.2s var(--ease-out);
}
.hero:has(.hero-field[data-ready]) .hero-glow::after {
opacity: 0;
}
.hero-field {
position: absolute;
inset: 0;
opacity: 0;
transition: opacity 1.6s var(--ease-out);
&[data-ready] {
opacity: 1;
}
}
@keyframes hero-rise {
.hero-scrim {
position: absolute;
inset: 0;
pointer-events: none;
background:
linear-gradient(90deg, rgba(10, 10, 10, 0.86) 0%, rgba(10, 10, 10, 0.55) 36%, transparent 62%),
linear-gradient(0deg, rgba(10, 10, 10, 0.9), transparent 28%);
@media (max-width: 1023px) {
background: linear-gradient(0deg, rgba(10, 10, 10, 0.92) 0 30%, rgba(10, 10, 10, 0.5) 65%, rgba(10, 10, 10, 0.7));
}
}
/* Entrada: as palavras do título já pintam no primeiro quadro (sem opacity:0, bom para o LCP) e ganham foco em cascata */
.hero-word {
display: inline-block;
animation: hero-word 1.1s var(--ease-out) both;
animation-delay: calc(0.12s + var(--i) * 45ms);
}
@keyframes hero-word {
from {
transform: translateY(16px);
transform: translateY(0.42em) rotate(2deg);
filter: blur(12px);
}
}
.hero-in,
.hero-foot > * {
animation: hero-in 1s var(--ease-out) both;
animation-delay: var(--d, 0.05s);
}
.hero-foot > * {
--d: 0.85s;
}
@keyframes hero-in {
from {
opacity: 0;
transform: translateY(18px);
}
}
.hero-cue-line {
width: 56px;
height: 1px;
background: currentColor;
transform-origin: left;
animation: hero-cue 2.4s var(--ease-out) infinite;
}
@keyframes hero-cue {
0% {
transform: scaleX(0);
}
60% {
transform: scaleX(1);
opacity: 1;
}
100% {
transform: scaleX(1);
opacity: 0;
}
}
@media (min-width: 1024px) and (min-height: 700px) and (prefers-reduced-motion: no-preference) {
.hero {
height: 220svh;
}
.hero-stage {
position: sticky;
top: 0;
height: 100svh;
}
/* o texto sai de cena enquanto a câmera mergulha: sobe, encolhe e perde o foco */
.hero-content {
transform: translate3d(0, calc(var(--hero-p) * -16vh), 0) scale(calc(1 - var(--hero-p) * 0.08));
transform-origin: 0 50%;
opacity: calc(1 - var(--hero-p) * 2.6);
filter: blur(calc(var(--hero-p) * 18px));
}
.hero-foot {
opacity: calc(1 - var(--hero-p) * 5);
}
.hero-scrim {
opacity: calc(1 - var(--hero-p) * 1.4);
}
}
@media (prefers-reduced-motion: reduce) {
.hero-rise > * {
.hero-word,
.hero-in,
.hero-foot > *,
.hero-cue-line {
animation: none;
}
}
/* Transição entre seções: cada seção sobe como um cartão e abre até a borda da tela.
CSS puro com linha do tempo de rolagem (Chrome, Edge, Safari 26+); sem suporte, a seção só aparece. */
.ds-main {
background: var(--ink);
}
@keyframes section-rise {
from {
clip-path: inset(8vh 4vw 0 4vw round 28px);
translate: 0 12vh;
}
to {
clip-path: inset(0 0 0 0 round 0);
translate: 0 0;
}
}
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
.ds-main > section:not(.hero, .notebook-story) {
animation: section-rise linear both;
animation-timeline: view();
animation-range: entry 0% entry 85vh;
}
}
}
/* Capa dos cards de projeto: captura real ou painel gerado em codigo */
.ds-cover {
position: relative;