The runtime injects brand colors, safe-area insets, layout helper classes, and a signature glow — so widgets look at home on the phone and adapt to light/dark without shipping their own theme.
Brand color variables
Six --preen-* variables are set on :root, resolved for the current
appearance. The runtime also sets color-scheme and data-preen-scheme
("light" / "dark") on the document element.
| Variable | Light | Dark |
|---|---|---|
--preen-accent | #C2143A | #E0435C |
--preen-bg | #F0EEE6 | #241D15 |
--preen-ink | #1A1915 | #ECE8D6 |
--preen-muted | #6B6862 | #A79C8C |
--preen-grain | #CDC1AA | #3A2D1F |
--preen-green | #2EA043 | #327A3A |
.value { color: var(--preen-ink); }
.label { color: var(--preen-muted); }
.accent { color: var(--preen-accent); }
Your device’s bird color
Each paired phone is given its own bird tint — the color of its bird in the
Mac’s device strip (and of its “active” check in the widget list). The runtime
exposes that same color to widgets as --preen-bird, so a widget can theme
itself to match the device it’s running on:
/* Match this device's bird; fall back to the brand green if unset. */
.hero { color: var(--preen-bird, var(--preen-green)); }
Notes:
- Like the brand colors, it’s resolved for the current appearance (light/dark).
- It’s optional — always provide a fallback (
var(--preen-bird, …)). It’s absent when a widget is opened standalone, and from older Mac hubs. - Each device starts with a tint from its position in the paired-device strip
(cycling a small palette so two phones read as distinct); you can override it
per device on the Mac (right-click the phone → Bird color). Either way the
widget’s
--preen-birdfollows whatever color that phone shows on the Mac.
The built-in Tap demo uses it: its bird, button, and pulse ring all take
--preen-bird, so the on-screen bird matches the one it chirps on the Mac.
Safe-area insets
The notch, calibration offset, and bottom bleed are pushed as variables — use them for padding so content clears the hardware:
| Variable | Meaning |
|---|---|
--preen-safe-top | Clears the notch + user calibration. |
--preen-safe-right / --preen-safe-left | Mirrored side insets. |
--preen-safe-bottom | Always 0px — content bleeds to the bottom edge. |
--preen-display-bottom | Symmetric chin (= top) for vertically-centered layouts. |
Two helper classes apply these for you:
.preen-safe— clears the notch, mirrors side insets, bleeds to the bottom..preen-display— same, but adds a symmetric bottom chin so centered content sits optically centered..preen-content— a full-bleed child wrapper (100%width/height,border-box).
<body>
<div class="preen-safe">
<div class="preen-content"><!-- centered, notch-safe UI --></div>
</div>
</body>
Orientation
The runtime toggles preen-landscape / preen-portrait on <body> on
load, resize, and orientationchange — the CSS orientation media query is
unreliable in the widget web view, so use these classes instead:
body.preen-landscape .row { flex-direction: row; }
body.preen-portrait .row { flex-direction: column; }
The glow
Add preen-glow to <body> for the signature Preen hue — a radial wash
rising from the bottom into the background. Tune it with three variables:
body {
--preen-glow-color: #C2143A; /* the hue */
--preen-glow-base: #000; /* what it fades into */
--preen-glow-intensity: 0; /* 0 = off … 1 = full */
}
Drive --preen-glow-intensity from your data for an at-a-glance signal (e.g. the
system widget ramps it with load):
window.PREEN.onData((d) => {
const hot = Math.max(0, Math.min(1, (d.cpu - 80) * 0.05));
document.body.style.setProperty("--preen-glow-intensity", hot.toFixed(3));
});
The style kit
A handful of themeable building blocks ship with the runtime so widgets share one
visual language instead of re-styling the same idioms. Each reads the brand
tokens (--preen-accent / --preen-muted) with literal fallbacks — so override
--preen-accent on <body> to re-tint a whole widget at once.
| Class | What it is |
|---|---|
.preen-press | The house “jelly” tap feedback — springs down on press, bounces back. Drop it on any tappable element. |
.preen-label | Small uppercase caption (section headers, device names, on/off state). Muted; add is-on to accent it. |
.preen-readout | A large live number/timer — tabular digits (no jitter as it ticks) at a light weight. Set your own font-size. |
.preen-faint | Ambient low-contrast text (a best-time line, idle stats). |
.preen-orb | The circular hero button (toggles, push-to-talk, power). Neutral dark by default; add is-on to fill it with the accent + a matching glow. Size with --preen-orb-size; nest an <svg> glyph. |
<body class="preen-glow" style="--preen-accent:#7b6cff; --preen-orb-size:148px">
<!-- a toggle: tap-springs, fills + glows when active -->
<div class="preen-orb preen-press is-on">
<svg viewBox="0 0 24 24"><!-- glyph --></svg>
</div>
<div class="preen-label is-on">Focused</div>
<!-- a faint, ticking count-up timer -->
<div class="preen-readout preen-faint" style="font-size:58px">12:34</div>
</body>
Toggle is-on from JS to switch state; the orb, label, and accent all follow it:
const on = !el.classList.contains("is-on");
orb.classList.toggle("is-on", on);
label.classList.toggle("is-on", on);