"Pretty" is taste; "legible" can be measured. Faint grey text looks fine on a good monitor and disappears on a phone in daylight — picking by eye guarantees that eventually.
python3 addons/gohud/tools/check_contrast.py
| What | Required |
|---|---|
| Body text | 4.5 : 1 |
| Large text (24px and up) | 3 : 1 |
| Accent borders and icons | 3 : 1 |
| Decorative borders | 2 : 1 — WCAG does not apply its 3:1 rule to decoration |
| Adjacent surfaces | 1.12 : 1 — enough that a card reads as separate from its background |
Same-hue text on a same-hue tint
This trap appeared three times in this codebase — pressed buttons (accent panel, accent label), danger buttons, and chips. It looks good in a swatch and collapses the moment it is measured, because the two share a hue and so have no difference in lightness.
Label colours are therefore not chosen by hand. The theme builder and GoSkin each
push lightness — and only lightness — until the threshold is cleared on the panel the text actually
sits on. Hue and saturation survive, so the palette keeps its character and changing it keeps its contrast.
Colour read as text and colour painted as area want opposite things
On a light theme a warning colour has to darken to stay readable as text (#96500A).
When the bar fill inherits that, the experience bar turns brown. Colour alone cannot fix it:
yellow is intrinsically bright, so it clears 3:1 over no grey track at all, and darkening the track
makes it worse (1.52 → 1.06).
So the two are separated. Colour stays vivid — the palette carries fill-only colours such as
warning_vivid. The edge carries the contrast — GoSkin draws a 1dp
outline on any fill that cannot reach 3:1 against its track, so "how full is it" stays legible.
MY_LIGHT = dict(
warning=hexc("9A5407"), # read as text
warning_vivid=hexc("F2A007"), # painted on the bar
)
*_vivid, a fill that comes out dark makes the
builder print the bar looks drab — add warning_vivid to the palette. A place that used to
degrade silently now says so.
A floating panel cannot know what is behind it
A HUD is drawn over the game — a snowfield, or a cave. If its panel is translucent, whatever is
behind it bleeds through and changes the panel's colour, taking the text contrast down with it.
At 82% opacity the dark themes' muted text fell to 3.74:1 over a bright backdrop — below
the minimum.
So translucent panels are measured with pure white and pure black behind them; those are the worst a game scene can offer. The bundled themes' HUD panel is opaque enough to pass (92%) while keeping the glassy look.
For the same reason icon colours are not fixed either. The quick slot's glyph was hard-coded
white and measured 1.11:1 on a light theme — it now follows the panel it sits on, exactly
as the labels do.
check_contrast.py reads the theme files;
colours a skin mixes while running (chips, slots) are measured by the suite's
skin contrast section inside Godot.