Compare commits

...

53 Commits

Author SHA1 Message Date
kbot
a294153599 moved the test post out 2026-07-06 22:26:18 -05:00
kbot
c6475cce54 fix: drop stale ssh palette keyword audit remediation 2026-07-06 22:21:54 -05:00
kbot
0bdd4861f0 content: rendering test card 2026-07-06 22:16:00 -05:00
kbot
189680f772 callouts: emit type as string attribute 2026-07-06 22:11:33 -05:00
kbot
974f60ffac callouts: container directive support, mono tags 2026-07-06 22:07:00 -05:00
kbot
fb1d6bf69a blog: dossier-style article header 2026-07-06 22:06:46 -05:00
kbot
76676ddfed blog: sharpen cards, search, toc 2026-07-06 22:06:32 -05:00
kbot
ca4885301a mdx: square radii, quiet blockquotes 2026-07-06 22:06:19 -05:00
kbot
764600e6fe blog: blacksite chrome 2026-07-06 22:06:07 -05:00
kbot
40d5c75775 blog: terse archive + tag headers 2026-07-06 22:05:55 -05:00
kbot
2c4e7a6649 css: remap blog tokens to blacksite, style footnotes+callouts 2026-07-06 22:03:04 -05:00
kbot
99619c4e9f featured: align aria-label with new copy 2026-07-06 21:59:51 -05:00
kbot
b525ddcd04 manifesto+capabilities: renumber, rescale, breathe 2026-07-06 21:55:50 -05:00
kbot
41cfccda2a footer: drop build credits, tighten ctas 2026-07-06 21:55:50 -05:00
kbot
709278e649 work: PROOF column, drop heading prose 2026-07-06 21:55:50 -05:00
kbot
13a82263e3 notes+palette: cut copy 2026-07-06 21:55:50 -05:00
kbot
18fe7a1613 operator: surface the banner, cut label noise 2026-07-06 21:55:50 -05:00
kbot
6c1c751313 contact: three rows, zero pitch 2026-07-06 21:55:50 -05:00
kbot
e58f2c7c7c hero: oversized stacked display type 2026-07-06 21:55:50 -05:00
kbot
14b9274c47 featured: declutter labels, open spacing 2026-07-06 21:52:23 -05:00
kbot
57431f91c2 chrome: trim status bar, align nav padding 2026-07-06 21:52:06 -05:00
kbot
3ee150280f meta: sharpen site description 2026-07-06 21:49:53 -05:00
kbot
bc65f83c1e content: cut portfolio copy to fragments, add proofHeadline, drop ssh 2026-07-06 21:47:23 -05:00
kbot
4ea90f90d7 fix(portfolio): replace faint text with muted for AA contrast 2026-07-06 20:59:37 -05:00
kbot
c0376df402 fix(portfolio): enforce accent discipline, display heading, and AA contrast 2026-07-06 20:56:51 -05:00
kbot
ff4b556381 feat(seo): add build-time sitemap 2026-07-06 20:50:16 -05:00
kbot
fd2102ed21 feat(404): restyle not-found for blacksite shell 2026-07-06 20:45:23 -05:00
kbot
48af740469 feat(portfolio): assemble signal blacksite landing page 2026-07-06 20:42:46 -05:00
kbot
00d1859f41 feat(portfolio): add cmdk command palette 2026-07-06 20:37:20 -05:00
kbot
d4a6819093 feat(portfolio): add hero command deck with canvas signal grid 2026-07-06 20:34:19 -05:00
kbot
d332691e7e feat(portfolio): add contact terminal and closing cta tiles 2026-07-06 20:33:55 -05:00
kbot
d8631dfc1b feat(portfolio): add manifesto and numbered capability rows 2026-07-06 20:33:34 -05:00
kbot
ecfe0ce9cc feat(portfolio): add motion-first work index table 2026-07-06 20:33:22 -05:00
kbot
ed94a782a3 feat(portfolio): add field notes section bridging to blog 2026-07-06 20:33:10 -05:00
kbot
c1a5d2ea0d feat(portfolio): add featured project dossier card 2026-07-06 20:32:35 -05:00
kbot
efe0b5b394 feat(portfolio): add operator profile with banner, publication, and honors 2026-07-06 20:32:12 -05:00
kbot
f4264886f9 feat(portfolio): add status bar and floating nav 2026-07-06 20:31:47 -05:00
kbot
7199eb458a refactor(shell): strip global chrome and retire /posts routes 2026-07-06 20:28:01 -05:00
kbot
3e2a5a391c feat(nav): point post and tag links at /blog 2026-07-06 20:25:10 -05:00
kbot
5b3e24ca78 feat(blog): add /blog routes with dedicated editorial layout 2026-07-06 20:22:27 -05:00
kbot
ff58dd000c refactor(tags): centralize tag slug logic in client-safe util 2026-07-06 20:15:54 -05:00
kbot
7c8a4d8c52 feat(theme): force dark mode site-wide 2026-07-06 20:12:05 -05:00
kbot
b21a83c548 fix(theme): use font-family for section-label mono font 2026-07-06 20:09:15 -05:00
kbot
637cb7cdaa feat(theme): add signal blacksite design tokens 2026-07-06 20:07:27 -05:00
kbot
7adc47d65f feat(fonts): add Space Grotesk display font 2026-07-06 20:05:25 -05:00
kbot
4dd609179a Revert "chore(deps): switch package manager to bun"
This reverts commit 903ff3d0d6.
2026-07-06 20:02:11 -05:00
kbot
903ff3d0d6 chore(deps): switch package manager to bun 2026-07-06 19:59:39 -05:00
kbot
e005040109 feat(portfolio): add portfolio content model and dossier data 2026-07-06 19:55:36 -05:00
kbot
ebefb3496a chore(deps): add cmdk for command palette 2026-07-06 19:53:57 -05:00
kbot
b8e0a180d1 chore(assets): add profile banner image 2026-07-06 19:52:09 -05:00
kbot
9f6b22891f chore: commit in-progress metadata and asset updates 2026-07-06 19:49:42 -05:00
kbot
eaef10c824 fix(tags): guard generateStaticParams against empty content dir 2026-06-03 23:08:12 -05:00
kbot
9ae127eb77 fix(posts): guard generateStaticParams against empty content dir 2026-06-03 23:08:01 -05:00
53 changed files with 4198 additions and 321 deletions

82
app/blog/[slug]/page.tsx Normal file
View File

@@ -0,0 +1,82 @@
import { notFound } from 'next/navigation'
import Link from 'next/link'
import { getPosts, getPost } from '@/lib/posts'
import { tagToSlug } from '@/lib/tags'
import { TableOfContents } from '@/components/posts/TableOfContents'
import { ScrollToTop } from '@/components/ui/ScrollToTop'
import { ReadingProgress } from '@/components/ui/ReadingProgress'
export const dynamicParams = false
export const dynamic = 'force-static'
export async function generateStaticParams() {
const posts = await getPosts()
if (posts.length === 0) {
return [{ slug: '__no-posts__' }]
}
return posts.map((post) => ({ slug: post.slug }))
}
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const slug = (await params).slug
const post = await getPost(slug)
if (!post) return { title: 'Not Found' }
return { title: post.title }
}
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const slug = (await params).slug
if (slug === '__no-posts__') notFound()
const post = await getPost(slug)
if (!post) notFound()
const { default: PostContent } = await import(
/* turbopackOptional: true */
`@/content/posts/${slug}.mdx`
)
return (
<>
<ScrollToTop />
<ReadingProgress />
<div className="grid grid-cols-1 gap-8 lg:grid-cols-[minmax(0,1fr)_14rem] lg:gap-12">
<article className="min-w-0">
<header className="mb-12">
<time className="section-label" dateTime={post.date}>{new Date(post.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })} · {post.author} · {post.readingTime} min</time>
<h1 className="heading-xl font-display text-ink mt-3 mb-2">
{post.title}
</h1>
{post.coverImage && (
<img
src={post.coverImage}
alt={`Featured image for article: ${post.title}`}
className="my-8 aspect-[16/9] w-full rounded-lg border border-border bg-surface object-cover shadow-card"
loading="eager"
decoding="async"
fetchPriority="high"
/>
)}
{post.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mt-4">
{post.tags.map((tag) => (
<Link key={tag} href={`/blog/tags/${tagToSlug(tag)}/`} className="rounded-sm bg-surface px-3 py-1 font-mono text-[11px] uppercase tracking-wider text-ink-soft border border-border transition-colors hover:border-accent/50 hover:text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">
{tag}
</Link>
))}
</div>
)}
</header>
<div className="prose prose-lg article-prose max-w-none min-w-0">
<PostContent />
</div>
</article>
<aside className="hidden lg:block">
<TableOfContents />
</aside>
</div>
</>
)
}

16
app/blog/layout.tsx Normal file
View File

@@ -0,0 +1,16 @@
import { Header } from '@/components/layout/Header'
import { Footer } from '@/components/layout/Footer'
import { ScrollProgress } from '@/components/ui/ScrollProgress'
export default function BlogLayout({ children }: { children: React.ReactNode }) {
return (
<>
<ScrollProgress />
<Header />
<main className="mx-auto w-full max-w-6xl px-6 py-16 sm:py-24">
{children}
</main>
<Footer />
</>
)
}

View File

@@ -1,16 +1,17 @@
import { getPosts } from '@/lib/posts'
import { PostSearch } from '@/components/posts/PostSearch'
export const metadata = { title: 'Posts' }
export const metadata = { title: 'Field Notes' }
export default async function PostsPage() {
const posts = await getPosts()
return (
<main className="mx-auto max-w-5xl px-6 py-12 sm:py-16">
<>
<header className="mb-10 border-b border-border pb-8">
<p className="section-label mb-4">FIELD NOTES /.06</p>
<h1 className="heading-xl m-0 text-ink">
Posts
Field Notes
</h1>
</header>
@@ -18,13 +19,9 @@ export default async function PostsPage() {
<PostSearch posts={posts} />
) : (
<section className="empty-state" aria-label="No posts published">
<p className="font-mono text-xs uppercase tracking-[0.22em] text-ink-soft">No posts yet</p>
<h2 className="heading-sm m-0 text-ink">The archive is empty.</h2>
<p className="m-0 max-w-xl text-sm leading-6 text-ink-soft">
Publish an MDX post to populate this list and enable archive search.
</p>
<p className="m-0 text-sm leading-6 text-ink-soft">Archive empty.</p>
</section>
)}
</main>
</>
)
}

View File

