diff --git a/app/components/ProjectCover.tsx b/app/components/ProjectCover.tsx new file mode 100644 index 0000000..67fca2c --- /dev/null +++ b/app/components/ProjectCover.tsx @@ -0,0 +1,52 @@ +import Image from "next/image"; +import { SCREENSHOT_BY_PROJECT_ID } from "./story/notebookProjects"; + +type CoverProject = { id: string; title: string }; + +const SCREENSHOTS: Record = + SCREENSHOT_BY_PROJECT_ID; + +function initials(title: string) { + return title + .split(/\s+/) + .filter((word) => word.length > 2 || /^[A-Z]/.test(word)) + .slice(0, 2) + .map((word) => word[0]) + .join("") + .toUpperCase(); +} + +/** + * Capa do card de projeto: a captura de tela real quando existe; sem captura, um painel gerado em código + * (só o monograma, sem repetir a stack que já aparece nas tags do card) em vez de uma imagem inventada. + */ +export function ProjectCover({ + project, + priority = false, +}: { + project: CoverProject; + priority?: boolean; +}) { + const src = SCREENSHOTS[project.id]; + + if (src) { + return ( +
+ {`Captura +
+ ); + } + + return ( + + ); +} diff --git a/app/components/Projects.tsx b/app/components/Projects.tsx index 81a3781..c7b296c 100644 --- a/app/components/Projects.tsx +++ b/app/components/Projects.tsx @@ -6,6 +6,7 @@ import type { PortfolioProjectView } from "@/lib/portfolio-data"; import { caseStudies } from "@/lib/site-content"; import { DrawLine } from "./animations/DrawLine"; import MagneticButton from "./cinematic/MagneticButton"; +import { ProjectCover } from "./ProjectCover"; import { Section } from "./Section"; export default function Projects({ @@ -59,6 +60,7 @@ export default function Projects({ whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }} transition={{ type: "spring", stiffness: 170, damping: 18 }} > +
{project.category}
diff --git a/app/components/story/notebookProjects.ts b/app/components/story/notebookProjects.ts index dda3c1b..afa19b2 100644 --- a/app/components/story/notebookProjects.ts +++ b/app/components/story/notebookProjects.ts @@ -20,7 +20,8 @@ const NOTEBOOK_ORDER = [ "as-servicos", ] as const; -const SCREENSHOT_BY_PROJECT_ID: Record< +/** Capturas de tela publicadas; os cards de projeto reutilizam as mesmas imagens. */ +export const SCREENSHOT_BY_PROJECT_ID: Record< (typeof NOTEBOOK_ORDER)[number], string > = { diff --git a/app/globals.css b/app/globals.css index 997e631..3838add 100644 --- a/app/globals.css +++ b/app/globals.css @@ -942,6 +942,53 @@ textarea { } } +/* Capa dos cards de projeto: captura real ou painel gerado em codigo */ +.ds-cover { + position: relative; + flex: none; /* o card e uma coluna flex: sem isto a capa encolhe e perde a proporcao */ + aspect-ratio: 16 / 10; + overflow: hidden; + margin-bottom: var(--sp-5); + border: 1px solid var(--border); + background: var(--ink); + + & img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: top; + transition: transform 0.7s var(--ease-out); + } + + .ds-card:hover & img { + transform: scale(1.035); + } +} + +.ds-section-dark .ds-cover { + border-color: rgba(255, 255, 255, 0.1); +} + +.ds-cover-fallback { + display: grid; + place-items: center; + padding: var(--sp-4); + color: var(--paper); + background: + radial-gradient(120% 90% at 100% 0%, var(--slash-dim), transparent 60%), + linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px) 0 0 / 100% 32px, + linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px) 0 0 / 32px 100%, + var(--ink); + + & .ds-cover-mark { + font-family: var(--font-display); + font-size: clamp(48px, 7vw, 96px); + font-weight: 800; + letter-spacing: -0.05em; + color: var(--slash); + } +} + /* Cinematic card hover effect */ .ds-card { transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);