From d76bbf59740737dc03a43bebcb519869a1b3dd53 Mon Sep 17 00:00:00 2001 From: Evandro Fonseca Junior Date: Wed, 15 Jul 2026 01:31:46 -0300 Subject: [PATCH] feat: revamp portfolio with case studies, curriculo page, and local SEO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds dynamic case study and curriculo routes, project health monitoring for live demo status, and strengthens local SEO (structured data, service-area targeting for Garuva/Joinville/Curitiba, a dedicated "criação de sites" service page) so search results better match the professional title (Engenheiro da Computação) and experience (5 anos). Co-Authored-By: Claude Sonnet 5 --- app/cases/[slug]/page.tsx | 146 ++++++++++++ app/cases/page.tsx | 59 +++-- app/components/About.tsx | 14 +- app/components/Experience.tsx | 44 ++-- app/components/Hero.reducedMotion.test.tsx | 8 +- app/components/Hero.test.tsx | 15 +- app/components/Hero.tsx | 72 +++--- app/components/Projects.tsx | 51 ++-- app/components/Skills.tsx | 27 +-- app/components/cinematic/CinematicHome.tsx | 73 +++--- .../cinematic/CinematicOverlays.tsx | 2 +- app/components/cinematic/PageIntro.test.tsx | 53 +++++ app/components/cinematic/PageIntro.tsx | 220 ++++++------------ app/curriculo/page.tsx | 133 +++++++++++ app/globals.css | 50 +--- app/icon.svg | 9 +- app/layout.tsx | 139 +++++++---- app/lib/should-load-hero-scene.test.ts | 37 +++ app/lib/should-load-hero-scene.ts | 11 + app/manifest.ts | 6 +- app/opengraph-image.tsx | 4 +- app/page.tsx | 13 +- app/servicos/[slug]/page.tsx | 3 + app/servicos/page.tsx | 12 + app/sitemap.ts | 16 +- data/project-health.json | 33 +++ docs/project-health-cron.md | 23 ++ .../plans/2026-07-10-portfolio-growth.md | 82 +++++++ .../2026-07-10-portfolio-growth-design.md | 27 +++ lib/portfolio-data.test.ts | 24 ++ lib/portfolio-data.ts | 50 ++++ lib/project-health.test.ts | 87 +++++++ lib/project-health.ts | 112 +++++++++ lib/site-content.ts | 45 ++++ linkedin.md | 6 +- package.json | 4 +- public/manifest.json | 20 -- public/portfolio-data.json | 4 +- public/robots.txt | 10 - public/sitemap.xml | 39 ---- scripts/check-projects.mjs | 68 ++++++ 41 files changed, 1359 insertions(+), 492 deletions(-) create mode 100644 app/cases/[slug]/page.tsx create mode 100644 app/components/cinematic/PageIntro.test.tsx create mode 100644 app/curriculo/page.tsx create mode 100644 app/lib/should-load-hero-scene.test.ts create mode 100644 app/lib/should-load-hero-scene.ts create mode 100644 data/project-health.json create mode 100644 docs/project-health-cron.md create mode 100644 docs/superpowers/plans/2026-07-10-portfolio-growth.md create mode 100644 docs/superpowers/specs/2026-07-10-portfolio-growth-design.md create mode 100644 lib/portfolio-data.test.ts create mode 100644 lib/portfolio-data.ts create mode 100644 lib/project-health.test.ts create mode 100644 lib/project-health.ts delete mode 100644 public/manifest.json delete mode 100644 public/robots.txt delete mode 100644 public/sitemap.xml create mode 100644 scripts/check-projects.mjs diff --git a/app/cases/[slug]/page.tsx b/app/cases/[slug]/page.tsx new file mode 100644 index 0000000..e3130ab --- /dev/null +++ b/app/cases/[slug]/page.tsx @@ -0,0 +1,146 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { caseStudies, siteName, siteUrl } from "@/lib/site-content"; + +interface CasePageProps { + params: Promise<{ slug: string }>; +} + +export function generateStaticParams() { + return caseStudies.map((caseStudy) => ({ slug: caseStudy.id })); +} + +export async function generateMetadata({ + params, +}: CasePageProps): Promise { + const { slug } = await params; + const caseStudy = caseStudies.find((item) => item.id === slug); + + if (!caseStudy) return { title: `Case não encontrado | ${siteName}` }; + + const url = `${siteUrl}/cases/${caseStudy.id}`; + return { + title: `${caseStudy.title}: ${caseStudy.subtitle} | ${siteName}`, + description: caseStudy.summary, + alternates: { canonical: url }, + openGraph: { + title: caseStudy.title, + description: caseStudy.summary, + url, + siteName, + type: "article", + }, + }; +} + +export default async function CasePage({ params }: CasePageProps) { + const { slug } = await params; + const caseStudy = caseStudies.find((item) => item.id === slug); + if (!caseStudy) notFound(); + + const url = `${siteUrl}/cases/${caseStudy.id}`; + const jsonLd = { + "@context": "https://schema.org", + "@graph": [ + { + "@type": "Article", + headline: caseStudy.title, + description: caseStudy.summary, + mainEntityOfPage: url, + author: { "@id": `${siteUrl}/#person` }, + about: caseStudy.stack, + }, + { + "@type": "BreadcrumbList", + itemListElement: [ + { "@type": "ListItem", position: 1, name: "Inicio", item: siteUrl }, + { + "@type": "ListItem", + position: 2, + name: "Cases", + item: `${siteUrl}/cases`, + }, + { + "@type": "ListItem", + position: 3, + name: caseStudy.title, + item: url, + }, + ], + }, + ], + }; + + return ( +
+