Settled 2026-09-21

The primary action

The home page shipped a solid gold call to action and it reads as too loud. Underneath that is a contradiction in the accent policy itself, which lists the primary button under the warm accent while also reserving warm for state and barring either accent from large fills. Regenerated on the interaction ladder: three treatments the system defines, across the two hues the policy is arguing about, with every value a step on a scale rather than a percentage somebody picked. Settled on J, which is not in the grid: the question turned out to be about the pair rather than about the primary alone.

Run forRey’s Lab

Record

Settled 2026-09-21 on J. These values describe the comparison that produced it; the page is a record from here and is not updated again.

Opened
2026-09-20
Regenerated
2026-09-21
Status
settled · J
Variable
treatment × hue
Candidates
8 · 4 × 2, no holes · plus I and J, off-grid
Settled
2026-09-21
Floor
4.5:1 label · 3:1 border

Hypothesis

Written before looking, so that it can be wrong in public.

  1. 01

    B wins. It keeps the affordance that makes A work, resolves the double-booking, and measures 64% of gold's separation — enough to stop shouting without becoming timid.

  2. 02

    C and D will be hard to tell apart, and that is a result rather than a flaw: at 8% on a base this dark the fill contributes almost nothing, so the pair isolates hue from brightness. If one clearly beats the other, the difference is the policy argument and nothing else.

  3. 03

    E loses to the fixed secondary rather than to the other candidates, which is the useful way to lose.

  4. 04

    G is the wildcard and the most likely to overturn the prediction. If a delayed fill reads as calm at rest and obvious on hover, it beats B on both axes at once and the outline treatments stop being a lower bound and become the answer. The label inversion is where it will fail if it fails.

  5. 05

    F is predicted to measure fine and feel wrong — a warm outline is the treatment with the least to distinguish it from the secondary, and if that turns out to be false then the secondary needs re-tuning more urgently than the primary does.

Candidates

Each band is the hero as it would ship. Look at them at full width on a phone before reading a single number below them.

A · Solid warm

The control. What shipped.

Full-stack engineer. React and AWS.

Rey’s Lab

I build things and write down what they cost. Placeholder copy, here so the button is judged against a real page rather than against empty space.

What the home page has now, down to the class list. It is unambiguous as a button, which is the whole argument for it: a recruiter scanning for seconds gets one obvious thing to press. It is also the candidate that breaks both rules at once — a large fill in the brightest colour the palette has, in the hue that already means something else three inches above it in the nav. Separation 12.37:1, the loudest in the set by a wide margin, which is the complaint stated as a number.

measuring…

B · Solid cool

Same shape, interaction hue.

Full-stack engineer. React and AWS.

Rey’s Lab

I build things and write down what they cost. Placeholder copy, here so the button is judged against a real page rather than against empty space.

Keeps everything that works about A and changes only the hue, which resolves the double-booking: cool is defined as anything actionable, and a call to action is the most actionable thing on the site. Separation 7.96:1 — 64% of gold's, which is the 'quieter without being timid' claim measured rather than asserted. Still a fill, so it still contradicts the letter of the never-a-large-fill rule, but it is a 200×44 control and the rule was written about page backgrounds.

measuring…

C · Soft cool

Surface tinted 8% with cyan, step 8 border, step 11 label.

Full-stack engineer. React and AWS.

Rey’s Lab

I build things and write down what they cost. Placeholder copy, here so the button is judged against a real page rather than against empty space.

The system's own answer to what the old 14% tint was reaching for, arrived at twice. The fill began as the accent scale's step 3, which is #002e44, a dark teal that appears nowhere else on the site — a candidate that introduces a hue is answering a different question than the one being asked. The quick fix was to drop the fill to the plain card surface, which took the teal out and took the cool out with it, leaving a violet button with a cyan border. The fill is now the card surface tinted 8% with the accent itself: #262a55, violet mass with the hue riding on top, which is how every other surface here is built. Separation 1.31:1 — the fill is nearly nothing on a base this dark whatever it is made of, so the affordance is carried by the border at 3.81:1. Whether that still reads as the single primary action is what has to be looked at. Label 6.81:1 on the fill.

measuring…

D · Soft warm

Warm kept, fill dropped to an 8% tint.

Full-stack engineer. React and AWS.

Rey’s Lab

I build things and write down what they cost. Placeholder copy, here so the button is judged against a real page rather than against empty space.

