Skip to content
← shupp.dev / portfolio

day H 185° → redshift H 129°

PRISM · v0.9 · OKLCH · Passive light

Prismatic

Light splits at the edges; shadows carry color, not gray.

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

One hue. One light. Three densities.

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

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.

I2 / Density is optical depth

Permanence stops light.

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

One light, at rest.

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

The light is solved, not drawn.

Objects take finite values. Materials and events are lookup tables, not taste. Each formula below is the code that runs on this page.

Object = (mode, density → material, role, size) + events, lit by one radiance field
AxisValuesControls
Modeday | nightFigure and ground, shadow or glow, filled or outlined display type. Night is occlusion first.
Densitytransient | standing | immovableChooses the material: glass, frosted, pigment (T = 0.6 | 0.3 | 0.05).
Material14 kinds · data-materialHow the object treats light, when density is not enough to say it.
Event16 kinds · data-light-eventWhat an action does to light. Each settles within its listed q.
Rolebrand | info | success | warning | dangerBrand and info follow the anchor. The others are fixed hues.
Sizebutton xs–xl · badge, chip sm–lgPadding and type scale. The 1.5px edge stays fixed.
LAW 22 · Materials: how an object treats light
MaterialTDoes
glass0.6Transmits.transient surfaces
prism0.55Splits incident light into the four bands.primary actions, hero marks
dichroictintPasses its band; glows its complement on the lit side.brand accents
frosted0.3Transmits without direction and re-emits softly.standing surfaces, disabled controls
filtertintPasses only its band, so light on light tinges.selected or tinted states
pigment0.05Absorbs, bouncing a little of its own color.immovable surfaces
mirror0Redirects light into a reflected beam.dividers that route light
emitter1Is a source.screens, live status, hovered controls
phosphor0.5Stores light and re-emits it over 35q.notifications, recent changes
fog1Scatters: beams show, and dim with distance.atmosphere, emphasis zones
void0Absorbs everything.destructive actions, night occlusion
pipe0Carries light from its lit end along its length.focus, selection seams, progress
film0.8Shifts color with the angle of the light.foil, celebratory marks
lens0.9Converges light into a beam.toggles, the current choice
LAW 23 · Light events: what actions do to light
EventOnDoes
emithover · focusThe object turns faintly emissive.held
absorbpressThe object turns to pigment, then recovers.3q
pulsesuccessA ring of light disperses outward.12q
pipefocus-visibleA light pipe walks the edge once.10q
prismprimary (passive)The key light splits into bands across it.passive
voiddestructive pressThe object drinks the light near it.8q
frostdisabledThe object frosts: light passes, scattered.passive
lenstoggleA lens focuses a beam on the chosen option.held
seamselect rowThe row seam becomes a lit pipe.held
sunrisefirst paintThe key light rises to rest, once.25q
busyaria-busyLight breathes only while busy.held
liftdrag · holdThe object rises: its shadow detaches and softens.held
rejecterrorThe object becomes a danger filter, then clears.8q
afterglowarrivalPhosphor afterglow that decays.35q
carrycopy · sendLight travels to its destination.10q
eclipsemode changeAn occluder crosses the key light.25q

See every material and event live in the light lab.

LAW 21 · RADIANCE

Radiance cascades: the penumbra hypothesis at work

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.

Cascades at 1440 × 900
nProbe spacingRays / probeInterval
04.5 px40–5 px
19.0 px165–23 px
218.0 px6423–95 px
336.0 px25695–383 px
472.0 px1,024383–1535 px
5144.0 px4,0961535–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.

Cascades scale geometrically.

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.

Intervals merge front to back.

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.

Color is a derived coordinate.

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°

Light and time are quantized.

Δ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

Make the vocabulary consistent.

Color, type, motion, and geometry turn the optical idea into repeatable choices.

LAW 1–2 · COLOR

Bands, roles, fixed semantics

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.

anchor+0°
near−35°
far+75°
counter−140°
fieldground
surfaceraised
inktext
denseimmovable
✓ Approved · 152° ✕ Rejected · 27° ! Pending · 75° i Info · anchor
LAW 20 · TYPE

