📄 design-v2-spec.md 13,385 bytes Apr 21, 2026 📋 Raw

Blog Design V2 Spec — Functional Luxury

From: Daedalus 🎨
To: Matt, Socrates, Wadsworth
Date: 2026-04-21
Status: Ready for implementation
Live URL for reference: https://notes.hoffdesk.com


Executive Summary

The blog is technically sound but visually undercooked. The current aesthetic reads "MVP shipped" not "Functional Luxury." This spec delivers a prioritized roadmap to transform it from a functional document into a premium reading experience.

Current impression: Dark theme with spacing issues, generic typography, and no visual identity.
Target impression: A Roman spa for the mind — calm, intentional, luxurious in restraint.


Competitive Research: What Premium Blogs Do

The Patterns That Matter

Technique Paul Graham Basecamp Linear Vercel Implementation Priority
Generous whitespace Extreme High Medium Medium Sprint 1
Serif body text Yes No No No Sprint 1
Systematic scale 18px base 18px base 16px base 16px base Sprint 1
Category color coding No Yes Yes Yes Already implemented ✓
Dark mode default No No Yes Yes Already implemented ✓
Micro-interactions None Minimal Premium Medium Sprint 2
Featured imagery None Minimal Hero shots Gradients Sprint 2
Glassmorphism No No Yes Subtle Sprint 2

Key Insights

  1. Typography is 70% of the perception — David Bushell's 2025 redesign notes: "Atkinson Hyperlegible is the bee's knees." Premium blogs invest heavily in font choice and scale.

  2. Whitespace isn't empty — it's intentional — The Blog Herald research: "Think of it like a conversation. You wouldn't talk to someone with your face two inches from theirs."

  3. Restrained palette wins — "Pick two, maybe three colors max. Rainbow blogs don't scream premium. They scream amateur hour."

  4. Dark mode dominance — Linear and Vercel both default dark. The white background era is over for developer-focused content.


Top 5 Highest-Impact Visual Improvements

1. Typography Overhaul (Sprint 1 — HIGH)

Problem: Current CSS uses system fonts with no personality. The blog looks like every default Bootstrap site.

Solution:
- Display/Headings: Source Serif 4 (Google Fonts) — elegant, readable, premium feel
- Body text: Inter or Source Sans 3 — clean, modern, excellent at small sizes
- Code: 0xProto or JetBrains Mono — purpose-built for code, not just any monospace

Specific CSS additions:

@import url('https://fonts.googleapis.com/css2?family=Source+Serif+4:opsz,wght@8..60,400;8..60,600;8..60,700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');

:root {
  --font-display: 'Source Serif 4', Georgia, serif;
  --font-body: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', 'SF Mono', monospace;

  /* Fluid type scale */
  --text-hero: clamp(2rem, 5vw, 3rem);
  --text-h1: clamp(1.75rem, 4vw, 2.5rem);
  --text-h2: clamp(1.5rem, 3vw, 2rem);
  --text-body: clamp(1rem, 1.5vw, 1.125rem);
}

.blog-article-body {
  font-family: var(--font-display);
  font-size: var(--text-body);
  line-height: 1.75; /* Increased from 1.7 */
}

.blog-article-body h2, .blog-article-body h3 {
  font-family: var(--font-body);
  font-weight: 600;
}

.blog-article-body code {
  font-family: var(--font-mono);
}

Template change: Add Google Fonts link to base.html.j2 head.

Reference:
- https://dbushell.com/ — Atkinson Hyperlegible + 0xProto
- https://paulgraham.com/ — Verdana, generous spacing


2. Whitespace & Rhythm System (Sprint 1 — HIGH)

Problem: Current spacing feels cramped. The 960px container and tight padding don't let content breathe.

Solution: Adopt a systematic spacing scale with more generous defaults.

:root {
  /* Extended space scale */
  --space-1: 0.25rem;   /* 4px */
  --space-2: 0.5rem;    /* 8px */
  --space-3: 0.75rem;   /* 12px */
  --space-4: 1rem;      /* 16px */
  --space-5: 1.5rem;    /* 24px */
  --space-6: 2rem;      /* 32px */
  --space-8: 3rem;      /* 48px */
  --space-10: 4rem;     /* 64px */
  --space-12: 6rem;     /* 96px */
  --space-16: 8rem;     /* 128px */

  /* Content width */
  --content-max: 680px; /* Narrower for readability */
}

