From f5c6f0d58b713eed12ceff3a1b8dbc9a0d6588fb Mon Sep 17 00:00:00 2001 From: Yotam Faraggi Date: Sun, 20 Sep 2026 12:14:39 +0200 Subject: [PATCH 01/21] Add verified StarLinker portfolio evidence metadata --- lib/portfolio-showcase.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/portfolio-showcase.ts b/lib/portfolio-showcase.ts index dbf22ba..e6c9050 100644 --- a/lib/portfolio-showcase.ts +++ b/lib/portfolio-showcase.ts @@ -15,6 +15,7 @@ export type PortfolioShowcaseProject = { title: string; descriptor: string; year: string; + updatedAt: string; status?: string; summary: string; audience: string; @@ -47,7 +48,8 @@ const projects: LocalizedProject[] = [ number: '01', title: 'StarLinker', year: '2026', - technologies: ['Next.js', 'React', 'TypeScript', 'PostgreSQL', 'Supabase', 'AI SDK'], + updatedAt: '2026-09-20', + technologies: ['Next.js', 'React', 'TypeScript', 'PostgreSQL', 'Drizzle', 'Replicache', 'Supabase', 'AI SDK'], accent: '#7367f0', accentSoft: '#d8d3ff', liveUrl: 'https://starlinker.io', @@ -80,7 +82,7 @@ const projects: LocalizedProject[] = [ summary: 'A visual planning product that brings goals, projects, tasks, habits and notes into one connected workspace, with an AI agent that can understand and act inside it.', audience: 'People who think spatially and want one place to plan work, life and long-term goals without reducing everything to disconnected lists.', role: 'Founder · Product concept · UX · Architecture · Full-stack · AI', - highlights: ['Connected visual planning', 'AI assistant with workspace actions', 'Goals, projects, tasks and habits in one system'], + highlights: ['Graph-first workspace with offline-capable sync', 'Permission-aware AI actions with undo', 'Realtime collaboration, billing and operational tooling'], }, he: { descriptor: 'פרודוקטיביות AI-native', @@ -88,7 +90,7 @@ const projects: LocalizedProject[] = [ summary: 'מוצר תכנון ויזואלי שמחבר מטרות, פרויקטים, משימות, הרגלים והערות לסביבת עבודה אחת, עם סוכן AI שמבין את המרחב ויכול לפעול בתוכו.', audience: 'לאנשים שחושבים בצורה ויזואלית ורוצים מקום אחד לתכנן עבודה, חיים ומטרות ארוכות טווח בלי להפוך הכל לרשימות מנותקות.', role: 'Founder · קונספט מוצר · UX · ארכיטקטורה · Full-stack · AI', - highlights: ['תכנון ויזואלי מחובר', 'עוזר AI שפועל בתוך סביבת העבודה', 'מטרות, פרויקטים, משימות והרגלים במערכת אחת'], + highlights: ['סביבת graph עם sync שתומך בעבודה offline', 'פעולות AI עם הרשאות ו-undo', 'שיתוף בזמן אמת, billing וכלים תפעוליים'], }, }, { From 6b67ef828a36a00ea914b4a9067924d763621830 Mon Sep 17 00:00:00 2001 From: Yotam Faraggi Date: Sun, 20 Sep 2026 12:14:47 +0200 Subject: [PATCH 02/21] Clarify RightFlow evidence and Ensemblis development status --- lib/portfolio-showcase.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/portfolio-showcase.ts b/lib/portfolio-showcase.ts index e6c9050..34cdf0e 100644 --- a/lib/portfolio-showcase.ts +++ b/lib/portfolio-showcase.ts @@ -99,6 +99,7 @@ const projects: LocalizedProject[] = [ number: '02', title: 'RightFlow', year: '2025-26', + updatedAt: '2026-09-20', technologies: ['Next.js', 'React', 'TypeScript', 'Python', 'FastAPI', 'Firebase'], accent: '#2563eb', accentSoft: '#cbdcfb', @@ -152,7 +153,7 @@ const projects: LocalizedProject[] = [ summary: 'A document review and verification platform for pension and payroll contribution checks, turning sensitive multi-document work into a structured, repeatable workflow.', audience: 'Finance, payroll and operations teams that need to review contribution documents consistently, explain findings clearly and export a usable report.', role: 'Founder · Product · Workflow design · Automation · Full-stack', - highlights: ['Structured document review', 'Verification and exception handling', 'Repeatable report-ready workflow'], + highlights: ['Normalized multi-document review cases', 'Explicit exceptions and traceable findings', 'Repeatable report-ready verification workflow'], }, he: { descriptor: 'Document intelligence', @@ -160,7 +161,7 @@ const projects: LocalizedProject[] = [ summary: 'פלטפורמת בדיקה ואימות מסמכים להפקדות פנסיה ושכר, שהופכת עבודה רגישה ורבת מסמכים לתהליך מובנה, עקבי וניתן לחזרה.', audience: 'לצוותי כספים, שכר ותפעול שצריכים לבדוק מסמכי הפקדות באופן עקבי, להסביר ממצאים בצורה ברורה ולהפיק דוח שימושי.', role: 'Founder · מוצר · תכנון workflow · אוטומציה · Full-stack', - highlights: ['סקירת מסמכים מובנית', 'אימות וטיפול בחריגות', 'תהליך עקבי שמוביל לדוח'], + highlights: ['תיקי בדיקה מנורמלים ממספר מסמכים', 'חריגות מפורשות וממצאים ניתנים למעקב', 'תהליך אימות עקבי שמוביל לדוח'], }, }, { @@ -169,6 +170,7 @@ const projects: LocalizedProject[] = [ number: '03', title: 'Ensemblis', year: '2026', + updatedAt: '2026-09-20', technologies: ['Next.js', 'React', 'TypeScript', 'AI workflows', 'Media APIs', 'Automation'], accent: '#9b72f2', accentSoft: '#ddd0ff', @@ -178,7 +180,7 @@ const projects: LocalizedProject[] = [ }, en: { descriptor: 'Artist operating system', - status: 'In development', + status: 'Product in development', summary: 'An AI-assisted operating system for independent artists that brings release planning, content creation, media intelligence, marketing workflows and distribution operations into one product.', audience: 'Independent artists and small teams who currently run releases, content, media and distribution across too many disconnected tools.', role: 'Founder · Product concept · Product design · AI systems · Full-stack', @@ -186,7 +188,7 @@ const projects: LocalizedProject[] = [ }, he: { descriptor: 'מערכת הפעלה לאמנים', - status: 'בפיתוח', + status: 'מוצר בפיתוח', summary: 'מערכת הפעלה בסיוע AI לאמנים עצמאיים שמחברת תכנון ריליסים, יצירת תוכן, מודיעין מדיה, תהליכי שיווק ותפעול הפצה למוצר אחד.', audience: 'לאמנים עצמאיים וצוותים קטנים שמנהלים כיום ריליסים, תוכן, מדיה והפצה בין יותר מדי כלים מנותקים.', role: 'Founder · קונספט מוצר · Product design · מערכות AI · Full-stack', From 49d53b152e9635ba732b9f486b955410d224e29e Mon Sep 17 00:00:00 2001 From: Yotam Faraggi Date: Sun, 20 Sep 2026 12:14:59 +0200 Subject: [PATCH 03/21] Strengthen WakeMyWay proof and CartShift ownership --- lib/portfolio-showcase.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/portfolio-showcase.ts b/lib/portfolio-showcase.ts index 34cdf0e..f69a59e 100644 --- a/lib/portfolio-showcase.ts +++ b/lib/portfolio-showcase.ts @@ -201,7 +201,8 @@ const projects: LocalizedProject[] = [ number: '04', title: 'WakeMyWay', year: '2026', - technologies: ['Kotlin', 'Jetpack Compose', 'AlarmManager', 'On-device Speech', 'Roborazzi', 'GitHub Actions'], + updatedAt: '2026-09-20', + technologies: ['Kotlin', 'Jetpack Compose', 'AlarmManager', 'Direct Boot', 'On-device Speech', 'Roborazzi', 'GitHub Actions'], accent: '#ff8b5f', accentSoft: '#ffd7c6', repositoryUrl: 'https://github.com/yotamon/WakeMyWay', @@ -246,19 +247,19 @@ const projects: LocalizedProject[] = [ }, en: { descriptor: 'Adaptive Android alarm', - status: 'Founder dogfood', + status: 'Active personal product', summary: 'A local-first conversational Android alarm designed to help people move through sleep inertia into action, then learn which wake strategy works best for them.', audience: 'People who genuinely want to get up at a chosen time but need something smarter than a louder sound or another generic alarm challenge.', role: 'Founder · Product · Android architecture · UX · Kotlin', - highlights: ['Reliability-first native alarm', 'Local conversational wake', 'Physical activation signals and local learning'], + highlights: ['Exact native alarm with Direct Boot recovery', 'Deterministic local wake runtime with voice and motion', 'Local learning isolated from the critical alarm path'], }, he: { descriptor: 'שעון מעורר אדפטיבי ל-Android', - status: 'Founder dogfood', + status: 'מוצר אישי פעיל', summary: 'שעון מעורר שיחתי local-first ל-Android שנועד לעזור לעבור מאינרציית שינה לפעולה, ובהמשך ללמוד איזו אסטרטגיית השכמה עובדת הכי טוב לכל אדם.', audience: 'לאנשים שבאמת רוצים לקום בשעה שבחרו, אבל צריכים משהו חכם יותר מצליל חזק יותר או עוד משימת השכמה גנרית.', role: 'Founder · מוצר · ארכיטקטורת Android · UX · Kotlin', - highlights: ['Alarm native עם עדיפות לאמינות', 'השכמה שיחתית מקומית', 'אותות תנועה ולמידה מקומית'], + highlights: ['Alarm native מדויק עם Direct Boot recovery', 'Wake runtime דטרמיניסטי עם קול ותנועה', 'למידה מקומית שמופרדת מנתיב ההשכמה הקריטי'], }, }, { @@ -267,6 +268,7 @@ const projects: LocalizedProject[] = [ number: '05', title: 'CartShift Studio', year: '2025-26', + updatedAt: '2026-09-20', technologies: ['Next.js', 'React', 'TypeScript', 'Firebase', 'PayPal', 'Puppeteer'], accent: '#6157d8', accentSoft: '#d7d2ff', @@ -320,16 +322,16 @@ const projects: LocalizedProject[] = [ status: 'Active product', summary: 'The operating platform behind my web and commerce studio, combining the public site, lead generation, project proposals, client workflows, automation and internal tools.', audience: 'Clients buying digital product work and the studio team managing acquisition, proposals, delivery and ongoing relationships.', - role: 'Founder · Product · UX · Architecture · Full-stack', - highlights: ['Lead and store analysis funnels', 'Proposals and client workflows', 'Commerce, automation and studio operations'], + role: 'Co-founder · Lead engineer · Product architecture · Full-stack', + highlights: ['Lead and store-analysis funnels', 'Proposals, payments and client workflows', 'Client portal, automation and studio operations'], }, he: { descriptor: 'Commerce + product engineering', status: 'מוצר פעיל', summary: 'הפלטפורמה התפעולית מאחורי סטודיו ה-web וה-commerce שלי, שמחברת אתר ציבורי, יצירת לידים, הצעות עבודה, תהליכי לקוח, אוטומציות וכלים פנימיים.', audience: 'ללקוחות שרוכשים עבודת מוצר דיגיטלית ולצוות הסטודיו שמנהל acquisition, הצעות, delivery וקשר מתמשך.', - role: 'Founder · מוצר · UX · ארכיטקטורה · Full-stack', - highlights: ['פאנלים ללידים וניתוח חנויות', 'הצעות עבודה ותהליכי לקוח', 'Commerce, אוטומציה ותפעול סטודיו'], + role: 'Co-founder · Lead engineer · ארכיטקטורת מוצר · Full-stack', + highlights: ['פאנלים ללידים וניתוח חנויות', 'הצעות, תשלומים ותהליכי לקוח', 'פורטל לקוחות, אוטומציה ותפעול סטודיו'], }, }, ]; From 45dbaf6284c1f7c405efc0a0b5a4ffa3c3415b22 Mon Sep 17 00:00:00 2001 From: Yotam Faraggi Date: Sun, 20 Sep 2026 12:15:09 +0200 Subject: [PATCH 04/21] Show every personal project and fix client-work navigation --- app/[locale]/(standalone)/yotam/YotamProfile.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/[locale]/(standalone)/yotam/YotamProfile.tsx b/app/[locale]/(standalone)/yotam/YotamProfile.tsx index 14c3fa3..04f4845 100644 --- a/app/[locale]/(standalone)/yotam/YotamProfile.tsx +++ b/app/[locale]/(standalone)/yotam/YotamProfile.tsx @@ -10,7 +10,7 @@ import { getPortfolioShowcases } from '@/lib/portfolio-showcase'; const ease = [0.22, 1, 0.36, 1] as const; const reveal = { hidden: { opacity: 0, y: 26 }, visible: { opacity: 1, y: 0 } }; -const selectedProjectOrder = ['starlinker', 'rightflow', 'cartshift-studio', 'wakemyway']; +const selectedProjectOrder = ['starlinker', 'rightflow', 'ensemblis', 'wakemyway', 'cartshift-studio']; export default function YotamProfile() { const locale = useLocale(); @@ -31,7 +31,7 @@ export default function YotamProfile() { const isHebrew = locale === 'he'; const profileHref = `/${locale}/yotam`; const pdfHref = `/${locale}/cv/render?variant=default`; - const portfolioHref = `/${locale}/portfolio`; + const clientWorkHref = `/${locale}/work`; const copy = isHebrew ? { @@ -510,7 +510,7 @@ export default function YotamProfile() { From fb1eef946a0bf5ed56fdb6a501e4f30646a595a6 Mon Sep 17 00:00:00 2001 From: Yotam Faraggi Date: Sun, 20 Sep 2026 12:15:39 +0200 Subject: [PATCH 05/21] Add consistent project proof snapshots and direct portfolio navigation --- .../portfolio/[slug]/ProjectShowcase.tsx | 103 +++++++++++++++++- 1 file changed, 99 insertions(+), 4 deletions(-) diff --git a/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx b/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx index 817efeb..50fe8b7 100644 --- a/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx +++ b/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState, type CSSProperties, type ReactNode } from 'react'; +import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'; import { AnimatePresence, motion, useReducedMotion, type MotionProps } from 'framer-motion'; import { ArrowLeft, ArrowRight, ArrowUpRight, Github, X } from 'lucide-react'; import type { PortfolioShowcaseProject, ShowcaseMedia } from '@/lib/portfolio-showcase'; @@ -60,7 +60,7 @@ function Screen({ type="button" onClick={onOpen} className={`group relative block w-full overflow-hidden text-start focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#111113] ${shell} ${className}`} - aria-label={`Open ${media.label}`} + aria-label={media.alt} > +
+
+ {[ + [labels.status, project.status ?? (isHebrew ? 'פעיל' : 'Active')], + [labels.period, project.year], + [labels.ownership, project.role], + ].map(([label, value]) => ( +
+ +

{value}

+
+ ))} +
+ +
    + {project.highlights.map(item => ( +
  • + + {item} +
  • + ))} +
+
+
+
+ {labels.stack} + {project.technologies.map(technology => ( + {technology} + ))} +
+
+ + ); +} + +function TechnicalEvidence({ + isHebrew, + eyebrow, + title, + body, + steps, + dark = false, +}: { + isHebrew: boolean; + eyebrow: string; + title: string; + body: string; + steps: string[]; + dark?: boolean; +}) { + return ( +
+
+
+
+ +

{body}

+
+
+

{title}

+
+ {steps.map((step, index) => ( +
+ {String(index + 1).padStart(2, '0')} +

{step}

+
+ ))} +
+

+ {isHebrew ? 'תרשים מפושט של גבולות המערכת' : 'Simplified system boundary'} +

+
+
+
+
+ ); +} + function EngineeringDecisions({ isHebrew, items, @@ -121,8 +206,8 @@ function EngineeringDecisions({ } function SharedHeader({ project, locale, isHebrew }: { project: PortfolioShowcaseProject; locale: string; isHebrew: boolean }) { - const portfolioHref = `/${locale}/portfolio`; - const cvHref = `/${locale}/cv`; + const portfolioHref = `/${locale}/yotam#work`; + const cvHref = `/${locale}/yotam#experience`; return (
@@ -176,6 +261,8 @@ function StarLinkerStory({ project, isHebrew, reveal, onOpen }: StoryProps) { + +
@@ -287,6 +374,8 @@ function RightFlowStory({ project, isHebrew, reveal, onOpen }: StoryProps) {
+ +
@@ -396,6 +485,8 @@ function EnsemblisStory({ project, isHebrew, reveal }: StoryProps) {
+ +
@@ -464,6 +555,8 @@ function WakeMyWayStory({ project, isHebrew, reveal, onOpen }: StoryProps) {
+ +
@@ -573,6 +666,8 @@ function CartShiftStory({ project, isHebrew, reveal, onOpen }: StoryProps) {
+ +
From 2135a5657d933467b0f62d1ddd26df9e120d057b Mon Sep 17 00:00:00 2001 From: Yotam Faraggi Date: Sun, 20 Sep 2026 12:16:02 +0200 Subject: [PATCH 06/21] Expose verifiable architecture and workflow evidence in project case studies --- .../portfolio/[slug]/ProjectShowcase.tsx | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx b/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx index 50fe8b7..8732b1f 100644 --- a/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx +++ b/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx @@ -307,6 +307,25 @@ function StarLinkerStory({ project, isHebrew, reveal, onOpen }: StoryProps) {
) : null} + + ) : null} + + + + ); @@ -599,6 +655,25 @@ function WakeMyWayStory({ project, isHebrew, reveal, onOpen }: StoryProps) { ) : null} + + ) : null} + + Date: Sun, 20 Sep 2026 12:16:21 +0200 Subject: [PATCH 07/21] Fix project lightbox focus management and portfolio navigation --- .../portfolio/[slug]/ProjectShowcase.tsx | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx b/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx index 8732b1f..687e888 100644 --- a/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx +++ b/app/[locale]/(standalone)/portfolio/[slug]/ProjectShowcase.tsx @@ -887,19 +887,38 @@ export default function ProjectShowcase({ project, nextProject, locale }: Props) const isHebrew = locale === 'he'; const reduceMotion = useReducedMotion(); const [activeMedia, setActiveMedia] = useState(null); - const portfolioHref = `/${locale}/portfolio`; + const closeButtonRef = useRef(null); + const restoreFocusRef = useRef(null); + const portfolioHref = `/${locale}/yotam#work`; useEffect(() => { if (!activeMedia) return; + const previousOverflow = document.body.style.overflow; + restoreFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null; document.body.style.overflow = 'hidden'; + + const focusCloseButton = window.requestAnimationFrame(() => closeButtonRef.current?.focus()); const onKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape') setActiveMedia(null); + if (event.key === 'Escape') { + event.preventDefault(); + setActiveMedia(null); + return; + } + + if (event.key === 'Tab') { + event.preventDefault(); + closeButtonRef.current?.focus(); + } }; + window.addEventListener('keydown', onKeyDown); return () => { + window.cancelAnimationFrame(focusCloseButton); document.body.style.overflow = previousOverflow; window.removeEventListener('keydown', onKeyDown); + restoreFocusRef.current?.focus(); + restoreFocusRef.current = null; }; }, [activeMedia]); @@ -920,7 +939,7 @@ export default function ProjectShowcase({ project, nextProject, locale }: Props) const storyProps = { project, isHebrew, reveal, onOpen: setActiveMedia }; return ( -
+
{project.slug === 'starlinker' ? : null} @@ -941,7 +960,7 @@ export default function ProjectShowcase({ project, nextProject, locale }: Props) {activeMedia ? ( setActiveMedia(null)}> - + event.stopPropagation()}> From c00b0408840e9bc87d33903fdbaf9e5b1b96d39c Mon Sep 17 00:00:00 2001 From: Yotam Faraggi Date: Sun, 20 Sep 2026 12:16:49 +0200 Subject: [PATCH 08/21] Replace brittle accent-based portfolio CSS selectors --- .../portfolio/[slug]/project-showcase.css | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/[locale]/(standalone)/portfolio/[slug]/project-showcase.css b/app/[locale]/(standalone)/portfolio/[slug]/project-showcase.css index f0482f4..20ea92a 100644 --- a/app/[locale]/(standalone)/portfolio/[slug]/project-showcase.css +++ b/app/[locale]/(standalone)/portfolio/[slug]/project-showcase.css @@ -2,34 +2,34 @@ The global site heading rule otherwise forces a dark heading color even inside editorial dark sections. Keep this scoped to project showcase pages and preserve predictable contrast and wrapping across narrow mobile widths. */ -main[style*='--project-accent'] h1, -main[style*='--project-accent'] h2, -main[style*='--project-accent'] h3, -main[style*='--project-accent'] h4, -main[style*='--project-accent'] h5, -main[style*='--project-accent'] h6 { +main[data-project] h1, +main[data-project] h2, +main[data-project] h3, +main[data-project] h4, +main[data-project] h5, +main[data-project] h6 { color: inherit; } -main[style*='--project-accent'] h1, -main[style*='--project-accent'] h2 { +main[data-project] h1, +main[data-project] h2 { text-wrap: balance; } /* These are single-word product names. The old manual break was purely decorative and becomes awkward on narrow screens. */ -main[style*='--project-accent:#7367f0'] h1 br, -main[style*='--project-accent:#ff8b5f'] h1 br { +main[data-project='starlinker'] h1 br, +main[data-project='wakemyway'] h1 br { display: none; } @media (max-width: 639px) { - main[style*='--project-accent'] h1, - main[style*='--project-accent'] h2 { + main[data-project] h1, + main[data-project] h2 { line-height: 0.86; } - main[style*='--project-accent'] h1 { + main[data-project] h1 { overflow-wrap: normal; word-break: normal; } From f4401242834e924593745a6ef79f7c6be6f8b2e8 Mon Sep 17 00:00:00 2001 From: Yotam Faraggi Date: Sun, 20 Sep 2026 12:16:56 +0200 Subject: [PATCH 09/21] Use real project update dates in portfolio sitemap --- app/sitemap.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/sitemap.ts b/app/sitemap.ts index ff60222..8a41010 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,7 +1,7 @@ import { MetadataRoute } from 'next'; import { getAllPosts } from '@/lib/markdown'; import { getAllCaseStudies } from '@/lib/case-studies'; -import { portfolioShowcaseSlugs } from '@/lib/portfolio-showcase'; +import { getPortfolioShowcases } from '@/lib/portfolio-showcase'; import { ANALYZER_INTENTS } from '@/lib/analyzer/funnel'; import { adminDb } from '@/lib/firebase-admin'; @@ -41,6 +41,7 @@ export default async function sitemap(): Promise { const posts = await getAllPosts(); const caseStudies = getAllCaseStudies('en'); + const portfolioProjects = getPortfolioShowcases('en'); const blogUrls = posts.flatMap(post => createLocalizedUrls(baseUrl, `/blog/${post.slug}`, new Date(post.date), 'monthly', 0.7) @@ -50,8 +51,14 @@ export default async function sitemap(): Promise { createLocalizedUrls(baseUrl, `/work/${study.slug}`, undefined, 'monthly', 0.7) ); - const portfolioProjectUrls = portfolioShowcaseSlugs.flatMap(slug => - createLocalizedUrls(baseUrl, `/portfolio/${slug}`, now, 'monthly', 0.8) + const portfolioProjectUrls = portfolioProjects.flatMap(project => + createLocalizedUrls( + baseUrl, + `/portfolio/${project.slug}`, + new Date(project.updatedAt), + 'monthly', + 0.8 + ) ); const approvedInsights = adminDb From a6180b2f20e35c148de23c887c81e68cda9f69a1 Mon Sep 17 00:00:00 2001 From: Yotam Faraggi Date: Sun, 20 Sep 2026 12:17:13 +0200 Subject: [PATCH 10/21] Add dedicated social cards for personal project case studies --- .../portfolio/[slug]/opengraph-image.tsx | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 app/[locale]/(standalone)/portfolio/[slug]/opengraph-image.tsx diff --git a/app/[locale]/(standalone)/portfolio/[slug]/opengraph-image.tsx b/app/[locale]/(standalone)/portfolio/[slug]/opengraph-image.tsx new file mode 100644 index 0000000..37be7ec --- /dev/null +++ b/app/[locale]/(standalone)/portfolio/[slug]/opengraph-image.tsx @@ -0,0 +1,103 @@ +import { ImageResponse } from 'next/og'; +import { getPortfolioShowcase } from '@/lib/portfolio-showcase'; + +export const alt = 'Yotam Faraggi project case study'; +export const size = { width: 1200, height: 630 }; +export const contentType = 'image/png'; + +type Props = { + params: Promise<{ locale: string; slug: string }>; +}; + +export default async function OpenGraphImage({ params }: Props) { + const { locale, slug } = await params; + const project = getPortfolioShowcase(slug, locale); + const isHebrew = locale === 'he'; + + const title = project?.title ?? 'Project case study'; + const descriptor = project?.descriptor ?? 'Product engineering'; + const summary = project?.summary ?? ''; + const accent = project?.accent ?? '#7367f0'; + const status = project?.status ?? (isHebrew ? 'פרויקט' : 'Project'); + + return new ImageResponse( + ( +
+
+ + Yotam Faraggi + + + {status} + +
+ +
+ + {descriptor} + +
+ {title} +
+
+ {summary} +
+
+ +
+ {project?.year ?? '2026'} + cart-shift.com/{locale}/portfolio/{slug} +
+
+ ), + size + ); +} From fd99cfdcd5234a0bd4f8b91fb90fecce62aa7954 Mon Sep 17 00:00:00 2001 From: Yotam Faraggi Date: Sun, 20 Sep 2026 12:17:23 +0200 Subject: [PATCH 11/21] Improve project metadata and add structured software evidence --- .../(standalone)/portfolio/[slug]/page.tsx | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/app/[locale]/(standalone)/portfolio/[slug]/page.tsx b/app/[locale]/(standalone)/portfolio/[slug]/page.tsx index 5f3e18f..4460deb 100644 --- a/app/[locale]/(standalone)/portfolio/[slug]/page.tsx +++ b/app/[locale]/(standalone)/portfolio/[slug]/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from 'next'; +import Script from 'next/script'; import { notFound } from 'next/navigation'; import { setRequestLocale } from 'next-intl/server'; import ProjectShowcase from './ProjectShowcase'; @@ -29,8 +30,6 @@ export async function generateMetadata({ params }: Props): Promise { const isHebrew = locale === 'he'; const title = `${project.title} - ${project.descriptor} | Yotam Faraggi`; const description = project.summary; - const image = project.hero?.src.startsWith('/') ? project.hero.src : '/images/portfolio-v2/hero-art.webp'; - return { title, description, @@ -48,14 +47,12 @@ export async function generateMetadata({ params }: Props): Promise { description, type: 'website', url: `${siteUrl}/${locale}/portfolio/${slug}`, - images: [{ url: image, alt: project.hero?.alt ?? project.title }], locale: isHebrew ? 'he_IL' : 'en_US', }, twitter: { card: 'summary_large_image', title, description, - images: [image], }, }; } @@ -69,6 +66,31 @@ export default async function PortfolioProjectPage({ params }: Props) { if (!project) notFound(); const nextProject = getNextPortfolioShowcase(slug, validLocale); + const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://cart-shift.com'; + const jsonLd = { + '@context': 'https://schema.org', + '@type': 'SoftwareApplication', + name: project.title, + description: project.summary, + url: project.liveUrl ?? `${siteUrl}/${validLocale}/portfolio/${project.slug}`, + dateModified: project.updatedAt, + creator: { + '@type': 'Person', + name: 'Yotam Faraggi', + url: `${siteUrl}/${validLocale}/yotam`, + }, + applicationCategory: project.descriptor, + sameAs: [project.liveUrl, project.repositoryUrl].filter(Boolean), + }; - return ; + return ( + <> +