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.
95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import type { CSSProperties } from "react";
|
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
|
import { getProjectVideo, getShowcaseConfig } from "@/lib/project-showcase";
|
|
import { ProjectVideo } from "../ProjectVideo";
|
|
import { BrowserFrame, displayUrl } from "./BrowserFrame";
|
|
import { useShowcaseCard } from "./ShowcaseProvider";
|
|
import { useNearViewport, useViewportDevice } from "./useShowcaseMedia";
|
|
|
|
/**
|
|
* Capa do card com vídeo: o vídeo dentro de uma moldura de navegador, com brilho da cor do site quando é o
|
|
* card que toca, e o botão que amplia (a tela inteira é o botão). A versão do aparelho (celular abaixo de
|
|
* 768 px) só é escolhida no navegador; no servidor e antes de a capa chegar a ~1 tela da janela, nem o pôster
|
|
* é pedido (a moldura já reserva o espaço).
|
|
*/
|
|
export function ProjectMedia({
|
|
project,
|
|
}: {
|
|
project: {
|
|
id: string;
|
|
title: string;
|
|
demoUrl?: string | null;
|
|
};
|
|
}) {
|
|
const { frameRef, active, open, userPaused, toggleAutoplay } =
|
|
useShowcaseCard(project.id);
|
|
const reducedMotion = useReducedMotion();
|
|
const device = useViewportDevice();
|
|
const near = useNearViewport(frameRef);
|
|
const video = device ? getProjectVideo(project.id, device) : undefined;
|
|
const config = getShowcaseConfig(project.id);
|
|
if (!getProjectVideo(project.id) || !config) return null;
|
|
|
|
return (
|
|
<div
|
|
ref={frameRef}
|
|
className="ds-frame"
|
|
data-active={active}
|
|
style={{ "--accent": config.accent } as CSSProperties}
|
|
>
|
|
<BrowserFrame url={displayUrl(project)}>
|
|
<div
|
|
className="ds-frame-screen"
|
|
style={
|
|
video && near
|
|
? { backgroundImage: `url(${video.poster})` }
|
|
: undefined
|
|
}
|
|
>
|
|
{video && (
|
|
<ProjectVideo
|
|
video={video}
|
|
title={project.title}
|
|
active={active}
|
|
showPoster={near}
|
|
/>
|
|
)}
|
|
<button
|
|
type="button"
|
|
className="ds-frame-expand"
|
|
onClick={open}
|
|
aria-haspopup="dialog"
|
|
aria-label={`Ampliar o vídeo de ${project.title}`}
|
|
>
|
|
<span className="ds-frame-chip" aria-hidden="true">
|
|
Ampliar
|
|
</span>
|
|
</button>
|
|
{!reducedMotion && (
|
|
<button
|
|
type="button"
|
|
className="ds-frame-pause"
|
|
onClick={toggleAutoplay}
|
|
aria-label={
|
|
userPaused
|
|
? "Retomar os vídeos dos projetos"
|
|
: "Pausar os vídeos dos projetos"
|
|
}
|
|
>
|
|
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
{userPaused ? (
|
|
<path d="M4 2.5v11l9-5.5z" />
|
|
) : (
|
|
<path d="M4 3h3v10H4zM9 3h3v10H9z" />
|
|
)}
|
|
</svg>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</BrowserFrame>
|
|
</div>
|
|
);
|
|
}
|