.blog-main {
  max-width: var(--content-max);
  padding: var(--space-12) var(--space-6);
}

.blog-article-body p {
  margin-bottom: var(--space-6); /* Increased from space-4 */
}

.blog-article-body h2 {
  margin-top: var(--space-12); /* More breathing room */
  margin-bottom: var(--space-5);
}

.blog-article-body h3 {
  margin-top: var(--space-10);
  margin-bottom: var(--space-4);
}

.blog-article-body blockquote {
  margin: var(--space-10) 0;
  padding: var(--space-6) var(--space-8);
}

Why this matters: David Bushell: "I like big boring undistracted text." The current layout shifts when zoomed. This change minimizes layout shift.


3. Enhanced Dark Mode Polish (Sprint 1 — MEDIUM)

Problem: Dark mode works but lacks depth. No subtle gradients, no glassmorphism, flat appearance.

Solution: Add subtle elevation through layered backgrounds and soft borders.

:root {
  /* Refined color palette — still dark, but more nuanced */
  --bg-primary: #0a0c10;      /* Deeper black */
  --bg-secondary: #111318;    /* Elevated surfaces */
  --bg-tertiary: #1a1d24;    /* Hover states */
  --bg-card: rgba(17, 19, 24, 0.8); /* For glassmorphism */

  /* Subtle border colors */
  --border-default: rgba(255, 255, 255, 0.08);
  --border-subtle: rgba(255, 255, 255, 0.04);
  --border-hover: rgba(255, 255, 255, 0.12);

  /* Text with more nuance */
  --text-primary: #f0f1f5;
  --text-secondary: #9ca3af;
  --text-tertiary: #6b7280;

  /* Accent refinements */
  --accent-primary: #818cf8;
  --accent-glow: rgba(129, 140, 248, 0.15);
}

/* Subtle gradient overlay for hero */
.blog-hero-link {
  background: linear-gradient(
    145deg,
    var(--bg-secondary) 0%,
    rgba(17, 19, 24, 0.95) 100%
  );
  border: 1px solid var(--border-default);
}

/* Glassmorphism for header */
.blog-header {
  background: rgba(10, 12, 16, 0.85);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid var(--border-default);
}

4. Micro-Interactions & Hover States (Sprint 2 — MEDIUM)

Problem: Links and cards have basic hover states. No personality, no delight.

Solution: Add purposeful animation that guides attention without distraction.

/* Smooth transitions */
:root {
  --transition-fast: 150ms ease;
  --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
  --transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Card hover with lift and glow */
.blog-card {
  transition: transform var(--transition-base),
              box-shadow var(--transition-base),
              border-color var(--transition-fast);
}

.blog-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4),
              0 0 0 1px var(--border-hover);
}

/* Link hover with underline animation */
.blog-article-body a {
  text-decoration: none;
  background-image: linear-gradient(var(--accent-primary), var(--accent-primary));
  background-size: 0% 2px;
  background-position: 0% 100%;
  background-repeat: no-repeat;
  transition: background-size var(--transition-fast);
}

.blog-article-body a:hover {
  background-size: 100% 2px;
}

/* Focus states for accessibility */
:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 4px;
}

/* Staggered fade-in for content */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.blog-article-header {
  animation: fadeInUp 0.6s ease-out;
}

.blog-article-body > * {
  animation: fadeInUp 0.6s ease-out;
  animation-fill-mode: both;
}

/* Stagger children */
.blog-article-body > *:nth-child(1) { animation-delay: 0.1s; }
.blog-article-body > *:nth-child(2) { animation-delay: 0.15s; }
.blog-article-body > *:nth-child(3) { animation-delay: 0.2s; }
/* ... etc */

Problem: No imagery. The blog is text-only which works for PG but limits visual identity.

Solution: Add optional featured images with gradient fallbacks for posts without images.

/* Article header with optional featured image */
.blog-article-header {
  position: relative;
  padding: var(--space-12) 0;
  margin-bottom: var(--space-10);
}

.blog-article-header.has-image {
  padding: var(--space-16) 0;
  margin-bottom: var(--space-12);
}

