Hero e movimento - cena 3D em tempo real com os blocos da marca (kit feito no Blender, 3d/blocos.py): marca, tela, integrações e dados; segue a rolagem, tem vida em repouso, reage ao cursor e, parada no topo, passa sozinha pelos estados com legenda - três níveis de aparelho (full, lite, static); sem WebGL fica o pôster - título novo: "Sites e sistemas sob medida para o jeito que a sua empresa trabalha." - faixa de clientes com o ramo, linha do primeiro passo, hero alinhado às seções - rótulos digitados, entrada dos blocos, cortina de blocos entre páginas e capa compartilhada entre a lista de cases e o case Ofertas - atendimento no WhatsApp com IA e nota fiscal para a reforma tributária (páginas de serviço, cartões da home, formulário e aviso com data de validade) - página /ia atualizada, com fontes - peças 3D em loop nos cartões e topos de página (3d/pecas.py) Design - hover por papel (bloco nos botões, barra nos links), rótulos sem caixa alta - um destino por rótulo: "Falar sobre meu projeto" leva ao formulário, "Chamar no WhatsApp" abre o WhatsApp - logo montado em blocos, 404 redesenhado, grades sem célula vazia - ícones do PWA e manifesto novos Specs em specs/, contexto de design em DESIGN.md e PRODUCT.md.
73 lines
2.7 KiB
TypeScript
73 lines
2.7 KiB
TypeScript
import Image from "next/image";
|
|
import { ViewTransition } from "react";
|
|
import {
|
|
BrowserFrame,
|
|
displayUrl,
|
|
} from "@/app/components/showcase/BrowserFrame";
|
|
import { getCaseMedia } from "./case-media";
|
|
import { coverTransitionProps } from "./cover-transition";
|
|
|
|
/**
|
|
* Largura real da capa em cada lugar da página, para o navegador baixar a imagem do tamanho certo
|
|
* (com 100vw, um celular de 390 px pedia a versão de 750 px para uma capa de 300 px). As contas seguem o
|
|
* padding das seções (20 px até 768 px; depois 6vw, no máximo 64 px), a coluna central de 1240 px e os cartões
|
|
* (borda e 20 a 28 px de padding).
|
|
*/
|
|
export const coverSizes = {
|
|
/** Topo do case e do serviço: coluna da direita a partir de 1024 px, e a largura da página abaixo disso. */
|
|
hero: "(min-width: 1440px) 560px, (min-width: 1024px) 40vw, (min-width: 769px) 88vw, calc(100vw - 40px)",
|
|
/** Capa dentro de um cartão (`ds-card`) da grade de duas colunas, que vira uma só até 1024 px. */
|
|
card: "(min-width: 1440px) 560px, (min-width: 1025px) calc(50vw - 134px), (min-width: 769px) calc(88vw - 56px), calc(100vw - 88px)",
|
|
/** Capa solta numa grade de duas colunas, sem cartão em volta (a partir de 768 px). */
|
|
column:
|
|
"(min-width: 1440px) 604px, (min-width: 1068px) calc(50vw - 80px), (min-width: 768px) calc(44vw - 16px), calc(100vw - 40px)",
|
|
} as const;
|
|
|
|
/**
|
|
* Props da imagem que abre a página (a maior acima da dobra): carrega na hora e com prioridade de rede alta.
|
|
* No Next 16 a prop `priority` do `Image` só gera o `<link rel="preload">`; a prioridade de rede vem do `fetchPriority`.
|
|
*/
|
|
export const priorityImageProps = {
|
|
loading: "eager",
|
|
fetchPriority: "high",
|
|
} as const;
|
|
|
|
/**
|
|
* Capa do case: o pôster do site dentro de uma moldura de navegador. Sem captura gravada, não mostra nada.
|
|
* `priority` é para a imagem que abre a página: aplica `priorityImageProps`.
|
|
*/
|
|
export function CaseCover({
|
|
id,
|
|
title,
|
|
demoUrl,
|
|
sizes = coverSizes.card,
|
|
priority = false,
|
|
}: {
|
|
id: string;
|
|
title: string;
|
|
demoUrl?: string | null;
|
|
sizes?: string;
|
|
priority?: boolean;
|
|
}) {
|
|
const media = getCaseMedia(id, "desktop");
|
|
if (!media) return null;
|
|
const priorityProps = priority ? priorityImageProps : {};
|
|
|
|
return (
|
|
<BrowserFrame url={displayUrl({ demoUrl, title })}>
|
|
<div className="relative aspect-[16/10] bg-[#0a0a0a]">
|
|
<ViewTransition {...coverTransitionProps(id)}>
|
|
<Image
|
|
src={media.poster}
|
|
alt={`Captura de tela do site ${title}`}
|
|
fill
|
|
sizes={sizes}
|
|
{...priorityProps}
|
|
className="object-cover object-top"
|
|
/>
|
|
</ViewTransition>
|
|
</div>
|
|
</BrowserFrame>
|
|
);
|
|
}
|