Dreamweaver

Dreamweaver · style guide

The twilight hour

Dreamweaver is read at bedtime, so it lives in the half-hour after sunset: deep indigo and violet for the sky, warm parchment for the words, and one gold thread — lamplight — for the thing you should touch next. Everything on this page is drawn with the real tokens and utilities, so this is both the guide and the proof.

Foundations

Four rules. Everything else on this page follows from them.

Always night

One theme, dark: no light mode, no prefers-color-scheme, no light-dark() color-scheme: dark is set once so native controls and scrollbars follow.

Roles, not hexes

Components use semantic tokens only — bg-surface, text-ink-muted. Tailwind's stock palette is cleared, so there is no bg-slate-900 to reach for. Need a colour? Add a role.

Gold is rare

The accent goes on the primary action, the selected chip, the live caret, the thread and one title. If two gold things compete on a screen, one of them is wrong.

HIG underneath

The whimsy is in the sky and the titles; the controls stay Apple-plain: 44pt targets, 16px control text, visible focus, reduced motion honoured.

Colour

Two tiers. Primitives are raw OKLCH ramps with no meaning; roles say what a colour is for. Only roles reach components.

Primitives

plain CSS vars · no utilities · don't use directly
twilight
twilight-950
twilight-900
twilight-800
twilight-700
twilight-600
dusk
dusk-600
dusk-500
dusk-400
gold
gold-700
gold-500
gold-400
gold-300
gold-200
gold-100
mist
mist-600
mist-400
mist-200
mist-50
moss · ember
moss-900
moss-300
ember-900
ember-300
/* tokens.css — primitives live on :root, in OKLCH (perceptual, wide-gamut) */
:root {
  --twilight-950: oklch(0.165 0.055 282);  /* the canvas */
  --gold-400:     oklch(0.81  0.15   79);  /* the accent */
  --mist-50:      oklch(0.955 0.012  85);  /* the ink */
}

Roles

hex is measured live, composited over the canvas

Canvas & surfaces

Stacked from the sky up. Surfaces are translucent so the stars show through — never paint an opaque hex behind content.

  • opaque
    --color-canvas·bg-canvas

    The page. Set once on <html>; the sky draws over it.

  • opaque
    --color-surface·bg-surface

    Cards, lists, the book's pages — anything content sits on.

  • opaque
    --color-surface-raised·bg-surface-raised

    A surface on a surface: hover rows, menus, the details block.

  • opaque
    --color-surface-overlay·bg-surface-overlay

    Sheets, dialogs, toasts. The top of the stack.

  • opaque
    --color-surface-sunken·bg-surface-sunken

    Wells inside a surface: inputs at rest, code blocks.

Ink

Warm white text on a cool sky. Three steps, no more: if you need a fourth, you need a different layout.

  • opaque
    --color-ink·text-ink

    Body text, titles, icons.

  • opaque
    --color-ink-muted·text-ink-muted

    Secondary text: labels, metadata, helper copy.

  • opaque
    --color-ink-faint·text-ink-faint

    Placeholders, disabled text, decorative numerals.

  • opaque
    --color-ink-on-accent·text-ink-on-accent

    Text on a gold fill (the primary button).

Lines

Hairlines are mist at low alpha, so they sit on any surface without a second token.

  • opaque
    --color-line·bg-line

    Dividers, card borders.

  • opaque
    --color-line-strong·bg-line-strong

    Control borders, the thread at rest.

Accent — gold

The one warm thing in a cool world. Spend it on: the primary action, the selected chip, the live caret, the thread, one gilded title. Not on decoration.

  • opaque
    --color-accent·text-accent

    Primary fills and strokes; the thread.

  • opaque
    --color-accent-bright·text-accent-bright

    Highlights: selected-chip text, gradient tops.

  • opaque
    --color-accent-deep·bg-accent-deep

    Gradient bottoms, pressed states.

  • opaque
    --color-accent-soft·bg-accent-soft

    Tinted fills behind a selected or hovered thing.

  • opaque
    --color-accent-glow·bg-accent-glow

    Focus rings, halos, the pulse. Alpha on purpose.

Atmosphere