Answers the loudness complaint without touching the policy question, and keeps the warm primary that disko.media — the pairing this site's type came from — actually uses. Its fill is the same rule as C's with the other accent: #362c46, the card surface tinted 8% with gold, rather than warm step 3's #30270e, which is an olive and is the warm half of the same foreign-hue problem. Separation 1.37:1, within six hundredths of C, which is the point of having both: at this strength the two differ almost entirely in hue, so the band isolates the policy argument from the brightness one. It only works if the nav's current-page marker gives up colour, which the policy already permits, since it requires a second non-colour signal on every state warm marks.

measuring…

E · Outline cool

Border and label at rest. Fills to the tint on hover.

Full-stack engineer. React and AWS.

Rey’s Lab

I build things and write down what they cost. Placeholder copy, here so the button is judged against a real page rather than against empty space.

The lower bound at rest, and the closest to how the reference sites treat everything else. If E still reads as the primary against a fixed muted secondary, the fill was never doing the work and both A and B are overspending. Its hover is the interesting part: the border brightens from step 8 to the brand cyan and a fill appears underneath, so both changes point the same direction and the button gains substance rather than just changing colour. That fill was a hand-sampled hex until 2026-09-21 — Rey picked #252e52 off a mockup by eye, and the tint rule's 8% step landed on #262a55, close enough that the literal retired. E takes the 12% step rather than the 8% one, so that a hovered E is not the same object as C sitting still. Label 8.95:1 on the page at rest, 6.33:1 on the fill when hovered, border 7.96:1.

measuring…

F · Outline warm

Warm, nothing filled.

Full-stack engineer. React and AWS.

Rey’s Lab

I build things and write down what they cost. Placeholder copy, here so the button is judged against a real page rather than against empty space.

The cell that keeps the grid square. Without it there is no way to ask whether E beats D for being cool or for being unfilled — the two variables tangle at exactly the point the comparison exists to separate them. It is also the quietest way warm can stay the primary, and its border is the weakest in the set at 3.46:1, which is worth knowing before it wins.

measuring…

G · Delayed solid cool

Outline at rest. Fills solid on hover.

Full-stack engineer. React and AWS.

Rey’s Lab

I build things and write down what they cost. Placeholder copy, here so the button is judged against a real page rather than against empty space.

Rey's suggestion, and the only candidate whose hover is a jump rather than a step. At rest it is E exactly; on hover it becomes B exactly. That is the appeal — the quiet rest state of an outline with the unmistakable affordance of a fill, arriving at the moment a pointer says it is wanted. Two things to watch. It moves six steps where every other state on this site moves one, so it is the largest state change in the system by a wide margin. And the label has to invert: cyan cannot sit on a cyan fill, so it flips to the page colour in the same 150ms. Whether that reads as responsive or as a flash is the question, and it is a question only looking can answer.

measuring…

H · Delayed solid warm

Same jump, warm.

Full-stack engineer. React and AWS.

Rey’s Lab

I build things and write down what they cost. Placeholder copy, here so the button is judged against a real page rather than against empty space.

Here because the grid rule says so, not because anyone asked for it. G without H would confound the treatment with the hue exactly as the first version of this page did, and the whole coverage section exists to stop that recurring. It is also the more likely of the two to fail: gold's solid is the loudest fill in the set at 12.37:1, so H spends its rest state being the quietest candidate and its hover being the loudest, which is a wider swing than G's and may be more than a hover should carry.

measuring…

I · The pair

B's primary, and a secondary that is not the ruler.

Full-stack engineer. React and AWS.

Rey’s Lab

I build things and write down what they cost. Placeholder copy, here so the button is judged against a real page rather than against empty space.

The only band where the secondary moves, and so the only one that is not a controlled comparison — it is off the grid deliberately and it is not a ninth candidate for the same question. Rey's arrangement: a solid cool primary with a cool outline secondary that fills on hover, which is Bootstrap's btn-primary next to its btn-outline-primary and one of the most-copied pairings on the web. Hierarchy comes from fill rather than from hue, which satisfies the rule that colour is never the only signal. What it cannot do is fill solid on hover: that would land the secondary on the primary's rest state exactly, two identical buttons with the wrong one lit. It fills to the soft tint instead and brightens its border to the brand cyan — E's hover, one rank down from a solid. Filling it to something between was checked and does not exist on this scale: step 8 carries no label at 4.5:1 in any colour the site owns, and step 7 works only with a pale cyan label, which inverts the dark-text-on-solid convention the ladder just established.

