Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

This Website: From PDF to Terminal-Styled SPA in 72 Hours

I sent my CV as a PDF to Claude. It came back as a fully structured, terminal-themed, markdown-driven single-page application — with Mermaid diagrams, scroll spy, resizable sidebar, and four color themes. Then I spent three days making it mine.

The Spark

It started at 3 AM on March 18, 2026. I had a polished PDF resume and a simple question for Claude: "Can you turn this into a website?"

I didn't mean a static HTML page with Bootstrap. I didn't mean a WordPress theme. I meant something that felt like me — a developer who lives in terminals, thinks in modules, and believes that anything worth doing is worth automating.

What came back was the initial commit: 28 files, 5,620 lines — a complete SPA with markdown rendering, a sidebar TOC, syntax highlighting, and a dark terminal aesthetic. Not a template. Not a wireframe. A working site.

That was the starting point. The next 72 hours were a conversation — not between a user and a tool, but between a developer and an opinionated collaborator.

Architecture: Zero Frameworks, Maximum Control

The site runs on vanilla JavaScript, CSS3, and HTML5. No React, no Vue, no bundler, no build step beyond TOC generation. Three JS modules, one CSS file, a handful of markdown documents.

index.html                    ← Single HTML shell
├── css/style.css1,270 lines of themed CSS
├── js/
│   ├── app.js                ← SPA routing, TOC, scroll spy (~700 lines)
│   ├── markdown-renderer.js  ← marked.js + Mermaid + code blocks (~310 lines)
│   └── theme-switcher.js     ← Color/OS mode persistence (~170 lines)
├── content/                  ← All content as markdown
│   ├── about.md
│   ├── skills.md
│   ├── experience/*.md       ← One file per role
│   ├── projects/*.md
│   ├── blog/*.md             ← Technical articles
│   └── blog/cmf/*.md         ← 13-part series with parent-child nav
├── scripts/
│   ├── build-toc.js          ← Scans content/, generates toc.json
│   ├── workflow.js           ← Interactive dev CLI
│   └── download-assets.js    ← CDN asset downloader
└── toc.json                  ← Generated sidebar structure

Why no framework? Because the problem doesn't need one. The site loads markdown files via fetch, renders them with marked.js, highlights code with highlight.js, and draws diagrams with Mermaid. The browser already has everything else: DOM manipulation, history API, CSS custom properties, scroll events.

This isn't anti-framework ideology — it's the "anything-as-code" principle applied to itself. The simplest tool that solves the problem is the right tool.

The Terminal Aesthetic

The site mimics a terminal window because that's where I spend most of my time. The design isn't decoration — it communicates something about how I think about software.

┌─ ● ○ ○ ────── serard@dev00:~/cv ──── ☾ 🎨 ◑ 🍎 ⊞ 🐧 ─┐
├─ ~/about ──────┼──────────────────────────────────────────┤
│  > Who Am I?   │  # Stéphane ERARD                       │
│  > Quick Facts │  > Pragmatic Full-Stack Web Architect    │
│  ~/projects    │                                          │
│  ~/experience  │  As a full-stack web developer with      │
│  ~/skills      │  15+ years of experience...              │
│  ~/blog        │                                          │
└────────────────┴──────────────────────────────────────────┘

Three OS Flavors

The title bar adapts to three operating system styles — because why not?

OS Title Bar Window Controls
macOS serard — ~/cv Traffic light dots (●●●)
Windows C:\Users\serard\cv Minimize/maximize/close (— □ ×)
Linux serard@dev00:~/cv Square buttons with symbols

Each mode adjusts the title text, the window control styling, and the border radius. All persisted to localStorage.

Four Color Themes

The theming system uses CSS custom properties on the <html> element:

[data-color-mode="dark"] {
  --bg-primary: #0d1117;
  --text-primary: #e6edf3;
  --text-muted: #848d97;
  --accent-green: #3fb950;
  --accent-blue: #58a6ff;
  /* ... 16 variables total */
}

Switching themes is a single attribute change — data-color-mode and data-color-theme — and every element instantly reflows through the CSS cascade. No class toggling, no JavaScript DOM walks, no FOUC.

The four modes:

  1. Dark (default) — GitHub-dark inspired
  2. Light — GitHub-light inspired
  3. Dark High Contrast — WCAG AAA accessible
  4. Light High Contrast — WCAG AAA accessible