The sky's glows. Background only — never text, never a fill on a control.

  • opaque
    --color-glow-indigo·bg-glow-indigo

    The big glow, top-left.

  • opaque
    --color-glow-violet·bg-glow-violet

    The dusk glow, bottom-right.

  • opaque
    --color-glow-gold·bg-glow-gold

    A faint lamplight low on the horizon.

Signals

Status colours, each with a soft fill for the message block behind it. Green means finished, red means the loom stopped.

  • opaque
    --color-ok·text-ok

    Finished. Dot, text.

  • opaque
    --color-ok-soft·bg-ok-soft

    Background for a finished notice.

  • opaque
    --color-danger·text-danger

    Failed / error text and dots.

  • opaque
    --color-danger-soft·bg-danger-soft

    Background for an error block.

  • opaque
    --color-focus·bg-focus

    Keyboard focus ring — always visible, always gold.

/* tokens.css — roles are Tailwind theme variables, so they are utilities too */
@theme {
  --color-*: initial;                         /* no stock palette: roles only */
  --color-canvas:  var(--twilight-950);
  --color-surface: color-mix(in oklch, var(--twilight-900) 84%, transparent);
  --color-ink:     var(--mist-50);
  --color-accent:  var(--gold-400);
  --color-accent-glow: color-mix(in oklch, var(--gold-400) 38%, transparent);
}

/* a component */
<section className="card">…</section>                  // bg-surface + line + shadow
<p className="text-ink-muted">…</p>
<span className="bg-accent-soft text-accent-bright">…</span>
<div style={{ boxShadow: "var(--shadow-glow)" }} />    // or: shadow-glow

Surfaces

Depth comes from stacking translucent layers over the sky, not from grey drop shadows. Four levels, in order.

canvas · the sky shows through everything above it
surfacecard · bg-surface

Cards, the library list, the book's pages. 84% opaque — stars glimmer faintly behind.

surface-raisedbg-surface-raised

Hovered rows, the prompt disclosure, popovers.

surface-overlaybg-surface-overlay shadow-lift

Sheets and dialogs. Top of the stack.

surface-sunkenbg-surface-sunken

Wells: inputs at rest, code blocks.

  • Do
  • Use card for anything that holds content; it carries surface, line and shadow together.
  • Nest at most one level: surface → raised. Overlay is for things that float.
  • Let the sky do the atmosphere — a card on the canvas is already interesting.
  • Don't
  • Don't paint an opaque colour behind content; it cuts a hole in the sky.
  • Don't add backdrop-filter: blur to surfaces — the sky animates behind them and phones would repaint every frame.
  • Don't invent a fifth level. If the hierarchy needs more, use spacing or type.

Typography

Four faces, each with one job. The personality is in the titles; the rest stays out of the way.

FrauncesDisplay · .display
Once upon a quiet sky

opsz 144 · SOFT 100 · WONK 1 — the storybook voice. Titles only, via .display.

LiterataReading · .prose-book
The lighthouse kept its slow, patient turning, and Mila counted the beams until she lost the number.

Made for long reads on screens. Every sentence a child will hear is set in it.

GeistInterface · .font-sans
Who is it for? · Weave my book · Language

Labels, buttons, helper text. Neutral on purpose — it is the part that should feel like a phone.

Geist MonoData · .font-mono
writing · page 4 of 10 · 2m ago

Status words, timestamps, page numbers, the prompt. Anything the machine said.

Scale

named by role · size + leading travel together
  • text-display-2xl
    Page title — one per page, usually gilded
    Weave a book
  • text-display-xl
    Book title on its page
    The Lighthouse at the Edge of the Map
  • text-display-lg
    Section title inside a page (settings preview)
    A Door in the Fog
  • text-display-md
    List row title
    Mila and the Paper Moon
  • text-reading
    Reading text (Literata)
    The sea was quiet that night, and the lamp kept its slow, patient turning.
  • text-body
    UI body text
    Pick who it’s for and how it should feel.
  • text-label
    Field labels, nav links
    Where does it happen?
  • text-caption
    Metadata, helper text, timestamps
    Finished · 2h ago
  • text-eyebrow
    Spaced uppercase label above a section
    From the author’s notes
.display.gilded
The Paper Moon

Gold gradient clipped to the letterforms, with a soft drop-shadow glow behind. One per page — the page title or the book title, never both.

.eyebrow
From the author's notes
A door in the fog

Small, spaced, uppercase, muted. It labels what comes next; it is never the thing itself.

