Style Guide

A complete reference for every markdown and HTML element used in this Saga theme.

This page is the canonical styling reference for this Saga theme. Every element that the theme styles appears at least once below. Use it to verify your theme after changes, and refer to the Special Cases section at the bottom for elements that require raw HTML or a CSS class.


Typography and text

Body text is set in Avenir Next at 18px / 1.7 line-height. On platforms without Avenir Next (Windows, Android) the system sans-serif renders cleanly as a fallback.

This paragraph demonstrates bold (**bold**), italic (*italic*), and bold italic (***bold italic***). You can also use inline code which renders with a warm parchment background.

Here is a link to the home page — links render in the muted-red accent colour with a hairline underline.

Heading levels

H2 above is 28 px / 600 weight. H3 here is 21 px / 600 weight. Both use Avenir Next with tight letter-spacing. H4 and below follow the same family at progressively smaller sizes.

H4 example

H5 example
H6 example

Blockquote

The model picks the screen. The schema picks the model’s vocabulary. The renderer is glass.

Use blockquotes for pull-quotes and attributed passages. They render with a 2 px muted-red left rule and italic Avenir Next at 20 px.


Lists

Unordered list

  • Field kinds: text, picker, toggle, date, list.
  • Validation hints, expressed as a tiny DSL the renderer evaluates locally.
  • A small library of named presets the model can reference by name instead of describing.
    • Nested item — indented one level.
    • Another nested item.

Ordered list

  1. Hallucinated fields. The model invents a control that doesn’t map to anything the renderer can produce.
  2. Ugly-but-valid layouts. The spec is correct but the result looks like 1998.
  3. Excessive agreement. The model says yes to bad ideas. Fixed by a system prompt that asks it to push back.

Code

Inline code