Seven roles, line work on top

Use Tilt Prism only at 40px or larger, and Instrument Sans 600 below that. Day fills display type; night outlines it.

display · Tilt Prism · ≥40px

Refraction

headline · Instrument Sans 600

Light splits into spectra at the boundary.

body · Sans 400

The reading ground stays quiet. Color concentrates at edges, status, and dense actions.

data · Spline Sans Mono

--h: 185 → --hr: 129 · q = 40ms · AA ≥ 4.5:1

ui · Sans 500 · 13.5px

Inspect release · Save changes

eyebrow · Mono · 12px

04 / Component family

aside · Instrument Serif italic · one phrase

the human voice, used sparingly.

LAW 8–15 · MOTION

Quantized, massless, causal

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

Enter from the light

Arrives along −(lvx, lvy); its shadow settles 2q later.

Geometry / choose the family before the size
FamilyValuesRule
Radius10 / 16 / 24pxControl / foil / surface. Button sizes step 7–14px.
Boundary1.5px edge · 1px hairline · 2px seamKeep the roles distinct; heavier edges flatten hierarchy.
Spacing1rem · 1.5rem · 2.5–3remComponent gap, group gap, section space.

04 / Components

The rules become a family.

Choose density, then role, then size. Every specimen shares one edge and one light, and every action on it is a light event.

LAW 5 · DENSITY

Permanence condenses light into pigment

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.

LAW 24 · THE RIG

Parameters, not pointers

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.

source
where the key light rests
scroll
fog scatter: beams show and dim with distance
surfaces
where light may land
legibility
how text on the field keeps its contrast
depth
whether z-layer offsets a shadow from its object
night
night as scarce light, or night as light play
LAW 25 · PLAYGROUND

The one place the pointer holds the light

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.

prism
filter
pigment
SIZES × STYLES

The set, laid out

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

xssmmdlgxl primary secondary dense danger

Badge & chip · 3 sizes

✓ Approved ✕ Rejected ! Pending anchor q = 40ms sm
✓ Approved ✕ Rejected ! Pending anchor q = 40ms md
✓ Approved ✕ Rejected ! Pending anchor q = 40ms lg
LAW 22 · ICONS & DISPLAYS

Quantized light on tiny screens

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

Give light a reading order.

Summarize before showing evidence, and keep selection separate from status. As density rises, hairlines replace light.

Pattern / summary

One release needs attention.

Two checks are ready. One needs a decision.

! Review required

The summary floats above the records, and its status is named in words.

Pattern / comparison and decision
CheckResultNext step
Typography✓ ReadyKeep scale
Focus statesCurrent review! ReviewInspect ring
Motion✓ ReadyKeep timing
LAW 6–7 · ELEVATION

Casts float; hairlines ground

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.

RunHueState
0xA1185°ok
0xA2150°ok
0xA3027°fail

06 / Build with it

A recipe, with boundaries.

Build from the material classes, keep the model visible in each choice, and verify the composition.

Load the material, then the light.

<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.

Know what to avoid.

  • Track the cursor with the light

    Ambient motion while reading. The light rests (§4).

  • Loop an animation at rest

    Only aria-busy loops (§5).

  • Tilt or parallax objects with the pointer

    Nothing was selected, so nothing should move.

  • Show a pointer cursor on an inert row

    The cursor promises an action that is not there.

  • Make inert things focusable

    Focus promises an action too.

  • Pick a material for its looks

    Density picks the material (§3).

ACCEPTANCE
  • Moving the pointer across the page without clicking changes nothing outside the playground.
  • At rest the frame counter stops: the loop sleeps.
  • Every light event settles within its listed q; held states end with their state.
  • Under prefers-reduced-motion nothing moves, and held states still show.
  • The contrast audit passes AA under full shadow and full glow.
  • Turning the hue re-tints every band and every shadow; semantic hues hold.
  • A filter’s shadow is its color, a pigment’s is dark, and a prism throws four bands.
  • A rig change re-lights the page in one solve, and nothing else moves.