Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
29459e7
Improve case study content model and ordering
yotamon Sep 20, 2026
c8d23a6
Avoid synthetic article dates in structured data
yotamon Sep 20, 2026
c484631
Make case studies more specific and evidence-aware
yotamon Sep 20, 2026
ed7baf1
Reduce repeated portfolio card metadata
yotamon Sep 20, 2026
d3e9db7
Localize portfolio metadata and schema
yotamon Sep 20, 2026
c669879
Fix case study metadata and structured data
yotamon Sep 20, 2026
88bbde4
Refine English case study language
yotamon Sep 20, 2026
40d8299
Refine English case study language
yotamon Sep 20, 2026
b12f1c5
Refine Hebrew case study language
yotamon Sep 20, 2026
3859d7f
Refine Hebrew case study language
yotamon Sep 20, 2026
0548e4a
Update case study translation test fixtures
yotamon Sep 20, 2026
2cc98c1
Refine curalife-metabolic-wellness-platform case study
yotamon Sep 20, 2026
646ff5c
Refine i-love-my-honeypot-eco-ecommerce case study
yotamon Sep 20, 2026
6282b40
Refine stiletto-piercing-disposable-jewelry-supply case study
yotamon Sep 20, 2026
7c0d36a
Refine alondon-community-portal case study
yotamon Sep 20, 2026
9eacceb
Refine carmel-faraggi-independent-design-studio case study
yotamon Sep 20, 2026
b516231
Refine hands-and-vision-artist-collective case study
yotamon Sep 20, 2026
bfc45be
Refine colbotekk-label-collective-platform case study
yotamon Sep 20, 2026
86b61b3
Refine atlas-irwin-music-identity case study
yotamon Sep 20, 2026
b8e0309
Refine pedro-music-digital-release-store case study
yotamon Sep 20, 2026
f99378f
Tighten project notes for i-love-my-honeypot-eco-ecommerce
yotamon Sep 20, 2026
e561950
Tighten project notes for alondon-community-portal
yotamon Sep 20, 2026
ecd51bb
Tighten project notes for carmel-faraggi-independent-design-studio
yotamon Sep 20, 2026
7105700
Tighten project notes for hands-and-vision-artist-collective
yotamon Sep 20, 2026
8478619
Update case study gallery normalization tests
yotamon Sep 20, 2026
a2dc855
Avoid synthetic case study sitemap dates
yotamon Sep 20, 2026
1646f1b
Make Alondon summary factual and specific
yotamon Sep 20, 2026
9f145c3
Test evidence labels and contextual case study CTAs
yotamon Sep 20, 2026
d7cde3f
Test portfolio ordering and CuraLife proof context
yotamon Sep 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions app/[locale]/(website)/work/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ export async function generateMetadata({
params: Promise<{ locale: string; slug: string }>;
}): Promise<Metadata> {
const { locale, slug } = await params;
const isHe = locale === 'he';
const caseStudy = getCaseStudyBySlug(slug, locale);

if (!caseStudy) {
return genMeta(
{
title: 'Case Study Not Found',
description: 'The requested case study could not be found.',
title: isHe ? 'הפרויקט לא נמצא' : 'Case Study Not Found',
description: isHe
? 'הפרויקט המבוקש לא נמצא.'
: 'The requested case study could not be found.',
url: `/work/${slug}`,
noindex: true,
},
Expand All @@ -40,14 +43,14 @@ export async function generateMetadata({
const keywords = [
caseStudy.platform.toLowerCase(),
caseStudy.industry.toLowerCase(),
'case study',
'e-commerce success',
`${caseStudy.platform} development`,
isHe ? 'מקרה בוחן' : 'case study',
isHe ? 'פיתוח מסחר אלקטרוני' : 'e-commerce development',
`${caseStudy.platform} ${isHe ? 'פיתוח' : 'development'}`,
];

return genMeta(
{
title: `${caseStudy.title} | Case Study`,
title: `${caseStudy.title} | ${isHe ? 'מקרה בוחן' : 'Case Study'}`,
description: caseStudy.summary,
url: `/work/${slug}`,
type: 'article',
Expand All @@ -73,10 +76,11 @@ export default async function CaseStudyPage({

const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://cart-shift.com';

const isHe = locale === 'he';
const breadcrumbSchema = generateBreadcrumbSchema(
[
{ name: 'Home', url: '/' },
{ name: 'Work', url: '/work' },
{ name: isHe ? 'בית' : 'Home', url: '/' },
{ name: isHe ? 'פרויקטים' : 'Work', url: '/work' },
{ name: caseStudy.title, url: `/work/${slug}` },
],
locale as 'en' | 'he'
Expand All @@ -85,7 +89,6 @@ export default async function CaseStudyPage({
const articleSchema = generateArticleSchema({
title: caseStudy.title,
description: caseStudy.summary,
date: new Date().toISOString(),
url: `${siteUrl}/${locale}/work/${slug}`,
locale: locale as 'en' | 'he',
category: caseStudy.industry,
Expand Down
53 changes: 35 additions & 18 deletions app/[locale]/(website)/work/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,37 @@ export async function generateMetadata({
params: Promise<{ locale: string }>;
}): Promise<Metadata> {
const { locale } = await params;
const isHe = locale === 'he';
const featured = getAllCaseStudies(locale)[0];

return genMeta(
{
title: 'Shopify, WordPress, and Web App Case Studies | CartShift Studio Work',
description:
'Explore recent CartShift projects across Shopify, WordPress, custom web apps, migrations, and performance optimization. See how we turn technical work into sharper digital products and clearer growth paths.',
title: isHe
? 'פרויקטים ב-Shopify, WordPress ו-Web Apps | CartShift Studio'
: 'Shopify, WordPress, and Web App Case Studies | CartShift Studio Work',
description: isHe
? 'פרויקטים נבחרים של CartShift ב-Shopify, WordPress, אפליקציות ווב, מיגרציות ואופטימיזציה, עם מסכים אמיתיים, החלטות פיתוח ותוצאות מדודות כשיש נתונים.'
: 'Explore CartShift projects across Shopify, WordPress, custom web apps, migrations, and performance optimization, with real screens, implementation decisions, and measured results where available.',
url: '/work',
image: featured?.hero.image,
keywords: [
'shopify case studies',
'wordpress case studies',
'web app case studies',
'next.js case studies',
'ecommerce migration case study',
'shopify performance optimization',
'client work',
'ecommerce project examples',
],
keywords: isHe
? [
'פרויקטים בשופיפיי',
'פרויקטים בוורדפרס',
'פיתוח אפליקציות ווב',
'מקרי בוחן מסחר אלקטרוני',
'מיגרציה לשופיפיי',
'אופטימיזציית ביצועים',
]
: [
'shopify case studies',
'wordpress case studies',
'web app case studies',
'next.js case studies',
'ecommerce migration case study',
'shopify performance optimization',
'ecommerce project examples',
],
},
locale as 'en' | 'he'
);
Expand All @@ -43,18 +56,22 @@ export default async function WorkPage({ params }: { params: Promise<{ locale: s
setRequestLocale(locale as 'en' | 'he');
const caseStudies = getAllCaseStudies(locale);

const isHe = locale === 'he';
const breadcrumbSchema = generateBreadcrumbSchema(
[
{ name: 'Home', url: '/' },
{ name: 'Work', url: '/work' },
{ name: isHe ? 'בית' : 'Home', url: '/' },
{ name: isHe ? 'פרויקטים' : 'Work', url: '/work' },
],
locale as 'en' | 'he'
);

const collectionSchema = generateCollectionPageSchema({
name: 'Our Work | Shopify, WordPress & Web App Projects',
description:
'Recent Shopify, WordPress, web app, migration, and optimization projects from CartShift Studio.',
name: isHe
? 'הפרויקטים שלנו | Shopify, WordPress ואפליקציות ווב'
: 'Our Work | Shopify, WordPress & Web App Projects',
description: isHe
? 'פרויקטים נבחרים של CartShift ב-Shopify, WordPress, אפליקציות ווב, מיגרציות ואופטימיזציה.'
: 'Recent Shopify, WordPress, web app, migration, and optimization projects from CartShift Studio.',
url: '/work',
locale: locale as 'en' | 'he',
items: caseStudies.map(cs => ({
Expand Down
6 changes: 3 additions & 3 deletions app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ function generateAlternates(baseUrl: string, path: string): { languages: Record<
function createLocalizedUrls(
baseUrl: string,
path: string,
lastModified: Date,
lastModified: Date | undefined,
changeFrequency: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never',
priority: number
) {
return locales.map(locale => ({
url: `${baseUrl}/${locale}${path}`,
lastModified,
...(lastModified && { lastModified }),
changeFrequency,
priority,
alternates: generateAlternates(baseUrl, path),
Expand All @@ -47,7 +47,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
);

const caseStudyUrls = caseStudies.flatMap(study =>
createLocalizedUrls(baseUrl, `/work/${study.slug}`, now, 'monthly', 0.7)
createLocalizedUrls(baseUrl, `/work/${study.slug}`, undefined, 'monthly', 0.7)
);

const portfolioProjectUrls = portfolioShowcaseSlugs.flatMap(slug =>
Expand Down
37 changes: 27 additions & 10 deletions components/sections/CaseStudyDetailContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@ type WorkDetailTranslations = {
getConsultation: string;
viewMore: string;
projectOverview: string;
overviewEyebrow: string;
whatWeShipped: string;
deliveryEyebrow: string;
selectedScreens: string;
evidenceTitle: string;
evidenceEyebrow: string;
evidenceResultsTitle: string;
whatChangedTitle: string;
clientQuote: string;
projectNotes: string;
notesEyebrow: string;
visitSite: string;
galleryHint: string;
openImage: string;
Expand Down Expand Up @@ -83,6 +89,10 @@ export const CaseStudyDetailContent: React.FC<CaseStudyDetailContentProps> = ({
const heroStoryRotate = useTransform(heroProgress, [0, 1], [-6, -2]);

const workT = t.raw('work' as never) as WorkDetailTranslations;
const hasMeasuredResults = caseStudy.evidence.some(item => item.tone === 'quantitative');
const evidenceHeading = hasMeasuredResults
? workT.detail.evidenceResultsTitle
: workT.detail.whatChangedTitle;

const breadcrumbItems = [
{ label: t('navigation.home'), href: '/' },
Expand Down Expand Up @@ -281,14 +291,19 @@ export const CaseStudyDetailContent: React.FC<CaseStudyDetailContentProps> = ({
>
<div>
<p className="mb-3 text-xs font-semibold uppercase tracking-[0.28em] text-surface-500 dark:text-surface-400">
{workT.detail.projectOverview}
{workT.detail.overviewEyebrow}
</p>
<h2 className="max-w-2xl text-3xl font-bold tracking-[-0.03em] text-surface-900 dark:text-white sm:text-4xl">
{caseStudy.overview.title}
</h2>
<p className="mt-5 max-w-3xl text-lg leading-relaxed text-surface-600 dark:text-surface-300">
{caseStudy.overview.summary}
</p>
{caseStudy.attribution && (
<p className="mt-5 max-w-3xl border-s-2 border-[rgba(var(--case-primary-rgb),0.4)] ps-4 text-sm leading-relaxed text-surface-500 dark:text-surface-400">
{caseStudy.attribution}
</p>
)}
</div>

<div
Expand Down Expand Up @@ -353,9 +368,6 @@ export const CaseStudyDetailContent: React.FC<CaseStudyDetailContentProps> = ({
<p className="text-sm font-semibold text-surface-900 dark:text-white">
{caseStudy.duration}
</p>
<p className="text-sm text-surface-600 dark:text-surface-400">
{caseStudy.hero.supportingCopy || caseStudy.summary}
</p>
</div>
</div>
)}
Expand All @@ -379,7 +391,7 @@ export const CaseStudyDetailContent: React.FC<CaseStudyDetailContentProps> = ({
<div className="mb-10 flex items-end justify-between gap-6">
<div>
<p className="mb-3 text-xs font-semibold uppercase tracking-[0.28em] text-surface-500 dark:text-surface-400">
{workT.detail.whatWeShipped}
{workT.detail.deliveryEyebrow}
</p>
<h2 className="text-3xl font-bold tracking-[-0.03em] text-surface-900 dark:text-white sm:text-4xl">
{workT.detail.whatWeShipped}
Expand Down Expand Up @@ -441,10 +453,10 @@ export const CaseStudyDetailContent: React.FC<CaseStudyDetailContentProps> = ({
<motion.div {...sectionReveal}>
<div className="mb-10 max-w-3xl">
<p className="mb-3 text-xs font-semibold uppercase tracking-[0.28em] text-surface-500 dark:text-surface-400">
{workT.detail.evidenceTitle}
{workT.detail.evidenceEyebrow}
</p>
<h2 className="text-3xl font-bold tracking-[-0.03em] text-surface-900 dark:text-white sm:text-4xl">
{workT.detail.evidenceTitle}
{evidenceHeading}
</h2>
</div>

Expand All @@ -465,6 +477,11 @@ export const CaseStudyDetailContent: React.FC<CaseStudyDetailContentProps> = ({
<p className="mt-3 text-base leading-relaxed text-surface-600 dark:text-surface-300">
{item.description}
</p>
{item.context && (
<p className="mt-3 text-xs leading-relaxed text-surface-500 dark:text-surface-400">
{item.context}
</p>
)}
{(item.before || item.after) && (
<div className="mt-5 flex flex-wrap items-center gap-2 text-sm text-surface-500 dark:text-surface-400">
{item.before && (
Expand Down Expand Up @@ -549,7 +566,7 @@ export const CaseStudyDetailContent: React.FC<CaseStudyDetailContentProps> = ({
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.28em] text-surface-500 dark:text-surface-400">
{workT.detail.projectNotes}
{workT.detail.notesEyebrow}
</p>
<h2 className="text-2xl font-bold tracking-[-0.03em] text-surface-900 dark:text-white">
{workT.detail.projectNotes}
Expand Down Expand Up @@ -581,10 +598,10 @@ export const CaseStudyDetailContent: React.FC<CaseStudyDetailContentProps> = ({
{workT.cta.title}
</p>
<h2 className="text-3xl font-bold leading-tight tracking-[-0.03em] text-white sm:text-4xl lg:text-5xl">
{workT.detail.wantResults}
{caseStudy.cta?.title || workT.detail.wantResults}
</h2>
<p className="mt-4 text-base leading-relaxed text-white/80 sm:text-lg">
{workT.detail.wantResultsDesc}
{caseStudy.cta?.description || workT.detail.wantResultsDesc}
</p>
</div>

Expand Down
4 changes: 1 addition & 3 deletions components/sections/WorkPageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -366,9 +366,7 @@ function PortfolioWorkTile({
platformLabel: string;
}) {
const themeStyle = getCaseStudyThemeStyle(caseStudy.brand);
const metaLine = caseStudy.duration
? `${caseStudy.client} / ${caseStudy.duration}`
: caseStudy.client;
const metaLine = caseStudy.duration || platformLabel;

return (
<motion.article
Expand Down
Loading
Loading