Why a glass replaced a character in my menu bar
A small SwiftUI app sits in my Mac menu bar. It used to be a character with a face. I retired the face and replaced it with a glass: a round cup holding my own double-chevron mark, which fills with liquid as the day's three tasks get done. One look tells me how much of today is left. I did not need the glass to have an opinion about that. I needed it to be legible from across the room.
A note on who did what. I designed it and decided every value that follows. Claude wrote the Swift, and I reviewed every change. The rules are mine.
The fill and the colour
The glass reads a single number between zero and one, the fraction of the day's tasks done, and turns it into two things: how full the liquid is and what colour it is.
// Liquid level, as a fraction of the glass's own size
let level = 0.16 + 0.76 * progress
// Rim thickness, so the fill sits inside the glass, not against its edge
let rimWidth = size * 0.075
Colour is one hue value walking from red to green as progress climbs, never a crossfade between two named colours:
let hue = (2 + 103 * progress) / 360
// 2° at zero tasks done, 105° when the day is finished:
// red, through orange, through yellow-green, to green
The surface itself is never flat. Two waves drift sideways forever, one at
amplitude size * 0.028 completing a cycle every 2.6 seconds, a
second at size * 0.022 a half period behind it and completing
every 4.1 seconds. Because the two periods do not divide evenly, the surface
never repeats itself exactly, which is what makes it read as liquid instead of
a gif. It costs nothing while it runs: the waves are drawn once and handed to
the compositor, so the app's own CPU does nothing between frames. Only a
change of level or colour actually animates, and that lasts 1.4 seconds.
The slosh, and how it settles
Drag the glass and the water leans. I wanted the lean to come from the same
physics a real glass of water obeys, not a hand-tuned wobble, so it is a
damped pendulum reading the window's own acceleration: gravity of 3500 points
per second squared, a spring stiffness of 110 pulling the surface back level,
and a damping value of 3.4 so it settles in two or three swings rather than
one. Tilt is capped at 0.7 radians, about 40 degrees, because steeper reads as
a glitch rather than a liquid. Push past a wall at 1.15 times that cap and the
water rebounds off the glass at -0.35 instead of climbing it. A rough surface
while it is agitated decays on its own at exp(-2.4 * t), so the
chop fades out rather than cutting off.
That is the whole slosh: one spring, one wall, one decay curve, running only while the window is actually moving. The moment it settles the loop stops and the app goes back to costing nothing.
Hover and press
Lift the pointer over the glass and it grows to a scale of 1.06. Press it and it shrinks to 0.93. Both are springs, not curves, because a hand is on it: heavier damping on the way down than the way up, so a press feels immediate and a release feels a little more considered.
The alert, and why it gets to shake
When something is actually broken, the glass has to interrupt, so it is the
one place on this whole site where I allow motion that a normal element never
gets: a rim that flashes between red and near white on a 0.45 second
autoreverse, three sonar rings scaling from 1 to 1.4 while fading out over 1.5
seconds, each one starting 0.5 seconds after the last, and a shake along
[0, -0.16, 0.16, -0.13, 0.13, -0.08, 0.08, -0.03, 0] radians at
keyTimes [0, .03, .07, .11, .15, .19, .23, .27, .3], repeating
every two seconds until it clears. Everywhere else on this site, alarm-style
motion is banned outright. It is allowed here because it is reporting a real
failure state the glass cannot otherwise interrupt me about, and it turns
itself off the instant the thing it is reporting is fixed.
Reduced motion
With reduced motion on, every state still changes, it just stops travelling to get there: the waves hold still instead of drifting, a level or colour change lands rather than animating into place, and the shake and rings do not run. Nobody using the setting is told less about their day. They are told the same thing, faster.
What I cut
The old character had a face with an expression that tracked a mood variable. I liked it for about a week and then found myself reading the expression instead of the number, which is backwards for a status indicator. The glass replaced mood with one honest measurement: how much of the glass is full, and what colour it is. Nothing else about it has an opinion.
There's a live version of the glass, running the same numbers, on the home page.