Use backtick fences for short references: UISpec, compact-list, --color-accent. Inline code renders with a warm parchment background (#F0EBDC) and dark ink (#5A4A20).

Fenced code block

struct UISpec: Codable {
  let title: String
  let fields: [Field]
  let primary: Action
  let secondary: Action?
}

Code blocks use a dark (#020617) background with light ink — matching the dark-theme preference selected during setup.

/* CSS example */
.entry-body h2 {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  font-weight: 600;
  letter-spacing: -0.015em;
}
{
  "model": "claude-haiku-4-5",
  "latency": "380ms",
  "cost_per_1k": "$0.42"
}

Images

A full-bleed image with a caption:

A placeholder image

Figure 1. Caption text renders in 13 px italic Avenir Next, in --color-text3.

Images inside .entry-body receive border-radius: 6px and are capped at 100% width.


Tables

Standard pipe tables. Column alignment is controlled with :---, ---:, and :---: in the separator row.

Model Median latency Cost / 1k specs Reject rate
Claude Haiku 4.5 380 ms $0.42 2.1%
GPT-4o mini 510 ms $0.31 3.8%
Gemini 2.5 Flash 340 ms $0.18 5.4%
Local Llama 3.1 8B 1,200 ms $0.00 11.2%

Table 1. Measured against a fixed schema, on 1,000 representative prompts. Your mileage will vary.

Table styling notes:

  • Header row: 1 px solid ink bottom border, uppercase small-caps labels (12 px, 0.04 em tracking).
  • Body rows: 0.5 px hairline bottom border in --color-hair.
  • Right-aligned Cost column uses ---: in the separator. The theme respects both style="text-align: right" (emitted by Saga’s markdown parser) and the [align="right"] attribute.
  • The italic line immediately after the table becomes a caption.

Wide table (use the scroll wrapper)

For tables wider than the reading column, wrap the raw HTML in a <div class="table-scroll"> (see Special Cases below).


Horizontal rule

A hairline rule above and below this paragraph.


Special Cases — elements that require raw HTML or a CSS class

These elements cannot be produced with standard Markdown alone. Use them by dropping raw HTML into your .md file. Saga passes raw HTML through untouched.

Subscribe aside

Drop this block between paragraphs in a post. It renders with an accent-coloured top and bottom rule and a warm tinted background.

<div class="subscribe-aside">
  <p class="subscribe-aside-label">An aside</p>
  <p class="subscribe-aside-heading">
    If you're getting something from this,
    <em>the newsletter is a way to get more.</em>
  </p>
  <p>One short essay most Sundays. Swift, AI tools, and the long arc
  of shipping software. No teasers, no fluff.</p>
</div>

Live example:

Social proof line

Use above any Subscribe CTA. The <strong> carries the reader count in full ink; the rest is muted.

<p class="social-proof">
  <span class="social-proof-dot"></span>
  <span><strong>Join 4,000 readers.</strong> Swift, AI tools, and shipping software.</span>
</p>

Live example:

Newsletter form

Standard placement: inside any page, centered. The form POSTs directly to Substack.

<div class="newsletter-form-wrap">
  <form class="newsletter-form"
        action="https://newsletter.deverman.org/api/v1/free"
        method="post">
    <input class="newsletter-form-input" type="email" name="email"
           placeholder="[email protected]" required autocomplete="email" />
    <input type="hidden" name="redirect"
           value="https://deverman.org/subscribed/" />
    <button class="newsletter-form-btn" type="submit">Subscribe</button>
  </form>
  <p class="newsletter-form-note">No spam. Unsubscribe whenever.</p>
</div>

Live example:

Article gate (gated posts)

To gate a post, add gated: true to its front matter:

---
title: My gated post
date: 2026-05-16
path: /writing/my-post/
gated: true
---

The Swift renderer automatically:

  • Shows the first paragraph freely
  • Blurs the rest with a subscribe card overlay
  • Embeds a newsletter form with ?return=/writing/my-post/ so the subscriber is redirected back after signing up

Unlock flow:

  1. Visitor subscribes → Substack redirects to /subscribed/?return=/writing/my-post/
  2. /subscribed/ page sets localStorage.setItem('deverman_subscribed', '1')
  3. Visitor is auto-redirected back to the article after 4 seconds
  4. The inline <script> in <head> adds .deverman-unlocked to <html> before first paint
  5. CSS hides the gate card and shows the full content

On all future visits the article loads fully unlocked without any flash.

To reset the gate (for testing): open browser DevTools → Application → Local Storage → delete deverman_subscribed.

Wide table with scroll

<div class="table-scroll">

| Very long first column header | Second header | Third header | Fourth header | Fifth header |
|-------------------------------|---------------|--------------|---------------|--------------|
| Row one value                 | 123           | abc          | yes           | 99%          |
| Row two value                 | 456           | def          | no            | 87%          |

</div>

Live example:

Very long first column header Second header Third header Fourth header Fifth header
Row one value 123 abc yes 99%
Row two value 456 def no 87%

Small-caps label

Used as a section heading for archive pages, timeline sections, etc.

<p class="label">Recent Writing</p>

Recent Writing

Buttons

Two pill-style CTA buttons.

<a class="btn-primary" href="#">Subscribe to the newsletter</a>
<a class="btn-secondary" href="#">Get in touch</a>

Live example:

Subscribe to the newsletter Get in touch

Project card

Used on the home and work pages. Each card has a full-width coloured .project-card-hero area with large italic initials. An optional .project-card-featured badge appears top-right of the hero. Wrap two in .grid-2col.

<div class="grid-2col">
<div class="project-card">
  <div class="project-card-hero" style="background:#ECE6D5;color:#B5A98A;">SE</div>
  <div class="project-card-body">
    <div class="project-card-tags">
      <span class="project-card-tag">Stream Deck</span>
      <span class="project-card-tag">Shipped</span>
    </div>
    <h3 class="project-card-title">SafeEject</h3>
    <p class="project-card-desc">One-press disk manager for macOS. Native Swift, sold on the Stream Deck marketplace.</p>
    <a class="project-card-action" href="#">View on marketplace →</a>
  </div>
</div>
<div class="project-card">
  <div class="project-card-hero" style="background:#E0E7DF;color:#8AA89C;">
    FR
    <span class="project-card-featured">★ Featured</span>
  </div>
  <div class="project-card-body">
    <div class="project-card-tags">
      <span class="project-card-tag">Open Source</span>
      <span class="project-card-tag">25 ★</span>
    </div>
    <h3 class="project-card-title">FocusRelayMCP</h3>
    <p class="project-card-desc">Talk to your OmniFocus tasks. A Swift bridge connecting AI assistants to your task system.</p>
    <a class="project-card-action" href="#">Star on GitHub →</a>
  </div>
</div>
</div>

Live example:

SE
Stream DeckShipped

SafeEject

One-press disk manager for macOS. Native Swift, sold on the Stream Deck marketplace.

View on marketplace →
FR★ Featured
Open Source25 ★

FocusRelayMCP

Talk to your OmniFocus tasks. A Swift bridge connecting AI assistants to your task system.

Star on GitHub →

Case study row

Used on the work page. Outputs a div.case-row with year, title+detail, and action.

<div class="case-row">
  <div class="case-row-year">2024</div>
  <div class="case-row-body">
    <div class="case-row-title">Amplify Health (AIA): Cloud migration</div>
    <div class="case-row-detail">13 TB Oracle to PostgreSQL · 34-person team</div>
  </div>
  <div class="case-row-action">Read case study →</div>
</div>

Live example:

2024
Amplify Health (AIA): Cloud migration orchestration
13 TB Oracle to PostgreSQL · 34-person team
Read case study →

Timeline row

Used on the About page.

<div class="timeline-row">
  <span class="timeline-row-years">2026–present</span>
  <span class="timeline-row-role">Apple Developer Academy</span>
  <span class="timeline-row-place">Bali, Indonesia</span>
</div>

Live example:

2026–present Apple Developer Academy Bali, Indonesia

Now-page bullet list

Accent-dot bullet list used on the Now page.

<ul class="now-list">
  <li>Working through the Apple Developer Academy curriculum in Bali.</li>
  <li>A Miro plugin compiled with SwiftWasm.</li>
</ul>

Live example:

  • Working through the Apple Developer Academy curriculum in Bali.
  • A Miro plugin compiled with SwiftWasm.

Page hero

Used on section pages (Writing, Work, About, Now). The .page-hero-label slot takes a .label element. em inside .page-hero-title renders in italic 500 weight.

<div class="page-hero">
  <p class="page-hero-label"><span class="label">Writing</span></p>
  <h1 class="page-hero-title">Essays on <em>building software</em></h1>
  <p class="page-hero-dek">Swift, AI tools, and the long arc of shipping things that matter.</p>
</div>

Live example:

Writing

Essays on building software

Swift, AI tools, and the long arc of shipping things that matter.


Article header

Used on single post views. Emitted by the Swift renderer — shown here for reference when building custom pages.

<header class="article-header">
  <div class="article-meta">
    May 16, 2026
    <span class="article-meta-dot"></span>
    <a class="tax-pill" href="/tag/swift/">Swift</a>
    <a class="tax-pill tax-pill--accent" href="/tag/featured/">Featured</a>
  </div>
  <h1 class="article-title">How I rebuilt my site with Saga</h1>
  <p class="article-dek">A quiet rewrite, a weekend of yak-shaving, and lessons about when to stop tweaking.</p>
  <div class="article-byline-row">
    <img class="article-byline-avatar" src="/static/avatar.jpg" alt="Brent Deverman" />
    Brent Deverman
  </div>
</header>

Live example:

How I rebuilt my site with Saga

A quiet rewrite, a weekend of yak-shaving, and lessons about when to stop tweaking.


List rows (archive pattern)

The writing archive and tag pages use .list-rows > .list-row. Each row is a three-column grid: date / title / action. Use .list-section-year to group rows by year. The .list-row--compact modifier reduces vertical padding for dense lists.

<div class="list-section-year">
  <span class="label">2026</span>
  <span class="list-section-year-rule"></span>
</div>
<ul class="list-rows">
  <li class="list-row">
    <span class="list-row-date">May 16</span>
    <a class="list-row-title" href="#">How I rebuilt my site with Saga</a>
    <a class="list-row-action" href="#">Read →</a>
  </li>
  <li class="list-row">
    <span class="list-row-date">Apr 3</span>
    <a class="list-row-title" href="#">Advice to a young entrepreneur</a>
    <a class="list-row-action" href="#">Read →</a>
  </li>
</ul>

Live example:

2026

Taxonomy pills

Used for category and tag chips. .tax-pill--accent renders in the muted-red accent tint — used for high-signal tags like “Featured”.

<a class="tax-pill" href="#">Swift</a>
<a class="tax-pill" href="#">AI Tools</a>
<a class="tax-pill tax-pill--accent" href="#">Featured</a>

Live example:

Swift AI Tools Featured

Writing filter tabs

Used on the Writing page to filter by tag. The .active state fills the pill with ink.

<div class="writing-filters">
  <button class="writing-filter-tab active">All</button>
  <button class="writing-filter-tab">Swift</button>
  <button class="writing-filter-tab">AI Tools</button>
  <button class="writing-filter-tab">Entrepreneurship</button>
</div>

Live example:


RSS link chip

Small inline chip used on archive pages to link to a feed.

<a class="rss-link" href="/posts/feed.xml">
  <span class="rss-link-icon"></span>
  RSS feed
</a>

Live example:

RSS feed

Now page pulse dot

Live indicator shown beside “Last updated” on the Now page. The dot pulses via CSS animation.

<span class="now-pulse">
  <span class="now-pulse-dot"></span>
  Last updated May 16, 2026 · Bali, Indonesia
</span>

Live example:

Last updated May 16, 2026 · Bali, Indonesia

Case study components

A full set of named classes for case study pages. Use these instead of inline styles for consistent typography and spacing.

Meta row

<div class="cs-meta">
  Amplify Health
  <span class="cs-meta-dot"></span>
  2024
  <span class="cs-meta-dot"></span>
  Cloud Migration
</div>
Amplify Health 2024 Cloud Migration

Metric strip

Three-column card showing headline numbers. Stacks to single column on mobile.

<div class="metric-strip">
  <div class="metric-strip-item">
    <div class="metric-strip-num">13 TB</div>
    <div class="metric-strip-label">Data migrated from Oracle to PostgreSQL</div>
  </div>
  <div class="metric-strip-item">
    <div class="metric-strip-num">34</div>
    <div class="metric-strip-label">Person cross-functional team</div>
  </div>
  <div class="metric-strip-item">
    <div class="metric-strip-num">6 mo</div>
    <div class="metric-strip-label">From kickoff to production cutover</div>
  </div>
</div>
13 TB
Data migrated from Oracle to PostgreSQL
34
Person cross-functional team
6 mo
From kickoff to production cutover

Stat grid

Four key/value pairs in a compact grid, separated from content above by a hairline rule.

<div class="case-study-stat-grid">
  <div><div class="case-study-stat-key">Client</div><div class="case-study-stat-val">Amplify Health</div></div>
  <div><div class="case-study-stat-key">Year</div><div class="case-study-stat-val">2024</div></div>
  <div><div class="case-study-stat-key">Role</div><div class="case-study-stat-val">Lead Architect</div></div>
  <div><div class="case-study-stat-key">Stack</div><div class="case-study-stat-val">PostgreSQL, AWS</div></div>
</div>
Client
Amplify Health
Year
2024
Role
Lead Architect
Stack
PostgreSQL, AWS

Body typography (cs-h2, cs-p, cs-blockquote)

<h2 class="cs-h2">The challenge</h2>
<p class="cs-p">Thirteen terabytes of Oracle data, a 34-person team, and six months to production. The migration had to be zero-downtime.</p>
<div class="cs-blockquote">
  The database was the heart of everything. We couldn't just lift and shift.
  <div class="cs-blockquote-attr">— Head of Engineering, Amplify Health</div>
</div>

The challenge

Thirteen terabytes of Oracle data, a 34-person team, and six months to production. The migration had to be zero-downtime.

The database was the heart of everything. We couldn't just lift and shift.
— Head of Engineering, Amplify Health

Figure placeholder

Use while waiting for a real image. The hatched background and monospace caption label the slot.

<div class="cs-figure">
  <span class="cs-figure-caption">FIG 1 — ARCHITECTURE DIAGRAM</span>
</div>
FIG 1 — ARCHITECTURE DIAGRAM

Tag strip + CTA card

Footer of a case study. Tags on the left, a sharing note on the right. CTA card below.

<div class="cs-tag-strip">
  <div class="cs-tags">
    <span class="cs-tag">PostgreSQL</span>
    <span class="cs-tag">Cloud Migration</span>
    <span class="cs-tag">Oracle</span>
  </div>
  <span class="cs-shared-note">Share this case study</span>
</div>

<div class="case-cta-card">
  <div>
    <strong>Need a similar migration?</strong>
    <p style="font-size:13px;color:var(--color-text2);margin:4px 0 0;">I consult on large-scale database and cloud migrations.</p>
  </div>
  <a class="btn-secondary" href="/work/">See my work →</a>
</div>
PostgreSQL Cloud Migration Oracle
Share this case study
Need a similar migration?

I consult on large-scale database and cloud migrations.

See my work →

What is not yet styled

  • Page transition animations — Hover animations from the design’s motion artboards are not implemented; they require a JS runtime. CSS view transitions (cross-fade between pages) are implemented.