SESEBON

· 43 min read

Forty-Seven Palettes

I gave my assistant fifty skins and shipped thirty-nine I couldn't actually read. Why the generator produced them, why my first fix passed every contrast check and still looked worse, and what a design system written for agents did for the repair.

For about a year I've been building Jared, a personal assistant that runs on my own hardware — a local-first multi-agent system with long-term memory, a proactive daemon, and opinions. This year it got a new face. This is a short account of how that went, including the part where I gave it fifty colour schemes and couldn't read most of them.


Jared got a second face

The first interface was an ordinary app: a sidebar, a chat column, twenty-nine React components accumulated one feature at a time. It worked. It also quietly decided what Jared was — a chat window with panels bolted on — because the shape of an interface argues for a shape of thinking.

The second one starts from a different premise. Seven domains — surveillance, home, vitals, ledger, recall, cadence, dialogue — and a canvas that composes itself per turn. Ask about training and the canvas puts up training panels. The layout is a response, not a destination you navigate to. Composition is data, and Jared emits it.

That reframing is the actual improvement. The chat is still there, but it stopped being the product.

The design system did the half I'm bad at

The other change was that V2 doesn't own its look. It installs one — @sesebon/ui, a brutalist vocabulary I'd been extracting alongside it: pure-black surfaces, monospace numerals, hairline borders, zero radius, one amber accent, red reserved strictly for alerts.

The part worth mentioning is how it's packaged. Thirty-four components, and each ships a .prompt.md next to its code saying what it's for and when to reach for it — plus a skill file describing the token contract and the rules of the house. It's a design system written to be read by an agent as much as by me.

The effect is hard to overstate for someone working alone. I stopped describing what a panel should look like. I'd name the domain and the data, and get something already in the house style, because the constraints were legible to the thing doing the work. Making Jared fashionable stopped being a design task and became a composition task.

Which is exactly the kind of leverage that makes it easy to ship fifty of something without looking at any of them.

I hoarded colour like a magpie

Somewhere in there I started collecting palettes. Anyone who has fallen into this knows the pattern — you find a five-swatch palette with a nice name, you save it, and it costs nothing, so you do it forty-seven more times.

Then I fed the hoard into a generator, got forty-seven themes out, added them to the skin picker next to the three I'd made by hand, and admired the grid. Fifty skins. Lovely.

What I never did was read one. A few seconds each in the picker, judging the swatch rather than the page behind it — and a swatch is four squares of colour with no words on it.

I had been collecting colours, not reading them.

When I finally measured, thirty-nine of the forty-seven were below the readable threshold. Eighteen were so far below that body text was effectively invisible — one sat at 1.13 against a bar of 4.5.

Five colours, nine jobs

The cause turned out to be mechanical rather than a matter of taste, which was a relief and also a little embarrassing.

Each palette had five colours. The generator assigned them to roles by position — first colour is the background, second is the heading, and so on — and never measured what came out. Three things followed:

The system has five levels of text emphasis. Five colours, no spare. So "body" and "supporting" got the same hex, and "caption" and "placeholder" got the same hex, in forty-two of forty-seven themes. A five-step hierarchy quietly collapsed into three.

Nothing sorted by lightness. In twenty-four of forty-seven, the palette's brightest colour landed on the accent — the one role whose entire job is standing out from the background.

And only seventeen of thirty-eight tokens were ever re-bound, so light themes were drawing chart colours still tuned for a black ground.

The palettes were never the problem. Assigning colour by position and not looking was the problem.

Every check passed and it looked worse

The fix is to stop taking ink from a palette slot and start computing it against each theme's own background. Walk each role away from the surface until it reaches the contrast it owes.

My first attempt held hue and saturation and moved only lightness. Every check passed. The themes looked worse, and it took me an embarrassing minute to see why.

HSL saturation isn't perceptual. A pale pink reports "100% saturation" at 90% lightness, because in that model saturation means distance from grey at this lightness, not how vivid this is. Hold that 100% while you darken it and #FFCAD4 becomes #E3002B — a dusty pink turns into a fire engine. Redone in OKLCh, which separates lightness from perceptual chroma properly, the same pink lands on #94656F. Same colour, just darker.

Both versions scored identically. The numbers could not tell them apart, because I'd asked them the wrong question. Only looking caught it.

The swatch, before and after

Here is the whole set — all forty-nine, filterable, with every ratio the audit produced. Each card paints itself with that theme's real values rather than describing them, which is the point: a contrast figure is easy to skim past and an unreadable caption isn't.

Every skin, painted in its own tokens

