perf(movimento): uma transição, 3D leve e vídeo certo por aparelho

Passagens com fade e 32 px, cena anterior termina em opacidade 0, sem visibility nem clip-path. 3D do hero como fundo ambiente, sob demanda e desligado em toque, aparelho fraco, saveData e movimento reduzido. Lenis fora do toque e salto direto em âncoras distantes. Vitrine só de clientes, até 3 capítulos, fixa só no desktop e carrossel no celular; vídeo só do projeto em cena com preload metadata. Rótulos mono a partir de 12 px e limão só no contato.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 03:32:23 -03:00
1 parent 8d07b8c6bb
commit c9a0b80656
48 files changed
+7210 -1241

No files matched your search

+91 -551
View File
@@ -137,10 +137,9 @@ html {
background: var(--ink);
color: var(--white);
padding: 10px 14px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
font-family: var(--font-display);
font-size: 15px;
font-weight: 600;
text-decoration: none;
transition: transform 0.2s var(--ease-out);
}
@@ -174,6 +173,12 @@ html {
);
mix-blend-mode: overlay;
opacity: 0.08;
transition: opacity 0.3s ease;
}
/* partículas e efeitos nunca ficam por cima das telas dos projetos (FilmGrain marca quando uma está à vista) */
.film-grain[data-over-projects] {
opacity: 0;
}
@media (prefers-reduced-motion: reduce), (pointer: coarse) {
@@ -186,7 +191,8 @@ body {
position: relative;
background: var(--paper);
color: var(--ink);
font-family: var(--font-mono);
/* sans por padrão: a mono é só dos rótulos (.ds-eyebrow, .ds-kicker, .ds-tag...), que a pedem à parte */
font-family: var(--font-body);
font-size: 14px;
line-height: 1.68;
}
@@ -325,10 +331,11 @@ textarea {
}
}
/* rótulos curtos em mono: 12 px no mínimo e espaçamento moderado */
.ds-eyebrow,
.ds-kicker {
font-size: 10px;
letter-spacing: 0.12em;
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--meta);
}
@@ -422,8 +429,8 @@ textarea {
padding: 8px 12px;
border: 1px solid rgba(10, 10, 10, 0.12);
background: rgba(10, 10, 10, 0.03);
font-size: 11px;
letter-spacing: 0.04em;
font-size: 12px;
letter-spacing: 0.02em;
text-transform: none;
font-weight: 500;
/* pílula inteira numa linha: a quebra acontece entre tags (.ds-tags wrap), nunca dentro dela */
@@ -440,8 +447,8 @@ textarea {
}
.ds-section-dark .ds-tag:hover {
border-color: rgba(204, 255, 0, 0.3);
box-shadow: 0 2px 16px rgba(204, 255, 0, 0.1);
border-color: rgba(255, 255, 255, 0.3);
box-shadow: 0 2px 16px rgba(255, 255, 255, 0.06);
}
.ds-metric {
@@ -449,11 +456,14 @@ textarea {
align-items: center;
gap: var(--sp-2);
color: rgba(10, 10, 10, 0.65);
font-size: 10px;
letter-spacing: 0.1em;
font-size: 12px;
letter-spacing: 0.06em;
text-transform: uppercase;
}
/* Botões e links de ação: texto em sans 15 px, sem caixa-alta mono. O verde-limão preenchido
(.ds-link-accent) é só da ação de contato; as demais ações usam contorno (.ds-link-muted) ou
link de texto (.ds-link-text). */
.ds-link {
display: inline-flex;
align-items: center;
@@ -461,9 +471,12 @@ textarea {
min-height: 44px; /* alvo de toque minimo e mesma altura em todos os botoes */
gap: var(--sp-2);
text-decoration: none;
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
font-family: var(--font-display);
font-size: 15px;
font-weight: 500;
line-height: 1.25;
letter-spacing: 0;
text-transform: none;
transition:
transform 0.2s ease,
background 0.2s ease,
@@ -478,6 +491,7 @@ textarea {
color: var(--ink);
background: var(--slash);
padding: 12px 18px;
font-weight: 600;
transition:
transform 0.25s ease,
box-shadow 0.25s ease,
@@ -497,9 +511,10 @@ textarea {
0 0 60px rgba(204, 255, 0, 0.22);
}
/* contorno: ação secundária (sem lima) */
.ds-link-muted {
padding: 12px 18px;
border: 1px solid var(--border);
border: 1px solid rgba(10, 10, 10, 0.28);
transition:
transform 0.25s ease,
box-shadow 0.25s ease,
@@ -512,8 +527,21 @@ textarea {
}
.ds-section-dark .ds-link-muted:hover {
border-color: rgba(204, 255, 0, 0.35);
box-shadow: 0 4px 20px rgba(204, 255, 0, 0.08);
border-color: rgba(255, 255, 255, 0.7);
box-shadow: none;
}
/* link de texto: ação terciária, sublinhada, sem caixa */
.ds-link-text {
padding: 0 2px;
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 5px;
}
.ds-link-text:hover {
transform: none;
text-decoration-thickness: 2px;
}
.ds-list {
@@ -531,8 +559,8 @@ textarea {
.ds-loading,
.ds-error {
padding: var(--sp-10) clamp(24px, 6vw, 64px);
font-size: 11px;
letter-spacing: 0.1em;
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
@@ -563,7 +591,7 @@ textarea {
}
.ds-section-dark .ds-link-muted {
border-color: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.38);
}
.ds-draw-line {
@@ -673,15 +701,13 @@ textarea {
right: var(--sp-3);
z-index: 1;
min-height: 44px;
border: 1px solid var(--slash);
border: 1px solid rgba(244, 242, 237, 0.5);
background: var(--ink);
color: var(--paper);
padding-inline: var(--sp-3);
font-family: var(--font-mono);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
font-family: var(--font-display);
font-size: 15px;
font-weight: 500;
cursor: pointer;
}
@@ -815,17 +841,6 @@ textarea {
margin-bottom: var(--sp-8);
}
/* rotulos em 11-12px (antes 9-11px): legiveis no celular sem perder o estilo tecnico */
.ds-eyebrow,
.ds-kicker {
font-size: 11px;
}
.ds-tag,
.ds-link {
font-size: 12px;
}
.ds-section-title {
font-size: clamp(24px, 8vw, 34px);
}
@@ -847,7 +862,6 @@ textarea {
}
.ds-tag {
font-size: 10px;
padding: 7px 10px;
}
}
@@ -994,9 +1008,10 @@ textarea {
}
/* 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-p (0 a 1) e --hero-open (0 a 1: a máscara do fundo se abrindo) vêm de cinematic/HeroField.tsx. */
.hero {
--hero-p: 0;
--hero-open: 0;
isolation: isolate;
}
@@ -1008,7 +1023,21 @@ textarea {
overflow: hidden;
}
/* Sem WebGL (ou antes de a cena carregar): malha de pontos estática no lugar das camadas */
/* Máscara dos fundos ambientes do hero (cena 3D e malha estática): transparente no miolo, onde ficam o título
e as molduras da direita, e opaca nas bordas e cantos. Com o mergulho (--hero-open 0 → 1) o miolo some e o
fundo aparece inteiro, quando o texto e a coluna da direita já saíram de cena. */
.hero-glow::after,
.hero-field {
--clear: calc((1 - var(--hero-open)) * 52% - 1%);
--feather: calc((1 - var(--hero-open)) * 30%);
mask-image: radial-gradient(
ellipse at 46% 50%,
transparent var(--clear),
#000 calc(var(--clear) + var(--feather) + 1%)
);
}
/* Sem WebGL, com aparelho que não aguenta a cena, ou antes de ela carregar: malha de pontos estática, leve */
.hero-glow::after {
content: "";
position: absolute;
@@ -1016,7 +1045,7 @@ textarea {
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%);
opacity: 0.6;
transition: opacity 1.2s var(--ease-out);
}
@@ -1024,14 +1053,23 @@ textarea {
opacity: 0;
}
/* Fundo ambiente: a cena 3D fica com pouca opacidade (sobe para a opacidade cheia só no mergulho). O
desenho entra por animação (e não por transition) para a opacidade poder seguir a rolagem sem atraso. */
.hero-field {
position: absolute;
inset: 0;
opacity: 0;
transition: opacity 1.6s var(--ease-out);
pointer-events: none;
&[data-ready] {
opacity: 1;
opacity: calc(0.5 + 0.5 * var(--hero-open));
animation: hero-field-in 1.6s var(--ease-out) both;
}
}
@keyframes hero-field-in {
from {
opacity: 0;
}
}
@@ -1110,46 +1148,11 @@ textarea {
}
}
/* Etiquetas presas às camadas da cena: a posição vem da cena a cada quadro (transform), o recuo para a
esquerda do ponto de ancoragem fica no translate */
.hero-tag {
position: absolute;
top: 0;
left: 0;
display: none;
max-width: 230px;
translate: calc(-100% - 14px) -50%;
border: 1px solid rgba(244, 242, 237, 0.14);
background: rgba(10, 10, 10, 0.62);
backdrop-filter: blur(4px);
padding: 7px 11px;
color: rgba(244, 242, 237, 0.78);
font: 13px / 1.35 var(--font-body);
text-align: right;
opacity: 0;
pointer-events: none;
&::after {
content: "";
position: absolute;
top: 50%;
right: -14px;
width: 14px;
height: 1px;
background: var(--slash);
}
@media (min-width: 1024px) {
display: block;
}
}
.hero-tag-title,
.hero-caption-title {
display: block;
color: var(--slash);
font: 600 11px / 1.4 var(--font-mono);
letter-spacing: 0.14em;
font: 600 12px / 1.4 var(--font-mono);
letter-spacing: 0.08em;
text-transform: uppercase;
}
@@ -1244,6 +1247,12 @@ textarea {
opacity: calc(1 - var(--hero-p) * 5);
}
/* a coluna da direita (molduras) sai junto com o texto: o fundo 3D se abre sem passar atrás dela */
.hero-aside {
transform: translate3d(0, calc(var(--hero-p) * -10vh), 0);
opacity: calc(1 - var(--hero-p) * 4.2);
}
.hero-scrim {
opacity: calc(1 - var(--hero-p) * 1.4);
}
@@ -1355,475 +1364,6 @@ html.lenis {
will-change: transform, opacity;
}
/* Interactive quality laboratory — scoped to the Lighthouse section. */
.quality-lab {
--quality-muted: color-mix(in srgb, var(--paper) 72%, transparent);
--quality-line: color-mix(in srgb, var(--paper) 18%, transparent);
color: var(--paper);
}
.quality-intro {
display: grid;
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.3fr);
gap: var(--sp-12);
align-items: center;
margin-bottom: var(--sp-10);
}
.quality-copy .ds-eyebrow {
color: var(--slash);
margin-bottom: var(--sp-5);
}
.quality-copy .ds-section-title {
max-width: 16ch;
}
.quality-copy h2 span {
display: block;
color: var(--slash);
}
.quality-copy > p:not(.ds-eyebrow) {
max-width: 44ch;
margin: var(--sp-6) 0;
color: var(--quality-muted);
font-size: 15px;
line-height: 1.8;
}
.quality-copy .ds-link {
white-space: normal;
line-height: 1.7;
}
.quality-visual {
min-width: 0;
border: 1px solid var(--quality-line);
}
.quality-visual-label {
display: flex;
justify-content: space-between;
gap: var(--sp-4);
border-bottom: 1px solid var(--quality-line);
padding: var(--sp-4);
color: var(--quality-muted);
font: 10px / 1.5 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.quality-visual-label span:first-child {
color: var(--paper);
}
.quality-blueprint {
display: block;
width: 100%;
height: auto;
}
.quality-dimensions {
stroke: var(--quality-line);
}
.quality-dimensions text {
fill: var(--quality-muted);
stroke: none;
font: 9px var(--font-mono);
}
.quality-browser-frame {
fill: var(--ink);
stroke: color-mix(in srgb, var(--paper) 38%, transparent);
}
.quality-wire {
stroke: color-mix(in srgb, var(--paper) 30%, transparent);
}
.quality-browser-dots {
fill: var(--quality-muted);
}
.quality-address {
fill: var(--quality-muted);
font: 10px var(--font-mono);
}
.quality-browser-heading {
fill: var(--paper);
font: 700 24px var(--font-display);
}
.quality-browser-button {
fill: var(--slash-dim);
stroke: color-mix(in srgb, var(--slash) 50%, transparent);
}
.quality-browser-button-label {
fill: var(--paper);
font: 9px var(--font-mono);
}
.quality-layer {
stroke: var(--slash);
stroke-width: 1.2px;
opacity: 0;
transition: opacity 180ms ease-out;
}
.quality-layer text {
fill: var(--slash);
stroke: none;
font: 10px var(--font-mono);
}
.quality-layer circle {
fill: var(--slash);
}
.quality-highlight {
fill: color-mix(in srgb, var(--slash) 5%, transparent);
}
.quality-focus-frame {
stroke-width: 2px;
stroke-dasharray: 5 3;
}
.quality-lab[data-inspection="performance"] [data-layer="performance"],
.quality-lab[data-inspection="accessibility"] [data-layer="accessibility"],
.quality-lab[data-inspection="practices"] [data-layer="practices"],
.quality-lab[data-inspection="seo"] [data-layer="seo"] {
opacity: 1;
}
.quality-annotation {
fill: var(--ink);
stroke: none;
}
@media (prefers-reduced-motion: no-preference) {
.quality-lab[data-inspection="performance"]
[data-layer="performance"]
.quality-trace,
.quality-lab[data-inspection="accessibility"]
[data-layer="accessibility"]
.quality-trace,
.quality-lab[data-inspection="practices"]
[data-layer="practices"]
.quality-trace,
.quality-lab[data-inspection="seo"] [data-layer="seo"] .quality-trace {
stroke-dasharray: 1;
animation: quality-trace 650ms ease-out;
}
}
/* Entrada do diagrama (classe is-drawing posta por useCinematicScroll): linhas se desenham e rótulos
aparecem uma única vez, sem acompanhar cada quadro da rolagem */
@media (prefers-reduced-motion: no-preference) {
/* só com as transições ativas (html.lenis): sem o script, o diagrama aparece normalmente */
html.lenis .quality-lab:not(.is-drawing)
:is(.quality-blueprint, .quality-visual-label, .quality-visual figcaption) {
opacity: 0;
}
.quality-lab.is-drawing
.quality-blueprint
:is(
.quality-dimensions path,
.quality-browser-frame,
.quality-wire,
.quality-browser-button
) {
stroke-dasharray: 1800; /* maior que o maior contorno (moldura: ~1600) */
animation: quality-draw 1.1s var(--ease-out) both;
}
.quality-lab.is-drawing
:is(.quality-blueprint, .quality-visual-label, .quality-visual figcaption) {
animation: quality-fade 0.6s var(--ease-out) both;
}
}
@keyframes quality-draw {
from {
stroke-dashoffset: 1800;
}
to {
stroke-dashoffset: 0;
}
}
@keyframes quality-fade {
from {
opacity: 0;
}
}
@keyframes quality-trace {
from {
stroke-dashoffset: 1;
}
to {
stroke-dashoffset: 0;
}
}
.quality-visual figcaption {
display: grid;
min-height: 64px;
border-top: 1px solid var(--quality-line);
padding: var(--sp-4);
color: var(--quality-muted);
font-size: 12px;
line-height: 1.65;
}
/* Overlapping hidden panels reserve the tallest content without fixed heights. */
.quality-visual figcaption span,
.quality-report {
grid-area: 1 / 1;
}
.quality-visual figcaption span[aria-hidden="true"],
.quality-report[aria-hidden="true"] {
visibility: hidden;
}
.quality-report-stack {
display: grid;
}
.quality-controls {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
min-width: 0;
border: 0;
}
.quality-control {
position: relative;
min-width: 0;
cursor: pointer;
}
.quality-control input {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.quality-control-content {
display: flex;
flex-direction: column;
gap: var(--sp-3);
height: 100%;
padding: var(--sp-5);
border: 1px solid var(--quality-line);
border-top-width: 2px;
transition:
background-color 180ms ease-out,
border-color 180ms ease-out;
}
.quality-control + .quality-control .quality-control-content {
border-left: 0;
}
.quality-control:hover .quality-control-content {
background: color-mix(in srgb, var(--paper) 5%, transparent);
}
.quality-control input:checked + .quality-control-content {
border-top-color: var(--slash);
background: var(--slash-dim);
}
.quality-control input:focus-visible + .quality-control-content {
outline: 2px solid var(--slash);
outline-offset: 4px;
}
.quality-control-name {
display: flex;
align-items: center;
gap: var(--sp-2);
font-size: 15px;
font-weight: 600;
}
.quality-control-name::before {
content: "";
width: 7px;
height: 7px;
flex-shrink: 0;
border: 1px solid currentColor;
border-radius: 50%;
}
.quality-control input:checked + span .quality-control-name::before {
background: var(--slash);
border-color: var(--slash);
}
.quality-control-target {
display: flex;
align-items: baseline;
gap: var(--sp-2);
color: var(--quality-muted);
font: 11px / 1.2 var(--font-mono);
}
.quality-control-target strong {
margin-left: auto;
color: var(--paper);
font-size: 36px;
font-weight: 500;
font-variant-numeric: tabular-nums;
}
.quality-control input:checked + span strong {
color: var(--slash);
}
.quality-hint,
.quality-disclaimer {
color: var(--quality-muted);
font-size: 12px;
line-height: 1.8;
}
.quality-hint {
margin-top: var(--sp-4);
}
.quality-report {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
align-items: start;
gap: var(--sp-12);
min-height: 240px;
padding: var(--sp-8) 0;
border-bottom: 1px solid var(--quality-line);
}
.quality-code {
color: var(--slash);
font: 10px / 1.8 var(--font-mono);
letter-spacing: 0.04em;
overflow-wrap: anywhere;
}
.quality-report h3 {
margin: var(--sp-2) 0 var(--sp-3);
font: 600 clamp(20px, 2.4vw, 28px) / 1.2 var(--font-display);
letter-spacing: -0.025em;
}
.quality-report-copy > p:last-child {
max-width: 60ch;
color: var(--quality-muted);
font-size: 14px;
line-height: 1.8;
}
.quality-report ul {
list-style: none;
margin: 0;
padding: 0;
}
.quality-report li {
display: flex;
gap: var(--sp-3);
padding: var(--sp-3) 0;
border-bottom: 1px solid var(--quality-line);
font-size: 13px;
line-height: 1.65;
}
.quality-report li span {
color: var(--slash);
}
.quality-disclaimer {
margin-top: var(--sp-5);
max-width: 90ch;
}
@media (max-width: 1000px) {
.quality-intro {
grid-template-columns: 1fr;
gap: var(--sp-8);
}
.quality-copy .ds-section-title,
.quality-copy > p:not(.ds-eyebrow) {
max-width: 650px;
}
.quality-visual {
width: min(100%, 720px);
}
}
@media (max-width: 680px) {
.quality-controls {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--sp-2);
}
.quality-control + .quality-control .quality-control-content {
border-left: 1px solid var(--quality-line);
}
.quality-control-content {
padding: var(--sp-4) var(--sp-3);
}
.quality-control-name {
font-size: 13px;
}
.quality-control-target strong {
font-size: 28px;
}
.quality-visual-label {
padding: var(--sp-3);
font-size: 9px;
}
.quality-report {
grid-template-columns: 1fr;
gap: var(--sp-4);
min-height: 350px;
}
}
@media (max-width: 360px) {
.quality-control-content {
padding-inline: var(--sp-2);
}
.quality-control-name {
font-size: 12px;
}
}
@media (prefers-reduced-motion: reduce) {
.quality-layer,
.quality-control-content {
transition: none;
}
}
/* Navegação entre páginas (app/template.tsx liga as transições de visualização do React): a página atual
recua e desfoca, a nova chega de baixo. O cabeçalho tem nome próprio e fica parado durante a troca. */
.site-header {