updated look
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 2m15s
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 2m15s
This commit is contained in:
@@ -1,16 +1,23 @@
|
|||||||
|
const lineWidths = ['78%', '92%', '85%', '88%', '74%', '96%', '82%', '69%'];
|
||||||
|
|
||||||
export default function BlogPostLoading() {
|
export default function BlogPostLoading() {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl mx-auto px-6 py-24 animate-pulse">
|
<div className="page-frame py-20 sm:py-24">
|
||||||
<div className="space-y-4 mb-12">
|
<div className="mx-auto max-w-[70rem] animate-pulse xl:grid xl:grid-cols-[11rem_minmax(0,44rem)] xl:gap-x-10">
|
||||||
<div className="h-4 bg-zinc-200 dark:bg-zinc-800 rounded w-1/4" />
|
<div className="hidden xl:block" />
|
||||||
<div className="h-12 bg-zinc-200 dark:bg-zinc-800 rounded w-3/4" />
|
<div>
|
||||||
<div className="h-6 bg-zinc-200 dark:bg-zinc-800 rounded w-2/3" />
|
<div className="mb-8 space-y-4 border-b border-line pb-8">
|
||||||
|
<div className="h-3 w-1/3 rounded-full bg-accent-soft" />
|
||||||
|
<div className="h-14 w-3/4 rounded-[1rem] bg-paper-strong" />
|
||||||
|
<div className="h-5 w-2/3 rounded-full bg-paper-strong" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{Array.from({ length: 8 }).map((_, i) => (
|
{lineWidths.map((width) => (
|
||||||
<div key={i} className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded" style={{ width: `${70 + Math.random() * 30}%` }} />
|
<div key={width} className="h-4 rounded-full bg-paper-strong" style={{ width }} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ import { MDXRemote } from 'next-mdx-remote/rsc';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import rehypeSlug from 'rehype-slug';
|
|
||||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||||
import { TableOfContents } from '@/components/mdx/TableOfContents';
|
import { TableOfContents } from '@/components/mdx/TableOfContents';
|
||||||
import { SideNote } from '@/components/mdx/SideNote';
|
import { SideNote } from '@/components/mdx/SideNote';
|
||||||
import { Citation, Bibliography } from '@/components/mdx/Citation';
|
import { Citation, Bibliography } from '@/components/mdx/Citation';
|
||||||
import { MobileTableOfContents } from '@/components/mdx/MobileTableOfContents';
|
import { MobileTableOfContents } from '@/components/mdx/MobileTableOfContents';
|
||||||
|
|
||||||
// Utility to ensure consistent IDs
|
|
||||||
const slugify = (text: React.ReactNode): string => {
|
const slugify = (text: React.ReactNode): string => {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
const str = typeof text === 'string' ? text : String(text);
|
const str = typeof text === 'string' ? text : String(text);
|
||||||
@@ -21,16 +19,16 @@ const slugify = (text: React.ReactNode): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
h1: (props: React.ComponentPropsWithoutRef<'h1'>) => <h1 {...props} id={slugify(props.children)} className="text-3xl font-bold mt-8 mb-4 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
|
h1: (props: React.ComponentPropsWithoutRef<'h1'>) => <h1 {...props} id={slugify(props.children)} className="mt-12 scroll-mt-24 font-sans text-[2rem] font-medium leading-tight tracking-[-0.05em] text-ink sm:text-[2.5rem]" />,
|
||||||
h2: (props: React.ComponentPropsWithoutRef<'h2'>) => <h2 {...props} id={slugify(props.children)} className="text-2xl font-bold mt-8 mb-4 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
|
h2: (props: React.ComponentPropsWithoutRef<'h2'>) => <h2 {...props} id={slugify(props.children)} className="mt-14 scroll-mt-24 font-sans text-[1.65rem] font-medium leading-tight tracking-[-0.05em] text-ink sm:text-[2rem]" />,
|
||||||
h3: (props: React.ComponentPropsWithoutRef<'h3'>) => <h3 {...props} id={slugify(props.children)} className="text-xl font-bold mt-6 mb-3 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
|
h3: (props: React.ComponentPropsWithoutRef<'h3'>) => <h3 {...props} id={slugify(props.children)} className="mt-10 scroll-mt-24 font-sans text-[1.22rem] font-medium tracking-[-0.03em] text-ink sm:text-[1.42rem]" />,
|
||||||
p: (props: React.ComponentPropsWithoutRef<'p'>) => <p {...props} className="mb-4 leading-relaxed text-zinc-700 dark:text-zinc-300" />,
|
p: (props: React.ComponentPropsWithoutRef<'p'>) => <p {...props} className="mb-6 text-[1.02rem] leading-[1.85] text-ink-soft" />,
|
||||||
ul: (props: React.ComponentPropsWithoutRef<'ul'>) => <ul {...props} className="list-disc pl-5 mb-4 space-y-2 text-zinc-700 dark:text-zinc-300" />,
|
ul: (props: React.ComponentPropsWithoutRef<'ul'>) => <ul {...props} className="mb-6 list-disc space-y-2 pl-5 text-[0.98rem] leading-8 text-ink-soft marker:text-accent" />,
|
||||||
ol: (props: React.ComponentPropsWithoutRef<'ol'>) => <ol {...props} className="list-decimal pl-5 mb-4 space-y-2 text-zinc-700 dark:text-zinc-300" />,
|
ol: (props: React.ComponentPropsWithoutRef<'ol'>) => <ol {...props} className="mb-6 list-decimal space-y-2 pl-5 text-[0.98rem] leading-8 text-ink-soft marker:text-accent" />,
|
||||||
blockquote: (props: React.ComponentPropsWithoutRef<'blockquote'>) => <blockquote {...props} className="border-l-4 border-zinc-200 dark:border-zinc-700 pl-4 italic my-6 text-zinc-600 dark:text-zinc-400" />,
|
blockquote: (props: React.ComponentPropsWithoutRef<'blockquote'>) => <blockquote {...props} className="my-10 border-l border-line-strong pl-5 text-[1.05rem] italic leading-8 text-ink-soft" />,
|
||||||
code: (props: React.ComponentPropsWithoutRef<'code'>) => <code {...props} className="bg-zinc-100 dark:bg-zinc-800 text-pink-600 dark:text-pink-400 px-1 py-0.5 rounded text-sm font-mono" />,
|
code: (props: React.ComponentPropsWithoutRef<'code'>) => <code {...props} className="rounded bg-paper-strong px-1.5 py-0.5 font-mono text-[0.9em] text-ink" />,
|
||||||
pre: (props: React.ComponentPropsWithoutRef<'pre'>) => <pre {...props} className="bg-zinc-900 dark:bg-zinc-900 text-zinc-100 p-4 rounded-lg overflow-x-auto my-6 text-sm font-mono" />,
|
pre: (props: React.ComponentPropsWithoutRef<'pre'>) => <pre {...props} className="my-8 overflow-x-auto rounded-[1rem] border border-line bg-[#161412] p-5 text-sm text-paper shadow-[0_16px_40px_rgba(17,16,15,0.12)]" />,
|
||||||
a: (props: React.ComponentPropsWithoutRef<'a'>) => <a {...props} className="text-zinc-900 dark:text-zinc-100 underline decoration-zinc-300 dark:decoration-zinc-700 hover:decoration-zinc-900 dark:hover:decoration-zinc-100 transition-all font-medium" />,
|
a: (props: React.ComponentPropsWithoutRef<'a'>) => <a {...props} className="font-medium text-ink underline decoration-line-strong underline-offset-4 transition-colors hover:text-accent hover:decoration-accent" />,
|
||||||
SideNote,
|
SideNote,
|
||||||
Citation,
|
Citation,
|
||||||
Bibliography,
|
Bibliography,
|
||||||
@@ -82,7 +80,6 @@ export default async function BlogPost({ params }: Props) {
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract headings for TOC
|
|
||||||
const headingLines = post.content.match(/^#{2,3}\s+(.+)$/gm) || [];
|
const headingLines = post.content.match(/^#{2,3}\s+(.+)$/gm) || [];
|
||||||
const headings = headingLines.map((line) => {
|
const headings = headingLines.map((line) => {
|
||||||
const level = line.match(/^#+/)?.[0].length || 2;
|
const level = line.match(/^#+/)?.[0].length || 2;
|
||||||
@@ -90,47 +87,40 @@ export default async function BlogPost({ params }: Props) {
|
|||||||
const id = slugify(text);
|
const id = slugify(text);
|
||||||
return { id, text, level };
|
return { id, text, level };
|
||||||
});
|
});
|
||||||
|
const tags = Array.isArray(post.metadata.tags) ? post.metadata.tags : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-7xl mx-auto px-6 py-24 animate-fade-in relative">
|
<div className="page-frame py-20 sm:py-24">
|
||||||
|
<div className="mx-auto max-w-[78rem] xl:grid xl:grid-cols-[11rem_minmax(0,44rem)] xl:gap-x-10">
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-[250px_1fr] gap-12">
|
|
||||||
{/* Left Column: TOC */}
|
|
||||||
<aside className="hidden xl:block">
|
<aside className="hidden xl:block">
|
||||||
<div className="sticky top-32">
|
<div className="sticky top-24">
|
||||||
<TableOfContents headings={headings} />
|
<TableOfContents headings={headings} />
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{/* Right Column: Content */}
|
<article className="w-full">
|
||||||
<article className="max-w-3xl mx-auto xl:mx-0 w-full">
|
<header className="mb-8 space-y-4 border-b border-line pb-8">
|
||||||
|
<div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-[0.72rem] font-mono uppercase tracking-[0.14em] text-muted-strong">
|
||||||
{/* Mobile TOC */}
|
|
||||||
<MobileTableOfContents headings={headings} />
|
|
||||||
|
|
||||||
<header className="mb-12 text-center sm:text-left">
|
|
||||||
<div className="flex items-center gap-3 text-sm text-zinc-400 dark:text-zinc-500 mb-4 justify-center sm:justify-start">
|
|
||||||
<time dateTime={post.metadata.date}>
|
<time dateTime={post.metadata.date}>
|
||||||
{format(new Date(post.metadata.date), 'MMMM d, yyyy')}
|
{format(new Date(post.metadata.date), 'MMMM d, yyyy')}
|
||||||
</time>
|
</time>
|
||||||
<span className="w-1 h-1 rounded-full bg-zinc-300 dark:bg-zinc-700" />
|
{tags.map((tag: string) => (
|
||||||
<div className="flex gap-2">
|
<span key={tag}>{tag}</span>
|
||||||
{Array.isArray(post.metadata.tags) && post.metadata.tags.map((tag: string) => (
|
|
||||||
<span key={tag} className="text-xs uppercase tracking-wider">{tag}</span>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1 className="text-4xl sm:text-5xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50 mb-6 leading-tight">
|
<h1 className="max-w-[38rem] text-balance font-sans text-[clamp(2.7rem,5.8vw,4.4rem)] font-medium leading-[0.94] tracking-[-0.08em] text-ink">
|
||||||
{post.metadata.title}
|
{post.metadata.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-xl text-zinc-500 dark:text-zinc-400 font-light leading-relaxed">
|
<p className="max-w-[34rem] text-[1.04rem] leading-8 text-muted">
|
||||||
{post.metadata.description}
|
{post.metadata.description}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="prose prose-zinc dark:prose-invert prose-lg max-w-none relative">
|
<MobileTableOfContents headings={headings} />
|
||||||
|
|
||||||
|
<div className="essay-prose relative mt-8 max-w-none xl:max-w-[44rem]">
|
||||||
<MDXRemote
|
<MDXRemote
|
||||||
source={post.content}
|
source={post.content}
|
||||||
components={components}
|
components={components}
|
||||||
@@ -144,11 +134,11 @@ export default async function BlogPost({ params }: Props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-16 pt-8 border-t border-zinc-200 dark:border-zinc-800 flex justify-between items-center text-sm text-zinc-500">
|
<div className="mt-14 flex items-center justify-between border-t border-line pt-6 text-sm text-muted">
|
||||||
<Link href="/blog" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
|
<Link href="/blog" className="transition-colors hover:text-ink">
|
||||||
← Back to all posts
|
← Back to all posts
|
||||||
</Link>
|
</Link>
|
||||||
<a href="#" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
|
<a href="#" className="transition-colors hover:text-ink">
|
||||||
Scroll to top ↑
|
Scroll to top ↑
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
export default function BlogLoading() {
|
export default function BlogLoading() {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl mx-auto px-6 py-24 space-y-12 animate-pulse">
|
<div className="page-frame py-20 sm:py-24">
|
||||||
<div className="space-y-4">
|
<div className="mx-auto max-w-[72rem] animate-pulse space-y-10">
|
||||||
<div className="h-10 bg-zinc-200 dark:bg-zinc-800 rounded w-1/3" />
|
<div className="space-y-4 border-b border-line pb-10">
|
||||||
<div className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded w-2/3" />
|
<div className="h-3 w-20 rounded-full bg-accent-soft" />
|
||||||
|
<div className="h-14 w-2/3 rounded-[1rem] bg-paper-strong" />
|
||||||
|
<div className="h-5 w-1/2 rounded-full bg-paper-strong" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-8">
|
||||||
|
{Array.from({ length: 2 }).map((_, i) => (
|
||||||
|
<div key={i} className="grid gap-3 border-b border-line pb-6 md:grid-cols-[8rem_minmax(0,1fr)] md:gap-6">
|
||||||
|
<div className="h-3 w-20 rounded-full bg-accent-soft" />
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="h-9 w-3/4 rounded-[0.9rem] bg-paper-strong" />
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="h-4 w-full rounded-full bg-paper-strong" />
|
||||||
|
<div className="h-4 w-4/5 rounded-full bg-paper-strong" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-10">
|
|
||||||
{Array.from({ length: 3 }).map((_, i) => (
|
|
||||||
<div key={i} className="space-y-3">
|
|
||||||
<div className="h-4 bg-zinc-200 dark:bg-zinc-800 rounded w-1/4" />
|
|
||||||
<div className="h-7 bg-zinc-200 dark:bg-zinc-800 rounded w-3/4" />
|
|
||||||
<div className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded w-full" />
|
|
||||||
<div className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded w-5/6" />
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { getAllPosts } from '@/lib/mdx';
|
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
import { getAllPosts } from '@/lib/mdx';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Blog',
|
title: 'Blog',
|
||||||
@@ -16,47 +16,62 @@ export default function BlogIndex() {
|
|||||||
const posts = getAllPosts();
|
const posts = getAllPosts();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl mx-auto px-6 py-24 space-y-12 animate-fade-in">
|
<div className="page-frame py-20 sm:py-24">
|
||||||
<header className="space-y-4 text-center sm:text-left">
|
<div className="mx-auto max-w-[72rem] space-y-10">
|
||||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50">Writing</h1>
|
<header className="space-y-4 border-b border-line pb-10">
|
||||||
<p className="text-zinc-500 dark:text-zinc-400 font-light">
|
<div className="space-y-4">
|
||||||
Thoughts on software, design, and minimalism.
|
<p className="eyebrow">Writing</p>
|
||||||
|
<h1 className="max-w-[40rem] text-balance font-sans text-[clamp(3rem,6vw,5rem)] font-medium leading-[0.94] tracking-[-0.08em] text-ink">
|
||||||
|
Notes on software, deep learning, and research.
|
||||||
|
</h1>
|
||||||
|
<p className="max-w-[34rem] text-[1rem] leading-8 text-muted">
|
||||||
|
A small archive of ideas, experiments, and things worth slowing down enough to explain.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="grid gap-10">
|
<div className="divide-y divide-line">
|
||||||
{posts.map((post) => (
|
{posts.map((post) => (
|
||||||
<article key={post.slug} className="group relative flex flex-col space-y-3">
|
<article key={post.slug} className="grid gap-3 py-6 md:grid-cols-[8rem_minmax(0,1fr)] md:gap-6">
|
||||||
<div className="flex items-center gap-3 text-sm text-zinc-400 dark:text-zinc-500">
|
<div className="pt-1">
|
||||||
<time dateTime={post.date}>
|
<time dateTime={post.date} className="block font-mono text-[0.72rem] uppercase tracking-[0.18em] text-muted-strong">
|
||||||
{format(new Date(post.date), 'MMMM d, yyyy')}
|
{format(new Date(post.date), 'MMMM d, yyyy')}
|
||||||
</time>
|
</time>
|
||||||
<span className="w-1 h-1 rounded-full bg-zinc-300 dark:bg-zinc-700" />
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{post.tags?.map(tag => (
|
|
||||||
<span key={tag} className="text-xs uppercase tracking-wider">{tag}</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
<Link href={`/blog/${post.slug}`} className="block">
|
<Link href={`/blog/${post.slug}`} className="block">
|
||||||
<h2 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-100 group-hover:text-zinc-600 dark:group-hover:text-zinc-300 transition-colors">
|
<h2 className="max-w-[38rem] font-sans text-[1.8rem] font-medium leading-tight tracking-[-0.05em] text-ink transition-colors hover:text-accent sm:text-[2.1rem]">
|
||||||
{post.title}
|
{post.title}
|
||||||
</h2>
|
</h2>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<p className="text-zinc-600 dark:text-zinc-400 font-light leading-relaxed">
|
<p className="max-w-[34rem] text-[0.98rem] leading-7 text-muted">
|
||||||
{post.description}
|
{post.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="pt-2">
|
<div className="flex flex-wrap items-center gap-x-4 gap-y-2 pt-1 text-[0.76rem] font-mono uppercase tracking-[0.14em] text-muted-strong">
|
||||||
<Link href={`/blog/${post.slug}`} className="text-sm font-medium text-zinc-900 dark:text-zinc-100 underline decoration-zinc-300 dark:decoration-zinc-700 underline-offset-4 hover:decoration-zinc-900 dark:hover:decoration-zinc-100 transition-all">
|
{post.tags?.map((tag) => (
|
||||||
Read more
|
<span key={tag}>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
<Link href={`/blog/${post.slug}`} className="text-ink transition-colors hover:text-accent">
|
||||||
|
Read essay
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<section className="grid gap-4 border-t border-line pt-6 md:grid-cols-[8rem_minmax(0,1fr)]">
|
||||||
|
<p className="eyebrow md:pt-1">Archive</p>
|
||||||
|
<p className="max-w-[34rem] text-[0.96rem] leading-7 text-muted">
|
||||||
|
{posts.length} published {posts.length === 1 ? 'essay' : 'essays'}. The archive stays selective.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
275
app/globals.css
275
app/globals.css
@@ -1,54 +1,275 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin "@tailwindcss/typography";
|
@plugin "@tailwindcss/typography";
|
||||||
|
|
||||||
|
|
||||||
/* Getting rid of backticks in code blocks in blogs */
|
|
||||||
.prose code::before,
|
.prose code::before,
|
||||||
.prose code::after {
|
.prose code::after {
|
||||||
content: "" !important;
|
content: "" !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-sans: var(--font-inter);
|
--font-sans: var(--font-instrument-sans);
|
||||||
--color-zinc-50: #fafafa;
|
--font-mono: var(--font-ibm-plex-mono);
|
||||||
--color-zinc-100: #f4f4f5;
|
|
||||||
--color-zinc-200: #e4e4e7;
|
|
||||||
--color-zinc-300: #d4d4d8;
|
|
||||||
--color-zinc-400: #a1a1aa;
|
|
||||||
--color-zinc-500: #71717a;
|
|
||||||
--color-zinc-600: #52525b;
|
|
||||||
--color-zinc-700: #3f3f46;
|
|
||||||
--color-zinc-800: #27272a;
|
|
||||||
--color-zinc-900: #18181b;
|
|
||||||
--color-zinc-950: #09090b;
|
|
||||||
|
|
||||||
--animate-fade-in: fade-in 0.5s ease-in-out;
|
--color-paper: #f4efe7;
|
||||||
--animate-slide-up: slide-up 0.5s ease-in-out;
|
--color-paper-strong: #fbf7f0;
|
||||||
|
--color-paper-overlay: rgba(244, 239, 231, 0.92);
|
||||||
|
--color-ink: #171411;
|
||||||
|
--color-ink-soft: #27221d;
|
||||||
|
--color-muted: #5b544b;
|
||||||
|
--color-muted-strong: #7a7166;
|
||||||
|
--color-line: rgba(23, 20, 17, 0.12);
|
||||||
|
--color-line-strong: rgba(23, 20, 17, 0.22);
|
||||||
|
--color-accent: #8d6a42;
|
||||||
|
--color-accent-soft: rgba(141, 106, 66, 0.12);
|
||||||
|
|
||||||
|
--animate-fade-in: fade-in 0.7s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
--animate-fade-up: fade-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||||
|
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
from { opacity: 0; }
|
from {
|
||||||
to { opacity: 1; }
|
opacity: 0.82;
|
||||||
|
transform: translateY(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-up {
|
||||||
|
from {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: translateY(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
@keyframes slide-up {
|
|
||||||
from { opacity: 0; transform: translateY(10px); }
|
|
||||||
to { opacity: 1; transform: translateY(0); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: var(--color-zinc-50);
|
color-scheme: light;
|
||||||
--foreground: var(--color-zinc-900);
|
--background: var(--color-paper);
|
||||||
|
--foreground: var(--color-ink);
|
||||||
|
--page-wash: rgba(170, 142, 97, 0.09);
|
||||||
|
--page-shadow: rgba(255, 255, 255, 0.24);
|
||||||
|
--grain-opacity: 0.03;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] {
|
||||||
|
color-scheme: dark;
|
||||||
|
--background: #11100f;
|
||||||
|
--foreground: #f2ede4;
|
||||||
|
--color-paper: #11100f;
|
||||||
|
--color-paper-strong: #171614;
|
||||||
|
--color-paper-overlay: rgba(17, 16, 15, 0.92);
|
||||||
|
--color-ink: #f2ede4;
|
||||||
|
--color-ink-soft: #dfd7cb;
|
||||||
|
--color-muted: #b3a99d;
|
||||||
|
--color-muted-strong: #d0c4b5;
|
||||||
|
--color-line: rgba(242, 237, 228, 0.12);
|
||||||
|
--color-line-strong: rgba(242, 237, 228, 0.22);
|
||||||
|
--color-accent: #d0af80;
|
||||||
|
--color-accent-soft: rgba(208, 175, 128, 0.14);
|
||||||
|
--page-wash: rgba(208, 175, 128, 0.08);
|
||||||
|
--page-shadow: rgba(255, 255, 255, 0.02);
|
||||||
|
--grain-opacity: 0.055;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root:not([data-theme="light"]) {
|
||||||
--background: var(--color-zinc-950);
|
color-scheme: dark;
|
||||||
--foreground: var(--color-zinc-50);
|
--background: #11100f;
|
||||||
|
--foreground: #f2ede4;
|
||||||
|
--color-paper: #11100f;
|
||||||
|
--color-paper-strong: #171614;
|
||||||
|
--color-paper-overlay: rgba(17, 16, 15, 0.92);
|
||||||
|
--color-ink: #f2ede4;
|
||||||
|
--color-ink-soft: #dfd7cb;
|
||||||
|
--color-muted: #b3a99d;
|
||||||
|
--color-muted-strong: #d0c4b5;
|
||||||
|
--color-line: rgba(242, 237, 228, 0.12);
|
||||||
|
--color-line-strong: rgba(242, 237, 228, 0.22);
|
||||||
|
--color-accent: #d0af80;
|
||||||
|
--color-accent-soft: rgba(208, 175, 128, 0.14);
|
||||||
|
--page-wash: rgba(208, 175, 128, 0.08);
|
||||||
|
--page-shadow: rgba(255, 255, 255, 0.02);
|
||||||
|
--grain-opacity: 0.055;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
background: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-family: var(--font-inter), sans-serif;
|
font-family: var(--font-sans), sans-serif;
|
||||||
|
min-height: 100vh;
|
||||||
|
position: relative;
|
||||||
|
background-image: linear-gradient(180deg, var(--page-shadow), transparent 28%);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before,
|
||||||
|
body::after {
|
||||||
|
content: "";
|
||||||
|
inset: 0;
|
||||||
|
position: fixed;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 160 160' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.15' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
|
||||||
|
opacity: var(--grain-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
body::after {
|
||||||
|
background:
|
||||||
|
radial-gradient(60rem 24rem at 50% -8%, var(--page-wash), transparent 70%),
|
||||||
|
radial-gradient(24rem 18rem at 0% 0%, rgba(0, 0, 0, 0.02), transparent 74%);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: rgba(141, 106, 66, 0.18);
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid rgba(141, 106, 66, 0.5);
|
||||||
|
outline-offset: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-shell {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-frame {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 84rem;
|
||||||
|
padding-inline: clamp(1.25rem, 3vw, 2.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
color: var(--color-muted-strong);
|
||||||
|
font-family: var(--font-mono), monospace;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ambient-canvas {
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ambient-canvas__wash {
|
||||||
|
inset: 0;
|
||||||
|
position: absolute;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(68rem 28rem at 50% -10%, var(--page-wash), transparent 72%),
|
||||||
|
radial-gradient(40rem 24rem at 100% 0%, rgba(141, 106, 66, 0.05), transparent 76%);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle__sun,
|
||||||
|
.theme-toggle__moon {
|
||||||
|
position: absolute;
|
||||||
|
transition:
|
||||||
|
opacity 180ms ease,
|
||||||
|
transform 180ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle__moon {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] .theme-toggle__sun {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] .theme-toggle__moon {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.essay-prose {
|
||||||
|
color: var(--color-ink-soft);
|
||||||
|
font-size: 1.04rem;
|
||||||
|
line-height: 1.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.essay-prose > :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.essay-prose > :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.essay-prose img {
|
||||||
|
margin-block: 2.25rem;
|
||||||
|
border: 1px solid var(--color-line);
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.essay-prose pre code,
|
||||||
|
.essay-prose code[data-language] {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-note {
|
||||||
|
display: block;
|
||||||
|
margin-block: 1.6rem;
|
||||||
|
border-left: 1px solid var(--color-line-strong);
|
||||||
|
padding-left: 0.95rem;
|
||||||
|
color: var(--color-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-note strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
color: var(--color-ink);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.side-note {
|
||||||
|
float: right;
|
||||||
|
clear: right;
|
||||||
|
width: 12rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
margin-right: -14rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
margin-left: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import {
|
||||||
|
IBM_Plex_Mono,
|
||||||
|
Instrument_Sans,
|
||||||
|
} from "next/font/google";
|
||||||
|
import Script from "next/script";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { Navbar } from "../components/layout/Navbar";
|
import { Analytics } from "@/components/Analytics";
|
||||||
import { Footer } from "../components/layout/Footer";
|
import { AmbientCanvas } from "@/components/layout/AmbientCanvas";
|
||||||
import { Analytics } from "../components/Analytics";
|
import { Footer } from "@/components/layout/Footer";
|
||||||
|
import { Navbar } from "@/components/layout/Navbar";
|
||||||
|
|
||||||
const inter = Inter({
|
const instrumentSans = Instrument_Sans({
|
||||||
variable: "--font-inter",
|
variable: "--font-instrument-sans",
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
display: "swap",
|
display: "swap",
|
||||||
|
weight: ["400", "500", "600", "700"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const ibmPlexMono = IBM_Plex_Mono({
|
||||||
|
variable: "--font-ibm-plex-mono",
|
||||||
|
subsets: ["latin"],
|
||||||
|
display: "swap",
|
||||||
|
weight: ["400", "500"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
@@ -36,19 +49,36 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body
|
<body
|
||||||
className={`${inter.variable} antialiased flex flex-col min-h-screen`}
|
className={`${instrumentSans.variable} ${ibmPlexMono.variable} min-h-screen bg-paper font-sans text-ink antialiased`}
|
||||||
|
>
|
||||||
|
<Script id="theme-init" strategy="beforeInteractive">
|
||||||
|
{`
|
||||||
|
try {
|
||||||
|
const storedTheme = localStorage.getItem("theme-preference");
|
||||||
|
const theme = storedTheme === "light" || storedTheme === "dark"
|
||||||
|
? storedTheme
|
||||||
|
: (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
||||||
|
document.documentElement.dataset.theme = theme;
|
||||||
|
} catch {}
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
|
<a
|
||||||
|
href="#main-content"
|
||||||
|
className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-4 focus:z-[100] focus:rounded-full focus:bg-paper-strong focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-ink focus:shadow-[0_12px_30px_rgba(23,28,24,0.08)]"
|
||||||
>
|
>
|
||||||
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:bg-white focus:dark:bg-zinc-900 focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:rounded focus:shadow">
|
|
||||||
Skip to content
|
Skip to content
|
||||||
</a>
|
</a>
|
||||||
|
<AmbientCanvas />
|
||||||
|
<div className="site-shell flex min-h-screen flex-col">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main id="main-content" className="flex-grow">
|
<main id="main-content" className="flex-grow">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
<Analytics />
|
<Analytics />
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
92
app/page.tsx
92
app/page.tsx
@@ -1,81 +1,59 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { getAllPosts } from "@/lib/mdx";
|
import { getAllPosts } from "@/lib/mdx";
|
||||||
|
import profileImage from "@/public/profile.jpeg";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const posts = getAllPosts();
|
const posts = getAllPosts();
|
||||||
const latestPost = posts[0];
|
const latestPost = posts[0];
|
||||||
const latestPostHref = latestPost ? `/blog/${latestPost.slug}` : '/blog';
|
const latestPostHref = latestPost ? `/blog/${latestPost.slug}` : "/blog";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col items-center p-8 sm:p-20 relative overflow-hidden bg-zinc-50 dark:bg-zinc-950 text-zinc-900 dark:text-zinc-50 font-sans">
|
<div className="page-frame py-16 sm:py-20">
|
||||||
|
<section className="mx-auto grid min-h-[calc(100vh-11rem)] max-w-[64rem] gap-6 lg:grid-cols-[minmax(0,39rem)_15rem] lg:items-center lg:gap-4">
|
||||||
|
<div className="max-w-[39rem] space-y-6">
|
||||||
|
<p className="eyebrow">Akshay Kolli / Research + Writing</p>
|
||||||
|
|
||||||
{/* Background decoration */}
|
<div className="space-y-4">
|
||||||
<div className="fixed top-0 left-0 w-full h-full overflow-hidden z-0 pointer-events-none opacity-30 dark:opacity-10">
|
<h1 className="max-w-[34rem] text-balance font-sans text-[clamp(3.15rem,6vw,5.2rem)] font-medium leading-[0.94] tracking-[-0.085em] text-ink">
|
||||||
<div className="absolute top-[-20%] left-[-10%] w-[50%] h-[50%] rounded-full bg-linear-to-br from-zinc-200 to-transparent blur-3xl dark:from-zinc-800" />
|
World models and reinforcement learning.
|
||||||
<div className="absolute bottom-[-20%] right-[-10%] w-[50%] h-[50%] rounded-full bg-linear-to-tl from-zinc-200 to-transparent blur-3xl dark:from-zinc-800" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<main className="z-10 max-w-5xl w-full animate-fade-in mt-20 sm:mt-32 pb-24 px-6">
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24 items-start">
|
|
||||||
|
|
||||||
{/* Left Column: Text & Info */}
|
|
||||||
<section className="space-y-8 order-2 md:order-1">
|
|
||||||
<header className="space-y-4">
|
|
||||||
<h1 className="text-4xl sm:text-6xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 animate-slide-up" style={{ animationDelay: '0.1s' }}>
|
|
||||||
Akshay Kolli.
|
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-zinc-600 dark:text-zinc-300 font-light tracking-wide animate-slide-up" style={{ animationDelay: '0.2s' }}>
|
|
||||||
CS PhD Student @ UMass Lowell
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="space-y-6 text-lg text-zinc-500 dark:text-zinc-400 leading-relaxed font-light animate-slide-up" style={{ animationDelay: '0.3s' }}>
|
<div className="max-w-[32rem] space-y-3 text-[1.04rem] leading-8 text-ink-soft sm:text-[1.1rem]">
|
||||||
<p>
|
<p>
|
||||||
I am a PhD candidate at the University of Massachusetts, Lowell, focusing on <strong>World Models</strong>, <strong>Reinforcement Learning</strong>, and <strong>Multi-Agent Systems</strong>.
|
I'm a CS PhD student at UMass Lowell building world models for autonomous agents.
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
My work lies in building World Models for autonomous agents that can reason, adapt and learn in complex environments.
|
|
||||||
On the weekends I enjoy exercising, playing chess, listening to Jimi Hendrix, and playing video games.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="pt-4 border-t border-zinc-200 dark:border-zinc-800 animate-slide-up" style={{ animationDelay: '0.4s' }}>
|
<div className="flex flex-wrap gap-x-6 gap-y-3 text-[0.96rem] text-ink">
|
||||||
<h3 className="text-xs font-bold uppercase tracking-widest text-zinc-900 dark:text-zinc-100 mb-4">Connect</h3>
|
<Link href={latestPostHref} className="transition-colors hover:text-accent">
|
||||||
<div className="flex gap-6 font-medium text-sm">
|
Latest essay
|
||||||
<a href="mailto:akshaykolli@hotmail.com" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
|
</Link>
|
||||||
|
<Link href="/blog" className="transition-colors hover:text-accent">
|
||||||
|
Writing
|
||||||
|
</Link>
|
||||||
|
<Link href="/resume" className="transition-colors hover:text-accent">
|
||||||
|
Resume
|
||||||
|
</Link>
|
||||||
|
<a href="mailto:akshaykolli@hotmail.com" className="transition-colors hover:text-accent">
|
||||||
Email
|
Email
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/akkolli" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
<a href="https://x.com/thekolliakshay" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
|
|
||||||
Twitter
|
|
||||||
</a>
|
|
||||||
<a href="https://www.linkedin.com/in/akshay-kolli-/" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
|
|
||||||
LinkedIn
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* "Currently" - Answering "Anything else?" */}
|
<aside className="w-full max-w-[12rem] lg:max-w-[15rem] lg:justify-self-start">
|
||||||
<div className="pt-2 animate-slide-up" style={{ animationDelay: '0.5s' }}>
|
<div className="relative aspect-square overflow-hidden rounded-full border border-line bg-paper-strong">
|
||||||
<p className="text-sm text-zinc-400 dark:text-zinc-500 font-mono">
|
<Image
|
||||||
<span className="text-zinc-900 dark:text-zinc-100 mr-2">Currently:</span>
|
src={profileImage}
|
||||||
Writing about deep learning happenings <a href={latestPostHref} className="text-zinc-600 dark:text-zinc-400 underline decoration-zinc-300 dark:decoration-zinc-700 hover:text-zinc-900 dark:hover:text-zinc-100">Read latest →</a>
|
alt="Akshay Kolli"
|
||||||
</p>
|
fill
|
||||||
|
priority
|
||||||
|
className="object-cover object-center"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</aside>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Right Column: Photo */}
|
|
||||||
<div className="order-1 md:order-2 flex justify-center md:justify-end animate-slide-up" style={{ animationDelay: '0.2s' }}>
|
|
||||||
<div className="relative w-64 h-64 sm:w-80 sm:h-80 rounded-2xl overflow-hidden bg-zinc-100 dark:bg-zinc-900 shadow-2xl transition-transform duration-500 ease-out">
|
|
||||||
<Image src="/profile.jpeg" alt="Akshay Kolli" fill className="object-cover" priority />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,127 +9,144 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const experience = [
|
||||||
|
{
|
||||||
|
title: 'PhD in Computer Science',
|
||||||
|
org: 'University of Massachusetts',
|
||||||
|
period: 'Jan 2025 – Present',
|
||||||
|
bullets: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Research Assistant',
|
||||||
|
org: 'Exalabs, University of Massachusetts',
|
||||||
|
period: 'Sep 2022 – Present',
|
||||||
|
bullets: [
|
||||||
|
'Designing state-of-the-art ML pipelines for graph properties and trajectory forecasting.',
|
||||||
|
'Reduced multi-agent simulation time by 1000x.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Software Engineering Intern',
|
||||||
|
org: 'Siemens Healthineers',
|
||||||
|
period: 'May 2023 – Sep 2023',
|
||||||
|
bullets: [
|
||||||
|
'Created a Python data analysis tool for commercial blood testing machines.',
|
||||||
|
'Deployed a 1DConv autoencoder with 99.99% accuracy on a 700k+ dataset for real-time error detection.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const education = [
|
||||||
|
{
|
||||||
|
title: 'MSc Computer Science',
|
||||||
|
org: 'University of Massachusetts',
|
||||||
|
period: 'Aug 2022 – Dec 2024',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'BE Mechanical Engineering',
|
||||||
|
org: 'Osmania University',
|
||||||
|
period: 'Aug 2018 – Jun 2022',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const skills = [
|
||||||
|
{
|
||||||
|
label: 'Languages',
|
||||||
|
values: ['Python', 'Rust', 'C++', 'Go', 'SQL', 'TypeScript', 'Kotlin', 'R'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Technologies',
|
||||||
|
values: ['React', 'Django', 'Flask', 'TensorFlow', 'PyTorch', 'Jax', 'Tauri', 'Docker', 'Kubernetes', 'GCP', 'MongoDB'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Concepts',
|
||||||
|
values: ['NLP', 'Transformers', 'Encryption', 'AI', 'Machine Learning', 'Distributed Systems'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function ResumePage() {
|
export default function ResumePage() {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl mx-auto px-6 py-24 space-y-12 animate-fade-in">
|
<div className="page-frame py-20 sm:py-24">
|
||||||
<header className="space-y-4">
|
<div className="mx-auto max-w-[72rem] space-y-10">
|
||||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50">Resume</h1>
|
<header className="space-y-4 border-b border-line pb-10">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="eyebrow">Resume</p>
|
||||||
|
<h1 className="max-w-[40rem] text-balance font-sans text-[clamp(3rem,6vw,4.8rem)] font-medium leading-[0.94] tracking-[-0.08em] text-ink">
|
||||||
|
Experience, education, and technical depth.
|
||||||
|
</h1>
|
||||||
|
<p className="max-w-[34rem] text-[1rem] leading-8 text-muted">
|
||||||
|
Research, engineering, and systems work across academia and industry.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Timeline Section */}
|
<section className="grid gap-4 border-b border-line pb-10 lg:grid-cols-[8rem_minmax(0,1fr)]">
|
||||||
<section className="space-y-12">
|
<p className="eyebrow lg:pt-1">Experience</p>
|
||||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50 border-b border-zinc-200 dark:border-zinc-800 pb-4">
|
<div className="space-y-8">
|
||||||
Experience & Education
|
{experience.map((item) => (
|
||||||
|
<article key={`${item.title}-${item.period}`} className="grid gap-2 md:grid-cols-[8rem_minmax(0,1fr)] md:gap-5">
|
||||||
|
<p className="font-mono text-[0.72rem] uppercase tracking-[0.18em] text-muted-strong">
|
||||||
|
{item.period}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h2 className="font-sans text-[1.35rem] font-medium leading-tight tracking-[-0.04em] text-ink sm:text-[1.55rem]">
|
||||||
|
{item.title}
|
||||||
</h2>
|
</h2>
|
||||||
|
<p className="text-[0.96rem] leading-7 text-muted">
|
||||||
<div className="space-y-12 border-l border-zinc-200 dark:border-zinc-800 ml-8 pl-8 relative">
|
{item.org}
|
||||||
|
</p>
|
||||||
{/* PhD */}
|
|
||||||
<div className="relative">
|
|
||||||
<span className="absolute -left-[37px] top-1 h-4 w-4 rounded-full border-2 border-zinc-50 dark:border-zinc-950 bg-zinc-900 dark:bg-zinc-50" />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between items-baseline flex-wrap gap-2">
|
|
||||||
<h3 className="font-semibold text-lg text-zinc-900 dark:text-zinc-100">PhD in Computer Science</h3>
|
|
||||||
<span className="text-sm font-mono text-zinc-500">Jan 2025 – Present</span>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-zinc-600 dark:text-zinc-400">University of Massachusetts</p>
|
{item.bullets.length > 0 ? (
|
||||||
</div>
|
<ul className="list-disc space-y-2 pl-5 text-[0.94rem] leading-7 text-muted marker:text-accent">
|
||||||
</div>
|
{item.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
{/* Research Assistant */}
|
))}
|
||||||
<div className="relative">
|
|
||||||
<span className="absolute -left-[37px] top-1 h-4 w-4 rounded-full border-2 border-zinc-50 dark:border-zinc-950 bg-zinc-400 dark:bg-zinc-600" />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between items-baseline flex-wrap gap-2">
|
|
||||||
<h3 className="font-semibold text-lg text-zinc-900 dark:text-zinc-100">Research Assistant</h3>
|
|
||||||
<span className="text-sm font-mono text-zinc-500">Sep 2022 – Present</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-zinc-600 dark:text-zinc-400">Exalabs, University of Massachusetts</p>
|
|
||||||
<ul className="list-disc pl-5 space-y-1 text-sm text-zinc-500 dark:text-zinc-500 leading-relaxed marker:text-zinc-300">
|
|
||||||
<li>Designing state-of-the-art ML pipelines for graph properties & trajectory forecasting.</li>
|
|
||||||
<li>Reduced multi-agent simulation time by 1000x.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
|
))}
|
||||||
{/* Siemens */}
|
|
||||||
<div className="relative">
|
|
||||||
<span className="absolute -left-[37px] top-1 h-4 w-4 rounded-full border-2 border-zinc-50 dark:border-zinc-950 bg-zinc-300 dark:bg-zinc-700" />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between items-baseline flex-wrap gap-2">
|
|
||||||
<h3 className="font-semibold text-lg text-zinc-900 dark:text-zinc-100">Software Engineering Intern</h3>
|
|
||||||
<span className="text-sm font-mono text-zinc-500">May 2023 – Sep 2023</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-zinc-600 dark:text-zinc-400">Siemens Healthineers</p>
|
|
||||||
<ul className="list-disc pl-5 space-y-1 text-sm text-zinc-500 dark:text-zinc-500 leading-relaxed marker:text-zinc-300">
|
|
||||||
<li>Created data analysis tool with Python for commercial blood testing machines.</li>
|
|
||||||
<li>Deployed 1DConv AutoEncoder (99.99% accuracy) on 700k+ dataset for real-time error detection.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Masters */}
|
|
||||||
<div className="relative">
|
|
||||||
<span className="absolute -left-[37px] top-1 h-4 w-4 rounded-full border-2 border-zinc-50 dark:border-zinc-950 bg-zinc-200 dark:bg-zinc-800" />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between items-baseline flex-wrap gap-2">
|
|
||||||
<h3 className="font-semibold text-lg text-zinc-900 dark:text-zinc-100">MSc Computer Science</h3>
|
|
||||||
<span className="text-sm font-mono text-zinc-500">Aug 2022 – Dec 2024</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-zinc-600 dark:text-zinc-400">University of Massachusetts</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bachelors */}
|
|
||||||
<div className="relative">
|
|
||||||
<span className="absolute -left-[37px] top-1 h-4 w-4 rounded-full border-2 border-zinc-50 dark:border-zinc-950 bg-zinc-100 dark:bg-zinc-900" />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between items-baseline flex-wrap gap-2">
|
|
||||||
<h3 className="font-semibold text-lg text-zinc-900 dark:text-zinc-100">BE Mechanical Engineering</h3>
|
|
||||||
<span className="text-sm font-mono text-zinc-500">Aug 2018 – June 2022</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-zinc-600 dark:text-zinc-400">Osmania University</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Technical Skills */}
|
<section className="grid gap-4 border-b border-line pb-10 lg:grid-cols-[8rem_minmax(0,1fr)]">
|
||||||
<section className="space-y-8">
|
<p className="eyebrow lg:pt-1">Education</p>
|
||||||
<h2 className="text-2xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50 border-b border-zinc-200 dark:border-zinc-800 pb-4">
|
<div className="space-y-6">
|
||||||
Technical Skills
|
{education.map((item) => (
|
||||||
|
<article key={`${item.title}-${item.period}`} className="grid gap-2 md:grid-cols-[8rem_minmax(0,1fr)] md:gap-5">
|
||||||
|
<p className="font-mono text-[0.72rem] uppercase tracking-[0.18em] text-muted-strong">
|
||||||
|
{item.period}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h2 className="font-sans text-[1.35rem] font-medium leading-tight tracking-[-0.04em] text-ink sm:text-[1.55rem]">
|
||||||
|
{item.title}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-8">
|
<p className="text-[0.96rem] leading-7 text-muted">
|
||||||
<div>
|
{item.org}
|
||||||
<h3 className="font-semibold text-zinc-900 dark:text-zinc-100 mb-3 block">Languages</h3>
|
</p>
|
||||||
<div className="flex flex-wrap gap-2 text-sm text-zinc-600 dark:text-zinc-400 font-mono">
|
|
||||||
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Python</span>
|
|
||||||
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Rust</span>
|
|
||||||
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">C++</span>
|
|
||||||
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Go</span>
|
|
||||||
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">SQL</span>
|
|
||||||
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">TypeScript</span>
|
|
||||||
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Kotlin</span>
|
|
||||||
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">R</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-zinc-900 dark:text-zinc-100 mb-3 block">Technologies</h3>
|
|
||||||
<div className="text-sm text-zinc-600 dark:text-zinc-400 leading-relaxed">
|
|
||||||
React.js, Django, Flask, TensorFlow, PyTorch, Jax, Tauri, Android SDK, Docker, Kubernetes, GCP, MongoDB
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-zinc-900 dark:text-zinc-100 mb-3 block">Concepts</h3>
|
|
||||||
<div className="text-sm text-zinc-600 dark:text-zinc-400 leading-relaxed">
|
|
||||||
NLP, Transformers, Encryption, AI, Machine Learning, Distributed Systems
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section className="grid gap-4 lg:grid-cols-[8rem_minmax(0,1fr)]">
|
||||||
|
<p className="eyebrow lg:pt-1">Skills</p>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{skills.map((group) => (
|
||||||
|
<article key={group.label} className="grid gap-2 md:grid-cols-[9rem_minmax(0,1fr)] md:gap-5">
|
||||||
|
<h2 className="font-sans text-[1rem] font-medium tracking-[-0.02em] text-ink">
|
||||||
|
{group.label}
|
||||||
|
</h2>
|
||||||
|
<p className="text-[0.96rem] leading-7 text-muted">
|
||||||
|
{group.values.join(', ')}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
7
components/layout/AmbientCanvas.tsx
Normal file
7
components/layout/AmbientCanvas.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export function AmbientCanvas() {
|
||||||
|
return (
|
||||||
|
<div aria-hidden className="ambient-canvas">
|
||||||
|
<div className="ambient-canvas__wash" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,11 +1,23 @@
|
|||||||
export function Footer() {
|
export function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className="w-full py-8 text-center text-xs text-zinc-400 dark:text-zinc-600 font-mono border-t border-zinc-200/50 dark:border-zinc-800/50 mt-auto">
|
<footer className="mt-auto border-t border-line">
|
||||||
<div className="max-w-4xl mx-auto px-6 flex flex-col sm:flex-row justify-between items-center gap-4">
|
<div className="mx-auto flex max-w-[72rem] flex-col gap-3 px-5 py-6 sm:flex-row sm:items-center sm:justify-between sm:px-6">
|
||||||
|
<p className="text-[0.82rem] leading-6 text-muted">
|
||||||
<div className="flex gap-4">
|
Research, writing, and software.
|
||||||
<a href="https://x.com/thekolliakshay" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">Twitter</a>
|
</p>
|
||||||
<a href="https://github.com/akkolli" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">GitHub</a>
|
<div className="flex flex-wrap gap-4 text-[0.82rem] text-ink">
|
||||||
|
<a href="mailto:akshaykolli@hotmail.com" className="transition-colors hover:text-accent">
|
||||||
|
Email
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/akkolli" target="_blank" rel="noopener noreferrer" className="transition-colors hover:text-accent">
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
<a href="https://www.linkedin.com/in/akshay-kolli-/" target="_blank" rel="noopener noreferrer" className="transition-colors hover:text-accent">
|
||||||
|
LinkedIn
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/thekolliakshay" target="_blank" rel="noopener noreferrer" className="transition-colors hover:text-accent">
|
||||||
|
X
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { ThemeToggle } from './ThemeToggle';
|
||||||
|
|
||||||
export function Navbar() {
|
export function Navbar() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
@@ -9,35 +10,49 @@ export function Navbar() {
|
|||||||
const isActive = (path: string) => pathname?.startsWith(path);
|
const isActive = (path: string) => pathname?.startsWith(path);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav aria-label="Main navigation" className="fixed top-0 left-0 w-full z-50 backdrop-blur-md bg-zinc-50/80 dark:bg-zinc-950/80 border-b border-zinc-200/50 dark:border-zinc-800/50">
|
<nav
|
||||||
<div className="max-w-4xl mx-auto px-6 h-16 flex items-center justify-between">
|
aria-label="Main navigation"
|
||||||
<Link href="/" className="font-bold text-lg tracking-tight hover:opacity-70 transition-opacity">
|
className="fixed left-0 top-0 z-50 w-full border-b border-line bg-paper-overlay"
|
||||||
AK
|
>
|
||||||
|
<div className="mx-auto flex h-14 max-w-[72rem] items-center justify-between gap-6 px-5 sm:px-6">
|
||||||
|
<Link href="/" className="flex items-baseline gap-2.5 transition-opacity hover:opacity-75">
|
||||||
|
<span className="text-[0.96rem] font-medium tracking-[-0.03em] text-ink">
|
||||||
|
Akshay Kolli
|
||||||
|
</span>
|
||||||
|
<span className="hidden font-mono text-[0.65rem] uppercase tracking-[0.14em] text-muted-strong sm:inline">
|
||||||
|
Research
|
||||||
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="flex items-center gap-6 text-sm font-medium text-zinc-600 dark:text-zinc-400">
|
<div className="flex items-center gap-4 text-[0.68rem] font-mono uppercase tracking-[0.14em] text-muted-strong sm:gap-5">
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className={`transition-colors ${pathname === '/' ? 'text-ink' : 'hover:text-ink'}`}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/blog"
|
href="/blog"
|
||||||
className={`transition-colors ${isActive('/blog') ? 'text-zinc-900 dark:text-zinc-100 font-bold' : 'hover:text-zinc-900 dark:hover:text-zinc-100'}`}
|
className={`transition-colors ${isActive('/blog') ? 'text-ink' : 'hover:text-ink'}`}
|
||||||
>
|
>
|
||||||
Blog
|
Writing
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/resume"
|
href="/resume"
|
||||||
className={`transition-colors ${isActive('/resume') ? 'text-zinc-900 dark:text-zinc-100 font-bold' : 'hover:text-zinc-900 dark:hover:text-zinc-100'}`}
|
className={`transition-colors ${isActive('/resume') ? 'text-ink' : 'hover:text-ink'}`}
|
||||||
>
|
>
|
||||||
Resume
|
Resume
|
||||||
</Link>
|
</Link>
|
||||||
<div className="w-px h-4 bg-zinc-200 dark:bg-zinc-800 hidden sm:block"></div>
|
<a
|
||||||
<a href="https://code.akkolli.net/lepton" target="_blank" rel="noopener noreferrer" aria-label="Code repositories" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors hidden sm:block">
|
href="https://github.com/akkolli"
|
||||||
Code
|
target="_blank"
|
||||||
</a>
|
rel="noopener noreferrer"
|
||||||
<a href="https://github.com/akkolli" target="_blank" rel="noopener noreferrer" aria-label="GitHub profile" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors hidden sm:block">
|
aria-label="GitHub profile"
|
||||||
|
className="hidden transition-colors hover:text-ink sm:block"
|
||||||
|
>
|
||||||
GitHub
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
<a href="https://x.com/thekolliakshay" target="_blank" rel="noopener noreferrer" aria-label="Twitter profile" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors hidden sm:block">
|
<ThemeToggle />
|
||||||
Twitter
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
66
components/layout/ThemeToggle.tsx
Normal file
66
components/layout/ThemeToggle.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
type Theme = "light" | "dark";
|
||||||
|
|
||||||
|
const STORAGE_KEY = "theme-preference";
|
||||||
|
|
||||||
|
function applyTheme(theme: Theme) {
|
||||||
|
document.documentElement.dataset.theme = theme;
|
||||||
|
window.localStorage.setItem(STORAGE_KEY, theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentTheme(): Theme {
|
||||||
|
const currentTheme = document.documentElement.dataset.theme;
|
||||||
|
if (currentTheme === "light" || currentTheme === "dark") {
|
||||||
|
return currentTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ThemeToggle() {
|
||||||
|
const toggleTheme = () => {
|
||||||
|
const nextTheme = getCurrentTheme() === "dark" ? "light" : "dark";
|
||||||
|
applyTheme(nextTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={toggleTheme}
|
||||||
|
title="Toggle color theme"
|
||||||
|
aria-label="Toggle color theme"
|
||||||
|
className="theme-toggle inline-flex h-8 w-8 items-center justify-center rounded-full border border-transparent text-muted-strong transition-colors hover:border-line hover:text-ink"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Toggle color theme</span>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="theme-toggle__sun h-3.5 w-3.5 fill-none stroke-current"
|
||||||
|
strokeWidth="1.7"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="4" />
|
||||||
|
<path d="M12 2.5v2.2" />
|
||||||
|
<path d="M12 19.3v2.2" />
|
||||||
|
<path d="m4.9 4.9 1.6 1.6" />
|
||||||
|
<path d="m17.5 17.5 1.6 1.6" />
|
||||||
|
<path d="M2.5 12h2.2" />
|
||||||
|
<path d="M19.3 12h2.2" />
|
||||||
|
<path d="m4.9 19.1 1.6-1.6" />
|
||||||
|
<path d="m17.5 6.5 1.6-1.6" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="theme-toggle__moon h-3.5 w-3.5 fill-none stroke-current"
|
||||||
|
strokeWidth="1.7"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ export function Citation({ id, index }: { id: string; index: number }) {
|
|||||||
<sup id={`cite-ref-${id}`} className="ml-0.5">
|
<sup id={`cite-ref-${id}`} className="ml-0.5">
|
||||||
<a
|
<a
|
||||||
href={`#cite-note-${id}`}
|
href={`#cite-note-${id}`}
|
||||||
className="text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors font-mono text-[10px] no-underline"
|
className="font-mono text-[10px] text-muted-strong transition-colors no-underline hover:text-ink"
|
||||||
>
|
>
|
||||||
[{index}]
|
[{index}]
|
||||||
</a>
|
</a>
|
||||||
@@ -15,13 +15,13 @@ export function Bibliography({ items, children }: { items?: { id: string; conten
|
|||||||
if (!items && !children) return null;
|
if (!items && !children) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-12 pt-8 border-t border-zinc-200 dark:border-zinc-800">
|
<div className="mt-12 border-t border-line pt-8">
|
||||||
<h3 className="text-sm font-bold uppercase tracking-wider text-zinc-900 dark:text-zinc-100 mb-4">References</h3>
|
<h3 className="eyebrow mb-4">References</h3>
|
||||||
<ol className="list-decimal pl-4 space-y-2 text-sm text-zinc-600 dark:text-zinc-400">
|
<ol className="list-decimal space-y-2 pl-4 text-[0.94rem] leading-7 text-muted">
|
||||||
{Array.isArray(items) && items.map((item, i) => (
|
{Array.isArray(items) && items.map((item) => (
|
||||||
<li key={item.id} id={`cite-note-${item.id}`}>
|
<li key={item.id} id={`cite-note-${item.id}`}>
|
||||||
{item.content}
|
{item.content}
|
||||||
<a href={`#cite-ref-${item.id}`} className="ml-2 hover:text-zinc-900 dark:hover:text-zinc-100">
|
<a href={`#cite-ref-${item.id}`} className="ml-2 transition-colors hover:text-ink">
|
||||||
↩
|
↩
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
type Heading = {
|
type Heading = {
|
||||||
id: string;
|
id: string;
|
||||||
text: string;
|
text: string;
|
||||||
@@ -9,26 +5,14 @@ type Heading = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function MobileTableOfContents({ headings }: { headings: Heading[] }) {
|
export function MobileTableOfContents({ headings }: { headings: Heading[] }) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
if (headings.length === 0) return null;
|
if (headings.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="block xl:hidden mb-8 border border-zinc-200 dark:border-zinc-800 rounded-lg overflow-hidden">
|
<details className="mb-8 border-y border-line py-4">
|
||||||
<button
|
<summary className="eyebrow cursor-pointer list-none">
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
Contents
|
||||||
aria-expanded={isOpen}
|
</summary>
|
||||||
aria-controls="mobile-toc"
|
<ul className="mt-4 space-y-2">
|
||||||
className="w-full flex items-center justify-between p-4 bg-zinc-50 dark:bg-zinc-900 text-sm font-medium text-zinc-900 dark:text-zinc-100"
|
|
||||||
>
|
|
||||||
<span>Table of Contents</span>
|
|
||||||
<span className={`transform transition-transform ${isOpen ? 'rotate-180' : ''}`}>
|
|
||||||
▼
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{isOpen && (
|
|
||||||
<ul id="mobile-toc" className="p-4 bg-white dark:bg-black border-t border-zinc-200 dark:border-zinc-800 space-y-3">
|
|
||||||
{headings.map((heading) => (
|
{headings.map((heading) => (
|
||||||
<li
|
<li
|
||||||
key={heading.id}
|
key={heading.id}
|
||||||
@@ -36,15 +20,13 @@ export function MobileTableOfContents({ headings }: { headings: Heading[] }) {
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={`#${heading.id}`}
|
href={`#${heading.id}`}
|
||||||
onClick={() => setIsOpen(false)}
|
className="block text-[0.94rem] leading-7 text-muted transition-colors hover:text-ink"
|
||||||
className="block text-sm text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 truncate"
|
|
||||||
>
|
>
|
||||||
{heading.text}
|
{heading.text}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
</details>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
export function SideNote({ children, title }: { children: React.ReactNode; title?: string }) {
|
export function SideNote({ children, title }: { children: React.ReactNode; title?: string }) {
|
||||||
return (
|
return (
|
||||||
<aside className="my-6 p-4 bg-zinc-50 dark:bg-zinc-900/50 border-l-2 border-zinc-300 dark:border-zinc-700 text-sm text-zinc-600 dark:text-zinc-400 font-light italic rounded-r-lg lg:absolute lg:right-0 lg:w-64 lg:mr-[-20rem] lg:my-0 lg:p-0 lg:bg-transparent lg:dark:bg-transparent lg:border-0 lg:not-italic lg:rounded-none">
|
<span role="note" className="side-note">
|
||||||
{/* Mobile/Tablet view: distinct block */}
|
{title ? <strong>{title}</strong> : null}
|
||||||
{/* Desktop view: Absolute positioning to the right margin */}
|
|
||||||
<span className="lg:block lg:text-xs lg:leading-relaxed">
|
|
||||||
{title && <strong className="block mb-1 text-zinc-900 dark:text-zinc-200 not-italic">{title}</strong>}
|
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
</aside>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,19 +54,19 @@ export function TableOfContents({ headings }: { headings: Heading[] }) {
|
|||||||
if (headings.length === 0) return null;
|
if (headings.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav aria-label="Table of contents" className="text-sm animate-fade-in text-left">
|
<nav aria-label="Table of contents" className="text-left">
|
||||||
<h4 className="font-bold text-zinc-900 dark:text-zinc-100 mb-4 uppercase tracking-wider text-xs">On this page</h4>
|
<h4 className="eyebrow mb-4">Contents</h4>
|
||||||
<ul className="space-y-3">
|
<ul className="space-y-2">
|
||||||
{Array.isArray(headings) && headings.map((heading) => (
|
{Array.isArray(headings) && headings.map((heading) => (
|
||||||
<li
|
<li
|
||||||
key={heading.id}
|
key={heading.id}
|
||||||
style={{ paddingRight: `${(heading.level - 2) * 12}px` }}
|
style={{ paddingLeft: `${(heading.level - 2) * 10}px` }}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={`#${heading.id}`}
|
href={`#${heading.id}`}
|
||||||
className={`block transition-all duration-200 border-l-2 pl-4 ${activeId === heading.id
|
className={`block text-[0.82rem] leading-6 transition-colors ${activeId === heading.id
|
||||||
? 'border-zinc-900 dark:border-zinc-100 text-zinc-900 dark:text-zinc-50 font-bold'
|
? 'text-ink'
|
||||||
: 'border-transparent text-zinc-500 dark:text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300'
|
: 'text-muted hover:text-ink'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{heading.text}
|
{heading.text}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 161 KiB |
BIN
public/profile_old.jpeg
Normal file
BIN
public/profile_old.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
BIN
visitors.db
Normal file
BIN
visitors.db
Normal file
Binary file not shown.
BIN
visitors.db-shm
Normal file
BIN
visitors.db-shm
Normal file
Binary file not shown.
BIN
visitors.db-wal
Normal file
BIN
visitors.db-wal
Normal file
Binary file not shown.
Reference in New Issue
Block a user