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
42 changes: 20 additions & 22 deletions app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
export const metadata = {
title: 'About - Calen Irwin',
description: 'About page for Calen Irwin\'s personal website.',
description: 'About Calen Irwin.',
}

export default function AboutPage() {
return (
<div className="min-h-screen py-12 px-4">
<div className="max-w-4xl mx-auto">
<div className="prose prose-lg max-w-none">
<section className="mb-8">
<p className="text-gray-700 leading-relaxed mb-4">
I&apos;m Cale, a data scientist at Hitachi Rail in Toronto.
</p>
<br />
<p className="text-gray-700 leading-relaxed mb-4">
Most of my work involves designing and realizing prototype autonomy applications for the rail industry.
</p>
<br />
<p className="text-gray-700 leading-relaxed mb-4">
Lately, I&apos;ve been coding with Claude to make something cool. More on that later.
</p>
<br />
<p className="text-gray-700 leading-relaxed mb-4">
This site is a space for sharing my thoughts and projects. Thanks for checking it out.
</p>
</section>
</div>
<div className="max-w-2xl mx-auto px-6 py-16">
<h1 className="font-heading text-4xl md:text-5xl font-extrabold tracking-tight mb-12">
About
</h1>

<div className="space-y-6 text-gray-700 leading-relaxed">
<p>
I&apos;m Cale. Research data scientist at Hitachi Rail in Toronto,
working on autonomy systems for trains.
</p>

<p>
I studied CS at Queen&apos;s. Outside of work I read fantasy, run,
and camp.
</p>

<p>
I write here about AI, building products, and ideas I find interesting.
</p>
</div>
</div>
)
Expand Down
100 changes: 50 additions & 50 deletions app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@ import { notFound } from 'next/navigation'
import Link from 'next/link'
import { getAllPosts, getPostBySlug } from '@/lib/markdown'
import { formatDate, calculateReadingTime } from '@/lib/utils'
import Tag from '@/components/ui/Tag'

export async function generateStaticParams() {
const posts = await getAllPosts()
return posts.map((post) => ({
slug: post.slug,
}))
return posts.map((post) => ({ slug: post.slug }))
}

export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPostBySlug(slug)

if (!post) {
return {
title: 'Post Not Found',
}
return { title: 'Post Not Found' }
}

return {
Expand All @@ -38,53 +33,58 @@ export default async function BlogPostPage({ params }: { params: Promise<{ slug:
const readingTime = calculateReadingTime(post.content)

return (
<article className="min-h-screen py-12 px-4">
<div className="max-w-4xl mx-auto">
{/* Back Link */}
<Link
href="/blog"
className="inline-flex items-center text-primary-teal hover:underline mb-8"
>
← Back to Blog
</Link>

{/* Post Header */}
<header className="mb-8">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
{post.title}
</h1>

<div className="flex flex-wrap items-center gap-4 text-gray-600 mb-4">
<time dateTime={post.date}>{formatDate(post.date)}</time>
<span>•</span>
<span>{readingTime} min read</span>
</div>
<article className="max-w-2xl mx-auto px-6 py-16">
<Link
href="/blog"
className="text-sm text-gray-500 hover:text-black transition-colors"
>
&larr; Writing
</Link>

<div className="flex flex-wrap gap-2">
{post.tags.map((tag) => (
<Tag key={tag} variant="teal" clickable>
{tag}
</Tag>
))}
</div>
</header>
<header className="mt-8 mb-12">
<h1 className="font-heading text-3xl md:text-4xl font-black tracking-tight mb-4">
{post.title}
</h1>
<div className="flex items-center gap-3 text-sm text-gray-500">
<time dateTime={post.date}>{formatDate(post.date)}</time>
<span>&middot;</span>
<span>{readingTime} min read</span>
</div>
</header>

{/* Post Content */}
<div
className="prose prose-lg max-w-none prose-headings:text-gray-900 prose-p:text-gray-700 prose-a:text-primary-teal prose-a:no-underline hover:prose-a:underline prose-strong:text-gray-900 prose-code:text-primary-coral prose-code:bg-gray-100 prose-code:px-1 prose-code:py-0.5 prose-code:rounded"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
<div
className="prose prose-lg max-w-none prose-dropcap
prose-headings:font-heading prose-headings:tracking-tight prose-headings:text-black prose-headings:font-black
prose-p:text-gray-700 prose-p:leading-relaxed
prose-a:text-black prose-a:underline prose-a:underline-offset-2 prose-a:decoration-gray-300 hover:prose-a:decoration-black
prose-strong:text-black prose-strong:font-semibold
prose-em:text-gray-600
prose-code:text-gray-700 prose-code:bg-gray-50 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-normal prose-code:before:content-none prose-code:after:content-none
prose-blockquote:border-gray-300 prose-blockquote:text-gray-500 prose-blockquote:font-normal
prose-li:text-gray-700
prose-hr:border-gray-200"
dangerouslySetInnerHTML={{ __html: post.content }}
/>

{/* Back to Blog */}
<div className="mt-12 pt-8 border-t border-gray-200">
<Link
href="/blog"
className="inline-flex items-center text-primary-teal hover:underline font-medium"
>
← Back to all posts
</Link>
<footer className="mt-16 pt-8 border-t border-gray-200">
<div className="flex flex-wrap gap-3 mb-8 text-sm">
{post.tags.map((tag) => (
<Link
key={tag}
href={`/tags/${encodeURIComponent(tag)}`}
className="text-gray-500 hover:text-black transition-colors"
>
#{tag}
</Link>
))}
</div>
</div>
<Link
href="/blog"
className="text-sm text-gray-500 hover:text-black transition-colors"
>
&larr; All writing
</Link>
</footer>
</article>
)
}
82 changes: 50 additions & 32 deletions app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,65 @@
import BlogPost from '@/components/BlogPost'
import Tag from '@/components/ui/Tag'
import Link from 'next/link'
import { getAllPosts, getAllTags } from '@/lib/markdown'
import { formatDate } from '@/lib/utils'

export const metadata = {
title: 'Blog - Calen Irwin',
description: 'Thoughts on technology, AI, design, and more',
title: 'Writing - Calen Irwin',
description: 'Thoughts on AI, building products, and the philosophy behind both.',
}

export default async function BlogPage() {
const allPosts = await getAllPosts()
const tags = getAllTags(allPosts)

return (
<div className="min-h-screen py-12 px-4">
<div className="max-w-6xl mx-auto">
<div className="max-w-2xl mx-auto px-6 py-16">
<h1 className="font-heading text-4xl md:text-5xl font-extrabold tracking-tight mb-12">
Writing
</h1>

{/* Tags Filter */}
{tags.length > 0 && (
<div className="mb-8">
<h2 className="text-lg font-semibold text-gray-900 mb-3">Filter by tag:</h2>
<div className="flex flex-wrap gap-2">
{tags.map((tag) => (
<Tag key={tag} variant="teal" clickable>
{tag}
</Tag>
))}
</div>
</div>
)}
{tags.length > 0 && (
<div className="flex flex-wrap gap-x-4 gap-y-2 mb-12 text-sm">
{tags.map((tag) => (
<Link
key={tag}
href={`/tags/${encodeURIComponent(tag)}`}
className="text-gray-500 hover:text-black transition-colors"
>
{tag}
</Link>
))}
</div>
)}

{/* Blog Posts Grid */}
{allPosts.length > 0 ? (
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{allPosts.map((post) => (
<BlogPost key={post.slug} post={post} />
))}
</div>
) : (
<div className="text-center py-16 bg-gray-50 rounded-lg">
<p className="text-xl text-gray-600">No blog posts yet</p>
</div>
)}
</div>
{allPosts.length > 0 ? (
<div className="divide-y divide-gray-100">
{allPosts.map((post) => (
<Link
key={post.slug}
href={`/blog/${post.slug}`}
className="group flex flex-col sm:flex-row sm:items-baseline sm:justify-between gap-1 py-5"
>
<div>
<span className="group-hover:opacity-60 transition-opacity">
{post.title}
</span>
<div className="flex gap-3 mt-1">
{post.tags.map((tag) => (
<span key={tag} className="text-xs text-gray-500">
{tag}
</span>
))}
</div>
</div>
<span className="text-sm text-gray-500 shrink-0">
{formatDate(post.date, 'short')}
</span>
</Link>
))}
</div>
) : (
<p className="text-gray-500">No posts yet.</p>
)}
</div>
)
}
32 changes: 16 additions & 16 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 255, 255, 255;
--background-end-rgb: 255, 255, 255;
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
::selection {
background: #000;
color: #fff;
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
/* Drop cap on first paragraph of blog posts */
.prose-dropcap > p:first-of-type::first-letter {
font-family: var(--font-heading);
float: left;
font-size: 3.75em;
line-height: 0.8;
padding-right: 0.08em;
padding-top: 0.05em;
font-weight: 800;
color: #000;
}
11 changes: 6 additions & 5 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { Syne, Geist_Mono } from 'next/font/google'
import './globals.css'
import Navigation from '@/components/Navigation'
import Footer from '@/components/Footer'

const inter = Inter({ subsets: ['latin'] })
const syne = Syne({ subsets: ['latin'], weight: ['700', '800'], variable: '--font-heading' })
const geistMono = Geist_Mono({ subsets: ['latin'], variable: '--font-mono' })

export const metadata: Metadata = {
title: 'Calen Irwin - Personal Blog',
description: 'Personal blog and portfolio showcasing writing and projects',
title: 'Calen Irwin',
description: 'Writing about AI, building things, and what I\'m learning.',
}

export default function RootLayout({
Expand All @@ -18,7 +19,7 @@ export default function RootLayout({
}) {
return (
<html lang="en">
<body className={`${inter.className} flex flex-col min-h-screen`}>
<body className={`${syne.variable} ${geistMono.variable} font-mono flex flex-col min-h-screen bg-white text-black`}>
<Navigation />
<main className="flex-grow">{children}</main>
<Footer />
Expand Down
Loading
Loading