Each card renders with that theme's real values — the legibility is the evidence, not the number beside it. Bars are contrast against the panel; green clears the bar for its role, red does not. Filter by how each theme scored before, so the groups keep meaning when you switch views.

midnight-depthsunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary1.83body1.46secondary1.08tertiary1.09ghost1.13accent2.09chart3.75
primary11.74body8.26secondary5.75tertiary3.22ghost1.94accent5.02chart4.50

#312244 · #0064665/5 ladder steps

#312244 · #55A4A65/5 ladder steps

soft-frostunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary1.21body1.13secondary1.11tertiary1.01ghost1.00accent1.04chart1.53
primary14.61body10.81secondary5.72tertiary3.21ghost1.91accent4.84chart4.53

#F0EFEB · #FFF1E65/5 ladder steps

#F0EFEB · #72665D5/5 ladder steps

powder-cloudsunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary1.65body1.25secondary1.11tertiary1.09ghost1.03accent1.04chart1.53
primary14.67body10.64secondary5.77tertiary3.23ghost1.92accent4.84chart4.53

#F0EFEB · #FFF1E65/5 ladder steps

#F0EFEB · #72665D5/5 ladder steps

almond-skyunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary1.34body1.25secondary1.13tertiary1.10ghost1.08accent1.30chart1.67
primary15.97body11.71secondary5.83tertiary3.22ghost1.90accent4.87chart4.52

#F9F9F7 · #F8D5C25/5 ladder steps

#F9F9F7 · #8667565/5 ladder steps

pure-porcelainunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary1.89body1.16secondary1.16tertiary1.05ghost1.05accent1.36chart1.75
primary16.13body12.08secondary5.74tertiary3.20ghost1.93accent4.83chart4.55

#FFFFFF · #F3D8C73/5 ladder steps

#FFFFFF · #856D5E5/5 ladder steps

earthy-minimalistunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary2.56body1.21secondary1.21tertiary1.08ghost1.08accent1.57chart1.62
primary15.50body11.60secondary5.76tertiary3.20ghost1.92accent4.76chart4.50

#ECF8F8 · #E6BEAE3/5 ladder steps

#ECF8F8 · #8965575/5 ladder steps

lilac-hazeunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary2.02body1.24secondary1.24tertiary1.07ghost1.07accent1.70chart1.32
primary12.71body9.03secondary5.71tertiary3.26ghost1.93accent4.95chart4.52

#E1DEE9 · #B6A6CA3/5 ladder steps

#E1DEE9 · #6657785/5 ladder steps

ocean-orchidunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary1.52body1.29secondary1.29tertiary1.21ghost1.21accent1.22chart1.01
primary10.67body7.54secondary5.76tertiary3.21ghost1.94accent4.96chart4.50

#D7B9D5 · #F4CAE03/5 ladder steps

#DEC6DD · #6A475B5/5 ladder steps

pacific-laceunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary20.38body1.30secondary1.30tertiary1.06ghost1.06accent3.36chart1.75
primary16.29body12.03secondary5.74tertiary3.27ghost1.94accent4.88chart4.55

#FFFFFF · #1B9AAA3/5 ladder steps

#FFFFFF · #007D8B5/5 ladder steps

spring-blossomunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary2.48body1.43secondary1.43tertiary1.15ghost1.15accent1.12chart1.54
primary14.77body10.32secondary5.70tertiary3.26ghost1.92accent4.99chart4.51

#FEEAFA · #DEE2FF3/5 ladder steps

#FEEAFA · #62657E5/5 ladder steps

desert-roseunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary4.66body1.44secondary1.44tertiary1.00ghost1.00accent1.28chart1.01
primary10.49body7.58secondary5.78tertiary3.25ghost1.90accent4.91chart4.52

#C5BAAF · #EBCFB23/5 ladder steps

#D3CBC2 · #654D345/5 ladder steps

candy-onyxunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary8.30body2.03secondary1.54tertiary1.00ghost1.12accent17.65chart4.94
primary14.82body10.62secondary5.72tertiary3.22ghost1.91accent4.94chart4.52

#2E0219 · #FBFFB95/5 ladder steps

#2E0219 · #8688465/5 ladder steps

olive-ashunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary1.88body1.58secondary1.58tertiary1.03ghost1.03accent1.39chart1.15
primary11.07body7.96secondary5.79tertiary3.27ghost1.91accent4.88chart4.50

#D5D0CD · #F8F4E33/5 ladder steps

#D5D0CD · #5955475/5 ladder steps

