From ff181206efb91471df2a30ac2987924e960ca3fb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 19:16:40 +0000 Subject: [PATCH] board: let the accent run through the page's type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The boards wore their organ at the masthead and went grey underneath. The accent is now the page's *type* colour: section headings (over a lit rule that fades into the hairline), disclosure summaries, code spans and the emphasised head of a row all take the organ's hue, and the copy buttons and row hover carry a faint accent wash. Body copy stays plain — these are still lists read on a phone before breakfast. Colour now does two jobs, and the stylistic one never overwrites the semantic one. The reservation is literal in the sheet: the accent rule is `b:not([class])`, so ok/warn/bad, muted and the pill tones keep the colour their class means, and the verdict banner's headline says its verdict at a specificity the accent cannot reach. Shared theme, so this lands on the whole board family at once — the Brain board and the Mind dashboard alike. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017MaWGv7PEMSqCJubPBWeQH --- board/AGENTS.md | 27 ++++++++++------- board/_theme.py | 64 +++++++++++++++++++++++++++++---------- tests/test_board_theme.py | 25 +++++++++++++++ 3 files changed, 89 insertions(+), 27 deletions(-) diff --git a/board/AGENTS.md b/board/AGENTS.md index eacdd48e..1195cc43 100644 --- a/board/AGENTS.md +++ b/board/AGENTS.md @@ -71,17 +71,22 @@ rather than drifting page by page. Each organ has an accent sampled from its own logo, and every board opens with a dark hero reproducing that logo's wordmark — white `PyAuto`, the organ -name in its accent, the logo's tagline underneath. Below the hero the page -goes back to being a plain readable document, because these are lists people -scan on a phone before breakfast. - -Colour is information, never decoration: the accent is organ identity; pills -are task facets, toned so that only the *exception* is coloured (`supervised` -is 9 of 10 prompts in the Mind, so it stays neutral — tinting it would paint -the backlog and say nothing); `ok`/`warn`/`bad` stay reserved for verdict -semantics. `ORGANS` holds the palette — `mind` and `brain` are sampled from -the real logo files, the other four are placeholders in the right hue family, -to be confirmed against each logo when that renderer adopts the theme. +name in its accent, the logo's tagline underneath. Below the hero the organ +keeps speaking: the accent is the page's **type colour**, so section +headings, disclosure summaries, code spans and the emphasised head of a row +are all in the organ's hue. Only the running prose stays near-black, because +these are lists people scan on a phone before breakfast. + +Colour does two jobs, and the stylistic one never overwrites the semantic +one: the accent is organ identity; pills are task facets, toned so that only +the *exception* is coloured (`supervised` is 9 of 10 prompts in the Mind, so +it stays neutral — tinting it would paint the backlog and say nothing); +`ok`/`warn`/`bad` stay reserved for verdict semantics. The reservation is +literal — the accent rule is `b:not([class])`, so anything carrying a +semantic class keeps the colour that class means. `ORGANS` holds the palette +— `mind` and `brain` are sampled from the real logo files, the other four are +placeholders in the right hue family, to be confirmed against each logo when +that renderer adopts the theme. ## Configuration diff --git a/board/_theme.py b/board/_theme.py index 364c51fe..37259b33 100644 --- a/board/_theme.py +++ b/board/_theme.py @@ -14,9 +14,10 @@ The design in one line: *the page wears its organ's logo*. Every board opens with a dark hero that redraws the logo it sits under: the organ's own mark in line art, the wordmark (white `PyAuto`, the organ name in its accent), the -hairline-and-dot rule, and the letterspaced tagline. Below the hero the page -returns to a plain readable document, because these are lists people scan on -a phone before breakfast, not dashboards to admire. +hairline-and-dot rule, and the letterspaced tagline. Below the hero the organ +keeps speaking: the accent runs on through the page's *type* rather than +stopping at the masthead. Body copy stays plain, because these are lists +people scan on a phone before breakfast, not dashboards to admire. The mark is **drawn, not borrowed**. An emoji stood in for it at first, which read as a cartoon beside the real logo — 🧠 is not the Brain's circuit brain. @@ -24,15 +25,27 @@ it inherits the accent, stays sharp at any size, and costs ~1KB in the one page that uses it, where the 80-150KB `logo.png` would be most of the page. -Colour carries meaning, never decoration: +The accent is the page's **type colour**, not merely its link colour: the +things that give a page its shape — section headings, disclosure summaries, +code spans, the emphasised head of a row — are all set in the organ's hue, so +a board reads as its organ from top to bottom instead of as grey GitHub +chrome under a coloured masthead. Only the running prose stays near-black. -* the **accent** is organ identity — one hue per board, used for links, - section rules, stats and the hero; +Colour is doing two jobs at once now, and the decorative one never overwrites +the semantic one: + +* the **accent** is organ identity — links, headings, summaries, code, the + section rules, the stats and the hero; * **pills** are the task facets (target, difficulty, autonomy, priority), so a backlog can be scanned by colour instead of read word by word; * **ok / warn / bad** stay reserved for the verdict semantics they already carry on the Heart-style boards. +That reservation is literal in the stylesheet: the accent rule is +`b:not([class])`, so an element carrying a semantic class — `ok`, `warn`, +`bad`, `muted`, a pill tone — keeps the colour that class means, and only +unclassed emphasis takes the organ's hue. + `ORGANS` is the palette table, keyed by the same board names as `config/policy.yaml` `board: boards:` — that mapping is the declared config surface an adopting fork replaces, so no repo is named here. Five of the six @@ -304,11 +317,18 @@ def mark(key): Helvetica,Arial,sans-serif;-webkit-text-size-adjust:100%%} a{color:var(--accent);text-decoration:none} a:hover{text-decoration:underline} +/* The accent is the page's *type* colour, not just its link colour: the + things that give a page its shape — headings, disclosure summaries, code + spans, the emphasised head of a row — are all set in the organ's hue, so + the page reads as its organ instead of as grey GitHub chrome. Semantics + are untouched: anything carrying a class (ok/warn/bad, the pills, muted) + keeps the colour that class means. */ +b:not([class]),strong:not([class]){color:var(--accent)} .muted{color:var(--muted)} .ok{color:var(--ok)}.warn{color:var(--warn)}.bad{color:var(--bad)} code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.92em; - background:var(--btn);border:1px solid var(--line);padding:.05em .35em; - border-radius:5px} + color:var(--accent);background:var(--tint);border:1px solid var(--edge); + padding:.05em .35em;border-radius:5px} /* --- hero: the logo, rendered as type ---------------------------------- */ .hero{margin:0 -1rem 1.4rem;padding:2.1rem 1.4rem 1.7rem;position:relative; overflow:hidden;text-align:center;color:#fff;background:var(--hero-base); @@ -347,16 +367,25 @@ def mark(key): .lede{margin:0 0 .9rem} /* --- sections ---------------------------------------------------------- */ h2{font-size:1.1rem;margin:2.1rem 0 .3rem;padding:0 0 .35rem;font-weight:650; - border-bottom:2px solid var(--edge)} + position:relative;color:var(--accent);border-bottom:2px solid var(--edge)} +/* The hero's lit rule, quieted and reused: the hairline under a section + starts at full accent and fades into the edge tone. Purely the ::after, + so a browser that skips it still gets the plain hairline. */ +h2::after{content:"";position:absolute;left:0;bottom:-2px;width:44%%;height:2px; + background:linear-gradient(90deg,var(--accent),var(--accent) 15%%,transparent)} +h2 a{color:inherit} +/* The heading is the accent; its parenthetical stays a quiet aside rather + than competing at the same weight. */ +h2 .muted{font-weight:400} h3{font-size:.98rem;margin:1.3rem 0 .2rem;font-weight:650;color:var(--accent)} /* --- rows -------------------------------------------------------------- */ .task{display:flex;gap:.6rem;align-items:flex-start;padding:.45rem .35rem; margin:0 -.35rem;border-bottom:1px solid var(--line);border-radius:7px} -.task:hover{background:var(--tint)} +.task:hover{background:var(--tint);box-shadow:inset 2px 0 0 var(--accent)} .task p{margin:.25rem 0 0;flex:1;overflow-wrap:anywhere} button.copy{flex:0 0 auto;width:2.6rem;height:2.6rem;font-size:1.1rem; - border:1px solid var(--line);border-radius:9px;background:var(--btn); - cursor:pointer;color:var(--fg);transition:border-color .12s,color .12s} + border:1px solid var(--edge);border-radius:9px;background:var(--tint); + cursor:pointer;color:var(--accent);transition:border-color .12s,color .12s} button.copy:hover{border-color:var(--accent);color:var(--accent)} button.copy.ok{color:var(--ok);border-color:var(--ok);background:transparent} button.copy.term{font-size:.95rem} @@ -365,7 +394,7 @@ def mark(key): color:var(--muted);cursor:pointer;font:inherit;font-size:.9em} button.more:hover{color:var(--accent);border-style:solid} details{margin:.5rem 0} -summary{cursor:pointer;font-weight:600;padding:.4rem 0} +summary{cursor:pointer;font-weight:600;padding:.4rem 0;color:var(--accent)} summary::marker{color:var(--accent)} /* --- facet pills: the backlog, scannable by colour --------------------- */ .facets{color:var(--muted);font-size:.85em} @@ -390,7 +419,7 @@ def mark(key): .stats b{display:block;font-size:1.15rem;line-height:1.2;color:var(--accent); font-variant-numeric:tabular-nums} .stats span{font-size:.68rem;letter-spacing:.1em;text-transform:uppercase; - color:var(--muted)} + color:var(--accent);opacity:.75} /* --- verdict banner ---------------------------------------------------- */ .verdict{margin:0 0 1rem;padding:.7rem .85rem;border-radius:10px; border:1px solid var(--line);border-left:4px solid var(--muted); @@ -399,6 +428,9 @@ def mark(key): .verdict.warn{border-left-color:var(--warn)} .verdict.bad{border-left-color:var(--bad)} .verdict b{display:block;font-size:1.02rem} +.verdict.ok b{color:var(--ok)} +.verdict.warn b{color:var(--warn)} +.verdict.bad b{color:var(--bad)} /* --- tables ------------------------------------------------------------ */ table.recent{width:100%%;border-collapse:collapse;font-size:.95em} table.recent td{border-bottom:1px solid var(--line); @@ -406,8 +438,8 @@ def mark(key): table.recent tr:hover td{background:var(--tint)} table.recent td.when{white-space:nowrap;color:var(--muted); font-variant-numeric:tabular-nums} -table.recent td.what{white-space:nowrap;color:var(--muted);font-size:.85em; - padding-top:.58rem} +table.recent td.what{white-space:nowrap;color:var(--accent);font-size:.85em; + padding-top:.58rem;opacity:.8} table.recent td.pick{width:2.6rem;padding-right:0} table.recent button.copy{width:2.2rem;height:2.2rem;font-size:.95rem} /* --- the family footer: one chip per sibling board, in its own colour --- */ diff --git a/tests/test_board_theme.py b/tests/test_board_theme.py index d4b4b945..04399f8c 100644 --- a/tests/test_board_theme.py +++ b/tests/test_board_theme.py @@ -68,6 +68,31 @@ def test_css_substitutes_every_placeholder(): assert "prefers-color-scheme:dark" in sheet +def test_the_accent_is_the_pages_type_colour_not_only_its_link_colour(): + # The organ is meant to carry on past the masthead: the elements that give + # a page its shape are all set in the accent, so a board reads as its + # organ top to bottom rather than as grey chrome under a coloured hero. + sheet = _theme.css("brain") + for rule in ("h2{", "h3{", "summary{", "code{"): + block = sheet[sheet.index(rule):sheet.index("}", sheet.index(rule))] + assert "color:var(--accent)" in block, rule + # …and unclassed emphasis — the head of a row, a count, a label. + assert "b:not([class]),strong:not([class]){color:var(--accent)}" in sheet + + +def test_a_semantic_class_keeps_its_own_colour_against_the_accent(): + # The accent is the *default* type colour, never an override: the rule is + # class-guarded, so ok/warn/bad and the pills still say what they mean. + sheet = _theme.css("brain") + assert "b:not([class])" in sheet and "b{color:var(--accent)}" not in sheet + for cls, var in ((".ok", "--ok"), (".warn", "--warn"), (".bad", "--bad")): + assert f"{cls}{{color:var({var})}}" in sheet, cls + # The verdict banner's headline says its verdict in the verdict's colour, + # at a specificity the unclassed-bold rule cannot reach. + for tone in ("ok", "warn", "bad"): + assert f".verdict.{tone} b{{color:var(--{tone})}}" in sheet + + def test_each_board_wears_its_own_accent(): mind, brain = _theme.css("mind"), _theme.css("brain") assert _theme.ORGANS["brain"]["glow"] not in mind