Design system
Cellar — the system behind the portfolio.
A warm, low-light editorial system: brown night surfaces, one ochre accent, serif headlines against mono metadata. Everything below is the working source of truth — tokens, components, motion and the rules that hold them together.
The palette is defined in src/styles.css as OKLCH primitives, then mapped to semantic tokens. Components consume the semantic layer only.
Palette
Soot
--soot
oklch(0.14 0.012 50)
Page background. Warm near-black.
Bark
--bark
oklch(0.20 0.020 45)
Panels, popovers, floating bars.
Bark 2
--bark-2
oklch(0.25 0.025 45)
Lifted surfaces, secondary buttons.
Ochre
--ochre
oklch(0.72 0.13 70)
Primary accent: numbers, links, focus.
Amber
--amber
oklch(0.78 0.14 80)
Gradient partner to ochre. Glow only.
Sage
--sage
oklch(0.66 0.06 145)
Cool counterpoint. Method / process cues.
Olive
--olive
oklch(0.55 0.07 120)
Deep sage for icons and strokes.
Bone
--bone
oklch(0.93 0.02 80)
Foreground text. Never pure white.
Semantic tokens
| Swatch | Utility | Token | Maps to |
|---|---|---|---|
| background | --background | soot | |
| foreground | --foreground | bone | |
| card | --card | oklch(0.18 0.018 45) | |
| muted | --muted | oklch(0.24 0.015 45) | |
| muted-foreground | --muted-foreground | oklch(0.68 0.025 70) | |
| primary | --primary | ochre | |
| accent | --accent | sage | |
| border | --border | oklch(0.32 0.020 50 / 80%) | |
| ring | --ring | ochre | |
| destructive | --destructive | oklch(0.55 0.20 28) |
Gradients
Ember
--gradient-ember
CTAs, active pills, accent fills.
Cellar
--gradient-cellar
Top-of-page ambient light.
Vignette
--gradient-vignette
Edge darkening over imagery.
--font-display
Instrument Serif
Headlines, chapter numbers, card titles. Weight 400 only, tracking −0.02em.
--font-sans
Inter
All body copy, buttons and UI labels.
--font-mono
JetBrains Mono
Eyebrows, metadata, stats, tokens.
Display / hero
Driving innovation 0 → 1
Display / section
Strategy: the opportunities
Display / card title
The JOCO Method
Body / large
I generate user and business value with deep research.
Body / base
Paragraph copy sits at 85–90% foreground opacity.
Body / small
Supporting caption and metadata copy.
Eyebrow / mono
Chapter 04 — the marketplace
Hand / annotation
connecting dots to find the new future
Rhythm
Content sits in a max-w-6xl container with px-6 gutters. Chapters are separated by py-24 on mobile and py-32 frommd up. Inside a block, steps go 3 → 6 → 10 → 16.
Radius
Base --radius: 1rem. Cards use rounded-2xl, pills and controls use rounded-full. Nothing is square except hairlines.
rounded-md
rounded-lg
rounded-2xl
rounded-3xl
rounded-full
Dividers
.hairline — transparent → border → transparent
--shadow-warm
Lifts imagery and modals off the page. The only ambient shadow.
.ember-glow
Ochre halo. Reserved for the portrait and single hero accents.
.grain / .vignette
Film texture at 8% overlay, plus edge vignette on media.
Chapter header
Opens every section: serif number, hairline, mono label.
Buttons
Ember primary, hairline secondary, mono ghost. All rounded-full.
Tags & pills
Metadata chips. 10px mono, uppercase, 0.2em tracking.
Card
rounded-2xl, border-border/60, bg-card, ochre on hover.
01
The problem
Body copy at muted-foreground, one idea per card, no more than three lines.
02
The impact
Body copy at muted-foreground, one idea per card, no more than three lines.
Tabs (scrollable)
Mobile-first horizontal scroller with 44px+ targets.
Accordion
Rotating plus marker, hairline separators, one open at a time.
Interviews, journey mapping, opportunity framing.
Stat
Serif figure, mono caption. Used for outcomes.
10k
Views in 1 day
+38%
Conversion lift
12
Markets shipped
Quote
Serif, ochre rule, mono attribution.
I'm a system designer, connecting dots to find the new future.
Form controls
Inputs use --input background and ochre focus ring.
Every entrance is driven by an IntersectionObserver that adds .is-visible. Easing is cubic-bezier(0.22, 1, 0.36, 1) for fades and cubic-bezier(0.76, 0, 0.24, 1) for masks. Stagger through --reveal-delay.
.reveal
Body copy and generic blocks. Fade + 14px lift.
.reveal-slow
Ambient / supporting text. 1200ms fade only.
.reveal-mask
Eyebrows and dividers. Clip-path wipe from bottom.
.reveal-block
Cards, thumbnails, image tiles. Scale 0.96 → 1.
.reveal-drift
Side captions and metadata rails. X drift.
.reveal-title
Hero headline. Line-by-line mask, 110ms stagger.
.reveal-words
Short statements. Per-word stagger at 45ms.
All reveals are neutralised under prefers-reduced-motion: reduce
Do
- Use semantic tokens (bg-card, text-muted-foreground) before raw palette names.
- Keep exactly one ochre accent per viewport-sized block — it marks the point of interest.
- Set metadata, eyebrows and numbers in mono with 0.2–0.4em tracking, uppercase.
- Separate sections with .hairline, not solid 1px borders across the full width.
- Pair every reveal with a stagger delay via --reveal-delay rather than new keyframes.
Don't
- No pure #fff or #000 — bone and soot carry the warmth of the whole system.
- No hardcoded hex or text-white / bg-black utilities in components.
- No second serif or sans family; Instrument Serif + Inter + JetBrains Mono only.
- No drop shadows other than --shadow-warm and --shadow-glow.
- No motion longer than 900ms, and always honour prefers-reduced-motion.
Accessibility floor
- Body text ≥ 4.5:1 against soot; mono metadata never below 14px equivalent weight.
- Touch targets ≥ 44px on mobile controls, tabs and menu items.
- Focus visible everywhere via
ring-[color:var(--ring)]. - Motion, marquees and floats all respect reduced-motion.