Palette candidates on a dense page #53

Closed
opened 2026-07-28 08:10:37 +00:00 by archvillainette · 2 comments
Owner

Part of #47

Question

Which palette candidate actually survives contact with a real page?

Build two or three complete palette candidates as token sets and look at them in the live dev stack. Each candidate is a full role assignment — base, panel tones, borders, text ramp, accent, state colours — not a mood board.

The candidates must be judged on a dense forum topic page and a category list first, and only then on a card grid. A palette that only works on a spacious marketing page is not a palette for this site; the forum is where most pixels actually are.

Judge:

  • Whether hierarchy is legible without relying on shadow, since shadows here are mood and not stacking.
  • Whether a second metal earns its place, or just muddies the gold.
  • Whether a light surface earns its place, and if so on which component.
  • Whether the plum reads as noir rather than purple, at Plum Noir #351E28.
  • Whether long-form reading is comfortable for more than a minute — the real test of a dark palette, and the one a screenshot cannot answer.

Every candidate is contrast-checked against the headroom table before it is built; no candidate ships past AA failures. Prototypes are throwaway, on a prototype/* branch, linked from this ticket, never merged.

Resolution names the winning candidate and its full role assignment.

Part of #47 ## Question Which palette candidate actually survives contact with a real page? Build two or three complete palette candidates as token sets and look at them in the live dev stack. Each candidate is a full role assignment — base, panel tones, borders, text ramp, accent, state colours — not a mood board. The candidates must be judged on **a dense forum topic page and a category list first**, and only then on a card grid. A palette that only works on a spacious marketing page is not a palette for this site; the forum is where most pixels actually are. Judge: - Whether hierarchy is legible without relying on shadow, since shadows here are mood and not stacking. - Whether a second metal earns its place, or just muddies the gold. - Whether a light surface earns its place, and if so on which component. - Whether the plum reads as noir rather than purple, at Plum Noir `#351E28`. - Whether long-form reading is comfortable for more than a minute — the real test of a dark palette, and the one a screenshot cannot answer. Every candidate is contrast-checked against the headroom table before it is built; no candidate ships past AA failures. Prototypes are throwaway, on a `prototype/*` branch, linked from this ticket, never merged. Resolution names the winning candidate and its full role assignment.
archvillainette added the wayfinder:prototype label 2026-07-28 08:10:37 +00:00
archvillainette added a new dependency 2026-07-28 08:10:53 +00:00
archvillainette added a new dependency 2026-07-28 08:10:53 +00:00
archvillainette added a new dependency 2026-07-28 08:10:54 +00:00
archvillainette added a new dependency 2026-07-28 08:10:54 +00:00
archvillainette added a new dependency 2026-07-28 08:10:55 +00:00
archvillainette self-assigned this 2026-07-31 14:22:48 +00:00
Author
Owner

Prototype built — three candidates on the live stack

Branch prototype/53-palette-candidates (pushed, not for merge). Judged in a
browser on a real 13-post topic and the real category list first, card grid last,
per the ticket. Screenshots in prototype/shots/, replay steps in
prototype/README.md.

How it was built. The theme's colours were mostly literal — 233 rgba(194, 163, 90, …)
uses alone — so a candidate could not be swapped without a rebuild each time. The branch
mechanically tokenises them (var(--wg-gold-rgb), var(--wg-gold), …), which makes each
candidate a :root override injected at runtime. A computed-style probe (prototype/probe.js)
confirms the swap actually reaches the pixels: body bg rgb(15,13,18)rgb(21,14,19),
metadata rgb(154,144,134) → pewter rgb(158,167,179) under B.

Contrast first, as required. prototype/contrast.py (same method as #50, self-checked
against WCAG's published values) clears every candidate surface before it was built. The four
new surface hexes carry the whole text ramp at AA: bg #150E13 (Dust 6.08), bg-deep #0C0810
(6.34), panel #21161C (5.61), panel-2 #180F16 (6.00), with Plum Noir #351E28 as panel-3
(Dust 4.90, the known ceiling). Pewter #9EA7B3 is AA on all of them (6.31–8.16). plum-soft #4A2A38 is accent-only — Dust 3.99, Old Gilt 3.76 — which is exactly #50's rule 2 holding.

The candidates

  • A — Plum Noir Ledger. Surfaces relocated onto the Plum Noir lineage, one metal, gold
    keeps every job it has today.
  • B — Two Metals. A, plus pewter #9EA7B3 taking borders, rulings and metadata; gilt keeps
    links, ledger ink, buttons, focus ring, stamps.
  • C — Two Metals + plain paper. B, plus one champagne #EFE5D2 inset on quoted material,
    ink #351E28, no metal on it.

What the pages showed

  • The plum reads as noir, not purple. Against the current cold near-black, #150E13
    reads brown-plum and warm; nothing in the crops reads lavender.
  • Hierarchy is legible without shadow — but only in B. In A the category rows and the post
    metadata are the same warm ash as the body copy, so the row is one material and separation
    leans on panel tone alone. In B the description and the topics/posts labels go cool and
    the row splits into value (gold/bone) and structure (pewter) at a glance.
  • The second metal earns its place, and does not muddy the gold — because the two never
    compete for the same role. #50's warning that they are luminance twins (1.006:1) is real and
    is why this only works by hue and placement, never as a contrast step.
  • The light surface does not earn its place on quotes. shots/quote-c.png: the champagne
    block is the brightest thing on the page, so quoted material — secondary by definition —
    out-shouts the reply that contains it. Hierarchy inverts. A light surface may still earn a
    showcase plaque; it does not earn an app-surface component.
  • Long-form reading: A/B are more comfortable over a minute than the current cold ground;
    the warm ash on warm dark stops the faint blue cast the baseline has.

One thing the prototype exposed

scss/overrides.scss holds a second, hidden palette layer — Sass variables ($wg-ink,
$gray-*, $black, $dropdown-bg) that must be real colours, not var(), so Sass can pass
them to rgba()/Bootstrap. They do not follow a token swap: in the candidate shots the topbar
keeps the old cold ground while the page below it moves, a visible seam. Any re-tune has to
change both layers, or the shell and the page disagree.
That is a constraint on #55's token
contract, not a bug in the candidates.

Recommendation

B — Two Metals, with the light surface dropped from the app surfaces and left as an open
question for showcase ornament only.

Full role assignment for B:

Role Value
bg / bg-deep #150E13 / #0C0810
panel / panel-2 / panel-3 #21161C / #180F16 / #351E28 (Plum Noir)
plum / plum-soft #351E28 / #4A2A38 (accent surface, large text and non-text only)
text / soft / muted #E6E0D6 / #B9B2A6 / #9EA7B3 (pewter)
link / hover / border-gold Tarnished #C2A35A / Gold Lit #E0C878 / Old Gilt #A8893F
structure: border / border-soft / ruling pewter at α .20 / .11 / .075
ledger ink / ink-muted / stamp #D8C28A / pewter #9EA7B3 / #A84A4E
red #8E3438, Sparing Red unchanged
focus ring #C2A35A with outline-offset: 2px kept (#50 finding b)

This ticket is HITL and stays open until the human has looked. The judgement above is the
prototype's read, not the resolution.

## Prototype built — three candidates on the live stack Branch `prototype/53-palette-candidates` (pushed, **not for merge**). Judged in a browser on a real 13-post topic and the real category list first, card grid last, per the ticket. Screenshots in `prototype/shots/`, replay steps in `prototype/README.md`. **How it was built.** The theme's colours were mostly literal — 233 `rgba(194, 163, 90, …)` uses alone — so a candidate could not be swapped without a rebuild each time. The branch mechanically tokenises them (`var(--wg-gold-rgb)`, `var(--wg-gold)`, …), which makes each candidate a `:root` override injected at runtime. A computed-style probe (`prototype/probe.js`) confirms the swap actually reaches the pixels: body bg `rgb(15,13,18)` → `rgb(21,14,19)`, metadata `rgb(154,144,134)` → pewter `rgb(158,167,179)` under B. **Contrast first, as required.** `prototype/contrast.py` (same method as #50, self-checked against WCAG's published values) clears every candidate surface before it was built. The four new surface hexes carry the whole text ramp at AA: bg `#150E13` (Dust 6.08), bg-deep `#0C0810` (6.34), panel `#21161C` (5.61), panel-2 `#180F16` (6.00), with Plum Noir `#351E28` as panel-3 (Dust 4.90, the known ceiling). Pewter `#9EA7B3` is AA on all of them (6.31–8.16). `plum-soft #4A2A38` is accent-only — Dust 3.99, Old Gilt 3.76 — which is exactly #50's rule 2 holding. ### The candidates - **A — Plum Noir Ledger.** Surfaces relocated onto the Plum Noir lineage, one metal, gold keeps every job it has today. - **B — Two Metals.** A, plus pewter `#9EA7B3` taking borders, rulings and metadata; gilt keeps links, ledger ink, buttons, focus ring, stamps. - **C — Two Metals + plain paper.** B, plus one champagne `#EFE5D2` inset on quoted material, ink `#351E28`, no metal on it. ### What the pages showed - **The plum reads as noir, not purple.** Against the current cold near-black, `#150E13` reads brown-plum and warm; nothing in the crops reads lavender. - **Hierarchy is legible without shadow — but only in B.** In A the category rows and the post metadata are the same warm ash as the body copy, so the row is one material and separation leans on panel tone alone. In B the description and the `topics`/`posts` labels go cool and the row splits into *value* (gold/bone) and *structure* (pewter) at a glance. - **The second metal earns its place, and does not muddy the gold** — because the two never compete for the same role. #50's warning that they are luminance twins (1.006:1) is real and is why this only works by hue and placement, never as a contrast step. - **The light surface does not earn its place on quotes.** `shots/quote-c.png`: the champagne block is the brightest thing on the page, so *quoted* material — secondary by definition — out-shouts the reply that contains it. Hierarchy inverts. A light surface may still earn a showcase plaque; it does not earn an app-surface component. - **Long-form reading:** A/B are more comfortable over a minute than the current cold ground; the warm ash on warm dark stops the faint blue cast the baseline has. ### One thing the prototype exposed `scss/overrides.scss` holds a **second, hidden palette layer** — Sass variables (`$wg-ink`, `$gray-*`, `$black`, `$dropdown-bg`) that must be real colours, not `var()`, so Sass can pass them to `rgba()`/Bootstrap. They do not follow a token swap: in the candidate shots the topbar keeps the old cold ground while the page below it moves, a visible seam. **Any re-tune has to change both layers, or the shell and the page disagree.** That is a constraint on #55's token contract, not a bug in the candidates. ### Recommendation **B — Two Metals**, with the light surface dropped from the app surfaces and left as an open question for showcase ornament only. Full role assignment for B: | Role | Value | |---|---| | bg / bg-deep | `#150E13` / `#0C0810` | | panel / panel-2 / panel-3 | `#21161C` / `#180F16` / `#351E28` (Plum Noir) | | plum / plum-soft | `#351E28` / `#4A2A38` (accent surface, large text and non-text only) | | text / soft / muted | `#E6E0D6` / `#B9B2A6` / **`#9EA7B3` (pewter)** | | link / hover / border-gold | Tarnished `#C2A35A` / Gold Lit `#E0C878` / Old Gilt `#A8893F` | | structure: border / border-soft / ruling | pewter at α .20 / .11 / .075 | | ledger ink / ink-muted / stamp | `#D8C28A` / **pewter `#9EA7B3`** / `#A84A4E` | | red | `#8E3438`, Sparing Red unchanged | | focus ring | `#C2A35A` with `outline-offset: 2px` kept (#50 finding b) | **This ticket is HITL and stays open until the human has looked.** The judgement above is the prototype's read, not the resolution.
Author
Owner

Resolution

Candidate B — Two Metals wins, with the light surface dropped from every app surface.
Confirmed in a browser on the dense topic and the category list before the card grid.

Evidence: branch prototype/53-palette-candidates (throwaway, never merges) — candidates as
runtime :root swaps, prototype/contrast.py for the AA check, prototype/shots/ for the
images, prototype/README.md to replay it.

The winning role assignment

Role Value
--wg-bg / --wg-bg-deep #150E13 / #0C0810
--wg-panel / -2 / -3 #21161C / #180F16 / #351E28 (Plum Noir)
--wg-plum / -soft #351E28 / #4A2A38 — accent surface, large text and non-text only
--wg-text / -soft / -muted #E6E0D6 / #B9B2A6 / #9EA7B3 (pewter)
link / hover / gold border Tarnished #C2A35A / Gold Lit #E0C878 / Old Gilt #A8893F
structure: --wg-border / -soft / --wg-ledger-ruling pewter at α .20 / .11 / .075
--wg-ledger-ink / -ink-muted / -stamp #D8C28A / pewter #9EA7B3 / #A84A4E
--wg-red #8E3438, Sparing Red unchanged
focus ring #C2A35A, outline-offset: 2px kept — an a11y mechanism, not spacing (#50 finding b)

The five questions the ticket asked

  1. Hierarchy without shadow — yes, but only with the second metal. In the one-metal
    candidate a category row is a single material and separation leans on panel tone alone. In B
    the row splits into value (gold, bone) and structure (pewter) at a glance.
  2. A second metal earns its place. It does not muddy the gold because the two never compete
    for the same role — this is Two Metals from #49 doing real work. #50's warning holds: at
    1.006:1 they are luminance twins, so the split is carried by hue and placement only, never as
    a contrast step.
  3. A light surface does not earn its place on any app surface. On quoted material the
    champagne block becomes the brightest thing on the page, so secondary content out-shouts the
    reply containing it. Hierarchy inverts. It stays open only as possible showcase ornament
    handed to #54, not carried here.
  4. The plum reads as noir, not purple. #150E13 over Plum Noir #351E28 reads brown-plum
    and warm; nothing in the crops reads lavender.
  5. Long-form reading is comfortable. Warmer than the current cold ground, which removes the
    faint blue cast the baseline has over a minute of reading.

Contrast

Every surface was cleared before it was built, same method as #50, self-checked against WCAG's
published values. Full text ramp at AA on all four new surfaces (worst pair Dust on #21161C,
5.61); Plum Noir at its known ceiling (Dust 4.90); pewter #9EA7B3 AA on all of them
(6.31–8.16); #4A2A38 accent-only (Dust 3.99, Old Gilt 3.76), which is #50's rule 2 holding.

What this exposed for later tickets

  • scss/overrides.scss is a second, hidden palette layer. Its Sass variables ($wg-ink,
    $gray-*, $black, $dropdown-bg) must be real colours, not var(), because Sass passes
    them to rgba() and Bootstrap. They do not follow a token swap: in the candidate shots the
    topbar keeps the old cold ground while the page below it moves. A re-tune has to change both
    layers or the shell and the page disagree.
    Constraint on #55.
  • The theme's colours are mostly literal — 233 rgba(194, 163, 90, …) uses alone. Shipping
    any palette change means tokenising first. The prototype branch has a working mechanical pass
    to crib from.
  • The three panel tones survive, because pewter (not a third tone) now carries structure.
    The remaining question is which of velvet-panel / -deep / -raised still earn a slot, and
    that is #55's to answer, not fog.
## Resolution **Candidate B — Two Metals** wins, with the light surface dropped from every app surface. Confirmed in a browser on the dense topic and the category list before the card grid. Evidence: branch `prototype/53-palette-candidates` (throwaway, never merges) — candidates as runtime `:root` swaps, `prototype/contrast.py` for the AA check, `prototype/shots/` for the images, `prototype/README.md` to replay it. ### The winning role assignment | Role | Value | |---|---| | `--wg-bg` / `--wg-bg-deep` | `#150E13` / `#0C0810` | | `--wg-panel` / `-2` / `-3` | `#21161C` / `#180F16` / `#351E28` (Plum Noir) | | `--wg-plum` / `-soft` | `#351E28` / `#4A2A38` — accent surface, large text and non-text only | | `--wg-text` / `-soft` / `-muted` | `#E6E0D6` / `#B9B2A6` / **`#9EA7B3` (pewter)** | | link / hover / gold border | Tarnished `#C2A35A` / Gold Lit `#E0C878` / Old Gilt `#A8893F` | | structure: `--wg-border` / `-soft` / `--wg-ledger-ruling` | pewter at α .20 / .11 / .075 | | `--wg-ledger-ink` / `-ink-muted` / `-stamp` | `#D8C28A` / **pewter `#9EA7B3`** / `#A84A4E` | | `--wg-red` | `#8E3438`, Sparing Red unchanged | | focus ring | `#C2A35A`, `outline-offset: 2px` kept — an a11y mechanism, not spacing (#50 finding b) | ### The five questions the ticket asked 1. **Hierarchy without shadow — yes, but only with the second metal.** In the one-metal candidate a category row is a single material and separation leans on panel tone alone. In B the row splits into *value* (gold, bone) and *structure* (pewter) at a glance. 2. **A second metal earns its place.** It does not muddy the gold because the two never compete for the same role — this is Two Metals from #49 doing real work. #50's warning holds: at 1.006:1 they are luminance twins, so the split is carried by hue and placement only, never as a contrast step. 3. **A light surface does not earn its place on any app surface.** On quoted material the champagne block becomes the brightest thing on the page, so secondary content out-shouts the reply containing it. Hierarchy inverts. It stays open only as possible *showcase ornament* — handed to #54, not carried here. 4. **The plum reads as noir, not purple.** `#150E13` over Plum Noir `#351E28` reads brown-plum and warm; nothing in the crops reads lavender. 5. **Long-form reading is comfortable.** Warmer than the current cold ground, which removes the faint blue cast the baseline has over a minute of reading. ### Contrast Every surface was cleared before it was built, same method as #50, self-checked against WCAG's published values. Full text ramp at AA on all four new surfaces (worst pair Dust on `#21161C`, 5.61); Plum Noir at its known ceiling (Dust 4.90); pewter `#9EA7B3` AA on all of them (6.31–8.16); `#4A2A38` accent-only (Dust 3.99, Old Gilt 3.76), which is #50's rule 2 holding. ### What this exposed for later tickets - **`scss/overrides.scss` is a second, hidden palette layer.** Its Sass variables (`$wg-ink`, `$gray-*`, `$black`, `$dropdown-bg`) must be real colours, not `var()`, because Sass passes them to `rgba()` and Bootstrap. They do not follow a token swap: in the candidate shots the topbar keeps the old cold ground while the page below it moves. **A re-tune has to change both layers or the shell and the page disagree.** Constraint on #55. - **The theme's colours are mostly literal** — 233 `rgba(194, 163, 90, …)` uses alone. Shipping any palette change means tokenising first. The prototype branch has a working mechanical pass to crib from. - **The three panel tones survive**, because pewter (not a third tone) now carries structure. The remaining question is which of `velvet-panel` / `-deep` / `-raised` still earn a slot, and that is #55's to answer, not fog.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Blocks
#54 The ornament and frame component set
ShadowsOverWestgate/sow-nodebb-theme
#55 Role-based token contract
ShadowsOverWestgate/sow-nodebb-theme
#57 The home board
ShadowsOverWestgate/sow-nodebb-theme
Depends on
#49 North star and named rules for the noir re-tune
ShadowsOverWestgate/sow-nodebb-theme
#50 Contrast headroom of the candidate palette space
ShadowsOverWestgate/sow-nodebb-theme
Reference: ShadowsOverWestgate/sow-nodebb-theme#53