dusty-lilacunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary3.65body1.60secondary1.60tertiary1.11ghost1.11accent1.15chart1.09
primary10.80body7.59secondary5.77tertiary3.21ghost1.91accent4.95chart4.50

#E8C2CA · #F7D1CD3/5 ladder steps

#E9C6CD · #6A4B485/5 ladder steps

frosted-limeunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary2.32body1.68secondary1.68tertiary1.06ghost1.06accent1.07chart1.62
primary15.57body11.33secondary5.71tertiary3.24ghost1.90accent4.83chart4.52

#F1F7EE · #E7F59E3/5 ladder steps

#F1F7EE · #6772165/5 ladder steps

cherry-ashunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary3.54body1.71secondary1.71tertiary1.24ghost1.24accent1.43chart1.75
primary16.21body12.06secondary5.74tertiary3.23ghost1.92accent4.83chart4.55

#FFFFFF · #FFCAD43/5 ladder steps

#FFFFFF · #94656F5/5 ladder steps

monolithic-greyunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary3.07body1.77secondary1.77tertiary1.05ghost1.05accent6.99chart3.37
primary11.00body7.57secondary5.74tertiary3.23ghost1.92accent5.19chart4.51

#323031 · #C1BDB33/5 ladder steps

#2F2E2E · #A4A0965/5 ladder steps

deep-onyx-tealunusableAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary7.19body1.82secondary1.82tertiary1.39ghost1.39accent1.13chart4.43
primary13.61body9.63secondary5.79tertiary3.21ghost1.93accent5.03chart4.52

#1A1D1A · #03120E3/5 ladder steps

#1A1D1A · #7C908A5/5 ladder steps

rustic-trufflepoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary3.37body2.03secondary2.03tertiary1.37ghost1.37accent1.30chart1.09
primary10.49body7.56secondary5.77tertiary3.25ghost1.90accent4.89chart4.51

#AB947E · #C3A9953/5 ladder steps

#D5CABF · #644D3B5/5 ladder steps

stormy-rosepoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary3.21body2.03secondary2.03tertiary1.25ghost1.25accent1.01chart1.02
primary10.42body7.64secondary5.78tertiary3.26ghost1.94accent4.91chart4.51

#C9C5BA · #EBBAB93/5 ladder steps

#CFCBC1 · #6F46465/5 ladder steps

rich-burgundypoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary4.05body2.09secondary2.09tertiary1.42ghost1.42accent1.47chart2.71
primary11.01body7.65secondary5.71tertiary3.26ghost1.91accent5.08chart4.53

#81171B · #5408043/5 ladder steps

#5C1013 · #DD877A5/5 ladder steps

golden-olivepoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary3.95body2.14secondary2.14tertiary1.02ghost1.02accent3.00chart1.04
primary10.28body7.54secondary5.74tertiary3.26ghost1.93accent4.79chart4.53

#A59132 · #FFFBDB3/5 ladder steps

#D9CA82 · #5652375/5 ladder steps

pine-lilacpoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary3.17body2.16secondary2.16tertiary1.40ghost1.40accent3.52chart1.26
primary12.21body8.75secondary5.71tertiary3.22ghost1.92accent4.91chart4.51

#DFD9E2 · #2A7F623/5 ladder steps

#DFD9E2 · #04684C5/5 ladder steps

vintage-steelpoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary3.24body2.17secondary2.17tertiary1.53ghost1.53accent1.51chart1.01
primary10.63body7.61secondary5.79tertiary3.24ghost1.93accent4.86chart4.54

#76949F · #86BBBD3/5 ladder steps

#C1CFD4 · #265A5D5/5 ladder steps

wisteria-nightpoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary7.67body2.23secondary2.23tertiary1.29ghost1.29accent13.97chart1.75
primary16.22body12.15secondary5.76tertiary3.23ghost1.91accent4.91chart4.55

#FFFFFF · #4122343/5 ladder steps

#FFFFFF · #8B667A5/5 ladder steps

camel-graphitepoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary5.33body2.27secondary2.27tertiary2.23ghost2.23accent1.27chart1.02
primary10.50body7.48secondary5.76tertiary3.29ghost1.92accent4.93chart4.52

#C0B7B1 · #C69C723/5 ladder steps

#D1CBC6 · #6F49205/5 ladder steps

industrial-dunepoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary2.97body2.28secondary2.28tertiary1.01ghost1.01accent1.58chart1.06
primary10.49body7.59secondary5.78tertiary3.24ghost1.90accent4.92chart4.51

