Lab

Every uniform, every token, the hinge spring, and how it was made.

01

Playground

Open Shut 0.300
 
02

Tokens

--void#020204Hero and footer, the dark stage
--ink#000000Text, borders, icon fills
--carbon#636363Secondary text
--slate#888888Tertiary text, inactive
--silver#c6c6c6Dividers, placeholders
--graphite#575757Dark secondary buttons on light
--paper#ffffffCards, button surfaces
--bone#f8f8f8Page canvas, nav
--linen#efefefButton fills, pills
--lime#f2fcb3Accent wash band
--saffron#ffdc5cWarm highlight
--spectrum6 stopsOnce per page. The divider under the hero.

Shut

Follows the hinge, not a timer. 54, Clamp 300

Eight ways to shut. 48, Inter Tight 650

Heading small, 24

Subheading, 22.

Body at 18, line height 1.5. Short flat sentences. No exclamation marks. One joke per screen.

Body small at 16 with a 2.19 line height for long copy. It breathes.

Eyebrow · JetBrains Mono 13 · 0.1em

--ease-lidcubic-bezier(.45, 0, .85, .55)
--ease-2cubic-bezier(.4, 0, .2, 1)
--easeease · 0.2s, color only
03

Spring editor

The hinge on this page. SwiftUI's vocabulary on the left, the physical one on the right. They are the same spring.

mass
1
stiffness
damping
 
04

How it reads the lid

Most Apple silicon MacBooks, every M2 and later and most M1s, have a hinge angle sensor. It shows up as a HID device on IOKit: vendor 0x05AC, product 0x8104, usage page 0x20, usage 0x8A. Feature report 1 carries the angle in bytes 1 and 2, little-endian, in degrees. That is the whole interface. No private framework, no entitlement.

The raw readings are noisy and jump by a degree or two, so the app runs them through three stages. A short least-squares line fit smooths and gives velocity. A One Euro filter keeps latency low when the lid moves fast and steadies it when it moves slowly. A calibration learns the shut floor and the open reference for this particular machine, so nobody types their angles in. The result is a HingeState: angle, velocity, direction, and progress from 0 (open) to 1 (shut).

Idle stays idle. The sensor is polled once a second at rest, and only a real movement of two degrees or more wakes it. Nothing draws unless progress changed. Macs that only report open or closed play the style on a short timeline instead, about half a second to a second and a half depending on Speed, and after sleep the reopening is a spring from black because the lid finished moving while the screen was off.

Progress passes through a curve, cubic-bezier(.45, 0, .85, .55), published on this site as --ease-lid. The effect begins in earnest a set number of degrees above shut. The app calls that Speed. On this page it is the Speed slider, and it starts at 45°.

Porting the shaders to WebGL

The app renders with Metal: one fragment shader for Sinkhole, one for Frost, and a 48×48 mesh with a vertex stage for the five panel styles. The site runs the same code as GLSL ES 3.0, uniform for uniform. Three things needed care.

There is one WebGL context on the page. Every live surface is a plain canvas that receives a copy of the shared frame, so the nav can blur over it and no browser runs out of contexts. A surface only draws when its job changes, so a page at rest costs nothing.

Explorations

Source for the app and this site: notthamer/shut.