weekly
A living reference for weekly’s visual language — a quiet, well-set planner page, distinct through typography and restraint rather than decoration. Everything here renders the real tokens, primitives, and helpers, so it can’t drift from the app. The rationale lives in docs/design-principles.md; the code conventions in docs/frontend-architecture.md.
Warm paper, near-black ink, and one moss accent. Category hues arrive only as ~12% tints or slim ink marks — never saturated fills. Every swatch below paints its real token, so these can't drift from the app.
Paper, ink & accent
--backgroundwarm paper
L oklch(0.98 0.006 85)
D oklch(0.205 0.008 75)
--foregroundnear-black ink
L oklch(0.24 0.012 75)
D oklch(0.915 0.008 85)
--cardL oklch(0.99 0.004 85)
D oklch(0.245 0.009 75)
--primarythe one accent — moss
L oklch(0.5 0.085 168)
D oklch(0.66 0.1 168)
--secondaryL oklch(0.955 0.008 85)
D oklch(0.285 0.009 75)
--mutedL oklch(0.955 0.008 85)
D oklch(0.285 0.009 75)
--muted-foregroundmeta text · 7.3:1
L oklch(0.44 0.016 78)
D oklch(0.72 0.012 80)
--accentone-shot reward wash
L oklch(0.94 0.02 165)
D oklch(0.34 0.035 168)
--destructivecalm brick, never alarm
L oklch(0.5 0.11 30)
D oklch(0.62 0.15 30)
--borderhairline
L oklch(0.905 0.01 85)
D oklch(0.32 0.01 75)
Category tint ramp — used as ~12–40% washes
--tint-1moss
L oklch(0.92 0.035 165)
D oklch(0.5 0.07 165)
--tint-2ochre
L oklch(0.93 0.035 85)
D oklch(0.52 0.075 85)
--tint-3slate blue
L oklch(0.93 0.03 250)
D oklch(0.5 0.07 250)
--tint-4lavender
L oklch(0.93 0.028 285)
D oklch(0.5 0.065 285)
--tint-5plum
L oklch(0.93 0.03 320)
D oklch(0.5 0.07 320)
--tint-6sea
L oklch(0.93 0.03 200)
D oklch(0.5 0.07 200)
Ink-strength tints — slim identity marks only (dots, segments)
--tint-1-inkL oklch(0.58 0.09 165)
D oklch(0.72 0.11 165)
--tint-2-inkL oklch(0.62 0.1 85)
D oklch(0.76 0.12 85)
--tint-3-inkL oklch(0.58 0.08 250)
D oklch(0.72 0.1 250)
--tint-4-inkL oklch(0.58 0.08 285)
D oklch(0.72 0.1 285)
--tint-5-inkL oklch(0.58 0.08 320)
D oklch(0.72 0.1 320)
--tint-6-inkL oklch(0.58 0.08 200)
D oklch(0.72 0.1 200)
Daypart accents — the Today ambient palette (never interactive)
--daypart-dawnmorning
L oklch(0.65 0.09 60)
D oklch(0.72 0.1 60)
--daypart-dayafternoon
L oklch(0.52 0.09 165)
D oklch(0.66 0.1 165)
--daypart-duskevening
L oklch(0.55 0.09 320)
D oklch(0.66 0.1 320)
--daypart-nightnight
L oklch(0.45 0.05 265)
D oklch(0.62 0.08 265)
A serif display face (Fraunces) for the few large moments, a humanist sans (Inter) for everything else. The serif is what makes this not-a-dashboard — used sparingly, never below 24px.
What matters this week?
font-display · Fraunces (variable, optical sizing)
One pass gets this there. Nothing planned — keep it spacious. You can rest without guilt.
font-sans · Inter · body defaults to 14px / 1.5
Type scale
13pxA calm, well-set planner page14pxA calm, well-set planner page16pxA calm, well-set planner page20pxA calm, well-set planner page24pxA calm, well-set planner page34pxA calm, well-set planner pageMicro-steps
Three named sub-base sizes for dense secondary text — the former text-[10/11/15px] drift, now tokens. Reach for these before inventing another size; the scale below is the whole vocabulary.
text-micro10pxCalendar block times, gutter hours, mono specstext-mini11pxState badges, calendar & tab titles, dense metatext-list15pxWeek / Today list-row titles (a step under base)Roles
14px16px24 / 34pxtext-sm font-mediumMotion only confirms direct manipulation, and stays under 300ms. Entrances start at 96–98% scale + fade (never from nothing) via @starting-style, so rapid triggers retarget instead of restarting. Everything here respects prefers-reduced-motion.
Easing tokens
--ease-out--ease-in-out--ease-drawer--ease-out: cubic-bezier(0.23, 1, 0.32, 1) · --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1) · --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1)
Live entrance (.block-enter)
Budgets
100–160ms150–250ms< 300ms4s / 7sReduced motion (prefers-reduced-motion): positional travel drops, the comprehension-aiding fade stays — gentler, not none. The ≤2% scale entrances are kept as the “gentle enough” ceiling.
The owned shadcn primitives in src/components/ui/, shown live in every variant. Edit these to match the tokens — never wrap them in extra layers.
Button · variants
Button · sizes & icons
Inputs
Overlays & loading
Skeleton — one sheen crossing the stack (index staggers it). See Patterns for the full loading state.
Feedback (sonner)
The recurring, data-bearing marks — derived from the same lib helpers the app uses, so they stay identical everywhere.
Intention state — the sanctioned uppercase micro-label
Ready → In motion → Almost → Done → Extra. Never “overdue”, “missed”, or “streak”.
Pass indicator — dots, not a fraction (ui/pass-dots)
The exact tally stays in aria-label; the dots carry the glance.
Category identity — stable hue per life-area
categoryTintIndex() — a stable hash, no stored assignment. Dots & slim segments only, never a left-edge stripe.
Loading — the page’s furniture, arriving empty
Still waking up. The first load of the day takes a moment.
Three stages (lib/loading-stage): nothing for 180 ms, then placeholder shapes lit by one travelling sheen, then this line once the wait passes 2.4 s. Never a spinner, never a percentage.
Fixed rhythm — reserved-time hatching
Progress — accent + neutral, plus the word
Rejected in review. The calm is the moat — these are the temptations that would trade it for a dashboard.