Deep Linking & Navigation

The URL hash drives everything. The format is #path/to/file.md::heading-slug, with :: as the separator between page and anchor. This avoids collisions with heading slugs that contain / or #.

// Parse: "content/blog/ddd.md::bounded-contexts--strategic-design"
//    → page: "content/blog/ddd.md"
//    → anchor: "bounded-contexts--strategic-design"
const idx = hash.indexOf('::');

Heading slugs are hierarchical: a h3 under a h2 under a h1 produces h1-slug--h2-slug--h3-slug. This means every heading in every document has a globally unique, human-readable anchor — and the TOC links resolve natively.

The sidebar TOC supports:

  • Collapsible sections (~/about, ~/projects, ~/blog, etc.)
  • Nested content trees (the 13-part CMF series has its own parent-child hierarchy)
  • In-page heading navigation (h2/h3 headings appear below the active item)
  • Scroll spy — the sidebar highlights the heading currently visible in the viewport

Mermaid Diagrams: Click to Explore

Mermaid code blocks render inline as SVGs with full theming support. But the real feature is the full-screen overlay: click any diagram and it opens in a zoomable, pannable modal.

// Zoom with mouse wheel
container.addEventListener('wheel', (e) => {
  zoomLevel *= e.deltaY < 0 ? 1.1 : 0.9;
  zoomLevel = Math.max(0.1, Math.min(5, zoomLevel));
  applyTransform();
});

// Pan with click-drag
container.addEventListener('mousemove', (e) => {
  if (!isDragging) return;
  panX += e.clientX - lastMouseX;
  panY += e.clientY - lastMouseY;
  applyTransform();
});

This matters because architecture diagrams — like the BinaryWrapper pipeline or the Career Timeline — are dense. Being able to zoom into a specific node makes them actually useful, not just decorative.

The Sidebar: More Than a Menu

The sidebar is a resizable panel with a drag handle. Widths persist across sessions. It supports:

  • Folder-based grouping: files in content/blog/cmf/ with parent: cmf frontmatter nest under their parent index
  • Icons per section: 📝 for standalone files, 📁/📂 for folders
  • Tooltips: hover 650ms over a truncated title to see the full text (positioned outside the sidebar to avoid clipping)
  • Active state: the current page gets a blue left border and a blue-tinted background

The TOC itself is generated at build time by scripts/build-toc.js, which scans the content/ directory, parses YAML frontmatter, extracts h2/h3 headings, and produces a structured toc.json. The heading slugs in toc.json use the exact same algorithm as the client-side renderer — so sidebar links always resolve correctly.

Content as Markdown

Every page on this site is a markdown file with YAML frontmatter:

---
title: "BinaryWrapper: Type-Safe .NET Wrappers for CLI Binaries"
section: blog
order: 1
date: "2025-01-15"
tags: ["C#", ".NET", "Source Generator"]
---

