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:
1 parent
8d07b8c6bb
commit
c9a0b80656
48 files changed
+7210
-1241
No files matched your search
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import {
|
||||
type ChangeEvent,
|
||||
type CSSProperties,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
useState,
|
||||
} from "react";
|
||||
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||
import { isMobileViewport, isSaveData } from "@/lib/device";
|
||||
import {
|
||||
chapterFraction,
|
||||
chapterIndexAt,
|
||||
@@ -56,12 +58,17 @@ export function ProjectLightbox({
|
||||
const pendingFraction = useRef<number | null>(null);
|
||||
const pressedOnBackdrop = useRef(false);
|
||||
|
||||
// celular abaixo de 768 px (lib/device.ts); a troca de aparelho continua à escolha do visitante
|
||||
const [device, setDevice] = useState<Device>(() =>
|
||||
window.matchMedia("(max-width: 639px)").matches ? "mobile" : "desktop",
|
||||
isMobileViewport() ? "mobile" : "desktop",
|
||||
);
|
||||
// o vídeo de cada aparelho/projeto nasce tocando só se o visitante não pausou e não pediu menos movimento
|
||||
// "economizar dados": o arquivo só chega quando o visitante pede (play), não antes
|
||||
const [saveData] = useState(isSaveData);
|
||||
const [autoplay, setAutoplay] = useState(
|
||||
() => !window.matchMedia("(prefers-reduced-motion: reduce)").matches,
|
||||
() =>
|
||||
!window.matchMedia("(prefers-reduced-motion: reduce)").matches &&
|
||||
!saveData,
|
||||
);
|
||||
const wantsToPlay = useRef(autoplay);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
@@ -199,7 +206,6 @@ export function ProjectLightbox({
|
||||
};
|
||||
|
||||
const video = (
|
||||
// biome-ignore lint/a11y/useMediaCaption: demonstração sem áudio; a descrição está no título e nos capítulos
|
||||
<video
|
||||
key={`${config.id}-${device}`}
|
||||
ref={videoRef}
|
||||
@@ -208,7 +214,7 @@ export function ProjectLightbox({
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
preload="auto"
|
||||
preload={saveData ? "metadata" : "auto"}
|
||||
poster={assets.poster}
|
||||
width={assets.width}
|
||||
height={assets.height}
|
||||
@@ -249,9 +255,9 @@ export function ProjectLightbox({
|
||||
{String(index + 1).padStart(2, "0")} /{" "}
|
||||
{String(items.length).padStart(2, "0")}
|
||||
</p>
|
||||
<h3 id={titleId} className="ds-lb-title">
|
||||
<h2 id={titleId} className="ds-lb-title">
|
||||
{project.title}
|
||||
</h3>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<fieldset className="ds-lb-toggle">
|
||||
@@ -371,25 +377,18 @@ export function ProjectLightbox({
|
||||
</ol>
|
||||
|
||||
<div className="ds-lb-links">
|
||||
<Link className="ds-link ds-link-muted" href={`/cases/${project.id}`}>
|
||||
Ver case
|
||||
<span className="sr-only"> de {project.title}</span>
|
||||
</Link>
|
||||
{canOpenSite && (
|
||||
<a
|
||||
className="ds-link ds-link-accent"
|
||||
className="ds-link ds-link-text"
|
||||
href={project.demoUrl ?? undefined}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Abrir projeto
|
||||
<span className="sr-only"> (abre em nova aba)</span>
|
||||
</a>
|
||||
)}
|
||||
{project.githubUrl && (
|
||||
<a
|
||||
className="ds-link ds-link-muted"
|
||||
href={project.githubUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Ver codigo
|
||||
Abrir site
|
||||
<span className="sr-only"> (abre em nova aba)</span>
|
||||
</a>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user