Portfolio · card deep-dive · v5 · 2026-04-12

Typography unification +
featured cards

Two changes from v4, both driven by the same principle: visual marks that sit in the same frame should read as siblings, not strangers.

(1) Typography fix. v4 had numerals and monograms on Fraunces and icons on lucide-react. Fraunces is a transitional display serif; lucide draws with geometric sans strokes. Sitting in the same grid, types 3, 5, and 6 looked like three different projects pretending to be one. v5 moves numerals and monograms to Geist sans 500 and tunes lucide to stroke-width 2.25 so all three share a single visual family. Fraunces stays where it belongs — headings and prose — and is no longer asked to carry UI marks it wasn't drawn for.

(2) Featured card variations. With the gradient-mark type now reserved to featured cards only (Q1 resolution below), featured needs its own treatment vocabulary. v5 explores four options: gradient border (F1), gradient kicker pill (F2), gradient-mark strip (F3), and the combined border-plus-strip "hero" treatment (F4) reserved for the single most important card on a page.

§1 · typography unification

Geist sans for numerals, monograms, and icons

The v4 "three fonts in one frame" problem shows up clearest in a row of three cards. Below: v4 first, then v5. Same strip shell, same register colour, same sizing rules — only the typography family changes.

Before — v4 typography. Numeral = Fraunces 300 / 120px; Monogram = Fraunces 400 / 72px; Icon = lucide stroke 1.5 / 88px. The serif-versus-geometric mismatch is most visible between the icon and the two text marks.
01
SELECTED WORK · 01

Planner App

Numeral: Fraunces 300. Serif letterforms read editorial and dramatic.

  • v4
SELECTED WORK · 03

MOB analysis

Icon: lucide stroke 1.5. Geometric sans line-work with rounded caps.

  • v4
PLN
SELECTED WORK · 01

Planner App

Monogram: Fraunces 400. Same family as numeral, half the size.

  • v4
What you're seeing in v4. The Fraunces marks have flared terminals, bracketed serifs, and a modulated stroke — they carry obvious serif-family DNA. The lucide icon has round caps, constant stroke width, and geometric primitives — it carries obvious sans-family DNA. At close range the mismatch is subtle; at normal viewing distance the three cards look like they were authored by three different people. This is the "three fonts in one frame" problem: the shared shell from v4 fixed the sizing drift but didn't fix the family drift.
After — v5 typography. Numeral = Geist 500 / 120px; Monogram = Geist 500 / 72px; Icon = lucide stroke 2.25 / 88px. One family, three expressions.
01
SELECTED WORK · 01

Planner App

Numeral: Geist 500. Clean lining figures, tight tracking, tabular-nums locked.

  • v5
SELECTED WORK · 03

MOB analysis

Icon: lucide stroke 2.25. Heavier stroke visually matches Geist 500 glyph weight.

  • v5
PLN
SELECTED WORK · 01

Planner App

Monogram: Geist 500. Same family as numeral, half the size, same stroke weight.

  • v5
