Files
Claude a2f723ca2d Start the rebuild: note schema, vault kit and data migration
Phase 1 of the functional design plan.

- Move the old site into legacy/, unchanged, for reference. It still
  builds from there until cutover.
- Add packages/schema: the one definition of all 20 note types, with
  field titles and descriptions, note and vault checks, and JSON Schema
  export so later editing tools and the MCP server share it.
- Add vault-kit: Obsidian templates generated from the schema, and the
  Site guide for 24.01_PersonalWebsite.
- Add tools/migrate: turns the legacy JSON, collections, store data and
  page text into 545 Site folder notes and 84 images, and checks the
  result with the schema.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
2026-10-11 18:40:55 +00:00

6.8 KiB

Theming & Motion Guide

How color, light/dark mode, section tone, and animation work on this site.


Palette

The site uses the Patiun LLC brand palette (shared by Patiun and Greg Kilmer), exposed as CSS variables in src/styles/global.css under @theme:

Token Value Meaning
--color-teal #14A39A Patiun Teal, primary: buttons, fills, the glyph
--color-teal-bright #2BC2B7 Teal text and links on dark grounds
--color-teal-deep #0F8C84 Teal marks on light grounds
--color-slate-teal #2D4A58 Dark end of the Tide gradient
--color-gold #C9A24A Alchemist Gold: stars, rules, seal lines (never a large fill)
--color-gold-bright #D8B35A Gold text on dark grounds
--color-gold-deep #8A6516 Gold text under 24px on Starlight
--color-violet / --color-indigo #6A2FE0 / #3B1F8F Nebula light only, never text
--color-midnight #131C2B Panels, tiles, browser theme color
--color-sky #0E1522 Night-sky ground
--color-starlight #F4F2ED Text on dark; paper ground

The semantic Tailwind tokens (--color-accent, --color-surface, ...) resolve at :root, so content sections render with the Starlight values in themes.css. Night-sky bands (the nav and page heroes) carry the .on-sky class, which re-points accent and gold to their on-dark values.

Brand colors are fixed. Semantic tokens (--surface, --text, --accent, --secondary, --border, ...) resolve differently per theme + section, and are what components actually use.


Two theming axes

Axis 1 — Global light/dark

Set on <html data-theme="light|dark">:

  • A blocking inline script (src/scripts/theme-init.ts, inlined in <head>) reads localStorage['gk-theme'], falling back to the OS prefers-color-scheme. This runs before first paint, so there is no flash.
  • The nav ThemeToggle island flips it and persists the choice.
  • If the user has never chosen, the site follows OS changes live.

Axis 2 — Section tone

Set on <body data-surface="neutral|professional|interest"> via the surface prop on <BaseLayout>:

Surface Used on Tone
neutral Home, About, Contact Balanced — clean in light, calm in dark
professional Professional page Dark slate, polished, amber accents
interest Interests pages Warm cream/amber, inviting

The Professional surface leans dark even in light mode (it is the "polished wing"). The Interest surface is warm in both modes. This is what gives the two wings their distinct feel while sharing the same typography and spacing.

Each (data-theme, data-surface) combination redefines the semantic tokens in themes.css. Add or adjust a combination there to change any section's palette.


Changing colors

  1. Tweak a brand color: edit the fixed values in global.css under @theme (e.g. --color-amber).
  2. Tweak a section's tone: edit the relevant [data-surface="..."] block in themes.css. Each block lists --surface, --surface-raised, --surface-sunken, --text, --text-muted, --text-subtle, --border, --accent, --accent-contrast, --secondary.
  3. Components use utilities like bg-surface, text-text, text-accent (mapped from these variables), so they update automatically.

Contrast

Verify WCAG AA (4.5:1 for body text) whenever you change a token pair. The current combinations were chosen for AA compliance. The --accent on the neutral light surface uses a deeper amber (#b45309) specifically to meet AA on white.


Motion

Motion is layered and always respects prefers-reduced-motion:

  1. Hero background (AnimatedBackground.tsx):
    • A CSS mesh-gradient layer (GPU-friendly, animated via the animate-gradient-shift keyframes in animations.css).
    • An optional <canvas> particle field (slow drifting dots) on the home hero only. It renders at devicePixelRatio (capped at 2), pauses when off-screen, and is skipped entirely under reduced motion.
  2. Scroll reveals (Reveal.astro + the .reveal class):
    • IntersectionObserver toggles .is-visible, triggering a CSS fade-up. Zero framework cost. The reduced-motion media query in global.css forces these to their final state instantly.
  3. Page transitions: Astro <ClientRouter /> (View Transitions API) morphs between pages and persists the nav.
  4. Nav blur on scroll: a tiny inline script adds .scrolled after a few px, enabling backdrop-blur + a hairline border + shadow.
  5. Hover micro-interactions: Tailwind transitions on cards/buttons (lift, accent glow, arrow nudge).

Disabling motion

Everything is gated by the global reduced-motion rule in global.css:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
    ...
  }
}

The canvas island additionally checks matchMedia('(prefers-reduced-motion: reduce)') before drawing, so even JS-driven motion is skipped.


Typography

Self-hosted variable fonts via Fontsource (woff2, no external requests):

  • Headings, names, card titles: Cinzel Variable (font-display, used by h1-h4 automatically). Open tracking, never for paragraphs.
  • Body and UI: Montserrat Variable (font-sans, font-body).
  • Eyebrows / kickers: Montserrat uppercase with wide tracking (font-eyebrow).
  • Code-like blocks only: JetBrains Mono Variable (font-mono).

Font sizes use clamp() for fluid scaling (see the h1-h4 rules in global.css).


Icons and motifs

  • Icons: <Icon name="..." /> (Astro) and <BrandIcon name="..." /> (React islands) render the Patiun stroke icon set in src/lib/brand-icons.ts. Call sites can use either the brand name (dice) or the mdi name (dice-multiple); brandIconAliases maps one to the other. Only third-party logos (GitHub, LinkedIn, Steam, ...) fall back to mdi. The gold star accent uses --icon-gold (deep gold on Starlight, flat gold inside .on-sky). The file is generated from the brand source (newicons.py); add icons there.
  • Motifs: <Motif name="..." /> shows a brand motif from public/motifs/, copied from the brand files with ring text outlined to paths. -light files are the Starlight recolor. One focal motif per surface, kept faint. Every page carries at least one seal: night-sky heroes have the turning layered seal, and pages without a hero use <PageSeal name="..." /> behind the header.
  • Shape: cards and panels have 10px corners (--radius-xl/2xl/3xl are pinned to 10px); buttons and tags are pills. The Patiun seal is never shown below 72px (nav and footer use 72px).
  • Gold accents: image frames, quote bars, dividers and rules use --color-gold; teal stays for links, buttons and icons.