#93A8AC · #D7CEB23/5 ladder steps

#C3CED1 · #5951385/5 ladder steps

blackberry-silkpoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary6.06body2.29secondary2.29tertiary1.02ghost1.02accent1.43chart1.05
primary10.59body7.55secondary5.71tertiary3.27ghost1.91accent4.99chart4.52

#D5B9B2 · #ECE2D03/5 ladder steps

#DEC8C3 · #5850415/5 ladder steps

shadowed-thistlepoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary6.54body2.30secondary2.30tertiary1.37ghost1.37accent1.40chart1.04
primary10.51body7.50secondary5.71tertiary3.25ghost1.90accent5.04chart4.51

#C4B2BC · #D8D4F23/5 ladder steps

#D6C9D0 · #514D665/5 ladder steps

twilight-indigopoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary5.37body2.43secondary2.43tertiary1.22ghost1.22accent15.53chart4.92
primary14.88body10.89secondary5.74tertiary3.23ghost1.93accent4.89chart4.51

#0D1321 · #F0EBD83/5 ladder steps

#0D1321 · #8883725/5 ladder steps

coral-slatepoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary4.82body3.02secondary3.02tertiary1.87ghost1.87accent1.42chart1.17
primary11.15body7.98secondary5.74tertiary3.25ghost1.91accent4.94chart4.52

#BDD5EA · #F7F7FF3/5 ladder steps

#BDD5EA · #55545B5/5 ladder steps

muted-amethystpoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary4.90body3.33secondary3.33tertiary2.11ghost2.11accent1.25chart1.15
primary10.92body7.89secondary5.73tertiary3.26ghost1.92accent5.02chart4.52

#D6CFCB · #CCB7AE3/5 ladder steps

#D6CFCB · #614F485/5 ladder steps

moody-lavenderpoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary7.37body3.65secondary3.65tertiary1.44ghost1.44accent1.75chart1.08
primary10.64body7.52secondary5.71tertiary3.22ghost1.91accent5.02chart4.53

#C9ADA7 · #F2E9E43/5 ladder steps

#DBC9C5 · #574F4B5/5 ladder steps

deep-maritimepoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary11.52body3.95secondary3.95tertiary1.89ghost1.89accent1.15chart3.89
primary12.19body8.55secondary5.77tertiary3.22ghost1.90accent4.99chart4.52

#1B263B · #0D1B2A3/5 ladder steps

#1B263B · #8496A95/5 ladder steps

grape-mintpoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary11.89body4.31secondary4.31tertiary1.72ghost1.72accent1.57chart1.05
primary10.54body7.66secondary5.70tertiary3.22ghost1.92accent4.94chart4.51

#BBCBCB · #E5FFDE3/5 ladder steps

#C1CFCF · #41573B5/5 ladder steps

sunlit-walnutpoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary5.77body4.31secondary4.31tertiary3.27ghost3.27accent2.00chart1.08
primary10.36body7.47secondary5.81tertiary3.21ghost1.93accent4.80chart4.51

#DCAB6B · #FFFFB33/5 ladder steps

#E7C598 · #5754005/5 ladder steps

muted-fernpoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary7.11body4.35secondary4.35tertiary2.18ghost2.18accent9.77chart5.62
primary15.76body12.14secondary5.70tertiary3.20ghost1.93accent4.76chart4.52

#040303 · #BEB0A73/5 ladder steps

#040303 · #84776F5/5 ladder steps

vintage-grapepoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary6.70body4.36secondary4.36tertiary2.02ghost2.02accent1.49chart1.01
primary10.58body7.57secondary5.76tertiary3.24ghost1.90accent4.94chart4.51

#C0C5C1 · #EAF0CE3/5 ladder steps

#C9CECA · #5054375/5 ladder steps

berry-blushpoorAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary12.11body4.46secondary4.46tertiary2.47ghost2.47accent1.42chart1.33
primary12.82body9.28secondary5.74tertiary3.23ghost1.93accent4.80chart4.50

#F9DBBD · #FFA5AB3/5 ladder steps

#F9DBBD · #96474F5/5 ladder steps

olive-cherryAAAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary13.25body4.79secondary4.79tertiary2.32ghost2.32accent1.44chart4.71
primary14.28body10.27secondary5.75tertiary3.25ghost1.90accent4.98chart4.51

#31081F · #6B0F1A3/5 ladder steps

#31081F · #CD6B6A5/5 ladder steps