Why Geist and not Fraunces. The choice is forced by lucide. You cannot make a geometric-sans icon library visually match a transitional-serif display face without either replacing lucide (huge scope expansion, loses the library we've already committed to) or hand-drawing a custom icon family that matches Fraunces (also huge, and a permanent maintenance burden). The cheaper move is to align the text marks with the icon family. Geist is the portfolio's body sans, so numeral and monogram fall into a font that was already on the page — Fraunces isn't displaced, it's returned to the role it was drawn for (display headings, prose, long-form writing). The gradient border, register colours, shared shell, and sizing rules from v4 are all unchanged.
Writing-register row (v5) — same typography rules, violet register, to confirm the family holds on the writing side too.
§2 · featured cards

Four ways to say "start here"

A featured card is the anchor of a grid — the "start here" mark. There is at most one per grid (the landing 2×2, the /projects index hero, the /writing index hero). It doesn't replace hierarchy from content — it reinforces the hierarchy that's already there from position and writing.

With Q1 resolved to gradient-mark is reserved for featured cards only, the gradient leaves the default-content pool entirely. It becomes a featured-card treatment, not a visual type the author can pick. Four variations below, from lightest to heaviest.

F1 — Gradient border. 1.5px gradient runs the royal→violet wash around the card perimeter. Strip content is whatever the author picks (screenshot, diagram, numeral, icon, monogram). The border alone marks the card as featured. Lightest treatment.
F2 — Gradient kicker pill. The kicker label becomes a small gradient pill. Strip content, border, and body are all unchanged from a normal card. Subtle content-level treatment.
F3 — Gradient-mark strip. The imagery slot itself is the signature gradient — radial royal plus radial violet on a deep royal-1 background with a subtle diagonal ruled texture. Per Q1, this strip type only exists on featured cards. An optional label can sit at the bottom-left as a meta-mark. Strong strip-level treatment.
Featured · 01
SELECTED WORK · 01

Planner App

The strip IS the gradient. No screenshot, no diagram, no numeral — the gradient mark is the imagery. Strongest "featured" signal short of combining treatments.

  • TYPESCRIPT
  • GTD
Featured · Essay
ESSAY · FEATURED

Writing as research

Register-neutral by design — the gradient is the signal, register colour still reads in kicker, tags, and border on hover.

  • craft
  • meta
F4 — Combined hero: border + gradient-mark strip. The maximum treatment. Gradient border around the perimeter, gradient-mark strip inside. Reserved for the single most important card on the landing page — not "featured cards in general," just the one hero card the grid is built around. Use at most once per page.
In context — a landing 2×2 grid with one F4 hero card and three normal cards. This is the rule: one hero, three normal, mixed visual types, mixed registers. The hero reads as the anchor without crowding out the others.
SELECTED WORK · 02

The Weekly

Coles recipes become a single consolidated shopping trolley. POC in three weekends.

  • REACT
  • POC
R/W
NOTE · FRAGMENT

Read-write reciprocity

Writing as the thing that forces you to understand what you were claiming.

  • notes
Option Where the gradient lives Strip freedom Strength Good for
F1 · Gradient border Perimeter Any visual type Light Any featured card where strip content needs to do the talking. Plays well with screenshot and diagram.
F2 · Gradient kicker pill Content (kicker label) Any visual type Lightest Featured-tagging without perimeter weight. Subtle — might read as "just a label style" at distance.
F3 · Gradient-mark strip Strip (fills the whole slot) None — the strip IS the gradient Strong Featured cards where no specific imagery earned the slot. Reserved — not a "no screenshot available" fallback.
F4 · Hero (border + strip) Perimeter + strip None Maximum The single anchor card on the landing grid. At most one per page. The clearest "start here" the system allows.

Recommendation

Ship all four as available treatments, but apply them with a fixed rule. The rule has two parts:

  1. Default featured treatment is F1 (gradient border). It leaves the strip open to any visual type, so the author's content choice isn't overridden by the featured signal. This is what every non-hero featured card gets — /projects index hero, /writing index hero, related-work footer anchors.
  2. F4 (hero) is reserved for the landing 2×2's anchor card. At most one per page, and only on the landing grid — never inside a flat index. F3 on its own is never used directly by an author; it only exists as the strip half of F4.

F2 (gradient kicker pill) drops out. Two reasons: it's too subtle to carry a featured signal on its own at a glance, and it introduces a second role for the kicker label that conflicts with the kicker's existing job (index number, type label). One more element is one more thing to maintain — and F1 already covers the "light featured" case better.

Why this works. F1 plays with every visual type (author keeps control of the imagery slot), F4 is the unambiguous hero (one per page, impossible to confuse with "just a featured card"), and the gradient-mark strip never leaks out of F4 context — which is exactly the discipline Q1 was reaching for. The gradient stays rare and meaningful. Every time a reader sees it, it's saying "this is the one."

Implementation in the Card component: add an optional prop featured?: "border" | "hero" (default: none). The "hero" variant forces the strip to the gradient-mark type regardless of what imagery was passed — the component ignores the other imagery props when featured === "hero". This means the author can't accidentally combine F4 with a screenshot; the combination is either hero treatment or normal treatment, nothing in between.

§3 · decisions locked

Card spec freeze items

With typography unified and featured treatments defined, both open questions from v4 can lock. The card component spec is now a complete design — everything below ships into the Card component's prop surface.

Locked

Q1 Gradient-mark is reserved for featured cards only

The gradient-mark strip type (v-gradmark) is no longer part of the author's default-content pool. It cannot be picked for a regular card — not for palette-subject writing posts, not for "nothing else fits" cases, not as a fallback. The default pool is five visual types: screenshot, diagram, numeral, icon, monogram. Gradient-mark only appears as the strip half of the F4 hero treatment, and F4 is capped at one per page on the landing grid only.

Why stricter than the original recommendation. Keeping gradient-mark available for palette-subject writing posts ("Royal Tonal in twelve steps") looked reasonable, but it creates two authorship contexts for the gradient (featured cards + "my post is about colour") and those two contexts will drift — the line between "about colour" and "mentions colour" is not defensible. Reserving it to a single context (featured hero treatment, one per page) makes the rule mechanical: the gradient marks the hero, nothing else. Palette-subject writing posts use whatever visual type fits (typically numeral or diagram), and if one of them happens to earn the hero slot on the /writing index, the gradient shows up through F4 — not through a separate authorship permission.

Q2 M5 / M6 live in the Screenshot helper, not the Card API

The Card component's imagery surface stays small: kind: "screenshot" | "diagram" | "numeral" | "icon" | "monogram". The M5 (meta split) and M6 (detail zoom) variants are composition decisions inside the <Screenshot> helper, not sub-types on Card. The helper signature:

<Screenshot src="..." variant="detail" | "meta-split" eyebrow? num? />

Default is variant="detail" (M6). M5 is explicit opt-in — the author types variant="meta-split" when they want anchor treatment. This makes M6 the common case (zero effort) and keeps M5's "max one per grid" rule a deliberate choice at the call site, not an accident. The hierarchy rule lives in the style guide and is enforced by review, since it's a page-level constraint TS can't check anyway.

Why not sub-types on Card. The main argument for sub-typing was compile-time misuse prevention — but the constraint we'd be enforcing ("max one M5 per grid") is about page composition, not component use. TS can't express "at most one instance in this map call" without turning the Card surface into something much more complicated. Keeping Card's surface simple means M5/M6 can evolve (new variants, retired variants) without a Card breaking change. The composition decision stays in the component that implements it.

Typography Geist sans for all centred marks

Numeral: Geist 500, 120px, tight tracking, tabular lining nums. Monogram: Geist 500, 72px, slightly looser tracking. Icon: lucide stroke-width 2.25 at 88px. All three marks share the shared shell from v4 (same background, padding, grid texture, register tint). Fraunces stays on headings and long-form prose — it is not used for UI marks anywhere in the card system.

Featured Two treatments: F1 (border) and F4 (hero)

featured?: "border" | "hero" on the Card component. F1 "border" adds the gradient perimeter and leaves strip content untouched — any visual type is valid. F4 "hero" forces the strip to gradient-mark regardless of the imagery props passed, and is capped at one per page on the landing grid only. F2 (kicker pill) and F3 (strip-only) are dropped from the API surface.

§4 · next

Card spec freezes, foundation work resumes

If v5 locks, the card component spec is frozen. The next work on the critical path is not a card iteration — it's chunk 4a.4, the workspace-wide writing and imagery style guide at .claude/rules/writing-style.md. Foundation work (4a.4 → 4a.5 → 4a.6) still gates chunk 4b.

The card component gets built in chunk 4c.1 alongside the case study layout mockups. By then the spec is stable, the decisions are locked, and the component can be written straight from this doc — no more live iteration. The iteration trail (v1 → v5) will be preserved for the eventual design-system case study, where "here's what we rejected at each step and why" becomes the spine of the narrative.

To confirm before this locks. Three items, all small: (1) Geist 500 visual weight — too light at 120px, or right? If too light, v6 bumps to Geist 600 and lucide stroke to 2.5. (2) F1 border thickness — 1.5px currently, could be 1px (subtler) or 2px (louder). (3) F4 gradient-mark label placement — bottom-left "Start here · 01" as shown, or drop the label entirely and let the border + gradient carry it alone. Your call on all three.