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.