Lucian Labs system A — a contract for what a look is. Shipped in Forget Me Not.
Console, Punch Clock and House Pour each commit to one identity. This does the opposite: it defines the shape a look must have, then ships eleven of them. Reach for it when the app must be re-skinnable — by the user, by a URL, or by you at build time.
type ThemeStyle = {
name, label
colors: { bg surface border text dim // structure
accent // identity — one hue
green orange red cyan } // state — never repurposed
borderRadius, fontSize, spacing // form
headerFont, bodyFont, fontFamily // voice
animation // motion signature
sound: { preset, bpm, volume, mode } // notification character
}
Ten colour roles, one radius, two faces, one density, one motion, one sound. That is the entire surface area. If a component needs a colour outside the ten roles, the component is wrong — not the theme.
Every value in the panel below is a CSS custom property. Switching rewrites the properties; nothing re-renders.
Tap a ✓ to play this theme's exit animation. Enter is the same keyframes reversed and faster.
This is the idea worth stealing. Each theme names one exit animation, and it carries as much identity as the palette.
| Midnight | fade | opacity out, scale to .95 — 0.4s ease-out |
| Sunrise | float | rises 40px and fades — 0.5s ease-in-out |
| Selva | grow | swells to 1.1 and blurs 4px — 0.5s ease-out |
| Kente | slide | feints right, then hard left off-screen — sharp cubic-bezier |
| Neon | glitch | 7 stepped frames, hue-rotates 720°, clip-path tears, collapses scaleY |
| Cloud | drift | up 20px, right 30px, 2px blur — 0.6s ease-in-out |
| Terracotta | crumble | settles 4px, then falls 30px rotating 2° — 0.5s ease-in |
| Matcha | zen | 8px blur + brightness lift, barely moves — 0.7s ease-out |
| Vinyl | spin | 180° rotation shrinking to .3 — 0.5s |
| Océano | wave | rides up, down, then away on scaleX — 0.6s ease-in-out |
| Sakura | petals | 5 frames drifting up-and-sideways, rotating 8° — 0.7s ease-out |
fmn-anim-{style}. No component picks its own.reverse, ~0.3s — faster in than out. One definition, two directions, guaranteed coherence.animationend can never strand a row.Durations sit between 0.4s and 0.7s — long enough to read as character, short enough to stay out of the way.
documentElement as a custom property. Nothing in the app reads a colour any other way.Set tracks injected families; switching pulls only what the new theme needs. A session that never visits Sakura never downloads Kaisei Tokumin.{ ...preset, ...customs } — a user's colour, radius, font or spacing layers onto any preset without forking it. User themes append to the same array as built-ins and are indistinguishable downstream.#theme=sakura, copy/paste JSON, or a script tag. A look that can't be shared as data stays in one app.text on both bg and surface.These presets are a user-facing customization system, not the house brand. Several use faces the house ban list rejects for brand work (Poppins, Inter, Nunito) — fine here, because the point of this system is range, not identity.
Two things are still wrong: midnight and neon
fall back to 'SF Mono', which the house rule forbids
outright — that should be Inconsolata. And if you're building a Lucian
Labs product rather than a re-skinnable toy, use
Console,
Punch Clock or
House Pour and
take only the architecture from this page.