diff --git a/BLOG_REWRITE_PLAN.md b/BLOG_REWRITE_PLAN.md deleted file mode 100644 index c5cfe60..0000000 --- a/BLOG_REWRITE_PLAN.md +++ /dev/null @@ -1,1099 +0,0 @@ -# Personal Blog Rewrite Plan - -## Goal - -Replace the existing portfolio with a statically generated personal blog that still helps with hiring, but earns that trust through writing, project essays, and technical evidence instead of a conventional portfolio timeline. - -The site should feel current, minimal, and carefully made. It should not imitate old plain-HTML blogs, but it should learn from why many respected technical blogs work: durable URLs, direct navigation, readable typography, fast pages, RSS, and content that carries the authority. - -This plan incorporates the rendered review of 100 CS/technical blogs and a 10-lens review pass covering content, visual design, mobile ergonomics, accessibility, performance, information architecture, implementation risk, hiring signal, typography, and content migration. - -## Inputs - -### Existing Repo - -The current repo is a custom TypeScript/Webpack single-page portfolio. The strongest reusable material is in: - -- `src/data/projects/*.ts`: project titles, date strings, summaries, longer paragraphs, media references, external links -- `src/data/media`: screenshots, posters, videos, CV, BSc/MSc thesis PDFs -- `static/no-change`: favicons, `robots.txt`, 404 page, Open Graph image - -The current implementation should be replaced rather than evolved. It depends on JavaScript for core content and is organized around timeline cards, expanded details, image viewers, theme controls, and decorative UI. The new site should ship little to no JavaScript and render normal pages as static HTML. - -### Rendered Blog Research - -I installed Playwright and rendered 100 technical/personal CS blogs at desktop and phone widths. The artifacts are in: - -- `/tmp/codex-blog-design-pass/index.html` -- `/tmp/codex-blog-design-pass/summary.json` -- `/tmp/codex-blog-design-pass/summary.csv` - -Useful metrics from the pass: - -- Median desktop paragraph width: about `662px` -- Median mobile paragraph width: about `350px` -- Median paragraph font size: `16px` -- 73 of 100 exposed RSS or Atom -- Most strong personal blogs used very few card-like UI elements -- Several famous blogs had mobile overflow, which this site should explicitly avoid -- The best visual references used media as explanation, not decoration - -Representative references: - -- Writing-first archive: [Simon Willison](https://simonwillison.net/), [Marc Brooker](https://brooker.co.za/blog/), [Brandur](https://brandur.org/articles), [Armin Ronacher](https://lucumr.pocoo.org/), [Phil Eaton](https://notes.eatonphil.com/) -- Minimal but current personal sites: [Tom MacWright](https://macwright.com/), [Thorsten Ball](https://thorstenball.com/), [matklad](https://matklad.github.io/), [BurntSushi](https://burntsushi.net/) -- Technical visual writing: [Red Blob Games](https://www.redblobgames.com/), [Distill](https://distill.pub/), [Bret Victor](https://worrydream.com/), [Jay Alammar](https://jalammar.github.io/), [fasterthanli.me](https://fasterthanli.me/) -- Strong code-heavy writing: [Ned Batchelder](https://nedbatchelder.com/blog/), [Dave Cheney](https://dave.cheney.net/), [Eli Bendersky](https://eli.thegreenplace.net/), [Laurence Tratt](https://tratt.net/laurie/blog/) - -## Product Direction - -The site should be a blog first and a hiring surface second. - -The homepage should not say "hire me" first. It should establish a serious technical writer and builder through the quality of the archive. Recruiters should still quickly find the About page, CV, GitHub, LinkedIn, contact details, and the most relevant project essays. - -The tone should be: - -- Text-first -- Calm -- Precise -- Modern -- Functional -- Slightly editorial -- Never flashy -- Never intentionally dated - -Avoid: - -- Purple or blue-purple gradients -- Glowing blobs -- Timeline cards -- Hero illustrations -- Glassmorphism -- Decorative animations -- Dense nav chrome -- Flat boxes pretending to be a "retro" blog -- Landing-page copy -- Skill grids, logo walls, metric blocks, or resume-card layouts - -## Key Decisions - -- Canonical essays live under `/writing/[slug]/`, including project essays. -- `/projects/` is a complete text index of built work with stable anchors; it is not a second set of duplicate project detail pages. -- Tags are Phase 1 metadata but tag pages can wait. -- The visual system uses a refined sans-serif voice, not Times New Roman and not browser defaults. -- Normal pages ship no client JavaScript. Any exception must be post-specific and justified. -- Images that need optimization live in `src/assets` and render through Astro image tooling. `public/` is for stable raw files such as PDFs, favicons, legacy assets, and videos/downloads. -- The deployment plan must match the existing Forgejo workflow that builds `dist/` and rsyncs it to `/pages/schmelczer-dev`. - -## Design Principles - -### Function Includes Reading - -Minimal design is not an excuse for weak typography. The site should feel better on a phone than most personal blogs. - -Core reading requirements: - -- No horizontal scrolling at `320px`, `390px`, or `430px` -- Prose width around `36rem-42rem` on desktop -- Mobile content width: `min(100% - 2 * var(--gutter), var(--measure))` -- Mobile gutter: `clamp(16px, 5vw, 24px)` -- Body font size: `17px` on phones, `17px-18px` on larger screens -- Prose line height: `1.62-1.68` -- Paragraph spacing tuned for long reading, not marketing skimming -- Code blocks scroll horizontally only inside the code block -- Figures, tables, long links, and code never force page overflow -- Header nav, footer links, RSS/contact links, project links, footnotes, backlinks, and pagination use a `44px` minimum hit area or equivalent padding - -### Typography - -Use a refined sans-serif as the main voice. Do not use Times New Roman. Do not make the site look like default browser HTML. - -Recommended direction: - -- Body and UI: `Source Sans 3` first, then `Inter`, then the system sans stack -- Code: `IBM Plex Mono`, `JetBrains Mono`, or a careful system monospace stack -- No custom display font -- No negative letter-spacing -- Use font weights sparingly: regular, medium, semibold - -Default recommendation: - -```css -font-family: - 'Source Sans 3', - Inter, - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - sans-serif; -``` - -Font loading policy: - -- Start with the system stack or one self-hosted variable WOFF2 for `Source Sans 3` -- Use only regular-to-semibold weights -- Use `font-display: swap` -- Preload only the single above-fold face if measurement proves it helps -- Avoid third-party font requests - -Editorial type targets: - -- Body: `17px`, line-height `1.65` -- Article `h1`: about `2rem` on phones, restrained on desktop, line-height `1.15-1.2` -- `h2`: line-height `1.2-1.25`, with generous space above -- Metadata and captions: `14px-15px`, line-height `1.35-1.45` -- Code: about `.88em`, line-height `1.55` - -### Color - -Use a quiet near-white background and near-black text. Let links and small metadata carry the color. - -Candidate palette: - -- Background: `#fbfaf7` -- Text: `#181817` -- Muted text: `#6b6860` -- Rule/border: `#d9d5ca` -- Strong rule/focus: `#7f7668` -- Link: `#285f74` -- Link hover: `#8a4b2f` -- Code background: `#efede6` -- Callout background: `#f4f1e8` - -The palette should feel warm and literary without becoming beige-heavy. The teal link is the only real accent. Purple is out. - -Low-contrast tokens such as `--rule`, `--code-bg`, and `--callout-bg` must not be the only focus, active, or error indicator. - -### Link Treatment - -Prose links should look like links. - -```css -.prose a { - color: var(--link); - text-decoration-line: underline; - text-decoration-thickness: 0.08em; - text-underline-offset: 0.18em; -} -``` - -Hover can shift color. Keyboard focus must use a visible `2px+` outline with offset. Do not remove outlines. - -### Layout - -Use a simple centered layout with a narrow content column and a slightly wider media column. - -Desktop: - -- Page max width around `72rem` -- Main article column around `40rem` -- Figures can break out to `56rem-64rem` when useful -- Header aligned with content, not full-width heavy navigation -- Footer minimal - -Mobile: - -- Single column -- Header wraps naturally; no menu JavaScript -- Nav uses `flex-wrap: wrap`, small gaps, and no pill labels that can force overflow -- Site title and nav may become two short rows -- Article title should not dominate the whole first viewport -- First paragraph should appear without excessive scrolling -- Metadata should be visible but quiet - -### UI Shape - -Use rules, whitespace, indentation, and typographic hierarchy instead of cards. - -Default primitives: - -- Thin horizontal rules -- Muted metadata -- Hanging dates on desktop where appropriate -- Indented summaries -- Captions -- Narrow dividers -- Text rows for archive/project entries - -Acceptable framed elements: - -- Code blocks -- Tables -- Figures with captions -- Callouts for short notes -- A small "At a glance" metadata block inside project essays when it is content, not decoration - -Avoid using cards for every post preview. Archive entries should mostly be text rows with date, title, tags, and one sentence. - -### Figures, Tables, and Code - -Code blocks: - -- `overflow-x: auto` -- `max-width: 100%` -- `-webkit-overflow-scrolling: touch` -- No wrapping by default -- Mobile code font around `13px-14px` -- `padding: 1rem` -- `border: 1px solid var(--rule)` -- `border-radius: 6px` -- No box shadow - -Tables: - -- Small tables stay full width -- Wide tables live inside an overflow container -- Use tabular numerals where useful -- Use subtle row rules -- Avoid zebra striping unless the data genuinely needs it - -Figures: - -- `margin-block: 2rem` -- Captions in muted text at `14px-15px` -- `img`, `video`, `canvas`, and `svg` use `max-width: 100%; height: auto` -- Optional `border: 1px solid var(--rule)` for screenshots -- Wide figures only above tablet widths -- Avoid `width: 100vw`; use `max-inline-size: min(100%, var(--measure-wide))` - -### Media - -Media should be evidence. - -Good uses: - -- A screenshot showing the UI state being discussed -- A short video demonstrating an engine, simulation, or interaction -- A diagram that explains architecture or data flow -- A small inline image that makes a project concrete - -Bad uses: - -- Decorative thumbnails for every post -- Hero images -- Cropped atmospheric images -- Large videos above the fold -- Autoplay - -For project posts, use one strong media item near the top only when it immediately clarifies the subject. Prefer "evidence figure" or "lead figure" language, not "hero." - -Videos: - -- Never load videos in archive/listing pages -- Use posters -- Set explicit dimensions -- Use `preload="metadata"` or `preload="none"` -- Provide captions, transcript, or a text summary when the video carries meaning -- Compress large MP4/WebM files before publishing -- Set a per-page media budget before launch - -## Information Architecture - -### Routes - -```text -/ -/writing/ -/writing/[slug]/ -/projects/ -/about/ -/rss.xml -/sitemap-index.xml -``` - -Optional later: - -```text -/notes/ -/tags/[tag]/ -/now/ -``` - -Project essays are canonical writing posts. The project index links to those posts and to demos/source/packages/papers. Do not create duplicate `/projects/[slug]/` pages in the first version; use stable anchors such as `/projects/#ad-astra` for smaller items if needed. - -### Navigation - -Persistent header: - -- `Writing` -- `Projects` -- `About` -- `RSS` - -Footer: - -- Email -- CV -- GitHub -- LinkedIn -- RSS - -Keep the header compact and text-only. Put dense hiring links in the About page and footer rather than in the primary nav. - -### Homepage - -Purpose: establish an editorial surface first, then make the best built work easy to find. - -Structure: - -1. Compact intro -2. Latest writing -3. Selected project essays -4. Selected technical work -5. Footer with RSS/contact links - -Example intro direction: - -> I'm Andras Schmelczer. I write about building software systems, AI deployment, graphics, simulations, and tools. - -No large hero. No headshot on the homepage unless it is tiny and secondary. Avoid badges, thumbnails, big feature blocks, and ranking language. "Selected" should be a quiet editorial choice, not a portfolio showcase. - -### Writing Index - -Chronological archive with tasteful grouping. - -Rules: - -- Group by year -- Newest first -- Show all posts -- Tags are inline metadata -- Tag pages can wait, but tags must exist in frontmatter from Phase 1 -- Use "selected" markers very sparingly, if at all - -Each entry: - -- Date -- Title -- One-sentence summary -- Tags - -Avoid card grids. A reader should be able to scan the archive quickly on a phone. - -### Project Index - -This is not the old portfolio timeline. It is an index of things built. - -Groups: - -- Selected projects -- Older and smaller projects - -Each row: - -- Name -- Problem, constraint, or essay angle -- Year or period -- Quiet technology metadata -- Primary link to essay when one exists -- Secondary links: demo, source, package, paper, video - -Use technologies as evidence, not as the lead. Every legacy project should be accounted for either as a row or as part of a merged row, even when it does not deserve a full essay. - -### About - -This is the hiring-focused page. - -Sections: - -- Short bio -- Quick facts: target role/domain, location/remote preference, availability if relevant, email, CV, GitHub, LinkedIn -- Best starting points: 3-5 strongest essays -- What I work on -- Selected technical work -- Technical strengths -- Experience/education summary - -This page can be more direct: - -- MSc in Computer Science -- Professional software engineering experience -- AI/ML systems, large-scale architecture, graphics, visualization, embedded projects -- Preference for complex, multidisciplinary systems - -Keep it typographic and compact. No skill grids, logo walls, metric blocks, or resume cards. - -## Content Plan - -### Editorial Standard - -The first version should not read like project cards converted to Markdown. The project material should become technical essays, some of which happen to come from past projects. - -Every substantial post should answer: - -- Does this teach something to a technical reader who is not evaluating me? -- Does the title make a specific promise? -- Is there a thesis, reader takeaway, and evidence? -- Is there at least one concrete constraint, number, diagram, code excerpt, or failure? -- Is the project age clearly framed? -- Are technologies evidence rather than the point? -- Would the post still be worth reading if my name and CV links disappeared? - -### Initial Essays - -Use essay angles rather than project labels: - -1. `Designing an ML deployment API around best practices` from GreatAI -2. `Tile-based optimization for 2D SDF ray tracing` from SDF-2D -3. `Shared simulation code in a mobile multiplayer browser game` from decla.red -4. `A 50 FPS game engine on an ATtiny85` from ad_astra -5. `Syncing state with immutable tries` from life-towers -6. `Graph models for a real-time cooling simulation` from the nuclear simulator and graph editor - -Best recruiter starting set: - -- GreatAI -- SDF-2D -- life-towers or nuclear-simulation - -Secondary project rows or shorter notes: - -- City simulation in Unity -- Forex prediction experiment -- My Notes Android app -- Platform game in C/SDL -- LED music visualizer -- Photo site generator -- Avoid -- Photo colour grader - -Add 2-3 non-project technical notes shortly after launch so the site does not feel like a portfolio in blog clothing. Good candidates: - -- Lessons from deploying ML systems -- Notes on mobile graphics performance -- What makes old side projects still worth writing about -- A debugging or architecture retrospective from recent work - -### Date Framing - -Separate project dates from publication dates. - -- `date`: publication date for RSS and archive ordering -- `updated`: material revision date -- `projectPeriod`: human text such as `Autumn-Winter 2020` -- `sortDate`: approximate ISO date for project ordering - -Old projects should be framed as current reflection on past work, not as current products. - -### Post Template - -Each project essay should follow a consistent loose structure: - -```markdown ---- -title: -description: -date: -updated: -projectPeriod: -tags: -project: -role: -stack: -scale: -outcome: -links: - - label: - type: - url: -media: - - type: - src: - poster: - mp4: - webm: - alt: - caption: - transcript: - role: evidence ---- - -Intro: the thesis, why this is worth reading, and what the reader will learn. - -## At a glance - -Role, timeframe, stack, scale, outcome, links. - -## The Problem - -## Constraints - -## Design - -## What Worked - -## What I Would Change - -## Links -``` - -This should not become rigid. Some posts need a story; others need an architecture walkthrough. - -### Writing Style - -Prefer: - -- Specific constraints -- Numbers -- Concrete tradeoffs -- Architecture sketches -- What shipped -- What improved -- Who used it, if known -- What was measured -- What I personally owned -- What failed -- What changed your mind -- Short code excerpts when useful - -Avoid: - -- "I was passionate about..." -- Generic technology lists -- Resume phrasing -- Overexplaining old projects as if they are current products -- Hiding rough edges - -Rough edges are useful if framed as engineering judgment. - -## Migration Plan - -### Migration Manifest - -Create a migration manifest before rewriting content. It should map executable TS project records into static content. - -Fields: - -```ts -{ - sourceProjectId: string; - sourceFile: string; - slug: string; - target: "essay" | "project-row" | "merged-project-row"; - canonicalPath: string; - legacyAnchor?: string; - title: string; - essayTitle?: string; - publishedDate?: string; - projectPeriod: string; - sortDate: string; - bodySources: Array<"description" | "more">; - links: Array<{ label: string; type: "source" | "demo" | "package" | "paper" | "video" | "site"; url: string; download?: boolean }>; - media: Array<{ type: "image" | "video" | "preview"; src?: string; poster?: string; mp4?: string; webm?: string; externalUrl?: string; alt: string; caption: string }>; - downloads: Array<{ label: string; url: string }>; -} -``` - -Initial mapping: - -| Source | Target | Notes | -| ---------------------------------- | --------------------------------- | ---------------------------------------------------------- | -| `great-ai.ts` | Essay | Use `great-ai.png`, MSc thesis PDF, PyPI/site links | -| `sdf2d.ts` | Essay | Use `sdf2d.png`, NPM, YouTube, demo; cross-link BSc thesis | -| `declared.ts` | Essay | Use `decla-red.png`, GitHub, demo, BSc thesis | -| `ad-astra.ts` | Essay | Use poster and MP4/WebM; GitHub link | -| `towers.ts` | Essay or selected project row | Use `towers.png`, GitHub, demo; focus on trie sync | -| `nuclear.ts` + `nuclear-editor.ts` | One essay or selected project row | Merge simulator/editor into one story with two screenshots | -| `city-simulation.ts` | Project row or short note | Use video only inside detail writing if kept | -| `forex.ts` | Project row or short note | Frame carefully as experiment, not financial claim | -| `my-notes.ts` | Older project row | Android/Markdown note app | -| `platform-game.ts` | Older project row | Early C/SDL learning story | -| `leds.ts` | Older project row | Raspberry Pi, FFT, LEDs | -| `photos.ts` | Older project row | Static photo site generator | -| `avoid.ts` | Older project row | First web game, external demo | -| `colors.ts` | Older project row | Color-grader proof of concept | - -Migration copyedit rules: - -- Decode HTML entities such as `—` and `&` -- Replace generic icon/helper labels with semantic labels -- Preserve external URLs -- Replace generic video alt text with project-specific alt/captions -- Keep BSc/MSc PDFs with explicit labels -- Cross-link shared thesis context between SDF-2D and decla.red - -### Link Labels - -Normalize links to reader-facing labels: - -- `Source` -- `Demo` -- `Package` -- `Paper` -- `Thesis` -- `Video` -- `Project site` - -Do not use helper-derived labels such as "Open in new tab" in content. - -## Technical Plan - -### Framework - -Use Astro. - -Reasons: - -- Static generation by default -- Markdown content collections -- Little-to-no client JavaScript -- Good TypeScript fit for this repo -- Straightforward RSS/sitemap support -- Easy to preserve a content-first architecture - -Do not use Typst as the primary web writing format for now. Typst's HTML export is still not the right foundation for this production blog. Use Markdown for posts. Typst can be revisited later for PDFs or long-form downloadable documents. - -### JavaScript Policy - -Default: ship no client JavaScript. - -Allowed later only if it materially improves a specific post: - -- A small interactive diagram -- A progressive enhancement for a specific demo -- A tiny script for theme preference if dark mode is added - -Hard rules: - -- No global app shell -- No client-side routing -- No required JS for reading -- No `client:*` islands in normal pages -- No global view transitions -- No global prefetching -- No analytics script unless explicitly accepted as a measured exception - -QA gates: - -- Run Playwright with JavaScript disabled and verify reading, nav, RSS discovery, media access, archive scanning, and project links -- Fail CI if normal pages emit unexpected `_astro/*.js` or ` + + diff --git a/src/components/PostMedia.astro b/src/components/PostMedia.astro index 37bcb76..f7cc71a 100644 --- a/src/components/PostMedia.astro +++ b/src/components/PostMedia.astro @@ -9,37 +9,77 @@ interface Props { } const { items } = Astro.props; + +const fallbackFormatFor = (format: string | undefined): 'png' | 'jpg' => + format === 'png' ? 'png' : 'jpg'; --- { - items.map((item) => ( -
- {item.type === 'video' ? ( - - ) : ( - item.src && ( - - ) - )} - {item.caption &&
{item.caption}
} - {item.transcript &&

{item.transcript}

} -
- )) + items.length > 1 ? ( + + ) : ( + items.map((item) => ( +
+ {item.type === 'video' ? ( + + ) : ( + item.src && ( + + ) + )} + {item.caption &&
{item.caption}
} + {item.transcript &&

{item.transcript}

} +
+ )) + ) } diff --git a/src/components/ProjectLinks.astro b/src/components/ProjectLinks.astro index 08c1866..7404884 100644 --- a/src/components/ProjectLinks.astro +++ b/src/components/ProjectLinks.astro @@ -22,9 +22,29 @@ function isExternal(url: string) { {link.label} + {isExternal(link.url) && ( + + )} {link.download && (
    @@ -17,7 +54,7 @@ type ProjectLink = CollectionEntry<'projects'>['data']['links'][number]; projects.map((project) => { const anchor = projectAnchor(project); const titleId = `${anchor}-title`; - const essayHref = project.data.essay ? articlePath(project.data.essay) : undefined; + const essayHref = essayHrefs.get(project.id); const essayLink: ProjectLink | undefined = essayHref ? { label: 'Article', type: 'site', url: essayHref } : undefined; @@ -37,7 +74,7 @@ type ProjectLink = CollectionEntry<'projects'>['data']['links'][number]; widths={[240, 320, 480, 640, 800]} sizes="(max-width: 700px) 7rem, (max-width: 960px) clamp(7rem, 18vw, 9.5rem), 19rem" /> -
    +

    {primaryHref ? ( {project.data.title} @@ -51,7 +88,7 @@ type ProjectLink = CollectionEntry<'projects'>['data']['links'][number]; {project.data.period} · {project.data.technologies.join(', ')}

    {links.length > 0 && } -

    + ); }) diff --git a/src/components/TagList.astro b/src/components/TagList.astro index c669823..7c873dd 100644 --- a/src/components/TagList.astro +++ b/src/components/TagList.astro @@ -5,19 +5,37 @@ interface Props { tags: readonly string[]; currentTag?: string; labelled?: boolean; + limit?: number; + counts?: Record; } -const { tags, currentTag, labelled = true } = Astro.props; +const { tags, currentTag, limit, counts } = Astro.props; + +const visibleTags = typeof limit === 'number' ? tags.slice(0, limit) : tags; +const remaining = + typeof limit === 'number' && tags.length > limit ? tags.length - limit : 0; --- -