weekly

Design system

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.

Colour

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

--background

warm paper

L oklch(0.98 0.006 85)
D oklch(0.205 0.008 75)

--foreground

near-black ink

L oklch(0.24 0.012 75)
D oklch(0.915 0.008 85)

--card

L oklch(0.99 0.004 85)
D oklch(0.245 0.009 75)

--primary

the one accent — moss

L oklch(0.5 0.085 168)
D oklch(0.66 0.1 168)

--secondary

L oklch(0.955 0.008 85)
D oklch(0.285 0.009 75)

--muted

L oklch(0.955 0.008 85)
D oklch(0.285 0.009 75)

--muted-foreground

meta text · 7.3:1

L oklch(0.44 0.016 78)
D oklch(0.72 0.012 80)

--accent

one-shot reward wash

L oklch(0.94 0.02 165)
D oklch(0.34 0.035 168)

--destructive

calm brick, never alarm

L oklch(0.5 0.11 30)
D oklch(0.62 0.15 30)

--border

hairline

L oklch(0.905 0.01 85)
D oklch(0.32 0.01 75)

Category tint ramp — used as ~12–40% washes

--tint-1

moss

L oklch(0.92 0.035 165)
D oklch(0.5 0.07 165)

--tint-2

ochre

L oklch(0.93 0.035 85)
D oklch(0.52 0.075 85)

--tint-3

slate blue

L oklch(0.93 0.03 250)
D oklch(0.5 0.07 250)

--tint-4

lavender

L oklch(0.93 0.028 285)
D oklch(0.5 0.065 285)

--tint-5

plum

L oklch(0.93 0.03 320)
D oklch(0.5 0.07 320)

--tint-6

sea

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-ink

L oklch(0.58 0.09 165)
D oklch(0.72 0.11 165)

--tint-2-ink

L oklch(0.62 0.1 85)
D oklch(0.76 0.12 85)

--tint-3-ink

L oklch(0.58 0.08 250)
D oklch(0.72 0.1 250)

--tint-4-ink

L oklch(0.58 0.08 285)
D oklch(0.72 0.1 285)

--tint-5-ink

L oklch(0.58 0.08 320)
D oklch(0.72 0.1 320)

--tint-6-ink

L oklch(0.58 0.08 200)
D oklch(0.72 0.1 200)

Daypart accents — the Today ambient palette (never interactive)

--daypart-dawn

morning

L oklch(0.65 0.09 60)
D oklch(0.72 0.1 60)

--daypart-day

afternoon

L oklch(0.52 0.09 165)
D oklch(0.66 0.1 165)

--daypart-dusk

evening

L oklch(0.55 0.09 320)
D oklch(0.66 0.1 320)

--daypart-night

night

L oklch(0.45 0.05 265)
D oklch(0.62 0.08 265)

Typography

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 page
14pxA calm, well-set planner page
16pxA calm, well-set planner page
20pxA calm, well-set planner page
24pxA calm, well-set planner page
34pxA calm, well-set planner page

Micro-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 specs
text-mini11pxState badges, calendar & tab titles, dense meta
text-list15pxWeek / Today list-row titles (a step under base)

Roles

UI text (labels, controls, meta)14px
Reading text (notes, prose)16px
Display (view headings, Today hero)24 / 34px
Section labels — sentence case, not eyebrowstext-sm font-medium

Motion

Motion 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

enter/exit + anything answering the user--ease-out
on-screen movement--ease-in-out
sheet / drawer travel--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)

Scheduled block

Budgets

Press feedback (every Button scales to 0.97)100–160ms
Popovers / entrances150–250ms
Everything in-app< 300ms
Ambient (Today now-dot breathe, landing drift)4s / 7s

Reduced 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.

Components

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)

Patterns

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

ReadyIn motionAlmostDoneExtra

Ready → In motion → Almost → Done → Extra. Never “overdue”, “missed”, or “streak”.

Pass indicator — dots, not a fraction (ui/pass-dots)

Ready
In motion
Done
Extra +2

The exact tally stays in aria-label; the dots carry the glance.

Category identity — stable hue per life-area

HealthWorkPersonalLearningHomeSocial

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

Work focus · 9–12

Progress — accent + neutral, plus the word

Almost

Anti-patterns

Rejected in review. The calm is the moat — these are the temptations that would trade it for a dashboard.

  • Colored left-edge stripes on cards (the canonical AI-design tell)
  • Stat cards, KPI rows, metric tiles, notification bells, avatars
  • Shame mechanics: streaks, “you missed X”, red incomplete-highlights
  • Modals where an inline editor, popover, or drawer works
  • Raw hex / arbitrary Tailwind values instead of tokens
  • A second client store of domain data (the query cache is the only copy)