measuring…

J · The pair, ruled

I again, under one rule: a fill means primary.

Full-stack engineer. React and AWS.

Rey’s Lab

I build things and write down what they cost. Placeholder copy, here so the button is judged against a real page rather than against empty space.

The same two buttons as I, with the collision designed out instead of tolerated. One rule decides everything: A FILL MEANS PRIMARY. The primary is filled at rest; the secondary is a ring and never gains a fill in any state, so hovering it cannot produce a second primary. Three shipping systems were read for this. Bootstrap accepts the collision — `btn-outline-primary:hover` sets its background to `$color`, the identical value `btn-primary` wears at rest, and survives only because their primary moves too. Carbon drops the shared hue: in the g100 dark theme `buttonTertiary` is white hovering to gray-10, so no collision is possible. Material 3 never fills it at all — its outlined button has no hover container colour in the token set and `hover-outline-color` is listed as unsupported. M3 has the right rule and the wrong mechanism, because a state layer over 0.009 luminance is nothing. Second finding, separate and worse: step 10 is the wrong colour to hover a solid to. Every published Radix DARK scale has 10 lighter than 9 — blue 0090ff to 3b9eff, cyan 00a2c7 to 23afd0, amber ffc53d to ffd60a — but all three scales generated here through the ACCENT slot have it darker, because the generator pins step 9 to the seed and these seeds sit above its own curve. Only the gray, generated through the gray slot, came out the right way round. So I's primary dims by 1.13:1 on hover, which is measurably a change and visually nothing. J climbs 9 to 12 instead and presses back to 11. Third: I's resting ring is step 8, the only thing on the site wearing that step — same hue to within a degree but 35% lightness against the brand's 60%, so it reads as a second, muddier cyan with nothing to be read against. J uses two cyans total, step 9 at rest and step 12 on hover, for both controls. This is what ships as of 2026-09-21 — it went into global.css before it was a candidate, which is backwards, and it is here so the comparison exists.

measuring…

Coverage

Two axes, four treatments by two hues, plus I and J off the grid. Letters read across the grid now rather than following the order candidates were added — see the last note for why that renumbering was worth its cost.

HueSolidSoftOutlineDelayed
CoolBCEG
WarmA · controlDFH

Square, with no empty cells, which is the whole reason it is drawn. The previous version had cool in three treatments and warm in two, and a hole in a comparison grid is not an omission — it makes the question unanswerable, because no comparison between an unfilled cool candidate and a filled warm one can separate the hue from the treatment. A third near-white row was dropped with the old set: it was a calibration for an instrument question that has since been stated plainly in the notes instead.

Method

Design a comparison so one thing moves. Experiment 01 learned this the expensive way and experiment 02 inherited it.

  1. 01

    Every band is the same hero block, the same copy and the same secondary button. Only the primary button's treatment and hue move.

  2. 02

    The secondary is held constant on purpose. It is the ruler: a primary that does not visibly out-rank a fixed muted outline has failed, whatever it measures.

  3. 03

    Every treatment is a step on a generated scale. There is not one invented percentage in the set — that is the difference between this version and the one it replaces, and the reason this experiment can now be closed rather than deferred.

  4. 04

    Separation is the fill's contrast against the page — how far the control jumps off the background, which is what 'too aggressive' actually describes. Label is the ordinary text check. Border is the control's own edge against the page, which WCAG puts at 3:1 for anything whose shape carries the affordance.

  5. 05

    The buttons are real anchors with real hover, active and focus states, because those are part of the treatment. Hover is forced by script for the measurement rather than waited for, since an unmeasured hover is the easiest place for a candidate to slip under the floor.

  6. 06

    Both are read off the rendered elements, not from the token names declared above.

  7. 07

    Judged at full width on a phone first. A filled control takes a larger share of a 390px screen than of a desktop, so the loudness complaint is strictest there.

What the winner has to accept

These hold whichever candidate wins, and they are why the question is narrow enough to answer by looking.

The pair moves together

Whatever wins, the secondary is re-tuned against it afterwards. It is frozen here to make the comparison fair, not because it is finished — if E wins, the secondary has to drop to text-only or the two stop reading as a hierarchy at all.

Colour is never the only signal