prussian-dimAAAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary17.32body4.93secondary4.93tertiary1.29ghost1.29accent17.90chart1.75
primary16.08body12.16secondary5.82tertiary3.24ghost1.92accent4.84chart4.55

#FFFFFF · #0116383/5 ladder steps

#FFFFFF · #57739C5/5 ladder steps

teal-steelAAAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary7.00body5.18secondary5.18tertiary2.26ghost2.26accent2.80chart1.14
primary10.83body7.93secondary5.81tertiary3.28ghost1.92accent4.96chart4.51

#B7D5D4 · #4882863/5 ladder steps

#B7D5D4 · #1F5B5F5/5 ladder steps

mocha-blushAAAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary8.37body5.25secondary5.25tertiary2.78ghost2.78accent1.36chart1.12
primary10.71body7.63secondary5.71tertiary3.27ghost1.92accent5.00chart4.50

#D1CCDC · #F5EDF03/5 ladder steps

#D1CCDC · #5750535/5 ladder steps

corporate-slateAAAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary12.27body5.35secondary5.35tertiary1.32ghost1.32accent9.22chart1.75
primary16.29body12.03secondary5.85tertiary3.22ghost1.94accent4.82chart4.55

#FFFFFF · #284B633/5 ladder steps

#FFFFFF · #5276905/5 ladder steps

obsidianreferencereference

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary20.12body17.68secondary5.73tertiary2.87ghost1.82accent11.46chart5.73
primary20.12body17.68secondary5.73tertiary2.87ghost1.82accent11.46chart5.73

#000000 · #FFB0005/5 ladder steps

#000000 · #FFB0005/5 ladder steps

graphitereferencereference

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary19.43body15.83secondary6.82tertiary3.34ghost2.04accent10.61chart5.19
primary19.43body15.83secondary6.82tertiary3.34ghost2.04accent10.61chart5.19

#0C0D10 · #FFB0005/5 ladder steps

#0C0D10 · #FFB0005/5 ladder steps

shadowed-boneAAAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary13.22body7.89secondary7.89tertiary6.92ghost6.92accent1.19chart1.54
primary14.72body10.86secondary5.77tertiary3.22ghost1.92accent4.82chart4.52

#F1F0EA · #E0DDCF3/5 ladder steps

#F1F0EA · #6C695D5/5 ladder steps

urban-sunAAAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary13.03body9.73secondary9.73tertiary1.12ghost1.12accent1.30chart1.47
primary14.15body10.36secondary5.76tertiary3.23ghost1.91accent4.83chart4.51

#E8EDDF · #F5CB5C3/5 ladder steps

#E8EDDF · #7F62005/5 ladder steps

modern-contrastAAAA

resting hridle

58bpm

Body copy sits at this weight.

Secondary — supporting values

Tertiary caption, micro-label size

Ghost / placeholder text

commitalert

primary21.00body14.91secondary14.91tertiary1.18ghost1.18accent2.02chart1.75
primary16.29body12.09secondary5.72tertiary3.22ghost1.94accent4.89chart4.55

#FFFFFF · #FCA3113/5 ladder steps

#FFFFFF · #9F64005/5 ladder steps

Every palette kept its five source colours; they're now the generator's input rather than its output. What changed is that lightness is computed against each theme's own ground, in OKLCh, so a palette keeps its character and loses its illegibility.

Seventeen backgrounds also had to move, because a mid-grey simply hasn't the room to carry five distinguishable steps — one had 6.5:1 of total range to work with. Those shift toward whichever end the palette had already put its ink, so a light theme stays a light theme.

before after
unusable 18 0
poor 21 0
readable 10 49
full five-step hierarchy 7 49

What I'd tell myself

Three things, none of them about colour.

A generated file whose generator isn't in the repo can't be reviewed. Mine said "generated by generate-themes.js — do not edit by hand." That file wasn't there. Nobody could see what it did, so nobody did, for as long as it took me to notice. It exists now, next to the palettes it reads.

Measure the thing, then look at it. Those are two different checks and neither substitutes for the other. My contrast audit found thirty-nine broken themes the eye had missed; my eye found a fire engine the audit was perfectly happy with.

Then make the check permanent. There's a script now that reads the shipped stylesheet and fails the build on any of eight hundred and thirty-three contrast pairs, on any collapsed hierarchy, on series colours that aren't distinct from one another. Run against the old file it reports seven hundred and seven problems. It won't catch a fire engine — nothing automated will — but it means the next batch can't ship blind in the way this one did.

Fifty skins, and I can read all of them now. It only took admitting I'd never actually looked.