The renderer adds:

  • Heading anchors with copy-to-clipboard buttons (📋)
  • Code blocks with syntax highlighting and copy buttons
  • Tables with striped rows and uppercase headers
  • Internal links that resolve relative paths ([DDD](#content/blog/ddd.md))
  • Mermaid diagrams with full-screen overlay support

This means adding a new page is: create a .md file, add frontmatter, run npm run build:toc. Done.

Accessibility: Lighthouse 100/100

After the initial build, I ran automated accessibility scans using three tools — axe-core, Pa11y (with both axe and htmlcs runners), and Lighthouse — and discovered 50+ WCAG violations. Fixing them became a systematic effort across multiple layers.

Automated Fixes

Color contrast was the biggest category. The CSS variable --text-muted was #6e7681 on --bg-secondary #161b22 — a 3.77:1 ratio, below the WCAG AA minimum of 4.5:1. Fixed across all four themes:

Theme --text-muted before After Ratio
Dark #6e7681 #848d97 4.5:1+
Light #818b98 #656d76 4.5:1+
Dark HC / Light HC Already compliant 7:1+

Other automated fixes:

  • Skip links — TOC heading links used #path::slug format but targets only had id="slug". Added focusable <span> anchors with the composite ID before each heading.
  • Mermaid duplicate IDs — Mermaid generates id="node-undefined" on SVG <path> elements. Post-processing deduplicates them. The fullscreen overlay SVG is lazily injected on click (not in the DOM at scan time) to avoid duplicate IDs entirely.
  • Table header contrast — light theme used --accent-blue on --bg-tertiary (3.8:1). Switched to --text-primary.
  • Active TOC item — light theme used --accent-blue on a semi-transparent blue background (4.3:1). Darkened to #0550ae with reduced opacity.
  • Theme buttons — changed icon color from --text-secondary to --text-primary for sufficient contrast.

ARIA & Keyboard Accessibility

Beyond automated scanning, I addressed Lighthouse's 10 manual audit items:

Audit Item Implementation
Interactive controls are keyboard focusable Added tabindex="0" to resize handle, contact link, Mermaid SVGs, and images
Elements indicate purpose and state Added role="switch" + aria-checked to dark/light and contrast toggles, synced on every toggle
Custom controls have labels Added aria-label to all OS buttons, theme toggles, contact link, copy buttons
Custom controls have ARIA roles role="switch" for toggles, role="separator" for resize handle, role="button" for contact and diagrams
Focus directed to new content SPA navigation focuses #markdown-output after page load so screen readers announce new content
Focus not trapped Mermaid/image overlays close with Escape, focus returns to the triggering element
HTML5 landmarks <header>, <nav>, <main>, <article> used throughout
Offscreen content hidden aria-hidden="true" on icon spans (only the visible one per theme renders)

Keyboard-Accessible Diagrams and Images

Mermaid diagrams and images support full keyboard interaction:

  1. Tab to a diagram or image (focusable, role="button")
  2. Enter or Space opens the fullscreen overlay with zoom/pan controls
  3. Focus moves to the close button in the overlay
  4. Escape closes the overlay
  5. Focus returns to the original element

Mermaid Theme Switching

Mermaid diagrams re-render when switching between dark and light mode. Each diagram stores its raw source in a data-source attribute, and mermaid.initialize() is called with the new theme variables before re-rendering. Timeline diagrams use explicit cScale0cScale11 color palettes for both themes — pastel backgrounds with dark text for light mode, saturated backgrounds with white text for dark mode.

Cross-Theme Test Suite

The accessibility test suite runs across all four theme combinations using Pa11y's action system to click theme toggle buttons before scanning:

const themes = [
  { name: 'Dark (default)',        actions: [] },
  { name: 'Dark + High Contrast', actions: ['click element #btn-color-theme'] },
  { name: 'Light',                actions: ['click element #btn-color-mode'] },
  { name: 'Light + High Contrast', actions: ['click element #btn-color-mode',
                                              'click element #btn-color-theme'] },
];

Results:

  • axe-core: 0 violations (all themes)
  • Lighthouse: 100/100
  • Pa11y: 18 remaining errors across all themes — all emoji contrast false positives (, , , 📋) that axe-core correctly ignores

Run it yourself: npm run a11y (quick axe-core scan) or npm run a11y:full (all themes with Pa11y).

The Claude Collaboration

This website is the product of a larger story. The 72 hours weren't just about building a CV site — they were about building an entire ecosystem of .NET projects with Claude, and then documenting them here.

The Bigger Picture: FrenchExDev

In parallel with this website, I was working with Claude on the FrenchExDev ecosystem — a .NET & PowerShell monorepo with 20+ projects: type-safe CLI wrappers for 7 binaries (Docker, Podman, Vagrant, Packer, GitLab CLI...), a composable Result<T> pattern, a source-generated Builder, a meta-metamodeling DSL framework, a full DDD code generation pipeline, a Content Management Framework with 4 sub-DSLs, an AI-powered document search engine, and a Roslyn-based static analysis quality gate.

Each blog post on this site describes a real project that exists and was built or significantly advanced during those same 72 hours:

Project What It Does
BinaryWrapper Wraps CLI binaries into typed C# APIs — powers 7 wrappers across 200+ binary versions
Result Pattern Composable error handling replacing exceptions with Result<T, TError> types
Builder Pattern Source-generated async object construction with validation
DDD & Code Generation Attribute-based domain modeling that generates repositories, commands, events
QualityGate Roslyn-powered static analysis — complexity, coupling, cohesion, mutation testing
Docker Compose Bundle Typed models from 32 Docker Compose schema versions
Modeling & Metamodeling The M0-M3 meta-metamodel theory behind the DSL framework
Feature Tracking Requirements as code, with compile-time traceability from feature to test
Infrastructure as Code The PowerShell module ecosystem tying it all together
CMF Series 13-part deep dive into building a Content Management Framework from scratch

The website was born from the need to present these projects coherently. A PDF CV can list job titles. A terminal-styled SPA with Mermaid diagrams, cross-referenced articles, and live code samples can show how you actually think and build.

The Collaboration Pattern

The pattern was always the same:

  1. I describe what I want (sometimes vaguely, sometimes precisely)
  2. Claude proposes an implementation
  3. I review, adjust, redirect
  4. We iterate until it feels right

This applied equally to writing C# source generators, designing DSL attribute hierarchies, and building this website. The projects and the site grew together — I'd implement a feature in the .NET monorepo, then write about it here, then Claude would help refine both the code and the article.

Some examples specific to this site:

  • "Turn my CV PDF into a website" → Claude generated the initial 28-file SPA structure, choosing the terminal aesthetic based on my profile (DevOps background, IaC philosophy, terminal-centric workflow)
  • "Add a blog section" → not just a list of posts, but a full markdown rendering pipeline with Mermaid support, code highlighting, and internal cross-references
  • "The CMF series needs 13 parts with navigation" → Claude designed the parent-child TOC system with collapsible trees
  • "Test accessibility" → research on tools (pa11y, axe-core, Lighthouse), installation, scanning, fixing violations, building a multi-theme test script
  • "Write about this website" → this article

The interesting part: Claude made architectural decisions that I wouldn't have made myself — the :: deep-link separator, the hierarchical slug system, the CSS-custom-property-only theming. Some I kept because they were better than what I had in mind. Some I redirected. That's the nature of a good collaboration.

What Claude Code Brought to the Table

The first generation happened in Claude's web interface — I uploaded the PDF and got back a website structure. But the refinement happened in Claude Code (the CLI), which changed the dynamic entirely:

  • File-level edits: Claude reads and modifies files directly, so iterations are fast
  • Tool access: running accessibility scans, git operations, npm installs — all within the conversation
  • Context persistence: Claude remembers decisions across the session, so I don't repeat myself
  • Parallel exploration: while I review one change, Claude researches the next problem
  • Cross-project context: Claude understands both the .NET codebase and this website, so blog articles accurately reflect the actual implementations

The 72-hour timeline:

Day What Happened
Day 1 (Mar 18) Initial generation from PDF. Theme system. Experience entries. First blog posts on BinaryWrapper, Result, Builder.
Day 2 (Mar 19) Homelab project writeup. DDD, CMF (13-part series), feature tracking, quality gates articles — all describing real FrenchExDev projects built in parallel. Menu fixes.
Day 3 (Mar 20) Cross-references between posts. Workflow automation script. TOC refinements. Internal link resolution.
Day 4 (Mar 21) Docker Compose article. Accessibility audit & fixes (Lighthouse 100/100). This blog post.

The Developer Workflow

Local development is intentionally simple:

npm run dev          # Rebuild TOC + start local server
npm run build:toc    # Regenerate toc.json from content/
npm run a11y         # Quick accessibility scan
npm run a11y:full    # Test all 4 theme combinations
npm run work         # Interactive CLI (commit, push, serve, rebuild)

The workflow.js script provides an interactive menu for common operations — commit with message, restart server, rebuild TOC — because even the development process should follow the "anything-as-code" principle.

What I'd Do Differently

  • Service Worker: the site could work offline with minimal effort — all content is static markdown
  • Search: a client-side full-text search over the markdown files would be useful as content grows
  • RSS feed: generated from frontmatter dates and blog section items
  • Print stylesheet: exists but could be refined for PDF export (closing the loop from PDF → website → PDF)

The Takeaway

This site isn't just a CV. It's a demonstration of how I work: iteratively, with clear separation of concerns, using the simplest tools that solve the problem, and collaborating effectively with AI.

The entire codebase is vanilla — no framework, no bundler, no build complexity. The content is markdown — portable, version-controlled, readable without rendering. The theming is CSS custom properties — no JavaScript required for style changes. The architecture is modular — each concern (rendering, navigation, theming) lives in its own file.

And the whole thing started with a PDF and a prompt.

Total time: ~72 hours of iterative development with Claude. Total JavaScript: ~1,180 lines across 3 modules (no dependencies beyond marked/mermaid/hljs). Total CSS: ~1,270 lines, 4 color themes, 3 OS styles. Lighthouse score: 100/100 accessibility. Framework dependencies: zero.