.blog-article-header-bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0.3;
  filter: saturate(0.8);
}

/* Gradient fallback when no image */
.blog-article-header-gradient {
  background: linear-gradient(
    135deg,
    var(--cat-homelab) 0%,
    var(--accent-primary) 50%,
    var(--cat-ai) 100%
  );
  opacity: 0.1;
}

/* Category-specific gradients */
.cat-homelab .blog-article-header-gradient {
  background: linear-gradient(135deg, var(--cat-homelab), #c2410c);
}

.cat-openclaw .blog-article-header-gradient {
  background: linear-gradient(135deg, var(--cat-openclaw), #4f46e5);
}

.cat-ai .blog-article-header-gradient {
  background: linear-gradient(135deg, var(--cat-ai), #0891b2);
}

Template addition: Add optional featured_image field to frontmatter, render conditionally.


Sprint Breakdown

Sprint 1: Typography & Foundation (Estimated: 2-3 hours)

Files to modify:
1. blog/templates/base.html.j2 — Add Google Fonts
2. blog/static/blog.css — Typography system, spacing scale, color refinements

Visual impact: HIGH — This alone transforms the "feel" of the blog

Testing checklist:
- [ ] Fonts load correctly on mobile
- [ ] Content width comfortable at 320px viewport
- [ ] No layout shift on zoom (150%, 200%)
- [ ] Code blocks remain monospace
- [ ] Dark mode still works


Sprint 2: Polish & Delight (Estimated: 3-4 hours)

Files to modify:
1. blog/static/blog.css — Animations, hover states, glassmorphism
2. blog/templates/blog_article.html.j2 — Featured image support
3. Backend (Socrates) — Optional featured_image field in schema

Visual impact: MEDIUM — Adds personality and premium feel

Testing checklist:
- [ ] Animations respect prefers-reduced-motion
- [ ] Hover states work on touch devices
- [ ] No performance degradation
- [ ] Featured images render correctly with fallback


Reference URLs — Blogs That Nailed It

Blog Why It Works URL
Linear Blog Glassmorphism, subtle gradients, premium feel https://linear.app/blog
Vercel Blog Dark mode, great typography, code highlighting https://vercel.com/blog
David Bushell Atkinson Hyperlegible, excellent spacing https://dbushell.com/
Paul Graham Extreme minimalism, readable serif https://paulgraham.com/
Tailwind Blog Warmth, approachable, category colors https://tailwindcss.com/blog
37signals / Basecamp Bold typography, clear hierarchy https://37signals.com/

Specific CSS/Tailwind Additions Summary

Fonts to Load (Google Fonts)

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Serif+4:opsz,wght@8..60,400;8..60,600;8..60,700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">

Critical CSS Variables to Add

/* Typography */
--font-display: 'Source Serif 4', Georgia, serif;
--font-body: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;

/* Fluid scale */
--text-hero: clamp(2rem, 5vw, 3rem);
--text-h1: clamp(1.75rem, 4vw, 2.5rem);
--text-body: clamp(1rem, 1.5vw, 1.125rem);

/* Extended spacing */
--space-10: 4rem;
--space-12: 6rem;
--space-16: 8rem;
--content-max: 680px;

/* Transitions */
--transition-fast: 150ms ease;
--transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);

Aundrea Test

Would Aundrea enjoy reading this at 7 AM, half-awake, on her phone?

Current state: Maybe. Text is readable but feels utilitarian.

After Sprint 1: Yes. Serif body text feels like a real article, not a documentation page. Generous spacing lets her skim without effort.

After Sprint 2: Yes, and she'd notice the polish. The subtle animations and refined colors feel intentional, not accidental.


Deliverables Checklist

  • [x] Top 5 highest-impact improvements identified
  • [x] Specific CSS/Tailwind additions documented
  • [x] Reference URLs compiled
  • [x] Effort estimated (Sprint 1 vs Sprint 2)
  • [ ] Next: Matt reviews and approves
  • [ ] Next: Socrates implements Sprint 1
  • [ ] Next: Daedalus reviews live result
  • [ ] Next: Sprint 2 if Sprint 1 lands well

"The words are the product." — This spec ensures the container honors the content.

— Daedalus 🎨