Whichever hue wins, the primary is also distinguished by fill, border or weight. Roughly 8% of men cannot reliably separate these two hues, and a CTA that depends on telling gold from cyan is a CTA that disappears for them.

The label is 11px, and both solids take dark text

It is the mono micro-label like everything else, which puts it under the 4.5:1 small-text floor rather than the 3:1 large-text one. Radix reports white as the contrast colour for the cyan solid; in WCAG terms that is 2.24:1 and fails outright, so both A and B take the page colour as their label — 12.37:1 on gold, 7.96:1 on cyan. That matters beyond the label: the legibility argument that quietly favoured gold is gone, and the two solids are now equally readable. What is left between them is the policy, which is what this experiment was always actually about.

A control's border is not decoration

For C through F the border is what makes the thing a button, so it falls under WCAG 1.4.11 at 3:1 against the page rather than under the decorative-edge exemption the site's card borders use. Radix assigns element borders to step 7; measured here that is 2.80:1 cool and 2.45:1 warm, both under the floor. Step 8 clears it at 3.81 and 3.46. Every bordered candidate uses step 8, which is the second place on this site where step 7 failed and step 8 was the answer — the focus ring was the first.

The policy gets amended, not bent

If a cool candidate wins, 'primary button' comes out of warm's job list in the design plan and in global.css. If a warm one wins, the nav's current-page marker gives up colour. Either way the contradiction is resolved in writing rather than left as a per-component judgement call.

Notes from building it

Not the result. What the comparison turned up about itself while it was being assembled.

The measure has to match the complaint

The first instinct was to print contrast ratios, which is what the other experiments do. It would have been useless here: every candidate passes contrast comfortably, including the one that prompted the experiment. The complaint was that the control is too loud, and loudness is the fill's separation from the page, not the label's separation from the fill. Measuring the wrong quantity would have produced six green ticks and no answer.

The invented percentages were why this could not be closed

The first set ran on a 14% tint, a 26% hover and a 16% outline fill. None of them came from anywhere. Settling the experiment would have meant settling those numbers, and any later system would have regenerated them — which is precisely why the experiment was left open rather than decided. Every value in this version is a step on a scale, so the set is now judgeable on what it looks like rather than on where its numbers came from.

The tint did nothing, and now there is a reason

Carried over, and confirmed twice. The old note recorded that a 14% tint measured 1.03:1 against an unfilled 1.00:1 — the fill moved luminance by three hundredths and on screen the two were nearly the same button. The cause was the base: the page sits at 0.009 relative luminance, so mixing anything into it barely moves the result. The soft fills are now built the other way round, as the card surface tinted with the accent, and they measure 1.31:1 and 1.37:1. Better by about a third of a point and still almost nothing. Three constructions have now been tried and the number barely moves, which is the finding: a soft fill on a base this dark is faint by arithmetic, not by choice, and it is why C and D carry a border rather than relying on the fill.

The instrument is blind to chroma

Carried over, narrowed. Separation is a luminance ratio and has no chroma term, so it cannot distinguish C from D even though one is visibly bluer than the other. The old set made the same point twice, with a near-white candidate that measured loudest and was expected to feel calmest; that candidate is gone with the rest of the old set, but the limit it exposed is not. It is stated here rather than patched: the numbers rank the candidates honestly on one axis, and the eye settles the rest.

Each treatment hovers on its own axis

A solid moves to step 10; a soft fill moves up one strength of the tint, 8% to 12%; an outline gains the fill it does not have at rest, at the hovered strength rather than the resting one. That replaces the old arrangement, where fills dimmed by an opacity step and tints brightened by twelve percentage points, so the two sides of the set hovered in opposite directions. They still do, in a sense: a solid at step 10 is slightly darker than at 9, while a soft at 12% is lighter than at 8%. The difference is that the direction is now a property of the ladder rather than a choice made per candidate, and one rule produces both. The outline taking the hovered strength is the one deliberate exception, and it is there because at the resting strength a hovered E and a resting C would paint the same fill.

The soft hover is a pressed step wearing a hover's name

Open, and the next thing to settle. The soft fills step 8, 12 and 16 per cent, which looks like a ladder and is not one: the rest-to-hover move measures 2.4 points of lightness cool and 3.3 warm, while every other hover on this site — a card taking the shared hover token — moves 7.0. On screen it is barely visible. Raising the strengths to 8, 20 and 24 matches the site's step and was tried on 2026-09-21, but the outline candidates fill to the same token on hover, so it drags E's hover off the value Rey picked by eye. Either the outline gets its own strength, or the hover moves something other than the fill. Recorded here rather than patched, because picking between those two is the same kind of decision this experiment exists to make.

