Diagnóstico sem case que não cabe; desktop com o GCC Installer como projeto próprio; IoT com o Twins Lab e a nota da Apolo. Mosaico de pôsteres em /servicos e /cases, faixa baixa em uma coluna no currículo, privacidade e laboratório; Laboratório como lista compacta; SEV começando por catálogo, pedido no WhatsApp e painel; Joinville e SC nos títulos; imagens do LCP com fetchPriority e sizes reais; pré-carregamento só no hover; 404 com Open Graph próprio.
91 lines
2.9 KiB
TypeScript
91 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import { Play } from "lucide-react";
|
|
import Image from "next/image";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import type { Device, ShowcaseAssets } from "@/lib/project-showcase";
|
|
|
|
const deviceLabel: Record<Device, string> = {
|
|
desktop: "no computador",
|
|
mobile: "no celular",
|
|
};
|
|
|
|
/**
|
|
* Pôster com um botão de play. O vídeo só é montado, e só baixa, quando a pessoa aperta o botão:
|
|
* antes disso a página mostra a imagem do site, não um vídeo parado em 0:00 com controles do navegador.
|
|
* Vai dentro de um quadro com `position: relative` (moldura de navegador ou de celular) que dá o tamanho.
|
|
* `sizes` segue a grade de CaseMedia: a coluna da esquerda de um bloco de até 1040 px (88 vw da página abaixo de 1182 px)
|
|
* com a moldura do celular ao lado (220 px mais 32 px de vão); abaixo de 768 px, a largura da página menos 40 px.
|
|
*/
|
|
export function CasePlayer({
|
|
assets,
|
|
title,
|
|
device,
|
|
sizes = "(min-width: 1182px) 788px, (min-width: 768px) calc(88vw - 252px), calc(100vw - 40px)",
|
|
}: {
|
|
assets: ShowcaseAssets;
|
|
title: string;
|
|
device: Device;
|
|
sizes?: string;
|
|
}) {
|
|
const [playing, setPlaying] = useState(false);
|
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
|
|
// o botão sai de cena ao tocar: o foco vai para o vídeo, para quem usa o teclado não o perder
|
|
useEffect(() => {
|
|
if (playing) videoRef.current?.focus();
|
|
}, [playing]);
|
|
|
|
if (playing) {
|
|
return (
|
|
<video
|
|
ref={videoRef}
|
|
className="absolute inset-0 h-full w-full object-cover object-top"
|
|
autoPlay
|
|
controls
|
|
muted
|
|
loop
|
|
playsInline
|
|
poster={assets.poster}
|
|
width={assets.width}
|
|
height={assets.height}
|
|
tabIndex={0}
|
|
aria-label={`Vídeo do site ${title} ${deviceLabel[device]}`}
|
|
disablePictureInPicture
|
|
>
|
|
{assets.sources.map((source) => (
|
|
<source key={source.src} src={source.src} type={source.type} />
|
|
))}
|
|
</video>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/* o quadro do celular já mostra o pôster como fundo; no computador a imagem entra aqui */}
|
|
{device === "desktop" ? (
|
|
<Image
|
|
src={assets.poster}
|
|
alt=""
|
|
fill
|
|
sizes={sizes}
|
|
className="object-cover object-top"
|
|
/>
|
|
) : null}
|
|
<button
|
|
type="button"
|
|
onClick={() => setPlaying(true)}
|
|
aria-label={`Reproduzir o vídeo do site ${title} ${deviceLabel[device]}`}
|
|
className="group absolute inset-0 grid place-items-center focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-white"
|
|
>
|
|
<span
|
|
aria-hidden="true"
|
|
className="grid size-14 place-items-center rounded-full border border-white/70 bg-black/65 text-white transition-colors group-hover:bg-black/85 motion-reduce:transition-none sm:size-16"
|
|
>
|
|
<Play className="ml-1 size-6" fill="currentColor" />
|
|
</span>
|
|
</button>
|
|
</>
|
|
);
|
|
}
|