North Star: 「些細でも経験値、今日自分頑張ったなと自分を肯定できる感覚」
Every trivial task gets visualized as accumulation. The user closes the app feeling validated, never judged. All design decisions are evaluated against this north star.
- What this is: Personal task tracker + BrainDump archive whose centerpiece is an Activity Heatmap visualizing every completed task as warm density over a year.
- Who it's for: Solo, taste-driven productivity users who want quiet self-affirmation, not KPI grading or social comparison. Single-user (no team features).
- Space/industry: Personal productivity. Adjacent: Things 3, Bear, Linear, Reflect, Tana, Cron. Distant: Notion (too much), Todoist (KPI guilt), Asana (team).
- Project type: Hybrid —
(main)/*is the dashboard app,/loginand/sign-upare auth, marketing surfaces may follow. Web + Electron (macOS only).
- Direction: Warm Cathedral — industrial-utilitarian craft warmed by daylight. The Heatmap year is the stained-glass window; accumulated days make the room glow.
- Decoration level: Intentional — paper-grain noise on backgrounds (≤2% opacity), no decorative blobs, no gradient orbs. Visual weight lives in the Heatmap; chrome stays quiet.
- Mood: Quiet pride. A workshop with a real window, not fluorescent tubes. Studio Ghibli morning, not corporate dashboard.
- NOT: Linear (cold completionism), Notion (feature bloat), Things 3 (precious minimalism), Todoist (KPI scoring), gamified RPG (badges/SFX/confetti).
- Reference touchstones: Apple Activity Rings (closing-the-loop emotional payoff), Strava personal heatmap (accumulation without judgment), Studio Ghibli morning light, Tana / Reflect (personal-tool craft).
corelive ships an opinionated concept — the North Star and Warm Cathedral. That concept owns every default and preset. It is not, however, a cage for the user.
The rule: an option that falls outside the concept is allowed only after the on-concept choice is fully built and set as the default.
- The default is always concept-faithful — a fresh install embodies the concept with zero configuration.
- On-concept presets stand on their own — someone who never opens settings still gets the full intended experience.
- Out-of-concept options are opt-in, never forced, never the default, and sit one layer deeper (settings), not in the user's face.
This holds for design, features, and UX alike — themes, sound, motion, copy, all of it. The concept owns the default; the user owns the deviation.
Never deviable: the North Star itself — self-affirmation over judgment. You may offer an off-concept aesthetic (a louder theme, an optional sound); you may not offer one that grades, shames, or guilts (no opt-in streak-shame, no KPI-percentage mode). Style is negotiable; the emotional contract is not.
Instances. Sound palette — default is silence (on-concept); the soft cues are the opt-in layer, off per moment until chosen. Theme system — Warm Cathedral is the default; the colored families are opt-in deviations. Next customization? Same shape: ship the concept default, make it sufficient, then let the option ride on top.
Three-font stack, deliberately unusual for the productivity category. The serif display is the loudest differentiation move — productivity apps converge on geometric sans (Inter / Geist / DM Sans), so a serif says "this is craft, not corporate" within the first 0.3 seconds.
- Display / Hero (≥18px): Newsreader — variable serif, optical sizes built-in. Editorial without being precious. Loaded from Google Fonts.
- Body / UI: Inter Tight — variable sans, dense and modern. Compact enough for dashboard density; pairs with Newsreader's character. Loaded from Google Fonts. Never plain Inter (AI default), never DM Sans (category convergence).
- Data / Code: Geist Mono — tabular-nums, warmer than JetBrains Mono, more disciplined than Fira Code. Loaded from Vercel Fonts or self-hosted.
- Loading: Google Fonts (
display=swap) for Newsreader + Inter Tight, Vercel Fonts or self-hosted for Geist Mono.
Inter, Roboto, Arial, Helvetica, DM Sans, Geist Sans, Plus Jakarta Sans, Open Sans, Lato, Montserrat, Poppins, Space Grotesk. All are productivity-category convergence fonts; using them dilutes the differentiation Newsreader earns.
| Tier | Size | Font | Weight | Notes |
|---|---|---|---|---|
| Hero | clamp(40px, 5vw, 64px) |
Newsreader | 600 | Marketing/empty-state moments |
| H1 | 36px | Newsreader | 600 | Section headers, dialog titles |
| H2 | 24px | Newsreader | 500 | Subsection |
| H3 | 18px | Inter Tight | 600 | Card headers, group labels |
| Body | 15px | Inter Tight | 400 | Default reading size |
| Small | 13px | Inter Tight | 400 | Secondary copy, footers |
| Caption | 12px | Inter Tight | 500 | text-transform: uppercase, letter-spacing: 0.05em |
| Data | 13px | Geist Mono | 500 | font-variant-numeric: tabular-nums for counts/dates |
OKLCH color space throughout. Departing deliberately from the current cold-neutral baseline to introduce warmth via small chroma values (0.005–0.015) on neutrals plus an amber-warm accent.
- Restrained chromatic accent: amber-warm is the only saturated color in chrome. Heatmap cells get the chromatic stage.
- Warm neutrals: every solid neutral surface carries OKLCH chroma ≥0.016 (light) / ≥0.026 (dark) — the golden-hour warm-up (2026-06-03) that pushed the whole UI off clinical white toward late-afternoon light. Earlier values (0.005–0.015) read as "Linear cold". Exception: dark
--border/--inputstay translucent white (oklch(1 0 0 / 8–12%)); they composite over the warm coal background, so the rendered line reads warm without carrying its own chroma. - Temperature gradient over lightness gradient for the Heatmap — the eureka. Cooler-empty → hotter-full, not lighter → darker.
| Token | Value | Role |
|---|---|---|
--background |
oklch(0.975 0.016 80) |
Golden-hour paper (warmed off white) |
--card / surface |
oklch(0.972 0.018 78) |
Sand-lit elevated surface |
--popover |
oklch(0.972 0.018 78) |
Same as card |
--foreground |
oklch(0.18 0.015 30) |
Deep warm charcoal (not pure black) |
--muted-foreground |
oklch(0.5 0.026 56) |
Earthy taupe for secondary copy (5.6:1 on card, AA) |
--border |
oklch(0.908 0.022 76) |
Warm dawn gray |
--input |
oklch(0.908 0.022 76) |
Form input border |
--ring |
oklch(0.56 0.16 50) |
Focus ring = primary amber |
--primary |
oklch(0.56 0.16 50) |
Amber CTA |
--primary-foreground |
oklch(0.99 0 0) |
Paper text on amber |
--secondary |
oklch(0.942 0.024 78) |
Quiet button bg |
--secondary-foreground |
oklch(0.18 0.015 30) |
Same as foreground |
--accent |
oklch(0.942 0.024 78) |
Hover surfaces |
--accent-foreground |
oklch(0.18 0.015 30) |
|
--muted |
oklch(0.942 0.024 78) |
Disabled/quiet bg |
--destructive |
oklch(0.6 0.20 25) |
Clay red, retuned warmer than the default oklch destructive |
--primary/--ringdarkened L0.62 → L0.56 (2026-06-11, WCAG AA). White--primary-foregroundon the old lighter amber was only 3.75:1 — below the AA 4.5:1 floor for CTA text. L0.56 lifts white-on-amber to 4.81:1 while staying brand amber (hue 50, chroma 0.16).--sidebar-primary/--sidebar-ringmove with it;--chart-1stays L0.62 (data-viz fill, no overlaid text). Dark mode already passed (dark ink on lifted amber, 6.93:1). Locked bycathedral-contrast.test.ts.
Dark mode is a redesign of the surfaces, not a saturation reduction. The warm undertone (chroma 0.026–0.028 on hue 40–66, raised in the 2026-06-03 golden-hour warm-up from 0.012–0.015) makes the coal glow like embers rather than slate.
| Token | Value | Role |
|---|---|---|
--background |
oklch(0.172 0.026 40) |
Ember coal, warm ochre undertone |
--card / surface |
oklch(0.235 0.028 44) |
Warm coal |
--popover |
oklch(0.235 0.028 44) |
|
--foreground |
oklch(0.96 0.005 75) |
Warm white |
--muted-foreground |
oklch(0.74 0.026 66) |
Warm fog (7.3:1 on card, AA) |
--border |
oklch(1 0 0 / 8%) |
Soft glow boundary |
--input |
oklch(1 0 0 / 12%) |
|
--ring |
oklch(0.7 0.16 55) |
Focus ring slightly brighter than primary in dark |
--primary |
oklch(0.7 0.16 55) |
Amber lifted for dark contrast |
--primary-foreground |
oklch(0.16 0.012 35) |
Background on amber |
--secondary |
oklch(0.285 0.026 44) |
|
--accent |
oklch(0.285 0.026 44) |
|
--muted |
oklch(0.285 0.026 44) |
|
--destructive |
oklch(0.7 0.19 25) |
Clay red lifted |
This is the project's most consequential color decision. Departs from GitHub's green ramp to a warm temperature gradient (paper → dawn → amber → terracotta). Chroma and hue move; lightness moves only enough to stay readable.
| Level | Light | Dark | Meaning |
|---|---|---|---|
| 0 (empty) | oklch(0.96 0.008 80) paper |
oklch(0.22 0.012 40) warm coal |
Rest, not failure |
| 1 | oklch(0.89 0.06 75) first dawn light |
oklch(0.32 0.06 50) ember |
Started the day |
| 2 | oklch(0.78 0.11 70) amber |
oklch(0.45 0.10 55) copper |
Productive |
| 3 | oklch(0.65 0.14 60) honey |
oklch(0.58 0.13 60) brass |
Full day |
| 4 | oklch(0.55 0.16 40) terracotta |
oklch(0.70 0.15 65) warm sun |
Cathedral lit |
- success:
oklch(0.65 0.13 145)moss — confirmations only, NEVER as primary CTA - warning:
oklch(0.7 0.14 80)caution amber - error:
oklch(0.6 0.20 25)(light) /oklch(0.7 0.19 25)(dark) — clay red - info:
oklch(0.6 0.10 230)slate blue — rare, advisory only
Warm Cathedral is the soul of the product; the colored families are optional self-expression layered on top of it without ever touching it. The ten rules below govern how themes are added, derived, and applied.
- The default is sacred. Warm Cathedral light + dark are the untouched default, byte-for-byte. Every other theme is derived or layered; none may alter the cathedral tokens. The cathedral pair stays hand-authored in
src/globals.css(never machine-generated) — brand safety over DRY. - 12 themes, 6 families. Warm Cathedral (default) + five colored families — Harbor (calm blue), Grove (forest green), Rose Tea (dusty rose), Iris (soft violet), Graphite (near-neutral slate) — each in
{light, dark}. - The registry is the single source of truth.
src/lib/themes/registry.tsholds every theme as a discriminated union:PreservedTheme(cathedral — metadata only) orDerivedTheme(a colored family's accent L/C/H, a neutral tint, and a 5-stop heatmap hue path).THEMES, the picker, anduseThemeAxisall derive from it — never edit them directly. - Colored families are generated, the default is not. A build-time generator (
scripts/generate-theme-css.ts) reads the registry and emits static CSS tosrc/lib/themes/generated.css, one:root[data-theme='id']block per derived theme (specificity0,2,0, so it beats the cathedral:rootregardless of@importorder). The generator skips everypreservetheme — cathedral is never emitted. WCAG math (culori) runs at build/test time only; it is never shipped in the client bundle.pnpm theme:generateregenerates;theme:checkfails CI if the committed CSS is stale. - Derivation is systematic, identity is preserved. A derived family tints its neutral surfaces toward the family hue at the cathedral lightness ladder (so AA is L-driven and always holds); the accent is the family's own L/C/H; fixed-identity tokens —
--destructive,--chart-1…5— are emitted as the cathedral value unchanged for every theme; foregrounds are contrast-computed for WCAG AA, never hand-picked. - Graphite is the temperature=pride proof. Its accent is nearly desaturated (chroma ~0.02), so the only real chroma on screen is the heatmap bloom. A near-monochrome chrome with a glowing heatmap is the purest statement of the north star: the year you accumulated is the color.
- The picker is two-axis. Settings and the sidebar quick-switch both pick a family and a mode independently. Each family card previews the family's real tokens (surface · accent · text · heatmap ramp) as a composite swatch reconstructed at runtime from the registry — never a single hex dot, never the culori library.
- System belongs to the default only.
Systemis OS-managed and maps only to the Warm Cathedral light/dark pair (it is a light↔dark axis, not a palette). Colored families expose Light/Dark only; choosing a colored family while on System collapses to an explicit id. - Theme toggle = crossfade. Switching theme crossfades rather than hard-cutting (Motion table, "Theme toggle"). The whole UI — page background/text plus component surfaces (cards, sidebar, popovers) — crossfades together (200ms
ease-out, gated behindmotion-safe:—prefers-reduced-motionusers get an instant change). To keep hover/focus instant, the fade is scoped to a transienttheme-transitionclass that<ThemeTransition>puts on<html>only for the switch window. Decided 2026-06-11 (design-review #5): broadened from body-only to all surfaces. - Theme lives in the web, applied as
data-theme. Persistence is 100% weblocalStorage(storageKey="corelive-theme"); next-themes setsdata-themeon<html>. Tailwinddark:is keyed to the app'sdata-theme(@custom-variant darkon[data-theme$=dark]), never the OSprefers-color-scheme. The Electron shell reads nothing native (its vestigialappearance.theme/accentColorwere removed). An unknown or stale stored id self-heals to the default on mount (ThemeAllowlistGuard).
Family signatures (accent hue; full seeds live in the registry):
| Family | Accent hue | Character | Heatmap rest → apex hue |
|---|---|---|---|
| Warm Cathedral | ~50 (amber) | the default craft | 80 → 40 (light) |
| Harbor | 250 | calm blue | 235 → 42 |
| Grove | 145 | forest green | 140 → 42 |
| Rose Tea | 18 | dusty rose | 25 → 38 |
| Iris | 292 | soft violet | 300 → 45 |
| Graphite | 250 (~0 chroma) | near-neutral slate | 250 → 42 |
Accepted hue collisions (both AA-pass, separable by chroma/lightness, flagged in-code for design-review): Grove accent 145 lands exactly on the fixed --chart-2 token (oklch(0.65 0.13 145)) and beside the app's success-green (--color-success, hue ≈149), and a green theme also dilutes green's "success-everywhere" semantic; Rose Tea accent 18 sits near the fixed --destructive (hue 25). Both ship as deliberate taste calls — the family signature is worth more than perfect hue isolation.
The heatmap's temperature = pride. The hot end blooms warm on every theme, forever — it is a CI-enforced invariant, not a guideline.
Adding a colored family must never let the heatmap's payoff drift back toward a "GitHub-green work-grid." The guarantee has three tiers, strongest first. For every registered theme, src/lib/themes/generate-theme-css.test.ts asserts the five heatmap stops (--hm-0 rest → --hm-4 apex):
- The two hottest stops bloom warm (CI-locked seed constraint — the load-bearing gate).
--hm-3and--hm-4hue ∈ [20, 70], regardless of where the family rests. Per-stop hue is the only thing aheatmapHuesseed actually controls, so this is what makes "more completions = hotter" read the same across families. A seed that keeps a cool body all the way up (e.g. green[145, 145, 145, 145, 42]) fails the build — the cool rest with a single warm apex is the GitHub-green failure mode. - Lightness moves monotonically and chroma intensifies toward the apex (structural — holds by construction). Light mode rests palest and deepens to the apex; dark mode rests darkest and brightens to it; the fullest day is the most saturated cell. The generator reuses the cathedral L/C ladder verbatim for every family, so the test here guards against a generator regression, not a bad seed — a seed cannot move L or C.
- The cool rest stops stay the family's hue (intentional, unconstrained).
--hm-0…--hm-2carry the family color — Grove rests green, Iris rests violet — and are deliberately not forced warm. In a real contribution grid most cells are low-intensity, so a family genuinely reads in its own hue at the cool end, by design. The rest hue is the family's; the payoff hue is the project's.
Per-theme WCAG AA gates ride alongside (fg/bg ≥ 7, muted-fg/card ≥ 4.5, primary-fg/primary ≥ 4.5). A new family that cools its hot end, breaks the L/C ramp, or fails AA fails the build — the north-star payoff cannot regress by accident.
- Base unit: 4px
- Density: Comfortable (between Things 3 and Linear)
- Scale:
2xs(4) xs(8) sm(12) md(16) lg(24) xl(32) 2xl(48) 3xl(64) 4xl(96) - Card padding:
lg(24px) - Section spacing:
2xl(48px) between major sections;xl(32px) within sections
- Cell size: 12px minimum, 32px maximum (per Heatmap Cathedral plan, eng review D6 lock)
- Cell gap: 3px (light theme), 4px (dark theme — needs slightly more breathing room)
- Approach: Hybrid — grid-disciplined for app surfaces, editorial moments for hero / empty states
- Grid: 12-column, 24px gutter
- Max content width: 1180px (intentionally tighter than the 1200px convention; the few px signal restraint)
- Border radius:
- sm: 4px (chips, small badges)
- md: 8px (buttons, inputs)
- lg: 12px (cards, dialogs)
- xl: 16px (large modals, hero cards)
- full: 9999px (avatar, pill badges)
- Heatmap container: full content-width, the home page centerpiece — never demoted to sidebar
- Sidebar: 248px, top-aligned navigation, Caption-style section labels
Intentional choreography. Most motion is in the 150–250ms range. The one motion moment that earns extended duration is the completion celebration — the only place where the design "performs" for the user.
- Approach: Intentional (between minimal-functional and expressive)
- Easing:
- enter:
ease-out - exit:
ease-in - move:
ease-in-out - celebration:
cubic-bezier(0.16, 1, 0.3, 1)(gentle overshoot)
- enter:
- Duration:
- micro: 50–100ms (focus rings, color shifts)
- short: 150–250ms (hover, fade)
- medium: 250–400ms (modal, page transition)
- long: 400–700ms (cell completion fill, the hero motion)
| Action | Motion | Duration | Easing |
|---|---|---|---|
| Task completion → Heatmap cell fill | radial-sweep fill | 400ms | ease-out |
| Task completion → checkbox fill | amber fill (checkbox only, NOT row) | ~200ms | ease-out |
| Hover lift (cell, card, button) | translateY(-1px) + scale(1.04) |
150ms | ease-out |
| Page transition | fade | 200ms | ease-out |
| Modal / Dialog | scale(0.96 → 1) + fade |
250ms | celebration easing |
| Tooltip | fade + 4px slide-up | 100ms | ease-out |
| Theme toggle | crossfade (whole UI, surfaces) | 200ms | ease-out |
Checkbox completion fill (2026-06-04, opt-in-feedback feature). Checking a task plays a soft amber fill on the CHECKBOX (not the whole row) over ~200ms
ease-out— short tier, deliberately NOT the heatmap's radial-sweep geometry or celebration easing. The heatmap-cell fill stays the single performative "hero" moment (400ms); the checkbox fill is the quiet, repeatable acknowledgment of the app's most-frequent gesture, and that restraint is what lets it fire on every check without fatigue. Gated behindmotion-safe:—prefers-reduced-motionusers get an instant, motionless state change. On an optimistic-update rollback the state snaps back instantly (no reverse-celebration).
- Bounce springs (any
cubic-bezierwith overshoot >1.05 except modals) - Confetti, particle bursts, screen-flash
- SFX, haptics — EXCEPT an opt-in palette of soft sounds at a few earned moments (default OFF; see note below)
- Scroll-driven hero animations
- Loading spinners that exceed 3s without progress text
Opt-in sound palette (2026-06-04 single completion sound → 2026-06-13 widened to a palette). The one sanctioned exception to the SFX ban: a small, opt-in palette of soft, warm, non-melodic cues at a few earned task-life moments — creating a task, completing, and clearing completed. Each cue is a single short organic texture (≤~400ms), chosen from 3–5 selectable timbres at a user-set volume, off by default and toggled per moment. It is a quiet companion's acknowledgment — never a gamified "level-up" chime, never a melodic run, and never plays by default. OS mute is honored by the OS (the app does not detect it). At most one cue plays at a time — a rapid second action cuts/restarts rather than layering (scoped per window). High-frequency UI manipulation (reorder, category-switch) is deliberately excluded to keep these moments feeling earned.
The product talks like a quiet companion, not a coach. Microcopy is one of the few places where the north star comes through directly.
GitHub: 8 contributions on May 9, 2026
Things 3: 8 completed
corelive: 8 things done — good day (English) / 今日は 8 個 — お疲れさま (Japanese)
The praise is one short phrase appended to the count. Not saccharine, not corporate.
- Empty Heatmap day on hover: "rest day" / "休息日" — never "0 tasks", never "missed".
- New user, never any data: "your year starts here" / "ここから始まる一年" — invitational.
Instead of "5-day streak" (creates fragility), display "shown up 18 days this month" — additive, generous, never decreases without explanation.
- "Productivity" (loaded category word)
- "Crush your tasks", "ship faster", "10x"
- "You missed a day" / 「途切れました」
- Percent-completion KPIs in primary UI ("47% complete today")
The full color system maps onto the existing shadcn/ui tokens. Migration is per-token, not per-component. Every shadcn primitive in src/components/ui/ keeps working — only the underlying CSS variables change.
- Button (primary): amber bg, paper-white text, 8px radius, hover lifts 1px
- Card: surface bg, 1px soft border, 12px radius, no shadow by default (shadow only when elevated/dragging)
- Input: transparent bg in light mode, white-on-white feel; focus ring uses
--ring(amber) - Badge: pill (full radius), Caption typography (12px Inter Tight 500 uppercase)
- Tooltip: popover bg, 4px slide-up entrance, 13px Inter Tight; max 2 lines, no markdown
- Heatmap cell:
<rect>with explicit fill from--hm-0…--hm-4(resolved per active theme; the component reads them via theHEATMAP_LEVEL_TOKENSsource-of-truth insrc/lib/heatmap-intensity.ts), role=button when count>0, focus ring identical to other interactive elements
| Date | Decision | Rationale |
|---|---|---|
| 2026-05-10 | DESIGN.md created from /design-consultation (dry-run, OpenAI verification pending) |
First codified design system; supersedes implicit shadcn/ui defaults |
| 2026-05-10 | Aesthetic: Warm Cathedral (vs cold Linear / Things baseline) | Aligns with north star "self-affirmation feeling"; differentiates from productivity category convergence |
| 2026-05-10 | Heatmap recolored from green to warm temperature gradient | Eureka: temperature carries the "personal pride" signal that green carries "GitHub work" — orthogonal axis to North Star |
| 2026-05-10 | Newsreader serif for display (vs Inter / Geist / DM Sans) | Productivity apps 100% converge on geometric sans; a serif says "craft" within 0.3s of page load |
| 2026-05-10 | Inter Tight (not Inter) for body | Inter alone is the AI default; Tight is denser and pairs with Newsreader's character |
| 2026-05-10 | Geist Mono for data/code (vs JetBrains Mono) | Warmer character, tabular-nums, fewer false-positive associations with terminal/code |
| 2026-05-10 | Streak counter replaced with "shown-up days this month" | Eliminates streak-guilt; empty days = rest, never failure; aligns with "self-affirmation" |
| 2026-05-10 | Tooltip copy as quiet praise, not raw stats | Each hover delivers a small affirmation moment — north star delivered at the interaction layer |
| 2026-05-10 | Cell size: 12px min / 32px max | Locked by Heatmap Cathedral eng review D6 |
| 2026-05-10 | OKLCH color space throughout | Perceptually even gradient steps; current globals.css already uses oklch — alignment, not migration |
| 2026-05-27 | Startup-window settings ("On launch") — Sunrise framing + quiet-companion microcopy | Choosing the boot window reads as "waking up for me," not "is it broken?"; the last enabled toggle locks with "At least one window opens at launch" — gentle guidance, never an error; north star: control, not KPI guilt |
| 2026-06-04 | Completion feedback: checkbox amber fill (~200ms ease-out, NOT the heatmap hero) always-on + opt-in completion sound (default OFF) | The app's most-repeated gesture returned nothing; a quiet, design-sanctioned fill turns each check into self-affirmation without gamification. Motion stays subordinate to the heatmap hero so it repeats without fatigue; sound is opt-in so it is a deliberate choice, never default SFX (north star: self-affirmation, not dopamine hits) |
| 2026-06-10 | Theme system expanded to 12 themes / 6 families (Warm Cathedral default + Harbor / Grove / Rose Tea / Iris / Graphite) via registry → build-time generator → static CSS; cathedral stays the untouched, hand-authored default | Self-expression without diluting the default craft; the generator keeps every colored family systematic and WCAG-AA-gated, while the cathedral stays hand-authored for brand safety; culori runs build-only so the client bundle is unaffected |
| 2026-06-10 | Heatmap "temperature = pride" locked as a CI invariant across ALL themes — the two hottest stops (--hm-3/--hm-4 hue ∈ [20, 70]) are the seed gate; the L/C ramp is reused from cathedral (structural); cool rest stops stay family-hued by design |
The north-star payoff must survive every future family — a green Grove still blooms warm at the hot end yet rests green by design; the build fails if a new theme cools its hot end, breaks the L/C ramp, or drops AA |
| 2026-06-10 | System maps only to the default Warm Cathedral light/dark pair; colored families are explicit Light/Dark |
System means "follow my OS light↔dark," not a palette choice — keeps the two-axis (family × mode) mental model clean |
| 2026-06-11 | Two hue collisions accepted (Grove accent 145 ≈ --chart-2 145 / --success 149; Rose Tea accent 18 ≈ --destructive 25), separable by chroma/lightness, AA-pass, flagged in-code |
The family signature is worth more than perfect hue isolation; surfaced for design-review rather than silently compromised |
| 2026-06-11 | Theme toggle is a whole-UI crossfade (page + card/sidebar/popover surfaces, 200ms ease-out, motion-safe), scoped to a transient theme-transition class so hover/focus stay instant; Electron native appearance.theme / accentColor removed |
Reduced-motion-safe transition, broadened from body-only to all surfaces (design-review #5 decided 2026-06-11); the native theme copy had zero readers and could only drift from the real web-persisted value |
| 2026-06-13 | "Presets First, Then Options" codified as a cross-cutting principle — the concept owns every default/preset; an out-of-concept option is allowed only as an opt-in deviation layered on a sufficient on-concept default. Applies to design, features, and UX | Generalizes the opt-in sound palette's logic into a reusable governance rule, so future customization (themes, sound, motion, copy) follows one shape: ship the concept default, make it sufficient, then let options ride on top. The North Star itself stays non-deviable — style is optional, the emotional contract (self-affirmation, never judgment) is not |
This DESIGN.md describes the target system. Migration from current state (cold-neutral oklch + GitHub green Heatmap) is per-token in src/globals.css. No component code changes are required for the color/spacing migration. Typography requires:
- Adding Newsreader, Inter Tight, Geist Mono to
<head>(Google Fonts + Vercel) - Updating
bodyclassName insrc/app/layout.tsxto apply Inter Tight as default - A type-scale utility component or CSS layer for the Hero / H1 / H2 / H3 / Body / Small / Caption / Data tiers
The Heatmap palette is defined by the --hm-0…--hm-4 tokens. For the default Warm Cathedral, edit them directly in src/globals.css (light and dark). For a colored family, change its heatmapHues seed in src/lib/themes/registry.ts and run pnpm theme:generate (the generator rewrites src/lib/themes/generated.css). Either way no ContributionGraph.tsx change is required — the component resolves the tokens through the HEATMAP_LEVEL_TOKENS source-of-truth.
Streak and tooltip copy changes are scoped to ContributionGraph.tsx and any future StreakBadge component (deferred to the Heatmap Cathedral plan, decision D12 / Electron-only).