Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
160 changes: 130 additions & 30 deletions src/app/projects/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { notFound } from 'next/navigation';
import { getAllSlugs, getContentBySlug } from '@/lib/content';
import Link from 'next/link';
import {
getAllSlugs,
getContentBySlug,
getAdjacentContent,
getAllContent,
} from '@/lib/content';
import ProjectNavigation from '@/components/ProjectNavigation';

export function generateStaticParams() {
return getAllSlugs('projects').map((slug) => ({ slug }));
Expand All @@ -26,39 +33,132 @@ export default async function ProjectPage({
const item = getContentBySlug('projects', params.slug);
if (!item) notFound();

const all = getAllContent('projects');
const idx = all.findIndex((p) => p.slug === params.slug);
const num = String(idx + 1).padStart(2, '0');
const { prev, next } = getAdjacentContent('projects', params.slug);

const { default: MDXContent } = await import(
`../../../../content/projects/${params.slug}.mdx`
);

const date = new Date(item.frontmatter.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});

return (
<div className="mx-auto max-w-3xl px-6 py-10">
<article className="prose-mdx">
<header className="mb-8">
<h1 className="text-3xl font-bold text-neutral-900 dark:text-neutral-100">
{item.frontmatter.title}
</h1>
<p className="mt-2 text-sm text-neutral-500 dark:text-neutral-400">
{new Date(item.frontmatter.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</p>
{item.frontmatter.tags && item.frontmatter.tags.length > 0 && (
<ul className="mt-3 flex flex-wrap gap-2">
{item.frontmatter.tags.map((tag) => (
<li
key={tag}
className="rounded-full bg-neutral-100 px-2.5 py-0.5 text-xs font-medium text-neutral-600 dark:bg-neutral-800 dark:text-neutral-300"
>
{tag}
</li>
))}
</ul>
)}
</header>
<MDXContent />
</article>
</div>
<section style={{ padding: '60px 0 100px' }}>
<div className="wrap" style={{ maxWidth: 920 }}>
{/* Back link */}
<Link
href="/projects"
style={{
fontFamily: "'JetBrains Mono', monospace",
fontSize: 13,
color: 'var(--muted)',
textDecoration: 'none',
display: 'inline-flex',
alignItems: 'center',
gap: 8,
marginBottom: 32,
transition: 'color .2s',
}}
>
← back to projects
</Link>

{/* Section head */}
<div className="section-head" style={{ marginBottom: 24 }}>
<span className="num">{num} {'//'}</span>
<span>{params.slug}.mdx</span>
<span className="line" />
<span>{date}</span>
</div>

{/* Hero */}
<header style={{ marginBottom: 40 }}>
<h1
style={{
fontFamily: "'Space Grotesk', sans-serif",
fontWeight: 700,
fontSize: 'clamp(2rem, 4.5vw, 3.2rem)',
letterSpacing: '-0.03em',
lineHeight: 1.05,
margin: '0 0 16px',
color: 'var(--text)',
}}
>
{item.frontmatter.title}
</h1>
<p
style={{
fontSize: 18,
lineHeight: 1.55,
color: 'var(--muted)',
margin: '0 0 20px',
maxWidth: 720,
}}
>
{item.frontmatter.description}
</p>
{item.frontmatter.tags && item.frontmatter.tags.length > 0 && (
<ul
style={{
display: 'flex',
flexWrap: 'wrap',
gap: 7,
listStyle: 'none',
padding: 0,
margin: 0,
}}
>
{item.frontmatter.tags.map((tag) => (
<li key={tag}>
<span className="chip">{tag}</span>
</li>
))}
</ul>
)}
</header>

{/* Gradient separator */}
<div
style={{
height: 1,
background: 'var(--grad-soft)',
opacity: 0.6,
margin: '0 0 40px',
}}
/>

{/* MDX body */}
<article className="prose-mdx">
<MDXContent />
</article>

{/* Prev/Next navigation */}
<ProjectNavigation prev={prev} next={next} />

{/* All projects link */}
<div style={{ marginTop: 40, textAlign: 'center' }}>
<Link
href="/projects"
style={{
fontFamily: "'JetBrains Mono', monospace",
fontSize: 13,
color: 'var(--muted)',
textDecoration: 'none',
display: 'inline-flex',
gap: 8,
transition: 'color .2s',
}}
>
all projects →
</Link>
</div>
</div>
</section>
);
}
83 changes: 60 additions & 23 deletions src/app/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import MotionSection from '@/components/MotionSection';
import ContentProjectCard from '@/components/ContentProjectCard';
import { getAllContent } from '@/lib/content';
import MdxProjectCard from '@/components/MdxProjectCard';
import RevealOnScroll from '@/components/RevealOnScroll';

export const metadata = {
title: 'Projects — Altan Esmer',
Expand All @@ -11,27 +11,64 @@ export default function ProjectsPage() {
const projects = getAllContent('projects');

return (
<div className="mx-auto max-w-3xl px-6 py-10">
<MotionSection>
<h1 className="mb-8 text-3xl font-bold tracking-tight text-neutral-900 dark:text-neutral-100">
Projects
</h1>
{projects.length > 0 ? (
<div className="flex flex-col gap-4">
{projects.map((item) => (
<ContentProjectCard
key={item.slug}
slug={item.slug}
frontmatter={item.frontmatter}
/>
))}
<section style={{ padding: '80px 0 100px' }}>
<div className="wrap">
{/* Section head */}
<div className="section-head reveal">
<span className="num">02 //</span>
<span>all_projects.json</span>
<span className="line" />
<span>{projects.length} entries</span>
</div>
) : (
<p className="text-neutral-500 dark:text-neutral-400">
No projects yet. Check back soon.
</p>
)}
</MotionSection>
</div>

{/* Hero title */}
<h1
className="reveal"
style={{
fontFamily: "'Space Grotesk', sans-serif",
fontWeight: 700,
fontSize: 'clamp(2.4rem, 5vw, 4.4rem)',
letterSpacing: '-0.04em',
lineHeight: 0.95,
margin: '0 0 48px',
color: 'var(--text)',
}}
>
All the work I&apos;ve{' '}
<span style={{ color: 'var(--magenta)' }}>shipped.</span>
</h1>

<RevealOnScroll>
{projects.length > 0 ? (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '24px',
}}
className="projects-grid"
>
{projects.map((item, i) => (
<MdxProjectCard
key={item.slug}
slug={item.slug}
frontmatter={item.frontmatter}
index={i}
/>
))}
</div>
) : (
<p style={{ color: 'var(--muted)' }}>No projects yet. Check back soon.</p>
)}
</RevealOnScroll>

{/* Responsive: collapse to 1 col on mobile */}
<style>{`
@media (max-width: 720px) {
.projects-grid { grid-template-columns: 1fr !important; gap: 18px !important; }
}
`}</style>
</div>
</section>
);
}
Loading
Loading