Solids only have two states

The scale gives step 9 for rest and step 10 for hover and stops. There is no step 11 for a solid, because 11 is text. So A and B press to the same colour they hover to, and the distinction is carried by the focus ring and by the pointer itself. Recorded as a limitation rather than solved with an invented value, which would put back exactly what this regeneration removed.

The ruler impersonated the thing it was measuring

Carried over. The secondary button was declared constant so it could serve as a fixed reference, and then given a hover that took the accent on both its border and its label — which was a candidate's rest state exactly. Hovering the secondary produced two identical buttons and destroyed the hierarchy the band exists to demonstrate. Found by hovering the wrong button, which is the only way it could have been found: at rest the band was correct, and every number the page printed was correct too. A control has to be a control in every state, not only the one that gets screenshotted. The border does move now, one step up the gray ramp — the lesson was about the accent, not about movement, and holding the ruler still in every respect had started to read as the ruler being broken.

A missing cell is a confound, not an omission

Carried over, and now structural. The first set had cyan in three treatments and gold in two, which made the question unanswerable: no comparison between an unfilled cyan candidate and a filled gold one can separate the hue from the treatment. This version is a full 4 × 2 with no empty cells, and the grid below is drawn so a hole cannot reopen quietly.

Flex stretch hid a 2px mismatch until the buttons wrapped

Carried over. A border adds 1px a side, so a bordered button is 2px taller than a filled one at the same padding. In a flex row the two stretch to match and the difference is invisible — until 360px, where they wrap onto separate lines, the stretch stops and the mismatch appears. The live home page had it too, for the same reason and with the same pair. Fixed by giving every candidate a border, transparent where there is nothing to show: one box model beats a compensation rule that has to be remembered every time a candidate is added.

A hover you just triggered measures as the state you left

Carried over. Forcing the hover class and reading getComputedStyle immediately reported every candidate's hover as identical to its rest state — unchanged numbers, which looked like a correct result rather than a broken probe. The cause is the 150ms transition: computed style returns the value as currently animated, and at the instant the class lands that is still the old one. Taking transitions off for the duration of the read fixes it. Worth keeping because the failure was silent and self-consistent, which is the kind that survives review.

The control is the shipped state, not a straw man

Carried over. A is exactly what is live on the home page, down to the class list, rather than a reconstruction of it. That matters because the whole comparison is an argument for changing it, and an experiment that quietly makes its control worse than reality is not evidence of anything.

Renumbering was the lesser harm

The old page warned that renumbering mid-experiment would invalidate every note written against the letters, and it was right. It was done anyway, because the alternative was worse: six letters pointing at seven things, two of which no longer exist. The rule the warning was protecting — that a letter means one thing forever — survives within a set, not across a regeneration that replaces the set. The old letters are not reused with new meanings by accident; they are reassigned deliberately and the fact is recorded here.

Result

Settled. This page is a record from here and is not updated again.

J wins, and it is not one of the eight.The grid asked which treatment and which hue the primary should take, and held the secondary still so the answer would be clean. That question has an answer — cool, filled — but it was the wrong question. What was actually wrong with the home page was a rule the pair did not have: a filled secondary is a primary, whatever colour it is, so any candidate whose secondary could fill had two primaries the moment someone hovered the wrong one. J states the rule instead of tuning around it. A fill means primary. The primary is filled; the secondary is a ring and never fills.

Two defects fell out of getting there, both visible in the strip above. Band I's primary hovers from step 9 to step 10 and its separation goesdown, 7.96:1 to 7.02:1 — because all three scales generated through the accent slot put step 10 below step 9, which is backwards for a dark theme and the opposite of every published Radix dark scale. J climbs to step 12 instead and reads 7.96:1 to 13.72:1. And I's resting ring is step 8, the only thing on the site wearing that step: the same hue to within a degree but at 35% lightness against the brand's 60%, so it read as a second, muddier cyan. J uses two cyans and no more.

The order was wrong and is worth recording as such: J went into global.css before it was a candidate here, so the comparison was built after the decision rather than before it. Bands A–H and I stay exactly as they were — scoped to this page, the way navy and jungle stayed on experiment 01.