feat: add route-backed project case studies
Some checks failed
Test and deploy / Verify production build (push) Has been cancelled
Test and deploy / Deploy production (push) Has been cancelled

This commit is contained in:
2026-09-09 22:00:57 +03:30
parent b3cfcfe057
commit 24cd86ede7
25 changed files with 1892 additions and 65 deletions

View File

@@ -0,0 +1,128 @@
import Image from 'next/image';
import Link from 'next/link';
import { ArrowLeft, ArrowRight } from 'lucide-react';
import { messagesByLocale, type Locale } from '@/i18n/content';
import { getProjectPath, type Project } from '@/data/projects';
import styles from './CaseStudyDrawer.module.css';
export function CaseStudyContent({
project,
locale,
previous,
next,
}: {
project: Project;
locale: Locale;
previous: Project;
next: Project;
}) {
const copy = messagesByLocale[locale].projects;
const caseStudy = project.caseStudy;
const isRtl = locale === 'fa';
return (
<article className={styles.content}>
<header className={styles.hero}>
<p className={styles.date}>{project.date}</p>
<h1 id="case-study-title" className={styles.title} tabIndex={-1} data-case-study-heading>
{project.title}
</h1>
<p id="case-study-introduction" className={styles.introduction}>{caseStudy.introduction}</p>
<dl className={styles.facts}>
<div><dt>{copy.roleLabel}</dt><dd>{caseStudy.role}</dd></div>
<div><dt>{copy.usersLabel}</dt><dd>{caseStudy.targetUsers}</dd></div>
<div><dt>{copy.statusLabel}</dt><dd>{caseStudy.status}</dd></div>
{caseStudy.team && <div><dt>{copy.teamLabel}</dt><dd>{caseStudy.team}</dd></div>}
</dl>
</header>
<section className={styles.section} aria-labelledby="case-study-gallery">
<h2 id="case-study-gallery" className={styles.sectionTitle}>{copy.galleryLabel}</h2>
<div className={styles.gallery}>
{caseStudy.gallery.map((item, index) => (
<figure key={`${item.image.src}-${index}`} className={index === 0 ? styles.featuredMedia : styles.media}>
<div className={styles.imageFrame}>
<Image
src={item.image.src}
width={item.image.width}
height={item.image.height}
alt={item.alt}
sizes={index === 0 ? '(max-width: 650px) 100vw, 68rem' : '(max-width: 650px) 100vw, 34rem'}
className={styles.galleryImage}
priority={index === 0}
/>
</div>
<figcaption>{item.caption}</figcaption>
</figure>
))}
</div>
</section>
<div className={styles.storyGrid}>
<section className={styles.section} aria-labelledby="case-study-challenge">
<h2 id="case-study-challenge" className={styles.sectionTitle}>{copy.challengeLabel}</h2>
<p>{caseStudy.challenge}</p>
</section>
<section className={styles.section} aria-labelledby="case-study-ownership">
<h2 id="case-study-ownership" className={styles.sectionTitle}>{copy.ownershipLabel}</h2>
<ul className={styles.list}>{caseStudy.ownership.map((item) => <li key={item}>{item}</li>)}</ul>
</section>
</div>
{caseStudy.constraints && (
<section className={styles.section} aria-labelledby="case-study-constraints">
<h2 id="case-study-constraints" className={styles.sectionTitle}>{copy.constraintsLabel}</h2>
<ul className={styles.list}>{caseStudy.constraints.map((item) => <li key={item}>{item}</li>)}</ul>
</section>
)}
<section className={styles.section} aria-labelledby="case-study-decisions">
<h2 id="case-study-decisions" className={styles.sectionTitle}>{copy.decisionsLabel}</h2>
<div className={styles.decisions}>
{caseStudy.decisions.map((decision) => (
<article key={`${decision.discipline}-${decision.title}`} className={styles.decision} data-discipline={decision.discipline}>
<span>{copy.decisionLabels[decision.discipline]}</span>
<h3>{decision.title}</h3>
<p>{decision.description}</p>
</article>
))}
</div>
</section>
<section className={styles.section} aria-labelledby="case-study-outcomes">
<h2 id="case-study-outcomes" className={styles.sectionTitle}>{copy.outcomesLabel}</h2>
{caseStudy.metrics && <dl className={styles.metrics}>{caseStudy.metrics.map((metric) => <div key={metric.label}><dt>{metric.value}</dt><dd>{metric.label}</dd></div>)}</dl>}
<ul className={styles.list}>{caseStudy.outcomes.map((item) => <li key={item}>{item}</li>)}</ul>
</section>
<div className={styles.stackGrid}>
<section className={styles.section} aria-labelledby="case-study-skills">
<h2 id="case-study-skills" className={styles.sectionTitle}>{copy.skillsLabel}</h2>
<div className={styles.badges}>{caseStudy.skills.map((skill) => <span key={skill}>{skill}</span>)}</div>
</section>
<section className={styles.section} aria-labelledby="case-study-tools">
<h2 id="case-study-tools" className={styles.sectionTitle}>{copy.toolsLabel}</h2>
<div className={`${styles.badges} ltr-token`} lang="en" dir="ltr">{caseStudy.tools.map((tool) => <span key={tool}>{tool}</span>)}</div>
</section>
</div>
{caseStudy.lessons && (
<section className={styles.section} aria-labelledby="case-study-lessons">
<h2 id="case-study-lessons" className={styles.sectionTitle}>{copy.lessonsLabel}</h2>
<ul className={styles.list}>{caseStudy.lessons.map((item) => <li key={item}>{item}</li>)}</ul>
</section>
)}
<nav className={styles.projectNavigation} aria-label={copy.drawerLabel}>
<Link href={getProjectPath(locale, previous.id)} replace scroll={false} className={styles.projectNavLink}>
{isRtl ? <ArrowRight aria-hidden="true" /> : <ArrowLeft aria-hidden="true" />}
<span><small>{copy.previousCaseStudy}</small><strong>{previous.title}</strong></span>
</Link>
<Link href={getProjectPath(locale, next.id)} replace scroll={false} className={`${styles.projectNavLink} ${styles.nextProject}`}>
<span><small>{copy.nextCaseStudy}</small><strong>{next.title}</strong></span>
{isRtl ? <ArrowLeft aria-hidden="true" /> : <ArrowRight aria-hidden="true" />}
</Link>
</nav>
</article>
);
}