I1 / Derive the spectrum
One hue, four bands.
The anchor is a parameter, not a swatch. Fixed offsets derive the brand bands. Success, warning, and danger keep their own hues.
PRISM · v0.9 · OKLCH · Passive light
A light-mode-first design system. One hue derives the palette, density chooses a material, and one light falls across every object. The light rests where the rig puts it and moves only when something happens. Set it in the light rig, play with it in the playground, or compare variants in the lab.
01 / Core concepts
The identity test: rotate the hue and the system stays recognizable. You still see chromatic edges, density that signals permanence, and one light source for every object.
I1 / Derive the spectrum
The anchor is a parameter, not a swatch. Fixed offsets derive the brand bands. Success, warning, and danger keep their own hues.
I2 / Density is optical depth
Transient glass passes most light, standing surfaces filter it, and immovable pigment blocks it. Permanence shows in the shadow.
T: transient 0.6 → standing 0.3 → immovable 0.05
I3 / Share the light
The light rests where the rig puts it and moves for a story beat or an action, never for the pointer. Shadows are solved from it; edges and foil read the same reference.
02 / Objects & mathematics
Objects take finite values. Materials and events are lookup tables, not taste. Each formula below is the code that runs on this page.
| Axis | Values | Controls |
|---|---|---|
| Mode | day | night | Figure and ground, shadow or glow, filled or outlined display type. Night is occlusion first. |
| Density | transient | standing | immovable | Chooses the material: glass, frosted, pigment (T = 0.6 | 0.3 | 0.05). |
| Material | 14 kinds · data-material | How the object treats light, when density is not enough to say it. |
| Event | 16 kinds · data-light-event | What an action does to light. Each settles within its listed q. |
| Role | brand | info | success | warning | danger | Brand and info follow the anchor. The others are fixed hues. |
| Size | button xs–xl · badge, chip sm–lg | Padding and type scale. The 1.5px edge stays fixed. |
| Material | T | Does |
|---|---|---|
| glass | 0.6 | Transmits.transient surfaces |
| prism | 0.55 | Splits incident light into the four bands.primary actions, hero marks |
| dichroic | tint | Passes its band; glows its complement on the lit side.brand accents |
| frosted | 0.3 | Transmits without direction and re-emits softly.standing surfaces, disabled controls |
| filter | tint | Passes only its band, so light on light tinges.selected or tinted states |
| pigment | 0.05 | Absorbs, bouncing a little of its own color.immovable surfaces |
| mirror | 0 | Redirects light into a reflected beam.dividers that route light |
| emitter | 1 | Is a source.screens, live status, hovered controls |
| phosphor | 0.5 | Stores light and re-emits it over 35q.notifications, recent changes |
| fog | 1 | Scatters: beams show, and dim with distance.atmosphere, emphasis zones |
| void | 0 | Absorbs everything.destructive actions, night occlusion |
| pipe | 0 | Carries light from its lit end along its length.focus, selection seams, progress |
| film | 0.8 | Shifts color with the angle of the light.foil, celebratory marks |
| lens | 0.9 | Converges light into a beam.toggles, the current choice |
| Event | On | Does |
|---|---|---|
| emit | hover · focus | The object turns faintly emissive.held |
| absorb | press | The object turns to pigment, then recovers.3q |
| pulse | success | A ring of light disperses outward.12q |
| pipe | focus-visible | A light pipe walks the edge once.10q |
| prism | primary (passive) | The key light splits into bands across it.passive |
| void | destructive press | The object drinks the light near it.8q |
| frost | disabled | The object frosts: light passes, scattered.passive |
| lens | toggle | A lens focuses a beam on the chosen option.held |
| seam | select row | The row seam becomes a lit pipe.held |
| sunrise | first paint | The key light rises to rest, once.25q |
| busy | aria-busy | Light breathes only while busy.held |
| lift | drag · hold | The object rises: its shadow detaches and softens.held |
| reject | error | The object becomes a danger filter, then clears.8q |
| afterglow | arrival | Phosphor afterglow that decays.35q |
| carry | copy · send | Light travels to its destination.10q |
| eclipse | mode change | An occluder crosses the key light.25q |
See every material and event live in the light lab.
A shadow is sharp at its occluder and softens with distance. Near light therefore needs many probes and few directions; far light needs few probes and many directions. Each cascade halves the probe grid, quadruples the rays, and reaches four times farther. Merged from the top down, the cascades solve the whole field in a fixed number of passes. The method is Alexander Sannikov's; see radiance.wiki.
| n | Probe spacing | Rays / probe | Interval |
|---|---|---|---|
| 0 | 4.5 px | 4 | 0–5 px |
| 1 | 9.0 px | 16 | 5–23 px |
| 2 | 18.0 px | 64 | 23–95 px |
| 3 | 36.0 px | 256 | 95–383 px |
| 4 | 72.0 px | 1,024 | 383–1535 px |
| 5 | 144.0 px | 4,096 | 1535–6143 px |
Field monitor · live
field — texels · 1 texel = — px
solves 0 · frames 0 · ref writes 0
Remove cascades and the light stops at the top interval. The field is re-solved only when the rig, a light event, the scroll, or the hue changes. At rest the loop sleeps: the frame count stops.
Needs WebGL2. Without it, the six light refs still drive edges, casts, and foil.
spacingₙ = 2ⁿ · raysₙ = 4ⁿ⁺¹
intervalₙ = L₀ · [(4ⁿ − 1)/3, (4ⁿ⁺¹ − 1)/3) Units are field texels with L₀ = 1. Each cascade stores 4·W·H rays, so memory stays constant. Cascades are added until one interval covers the viewport diagonal.
L(a→c) = L(a→b) + T(a→b) · L(b→c) · v = f / f_open
b = 1 − e^(−f₀(1 − v)) · g = min(b) · day = max(0.8, 1 − 0.16(g·w + 1.6(b − g)))
night = min(0.22, 0.22(1 − e^(−(0.6·f₀v + play)))) A parent ray averages its four children, bilinearly from the nearest upper probes. Solving with and without objects gives visibility v, which cancels the merge's ringing. The neutral part of a shadow leans to the far band (w); light a filter lets through keeps its hue. Play light (spots, bands, emitters) tinges by day and glows by night.
Hᵢ = wrap(H + Δᵢ, 360°) · colorᵢ = oklch(Lᵢ Cᵢ Hᵢ)
day Δ = 0, −35, +75, −140 · night Δ = 0, −25, +55, −95 on R(H)
R(h) = h ≤ 45 ? h : h ≤ 300 ? h − 0.4(h − 45) : (h + 0.4(385 − h)) mod 360 Day: H = 185° → bands 185°, 150°, 260°, 45°
Δsource = 1 probe · Δangle = 3° · Δvector = 0.02
q = 40ms · flash 3q · arrive 6q · settle 10q · mode 25q Every light event lists its duration in q; held states last as long as their state. Press-in is the one exception at 1.5q. Reduced motion resolves timed events at once and keeps held states.
03 / Style guide
Color, type, motion, and geometry turn the optical idea into repeatable choices.
Day bands hold L 0.70–0.78 and C 0.11–0.14. Success (152°), danger (27°), and warning (75°) never rotate; info follows the anchor. Status always carries a word. Check each foreground on its actual ground in both modes.
Use Tilt Prism only at 40px or larger, and Instrument Sans 600 below that. Day fills display type; night outlines it.
Refraction
Light splits into spectra at the boundary.
The reading ground stays quiet. Color concentrates at edges, status, and dense actions.
--h: 185 → --hr: 129 · q = 40ms · AA ≥ 4.5:1
Inspect release · Save changes
04 / Component family
the human voice, used sparingly.
Brightness leads, form follows, and the shadow trails by 2q. Objects enter from the light. Unknown state is superposed, and moves only while busy.
Stagger · 1q, cap 6
Superposition · while busy
↳ run 0xA2 · hue 150° · status ok
Enter from the light
Arrives along −(lvx, lvy); its shadow settles 2q later.
| Family | Values | Rule |
|---|---|---|
| Radius | 10 / 16 / 24px | Control / foil / surface. Button sizes step 7–14px. |
| Boundary | 1.5px edge · 1px hairline · 2px seam | Keep the roles distinct; heavier edges flatten hierarchy. |
| Spacing | 1rem · 1.5rem · 2.5–3rem | Component gap, group gap, section space. |
04 / Components
Choose density, then role, then size. Every specimen shares one edge and one light, and every action on it is a light event.
Density chooses the material: glass, frosted, pigment. Compare the shadows, then act on the buttons; each one is a light event.
Transient · glass · T 0.6
Seconds. Edge and glow; most light passes.
Standing · frosted · T 0.3
Session. Tinted wash, 1px border, short shadow.
Immovable · pigment · T 0.05
Structure. Opaque pigment, no spectra.
The rig is this page's light. Each control is a parameter: change one and the page re-solves once, and nothing else moves. Its variants are the open decisions; the lab shows them side by side.
Drag the lights and the objects; arrow keys nudge the focused one. Pick a material for each object and add fog to see the beams.
Five sizes over four button styles, and three sizes over badges and chips. Drag the hue: brand styles rotate, semantics hold.
Button · 5 sizes / 4 styles
Badge & chip · 3 sizes
After Ink & Switch's Tenfold: icons are 8×8 displays that emit rather than print, so the glass stays dark in both modes. Click an icon to re-tune its band. Drag on the large display to paint.
16×10 · pixels emit the anchor band.
05 / Information patterns
Summarize before showing evidence, and keep selection separate from status. As density rises, hairlines replace light.
Pattern / summary
Two checks are ready. One needs a decision.
! Review required
The summary floats above the records, and its status is named in words.
| Check | Result | Next step |
|---|---|---|
| Typography | ✓ Ready | Keep scale |
| Focus statesCurrent review | ! Review | Inspect ring |
| Motion | ✓ Ready | Keep timing |
Day casts fall away from the light; night casts become halos. Data rows never cast. Selection is a 2px seam lit as a pipe, and a shared datum lights everywhere at once (Δt = 0).
Floating card · entangled
Hover a dot or a row. Both light at once; their neighbors stay put.
06 / Build with it
Build from the material classes, keep the model visible in each choice, and verify the composition.
<html data-mode="day" style="--h:185; --hr:129">
<link rel="stylesheet" href="/prism/prism.css">
<script type="module" src="/prism/light.js"></script>
<article class="prism-card prism-edge" data-light="transient">
<h2>Review the release</h2>
<button class="prism-btn prism-btn--dense"
data-light-event="emit absorb">
Inspect release
</button>
</article>
</html> prism.css is the resting material. light.js is optional: it solves the field, reads density (data-light), materials (data-material) and events (data-light-event), and exposes light.setRig(). Agents read the same rules as PRISM.md.
Ambient motion while reading. The light rests (§4).
Only aria-busy loops (§5).
Nothing was selected, so nothing should move.
The cursor promises an action that is not there.
Focus promises an action too.
Density picks the material (§3).