feat(cases): cases de clientes, serviços ligados à prova e SEO por página
Cases da SEV Moda Íntima e da Lavação 047; case no formato cliente, problema, entrega, mídia e contato, com detalhes técnicos recolhidos; sem GitHub, ressalvas e fontes de commit. /cases separa clientes e Laboratório. Serviços mostram o case de cliente certo e o bloco de investimento e prazo só aparece preenchido. Metadata social própria por página, not-found com noindex, sitemap com /ia.
This commit is contained in:
1 parent
3756905a1b
commit
6e1022b58c
44 files changed
+2691
-1424
No files matched your search
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
BrowserFrame,
|
||||
displayUrl,
|
||||
} from "@/app/components/showcase/BrowserFrame";
|
||||
import { PhoneFrame } from "@/app/components/showcase/PhoneFrame";
|
||||
import type { ShowcaseAssets } from "@/lib/project-showcase";
|
||||
import { getCaseMedia } from "./case-media";
|
||||
import "../../components/showcase/showcase.css";
|
||||
|
||||
function CaseVideo({
|
||||
assets,
|
||||
label,
|
||||
className,
|
||||
}: {
|
||||
assets: ShowcaseAssets;
|
||||
label: string;
|
||||
className: string;
|
||||
}) {
|
||||
return (
|
||||
<video
|
||||
className={className}
|
||||
controls
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={assets.poster}
|
||||
width={assets.width}
|
||||
height={assets.height}
|
||||
aria-label={label}
|
||||
disablePictureInPicture
|
||||
>
|
||||
{assets.sources.map((source) => (
|
||||
<source key={source.src} src={source.src} type={source.type} />
|
||||
))}
|
||||
</video>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pôster e vídeo do projeto: o site no computador (moldura de navegador) e no celular (moldura de celular).
|
||||
* O vídeo só baixa quando a pessoa aperta o play. Sem captura gravada, não mostra nada.
|
||||
*/
|
||||
export function CaseMedia({
|
||||
id,
|
||||
title,
|
||||
demoUrl,
|
||||
}: {
|
||||
id: string;
|
||||
title: string;
|
||||
demoUrl?: string | null;
|
||||
}) {
|
||||
const desktop = getCaseMedia(id, "desktop");
|
||||
const mobile = getCaseMedia(id, "mobile");
|
||||
if (!desktop && !mobile) return null;
|
||||
|
||||
return (
|
||||
<div className="mt-sp-8 grid items-end gap-sp-8 md:grid-cols-[minmax(0,1fr)_auto]">
|
||||
{desktop ? (
|
||||
<figure className="min-w-0">
|
||||
<BrowserFrame url={displayUrl({ demoUrl, title })}>
|
||||
<div className="relative aspect-[16/10] bg-[#0a0a0a]">
|
||||
<CaseVideo
|
||||
assets={desktop}
|
||||
label={`Vídeo do site ${title} no computador`}
|
||||
className="absolute inset-0 h-full w-full object-cover object-top"
|
||||
/>
|
||||
</div>
|
||||
</BrowserFrame>
|
||||
<figcaption className="mt-sp-3 font-[family-name:var(--font-body)] text-[15px] text-white/70">
|
||||
O site {title} no computador.
|
||||
</figcaption>
|
||||
</figure>
|
||||
) : null}
|
||||
{mobile ? (
|
||||
<figure className="mx-auto w-[min(62vw,220px)] md:mx-0">
|
||||
<PhoneFrame poster={mobile.poster}>
|
||||
<CaseVideo
|
||||
assets={mobile}
|
||||
label={`Vídeo do site ${title} no celular`}
|
||||
className="absolute inset-0 h-full w-full object-cover object-top"
|
||||
/>
|
||||
</PhoneFrame>
|
||||
<figcaption className="mt-sp-3 text-center font-[family-name:var(--font-body)] text-[15px] text-white/70">
|
||||
No celular.
|
||||
</figcaption>
|
||||
</figure>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user