A bevel encodes a light direction, which means it cannot be
inverted, only re-lit. That one fact decides the
whole family: filter: invert(1) on a bevelled control swaps the
light pair for the dark pair while leaving both on the same sides, and that
swap is the definition of sunken, so every button comes out looking
pressed. Nothing here inverts anything. Each theme declares where its light
comes from; the rings are derived from that declaration and from the
theme's own surfaces by tools/palette.py, which gates them.
Sunken is the same two colours on the opposite sides, which is what a
well looks like under a fixed light. The dot on each control marks the
direction that theme declared.
⭐ And a bevel states an affordance. Raised means
pressable, sunken means a well that takes input, and a bevel on something that
is neither is lying — the same standard the ornament is held to. That is
checked in audit.html rather
than asserted here: every .ov-bevel has to be operable.
import 'overscan'
import 'overscan/overscan.css'
<button class="ov-bevel">…</button>import 'overscan'
import 'overscan/overscan.css'
<button
class="ov-bevel"
aria-pressed="true"
>…</button>import 'overscan'
import 'overscan/overscan.css'
<button class="ov-bevel ov-bevel--deep">…</button>none, exactly the way it declares no field shader — that is
its register, not an omission — and states the affordance the way it
states everything else, as a flat block with a boundary.lit: two highlight rings, no shadow, depth read from brightness
falloff. A mechanism difference rather than a hue difference, which is the
test every theme in this kit has to pass.--ov-line is, and are
ungated — but they are still required to be strictly ordered,
because a shadow lighter than its face is a lie about which way the light is
coming from, and the direction is the one thing the treatment encodes.An audio rack is the model and webaudio-controls is the reference: a faceplate with things mounted on it. That is where raised controls actually come from, and it is a stronger case for the treatment than a bevelled button on its own, because here the bevel is doing work rather than decorating. ⭐ Everything on a faceplate is either raised out of it or sunk into it, and which one says what the thing does — a knob and a button are raised because you operate them, a readout is sunk because you do not. Nothing sits flush. 🔴 Two ring sets: the faceplate is raised out of the field and the controls are raised out of the faceplate, so they cannot be lit from the same face or a knob would read exactly like the panel it is screwed to. Both come from the same declared light, so the rack still reads as one object under one lamp.
⚠️ The controls are drawn, not sprited. A sweep of
101 steps measured webaudio-controls' generated
101-frame filmstrip at 1.42° of mean error against a single
continuously rotated frame at 0.00°, while being thirteen times
larger — and findings 122 and 123 are why: the frame index truncates
rather than rounds, so the knob is drawn below the value it holds at
every one of 101 steps, and the strip is generated with the wrong divisor so a
knob at maximum never reaches its own end stop. A filmstrip only earns its
cost when the frames are not rotations of one another.
<ov-dial> rotates one pointer, so it has nothing to gain and
an error to avoid.