@@ -12,6 +12,9 @@ export const dynamic = 'force-static'
export async function generateStaticParams() {
const tags = await getAllTags()
if (tags.length === 0) {
return [{ tag: '__no-tags__' }]
}
return tags.map((tag) => ({ tag: tag.slug }))
}
@@ -27,6 +30,9 @@ export async function generateMetadata({ params }: TagPageProps): Promise<Metada
export default async function TagPage({ params }: TagPageProps) {
const slug = (await params).tag
if (slug === '__no-tags__') notFound()
const tags = await getAllTags()
const tag = tags.find((item) => item.slug === slug)
@@ -35,9 +41,9 @@ export default async function TagPage({ params }: TagPageProps) {
const posts = await getPostsByTag(slug)
return (
<main className="max-w-4xl mx-auto px-6 py-12">
<div className="max-w-4xl mx-auto px-6 py-12">
<header className="mb-12">
<p className="font-mono text-sm text-ink-soft mb-3">Tag</p>
<p className="section-label mb-3">Tag</p>
<h1 className="heading-xl text-ink mb-3">{tag.label}</h1>
<p className="text-ink-soft">
{tag.count} {tag.count === 1 ? 'post' : 'posts'}
@@ -50,6 +56,6 @@ export default async function TagPage({ params }: TagPageProps) {
</li>
))}
</ul>
</main>
</div>
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -38,6 +38,7 @@
--font-sans: "Fraunces", ui-sans-serif, system-ui, sans-serif;
--font-serif: "IBM Plex Mono", ui-monospace, monospace;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
--font-display: "Space Grotesk";
/* Typography scale */
--text-xs: 0.75rem; --text-xs--line-height: 1.2;
@@ -71,6 +72,22 @@
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
/* Signal Blacksite */
--color-blacksite-bg: #030506;
--color-blacksite-bg2: #07090a;
--color-blacksite-surface: #101517;
--color-blacksite-surface2: #171d21;
--color-blacksite-surface3: #20282d;
--color-blacksite-text: #f4f7f8;
--color-blacksite-muted: #8b949a;
--color-blacksite-faint: #5f696f;
--color-blacksite-line: #2c353a;
--color-signal-cyan: #00f0ff;
--color-signal-orange: #ff5a1f;
--color-signal-green: #b6ff00;
--color-signal-red: #ff3b30;
--color-signal-violet: #805dff;
}
@layer base {
@@ -86,7 +103,7 @@
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-sans);
font-family: var(--font-display);
font-weight: 700;
line-height: 1.25;
letter-spacing: -0.015em;
@@ -114,25 +131,25 @@
/* === Dark mode token overrides === */
.dark {
--color-canvas: oklch(0 0 0);
--color-surface: oklch(0.07 0 0);
--color-ink: oklch(1 0 0);
--color-ink-soft: oklch(0.72 0 0);
--color-border: oklch(0.17 0 0);
--color-accent: oklch(0.68 0.20 250);
--color-canvas: #030506;
--color-surface: #101517;
--color-ink: #f4f7f8;
--color-ink-soft: #8b949a;
--color-border: #2c353a;
--color-accent: #00f0ff;
/* Code blocks — Dark mode */
--color-code-block: oklch(0.04 0 0);
--color-code-block-hover: oklch(0.06 0 0);
--color-code-gutter: oklch(0.30 0 0);
--color-code-gutter-border: oklch(0.14 0 0);
--color-code-title-bg: oklch(0.03 0 0);
--color-code-title-text: oklch(0.55 0 0);
--color-code-block: #07090a;
--color-code-block-hover: #101517;
--color-code-gutter: #8b949a;
--color-code-gutter-border: #2c353a;
--color-code-title-bg: #07090a;
--color-code-title-text: #8b949a;
--color-code-line-highlight: oklch(0.12 0.02 250 / 0.3);
--color-code-line-highlight-border: oklch(0.68 0.20 250);
--color-code-line-highlight-border: #00f0ff;
--color-code-mark-bg: oklch(0.10 0.03 250 / 0.4);
--color-code-copy: oklch(0.60 0 0);
--color-code-copy-hover: oklch(0.85 0 0);
--color-code-copy: #8b949a;
--color-code-copy-hover: #f4f7f8;
--shadow-card: 0 1px 4px oklch(1 0 0 / 0.06);
--shadow-card-hover: 0 22px 50px -34px oklch(1 0 0 / 0.22), 0 1px 4px oklch(1 0 0 / 0.08);
@@ -170,11 +187,19 @@
opacity: 0.22;
}
.section-label {
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--color-blacksite-muted);
}
.empty-state {
display: grid;
gap: 0.75rem;
border: 1px dashed color-mix(in oklch, var(--color-border) 78%, var(--color-ink) 22%);
border-radius: 1.25rem;
border-radius: 0.5rem;
background: color-mix(in oklch, var(--color-surface) 55%, transparent);
padding: 2rem;
}
@@ -204,7 +229,7 @@
}
.article-prose :where(p, ul, ol, blockquote, details, .callout, [data-callout]) {
max-width: 72ch;
max-width: 68ch;
}
.prose :where(a) {
@@ -212,6 +237,30 @@
text-decoration-thickness: 1px;
}
.prose [data-footnote-ref] {
color: var(--color-signal-cyan);
font-family: var(--font-mono);
font-size: 0.8em;
}
.prose .footnotes {
margin-top: 3rem;
border-top: 1px solid var(--color-border);
padding-top: 1rem;
color: var(--color-ink-soft);
font-size: 0.875rem;
line-height: 1.5;
}
.prose .footnotes ol {
margin: 0.75rem 0 0;
padding-left: 1.25rem;
}
.prose .footnotes li {
margin: 0.25rem 0;
}
.prose :where(ul, ol) {
margin: 1.5rem 0;
padding-left: 1.5rem;
@@ -545,7 +594,7 @@ html.dark code[data-theme*=" "] span {
left: 0;
height: 2px;
width: 0%;
background: var(--color-ink);
background: var(--color-signal-cyan);
z-index: 9999;
will-change: width;
}
@@ -586,7 +635,8 @@ html.dark code[data-theme*=" "] span {
pointer-events: auto;
}
.scroll-to-top:hover {
border-color: var(--color-ink);
border-color: var(--color-accent);
color: var(--color-accent);
}
/* === Reading progress vertical bar === */
@@ -602,7 +652,7 @@ html.dark code[data-theme*=" "] span {
.reading-progress-fill {
width: 100%;
height: 0%;
background: var(--color-ink);
background: var(--color-signal-cyan);
border-radius: 2px;
}
@keyframes reading-progress { from { height: 0%; } to { height: 100%; } }
@@ -653,29 +703,33 @@ body, body *, [class*="bg-"], [class*="border-"], [class*="text-"] {
/* Type variants */
.callout-note {
@apply border-accent/30 bg-accent/[0.06];
border-color: color-mix(in oklch, var(--color-signal-cyan) 30%, var(--color-border));
background: color-mix(in oklch, var(--color-signal-cyan) 8%, var(--color-surface));
}
.callout-note .callout-title {
@apply text-accent;
color: var(--color-signal-cyan);
}
.callout-tip {
@apply border-emerald-500/30 bg-emerald-500/[0.06];
border-color: color-mix(in oklch, var(--color-signal-green) 30%, var(--color-border));
background: color-mix(in oklch, var(--color-signal-green) 8%, var(--color-surface));
}
.callout-tip .callout-title {
@apply text-emerald-600 dark:text-emerald-400;
color: var(--color-signal-green);
}
.callout-warning {
@apply border-amber-500/30 bg-amber-500/[0.06];
border-color: color-mix(in oklch, var(--color-signal-orange) 30%, var(--color-border));
background: color-mix(in oklch, var(--color-signal-orange) 8%, var(--color-surface));
}
.callout-warning .callout-title {
@apply text-amber-600 dark:text-amber-400;
color: var(--color-signal-orange);
}
.callout-danger {
@apply border-red-500/30 bg-red-500/[0.06];
border-color: color-mix(in oklch, var(--color-signal-red) 30%, var(--color-border));
background: color-mix(in oklch, var(--color-signal-red) 8%, var(--color-surface));
}
.callout-danger .callout-title {
@apply text-red-600 dark:text-red-400;
color: var(--color-signal-red);
}

103
app/icon.svg Normal file
View File

@@ -0,0 +1,103 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" width="500" height="500">
<rect width="500" height="500" fill="#fff"/>
<g transform="translate(250,250)">
<!-- Outer circle -->
<circle cx="0" cy="0" r="220" fill="none" stroke="#000" stroke-width="2"/>
<!-- Triangle inscribed in outer circle (pointing up) -->
<polygon points="0,-220 190.5,110 -190.5,110" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Triangle inscribed (pointing down) -->
<polygon points="0,220 -190.5,-110 190.5,-110" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Circle inscribed in the star (radius ~110) -->
<circle cx="0" cy="0" r="110" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Hexagon from the star intersections -->
<polygon points="95,-165 190.5,-55 190.5,55 95,165 -95,165 -190.5,55 -190.5,-55 -95,-165"
fill="none" stroke="#000" stroke-width="1"/>
<!-- Inner circle -->
<circle cx="0" cy="0" r="55" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Triangle inscribed in inner circle (up) -->
<polygon points="0,-55 47.6,27.5 -47.6,27.5" fill="none" stroke="#000" stroke-width="1"/>
<!-- Triangle inscribed in inner circle (down) -->
<polygon points="0,55 -47.6,-27.5 47.6,-27.5" fill="none" stroke="#000" stroke-width="1"/>
<!-- Small circle at center -->
<circle cx="0" cy="0" r="27" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Tiny triangle up -->
<polygon points="0,-27 23.4,13.5 -23.4,13.5" fill="none" stroke="#000" stroke-width="0.8"/>
<!-- Tiny triangle down -->
<polygon points="0,27 -23.4,-13.5 23.4,-13.5" fill="none" stroke="#000" stroke-width="0.8"/>
<!-- Center dot -->
<circle cx="0" cy="0" r="4" fill="#000"/>
<!-- Lines from center through all vertices -->
<g stroke="#000" stroke-width="0.5" opacity="0.4">
<!-- Through outer triangle vertices -->
<line x1="0" y1="-4" x2="0" y2="-220"/>
<line x1="0" y1="4" x2="0" y2="220"/>
<line x1="0" y1="0" x2="190.5" y2="110"/>
<line x1="0" y1="0" x2="-190.5" y2="110"/>
<line x1="0" y1="0" x2="190.5" y2="-110"/>
<line x1="0" y1="0" x2="-190.5" y2="-110"/>
</g>
<!-- Small circles at outer triangle vertices -->
<g fill="#fff" stroke="#000" stroke-width="1.2">
<circle cx="0" cy="-220" r="5"/>
<circle cx="190.5" cy="110" r="5"/>
<circle cx="-190.5" cy="110" r="5"/>
</g>
<g fill="#000">
<circle cx="0" cy="-220" r="2"/>
<circle cx="190.5" cy="110" r="2"/>
<circle cx="-190.5" cy="110" r="2"/>
</g>
<!-- Small circles at inner triangle vertices -->
<g fill="#fff" stroke="#000" stroke-width="1">
<circle cx="0" cy="-55" r="3.5"/>
<circle cx="47.6" cy="27.5" r="3.5"/>
<circle cx="-47.6" cy="27.5" r="3.5"/>
<circle cx="0" cy="55" r="3.5"/>
<circle cx="47.6" cy="-27.5" r="3.5"/>
<circle cx="-47.6" cy="-27.5" r="3.5"/>
</g>
<!-- Dashed outermost ring -->
<circle cx="0" cy="0" r="232" fill="none" stroke="#000" stroke-width="0.6" stroke-dasharray="2 6"/>
<!-- Arc marks at cardinal points on outer ring -->
<g stroke="#000" stroke-width="1.2" stroke-linecap="round" fill="none">
<path d="M -6,-226 A 6,6 0 0,1 6,-226"/>
<path d="M -6,226 A 6,6 0 0,0 6,226"/>
<path d="M -226,-6 A 6,6 0 0,0 -226,6"/>
<path d="M 226,-6 A 6,6 0 0,1 226,6"/>
</g>
<!-- Tiny dots at cardinal points -->
<g fill="#000">
<circle cx="0" cy="-232" r="1.5"/>
<circle cx="0" cy="232" r="1.5"/>
<circle cx="-232" cy="0" r="1.5"/>
<circle cx="232" cy="0" r="1.5"/>
</g>
<!-- Corner marks -->
<g stroke="#000" stroke-width="1" stroke-linecap="round" opacity="0.5">
<line x1="-164" y1="-164" x2="-152" y2="-152"/>
<line x1="164" y1="-164" x2="152" y2="-152"/>
<line x1="-164" y1="164" x2="-152" y2="152"/>
<line x1="164" y1="164" x2="152" y2="152"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,11 +1,8 @@
import type { Metadata } from 'next'
import { Fraunces, IBM_Plex_Mono, JetBrains_Mono } from 'next/font/google'
import { Fraunces, IBM_Plex_Mono, JetBrains_Mono, Space_Grotesk } from 'next/font/google'
import { ThemeProvider } from '@wrksz/themes/next'
import './globals.css'
import 'katex/dist/katex.min.css'
import { Header } from '@/components/layout/Header'
import { Footer } from '@/components/layout/Footer'
import { ScrollProgress } from '@/components/ui/ScrollProgress'
import { Providers } from './providers'
const fraunces = Fraunces({
@@ -17,6 +14,12 @@ const fraunces = Fraunces({
variable: '--font-fraunces',
})
const spaceGrotesk = Space_Grotesk({
subsets: ['latin'],
display: 'swap',
variable: '--font-space-grotesk',
})
const jetbrainsMono = JetBrains_Mono({
subsets: ['latin'],
display: 'swap',
@@ -31,27 +34,33 @@ const plexMono = IBM_Plex_Mono({
variable: '--font-plex-mono',
})
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://localhost:3000'
export const metadata: Metadata = {
metadataBase: new URL(siteUrl),
title: { template: '%s | Krishna', default: 'Krishna' },
description: 'A sleek static journal with code and math.',
description: 'GPU pipelines, hardened systems, real-time infrastructure.',
icons: {
icon: '/ouroboros.svg',
},
openGraph: {
title: 'Krishna',
description: 'GPU pipelines, hardened systems, real-time infrastructure.',
images: ['/og.png'],
},
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${fraunces.variable} ${jetbrainsMono.variable} ${plexMono.variable}`} data-scroll-behavior="smooth" suppressHydrationWarning>
<html lang="en" className={`${fraunces.variable} ${spaceGrotesk.variable} ${jetbrainsMono.variable} ${plexMono.variable} dark`} data-scroll-behavior="smooth" suppressHydrationWarning>
<body className={`antialiased bg-canvas text-ink ${fraunces.className}`}>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
defaultTheme="dark"
forcedTheme="dark"
disableTransitionOnChange
>
<Providers>
<ScrollProgress />
<Header />
{children}
<Footer />
</Providers>
<Providers>{children}</Providers>
</ThemeProvider>
</body>
</html>

View File

@@ -4,14 +4,29 @@ export const metadata = { title: "Not Found" }
export default function NotFound() {
return (
<main className="min-h-screen flex items-center justify-center px-6">
<div className="text-center">
<p className="font-mono text-sm text-ink-soft mb-4">404</p>
<h1 className="heading-xl text-ink mt-0 mb-4">Page not found</h1>
<p className="text-ink-soft mb-8">The page you{`'`}re looking for doesn{`'`}t exist.</p>
<Link href="/" className="inline-block rounded-lg bg-ink px-6 py-3 text-sm font-medium text-canvas hover:opacity-80 transition-opacity">
Go home
</Link>
<main className="flex min-h-screen items-center justify-center bg-blacksite-bg px-6 text-blacksite-text">
<div className="w-full max-w-2xl border border-blacksite-line bg-blacksite-surface/60 p-8 shadow-2xl shadow-black/40 sm:p-12">
<p className="section-label mb-5">SIGNAL LOST /.404</p>
<h1 className="mb-5 font-display text-5xl font-semibold tracking-tight text-blacksite-text sm:text-7xl">
Route dissolved into static.
</h1>
<p className="mb-8 max-w-lg text-sm leading-6 text-blacksite-muted sm:text-base">
This coordinate is dark; return to base or scan the field notes.
</p>
<div className="flex flex-wrap gap-3">
<Link
href="/"
className="border border-signal-orange px-4 py-3 font-mono text-xs uppercase tracking-[0.16em] text-signal-orange transition-colors hover:bg-signal-orange hover:text-blacksite-bg focus-visible:bg-signal-orange focus-visible:text-blacksite-bg"
>
Return home
</Link>
<Link
href="/blog/"
className="border border-blacksite-line px-4 py-3 font-mono text-xs uppercase tracking-[0.16em] text-blacksite-text transition-colors hover:border-signal-cyan hover:text-signal-cyan focus-visible:border-signal-cyan focus-visible:text-signal-cyan"
>
Field notes
</Link>
</div>
</div>
</main>
)

View File

@@ -1,30 +1,45 @@
import type { Metadata } from 'next'
import { CapabilityRows } from '@/components/portfolio/CapabilityRows'
import { ClosingCta } from '@/components/portfolio/ClosingCta'
import { CommandPalette } from '@/components/portfolio/CommandPalette'
import { ContactTerminal } from '@/components/portfolio/ContactTerminal'
import { FeaturedDossier } from '@/components/portfolio/FeaturedDossier'
import { FieldNotes } from '@/components/portfolio/FieldNotes'
import { Hero } from '@/components/portfolio/Hero'
import { Manifesto } from '@/components/portfolio/Manifesto'
import { OperatorProfile } from '@/components/portfolio/OperatorProfile'
import { PortfolioNav } from '@/components/portfolio/PortfolioNav'
import { StatusBar } from '@/components/portfolio/StatusBar'
import { WorkIndex } from '@/components/portfolio/WorkIndex'
import { getPosts } from '@/lib/posts'
import { PostList } from '@/components/posts/PostList'
import { hero } from '@/lib/portfolio'
export const metadata: Metadata = {
title: 'Krishna Ayyalasomayajula — Systems Engineer',
description: hero.subline,
openGraph: {
images: ['/og.png'],
},
}
export default async function HomePage() {
const posts = await getPosts()
const posts = (await getPosts()).slice(0, 4)
return (
<main className="mx-auto max-w-5xl px-6 py-10 sm:py-14">
<section aria-labelledby="recent-heading" className="space-y-7">
<div className="border-b border-border pb-5">
<h1 id="recent-heading" className="heading-xl m-0 text-ink">
Recent
</h1>
</div>
{posts.length > 0 ? (
<PostList posts={posts} />
) : (
<div className="empty-state">
<p className="font-mono text-xs uppercase tracking-[0.22em] text-ink-soft">No posts yet</p>
<h3 className="heading-sm m-0 text-ink">The notebook is ready.</h3>
<p className="m-0 max-w-xl text-sm leading-6 text-ink-soft">
Add your first MDX post and it will show up here with the same polished card treatment.
</p>
</div>
)}
</section>
</main>
<div className="min-h-screen bg-blacksite-bg text-blacksite-text">
<StatusBar />
<PortfolioNav />
<Hero />
<FeaturedDossier />
<WorkIndex />
<Manifesto />
<CapabilityRows />
<OperatorProfile />
<FieldNotes posts={posts} />
<ContactTerminal />
<ClosingCta />
<CommandPalette />
</div>
)
}

View File

@@ -1,81 +0,0 @@
import { notFound } from 'next/navigation'
import Link from 'next/link'
import { getPosts, getPost, tagToSlug } from '@/lib/posts'
import { TableOfContents } from '@/components/posts/TableOfContents'
import { ScrollToTop } from '@/components/ui/ScrollToTop'
import { ReadingProgress } from '@/components/ui/ReadingProgress'
export const dynamicParams = false
export const dynamic = 'force-static'
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const slug = (await params).slug
const post = await getPost(slug)
if (!post) return { title: 'Not Found' }
return { title: post.title }
}
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const slug = (await params).slug
const post = await getPost(slug)
if (!post) notFound()
const { default: PostContent } = await import(
/* turbopackOptional: true */
`@/content/posts/${slug}.mdx`
)
return (
<>
<ScrollToTop />
<ReadingProgress />
<div className="mx-auto max-w-6xl px-6 py-16">
<div className="grid grid-cols-1 gap-8 lg:grid-cols-[minmax(0,1fr)_14rem] lg:gap-12">
<article className="min-w-0">
<header className="mb-12">
<time className="font-mono text-sm text-ink-soft" dateTime={post.date}>{new Date(post.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}</time>
<h1 className="heading-xl text-ink mt-3 mb-2">
{post.title}
</h1>
<div className="flex items-center gap-4 font-mono text-xs text-ink-soft">
{post.author && <span>by {post.author}</span>}
<span>{post.readingTime} min read</span>
</div>
{post.coverImage && (
<img
src={post.coverImage}
alt={`Featured image for article: ${post.title}`}
className="my-8 aspect-[16/9] w-full rounded-2xl border border-border bg-surface object-cover shadow-card"
loading="eager"
decoding="async"
fetchPriority="high"
/>
)}
{post.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mt-4">
{post.tags.map((tag) => (
<Link key={tag} href={`/tags/${tagToSlug(tag)}/`} className="rounded-full bg-surface px-3 py-1 text-xs font-medium text-ink-soft border border-border transition-colors hover:border-accent/50 hover:text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">
{tag}
</Link>
))}
</div>
)}
</header>
<div className="prose prose-lg article-prose max-w-none min-w-0">
<PostContent />
</div>
</article>
<aside className="hidden lg:block">
<TableOfContents />
</aside>
</div>
</div>
</>
)
}

20
app/sitemap.ts Normal file
View File

@@ -0,0 +1,20 @@
import type { MetadataRoute } from 'next'
import { getAllTags, getPosts } from '@/lib/posts'
import { tagToSlug } from '@/lib/tags'
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://localhost:3000'
export const dynamic = 'force-static'
const absoluteUrl = (pathname: string): string => new URL(pathname, siteUrl).toString()
const [posts, tags] = await Promise.all([getPosts(), getAllTags()])
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: absoluteUrl('/') },
{ url: absoluteUrl('/blog/') },
...posts.map((post) => ({ url: absoluteUrl(`/blog/${post.slug}/`) })),
...tags.map((tag) => ({ url: absoluteUrl(`/blog/tags/${tagToSlug(tag.label)}/`) })),
]
}

View File

@@ -1,8 +1,8 @@
export function Footer() {
return (
<footer className="border-t border-border mt-16 py-8 text-center">
<p className="font-mono text-sm text-ink-soft">
© {new Date().getFullYear()} Krishna A. All rights reserved.
<p className="font-mono text-xs text-ink-soft">
© {new Date().getFullYear()} Krishna Ayyalasomayajula
</p>
</footer>
)

View File

@@ -3,11 +3,10 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { m } from "motion/react";
import { ThemeToggle } from "@/components/ui/ThemeToggle";
const navLinks = [
{ label: "Home", href: "/" },
{ label: "Posts", href: "/posts/" },
{ label: "PORTFOLIO", href: "/" },
{ label: "FIELD NOTES", href: "/blog/" },
];
export function Header() {
@@ -22,8 +21,11 @@ export function Header() {
className="sticky top-0 z-50 bg-canvas/80 backdrop-blur-sm border-b border-border"
>
<div className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
<Link href="/" className="heading-sm text-ink hover:text-accent transition-colors">
Krishna
<Link
href="/"
className="font-mono text-xs uppercase tracking-[0.16em] text-ink transition-colors hover:text-ink-soft"
>
KRISHNA / SIGNAL
</Link>
<div className="flex items-center gap-8">
{navLinks.map((link) => {
@@ -37,7 +39,7 @@ export function Header() {
key={link.href}
href={link.href}
aria-current={isActive ? "page" : undefined}
className={`font-medium text-sm transition-colors ${
className={`font-mono text-xs uppercase tracking-[0.16em] transition-colors ${
isActive ? "text-ink" : "text-ink-soft hover:text-ink"
}`}
>
@@ -45,7 +47,6 @@ export function Header() {
</Link>
);
})}
<ThemeToggle />
</div>
</div>
</m.nav>

View File

@@ -0,0 +1,53 @@
"use client";
import { m, useReducedMotion } from "motion/react";
import { capabilities } from "@/lib/portfolio";
export function CapabilityRows() {
const shouldReduceMotion = useReducedMotion();
return (
<section
id="capabilities"
aria-labelledby="capabilities-heading"
className="mx-auto max-w-7xl px-6 py-28 text-blacksite-text sm:px-8 sm:py-36 lg:px-12"
>
<div className="mb-12">
<div>
<p className="section-label mb-3">CAPABILITIES /.04</p>
<h2
id="capabilities-heading"
className="font-display text-3xl font-semibold tracking-[-0.05em] text-blacksite-text sm:text-5xl"
>
Built for hostile constraints.
</h2>
</div>
</div>
<div className="border-t border-blacksite-line">
{capabilities.map((capability, index) => (
<m.div
key={capability.index}
className="grid gap-5 border-b border-blacksite-line py-6 sm:grid-cols-[1fr_auto] sm:items-center sm:py-8"
initial={shouldReduceMotion ? false : { opacity: 0, y: 14 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-12%" }}
transition={{
duration: shouldReduceMotion ? 0 : 0.45,
delay: shouldReduceMotion ? 0 : index * 0.07,
ease: "easeOut",
}}
>
<p className="font-display text-[clamp(28px,4.5vw,64px)] font-semibold leading-[0.95] tracking-[-0.06em] text-blacksite-text">
{capability.text}
</p>
<p className="section-label text-left text-signal-orange sm:text-right">
{capability.index}
</p>
</m.div>
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,84 @@
import Link from "next/link";
import { contact } from "@/lib/portfolio";
const sectionLinks = [
{ label: "Work", href: "#work" },
{ label: "Capabilities", href: "#capabilities" },
{ label: "Operator", href: "#operator" },
{ label: "Contact", href: "#contact" },
];
const elsewhereLinks = [
{ label: "Git", href: contact.git },
{ label: "LinkedIn", href: contact.linkedIn },
];
export function ClosingCta() {
const year = new Date().getFullYear();
return (
<footer className="border-t border-blacksite-line bg-blacksite-bg px-6 py-20 text-blacksite-text sm:px-8 sm:py-24 lg:px-12">
<div className="mx-auto max-w-7xl">
<div className="grid gap-4 lg:grid-cols-2">
<a
href="#work"
className="group min-h-52 border border-blacksite-line bg-blacksite-surface p-6 transition-colors hover:border-signal-orange/70 focus-visible:outline-signal-orange sm:p-8"
>
<span className="font-display text-4xl font-semibold tracking-[-0.04em] text-blacksite-text transition-colors group-hover:text-signal-orange sm:text-6xl">
View Work
</span>
</a>
<a
href={`mailto:${contact.email}`}
className="group min-h-52 border border-blacksite-line bg-blacksite-surface p-6 transition-colors hover:border-signal-orange/70 focus-visible:outline-signal-orange sm:p-8"
>
<span className="font-display text-4xl font-semibold tracking-[-0.04em] text-blacksite-text transition-colors group-hover:text-signal-orange sm:text-6xl">
Send Signal
</span>
</a>
</div>
<div className="mt-8 grid gap-8 border border-blacksite-line bg-blacksite-surface2/35 p-6 sm:grid-cols-2 lg:grid-cols-3 lg:p-8">
<nav aria-label="Footer sections">
<p className="section-label mb-4">Sections</p>
<ul className="grid gap-3 font-mono text-xs uppercase tracking-[0.14em] text-blacksite-muted">
{sectionLinks.map((link) => (
<li key={link.href}>
<a href={link.href} className="transition-colors hover:text-signal-orange focus-visible:outline-signal-orange">
{link.label}
</a>
</li>
))}
</ul>
</nav>
<nav aria-label="Footer field notes">
<p className="section-label mb-4">Field Notes</p>
<Link href="/blog/" className="font-mono text-xs uppercase tracking-[0.14em] text-blacksite-muted transition-colors hover:text-signal-orange focus-visible:outline-signal-orange">
/blog/
</Link>
</nav>
<nav aria-label="Footer elsewhere">
<p className="section-label mb-4">Elsewhere</p>
<ul className="grid gap-3 font-mono text-xs uppercase tracking-[0.14em] text-blacksite-muted">
{elsewhereLinks.map((link) => (
<li key={link.href}>
<a href={link.href} target="_blank" rel="noreferrer" className="transition-colors hover:text-signal-orange focus-visible:outline-signal-orange">
{link.label}
</a>
</li>
))}
</ul>
</nav>
</div>
<div className="mt-6 flex flex-wrap items-center justify-between gap-4 font-mono text-xs uppercase tracking-[0.14em] text-blacksite-muted">
<p>© {year}</p>
<p>K</p>
</div>
</div>
</footer>
);
}

View File

@@ -0,0 +1,207 @@
"use client";
import { Command } from "cmdk";
import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { contact } from "@/lib/portfolio";
type PaletteAction =
| { kind: "scroll"; targetId: "work" | "capabilities" | "operator" | "contact" }
| { kind: "route"; href: "/blog/" }
| { kind: "copy-email" }
| { kind: "external"; href: string };
const commands: {
label: string;
hint: string;
keywords: string[];
action: PaletteAction;
}[] = [
{
label: "Go to Work",
hint: "#work",
keywords: ["projects", "dossiers", "selected work"],
action: { kind: "scroll", targetId: "work" },
},
{
label: "Go to Capabilities",
hint: "#capabilities",
keywords: ["skills", "systems", "kernel"],
action: { kind: "scroll", targetId: "capabilities" },
},
{
label: "Go to Operator",
hint: "#operator",
keywords: ["profile", "identity", "honors"],
action: { kind: "scroll", targetId: "operator" },
},
{
label: "Go to Contact",
hint: "#contact",
keywords: ["email", "signal"],
action: { kind: "scroll", targetId: "contact" },
},
{
label: "Open Field Notes",
hint: "/blog/",
keywords: ["blog", "notes", "archive"],
action: { kind: "route", href: "/blog/" },
},
{
label: "Copy Email",
hint: "copy",
keywords: [contact.email, "mail", "contact"],
action: { kind: "copy-email" },
},
{
label: "Open Git profile",
hint: "git",
keywords: ["source", "forge", "code"],
action: { kind: "external", href: contact.git },
},
{
label: "Open LinkedIn",
hint: "in",
keywords: ["profile", "social", "linkedin"],
action: { kind: "external", href: contact.linkedIn },
},
];
export function CommandPalette() {
const router = useRouter();
const [open, setOpen] = useState(false);
const [pendingAction, setPendingAction] = useState<PaletteAction | null>(null);
const lastFocusedElement = useRef<HTMLElement | null>(null);
const wasOpen = useRef(false);
useEffect(() => {
function rememberFocus() {
lastFocusedElement.current =
document.activeElement instanceof HTMLElement ? document.activeElement : null;
}
function onKeyDown(event: KeyboardEvent) {
if (event.key.toLowerCase() !== "k" || (!event.metaKey && !event.ctrlKey)) {
return;
}
event.preventDefault();
rememberFocus();
setOpen((current) => !current);
}
function onPaletteEvent() {
rememberFocus();
setOpen(true);
}
window.addEventListener("keydown", onKeyDown);
window.addEventListener("blacksite:palette", onPaletteEvent);
return () => {
window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("blacksite:palette", onPaletteEvent);
};
}, []);
useEffect(() => {
if (wasOpen.current && !open) {
lastFocusedElement.current?.focus();
}
wasOpen.current = open;
}, [open]);
useEffect(() => {
if (!pendingAction) {
return;
}
let cancelled = false;
async function runAction(action: PaletteAction) {
if (action.kind === "scroll") {
document.getElementById(action.targetId)?.scrollIntoView({ block: "start" });
}
if (action.kind === "route") {
router.push(action.href);
}
if (action.kind === "copy-email") {
try {
await navigator.clipboard.writeText(contact.email);
} catch {
// Clipboard failures should not strand the command dialog open.
}
}
if (action.kind === "external") {
window.open(action.href, "_blank", "noopener");
}
if (!cancelled) {
setOpen(false);
setPendingAction(null);
}
}
void runAction(pendingAction);
return () => {
cancelled = true;
};
}, [pendingAction, router]);
return (
<Command.Dialog
open={open}
onOpenChange={setOpen}
label="Signal Blacksite command palette"
loop
className="border border-blacksite-line bg-blacksite-surface text-blacksite-text shadow-[0_32px_120px_rgba(0,0,0,0.62)]"
overlayClassName="fixed inset-0 z-[90] bg-black/72 backdrop-blur-sm"
contentClassName="fixed left-1/2 top-[12vh] z-[100] w-[calc(100vw-2rem)] max-w-2xl -translate-x-1/2 overflow-hidden rounded-lg border border-blacksite-line bg-blacksite-surface shadow-[0_32px_120px_rgba(0,0,0,0.62)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-signal-cyan"
>
<div className="border-b border-blacksite-line px-4 py-3 sm:px-5">
<p className="section-label">COMMAND PALETTE</p>
<Command.Input
autoFocus
placeholder="Command…"
className="mt-3 w-full bg-transparent font-mono text-base text-blacksite-text outline-none placeholder:text-blacksite-muted focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-signal-cyan sm:text-lg"
/>
</div>
<Command.List className="max-h-[min(24rem,60vh)] overflow-y-auto p-2 [scroll-padding-block:0.5rem]">
<Command.Empty className="px-3 py-8 text-center font-mono text-xs uppercase tracking-[0.16em] text-blacksite-muted">
No matching command.
</Command.Empty>
<Command.Group
heading="Navigation"
className="[&_[cmdk-group-heading]]:px-3 [&_[cmdk-group-heading]]:pb-2 [&_[cmdk-group-heading]]:pt-3 [&_[cmdk-group-heading]]:font-mono [&_[cmdk-group-heading]]:text-[11px] [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-[0.16em] [&_[cmdk-group-heading]]:text-blacksite-muted"
>
{commands.map((command) => (
<Command.Item
key={command.label}
value={command.label}
keywords={command.keywords}
onSelect={() => setPendingAction(command.action)}
className="group flex cursor-pointer select-none items-center justify-between gap-4 rounded-md border border-transparent px-3 py-3 font-mono text-sm text-blacksite-text outline-none transition-colors data-[selected=true]:border-signal-cyan/60 data-[selected=true]:bg-signal-cyan/12 data-[selected=true]:text-signal-cyan data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50"
>
<span>{command.label}</span>
<kbd className="shrink-0 rounded border border-blacksite-line bg-blacksite-bg2 px-2 py-1 text-[10px] uppercase tracking-[0.16em] text-blacksite-muted transition-colors group-data-[selected=true]:border-signal-cyan/50 group-data-[selected=true]:text-signal-cyan">
{command.hint}
</kbd>
</Command.Item>
))}
</Command.Group>
</Command.List>
<div className="flex items-center justify-between border-t border-blacksite-line px-4 py-3 font-mono text-[11px] uppercase tracking-[0.16em] text-blacksite-muted sm:px-5">
<span> select</span>
<span>Enter run · Esc close</span>
</div>
</Command.Dialog>
);
}

View File

@@ -0,0 +1,102 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { contact } from "@/lib/portfolio";
type CopyTarget = "email";
type CopyStatus = "idle" | "copied" | "failed";
const rows = [
{ label: "EMAIL", value: contact.email, kind: "copy" as const, target: "email" as CopyTarget },
{ label: "GIT", value: contact.git, kind: "link" as const },
{ label: "LINKEDIN", value: contact.linkedIn, kind: "link" as const },
];
export function ContactTerminal() {
const [copyState, setCopyState] = useState<Record<CopyTarget, CopyStatus>>({
email: "idle",
});
const resetTimers = useRef<Partial<Record<CopyTarget, ReturnType<typeof setTimeout>>>>({});
useEffect(() => {
const timers = resetTimers.current;
return () => {
Object.values(timers).forEach((timer) => {
if (timer) {
clearTimeout(timer);
}
});
};
}, []);
async function copyValue(target: CopyTarget, value: string) {
if (resetTimers.current[target]) {
clearTimeout(resetTimers.current[target]);
}
try {
await navigator.clipboard.writeText(value);
setCopyState((current) => ({ ...current, [target]: "copied" }));
} catch {
setCopyState((current) => ({ ...current, [target]: "failed" }));
}
resetTimers.current[target] = setTimeout(() => {
setCopyState((current) => ({ ...current, [target]: "idle" }));
}, 2000);
}
return (
<section id="contact" aria-labelledby="contact-title" className="mx-auto max-w-7xl px-6 py-28 text-blacksite-text sm:px-8 sm:py-36 lg:px-12">
<p className="section-label mb-12">CONTACT /.07</p>
<div className="border border-blacksite-line bg-blacksite-surface shadow-[0_32px_120px_rgba(0,0,0,0.42)]">
<div className="flex items-center gap-3 border-b border-blacksite-line px-5 py-4 sm:px-7">
<span aria-hidden="true" className="size-2 rounded-full bg-signal-green shadow-[0_0_18px_rgba(182,255,0,0.5)]" />
<p className="font-mono text-xs text-blacksite-muted">&gt; contact.channel</p>
</div>
<div className="grid gap-0 lg:grid-cols-[0.8fr_1.2fr]">
<div className="border-b border-blacksite-line px-5 py-8 sm:px-7 lg:border-b-0 lg:border-r lg:px-10">
<h2 id="contact-title" className="font-display text-3xl font-semibold uppercase tracking-[-0.04em] text-blacksite-text sm:text-5xl">
Send signal.
</h2>
</div>
<div className="divide-y divide-blacksite-line">
{rows.map((row) => (
<div key={row.label} className="grid gap-3 px-5 py-6 sm:px-7 md:grid-cols-[7rem_minmax(0,1fr)] lg:px-8">
<p className="section-label pt-1">{row.label}</p>
{row.kind === "copy" ? (
<button
type="button"
onClick={() => copyValue(row.target, row.value)}
className="group flex min-w-0 items-center justify-between gap-4 text-left font-mono text-sm text-blacksite-text transition-colors hover:underline focus-visible:outline-blacksite-text"
>
<span className="break-all">{row.value}</span>
<span className="shrink-0 text-[11px] uppercase tracking-[0.16em] text-blacksite-muted group-hover:text-blacksite-text">
{copyState[row.target] === "copied" ? "copied" : copyState[row.target] === "failed" ? "copy failed" : "copy"}
</span>
</button>
) : (
<a
href={row.value}
target="_blank"
rel="noreferrer"
className="group flex min-w-0 items-center justify-between gap-4 font-mono text-sm text-blacksite-text transition-colors hover:underline focus-visible:outline-blacksite-text"
>
<span className="break-all">{row.value}</span>
<span aria-hidden="true" className="shrink-0 text-blacksite-muted transition-transform group-hover:translate-x-0.5 group-hover:text-blacksite-text">
</span>
</a>
)}
</div>
))}
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,92 @@
"use client";
import { m, useReducedMotion } from "motion/react";
import { featuredDossier } from "@/lib/portfolio";
const fieldRows = [
{ label: "MISSION", value: featuredDossier.mission },
{ label: "CONSTRAINT", value: featuredDossier.constraint ?? "—" },
{ label: "SYSTEM", value: featuredDossier.system },
{ label: "RESULT", value: featuredDossier.result },
];
export function FeaturedDossier() {
const shouldReduceMotion = useReducedMotion();
return (
<m.section
aria-labelledby="featured-dossier-title"
initial={shouldReduceMotion ? false : { opacity: 0, y: 18 }}
whileInView={shouldReduceMotion ? undefined : { opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
viewport={{ once: true, amount: 0.18 }}
className="mx-auto max-w-7xl px-6 py-28 text-blacksite-text sm:px-8 sm:py-36 lg:px-12"
>
<div className="mb-12 flex items-center justify-between gap-4">
<p className="section-label">FEATURED /.01</p>
<span aria-hidden="true" className="size-2 rounded-full bg-signal-cyan shadow-[0_0_22px_rgba(0,240,255,0.62)]" />
</div>
<article className="overflow-hidden border border-blacksite-line bg-blacksite-surface shadow-[0_32px_120px_rgba(0,0,0,0.42)]">
<div className="relative overflow-hidden border-b border-blacksite-line p-6 pb-0 sm:p-8 sm:pb-0">
<div aria-hidden="true" className="absolute inset-x-0 top-0 h-px bg-signal-cyan/50" />
<h2
id="featured-dossier-title"
className="font-display translate-y-4 text-[clamp(4.5rem,18vw,15rem)] font-semibold uppercase leading-[0.72] tracking-[-0.08em] text-blacksite-text"
>
{featuredDossier.title.toUpperCase()}
</h2>
</div>
<div className="grid gap-0 lg:grid-cols-[minmax(0,1fr)_22rem]">
<div className="divide-y divide-blacksite-line border-b border-blacksite-line lg:border-b-0 lg:border-r">
{fieldRows.map((row) => (
<div key={row.label} className="grid gap-3 p-6 sm:p-8 md:grid-cols-[9rem_minmax(0,1fr)]">
<p className="section-label text-signal-cyan">{row.label}</p>
<p className="text-sm leading-7 text-blacksite-muted sm:text-base">{row.value}</p>
</div>
))}
</div>
<aside className="bg-blacksite-surface2/45 p-6 sm:p-8" aria-label="Featured dossier details and links">
<div className="border-b border-blacksite-line pb-6">
<p className="font-mono text-xs uppercase tracking-[0.14em] text-blacksite-text">
{featuredDossier.role} <span className="text-signal-cyan">·</span> {featuredDossier.year}
</p>
<div className="mt-5 flex flex-wrap gap-2">
{featuredDossier.stack.map((item) => (
<span
key={item}
className="border border-blacksite-line bg-blacksite-surface px-3 py-1 font-mono text-[11px] uppercase tracking-[0.14em] text-blacksite-muted"
>
{item}
</span>
))}
</div>
</div>
<div className="pt-6">
<p className="section-label mb-3">LINKS</p>
<div className="grid gap-3">
{featuredDossier.links.map((link) => (
<a
key={link.href}
href={link.href}
target="_blank"
rel="noopener noreferrer"
className="group flex items-center justify-between gap-4 border border-blacksite-line px-4 py-3 font-mono text-xs uppercase tracking-[0.14em] text-blacksite-text transition-colors hover:border-signal-cyan/70 hover:text-signal-cyan focus-visible:outline-signal-cyan"
>
<span>{link.label}</span>
<span aria-hidden="true" className="text-signal-cyan transition-transform group-hover:translate-x-0.5">
</span>
</a>
))}
</div>
</div>
</aside>
</div>
</article>
</m.section>
);
}

View File

@@ -0,0 +1,75 @@
import Link from "next/link";
import type { PostMeta } from "@/lib/posts";
type FieldNotesProps = {
posts: PostMeta[];
};
function formatPostDate(date: string) {
const parsed = new Date(date);
if (Number.isNaN(parsed.getTime())) return date;
return parsed.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "2-digit",
});
}
export function FieldNotes({ posts }: FieldNotesProps) {
const recentPosts = posts.slice(0, 4);
return (
<section
id="notes"
className="border-y border-blacksite-line bg-blacksite-bg2 px-6 py-28 text-blacksite-text sm:px-8 sm:py-36 lg:px-12"
>
<div className="mx-auto max-w-7xl">
<div className="mb-12">
<p className="section-label">FIELD NOTES /.06</p>
</div>
{recentPosts.length > 0 ? (
<>
<div className="divide-y divide-blacksite-line border-y border-blacksite-line">
{recentPosts.map((post) => (
<Link
key={post.slug}
href={`/blog/${post.slug}/`}
className="group grid gap-3 px-0 py-6 transition-colors hover:bg-blacksite-surface/55 focus-visible:outline-signal-cyan sm:grid-cols-[12rem_minmax(0,1fr)] sm:px-4"
>
<time className="section-label pt-1" dateTime={post.date}>
{formatPostDate(post.date)}
</time>
<span className="min-w-0">
<span className="block font-display text-2xl font-medium tracking-[-0.03em] text-blacksite-text transition-colors group-hover:text-signal-cyan group-focus-visible:text-signal-cyan">
{post.title}
</span>
{post.excerpt && (
<span className="mt-2 block max-w-3xl text-sm leading-6 text-blacksite-muted">
{post.excerpt}
</span>
)}
</span>
</Link>
))}
</div>
<div className="mt-8 flex justify-end">
<Link
href="/blog/"
className="section-label rounded-md border border-blacksite-line px-4 py-3 !text-blacksite-text transition-colors hover:border-signal-cyan/70 hover:!text-signal-cyan focus-visible:outline-signal-cyan"
>
All field notes
</Link>
</div>
</>
) : (
<div className="border border-dashed border-blacksite-line bg-blacksite-surface/45 p-8 sm:p-10">
<p className="section-label !text-blacksite-text">ARCHIVE EMPTY</p>
</div>
)}
</div>
</section>
);
}

View File

@@ -0,0 +1,85 @@
"use client";
import { m, useReducedMotion } from "motion/react";
import { hero } from "@/lib/portfolio";
import { HeroCanvas } from "./HeroCanvas";
const paletteEventName = "blacksite:palette";
function openCommandPalette() {
if (typeof window === "undefined") {
return;
}
window.dispatchEvent(new CustomEvent(paletteEventName));
}
export function Hero() {
const shouldReduceMotion = useReducedMotion();
const headingFragments = hero.heading
.split(". ")
.map((fragment, index, fragments) => {
if (index < fragments.length - 1 && !fragment.endsWith(".")) {
return `${fragment}.`;
}
return fragment;
});
return (
<section
aria-labelledby="hero-heading"
className="relative isolate flex min-h-screen overflow-hidden bg-blacksite-bg px-5 py-28 text-blacksite-text sm:px-8 lg:px-12"
>
<HeroCanvas />
<div className="absolute inset-0 z-0 bg-[linear-gradient(90deg,rgba(3,5,6,0.98),rgba(3,5,6,0.78)_52%,rgba(3,5,6,0.96))]" />
<m.div
initial={shouldReduceMotion ? false : { opacity: 0, y: 18 }}
animate={shouldReduceMotion ? undefined : { opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
className="relative z-10 mx-auto flex w-full max-w-7xl flex-col justify-center"
>
<div className="mb-8 inline-flex w-fit items-center gap-3 border border-blacksite-line bg-blacksite-surface/80 px-4 py-2 font-mono text-[11px] uppercase tracking-[0.16em] text-blacksite-muted shadow-[0_0_40px_rgba(0,0,0,0.35)] backdrop-blur-sm">
<span className="h-2 w-2 rounded-full bg-signal-green shadow-[0_0_14px_rgba(182,255,0,0.75)]" />
[STATUS: ONLINE]
</div>
<h1
id="hero-heading"
className="max-w-6xl font-display text-[clamp(3rem,9vw,7.5rem)] font-medium leading-[0.92] tracking-[-0.05em] text-balance text-blacksite-text"
>
{headingFragments.map((fragment) => (
<span key={fragment} className="block">
{fragment}
</span>
))}
</h1>
<p className="mt-6 max-w-2xl font-mono text-sm leading-7 text-blacksite-muted sm:text-base">
{hero.subline}
</p>
<div className="mt-10 flex flex-col gap-3 sm:flex-row">
<a
href="#work"
className="group inline-flex items-center justify-center border border-blacksite-line border-l-signal-orange bg-blacksite-surface px-6 py-3 font-mono text-xs uppercase tracking-[0.16em] text-blacksite-text transition-colors hover:border-signal-orange hover:bg-blacksite-surface2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-signal-orange"
>
View Work
<span className="ml-3 text-signal-orange" aria-hidden="true">
/.
</span>
</a>
<button
type="button"
onClick={openCommandPalette}
className="inline-flex items-center justify-center border border-blacksite-line bg-blacksite-bg2/85 px-6 py-3 font-mono text-xs uppercase tracking-[0.16em] text-blacksite-muted transition-colors hover:border-signal-orange hover:text-blacksite-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-signal-orange"
>
K
</button>
</div>
</m.div>
</section>
);
}

View File

@@ -0,0 +1,206 @@
"use client";
import { useEffect, useRef } from "react";
import { useReducedMotion } from "motion/react";
const COLORS = {
bg: "#030506",
line: "rgba(44, 53, 58, 0.46)",
dot: "rgba(244, 247, 248, 0.38)",
cyan: "rgba(0, 240, 255, 0.82)",
};
const GRID_COLUMNS = 14;
const GRID_ROWS = 13;
const PULSE_PERIOD_MS = 5200;
function projectX(width: number, xIndex: number, yIndex: number, time: number) {
const normalizedX = (xIndex / (GRID_COLUMNS - 1) - 0.5) * 2;
const normalizedY = yIndex / (GRID_ROWS - 1);
const depth = normalizedY * normalizedY;
return (
width * 0.5 +
normalizedX * width * (0.13 + depth * 0.5) +
Math.sin(time * 0.00008 + yIndex * 0.45) * 7
);
}
function projectY(height: number, yIndex: number, time: number) {
const normalizedY = yIndex / (GRID_ROWS - 1);
const drift = Math.sin(time * 0.00012 + yIndex * 0.7) * 5;
return height * 0.24 + height * 0.82 * normalizedY * normalizedY + drift;
}
export function HeroCanvas() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const shouldReduceMotion = useReducedMotion();
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) {
return;
}
const context = canvas.getContext("2d", { alpha: true });
if (!context) {
return;
}
let width = 0;
let height = 0;
let dpr = 1;
let frameId = 0;
let isMounted = true;
let resizeObserver: ResizeObserver | null = null;
const resize = () => {
const rect = canvas.getBoundingClientRect();
dpr = Math.min(window.devicePixelRatio || 1, 2);
width = Math.max(1, rect.width);
height = Math.max(1, rect.height);
canvas.width = Math.floor(width * dpr);
canvas.height = Math.floor(height * dpr);
context.setTransform(dpr, 0, 0, dpr, 0, 0);
if (shouldReduceMotion) {
draw(0);
}
};
const drawGridLines = (time: number) => {
context.lineWidth = 1;
context.strokeStyle = COLORS.line;
for (let column = 0; column < GRID_COLUMNS; column += 1) {
context.beginPath();
for (let row = 0; row < GRID_ROWS; row += 1) {
const x = projectX(width, column, row, time);
const y = projectY(height, row, time);
if (row === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
}
context.stroke();
}
for (let row = 0; row < GRID_ROWS; row += 1) {
context.beginPath();
for (let column = 0; column < GRID_COLUMNS; column += 1) {
const x = projectX(width, column, row, time);
const y = projectY(height, row, time);
if (column === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
}
context.stroke();
}
};
const drawDots = (time: number) => {
context.fillStyle = COLORS.dot;
for (let row = 0; row < GRID_ROWS; row += 1) {
const normalizedY = row / (GRID_ROWS - 1);
const radius = 0.9 + normalizedY * 1.15;
for (let column = 0; column < GRID_COLUMNS; column += 1) {
context.beginPath();
context.arc(
projectX(width, column, row, time),
projectY(height, row, time),
radius,
0,
Math.PI * 2,
);
context.fill();
}
}
};
const drawPulse = (time: number) => {
if (shouldReduceMotion) {
return;
}
const progress = (time % PULSE_PERIOD_MS) / PULSE_PERIOD_MS;
const row = 8;
const maxColumn = Math.min(
GRID_COLUMNS - 1,
Math.max(1, Math.floor(progress * (GRID_COLUMNS + 2))),
);
context.save();
context.globalAlpha = progress > 0.84 ? (1 - progress) / 0.16 : 1;
context.lineWidth = 2;
context.strokeStyle = COLORS.cyan;
context.shadowBlur = 14;
context.shadowColor = COLORS.cyan;
context.beginPath();
for (let column = 0; column <= maxColumn; column += 1) {
const x = projectX(width, column, row, time);
const y = projectY(height, row, time);
if (column === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
}
context.stroke();
context.restore();
};
const draw = (time: number) => {
context.clearRect(0, 0, width, height);
context.fillStyle = COLORS.bg;
context.fillRect(0, 0, width, height);
drawGridLines(time);
drawDots(time);
drawPulse(time);
};
const tick = (time: number) => {
if (!isMounted) {
return;
}
draw(time);
frameId = window.requestAnimationFrame(tick);
};
resize();
if ("ResizeObserver" in window) {
resizeObserver = new ResizeObserver(resize);
resizeObserver.observe(canvas);
}
window.addEventListener("resize", resize, { passive: true });
if (!shouldReduceMotion) {
frameId = window.requestAnimationFrame(tick);
}
return () => {
isMounted = false;
resizeObserver?.disconnect();
window.removeEventListener("resize", resize);
if (frameId) {
window.cancelAnimationFrame(frameId);
}
};
}, [shouldReduceMotion]);
return (
<canvas
ref={canvasRef}
aria-hidden="true"
className="pointer-events-none absolute inset-0 h-full w-full opacity-[0.4]"
/>
);
}

View File

@@ -0,0 +1,52 @@
"use client";
import { m, useReducedMotion } from "motion/react";
import { manifesto } from "@/lib/portfolio";
const SURVIVE_PHRASE = "survive contact";
function splitAccentPhrase(statement: string) {
const phraseIndex = statement.indexOf(SURVIVE_PHRASE);
if (phraseIndex < 0) {
return null;
}
return {
before: statement.slice(0, phraseIndex),
after: statement.slice(phraseIndex + SURVIVE_PHRASE.length),
};
}
export function Manifesto() {
const shouldReduceMotion = useReducedMotion();
const accentSplit = splitAccentPhrase(manifesto);
return (
<m.section
aria-labelledby="manifesto-heading"
className="mx-auto max-w-7xl px-6 py-28 text-blacksite-muted sm:px-8 sm:py-36 lg:px-12"
initial={shouldReduceMotion ? false : { opacity: 0, y: 18 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: shouldReduceMotion ? 0 : 0.6, ease: "easeOut" }}
>
<p className="section-label mb-12">PRINCIPLE /.03</p>
<h2
id="manifesto-heading"
className="max-w-5xl font-display text-[clamp(2.25rem,6vw,4.5rem)] font-semibold leading-[1.05] tracking-[-0.06em] text-blacksite-muted"
>
{accentSplit ? (
<>
{accentSplit.before}
<span className="text-signal-cyan">{SURVIVE_PHRASE}</span>
{accentSplit.after}
</>
) : (
manifesto
)}
</h2>
</m.section>
);
}

View File

@@ -0,0 +1,135 @@
import { honors, identity, publication, skillsMatrix } from "@/lib/portfolio";
export function OperatorProfile() {
return (
<section
id="operator"
className="bg-blacksite-bg px-6 py-28 text-blacksite-text sm:px-8 sm:py-36 lg:px-12"
>
<div className="mx-auto max-w-7xl">
<div className="section-label mb-12">OPERATOR /.05</div>
<div className="overflow-hidden rounded-2xl border border-blacksite-line bg-blacksite-surface shadow-[0_24px_90px_rgba(0,0,0,0.35)]">
<div className="relative h-72 overflow-hidden border-b border-blacksite-line bg-blacksite-bg2 sm:h-80">
<img
src="/banner.jpg"
alt=""
className="h-full w-full object-cover object-center grayscale contrast-125 opacity-70"
/>
<div
className="absolute inset-0 bg-gradient-to-t from-blacksite-bg via-blacksite-bg/55 to-transparent"
aria-hidden="true"
/>
<h2 className="absolute bottom-6 left-6 font-display text-3xl leading-none tracking-tight text-blacksite-text sm:bottom-8 sm:left-8 sm:text-5xl">
{identity.name}
</h2>
</div>
<div className="grid gap-px bg-blacksite-line lg:grid-cols-[1fr_1.15fr]">
<div className="bg-blacksite-surface p-8 sm:p-10">
<dl className="grid gap-5 text-sm text-blacksite-muted sm:text-base">
<div>
<dt className="section-label mb-2">ROLE</dt>
<dd className="text-blacksite-text">{identity.roleLine}</dd>
</div>
<div>
<dt className="section-label mb-2">EDUCATION</dt>
<dd>{identity.education}</dd>
</div>
<div>
<dt className="section-label mb-2">SITE</dt>
<dd>{identity.site}</dd>
</div>
</dl>
</div>
<div className="grid gap-px bg-blacksite-line">
<article className="bg-blacksite-surface2 p-8 sm:p-10">
<p className="section-label mb-4">PUBLICATION</p>
<h3 className="font-display text-2xl leading-tight text-blacksite-text">
{publication.title}
</h3>
<p className="mt-4 text-sm text-blacksite-muted sm:text-base">
{publication.venue} · {publication.year} · {publication.authorship}
</p>
<a
href={publication.href}
className="section-label mt-6 inline-flex text-signal-cyan underline decoration-signal-cyan/35 underline-offset-4 transition-colors hover:text-blacksite-text focus-visible:outline-signal-cyan"
>
DOI: {publication.doi}
</a>
</article>
<div className="bg-blacksite-surface p-8 sm:p-10">
<p className="section-label mb-5">HONORS</p>
<ul className="grid gap-4">
{honors.map((honor) => (
<li
key={`${honor.title}-${honor.year}`}
className="grid gap-2 border-l border-signal-cyan/50 pl-4 sm:grid-cols-[auto_1fr] sm:items-baseline sm:gap-4"
>
<span className="section-label text-signal-cyan">
{honor.year || "RECOGNITION"}
</span>
<span>
<span className="text-blacksite-text">{honor.title}</span>
{honor.detail ? (
<span className="text-blacksite-muted"> {honor.detail}</span>
) : null}
</span>
</li>
))}
</ul>
</div>
</div>
</div>
<div className="border-t border-blacksite-line bg-blacksite-bg2 p-8 sm:p-10">
<div className="mb-6 flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
<p className="section-label">SKILLS</p>
</div>
<div className="overflow-hidden rounded-xl border border-blacksite-line">
<table className="w-full border-collapse text-left text-sm sm:text-base">
<thead className="bg-blacksite-surface2">
<tr>
<th scope="col" className="section-label px-4 py-3 sm:px-5">
Domain
</th>
<th scope="col" className="section-label px-4 py-3 sm:px-5">
Capabilities
</th>
</tr>
</thead>
<tbody className="divide-y divide-blacksite-line bg-blacksite-surface">
{skillsMatrix.map((row) => (
<tr key={row.category}>
<th
scope="row"
className="w-56 px-4 py-4 align-top font-mono text-xs uppercase tracking-[0.16em] text-blacksite-text sm:px-5"
>
{row.category}
</th>
<td className="px-4 py-4 text-blacksite-muted sm:px-5">
<div className="flex flex-wrap gap-2">
{row.skills.map((skill) => (
<span
key={skill}
className="rounded-full border border-blacksite-line bg-blacksite-surface2 px-3 py-1 text-xs text-blacksite-text sm:text-sm"
>
{skill}
</span>
))}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,78 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
const navLinks = [
{ label: "WORK", href: "#work" },
{ label: "CAPABILITIES", href: "#capabilities" },
{ label: "FIELD NOTES", href: "/blog/" },
{ label: "CONTACT", href: "#contact" },
];
function openCommandPalette() {
window.dispatchEvent(new CustomEvent("blacksite:palette"));
}
export function PortfolioNav() {
const pathname = usePathname();
return (
<nav
aria-label="Portfolio navigation"
className="sticky top-3 z-50 mx-auto mt-3 w-[calc(100%-2rem)] max-w-5xl rounded-xl border border-blacksite-line bg-blacksite-surface/82 px-6 py-2 text-blacksite-muted shadow-[0_18px_70px_rgba(0,0,0,0.38)] backdrop-blur-md sm:px-8 lg:px-12"
>
<div className="flex items-center justify-between gap-3">
<Link
href="/"
aria-current={pathname === "/" ? "page" : undefined}
className="section-label whitespace-nowrap !text-blacksite-text transition-colors hover:!text-signal-cyan focus-visible:outline-signal-cyan aria-[current=page]:!text-signal-cyan"
>
KRISHNA / SIGNAL
</Link>
<div className="flex min-w-0 flex-1 items-center justify-end gap-1 sm:gap-2">
<div className="flex min-w-0 items-center gap-1 overflow-x-auto">
{navLinks.map((link) => {
const isBlogActive =
link.href === "/blog/" &&
(pathname === "/blog" || pathname.startsWith("/blog/"));
if (link.href.startsWith("/")) {
return (
<Link
key={link.href}
href={link.href}
aria-current={isBlogActive ? "page" : undefined}
className="section-label rounded-md px-2 py-2 transition-colors hover:!text-signal-cyan focus-visible:outline-signal-cyan aria-[current=page]:!text-signal-cyan sm:px-3"
>
{link.label}
</Link>
);
}
return (
<a
key={link.href}
href={link.href}
className="section-label rounded-md px-2 py-2 transition-colors hover:!text-signal-cyan focus-visible:outline-signal-cyan active:!text-signal-cyan sm:px-3"
>
{link.label}
</a>
);
})}
</div>
<button
type="button"
onClick={openCommandPalette}
className="section-label rounded-lg border border-blacksite-line bg-blacksite-surface2 px-3 py-2 !text-blacksite-text transition-colors hover:border-signal-cyan/70 hover:!text-signal-cyan focus-visible:outline-signal-cyan active:!text-signal-cyan"
aria-label="Open command palette"
>
K
</button>
</div>
</div>
</nav>
);
}

View File

@@ -0,0 +1,23 @@
import { statusLine } from "@/lib/portfolio";
export function StatusBar() {
return (
<div className="border-b border-blacksite-line bg-blacksite-bg text-blacksite-muted">
<div className="mx-auto flex max-w-7xl items-center justify-between gap-4 px-6 py-2 sm:px-8 lg:px-12">
<p className="section-label flex min-w-0 items-center gap-2">
<span
aria-hidden="true"
className="size-1.5 shrink-0 rounded-full bg-signal-green shadow-[0_0_14px_rgba(182,255,0,0.45)]"
/>
<span className="truncate">{statusLine}</span>
</p>
<a
href="#contact"
className="section-label shrink-0 !text-blacksite-text transition-colors hover:!text-signal-cyan focus-visible:outline-signal-cyan"
>
Contact
</a>
</div>
</div>
);
}

View File

@@ -0,0 +1,220 @@
"use client";
import { useState } from "react";
import { m, useReducedMotion } from "motion/react";
import { dossiers, type Dossier } from "@/lib/portfolio";
const activeText = "text-signal-green";
const activeBorder = "border-l-signal-green";
const activeGlow = "shadow-[0_0_32px_rgba(182,255,0,0.12)]";
function isLiveStatus(status: string) {
return /\b(live|deployed)\b/i.test(status);
}
function headlineProof(dossier: Dossier) {
return dossier.proof.find((proof) => proof.metric) ?? dossier.proof[0];
}
export function WorkIndex() {
const shouldReduceMotion = useReducedMotion();
const [activeId, setActiveId] = useState<string | null>(null);
return (
<section
id="work"
aria-labelledby="work-title"
className="mx-auto max-w-7xl px-6 py-28 text-blacksite-text sm:px-8 sm:py-36 lg:px-12"
>
<p id="work-title" className="section-label mb-12 text-blacksite-muted">
WORK /.02
</p>
<m.div
initial={shouldReduceMotion ? false : { opacity: 0, y: 18 }}
whileInView={shouldReduceMotion ? undefined : { opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-80px" }}
transition={{ duration: 0.5, ease: "easeOut" }}
className="overflow-hidden border-y border-blacksite-line bg-blacksite-bg2/70"
>
<div className="section-label grid grid-cols-[minmax(0,1.35fr)_minmax(0,1fr)_minmax(0,0.8fr)_5rem] gap-4 border-b border-blacksite-line px-4 py-3 text-blacksite-muted max-md:hidden">
<span>PROJECT</span>
<span>PROOF</span>
<span>TYPE</span>
<span className="text-right">YEAR</span>
</div>
<div className="divide-y divide-blacksite-line">
{dossiers.map((dossier, index) => {
const isActive = activeId === dossier.id;
const shouldDim = activeId !== null && !isActive;
const proof = headlineProof(dossier);
const detailsExpanded = shouldReduceMotion || isActive;
const liveStatus = isLiveStatus(dossier.status);
return (
<article
key={dossier.id}
tabIndex={0}
onMouseEnter={() => setActiveId(dossier.id)}
onMouseLeave={() => setActiveId(null)}
onFocus={() => setActiveId(dossier.id)}
onBlur={(event) => {
if (
!event.currentTarget.contains(
event.relatedTarget as Node | null,
)
) {
setActiveId(null);
}
}}
className={`group border-l-2 border-l-transparent bg-blacksite-bg2/20 px-4 py-6 outline-none transition duration-200 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-signal-green ${
isActive ? `${activeBorder} bg-blacksite-surface/70 ${activeGlow}` : ""
} ${shouldDim ? "opacity-45" : "opacity-100"}`}
>
<div className="grid gap-3 md:grid-cols-[minmax(0,1.35fr)_minmax(0,1fr)_minmax(0,0.8fr)_5rem] md:items-start md:gap-4">
<div className="min-w-0">
<p className="section-label mb-1 text-blacksite-muted md:hidden">
PROJECT
</p>
<div className="flex items-baseline gap-3">
<span className="font-mono text-xs text-blacksite-muted">
{String(index + 1).padStart(2, "0")}
</span>
<h3
className={`font-display text-2xl leading-none tracking-[-0.03em] transition-colors duration-200 ${
isActive ? activeText : "text-blacksite-text"
}`}
>
{dossier.title}
</h3>
{dossier.accent === "green" ? (
<span aria-hidden="true" className="size-2 rounded-full bg-signal-green shadow-[0_0_14px_rgba(182,255,0,0.45)]" />
) : null}
</div>
</div>
<div>
<p className="section-label mb-1 text-blacksite-muted md:hidden">
PROOF
</p>
<p className="font-mono text-xs uppercase tracking-[0.16em] text-signal-green">
{dossier.proofHeadline}
</p>
</div>
<div className="hidden md:block">
<p className="section-label mb-1 text-blacksite-muted md:hidden">
TYPE
</p>
<p className="text-sm leading-5 text-blacksite-muted">
{dossier.type}
</p>
</div>
<div className="md:text-right">
<p className="section-label mb-1 text-blacksite-muted md:hidden">
YEAR
</p>
<p className="font-mono text-xs text-blacksite-muted">
{dossier.year}
</p>
</div>
</div>
<m.div
initial={false}
animate={
detailsExpanded
? { opacity: 1, height: "auto" }
: { opacity: 0, height: 0 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: { duration: 0.22, ease: "easeOut" }
}
className="overflow-hidden [@media_(hover:none)]:!h-auto [@media_(hover:none)]:!opacity-100"
>
<div className="grid gap-5 pt-5 md:grid-cols-[minmax(0,1.35fr)_minmax(0,1fr)_minmax(0,0.8fr)_5rem] md:gap-4">
<div className="space-y-2 md:col-span-2">
<p className="section-label">MISSION</p>
<p className="text-sm leading-6 text-blacksite-text">
{dossier.mission}
</p>
{dossier.constraint ? (
<p className="text-xs leading-5 text-blacksite-muted">
Constraint: {dossier.constraint}
</p>
) : null}
</div>
<div className="space-y-2">
<p className="section-label">STATUS</p>
<p className="flex items-center gap-2 font-mono text-xs uppercase tracking-[0.16em] text-blacksite-muted">
{liveStatus ? (
<span aria-hidden="true" className="size-2 rounded-full bg-signal-green shadow-[0_0_14px_rgba(182,255,0,0.45)]" />
) : null}
{dossier.status}
</p>
</div>
<div className="space-y-2">
<p className="section-label">STACK</p>
<div className="flex flex-wrap gap-2">
{dossier.stack.map((item) => (
<span
key={item}
className="border border-blacksite-line bg-blacksite-surface px-2 py-1 font-mono text-[11px] uppercase tracking-[0.14em] text-blacksite-muted"
>
{item}
</span>
))}
</div>
</div>
<div className="space-y-2 md:text-right">
<p className="section-label">PROOF</p>
<p
className={`font-display text-xl leading-none ${activeText}`}
>
{proof?.metric ?? proof?.label ?? dossier.result}
</p>
<p className="text-xs leading-5 text-blacksite-muted">
{proof?.metric ? proof.label : dossier.result}
</p>
</div>
<div className="space-y-2 md:col-span-4">
<p className="section-label">LINKS</p>
{dossier.links.length > 0 ? (
<div className="flex flex-wrap gap-2">
{dossier.links.map((link) => (
<a
key={`${dossier.id}-${link.href}-${link.label}`}
href={link.href}
target="_blank"
rel="noopener noreferrer"
className="border border-blacksite-line bg-blacksite-surface2 px-3 py-2 font-mono text-xs uppercase tracking-[0.14em] text-blacksite-text transition-colors hover:border-signal-green hover:text-signal-green focus-visible:outline-signal-green"
>
{link.label}
</a>
))}
</div>
) : (
<p className="font-mono text-xs uppercase tracking-[0.14em] text-blacksite-muted">
Closed source
</p>
)}
</div>
</div>
</m.div>
</article>
);
})}
</div>
</m.div>
</section>
);
}

View File

@@ -3,18 +3,7 @@
import Link from 'next/link';
import { m, useReducedMotion } from 'motion/react';
import type { PostMeta } from '@/lib/posts';
const tagToClientSlug = (tag: string): string =>
tag
.normalize('NFKD')
.toLowerCase()
.replace(/[\u0300-\u036f]/g, '')
.replace(/\+/g, ' plus ')
.replace(/#/g, ' sharp ')
.replace(/[\\/]+/g, ' ')
.replace(/[^\p{L}\p{N}]+/gu, '-')
.replace(/-{2,}/g, '-')
.replace(/^-|-$/g, '');
import { tagToSlug } from '@/lib/tags';
export function PostCard({ slug, title, date, excerpt, tags = [], author, readingTime, index = 0, coverImage }: PostMeta & { index?: number }) {
const shouldReduceMotion = useReducedMotion();
@@ -26,11 +15,11 @@ export function PostCard({ slug, title, date, excerpt, tags = [], author, readin
whileHover={shouldReduceMotion ? undefined : { y: -2 }}
transition={{ duration: 0.35, delay: shouldReduceMotion ? 0 : Math.min(index * 0.04, 0.18), ease: [0.22, 1, 0.36, 1] }}
viewport={{ once: true, amount: 0.2 }}
className="group relative scroll-mt-20 rounded-2xl border border-border/90 bg-canvas p-5 shadow-card transition-[background-color,border-color,box-shadow] duration-200 ease-out hover:border-ink/25 hover:bg-surface/40 hover:shadow-card-hover dark:hover:border-ink/35 sm:p-7"
className="group relative scroll-mt-20 rounded-lg border border-border/90 bg-canvas p-5 shadow-card transition-[background-color,border-color,box-shadow] duration-200 ease-out hover:border-ink/25 hover:bg-surface/40 hover:shadow-card-hover dark:hover:border-ink/35 sm:p-7"
>
<Link href={`/posts/${slug}/`} className="block">
<Link href={`/blog/${slug}/`} className="block">
{coverImage && (
<div className="mb-5 overflow-hidden rounded-xl border border-border/80 bg-surface">
<div className="mb-5 overflow-hidden rounded-md border border-border/80 bg-surface">
<img
src={coverImage}
alt={`Cover image for ${title}`}
@@ -56,7 +45,7 @@ export function PostCard({ slug, title, date, excerpt, tags = [], author, readin
{tags && tags.length > 0 && (
<div className="mt-5 flex flex-wrap gap-2">
{tags.slice(0, 3).map((tag) => (
<Link key={tag} href={`/tags/${tagToClientSlug(tag)}/`} className="rounded-full border border-border bg-surface/70 px-3 py-1 text-xs text-ink-soft transition-colors duration-200 hover:border-accent/60 hover:text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">
<Link key={tag} href={`/blog/tags/${tagToSlug(tag)}/`} className="rounded-sm border border-border bg-surface/70 px-3 py-1 font-mono text-[11px] uppercase tracking-wider text-ink-soft transition-colors duration-200 hover:border-accent/60 hover:text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">
{tag}
</Link>
))}

View File

@@ -1,6 +1,7 @@
'use client'
import { useMemo, useState } from 'react'
import { tagToSlug } from '@/lib/tags'
import { PostCard } from './PostCard'
type SearchPost = {
@@ -37,15 +38,6 @@ const normalizeSearchText = (value: string): string =>
.toLowerCase()
.trim()
const tagToClientSlug = (tag: string): string =>
normalizeSearchText(tag)
.replace(/\+/g, ' plus ')
.replace(/#/g, ' sharp ')
.replace(/[\\/]+/g, ' ')
.replace(/[^\p{L}\p{N}]+/gu, '-')
.replace(/-{2,}/g, '-')
.replace(/^-|-$/g, '')
const getOrderedCharacterScore = (field: string, query: string): number => {
if (!query) return 0
@@ -100,7 +92,7 @@ const getTagFilters = (posts: SearchPost[]): TagFilter[] => {
const postTags = new Set<string>()
post.tags.forEach((tag) => {
const slug = tagToClientSlug(tag)
const slug = tagToSlug(tag)
if (!slug || postTags.has(slug)) return
postTags.add(slug)
@@ -130,7 +122,7 @@ export function PostSearch({ posts }: PostSearchProps) {
const results = useMemo(() => {
const scoredPosts = posts.reduce<ScoredPost[]>((matches, post, index) => {
if (selectedTag && !post.tags.some((tag) => tagToClientSlug(tag) === selectedTag)) {
if (selectedTag && !post.tags.some((tag) => tagToSlug(tag) === selectedTag)) {
return matches
}
@@ -155,7 +147,7 @@ export function PostSearch({ posts }: PostSearchProps) {
return (
<section aria-labelledby="posts-search-heading" className="space-y-8">
<div className="space-y-5 rounded-xl border border-border bg-surface/30 p-4 sm:p-5">
<div className="space-y-5 rounded-lg border border-border bg-surface/30 p-4 sm:p-5">
<div className="space-y-2">
<label id="posts-search-heading" htmlFor="post-search" className="block text-sm font-medium text-ink">
Search posts
@@ -166,7 +158,7 @@ export function PostSearch({ posts }: PostSearchProps) {
type="search"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search title, excerpt, tag, author, or slug"
placeholder="Search"
className="min-w-0 flex-1 rounded-lg border border-border bg-canvas px-3 py-2 text-sm text-ink outline-none transition-colors placeholder:text-ink-soft/70 focus:border-accent focus:ring-2 focus:ring-accent/20"
/>
<button
@@ -175,7 +167,7 @@ export function PostSearch({ posts }: PostSearchProps) {
disabled={!query}
className="rounded-lg border border-border px-3 py-2 text-sm text-ink-soft transition-colors hover:border-accent/50 hover:text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent disabled:cursor-not-allowed disabled:opacity-50"
>
Clear search
Clear
</button>
</div>
</div>
@@ -193,7 +185,7 @@ export function PostSearch({ posts }: PostSearchProps) {
type="button"
aria-pressed={isSelected}
onClick={() => setSelectedTag(isSelected ? null : tag.slug)}
className="rounded-full border border-border bg-canvas px-3 py-1 text-xs text-ink-soft transition-colors hover:border-accent/50 hover:text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent aria-pressed:border-accent aria-pressed:bg-accent/10 aria-pressed:text-accent"
className="rounded-sm border border-border bg-canvas px-3 py-1 font-mono text-[11px] uppercase tracking-wider text-ink-soft transition-colors hover:border-accent/50 hover:text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent aria-pressed:border-accent aria-pressed:bg-accent/10 aria-pressed:text-accent"
>
{tag.label} <span className="font-mono text-ink-soft/70">{tag.count}</span>
</button>
@@ -217,7 +209,7 @@ export function PostSearch({ posts }: PostSearchProps) {
}}
className="self-start rounded-lg border border-border px-3 py-1.5 text-sm text-ink-soft transition-colors hover:border-accent/50 hover:text-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent sm:self-auto"
>
Clear filters
Clear
</button>
)}
</div>
@@ -230,7 +222,7 @@ export function PostSearch({ posts }: PostSearchProps) {
))}
</div>
) : (
<p className="text-ink-soft">No posts match your search.</p>
<p className="text-ink-soft">No matches.</p>
)}
</section>
)

View File

@@ -75,8 +75,8 @@ export function TableOfContents() {
return (
<nav aria-label="Table of contents" className="lg:sticky lg:top-20">
<h4 className="font-sans text-xs font-semibold uppercase tracking-wider text-ink-soft mb-3">
On this page
<h4 className="section-label mb-3">
INDEX
</h4>
<ul className="space-y-1">
{headings.map((heading) => (

View File

@@ -8,11 +8,11 @@ interface CalloutProps {
children: ReactNode;
}
const iconMap: Record<string, string> = {
note: '',
tip: '💡',
warning: '⚠️',
danger: '🚫',
const tagMap: Record<string, string> = {
note: 'NOTE',
tip: 'TIP',
warning: 'WARNING',
danger: 'DANGER',
};
const typeClasses: Record<string, string> = {
@@ -27,12 +27,14 @@ export function Callout({ type = 'note', title, children }: CalloutProps) {
return (
<div className={className} role="note">
{title && (
<div className="callout-title">
<span className="callout-icon">{iconMap[type] || iconMap.note}</span>
<div className="callout-title">
<span className="text-[11px] font-mono tracking-[0.16em]">
{tagMap[type] || tagMap.note}
</span>
{title && (
<span>{title}</span>
</div>
)}
)}
</div>
<div className="callout-content">{children}</div>
</div>
);

View File

@@ -1,25 +0,0 @@
"use client";
import { useTheme } from "@wrksz/themes/client";
import { m } from "motion/react";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<m.button
type="button"
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
className="p-2 rounded-lg border border-border bg-canvas text-ink hover:border-ink transition-colors"
aria-label="Toggle theme"
>
{theme === "dark" ? (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
)}
</m.button>
);
}

View File

@@ -0,0 +1,228 @@
---
title: "The Rendering Test Card"
date: "2026-07-06"
excerpt: "Every element this pipeline renders, on one page."
tags: [meta, mdx, typography]
author: "Krishna"
---
## Typography
### Signal grammar
This card is a field check, not a showpiece. It asks the renderer to carry ordinary prose, sharp metadata, and the odd piece of tactical noise without losing the thread. If the page looks calm under load, the pipeline can be trusted. If it flinches here, it will flinch in production.
Read the text like a watch log. **Strong claims are reserved for verified contact.** *Emphasis marks drift, not drama.* ~~Dead assertions stay visible until the after-action report is signed.~~ Smart punctuation should land cleanly — “quotes” should curl, and a hard break should not feel like a crash. The internal archive lives at [the field notes index](/blog/). The outside manual can sit at [rehype-pretty-code](https://rehype-pretty.pages.dev/). A bare link should wake up by itself: https://example.com/rendering-test-card.
The voice here stays terse. Each paragraph names a capability, applies pressure, and moves. That is the house rule: fewer ornaments, more bearing. A page can be severe without becoming sterile. The interesting test is whether small UI affordances remain legible when the copy refuses to decorate them.
---
## Lists
### Inventory
Unordered checks keep the scan loose:
- Confirm the route exports as a static post.
- Confirm the prose measure stays narrow enough for a long read.
- Confirm links, code, math, and admonitions agree on the same blacksite language.
- Nested notes should indent, not collapse.
- Secondary bullets should feel like subordinate evidence.
- Confirm the page still works when there is no cover image.
Ordered checks keep the operator honest:
1. Build the archive.
2. Open the rendered HTML.
3. Search for the artifacts the pipeline promises.
1. Highlighted code lines.
2. KaTeX nodes.
3. Footnote anchors.
4. Callout wrappers.
4. Record only what happened.
Task lists expose the GFM path:
- [x] Write the proving document.
- [x] Include nested structure.
- [ ] Re-run after any renderer change.
- [ ] Delete nothing until the replacement has better evidence.
---
## Code
### Highlight pressure
The first fence checks title, caption, line highlights, range highlights, and character highlights in one pass. The word `signal` should glow wherever the highlighter is told to track it.
```ts title="lib/signal.ts" caption="Line + char highlights" {2,4-5} /signal/
type Packet = { channel: string; signal: number; sealed: boolean };
export function normalizeSignal(packet: Packet) {
const signal = Math.max(0, Math.min(packet.signal, 1));
const channel = packet.channel.trim().toLowerCase();
return { ...packet, channel, signal, sealed: true };
}
```
The second fence checks a custom starting line number. It should begin at ten and still keep the gutter stable.
```tsx showLineNumbers{10}
export function StatusGlyph({ hot }: { hot: boolean }) {
return (
<span data-state={hot ? "hot" : "cold"}>
{hot ? "ONLINE" : "QUIET"}
</span>
);
}
```
Shell output should remain compact and copyable.
```bash
npm run build
grep -l "data-highlighted-line" out/blog/rendering-test-card/index.html
```
Diff blocks stay plain. No transformer-only notation is allowed here.
```diff
- status: ornamental
+ status: verified
- proof: implied
+ proof: linked
```
ANSI logs are ugly by design; the renderer should still box them cleanly.
```ansi
\x1b[36m[signal]\x1b[0m route compiled
\x1b[32m[ok]\x1b[0m artifacts exported
\x1b[33m[warn]\x1b[0m rerun after CSS changes
```
Inline code should also carry language metadata: `[1,2,3].join("-"){:js}`. If that tiny token renders with the same discipline as the larger fences, the small print is doing its job.
---
## Math
### Compact proof
Inline math should sit inside the sentence without shaking the baseline: $e^{i\pi}+1=0$. The sentence remains prose; the equation is just a hard stop.
Display math gets its own clearing. The matrix below is simple, but it is enough to prove that block rendering, centering, and KaTeX styles all survived the export.
$$
\begin{bmatrix}
1 & 0 & 0 \\
0 & \cos\theta & -\sin\theta \\
0 & \sin\theta & \cos\theta
\end{bmatrix}
\begin{bmatrix}
x \\
y \\
z
\end{bmatrix}
=
\begin{bmatrix}
x \\
y\cos\theta - z\sin\theta \\
y\sin\theta + z\cos\theta
\end{bmatrix}
$$
The goal is not mathematical density. The goal is verifying that a technical note can carry the notation it needs without importing a new surface.
---
## Callouts
### Directive sweep
:::note{title="Renderer note"}
The note block marks neutral intelligence. It should accept a title, preserve block spacing, and render like part of the system rather than a pasted warning label.
This second paragraph proves container directives can carry more than one child. The gap between paragraphs matters because real notes often contain both the condition and the reason.
:::
:::tip
Use the smallest artifact that proves the path. A single post can test more of the stack than a dozen screenshots if it names the contracts directly.
:::
:::warning
Syntax that depends on an unconfigured transformer is a trap. Plain language fences are boring. Boring is stable.
:::
:::danger
Do not fix a content failure by changing the pipeline from this post. The test card is a witness. It is not a mechanic.
:::
---
## Table
### Alignment grid
| Element | Contract | Evidence |
| :--- | :---: | ---: |
| Code | highlighted line + copy chrome | built HTML |
| Math | inline + display KaTeX | `katex` |
| Footnotes | backrefs and labels | `data-footnote` |
| Callouts | four directive variants | `callout` |
| Media | body banner, no cover crop | `/banner.jpg` |
Tables are where spacing failures show up first. Left labels, centered contracts, and right-weighted evidence should all remain readable without becoming a spreadsheet costume.
---
## Quotes & Footnotes
### Chain of custody
> The first rule is to preserve the signal.
>
> > The second rule is to mark every repeater that touched it.
>
> A quote with a nested quote should feel like a transcript, not a decoration.
Footnotes carry side-channel evidence without derailing the main line.[^1] A second footnote proves numbering, backlinks, and spacing after multiple references.[^2] The prose should not pretend the notes are invisible. They are part of the record, just filed below the fold.
[^1]: The first note verifies the GFM footnote path and the styled reference marker in the paragraph.
[^2]: The second note verifies ordered footnote output and return links after export.
---
## Details
### Collapsible packet
<details>
<summary>Open the sealed appendix</summary>
Inside the disclosure sits the low-priority material: rerun instructions, caveats, and the kind of context that should be available without owning the first read. The summary line must stay keyboard reachable. The body must keep normal prose spacing. The border should match the rest of the blacksite shell.
</details>
This section is deliberately small. A details block is not a drawer for hiding bad structure. It is a pressure valve for material that belongs nearby but not inline.
---
## Media
### Banner and controls
<figure>
![Signal banner](/banner.jpg)
<figcaption>Signal banner: wide body media, uncropped by the post header.</figcaption>
</figure>
The banner belongs in the body because the card needs to prove ordinary image rendering, not cover-image cropping. The ratio is long and low. Let it stay that way. If the image fills the measure, rounds its corners, and keeps a readable caption, the media path is aligned with the rest of the archive.
Keyboard hints should remain tactile: <kbd>⌘</kbd> <kbd>K</kbd>. They are tiny controls, but tiny controls are where aesthetic systems usually leak. This one should look like hardware, not a pill from another site.
End of card. The route has now touched typography, lists, code, math, callouts, tables, quotes, footnotes, details, media, and keyboard chrome. Any future renderer change can come back here and ask the same blunt question: did the signal survive contact?

View File

@@ -3,8 +3,12 @@ import { visit } from 'unist-util-visit';
export default function calloutDirective() {
return (tree) => {
visit(tree, (node) => {
// Handle textDirective (has content) and leafDirective (no content)
if (node.type === 'textDirective' || node.type === 'leafDirective') {
// Handle textDirective, leafDirective, and containerDirective callouts
if (
node.type === 'textDirective' ||
node.type === 'leafDirective' ||
node.type === 'containerDirective'
) {
const name = node.name;
// Only handle known callout types
@@ -12,18 +16,17 @@ export default function calloutDirective() {
if (!validTypes.includes(name)) return;
// Extract attributes (e.g., title="...")
const attributes = node.attributes || [];
const attrs = attributes.map((attr) => ({
const attrs = Object.entries(node.attributes ?? {}).map(([name, value]) => ({
type: 'mdxJsxAttribute',
name: attr.name,
value: attr.value,
name,
value,
}));
// Add type attribute
attrs.push({
type: 'mdxJsxAttribute',
name: 'type',
value: { type: 'mdxFlowExpression', value: `"${name}"` },
value: name,
});
// Build children from node's children

384
lib/portfolio.ts Normal file
View File

@@ -0,0 +1,384 @@
export type Accent = "cyan" | "orange" | "green" | "violet";
export type Proof = {
label: string;
href?: string;
metric?: string;
};
export type DossierLink = {
label: string;
href: string;
};
export type Dossier = {
id: string;
title: string;
type: string;
status: string;
year: string;
role?: string;
stack: string[];
mission: string;
constraint?: string;
system: string;
result: string;
proofHeadline: string;
proof: Proof[];
links: DossierLink[];
accent: Accent;
};
export type Capability = {
index: string;
text: string;
};
export type Honor = {
title: string;
detail: string;
year: string;
};
export type Identity = {
name: string;
roleLine: string;
education: string;
site: string;
};
export type Hero = {
heading: string;
subline: string;
};
export type Publication = {
title: string;
venue: string;
year: string;
authorship: string;
doi: string;
href: string;
};
export type SkillsMatrixRow = {
category: string;
skills: string[];
};
export type Contact = {
email: string;
git: string;
linkedIn: string;
};
export const identity: Identity = {
name: "Krishna Ayyalasomayajula",
roleLine: "Founding Engineer @ CoGuide",
education: "Computational Engineering, UT Austin",
site: "krishna.ayyalasomayajula.net",
};
export const statusLine = "BUILDING AT COGUIDE";
export const hero: Hero = {
heading: "GPU pipelines. Hardened systems. Real-time infrastructure.",
subline: "Founding engineer at CoGuide.",
};
export const manifesto = "Systems that survive contact.";
export const capabilities: Capability[] = [
{ index: "01", text: "GPU pipelines, down to the kernel" },
{ index: "02", text: "Production backends from zero" },
{ index: "03", text: "Systems that survive the open internet" },
{ index: "04", text: "Data at market speed" },
{ index: "05", text: "Infrastructure, end to end" },
];
export const featuredDossier: Dossier = {
id: "coguide",
title: "CoGuide",
type: "AI instructional-insight platform",
status: "deployed",
year: "2026—",
role: "Founding Engineer / MTS",
stack: ["Rust", "AWS", "CUDA", "YOLOv8"],
mission: "Classroom audio → private coaching feedback.",
constraint: "Legacy T4 GPUs, zero to production",
system:
"Rust backend (~30k lines) on full AWS infra. Custom CUDA fusion kernels; two-stage CV pipeline at ~0.96× real-time.",
result: "10+ schools. 2× market cost efficiency; 10× vs AWS Transcribe.",
proofHeadline: "10+ schools · 10× AWS",
proof: [
{ label: "deployed in schools", metric: "10+ schools" },
{ label: "cost efficiency vs market alternatives", metric: "2×" },
{ label: "cost efficiency vs AWS Transcribe", metric: "10×" },
{ label: "hand-raise detection", metric: "~0.96× real-time" },
],
links: [
{ label: "coguide.ai", href: "https://coguide.ai" },
{
label: "CoGuide-Miniproj",
href: "https://git.cyber.ayyalasomayajula.net/marsultor/CoGuide-Miniproj",
},
],
accent: "cyan",
};
export const dossiers: Dossier[] = [
{
id: "exalock",
title: "exalock",
type: "GPU/Wayland lockscreen",
status: "WIP",
year: "2026",
stack: ["C", "C++23", "GLSL"],
mission: "GPU/Wayland lockscreen",
constraint: "raw ext-session-lock-v1",
system: "RK4 stream-function particle sim",
result: "WIP 2026 GPU/Wayland lockscreen",
proofHeadline: "RK4 particle sim",
proof: [{ label: "raw ext-session-lock-v1" }],
links: [
{
label: "marsultor/exalock",
href: "https://git.cyber.ayyalasomayajula.net/marsultor/exalock",
},
],
accent: "cyan",
},
{
id: "quant-streamer",
title: "Quant Streamer",
type: "real-time market data",
status: "closed source",
year: "2025",
stack: ["Kafka", "MQTT", "DXLink", "ClickHouse", "C++"],
mission: "real-time market data",
system: "100K datapoints/s via Kafka/MQTT/DXLink; ClickHouse; C++ LSTM + Heston/BlackScholes",
result: "100K datapoints/s via Kafka/MQTT/DXLink",
proofHeadline: "100K pts/s",
proof: [{ label: "market data throughput", metric: "100K datapoints/s" }],
links: [],
accent: "orange",
},
{
id: "mqtt-push-pipeline",
title: "MQTT Push Pipeline",
type: "infra",
status: "live since Aug 2025",
year: "2025",
stack: ["Rust", "MQTT", "RSS", "C++"],
mission: "infra pipeline for push alerts",
system: "Rust consumer sustaining 10M notifications/s benchmark",
result: "10k+ real alerts relayed",
proofHeadline: "10M notif/s",
proof: [
{ label: "notification benchmark", metric: "10M notifications/s" },
{ label: "real alerts relayed", metric: "10k+" },
],
links: [
{
label: "C++ repo",
href: "https://git.cyber.ayyalasomayajula.net/marsultor",
},
{
label: "Rust repo",
href: "https://git.cyber.ayyalasomayajula.net/marsultor",
},
{
label: "RSS repo",
href: "https://git.cyber.ayyalasomayajula.net/marsultor",
},
],
accent: "green",
},
{
id: "matrix-fs",
title: "Matrix FS",
type: "encrypted L2 storage over IPFS",
status: "closed source",
year: "—",
stack: [
"Rust",
"Tauri",
"Svelte",
"IPFS",
"AES-256-CBC",
"ChaCha20-Poly1305",
"Tor",
],
mission: "encrypted L2 storage over IPFS",
system: "Rust + Tauri/Svelte; AES-256-CBC + ChaCha20-Poly1305; Tor hidden-service ticketing",
result: "closed source encrypted L2 storage over IPFS",
proofHeadline: "dual-cipher, over Tor",
proof: [{ label: "AES-256-CBC + ChaCha20-Poly1305" }],
links: [],
accent: "violet",
},
{
id: "home-infrastructure",
title: "Home Infrastructure",
type: "infrastructure",
status: "live",
year: "—",
stack: ["Linux", "Docker/Podman", "ZFS"],
mission: "home infrastructure",
system: "3 HA servers, 40+ containers, 150TB ZFS, ~50 users",
result: "live infrastructure serving ~50 users",
proofHeadline: "150TB · 50 users",
proof: [
{ label: "HA servers", metric: "3" },
{ label: "containers", metric: "40+" },
{ label: "ZFS storage", metric: "150TB" },
{ label: "users", metric: "~50" },
],
links: [],
accent: "green",
},
{
id: "cyberpatriot-automation",
title: "CyberPatriot Automation",
type: "hardening suite",
status: "licensed",
year: "—",
stack: ["Rust", "PowerShell"],
mission: "Rust/PowerShell hardening suite",
system: "CyberPatriot Automation hardening suite",
result: "30+ pts/round; licensed to another team",
proofHeadline: "30+ pts/round",
proof: [
{ label: "points per round", metric: "30+" },
{ label: "licensed to another team" },
],
links: [],
accent: "orange",
},
{
id: "browser-security-red-team-poc",
title: "Browser-Security Red-Team PoC",
type: "educational",
status: "educational",
year: "—",
stack: ["TypeScript", "Bun", "Playwright"],
mission: "educational browser-security red-team PoC",
system: "TS/Bun + Playwright stealth defeating bot detection",
result: "educational PoC defeating bot detection",
proofHeadline: "detection defeated",
proof: [{ label: "Playwright stealth defeating bot detection" }],
links: [
{
label: "marsultor/linkedin-bot",
href: "https://git.cyber.ayyalasomayajula.net/marsultor/linkedin-bot",
},
],
accent: "violet",
},
{
id: "hardened-ssh-portfolio-server",
title: "Hardened SSH Portfolio Server",
type: "live security PoC",
status: "live",
year: "—",
stack: ["C++", "FTXUI", "SSH"],
mission: "live security PoC",
system: "C++ FTXUI over SSH",
result: "survived 1M+ attacks from 100 countries over 18 months, zero compromise",
proofHeadline: "1M+ attacks · 0 compromise",
proof: [
{ label: "attacks survived", metric: "1M+" },
{ label: "countries", metric: "100" },
{ label: "duration", metric: "18 months" },
{ label: "compromise count", metric: "zero" },
],
links: [],
accent: "cyan",
},
];
export const publication: Publication = {
title:
"Rule-based Tensor Mutations Embedded within LLMs for Low-Cost Mathematical Computation",
venue: "TechRxiv preprint (IEEE)",
year: "2025",
authorship: "sole author",
doi: "10.36227/techrxiv.175393256.68603080/v1",
href: "https://doi.org/10.36227/techrxiv.175393256.68603080/v1",
};
export const honors: Honor[] = [
{
title: "Lockheed Martin Cyber Quest 2nd place",
detail: "team captain, ~30% of team score",
year: "2025",
},
{ title: "CyberPatriot State Gold", detail: "", year: "2025" },
{ title: "NSA Codebreaker", detail: "5 tasks", year: "2024" },
{ title: "Hack The Box", detail: "peak global rank 564", year: "" },
{ title: "National Merit Commendation", detail: "", year: "" },
];
export const skillsMatrix: SkillsMatrixRow[] = [
{
category: "Languages",
skills: [
"Rust async/unsafe/FFI",
"C",
"C++23",
"Python",
"TypeScript",
"GLSL",
"SQL",
"Bash",
"Java",
],
},
{
category: "Systems & Infra",
skills: [
"Linux",
"Docker/Podman",
"WireGuard",
"Nginx/Traefik",
"PostgreSQL",
"ClickHouse",
"Redis",
"Kafka",
"MQTT",
"gRPC",
"ZFS",
],
},
{
category: "Scientific Computing",
skills: [
"NumPy/SciPy/JAX",
"Eigen",
"BLAS/LAPACK",
"CUDA/cuBLAS",
"ODE solvers",
"PyTorch",
],
},
{
category: "Security",
skills: [
"reverse engineering",
"binary exploitation",
"network forensics",
"eBPF/seccomp/AppArmor",
"red/blue tooling",
],
},
{
category: "Frontend & Embedded",
skills: ["Next.js/React", "Svelte", "Tauri", "FTXUI", "STM32/ESP32"],
},
];
export const contact: Contact = {
email: "krishna@ayyalasomayajula.net",
git: "https://git.cyber.ayyalasomayajula.net/marsultor",
linkedIn: "https://www.linkedin.com/in/krishna-ayy/",
};

View File

@@ -2,6 +2,9 @@ import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { cache } from 'react'
import { tagToSlug } from '@/lib/tags'
export { tagToSlug } from '@/lib/tags'
const postsDirectory = path.join(process.cwd(), 'content/posts')
@@ -61,18 +64,6 @@ export const normalizePostData = (
readingTime,
})
export const tagToSlug = (tag: string): string =>
tag
.normalize('NFKD')
.toLowerCase()
.replace(/[\u0300-\u036f]/g, '')
.replace(/\+/g, ' plus ')
.replace(/#/g, ' sharp ')
.replace(/[\\/]+/g, ' ')
.replace(/[^\p{L}\p{N}]+/gu, '-')
.replace(/-{2,}/g, '-')
.replace(/^-|-$/g, '')
const getMdxFiles = cache(async () => {
try {
const files = await fs.promises.readdir(postsDirectory)

11
lib/tags.ts Normal file
View File

@@ -0,0 +1,11 @@
export const tagToSlug = (tag: string): string =>
tag
.normalize('NFKD')
.toLowerCase()
.replace(/[\u0300-\u036f]/g, '')
.replace(/\+/g, ' plus ')
.replace(/#/g, ' sharp ')
.replace(/[\\/]+/g, ' ')
.replace(/[^\p{L}\p{N}]+/gu, '-')
.replace(/-{2,}/g, '-')
.replace(/^-|-$/g, '')

View File

@@ -37,11 +37,11 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
</p>
),
blockquote: ({ children }) => (
<blockquote className="my-6 border-l-4 border-accent/40 bg-surface/60 py-1 pl-5 pr-4 italic text-ink-soft">
<blockquote className="my-6 border-l-4 border-accent/60 bg-surface/60 py-1 pl-5 pr-4 text-ink-soft">
{children}
</blockquote>
),
hr: () => <hr className="my-8 border-border" />,
hr: () => <hr className="my-12 border-border" />,
a: ({ href, children, ...props }) => {
const isExternal = typeof href === 'string' && (href.startsWith('http://') || href.startsWith('https://'))
return (
@@ -80,14 +80,14 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
<img
src={src}
alt={alt ?? ''}
className="mx-auto my-8 h-auto max-h-[80vh] w-auto max-w-full rounded-2xl border border-border bg-surface object-contain shadow-card"
className="mx-auto my-8 h-auto max-h-[80vh] w-auto max-w-full rounded-lg border border-border bg-surface object-contain shadow-card"
loading="lazy"
decoding="async"
{...rest}
/>
),
table: ({ children }) => (
<div className="my-8 overflow-x-auto rounded-xl border border-border bg-canvas">
<div className="my-8 overflow-x-auto rounded-lg border border-border bg-canvas">
<table className="w-full min-w-[40rem] border-collapse text-sm">
{children}
</table>
@@ -113,7 +113,7 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
),
// Collapsible sections
details: (props) => <details className="my-6 rounded-xl border border-border bg-surface/70 p-4 shadow-card" {...props} />,
details: (props) => <details className="my-6 rounded-lg border border-border bg-surface/70 p-4 shadow-card" {...props} />,
summary: (props) => <summary className="cursor-pointer font-semibold text-ink" {...props} />,
}
}

474
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"@next/mdx": "^16.2.6",
"@rehype-pretty/transformers": "npm:@jsr/rehype-pretty__transformers@^0.13.4",
"@wrksz/themes": "^0.9.3",
"cmdk": "^1.1.1",
"gray-matter": "^4.0.3",
"katex": "^0.17.0",
"motion": "12.18.1",
@@ -1346,6 +1347,318 @@
"node": ">=12.4.0"
}
},
"node_modules/@radix-ui/primitive": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz",
"integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==",
"license": "MIT"
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz",
"integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-context": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz",
"integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog": {
"version": "1.1.19",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.19.tgz",
"integrity": "sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.5",
"@radix-ui/react-compose-refs": "1.1.3",
"@radix-ui/react-context": "1.2.0",
"@radix-ui/react-dismissable-layer": "1.1.15",
"@radix-ui/react-focus-guards": "1.1.4",
"@radix-ui/react-focus-scope": "1.1.12",
"@radix-ui/react-id": "1.1.2",
"@radix-ui/react-portal": "1.1.13",
"@radix-ui/react-presence": "1.1.7",
"@radix-ui/react-primitive": "2.1.7",
"@radix-ui/react-slot": "1.3.0",
"@radix-ui/react-use-controllable-state": "1.2.3",
"aria-hidden": "^1.2.4",
"react-remove-scroll": "^2.7.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz",
"integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.5",
"@radix-ui/react-compose-refs": "1.1.3",
"@radix-ui/react-primitive": "2.1.7",
"@radix-ui/react-use-callback-ref": "1.1.2",
"@radix-ui/react-use-effect-event": "0.0.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-focus-guards": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz",
"integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-focus-scope": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz",
"integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.3",
"@radix-ui/react-primitive": "2.1.7",
"@radix-ui/react-use-callback-ref": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-id": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz",
"integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-portal": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz",
"integrity": "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.7",
"@radix-ui/react-use-layout-effect": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-presence": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz",
"integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-primitive": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz",
"integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.3.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz",
"integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.3"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz",
"integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-controllable-state": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz",
"integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-effect-event": "0.0.3",
"@radix-ui/react-use-layout-effect": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-effect-event": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz",
"integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz",
"integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@rehype-pretty/transformers": {
"name": "@jsr/rehype-pretty__transformers",
"version": "0.13.4",
@@ -1870,7 +2183,7 @@
},
"node_modules/@types/react": {
"version": "19.2.15",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -1878,7 +2191,7 @@
},
"node_modules/@types/react-dom": {
"version": "19.2.3",
"dev": true,
"devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.2.0"
@@ -2562,6 +2875,18 @@
"sprintf-js": "~1.0.2"
}
},
"node_modules/aria-hidden": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
"integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/aria-query": {
"version": "5.3.2",
"dev": true,
@@ -2976,6 +3301,22 @@
"version": "0.0.1",
"license": "MIT"
},
"node_modules/cmdk": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz",
"integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-id": "^1.1.0",
"@radix-ui/react-primitive": "^2.0.2"
},
"peerDependencies": {
"react": "^18 || ^19 || ^19.0.0-rc",
"react-dom": "^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/collapse-white-space": {
"version": "2.1.0",
"license": "MIT",
@@ -3040,7 +3381,7 @@
},
"node_modules/csstype": {
"version": "3.2.3",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
@@ -3174,6 +3515,12 @@
"node": ">=8"
}
},
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
"node_modules/devlop": {
"version": "1.1.0",
"license": "MIT",
@@ -4159,6 +4506,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-nonce": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"dev": true,
@@ -7225,6 +7581,75 @@
"dev": true,
"license": "MIT"
},
"node_modules/react-remove-scroll": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
"integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
"license": "MIT",
"dependencies": {
"react-remove-scroll-bar": "^2.3.7",
"react-style-singleton": "^2.2.3",
"tslib": "^2.1.0",
"use-callback-ref": "^1.3.3",
"use-sidecar": "^1.1.3"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-remove-scroll-bar": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
"integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
"license": "MIT",
"dependencies": {
"react-style-singleton": "^2.2.2",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-style-singleton": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
"integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
"license": "MIT",
"dependencies": {
"get-nonce": "^1.0.0",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/recma-build-jsx": {
"version": "1.0.0",
"license": "MIT",
@@ -8653,6 +9078,49 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-callback-ref": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
"integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-sidecar": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
"integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
"license": "MIT",
"dependencies": {
"detect-node-es": "^1.1.0",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/vfile": {
"version": "6.0.3",
"license": "MIT",

View File

@@ -14,6 +14,7 @@
"@next/mdx": "^16.2.6",
"@rehype-pretty/transformers": "npm:@jsr/rehype-pretty__transformers@^0.13.4",
"@wrksz/themes": "^0.9.3",
"cmdk": "^1.1.1",
"gray-matter": "^4.0.3",
"katex": "^0.17.0",
"motion": "12.18.1",

File diff suppressed because it is too large Load Diff

BIN
public/banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -1 +0,0 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 391 B

View File

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

95
public/og-image.svg Normal file
View File

@@ -0,0 +1,95 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630">
<rect width="1200" height="630" fill="#fff"/>
<g transform="translate(400,315)">
<!-- Outer circle -->
<circle cx="0" cy="0" r="220" fill="none" stroke="#000" stroke-width="2"/>
<!-- Triangle up -->
<polygon points="0,-220 190.5,110 -190.5,110" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Triangle down -->
<polygon points="0,220 -190.5,-110 190.5,-110" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Circle inscribed -->
<circle cx="0" cy="0" r="110" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Hexagon -->
<polygon points="95,-165 190.5,-55 190.5,55 95,165 -95,165 -190.5,55 -190.5,-55 -95,-165"
fill="none" stroke="#000" stroke-width="1"/>
<!-- Inner circle -->
<circle cx="0" cy="0" r="55" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Inner triangle up -->
<polygon points="0,-55 47.6,27.5 -47.6,27.5" fill="none" stroke="#000" stroke-width="1"/>
<!-- Inner triangle down -->
<polygon points="0,55 -47.6,-27.5 47.6,-27.5" fill="none" stroke="#000" stroke-width="1"/>
<!-- Small circle -->
<circle cx="0" cy="0" r="27" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Tiny triangles -->
<polygon points="0,-27 23.4,13.5 -23.4,13.5" fill="none" stroke="#000" stroke-width="0.8"/>
<polygon points="0,27 -23.4,-13.5 23.4,-13.5" fill="none" stroke="#000" stroke-width="0.8"/>
<!-- Center dot -->
<circle cx="0" cy="0" r="4" fill="#000"/>
<!-- Radial lines -->
<g stroke="#000" stroke-width="0.5" opacity="0.4">
<line x1="0" y1="-4" x2="0" y2="-220"/>
<line x1="0" y1="4" x2="0" y2="220"/>
<line x1="0" y1="0" x2="190.5" y2="110"/>
<line x1="0" y1="0" x2="-190.5" y2="110"/>
<line x1="0" y1="0" x2="190.5" y2="-110"/>
<line x1="0" y1="0" x2="-190.5" y2="-110"/>
</g>
<!-- Vertex dots -->
<g fill="#fff" stroke="#000" stroke-width="1.2">
<circle cx="0" cy="-220" r="5"/>
<circle cx="190.5" cy="110" r="5"/>
<circle cx="-190.5" cy="110" r="5"/>
</g>
<g fill="#000">
<circle cx="0" cy="-220" r="2"/>
<circle cx="190.5" cy="110" r="2"/>
<circle cx="-190.5" cy="110" r="2"/>
</g>
<!-- Dashed outer ring -->
<circle cx="0" cy="0" r="232" fill="none" stroke="#000" stroke-width="0.6" stroke-dasharray="2 6"/>
<!-- Arc marks -->
<g stroke="#000" stroke-width="1.2" stroke-linecap="round" fill="none">
<path d="M -6,-226 A 6,6 0 0,1 6,-226"/>
<path d="M -6,226 A 6,6 0 0,0 6,226"/>
<path d="M -226,-6 A 6,6 0 0,0 -226,6"/>
<path d="M 226,-6 A 6,6 0 0,1 226,6"/>
</g>
<!-- Cardinal dots -->
<g fill="#000">
<circle cx="0" cy="-232" r="1.5"/>
<circle cx="0" cy="232" r="1.5"/>
<circle cx="-232" cy="0" r="1.5"/>
<circle cx="232" cy="0" r="1.5"/>
</g>
<!-- Corner marks -->
<g stroke="#000" stroke-width="1" stroke-linecap="round" opacity="0.5">
<line x1="-164" y1="-164" x2="-152" y2="-152"/>
<line x1="164" y1="-164" x2="152" y2="-152"/>
<line x1="-164" y1="164" x2="-152" y2="152"/>
<line x1="164" y1="164" x2="152" y2="152"/>
</g>
</g>
<!-- Text on the right -->
<text x="750" y="300" font-family="Georgia, serif" font-size="72" fill="#000" letter-spacing="-1">Krishna</text>
<text x="750" y="360" font-family="monospace" font-size="22" fill="#000" opacity="0.6" letter-spacing="2">JOURNAL</text>
<line x1="750" y1="390" x2="850" y2="390" stroke="#000" stroke-width="1" opacity="0.4"/>
<text x="750" y="425" font-family="monospace" font-size="14" fill="#000" opacity="0.35" letter-spacing="1">CODE &amp; MATH</text>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
public/og.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

103
public/ouroboros.svg Normal file
View File

@@ -0,0 +1,103 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" width="500" height="500">
<rect width="500" height="500" fill="#fff"/>
<g transform="translate(250,250)">
<!-- Outer circle -->
<circle cx="0" cy="0" r="220" fill="none" stroke="#000" stroke-width="2"/>
<!-- Triangle inscribed in outer circle (pointing up) -->
<polygon points="0,-220 190.5,110 -190.5,110" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Triangle inscribed (pointing down) -->
<polygon points="0,220 -190.5,-110 190.5,-110" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Circle inscribed in the star (radius ~110) -->
<circle cx="0" cy="0" r="110" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Hexagon from the star intersections -->
<polygon points="95,-165 190.5,-55 190.5,55 95,165 -95,165 -190.5,55 -190.5,-55 -95,-165"
fill="none" stroke="#000" stroke-width="1"/>
<!-- Inner circle -->
<circle cx="0" cy="0" r="55" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Triangle inscribed in inner circle (up) -->
<polygon points="0,-55 47.6,27.5 -47.6,27.5" fill="none" stroke="#000" stroke-width="1"/>
<!-- Triangle inscribed in inner circle (down) -->
<polygon points="0,55 -47.6,-27.5 47.6,-27.5" fill="none" stroke="#000" stroke-width="1"/>
<!-- Small circle at center -->
<circle cx="0" cy="0" r="27" fill="none" stroke="#000" stroke-width="1.5"/>
<!-- Tiny triangle up -->
<polygon points="0,-27 23.4,13.5 -23.4,13.5" fill="none" stroke="#000" stroke-width="0.8"/>
<!-- Tiny triangle down -->
<polygon points="0,27 -23.4,-13.5 23.4,-13.5" fill="none" stroke="#000" stroke-width="0.8"/>
<!-- Center dot -->
<circle cx="0" cy="0" r="4" fill="#000"/>
<!-- Lines from center through all vertices -->
<g stroke="#000" stroke-width="0.5" opacity="0.4">
<!-- Through outer triangle vertices -->
<line x1="0" y1="-4" x2="0" y2="-220"/>
<line x1="0" y1="4" x2="0" y2="220"/>
<line x1="0" y1="0" x2="190.5" y2="110"/>
<line x1="0" y1="0" x2="-190.5" y2="110"/>
<line x1="0" y1="0" x2="190.5" y2="-110"/>
<line x1="0" y1="0" x2="-190.5" y2="-110"/>
</g>
<!-- Small circles at outer triangle vertices -->
<g fill="#fff" stroke="#000" stroke-width="1.2">
<circle cx="0" cy="-220" r="5"/>
<circle cx="190.5" cy="110" r="5"/>
<circle cx="-190.5" cy="110" r="5"/>
</g>
<g fill="#000">
<circle cx="0" cy="-220" r="2"/>
<circle cx="190.5" cy="110" r="2"/>
<circle cx="-190.5" cy="110" r="2"/>
</g>
<!-- Small circles at inner triangle vertices -->
<g fill="#fff" stroke="#000" stroke-width="1">
<circle cx="0" cy="-55" r="3.5"/>
<circle cx="47.6" cy="27.5" r="3.5"/>
<circle cx="-47.6" cy="27.5" r="3.5"/>
<circle cx="0" cy="55" r="3.5"/>
<circle cx="47.6" cy="-27.5" r="3.5"/>
<circle cx="-47.6" cy="-27.5" r="3.5"/>
</g>
<!-- Dashed outermost ring -->
<circle cx="0" cy="0" r="232" fill="none" stroke="#000" stroke-width="0.6" stroke-dasharray="2 6"/>
<!-- Arc marks at cardinal points on outer ring -->
<g stroke="#000" stroke-width="1.2" stroke-linecap="round" fill="none">
<path d="M -6,-226 A 6,6 0 0,1 6,-226"/>
<path d="M -6,226 A 6,6 0 0,0 6,226"/>
<path d="M -226,-6 A 6,6 0 0,0 -226,6"/>
<path d="M 226,-6 A 6,6 0 0,1 226,6"/>
</g>
<!-- Tiny dots at cardinal points -->
<g fill="#000">
<circle cx="0" cy="-232" r="1.5"/>
<circle cx="0" cy="232" r="1.5"/>
<circle cx="-232" cy="0" r="1.5"/>
<circle cx="232" cy="0" r="1.5"/>
</g>
<!-- Corner marks -->
<g stroke="#000" stroke-width="1" stroke-linecap="round" opacity="0.5">
<line x1="-164" y1="-164" x2="-152" y2="-152"/>
<line x1="164" y1="-164" x2="152" y2="-152"/>
<line x1="-164" y1="164" x2="-152" y2="152"/>
<line x1="164" y1="164" x2="152" y2="152"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,17 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 540" role="img" aria-labelledby="title desc">
<title id="title">Starter design diagram</title>
<desc id="desc">Three connected nodes labeled design, content, and build.</desc>
<rect width="900" height="540" rx="36" fill="#f8fafc"/>
<path d="M250 270h400" stroke="#94a3b8" stroke-width="12" stroke-linecap="round"/>
<path d="M450 185v170" stroke="#94a3b8" stroke-width="12" stroke-linecap="round"/>
<g font-family="ui-sans-serif, system-ui, sans-serif" font-weight="700" text-anchor="middle">
<circle cx="250" cy="270" r="98" fill="#dbeafe" stroke="#2563eb" stroke-width="8"/>
<text x="250" y="282" fill="#1e3a8a" font-size="34">Design</text>
<circle cx="650" cy="270" r="98" fill="#dcfce7" stroke="#16a34a" stroke-width="8"/>
<text x="650" y="282" fill="#14532d" font-size="34">Content</text>
<circle cx="450" cy="150" r="88" fill="#fef3c7" stroke="#d97706" stroke-width="8"/>
<text x="450" y="162" fill="#78350f" font-size="32">Build</text>
<circle cx="450" cy="390" r="88" fill="#fce7f3" stroke="#db2777" stroke-width="8"/>
<text x="450" y="402" fill="#831843" font-size="32">Ship</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,26 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" role="img" aria-labelledby="title desc">
<title id="title">Starter MDX showcase cover</title>
<desc id="desc">A small abstract gradient cover with cards representing math, code, and design.</desc>
<defs>
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#7c3aed"/>
<stop offset="0.55" stop-color="#2563eb"/>
<stop offset="1" stop-color="#14b8a6"/>
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="18" stdDeviation="20" flood-color="#0f172a" flood-opacity="0.25"/>
</filter>
</defs>
<rect width="1200" height="630" rx="42" fill="url(#bg)"/>
<circle cx="1030" cy="120" r="150" fill="#ffffff" opacity="0.16"/>
<circle cx="130" cy="560" r="210" fill="#ffffff" opacity="0.12"/>
<g filter="url(#shadow)">
<rect x="155" y="145" width="890" height="340" rx="32" fill="#ffffff" opacity="0.92"/>
<rect x="210" y="205" width="260" height="42" rx="12" fill="#111827" opacity="0.9"/>
<rect x="210" y="275" width="330" height="28" rx="10" fill="#6366f1" opacity="0.85"/>
<rect x="210" y="330" width="260" height="28" rx="10" fill="#0f766e" opacity="0.75"/>
<text x="615" y="255" fill="#111827" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="46" font-weight="700">MDX</text>
<text x="615" y="325" fill="#334155" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="34">$E=mc²</text>
<text x="615" y="390" fill="#334155" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="30">code · links · tables</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 128 B

View File

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

Before

Width:  |  Height:  |  Size: 385 B