<h1 className="display gilded text-display-2xl">Weave a book</h1>
<p className="prose-book text-ink-muted">…</p>            // reading text
<span className="eyebrow">From the author's notes</span>
<time className="font-mono text-caption text-ink-muted">2h ago</time>

Controls

One vocabulary for everything touchable, sitting on the HIG floors: 44pt hit targets, 16px control text (so iOS never zooms), a visible gold focus ring.

control

Text input, textarea, select. Sunken at rest, lifts to surface and glows on focus.

chip · chip-on

A radio option drawn as a pill. The selected one borrows the accent: gold border, soft fill, a halo.

btn · btn-quiet

One primary action per screen, gold, 50pt tall, full width on a phone. Everything else is quiet.

tap · link

Text links get a full-height hit target with `tap`; links inside prose use `link` (gold underline, ink text).

LibraryNothing on the shelf yet. Weave the first book.
Signals

Status dots and the soft blocks behind messages. The live dot pulses in gold.

Weaving the pages Finished Something snapped

The loom stopped: the model returned no pages.

It won't try again on its own.

<input className="control" />
<span className="chip peer-checked:chip-on">Cozy</span>
<button className="btn sm:w-auto">Weave my book</button>
<button className="btn-quiet">Weave another</button>
<Link className="tap px-3 text-ink-muted hover:text-ink">Library</Link>
<a className="link">Weave the first book</a>

Motion

Motion tells you the loom is working. Every animation maps to a real state; none are decoration. All stop under prefers-reduced-motion.

.thread

Ten segments, one per page. Breathing while thinking, weaving on the active page, lit gold when done.

.caret · .rise

The caret blinks at the end of text that is still arriving. `rise` lifts a finished thing into place once.

and the lamp turned, and turned, and

.shimmer

A placeholder that is being painted — the cover, while Gemini works.

painting…
Easing

Two curves, both tokens.

  • --ease-rise cubic-bezier(.2,.7,.2,1) — things arriving
  • --ease-drift cubic-bezier(.4,0,.6,1) — the sky

The sky

The one signature. A fixed layer behind every page: a twilight gradient, three slow glows, a seeded field of stars that twinkle, and — every eighteen seconds — one that falls.

Layers

.sky gradient → .sky-glow ×3 (indigo, violet, gold; blurred, drifting 70–110s) → .sky-stars (110 SVG circles) → .sky-fall.

Deterministic

Stars come from makeStars(seed, n), a seeded PRNG in lib/sky.ts, so the server and the browser draw the same sky and nothing hydrates.

Cheap

Only opacity and transform animate — compositor work, no layout, no paint. Under prefers-reduced-motion it all holds still.

// app/layout.tsx
<body>
  <Sky />          {/* position: fixed; z-index: -1; aria-hidden */}
  <header>…</header>
  <main>…</main>
</body>

/* content overlays it on translucent surfaces */
<section className="card">…</section>

Using the tokens

Tailwind v4 theme variables are the system. Utilities reference the role variable, so a subtree can re-skin itself by redefining a role.

In a component
<li className="bg-surface hover:bg-surface-raised
               border-line text-ink">
  <span className="text-ink-muted">…</span>
</li>
In CSS
.thread > i[data-state="done"] {
  background: var(--color-accent);
  box-shadow: 0 0 8px var(--color-accent-glow);
}
Re-skin a subtree
/* the book page, later: warmer pages */
.book-page {
  --color-surface: color-mix(in oklch,
      var(--twilight-900) 70%, var(--gold-700));
}
/* every bg-surface inside follows */
Add a role
/* tokens.css → @theme */
--color-warning: var(--gold-300);
--color-warning-soft: color-mix(in oklch,
    var(--gold-700) 60%, transparent);

/* then add a row in styleguide/tokens.ts */
  • Do
  • Reach for a role utility first (bg-surface, text-ink-muted, border-line, shadow-glow).
  • Use opacity modifiers on roles when you need a tint: border-danger/30.
  • Put new shapes in tokens.css as --radius-*, --shadow-*, --text-*; they become utilities.
  • Don't
  • Don't reference a primitive (--gold-400) outside tokens.css.
  • Don't add a prefers-color-scheme block anywhere. There is one theme.
  • Don't write #hex or oklch() in a component; it is a missing role.