A design review of everything NOISE ships: the news site, the lounge, the chat, the exports, and the system behind them. Saturday, September 26, 2026.

The bones and the voice are yours. The paint is borrowed.

NOISE has had five design systems since June. Each repaint swapped the palette and the typefaces and kept the same skeleton: the edition names, Static, rules instead of boxes, one accent, sentence case, buttons that name their stakes. That skeleton is the design language. The paint on top of it today is the 2025 default shelf.

Verdant is carefully built. Its bans hold in production: no gradients, no shadows, no uppercase transforms. Its contrast is measured over the grain. But take the wordmark off any Verdant page and it could belong to a fintech app, a dev-tool landing page or a newsletter template. It has a dark ground with one acid accent (the token is literally named --acid), Instrument Serif for display, a mono face on every small label, an feTurbulence grain, Lucide icons and a sidebar-and-inspector shell.

Three problems matter more than the paint:

  • Chat has no system. Nearly every player meets NOISE in Signal or Discord. There, NOISE has no type roles, no emoji lexicon, no time format and no shape for an error. Meanwhile the web contract specifies 1 px column guides.
  • There is no scale under the rules. Production styles use 23 fixed font sizes plus 15 fluid ones, 37 spacing values and 13 breakpoints. The token block is copied by hand into 13 files, and nothing checks that the copies agree.
  • The contract became a changelog. The guide is 1,071 lines and has had 52 commits since August. 187 of those lines are one surface's feature narration, with dated decisions inline.

There is also an opportunity. The first guide, in June, opened with “A broadsheet front page picked up by a weak antenna,” and said of it, “It is also the pun: the bot is named Noise.” That was the last time the picture came from the name. On September 10 you shipped fifty Atkinson-dithered plates: noise shaped into an image, in the page's two inks. That is the brand, and today it's used as spot illustration. The hero above is the wordmark put through the same dither.

History: git log --since=2026-08-01 -- docs/design/; the first guide at e75c052f, lines 24 to 38. Scan: noise.css, both base.css files, news.css, lounge and gallery css, help, debug and color map pages, the three print templates, chain.css and the reader theme. Historical records and vendored reader assets excluded. The load-bearing claims were spot-checked against the code in this worktree at 329b8dc5. Counts from regex scans are approximate. Renders are local, from production templates with synthetic data. No production host was contacted.

Anyone could have this

  • One lime on a dark ground, named --acid
  • Instrument Serif, Bricolage Grotesque and Fragment Mono, the trending shelf of free faces
  • An feTurbulence grain at 8%
  • Mono on every small label; in the lounge room and pane it is about half the text
  • Hairlines, zero radius and a table register
  • Lucide, the default icon set of shadcn/ui and the kits built like it
  • A rail, a page bar and an inspector, with the nav modelled on a SaaS site (e0e656a4)

Only NOISE has this

  • The names: Morning NOISE, Evening NOISE, Sunday NOISE, Static, Signal read, “Assembled by NOISE.”
  • Fifty dithered Danish Golden Age landscapes in two inks
  • A mid-value sage, not black, and a plate that never changes between themes
  • Buttons that name their stakes: “Buy for 40,000,000 tokens”, “Keep this Furb”, “Leave the seat as it is”
  • Furbs, with numbered records, lineage, composition and curses
  • The Battleship fire-control sheet and its volley crops
  • Sourcing in the open: “Reporting used” beside “Related coverage”

History

Five systems since June. The paint changed every time.

The palette changed four times and the type stack four times. The texture went from animated static to interference bars to bulbs and glow to grain. In June the guide banned icons and illustrations; Verdant ships Lucide icons and fifty paintings. Since the commit that landed Verdant, the guide has changed 37 times, the charter 26 times and the token file once.

  1. NOISE, first draft

    IBM Plex Sans. A broadsheet picked up by a weak antenna, with animated TV static and scrambling glyphs.

  2. Theme bazaar

    Seven themes with no shared system. Zune went to production.

  3. Signal Desk

    IBM Plex Sans, with violet as the only accent.

  4. NOISE, bot-wide

    Newsreader, IBM Plex Sans and IBM Plex Mono. Static became an ink inversion.

  5. Marquee

    Bungee Shade, Bungee and Barlow. Neon, bulbs and ten palettes. It lasted two days.

  6. Verdant

    Instrument Serif, Bricolage Grotesque and Fragment Mono. Plates followed on September 10.

Under every one of them: NOISE as the masthead, Static, “Assembled by NOISE”, dispatch as the mechanism rather than the product, one accent (Marquee alone broke it), night by default, self-hosted open fonts, rules for structure, and no radius, shadow or gradient. That is the design language. Protect it and stop repainting it.

Chat

Where the players are, there is no system.

Chat has three strong local standards: the rejection markers (⚠️ ⏳ ⛔ 🤷 📭), the casino wager shape, and the edition teaser. The edition teaser is the best voice in the repository. Everything else is improvised, and in chat your typography is only five styles and some emoji.

Fix first

The money path strips styling, so no receipt can be bold or mono.

The ledger choke point merges the callback's value with (dissoc value :text-styles). Its own docstring keeps the callback's mentions because render only appends, and the same reasoning holds for styles. Across non-debug modules, styling is used almost only in menus: 109 bold spans, 4 monospace, 1 italic, 2 spoiler, no strikethrough. On Signal, the embed fallback isn't even bold in the title.

Fix. Keep the callback's :text-styles the same way its :mentions are kept, and build receipts through helpers that emit the body and its spans together (see the chat proposal).

src/bot/core/operation.clj:714-723src/bot/core/rich_response.clj:76

Now

NOISE

🎡 Roulette — bet 100 tokens on red 14 🔴 red ✅ Win! Paid 200 (net +100). 💰 Balance: 12,450 tokens 🧾 Ledger confirmed: 3fa9c2b1
What Signal receives today. For this REPL render the roulette callback value carried a bold slug and mono amounts, and the real casino render-message dropped them all at operation.clj:723. Balance and receipt id are example values.
After

NOISE

🎡 Roulette — bet 100 tokens on red 14 🔴 red ✅ Win! Paid 200 (net +100). 💰 Balance: 12,450 tokens 🧾 Ledger confirmed: 3fa9c2b1
The same call with operation.clj:723 changed to (merge base value), loaded from a scratch copy. The slug stays bold and the amounts mono. The header wording is left to chat-voice. Mocked in a neutral client.

Fix first

Two voice standards contradict each other, so every settled wager breaks the guide.

The guide bans middle dots, arrows and em dashes in interface copy and exempts only command menus. The casino message standard mandates a header that renders 🎡 Roulette — bet 100 tokens on red. About 450 string literals across 61 non-debug modules use ·, including the Furb list row #7F · ✅ · C#1234 · Cosmic Lava Moth · house 12,000,000 tokens and the footer of !help itself.

Fix. Make the header 🎡 Roulette, 100 tokens on red. Chat gets one join rule: one fact per line, and commas within a line.

docs/design/design-guide.md:983, 1000casino-messages.md:32src/bot/modules/casino/text.clj:35furb.clj:1104-1122help.clj:23

Now

NOISE

🎡 Roulette — bet 100 tokens on red 14 🔴 red ✅ Win! Paid 200 (net +100). 💰 Balance: 12,450 tokens 🧾 Ledger confirmed: 3fa9c2b1
#7F · ✅ · C#1234 · Cosmic Lava Moth · house 12,000,000 tokens
Open: !help <page> · Details: !help <command> ↩️ Or reply to this menu with a page number.
A settled roulette wager (casino/text.clj:35), one Furb list row (furb.clj:1104) and the !help footer (help.clj:23), rendered from the real functions. Balance and house price are example values.
After

NOISE

🎡 Roulette, 100 tokens on red 14 🔴 red ✅ Win! Paid 200 (net +100). 💰 Balance: 12,450 tokens 🧾 Ledger confirmed: 3fa9c2b1
#7F Cosmic Lava Moth, C#1234, ready to breed, house 12,000,000 tokens
Open a page with !help <page>, or a command's details with !help <command>. ↩️ Or reply to this menu with a page number.
The header from a scratch copy of game-header. The Furb row joins its facts with commas and spells out ✅ as ready to breed, and the footer is one sentence. Every other line, amount and command is unchanged.

Fix first

Errors end in “Check logs.” Players can't check logs.

Deferred failures append “. Check logs.” by default, and about a hundred reply-later! sites inherit it. Another 25 player-facing strings end with it, including a Pokémon pack purchase on a money path that never says whether tokens moved. Weather says it twice: “Weather lookup failed 🌡️” and then “Weather lookup failed. Check logs.” Busy messages name internals: “LLM workers are busy”, “I/O workers are busy”.

Fix. Every rejection gets the same shape: “⚠️ Weather for 10001 didn't load. Send !w 10001 again in a minute.” On a money path, the second sentence states the money, as in “Nothing was charged.”, which is only true for a failure before the commit.

src/bot/core/deferred.clj:15, 343, 510, 530poke.clj:687weather.clj:125, 725expert.clj:426caseopen.clj:1592, 1595 (the same gap)

Now

NOISE

Weather lookup failed 🌡️ Weather lookup failed. Check logs.
Expert query failed. Check logs.
⚠️ Pokémon pack purchase failed. Check logs.
LLM workers are busy. Try again shortly.
!w 10001 with the forecast request failing, rendered through weather.clj in a REPL. Then the deferred default (deferred.clj:343) under !expert, the pack purchase catch (poke.clj:687) and the expert busy reply (expert.clj:426), quoted from source.
After

NOISE

⚠️ Weather for 10001 didn't load. Send !w 10001 again in a minute.
⚠️ The tax expert's answer didn't come through. Ask again in a minute with !expert tax <question>.
⚠️ The pack purchase didn't go through. Nothing was charged. Send !poke buy 1 again in a minute.
⏳ The tax expert can't take a question right now. Ask again in a minute.
Each rejection says what failed and what to send next. The pack purchase says whether tokens moved, which holds only for a failure before the commit. The expert name and pack count are example values.

Fix first

Slots, the flagship game, ignores the casino standard and prints code fences on Signal.

The spin has an all-caps header, a literal triple-backtick fence (Signal gets no Markdown conversion), → Won, a number with no separators in (x69 = 2156), and a jackpot line reading 👑👑👑 PROGRESSIVE JACKPOT!!! 👑👑👑. A loss has no casino loss line, only “😔 No winning paylines.” Multi-spin adds MULTI-SPIN, @, Δ and pipes.

Fix. Use the receipt shape in the chat proposal, and add a :code-block style that maps to a monospace range on Signal and a fence on Discord.

slots/engine.clj:607-680, 621slots.clj:675-692discord/connector.clj:136-141

Now

NOISE

🎰 Fruit 5x5 SLOTS 🎰 ``` ⭐ 👑 🍋 🍋 🍊 🍒 🍋 🔔 ⭐ 🍒 🍋 🍋 🍋 🍋 🍋 🔔 🍊 👑 🍒 ⭐ 🍊 ⭐ 🍒 🔔 👑 ``` 🌟 MEGA WIN! 5 lines hit! 🌟 Line 3: 5x 🍋 (x69 = 2156) Line 13: 3x 🍋 (x11 = 343) Line 17: 3x 🍋 (x11 = 343) Line 29: 3x 🍋 (x11 = 343) Line 31: 3x 🍋 (x11 = 343) 🎯 Bet: 1,000 · Jackpot contribution: 50 · Total cost: 1,050 → Won: 3,528 (Net: +2,478) 💰 Balance: 1,234,567 tokens 👑 Progressive Jackpot: 48,210,733 tokens 🧾 Ledger confirmed: 3fa9c2b1
🎰 Fruit 5x5 SLOTS 🎰 ``` ⭐ 👑 🍋 🍇 🍊 🍒 🍇 🔔 ⭐ 🍒 🍋 🍊 💎 🍇 🔔 🔔 🍊 👑 🍒 ⭐ 🍊 ⭐ 🍒 🔔 👑 ``` 😔 No winning paylines. 🎯 Bet: 1,000 · Jackpot contribution: 50 · Total cost: 1,050 → Won: 0 (Net: -1,050) 💰 Balance: 1,233,517 tokens 👑 Progressive Jackpot: 48,210,783 tokens 🧾 Ledger confirmed: 9d04e7a2
A win and a loss on Fruit 5x5 at 1,000 tokens, from the real engine (slots/engine.clj:607) and the play-slots suffix, grids chosen by hand. Signal shows the fences as literal backticks. Balances, jackpot and receipt ids are example values.
After

NOISE

🎰 Fruit 5x5, 1,000 tokens⭐ 👑 🍋 🍋 🍊 🍒 🍋 🔔 ⭐ 🍒 🍋 🍋 🍋 🍋 🍋 🔔 🍊 👑 🍒 ⭐ 🍊 ⭐ 🍒 🔔 👑Line 3: five 🍋 at 69x, paid 2,156. Line 13: three 🍋 at 11x, paid 343. Line 17: three 🍋 at 11x, paid 343. Line 29: three 🍋 at 11x, paid 343. Line 31: three 🍋 at 11x, paid 343. Jackpot contribution: 50 tokens Progressive jackpot: 48,210,733 tokens ✅ Mega win, 5 lines. Paid 3,528 (net +2,478). 💰 Balance: 1,234,567 tokens 🧾 Ledger confirmed: 3fa9c2b1
🎰 Fruit 5x5, 1,000 tokens⭐ 👑 🍋 🍇 🍊 🍒 🍇 🔔 ⭐ 🍒 🍋 🍊 💎 🍇 🔔 🔔 🍊 👑 🍒 ⭐ 🍊 ⭐ 🍒 🔔 👑No winning paylines. Jackpot contribution: 50 tokens Progressive jackpot: 48,210,783 tokens ❌ Loss. Net -1,050. 💰 Balance: 1,233,517 tokens 🧾 Ledger confirmed: 9d04e7a2
The same engine results as a casino receipt: bold slug, a :code-block grid, one outcome line from the casino helpers and mono amounts. Scratch renderer; mocked in a neutral client.

Fix soon

Durations have no hours, and there are thirteen ways to write one.

format-duration only knows minutes and seconds, so twenty hours reads 1200m 00s, and players see “in 1187m 15s”. Thirteen separate duration formatters exist. The 4:00 reset is written “04:00 ET”, “4am Eastern” and “4 AM Eastern”. Cooldowns print raw doubles: “cooling down for 1.234s”.

Fix. Use one formatter (45s, 12m, 3h 12m, 2d 4h) and write clock times as 4:00 ET, as dispatch already does.

src/bot/core/cache.clj:89-96furb.clj:1222, 2467operation.clj:705casino/operation.clj:384

Now
(format-duration 72000000)  "1200m 00s"
(format-duration 11520000)  "192m 00s"
(format-duration 45000)     "45s"

⏳ Vaccine slot 2 can change again at 4am Eastern, in 1187m 15s.

💉 Slot 1: ember, swap 750,000,000 tokens. Ready after 4am Eastern, in 1200m 00s.

⏳ Casino actions are cooling down for 1.234s. Please try again shortly.

⏳ You've already tried the sword today — it resets at 04:00 ET.
format-duration (cache.clj:89-96) called at HEAD for 20h, 3h 12m and 45s, then the lines from furb.clj:2467 and 1222, casino/operation.clj:384 and chief.clj:129, assembled as the code does. The countdowns are example values.
After
(format-duration 72000000)  "20h"
(format-duration 11520000)  "3h 12m"
(format-duration 45000)     "45s"

⏳ Vaccine slot 2 can change again at 4:00 ET, in 19h 48m. Nothing was charged.

💉 Slot 1: ember, swap 750,000,000 tokens. Ready after 4:00 ET, in 20h.

⏳ Casino actions are cooling down for 2s. Nothing was charged. Please try again shortly.

⏳ You've already tried the sword today. It resets at 4:00 ET.
A patched scratch copy of cache.clj called on the same inputs: at most two units, rounded up at the smallest unit shown. Cooldowns go through the same formatter, every reset reads 4:00 ET, and the two rejections say nothing was charged.

Fix soon

The brand is misspelled where it's seen most, and its chat marker is shared.

Every !help reply is headed “📚 Noise Help”, and the changelog recap is headed “Noise Changelog Recap”, with “Noise Changelog” on its fallbacks. The charter's working verb, “Assembling the edition…”, ships nowhere. !dispatch ask shows readers “< Checking the coverage ... >” and then “Dispatch question failed. Check logs.”, so the mechanism is named in front of readers. Static's 📡 is reused for weather radar, fetch failures, the stock acknowledgement, Battleship sonar and the lottery's Signal marker.

Fix. Spell it NOISE, reserve 📡 for Static alone, and say “Assembling…” where the charter says to.

help.clj:22-24changelog.clj:256, 271, 283noise.md:45dispatch.clj:5605-5608weather.clj:697-755get.clj:333stock.clj:400battleship.clj:1430lotto.clj:30

Now

NOISE

📚 Noise Help 1️⃣ Utility & Basics 2️⃣ Scheduling & Reminders 3️⃣ Polls, Results & Quotes … 9️⃣ Around Town Open: !help <page> · Details: !help <command> ↩️ Or reply to this menu with a page number.
Noise Changelog Recap 39.2026 Sep 21 - Sep 27 …
< Checking the coverage ... >
Dispatch question failed. Check logs.
📡 Radar — New York, New York
📡 All browser collectors failed to fetch that page.
📡 Sonar contact: sector 004,017 · 15% of weapons redirected
📡 Static Both delegations sign the ceasefire accord in Doha Full bulletin: https://news.jzz.bz/…
The !help menu from menu-response (help.clj:534) at HEAD, pages 4 to 8 elided, and the recap title from changelog.clj:256. Then the !dispatch ask placeholder and the failure that later replaces it in the same message (dispatch.clj:5608, deferred.clj:343), and 📡 from weather.clj:697, get.clj:333, battleship.clj:1430 and dispatch.clj:2484. The location and headline are examples.
After

NOISE

📚 NOISE help 1️⃣ Utility & Basics 2️⃣ Scheduling & Reminders 3️⃣ Polls, Results & Quotes … 9️⃣ Around Town Open: !help <page> · Details: !help <command> ↩️ Or reply to this menu with a page number.
NOISE changelog recap 39.2026 Sep 21 - Sep 27 …
⏳ Assembling an answer from the coverage…
⚠️ NOISE couldn't answer that question. Send your question again in a minute.
Radar for New York, New York
⚠️ That page didn't load. Send !get with the link again in a few minutes.
Sonar contact: sector 004,017, 15% of weapons redirected
📡 Static Both delegations sign the ceasefire accord in Doha Full bulletin: https://news.jzz.bz/…
The menu from a patched scratch help.clj that still routes replies to old “Noise Help” menus. The ask speaks as NOISE with the charter's verb. Radar and sonar drop the marker rather than borrow another, so 📡 marks Static alone. The footer's dots and the Title Case pages belong to other findings.

Fix soon

Emoji are confetti, not a vocabulary.

Module strings hold about 440 distinct emoji, and about 200 of them appear in only one file. ✅ means a win, ready to breed, done and accepted. 💰 is “your balance, only” by standard and also a town jackpot tier. A +5% stock day prints 🚀🌕💎🙌. Discord embeds use Discord's own stock greens and yellows, and Baccarat colours wins green and losses red, which the system bans everywhere else.

Fix. Adopt a closed lexicon (see the chat proposal). Give the embed bar #43604d, with lime only for jackpots. Static is a plain-text teaser, so it has no embed bar.

casino/text.clj:49stock.clj:57-59town.clj:2120-2122work.clj:98-101baccarat.clj:31-33

Now

NOISE

#7F · ✅ · C#1234 · Cosmic Lava Moth · house 68,832,917 tokens
✅ Win! Paid 2,156 (net +1,106).
💰 You woke a dormant progressive meter and collected its ten-million-token handpay.
💰 Balance: 1,234,567 tokens
📊 NVDA — NVIDIA Corporation (NasdaqGS) 🔥 Current: $188.42 🚀🌕💎🙌 👆up $9.32 (+5.20%) vs prev close $179.10 🏆 Day High: $189.95 🛑 Day Low: $180.02
💀 ZZZQ didn't resolve on Yahoo Finance, so it wasn't added.
👀 Now watching 2 flights — I'll post here on status changes until they land.
;; baccarat.clj:210-213, the embed bar
:color (cond
         (= :tie winner) color-push  ; #F1C40F
         won? color-win              ; #57F287
         :else color-loss)           ; #ED4245
;; town.clj:2107-2109
(case tier
  :jackpot color-golden              ; #F1C40F
  …
Called at HEAD: holding-line (furb.clj:1104), win-line and balance-line (casino/text.clj:54, 27), outcome-line (town.clj:2125) on the jackpot at town.clj:421, and the first four lines of format-live-quote (stock.clj:63) on a +5.2% day. The last two are assembled from dispatch.clj:5985 and flight.clj:967, the board lines cut. Amounts are example values.
After

NOISE

#7F, ready, C#1234, Cosmic Lava Moth, house 68,832,917 tokens
✅ Win! Paid 2,156 (net +1,106).
🪙 You woke a dormant progressive meter and collected its ten-million-token handpay.
💰 Balance: 1,234,567 tokens
NVDA 188.42 (+5.20%) NVIDIA Corporation, NasdaqGS Up $9.32 from the previous close of $179.10 Day range: $180.02 to $189.95
🤷 ZZZQ didn't resolve on Yahoo Finance, so it wasn't added. Check the ticker and try again.
✅ Now watching 2 flights. I'll post here on status changes until they land.
;; baccarat.clj:210, the embed bar
:color embed-sage                    ; #43604d, win or lose
;; town.clj:2107-2109
(case tier
  :jackpot embed-lime                ; #dcff4f
  embed-sage)
The same lines against the proposal's lexicon: ✅ only for a win or done, 💰 only for a balance, 🪙 for a payout and 🤷 for not found. A quote takes no marker and its sign carries the direction, as in the stock item's After. 👀 goes back to the progress ladder. Embeds take the sage bar, and lime only for a jackpot.

Fix soon

Waiting has three grammars, and Discord gets no reaction ladder.

Placeholders read “< Updating ... >” and “Building the color map…”, and commands without one acknowledge with “GitHub lookup queued. I’ll post it shortly.” The bot calls itself “I” in 42 files and “Bot …” in six busy messages. The 👀 ⏳ ✅ reaction ladder is Signal-only, so a Discord user of a command with no placeholder sees nothing until the answer lands.

Fix. Use one placeholder, ⏳ Checking the forecast…, edited in place (or, when the answer carries an image, followed by the answer and then deleted), and run the same ladder on both transports.

deferred.clj:16toilet.clj:235color_guesser.clj:712gh.clj:155signal/signal.clj:1718-1722weather.clj:736-740

Now
!osrsbond
  < Updating ... >

!toilet <model> <content>
  < Flushing your request ... >

!color map
  Building the color map…

!gh clojure/clojure
  GitHub lookup queued. I’ll post it shortly.
  (a second message brings the answer)

!w 10001 on Signal
  👀 ⏳ ✅ on the command, no placeholder

!w 10001 on Discord
  nothing until the forecast lands
What each command shows in a group while it waits at HEAD: the default placeholder (deferred.clj:16), toilet.clj:235, color_guesser.clj:712, gh.clj:155 and weather.clj:736-740. Signal puts the ladder on every command (signal.clj:1718-1722); Discord has none.
After
!osrsbond
  ⏳ Checking the bond price…

!toilet <model> <content>
  ⏳ Flushing your request…

!color map
  ⏳ Building the color map…

!gh clojure/clojure
  ⏳ Looking up clojure/clojure on GitHub…
  (edited in place into the answer)

!w 10001 on Signal
  👀 ⏳ ✅ on the command
  ⏳ Checking the forecast…

!w 10001 on Discord
  👀 ⏳ ✅ on the command
  ⏳ Checking the forecast…
The same group, one grammar: ⏳, a present-tense verb and an ellipsis, with the same ladder on both transports. Text answers replace the placeholder by edit. When the forecast carries its radar image, it arrives as a new message and the placeholder is removed.

Also

  • 👎 on your own mint post grinds the Furb to the house with no confirmation. The post says so, but a common sentiment reaction is wired to an irreversible ledger action. furb.clj:1584, 2627-2645
    Now

    NOISE

    👾 Moss Toad · Collection #1204 · your slot #A3 Minted by: Kyle 👎 this message to grind it (current quote: 62,000,000 tokens).
    👾 Ground #A3 into 62,000,000 tokens. 🧾 Ledger confirmed: 3f9c1a7e
    The mint post (furb.clj:1584) and the reply to one 👎 from the minter, which sells at once (furb.clj:2627-2645). Style, quote and receipt are example values; the quote sits in the ordinary 40,000,000 to 95,000,000 band (furb.clj:131-132).
    After

    NOISE

    👾 Moss Toad · Collection #1204 · your slot #A3 Minted by: Kyle 👎 this message to start a grind (current quote: 62,000,000 tokens). Nothing sells until you confirm.
    👾 Grind #A3, your Moss Toad, for its current quote of 62,000,000 tokens? No tokens have moved yet. Send !furb grind A3 to confirm.
    Proposal, text only. 👎 now asks, and the existing !furb grind A3 confirms at the quote current when it runs. This changes a player-facing control.
  • The !furb help card says “An ordinary mint costs 100,000,000 tokens tokens”, and the same text feeds the reference page and !help <question>. furb.clj:155-158
    Now

    Furbs are collectibles bought with casino tokens. Start with !furb mint, or browse !furb market. An ordinary mint costs 100,000,000 tokens tokens and needs an available hand slot. …

    The !furb help-docs prose (furb.clj:155-158), rendered in a REPL and trimmed. casino-text/tokens already adds the word. Bold marks the error.
    After

    Furbs are collectibles bought with casino tokens. Start with !furb mint, or browse !furb market. An ordinary mint costs 100,000,000 tokens and needs an available hand slot. …

    The literal " tokens" after the call is dropped. The help card, reference page and !help <question> all read this one string.
  • Help page 7, Casino & Gambling, runs to 47 lines and also holds Battleship, Color, Furb, Sword and Trial. Pages 2, 3, 8 and 9 run to 3 or 4 lines. Category titles are Title Case with “&”. help/docs.clj:21-57
    Now

    NOISE

    📚 Noise Help — 🎰 Casino & Gambling Page 7 of 9 • !bacc <bet> player|banker — 🎴 Short form baccarat bet, e.g. !bacc 100 banker [2 more lines] • !battleship — 🚢 Show the Battleship command menu [7 more lines] • !color — 🎨 Roll a random unguessed 24-bit color [7 more lines] • !furb — Show the Furb command menu [17 more lines] • !sword — Pull the Sword in the Stone once a day: a hidden chance that grows a little with every failed pull here; success crowns you Sovereign and makes the sitting Sovereign the Court Jester [3 more lines] • !trial — Challenge the Lord Protector to Trial by Combat once a day: d20 against d20, ties to the Protector; win to unseat them and take 100,000, lose and you're slain [5 more lines] Open: !help <page> · Details: !help <command> ↩️ Or reply to this menu with a page number.
    📚 Noise Help — 🗳️ Polls, Results & Quotes Page 3 of 9 • !poll — Manage group polls (use !poll create / !poll vote / !poll list) • !quote — Show a random saved quote from this group • !results — Show the election-results command menu Open: !help <page> · Details: !help <command> ↩️ Or reply to this menu with a page number.
    !help 7 and !help 3 from the real renderer (help.clj, help/docs.clj:21-57), on Signal with every module on. Page 7 has 47 command lines; bracketed lines are elided here.
    After

    NOISE

    📚 Noise Help — 🎰 Casino games Page 6 of 9 • !bacc <bet> player|banker — 🎴 Short form baccarat bet, e.g. !bacc 100 banker [21 more lines] Open: !help <page> · Details: !help <command> ↩️ Or reply to this menu with a page number.
    📚 Noise Help — ⏰ Schedules, polls and quotes Page 2 of 9 • !cron — View scheduled group posts with !cron list; creating or changing schedules requires an admin • !fest — Manage festival schedules (use !fest add / !fest list / !fest show) • !music — Track live music by city (use !music add / !music list / !music tonight) • !poll — Manage group polls (use !poll create / !poll vote / !poll list) • !quote — Show a random saved quote from this group • !remind — Manage reminders (use !remind in / !remind at / !remind list) • !results — Show the election-results command menu Open: !help <page> · Details: !help <command> ↩️ Or reply to this menu with a page number.
    !help 6 and !help 2 from the same renderer with a help-only split, elided the same way. Casino becomes Casino games (22 lines) and Casino accounts and crews (18). Battleship, Color and Furb move to page 8, Sword and Trial to page 9. All 128 commands keep their text.
  • Raw ** reaches Signal from a day-job ability, and !bj chart is fenced. dayjob_ability.clj:698, blackjack.clj:1115
    Now

    NOISE

    🌠 Read the Stars: the sky is unambiguous tonight — **deathroll** pays you +5% on winning profit until the 04:00 ET reset.
    ♠️ Blackjack Basic Strategy [2 lines: rules and legend] ``` Dealer: 2 3 4 5 6 7 8 9 10 A Hard 5-8 H H H H H H H H H H 9 H Dh Dh Dh Dh H H H H H [29 more lines] ```
    Signal shows the asterisks and fences as typed. The Read the Stars reply from the real star-chart! (dayjob_ability.clj:698) and !bj chart (blackjack.clj:1115), trimmed. The game is an example value.
    After

    NOISE

    🌠 Read the Stars: the sky is unambiguous tonight, and deathroll pays you +5% on winning profit until the 04:00 ET reset.
    ♠️ Blackjack Basic Strategy [2 lines: rules and legend] Dealer: 2 3 4 5 6 7 8 9 10 A Hard 5-8 H H H H H H H H H H 9 H Dh Dh Dh Dh H H H H H [29 more lines]
    Bold and monospace travel as :text-styles ranges, as lead.clj and slots.clj already send them. Shown on Signal. Discord's connector turns monospace into single backticks, so a multi-line span needs a fenced case there.
  • The same file has two “not enough money” lines: “💸 Insufficient casino balance” and “⚠️ You need 1,050 tokens to spin”. casino/text.clj:74, 85
    Now
    💸 Insufficient casino balance. Balance: 800 tokens.
    ⚠️ You need 1,050 tokens to spin. Current balance: 800 tokens.
    insufficient-balance and insufficient-for-bet (casino/text.clj:74, 85) from a REPL. Balance 800 and a 1,050 spin are example values.
    After
    💸 Not enough tokens. Your balance is 800 tokens. Nothing was charged. Withdraw savings with !bank withdraw, or earn tokens with !washdishes.
    💸 Not enough tokens to spin: it takes 1,050 tokens and your balance is 800. Nothing was charged. Withdraw savings with !bank withdraw, or earn tokens with !washdishes.
    One shape for both helpers: what was short, the balance, that nothing was charged, and a next step the caller passes (the wager one here). Battleship's multi-volley stop note (battleship.clj:1394) follows volleys already charged, so it needs its own ending.
  • !stock leads with “📊 AAPL — Apple Inc. / 🔥 Current: $213.10 / 📈 👆up”, while the newsroom writes the house line “AAPL 213.10 (+0.45%)”. stock.clj:77-90, dispatch/markets.clj:206-213
    Now

    NOISE

    📊 AAPL — Apple Inc. (NasdaqGS) 🔥 Current: $213.10 📈 👆up $0.95 (+0.45%) vs prev close $212.15 🏆 Day High: $214.02 🛑 Day Low: $211.40 📦 Volume: 48,213,005 🗓️ 52w Range: $164.08 – $237.23
    format-live-quote (stock.clj:77-90) called with a synthetic quote. Prices are example values.
    After

    NOISE

    AAPL 213.10 (+0.45%) Apple Inc., NasdaqGS Up $0.95 from the previous close of $212.15 Day range: $211.40 to $214.02 Volume: 48,213,005 52-week range: $164.08 to $237.23
    Leads with the house line from format-market-value and format-market-change (dispatch/markets.clj:200-213). Same quote, rendered from a patched scratch copy.
  • One currency has three names: Goatinum, Gt and Ǥ. Say Goatinum in prose and write amounts as Ǥ3. vip.clj:46-48, bank.clj:114, casino.clj:44
    Now

    Use !vip convert [n] to buy Goatinum (Gt, Ǥ) for 1,000,000,000,000,000 casino tokens each. Bare !vip convert buys one Gt and !vip convert 3 buys three, up to Ǥ9,223 per command; conversion never happens automatically. … !vip spends it on Furb goods or cashouts; !vip cash exchanges each Goatinum for 250,000,000 playable tokens, not the original buy-in amount.

    The !vip help-docs prose (vip.clj:46-58) from a REPL, trimmed. Bold marks where the names mix.
    After

    Use !vip convert [n] to buy Goatinum for 1,000,000,000,000,000 casino tokens per Ǥ1. Bare !vip convert buys Ǥ1 and !vip convert 3 buys Ǥ3, up to Ǥ9,223 per command; conversion never happens automatically. … !vip spends it on Furb goods or cashouts; !vip cash pays 250,000,000 playable tokens per Ǥ1, not the original buy-in amount.

    Goatinum in prose and amounts as Ǥn. Commands and prices are unchanged.
  • On Signal, blackjack lists “Controls: Hit · Stand · Double”, which are Discord buttons that don't exist there; say “Send !bj hit”. blackjack.clj:603
    Now

    NOISE

    ♠️ Kyle's Blackjack Table Session: 4F2A · Active hand 1/1 🎯 Current bet: 5,000 tokens Dealer 9♣ 🂠 Hands ▶ Hand 1 · 5,000 tokens 6♠ 5♥ (11) Controls: Hit · Stand · Double
    format-active-response on a synthetic Signal hand (blackjack.clj:597-603, 716). The spoilered strategy hint below it is not shown.
    After

    NOISE

    ♠️ Kyle's Blackjack Table Session: 4F2A · Active hand 1/1 🎯 Current bet: 5,000 tokens Dealer 9♣ 🂠 Hands ▶ Hand 1 · 5,000 tokens 6♠ 5♥ (11) Send !bj hit, !bj stand or !bj double.
    Signal names the commands it accepts. Discord and the web table keep their buttons and drop the line.

News

The newest news is the smallest thing on the page.

The news site is the most finished surface, and its structure (the headline link beside a native disclosure, sourcing in the open) is the best in the system. Its hierarchy runs against the clock, and two of its four register columns are redundant or wrong.

Fix first

A Static bulletin sits in 16 px type, in a 46 px band, above the 104 px headline it supersedes.

In the fixture, the band says “Both delegations sign the ceasefire accord in Doha” while the giant headline under it still says the talks produced a draft. The Latest register renders after the instruments and the game board: its first row sits at 2,266 px down at 1440 × 900. After the lead, stories rank only within their desk. Desks run in a fixed order, so Sports starts about 6,100 px down.

Fix. When a bulletin is live, set its headline at statement size and step the lead down one role. Move Latest above the instruments. The bigger version is the front page as a log (see the proposals).

sidecar/news-site/site/index.html:9-44resources/dispatch/base.css:62-65model.py:17-18, 650

Now Front page with a live Static bulletin: a thin dark band carries the signed-accord headline in 16 px text, and under it the lead's six-line display headline about a draft accord fills the screen, followed by its blurb and sources.
Production templates and CSS at HEAD with the synthetic ledger, 1440 by 900, night, column from the nav rule down. Latest's first row sits 2,266 px down, below the instruments and the game board.
After The same front page with the bulletin headline set large in serif inside the Static band, the lead headline stepped down to three lines, and the Latest register following straight after the lead's sources.
Patched copies of index.html, story.html, base.css and news.css, same data and crop. The bulletin headline takes the statement size, the lead steps down from display to statement, and Latest's first row now sits 1,100 px down, above the instruments.

Fix first

The page states wrong times.

freshness-label computes “Updated <1h”, “2h” or “today” once, when the edition is built, and the site prints that text as it is. At 17:00, a story from 07:00 still says it was updated less than an hour ago. The front page carries stories from editions up to 72 hours old, so yesterday's “Updated 1h” appears on today's page.

Fix. Render publishedAt as <time datetime data-local>. The page script already localizes those, but as “Sat, 5:58 AM EDT”, so change its format to 24-hour time with a weekday only for earlier days. Show “10:00” for today and “Fri 18:05” for earlier days, and drop the word “Updated” from a column already headed Updated.

src/bot/modules/dispatch/editorial.clj:479-500sidecar/news-site/model.py:433, 635server.py:52

Now Five U.S. register rows whose last column reads Updated <1h, Updated today, Updated yesterday on two lines, Recent, and Updated 2h for a story from the previous evening's edition.
Production templates at HEAD, 1440 px, night. The labels are the build-time freshness-label text. The last row comes from Friday's Evening edition and still says Updated 2h.
After The same five rows with times in the last column: 07:25, 01:40, Fri 18:05, Wed 14:20 and Fri 16:00.
Patched story.html and a new clock filter in render_worker.py render publishedAt as <time datetime data-local> under the Updated head. site.js is not in the checkout, so this shows the server's fixed Eastern text; the lead's line becomes “World, 05:58, 5 sources.”

Fix first

The lead display is sized for a column it doesn't have.

The display size, clamp(56px,7.2vw,104px), was set so a headline holds three lines across 1,080 px. Beside a plate the copy gets 600 px. A headline within the 12-word copy limit sets in five or six lines, so the first screen is one headline, and the next story is two and a half screens down. On a phone the plate comes first and the headline starts 690 px down an 844 px screen.

Fix. Size the display by its container, clamp(48px,12.5cqi,80px) with .lead .story-copy{container-type:inline-size} (without the container, cqi falls back to the viewport), and put the plate after the blurb below 600 px.

design-guide.md:519sidecar/news-site/site/assets/news.css:28-34copy.clj:78

Now Left, the desktop first screen: a six-line display headline beside the plate, with only the start of the blurb above the fold. Right, the phone first screen: masthead, Static band and plate, with the headline starting near the bottom.
Production CSS at HEAD. Left: 1440 by 900 below the nav, headline at 104 px in a 600 px column. Right: phone at 390 by 844, where the headline starts 690 px down.
After Left, the same desktop screen with a four-line headline at 75 px, the full blurb and the sources above the fold. Right, the phone screen with the headline under the Static band and the blurb below it, the plate pushed after the blurb.
Patched base.css and news.css: the display is clamp(48px,12.5cqi,80px) on a .lead .story-copy container, and below 600 px the plate follows the blurb. The headline sets at 75 px on desktop and starts 363 px down on the phone.

Fix soon

Half the register repeats itself or wraps.

The Desk column repeats the section head on every row: “Business” four times under “Business, 4 stories”. The guide says the desk title hides on desktop, but it shows. Sources and Updated take 16rem of every row. Their column guides draw as 44 px stubs instead of columns, and “Updated yesterday” wraps inside 8.5rem.

Fix. Use Time, Story, Sources (6rem minmax(0,1fr) 8rem, since “Fri 17:30” in 13 px mono needs more than 5rem), with the desk named only in section heads and in Latest, where desks mix. Stretch the disclosure to the row so the guides run full height.

design-guide.md:411-413news.css:80-87resources/dispatch/base.css:123-128, 165

Now The Business section of the register: every row repeats Business in a Desk column, the Sources and Updated cells sit in short boxes whose guides stop after one line, and Updated yesterday wraps onto two lines.
Production templates and base.css at HEAD, 1440 px, night. Desk, Story, Sources and Updated at 7.5rem minmax(0,1fr) 7.5rem 8.5rem.
After The same section as Time, Story and Sources: times 05:50, 01:05 and Fri 17:30 on the left, no repeated desk name, a wider story column, and the Sources guide running the full height of each row.
Patched templates, render_worker.py and base.css at 6rem minmax(0,1fr) 8rem, because the page's 5rem wraps “Fri 17:30”. The disclosure stretches to the row, so the guide runs full height. Latest keeps the desk under the time. Times are the server's fixed text because site.js is absent.

Fix soon

The plates are wallpaper, and most screens resample them.

A plate is hash(story id) mod 10 within the desk's pool, skipping plates already on the page, so nothing ties the picture to the story: a hurricane is as likely to draw a mirror-still lake as a storm. Six plates per page carry the same caption, “Illustration: NOISE.”, seven when Latest shows. They are exact only on a 2x desktop: at 1x the browser drops every other row and column, and below 900 px the 280 px lead is resampled on every phone, upscaled by 1.235 at 3x.

Fix. Caption each plate with its name (“Storm clearing. Illustration: NOISE.”), and generate a dither for each displayed size and pixel ratio (340 and 680 px for the desktop lead, 280, 560 and 840 px below 900 px, and the same for the 240 and 200 px openers), served with srcset and sizes, instead of scaling the 680 master. The bigger version is plates as the day's weather.

src/bot/core/plates.clj:56design-guide.md:942-946

Now The front-page lead plate, a dithered fjord, as a 1x screen draws it: the 680 px dither drops every other pixel, so edges break into stray dots and flat areas turn streaky. The caption reads Illustration: NOISE.
Production front page at 1440 px and device scale 1, the lead plate at its 340 px CSS size, shown pixel for pixel.
After Same plate and crop at 1x from a native 340 px dither: even tones and clean edges. The caption reads Fjord morning. Illustration: NOISE.
Scratch story.html adds a 1x and 2x srcset and names the plate. The 340 px dither is re-derived from the 680 master, because the source paintings are not in the checkout.

Fix soon

The meter's current bar is colour-only, and by day the accent disappears.

At night, lime against the ink bars is 1.03:1 in luminance; only hue separates them. By day --acid-text is #3f5a48 against ink #2d4638, 1.35:1. The Static chip, live squares and the meter's “now” all turn into ink, and the Static band reads as a grey banner. Levels differ by 3 px, so busy and heavy can't be told apart.

Fix. Draw prior bars hollow and the current bar filled, so shape carries the state. Set Static as a lime fill with dark text in both themes; the charter already allows lime fills.

resources/dispatch/themes/noise.css:59resources/dispatch/base.css:103design-guide.md:1015-1016

Now Day mode front page: the Static chip is dark green text on the pale band, and all seven Signal read bars are solid dark green with the latest bar only slightly lighter.
Production templates and CSS by day at 1440 px, two crops from one page: the Static band and the Signal read.
After Same crops: Static is a lime chip with dark text, prior meter bars are hollow outlines and only the latest bar is filled lime.
Scratch copy of base.css with hollow prior bars, a filled current bar and a lime Static chip, same page and crops.

Fix soon

The share card is anonymous at the size people see it.

The headline is a fixed 50 px, so “Fed holds rates” floats in an empty half. The edition line, “Morning NOISE, Saturday, September 26, 2026” in 21 px mono, wraps mid-date. In a 330 px chat bubble the edition line is about 6 px and the wordmark about 7 px. What survives is a landscape and a serif headline, which reads as any literary newsletter.

Fix. Fit the headline from 84 px down to 50 px until it takes four lines or fewer. Set “Morning NOISE, Sep 26” in the sans at 26 px, and the wordmark at 44 px or more.

resources/dispatch/card.html:31-32news_site.clj:609-618dispatch/common.clj:325-327

Now Two share cards at 330 px chat width: the edition line is tiny mono wrapping mid-date, the headline is a small fixed serif, Fed holds rates floats in an empty half, and the NOISE wordmark is a speck at the bottom right.
Production card.html rendered at 1200 by 630, then scaled to a 330 px chat bubble. Long and short lead headlines.
After Same cards: one-line sans edition lines, Morning NOISE, Sep 26 and Evening NOISE, Sep 25, headlines fitted to 60 px and 84 px, and a 48 px wordmark that reads at chat size.
Scratch card.html with the headline fitted from 84 px down to four lines, the short edition line in the sans at 26 px and the wordmark at 48 px. Same plates and scale.

Also

  • Two clock conventions in one sentence: “Added since the Morning NOISE at 8:00 EDT. Updated Sat, 11:30 AM EDT.” Pick 24-hour everywhere. index.html:37, model.py:742
    Now

    Added since the Morning NOISE at 8:00 EDT. Updated Sat, 11:30 AM EDT.

    Rendered by the news-site renderer at HEAD from index.html:37. The first time is header-time-formatter (common.clj:326), the second eastern_time (model.py:742).
    After

    Added since the Morning NOISE at 08:00 EDT. Updated 11:30 EDT.

    One 24-hour convention: header-time-formatter becomes HH:mm z and eastern_time drops AM and PM. The weekday shows only when the update falls on another day than the edition. Rendered from a patched model.py copy.
  • The edition page stacks two 52 px statements: the edition title and the lead. publication.html:4, story.html:41
    Now Edition page head at night: the title Morning NOISE and the lead headline below it are both set in the same 52 px serif.
    Production edition page at 1440 px, night, the head and the lead's copy column.
    After Same crop: Morning NOISE is a 34 px condensed sans title and the lead headline is the only serif statement.
    Scratch copies of publication.html and news.css set the edition title in the sans at 34 px. Same page and crop.
  • The archive's up to 400 links all read “Morning NOISE” or “Evening NOISE”; the date and count sit in their own columns and the lead is missing. Put the date in the link and add the lead headline. archive.html:11
    Now Archive table at night: six rows whose links alternate Morning NOISE and Evening NOISE, with a mono weekday date and a story count beside each.
    Production archive.html at 1440 px, night, with a synthetic six-edition archive.
    After Same table: each link names its edition and date, such as Evening NOISE, September 24, beside the lead headline and the story count.
    Scratch archive.html and news.css: the link carries the edition and date, a Lead story column holds the lead headline. Same data, viewport and crop.
  • On short pages the grain stops at the end of the body; add body{min-height:100vh}. base.css:8, 12
    Now Archive page footer at night: below the footer links the grained sage stops in a straight line and flat, darker sage fills the rest of the window.
    Production archive page with two editions at 1440 by 900, night, cropped at the end of the body.
    After Same crop: the grain continues evenly to the bottom of the window with no seam.
    Scratch base.css with body min-height 100vh, same page, viewport and crop.
  • The guide still quotes the old board credit, “Scores and schedules from ESPN.” The credit changed on purpose when boards gained nflverse, Jolpica and saved schedules (25750fb5); update §6.11 to match. sports-board.html:15, design-guide.md:619-621
    Now
    sports-board.html:15 renders
    Scores from ESPN. Alternative and saved schedules are labeled on each game; game links open ESPN.
    
    design-guide.md:619-621 asks for
    one muted credit line, “Scores and schedules from ESPN.”, with ESPN as the link
    The board's credit and the contract sentence. The credit changed in 25750fb5, when boards gained nflverse, Jolpica and saved schedules.
    After
    sports-board.html:15 unchanged
    
    design-guide.md:619-621 asks for
    one muted credit line, “Scores from ESPN. Alternative and saved schedules are labeled on each game; game links open ESPN.”, with ESPN as the link
    The contract takes the current credit. Restoring the old sentence would credit ESPN for nflverse and Jolpica schedules, sports.clj:95 and 123.
  • The news site registers the Instrument Serif italic and never uses it, and every team name is underlined. base.css:272
    Now Edition page game board at night: every team name, New York Yankees, Boston Red Sox, Oregon and USC, carries a resting underline.
    Production edition page at 1440 px, night, Live and Final groups of the game board.
    After Same crop: team names sit without underlines; leading teams stay bold.
    Scratch base.css drops the resting team underline, so names take it on hover as register headlines do, and fonts.css drops the italic face. Same page and crop.
  • Desk links on edition pages go to today's front page. base.html:48
    Now
    {% for category in site.categoryNav %}
      <li><a href="/#{{ category.slug }}">
        {{ category.label }}</a></li>
    {% endfor %}
    
    Evening NOISE, September 25 renders:
    /#us  /#world  /#business  /#tech  /#sports
    sidecar/news-site/site/_includes/base.html:48, one line wrapped to fit, shared by every page. On an edition page each desk link leaves for today's front page.
    After
    {% if edition %}
      {% set board = edition.sportsBoard %}
      {% for section in edition.sections %}
        {% if (section.stories | length)
              or edition.categoryNotes[section.slug]
              or (section.slug == "sports"
                  and ((board.live | length)
                       or (board.post | length)
                       or (board.upcoming | length))) %}
          <li><a href="#{{ section.slug }}">
            {{ section.label }}</a></li>
        {% endif %}
      {% endfor %}
    {% else %}
      {# front page, archive, Static and previews: as now #}
    {% endif %}
    
    Evening NOISE, September 25 renders:
    #tech
    Patched base.html, rendered from a scratch copy against the fixture: edition pages link only the desks they show, in place.

Lounge

A creature market dressed as a settings app.

The collection and the Furb record feel like an archive of strange creatures, and that is the best of the lounge. The room and most of Games feel like a SaaS dashboard in sage. Account reading like a bank statement is fine.

Fix first

Most games are forms.

Only Blackjack, Video Poker, Scratch and Battleship have their own boards. Every other service goes through the generic renderer, which draws a shape only when the server sends one and shows the rest as label and value rows. Slots shows a View select that repeats the tabs, the machine picker twice, a “Selected machine” row reading “slots” and a folded 15-row Machines table, then a Balance and Debt that repeat the rail. No reels appear until you spin. Roulette is a dropdown reading “Red, 2×”.

Fix. Give the next four or five games a real shape: a reel grid that is always visible with the last spin, and a roulette layout with the outside bets as buttons. Pass balance and debt to the renderer's existing omit, and fold nested records after three rows as the guide already says.

docs/furb-frontend.md:145frontend/cljs/src/noise/service_records.cljs:112-144, 196

Now The Slots board in the lounge preview: the machine picker appears above the action tabs and again inside the Spin form, then a Machine record lists Number, Name, Rows 3, Columns 5 and Jackpot contribution percent 5%, a folded Machines table, then Jackpot, Balance and Debt rows. No reels are shown.
Lounge preview at HEAD, Slots on the Machines tab, 1024 px, night. The fields come from the synthetic preview fixture.
After Mock of the same board: one machine picker, a Your last spin plate with a three by five reel grid and the line 3 bells on the middle line, paid 5,000, then the Spin, Multi-spin and Repeat last wager register over a Spin form holding only Wager and the Spin button, then four rows (Jackpot, Jackpot condition, Spins today, Crown wins today) and a folded Details section. Balance and Debt are gone.
Mock. Board markup replaced in the running preview and styled by the production lounge CSS. The last spin and its payout are example values. Roulette is not shown.

Fix first

Breeding hides the odds under “How this works”.

The confirmation joins the inheritance odds, the rest rule and the vaccine note into one note. Any note over 170 characters folds, so “33% chance to inherit Holographic” is hidden. The parents are 64 px thumbnails. The one creative act in the lounge is dressed like a wire transfer.

Fix. Make the odds their own line, short enough never to fold. Show the parents at half the pane's width. See the breeding bench proposal.

frontend/cljs/src/noise/actions.cljs:92-96furb_ui.cljs:287-288lounge.css:258-261

Now The Breed these Furbs? confirmation: two parents as 64 px thumbnails beside their captions, then a folded How this works line above the lime Breed for 200,000,000 tokens button. The inheritance odds are hidden in the fold.
Lounge preview at HEAD, 1440 px, night: an ascended parent bred with a Holographic one. The joined note is 244 characters, so it folds.
After The same confirmation with both parents side by side at half the pane's width, then the 33% chance to inherit Holographic in ink on its own line, the rest and vaccine rule as a separate note, and the same button.
The same state with CSS and a DOM split injected into the preview: the parents sit at half the pane's width, and the odds get their own line, which never folds.

Fix first

On a phone, the actions are under the fold or under the tab bar.

The sheet's first action row starts at 802 px on an 844 px screen. The sheet head isn't sticky, so the name and Close scroll away. In Blackjack, Hit, Stand and Double down sit from 772 px to 816 px, and the tab bar starts at 780 px.

Fix. Make the pane head sticky. While a hand is live, pin the play controls above the tab bar on the ground colour with an ink rule.

frontend/cljs/resources/styles/lounge.css:173

Now Two phone crops. Top: a Furb sheet scrolled down, its name and Close gone off the top. Bottom: Blackjack with Hit, Stand and Double down cut off behind the tab bar.
Lounge preview at HEAD, 390 by 844 phone. Top: the sheet scrolled to its end. Bottom: Blackjack, where the controls start at 772 px and the tab bar at 780 px.
After The same two crops. Top: the sheet head with the Furb's name and Close stays pinned while the body scrolls under it. Bottom: Hit, Stand, Double down and Forfeit hand sit in a two-row bar on the ground colour under an ink rule, just above the tab bar, covering the basic strategy line.
The same states with CSS injected into the preview: a sticky pane head, and the live hand's controls pinned above the tab bar. The bar covers the basic strategy line until you scroll.

Fix soon

The room doesn't look like a trading floor.

At 1440 px the Furbs are 177 px thumbnails in the left third of the column, under about 210 px of stacked chrome: a page bar that repeats the rail, a tab row and the feed. Each card has four to six lines of identical 12.5 px mono. The finish, a trait one mint in ten rolls (2% for each of the five finishes), comes last in the muted colour, next to “Generated mint”.

Fix. Move the tabs into the page bar and widen walls to 220 px minimum. Put the finish on the line after the name, in ink, and leave the origin to the record.

furb_ui.cljs:20, 57lounge.css:101

Now The room at 1440 px: a page bar titled The room, a separate tab row, the feed, then two 177 px Furb cards whose captions end with Generated mint and the finish in the muted colour
Lounge preview at 1440 px, night, main column. Cards are 177 px and the finish trails the origin in the muted colour.
After The same room with the tabs in the page bar, one row of chrome gone, cards at 276 px, and Platinum and Ghost in ink on the line under each name
Tabs moved into the page bar, walls at a 220 px minimum, finish in ink under the name, origin left to the record. Injected into the same preview state.

Fix soon

Destroying a Furb looks like buying one.

“Banish for 0 tokens” is a lime button. The Grind button reads “Confirm grind” with no amount, although the contract says the primary names the act and its price. Banish uses the same x icon as Close, and the Furb about to be destroyed is a 64 px thumbnail.

Fix. Write “Grind for about 62,000,000 tokens”. Show the full plate of what you're about to lose. Give Banish its own icon and keep x for Close.

actions.cljs:150, 156, 393, 437

Now Two pane crops. Top: the Grind this Furb? confirmation, with a 64 px thumbnail of the Furb beside its caption, a note, and a lime button reading Confirm grind with no amount. Bottom: an ascended Furb's Let go register, where the Banish row carries the same x icon as Close.
Lounge preview at HEAD, 1440 px, night. Top: Grind on a Golden Furb with a house quote of 62,000,000 tokens. Bottom: the ascended Furb's sheet scrolled to Let go.
After The same two crops. Top: the Furb shown as a full-width plate above its caption, and the button reading Grind for about 62,000,000 tokens. Bottom: the Banish row now carries a trash can icon.
The same states with CSS, the new label and the new icon injected into the preview. 62,000,000 is an example value, the preview's quote. The trash icon is one possible choice.

Fix soon

Pressed has four looks, and the loudest one isn't the primary.

The rail and the catalog mark the current row with a --deep fill and an underline. Scratch tiers, the segmented control, pane toggles and held poker cards fill with ink, the collection uses a lime edge, and the floor tabs use an underline alone. At night an ink fill is a near-white slab louder than the lime primary: in the Slots screenshot above, the pressed Spin segment outshouts the Spin button.

Fix. Use one pressed treatment and one current treatment. Draw every playing card as paper and drop the ink fill on held cards, so the Held word they already carry marks them.

lounge.css:23, 51, 62, 186, 353, 388, 421, 432gallery.css:17, 35

Now Two Games views. Slots: the catalog's current Slots row in a deep fill, and the pressed Spin segment as a near-white slab brighter than the lime Spin button. Video Poker: the three held cards as near-white slabs, the two others in the deep fill, beside the lime Draw button
Lounge preview at 1440 px, night, Games on Slots and on Video Poker. The pressed segment and the held cards are ink fills; the catalog row is a deep fill.
After The same two views. The Spin segment is a 2 px ink ring on the ground, quieter than the lime Spin button. All five playing cards are paper; the three held cards carry the same 2 px ring and the word Held. The catalog rows keep their deep fill and underline
One pressed treatment, a 2 px ring with no fill, and one current treatment, the deep fill with an underline. Every card is paper and held cards say Held. Injected CSS, same states and crops.

Also

  • The lounge wordmark is underlined. The no-underline rule sits on p.mast and the global link rule reaches the anchor inside it. Add .mast a{text-decoration:none;color:inherit}. lounge.cljs:107, base.css:47, 57
    Now The NOISE Lounge wordmark at the top of the rail with a link underline under both words
    Rail wordmark, lounge preview at 1440 px, night.
    After The same wordmark with no underline
    With .mast a{text-decoration:none;color:inherit} injected.
  • When no other traders are here, the room's empty state offers only “Browse the marketplace”. Mint sits in the hand drawer, which starts folded. lounge.cljs:183-186, furb_ui.cljs:428
    Now An empty lounge room on a phone: The table is quiet, a note that no other traders are here, and one link, Browse the marketplace.
    Lounge preview at HEAD, the empty room fixture, 390 px phone, night, hand folded.
    After The same room with a second line saying the hand is empty, a lime Mint a Furb for 100,000,000 tokens button, and Browse the marketplace below it.
    The same state with the Mint button injected into the empty state. The price is an example value, the base mint price for a player with no genes or vaccines.
  • Icons draw at stroke 2, not 1.75, so the phone tab bar's 2.25 emphasis for the current tab never shows. ui.cljs:6-14
    Now Phone tab bar with Room current: the Room, Games and Account icons all draw at the same 2 px stroke
    Phone tab bar at 390 px, Room current. Every icon path computes to 2 px.
    After The same tab bar with Games and Account at a lighter 1.75 stroke and the current Room icon heavier at 2.25
    The inner stroke-width removed, so icons draw at 1.75 and the current tab at 2.25. Same preview, DOM patched.
  • Pane headings skip from h2 to h4. furb_ui.cljs:109, 116, 297, 345, 362
    Now
    h1 The room
      h2 Your Paper Lantern 8
            h4 Bred from
            h4 Composition 15
            h4 Sell
            h4 Keep
            h4 Mascot
            h4 Let go
      h2 Offers
            h4 Cash offers for you
            h4 Targeted offers for you
            h4 Your cash offers
    Heading outline read from the lounge preview: your bred Furb #8 and the Offers pane. The h4 sites are furb_ui.cljs:109, 116, 297, 345.
    After
    h1 The room
      h2 Your Paper Lantern 8
        h3 Bred from
        h3 Composition 15
        h3 Sell
        h3 Keep
        h3 Mascot
        h3 Let go
      h2 Offers
        h3 Cash offers for you
        h3 Targeted offers for you
        h3 Your cash offers
    Pane sections become h3. Their CSS selectors move from h4 to h3 too, so nothing looks different.
  • The mutation step is a pile of outlined buttons, and the name it produces only appears inside a sentence. Make a live name preview the head of the step. furb_ui.cljs:263-279
    Now The mutation step with Velvet and Comet picked: the name only appears inside the sentence The new Furb's style will be exactly Velvet Comet, in that order, above a pile of gene buttons.
    Lounge preview at HEAD, 1440 px, night, with two genes picked.
    After The same step headed by a live preview: a mono label, Velvet Comet set large in the serif, and Exactly these words, in this order in lime mono. The sentence is gone.
    The same state with the preview head injected into the pane. It would update with each gene tap.
  • “2 of 8 slots” sits beside three Furbs and “6 empty slots”, because an ascended Furb takes no slot. Say so.
    Now The open hand drawer: Your hand, 2 of 8 slots, above three Furbs, the first marked Ascended, and a tile reading 6 empty slots, Mint a Furb
    Hand drawer, lounge preview at 1440 px, night. furb_ui.cljs:408 counts with fmt/hand-count, which skips ascended Furbs.
    After The same drawer with the count reading 2 of 8 slots, 1 ascended Furb takes none
    The count says why: 2 of 8 slots, 1 ascended Furb takes none. The clause appears only while the hand holds an ascended Furb. Text added in the same preview state and crop.
  • The ✅ and ❌ marks are a recorded decision and stay. Worth knowing: ❌ reads as punishment for a Furb that is only resting.
    Now Three Furbs in the hand: Cobalt Bandana and Paper Lantern lead with a green check, Sage Astronaut with a red cross because it is resting
    Hand drawer, lounge preview at 1440 px, night. A resting Furb leads with ❌.
    After

    No change. The ✅ and ❌ breeding marks were restored by decision on 2026-09-09 and stay. The note only records that ❌ can read as a penalty for a Furb that is resting.

    A recorded decision, so there is no After image.

Exports and instruments

Nothing reads as NOISE at the size people see it.

Exports travel into chats, where they appear 300 px wide. At that size every wordmark is between 5 and 9 px, and identity comes from colour alone. The edition card is the only export that reads as NOISE in a feed, and it does so because of its plate.

Fix first

The Battleship print puts its coordinates in the wrong place and hides hits.

The rulers space seven labels evenly with justify-content:space-around, so “000” sits over column 13 and “149” over column 139. The rulers also cover rows and columns 0 to 2. Sector labels draw last and cover columns 0 to 3 and rows 0 to 2 of every sector, so a hit at 001,001 is invisible. The legend promises thirteen marks, and the raster paints about five tones. The fleet bars show hull remaining while the numbers show hits, so “0/3” draws a full bar.

Fix. Place ruler labels at tile offsets outside the plate. Draw sector labels before shots, or move them to the gutter. Give each legend entry the glyph the raster actually draws. Fill the fleet bars with hits so they match the count.

resources/battleship-battle-ui.html:58-60, 134-135battleship.clj:84-103, 584-592, 733battleship/page.clj:47-49

Now Three crops of the Battleship print. The top ruler reads 000, 025 and 050 at evenly spaced points that miss their columns, and the rulers cover the top-left tiles where two Destroyer hits should show. Fleet bars are full for the Battleship at 0/42 and empty for the sunk Patrol Boat at 8/8. The legend lists thirteen marks, several with lime outline, line and slash glyphs the map never draws.
Production template and raster at HEAD, one synthetic snapshot, 1600 px print at 2x. Map corner, fleet rows and legend.
After The same crops after the fix. Rulers sit in a gutter outside the plate with 000, 030 and 060 on their tiles and sector letters between them, and both Destroyer hits show in the corner. Fleet bars fill with hits, so 8/8 is full and 0/42 is empty. The legend groups all thirteen names into six entries, each with the glyph the map draws.
Scratch template and raster, same snapshot and crop sizes. Rulers and sector labels move to a gutter, the legend groups its names by the raster's glyphs, and bars show hits.

Fix first

A cursed card looks like a normal card in day mode, and a rare finish looks like nothing.

Cursed inverts the print to paper with sage type, which reads as the day palette. The finish is a 13 px mono word at the foot, so at chat size a Holographic card and a plain one are identical. Numbers print zero-padded (“#01”) while every caption writes “#1”.

Fix. Put the finish beside the name at 40 px. Give Cursed a treatment of its own (see the print family proposal). Drop the padding.

resources/furb/card.html:61-79src/bot/modules/furb/card.clj:89, 134

Now Three Furb print cards at 300 px chat width. A plain and a Holographic Cobalt Bandana look the same apart from a tiny lime word at the foot. The cursed Paper Lantern is paper with sage type, which reads as the day palette, and lists its parents as #01 and #02.
Production card template at HEAD, 750 × 1050 at 2x, shown at 300 px on a dark chat ground. Plain, Holographic, Cursed.
After The same three cards. The Holographic card reads Cobalt Bandana, Holographic, with the finish in lime in the 40 px name line. The cursed card is the darkest sage with its name in serif italic, Paper Lantern, Cursed, and its parents read #1 and #2.
Scratch template, same data and scale. Finish beside the name at 40 px, Cursed on the darkest sage with an italic name, numbers unpadded.

Fix soon

A printed Furb card never picks up a template fix.

The saved-card key is furby/cards/<art-stem>.png with no template version, and ensure-card! returns any copy that already exists. Cards came in hours before Verdant replaced Marquee, so any card printed before a template change keeps its old design permanently, including the Cursed fix above.

Fix. Add a template hash and a renderer version to the key, since the Cursed fix also changes card.clj.

src/bot/modules/furb/card.clj:22, 58-59

Now
(def ^:private template (delay (slurp (io/resource "furb/card.html"))))
(def ^:private card-object-prefix "furby/cards/")
;; …
(defn- card-object-key [asset]
  (str card-object-prefix (string/replace (art-file asset) #"\.webp$" ".png")))
;; …
(defn ensure-card!
  ;; …
  [asset]
  (let [url (card-url asset)]
    ;; …
       (let [{:keys [path status]} (image-cache/download-image-result! url)]
         (cond
           path {:path path :url url}
           (not= 404 status) (throw (ex-info "Furb card download unavailable" {}))
           :else
           ;; …
src/bot/modules/furb/card.clj:21-22, 58-59, 155-171, trimmed. The key never changes, so any stored card wins, and only a 404 renders a new one.
After
(def ^:private template (delay (slurp (io/resource "furb/card.html"))))
(def ^:private card-object-prefix "furby/cards/")
;; Bump when render-html changes what it fills in.
(def ^:private renderer-version 2)

(def ^:private template-hash
  "First 12 hex digits of SHA-256 over the renderer version and
   the template, so a template fix prints under a new key."
  (delay
   (let [digest (MessageDigest/getInstance "SHA-256")
         text (str renderer-version "\n" @template)]
     (subs (format "%064x" (BigInteger. 1 (.digest digest (.getBytes text "UTF-8"))))
           0 12))))

(defn- card-object-key [asset]
  (str card-object-prefix @template-hash "/"
       (string/replace (art-file asset) #"\.webp$" ".png")))
Patched excerpt. The key becomes furby/cards/<hash>/<art-stem>.png, so the next request after a template change renders a fresh card.

Fix soon

Charts are the charting library's defaults in new colours.

The title is a centred 18 px serif, about 6 px in a chat bubble. Volume ticks come out in scientific notation (“1.2E6”). Gridlines use the library's default dashes, and up and down volume are 1.74:1 apart. Tick numbers are proportional because no mono TTF is registered, although the guide says prices use the mono.

Fix. Make the title the statement, “AAPL 213.10”, with “+0.45%, Sep 25” in mono beneath, left-aligned. Format volume as “1.2M” and register Fragment Mono as a TTF.

src/bot/core/charts.clj:133-137, 148-149, 183

Now Left 560 px of a 1-day AAPL chart: a small centred serif title reading AAPL, 1 day, dashed guides, proportional price ticks and volume ticks in scientific notation such as 1.2E6 and 8E5
bot.core.charts/render-chart-bytes at HEAD with a synthetic 78-bar session, left 560 px of the 900 px PNG.
After The same chart and crop with the statement AAPL 212.21 in large serif at the left, the change line -0.56%, Sep 25 in mono beneath, mono tick numbers and volume ticks reading 1.2M, 800K and 400K
A patched scratch copy of charts.clj on the same series: the statement at the left with its mono change line, 1.2M volume ticks and Fragment Mono registered as a TTF. Guides and fills are unchanged.

Fix soon

The help page is 66,000 px tall, and what you type is its smallest text.

472 commands sit under 115 module headings shown as raw lowercase slugs (“culvers”), with the command in 13 px mono above a 16 px description. Every module boundary draws two rules 26 px apart. There is no module index. The kit's own instrument specimen already uses the two-column command register that production doesn't.

Fix. Use the kit's register: commands in ink at 15 px, descriptions beside them, and a pipe index of modules under each category.

src/bot/modules/help/page.clj:193, 217-220

Now The top of the Utility and basics category on the help page: slug headings calculator, changelog and culvers, each command in small mono above its description, and two rules at every module boundary
The production help page rendered from the command registry at HEAD, 1440 px, night, cropped from the first category head. The full page is 65,995 px tall.
After The same crop with a pipe index of the category's 25 modules under its head, and each module as a two-column register with the command in 15 px ink mono beside its description
A patched scratch copy of help/page.clj on the same registry: the kit's dl.kv register, a module index under each category and one guide per row. The page falls to 47,469 px.

Fix soon

The kit shows a broken chart and a card that doesn't exist.

The style guide's chart axis path has no fill:none, so a black triangle covers both chart specimens. The kit shows a day print card, which production never renders. Its invariants row still bans grain while the kit carries grain. It has no inspector pane, no lead plate, no Latest and no desk notes, so checklist item 12 fails.

Fix. Generate the print, chart and Battleship specimens from the production renderers, or rename the kit an identity specimen and stop maintaining it as a contract.

docs/design/style-guide.html:473-474, 698, 714, 721, 725

Now The kit's night chart specimen on paper: a solid black triangle fills the plot from the top of the y axis down to the right end of the x axis, with both series drawn across it
docs/design/style-guide.html at 1440 px, the night chart specimen. The day specimen beneath it carries the same triangle.
After The same specimen frame and crop holding the PNG that bot.core.charts renders: an AAPL 1-day price line over up and down volume bars, with the NOISE wordmark at the foot
A scratch copy of the kit with the hand-drawn SVG replaced by the chart bot.core.charts renders at HEAD for a synthetic session. The print and Battleship specimens would follow the same route.

Also

  • The debug reference gives !debug gt reset, which arms a wipe whose confirmation deletes every Gt account and event row, the same weight as a read. “grant” renders raw because it's missing from the kind labels. debug/help_page.clj:21-35
    Now The Actions category of the debug reference on a phone: database backup, gt reset and furb grant each carry a small mute kind label, Backup, Clear and a raw lowercase grant, and every row repeats its scope
    The production debug reference at 390 px, night. Three Actions rows kept and the category blurb hidden so they fit one crop.
    After The same crop with Clear and Grant as reversed mono tags, Backup left plain, the category scope printed once under the count and only differing scopes on the rows
    A patched scratch copy of debug/help_page.clj: mutating kinds as reversed mono tags, Grant added to the labels, scope once per category.
  • The fonts reach pages seven ways: same-origin /fonts/, the pull zone's /fonts/, an absolute CDN URL for the lounge, a relative fonts/ for the kit, a second tracked copy for the reader, base64 in prints (about 267 KB per render) and AWT TTFs with no mono.
    Now
    ;; Fragment Mono, seven ways
    ;; 1 same origin: news-site/site/assets/fonts.css:2
    src:url(/fonts/fragment-mono-400.woff2)
    ;; 2 pull zone: help/page.clj:132, debug/help_page.clj:109
    src:url(/fonts/fragment-mono-400.woff2)
    ;; 3 absolute CDN: frontend/cljs/resources/styles/base.css:2
    src:url(https://n.jzz.bz/fonts/fragment-mono-400.woff2)
    ;; 4 relative: docs/design/style-guide.html:16
    src:url(fonts/fragment-mono-400.woff2)
    ;; 5 second tracked copy: nitter/public/css/themes/noise.css:15
    src: url("/fonts/noise/fragment-mono-400.woff2")
    ;; 6 base64 in prints: core/screenshot.clj:27
    "__FONT_MONO__" "fonts/fragment-mono-400.woff2"
    ;; 7 AWT: core/charts.clj:148-149, four TTFs, no mono
    ["fonts/bricolage-grotesque-400.ttf" ... "fonts/instrument-serif-400.ttf"]
    The Fragment Mono declaration on six routes, and the AWT list that has none, at HEAD, trimmed.
    After
    ;; resources/fonts/manifest.edn
    {:faces
     [{:family "Bricolage Grotesque" :weight "200 800"
       :stretch "75% 100%" :woff2 "bricolage-grotesque-var.woff2"
       :ttf ["bricolage-grotesque-400.ttf" "bricolage-grotesque-600.ttf"
             "bricolage-grotesque-800-condensed.ttf"]}
      {:family "Fragment Mono" :weight 400
       :woff2 "fragment-mono-400.woff2" :ttf "fragment-mono-400.ttf"}
      {:family "Instrument Serif" :weight 400
       :woff2 "instrument-serif-400.woff2" :ttf "instrument-serif-400.ttf"}
      {:family "Instrument Serif" :weight 400 :style "italic"
       :woff2 "instrument-serif-400-italic.woff2"}]
     ;; one path prefix per surface
     :surfaces {:news "/fonts/"        :cdn "/fonts/"
                :lounge "https://n.jzz.bz/fonts/"
                :kit "fonts/"          :reader "/fonts/noise/"
                :print :data-uri       :awt :ttf}}
    
    ;; every @font-face block and the AWT list are generated:
    (fonts/face-css :lounge)  (fonts/awt-files)
    Mock of the manifest the tokens proposal names. The reader copy is built from resources/fonts rather than tracked, and charts register the mono TTF.
  • open-sans-latin.woff2 is no longer used, but it ships in the bot image and the news image and is published on the news site, because resources/fonts is copied whole. Dockerfile:80, Containerfile:34, render_worker.py:24
    Now
    # resources/fonts: a 42,964-byte face nothing loads, and its license
    open-sans-OFL.txt
    open-sans-latin.woff2
    # no reference in src, sidecar, frontend, resources or dev
    
    # Dockerfile:80, the bot image copies the whole tree
    COPY . .
    # sidecar/news-site/Containerfile:34, the news image
    COPY resources/fonts ./resources/fonts
    # sidecar/news-site/render_worker.py:24, published at /fonts/
    ("resources/fonts", "fonts"),
    The unused face and the three copies that carry it, at HEAD.
    After
    git rm resources/fonts/open-sans-latin.woff2 \
           resources/fonts/open-sans-OFL.txt
    
    # resources/fonts
    bricolage-grotesque-400.ttf
    bricolage-grotesque-600.ttf
    bricolage-grotesque-800-condensed.ttf
    bricolage-grotesque-OFL.txt
    bricolage-grotesque-var.woff2
    fragment-mono-400.woff2
    fragment-mono-OFL.txt
    instrument-serif-400-italic.woff2
    instrument-serif-400.ttf
    instrument-serif-400.woff2
    instrument-serif-OFL.txt
    Delete the face and its license. All three copies drop it with no build change.
  • Battleship labels use AWT's logical Monospaced bold, upper-cased: a fifth face, in capitals. battleship.clj:585, 663, 667
    Now Board raster at 3x: sector labels B2 and C2 and the landmark labels PORT HALDEN and KESSEL FIELD, all in AWT's logical Monospaced bold, the landmarks in capitals.
    Board raster at HEAD, synthetic snapshot, 3x nearest-neighbour crop. battleship.clj:585, 663, 667.
    After The same crop with B2 and C2 in Fragment Mono and the landmarks written as named, Port Halden and Kessel Field, in Bricolage Grotesque SemiBold.
    Same crop. Sector labels in Fragment Mono, landmark names as written in Bricolage SemiBold, both registered as TTFs.
  • When the collector fails, Battleship sends the raw raster with no wordmark. battleship.clj:896
    Now Bottom-right corner of the fallback board raster. The chart runs to the image edge with no rule and no wordmark.
    Fallback raster at HEAD, sent when the collector fails. battleship.clj:896. Bottom-right 525 × 320 px at 1x.
    After The same corner of the board with a 52 px foot added below it: a 1 px ink rule and the NOISE wordmark at the bottom right.
    Scratch AWT render, same board region plus the new foot. The fallback send adds an ink rule and the wordmark; archived frames stay bare.
  • The showcase PNG hard-codes night hex values and has no height cap. The cap is latent: a player holds at most 21 Furbs, so a poster stays near 4,000 px. png.cljs:66
    Now
    (defn save-showcase! [furbs title count-label]
      (let [theme (js/getComputedStyle (.-documentElement js/document))
            colors {:ground "#43604d" :plate "#2d4638"
                    :ink "#f2f4ee" :mute "#d3e0d6"}]
        ;; …
                    (set! (.-height canvas)
                      (+ grid-top (reduce + (map #(+ (:height %) gap
                                                     (if (:label %) 48 0))
                                                 rows))
                         100))
        ;; …
                    (download! canvas "furb-showcase.png")
    frontend/cljs/src/noise/png.cljs:64-66, 100-101, 140. Night hex in every theme, and the canvas grows one row per four Furbs.
    After
    ;; Well inside mobile canvas limits: iOS Safari caps canvas
    ;; area at 16,777,216 px, about 10,350 px tall at 1,620 wide.
    (def max-page-height 4096)
    
    (defn- paginate
      "Greedy pages of rows, none taller than limit with its chrome."
      [span rows chrome limit]
      (reduce (fn [pages row]
                (let [page (peek pages)]
                  (if (and (seq page)
                           (< limit (+ chrome (reduce + (map span (conj page row))))))
                    (conj pages [row])
                    (conj (pop pages) (conj page row)))))
              [[]] rows))
    
    (defn save-showcase! [furbs title count-label]
      (let [theme (js/getComputedStyle (.-documentElement js/document))
            token #(str/trim (.getPropertyValue theme (str "--" (name %))))
            colors (into {} (map (juxt identity token))
                         [:ground :plate :ink :mute])]
        ;; … rows measured as before …
        (let [pages (paginate #(+ (:height %) gap (if (:label %) 48 0))
                              rows (+ grid-top 100) max-page-height)
              total (count pages)]
          (js/Promise.all
           (to-array
            (for [[n page] (map-indexed vector pages)]
              ;; the existing drawing, one canvas per page; the count
              ;; line adds "page 2 of 3" when total > 1
              (download! (draw-page! page n total)
                         (if (= 1 total)
                           "furb-showcase.png"
                           (str "furb-showcase-" (inc n) "-of-" total ".png")))))))))
    Patched excerpt. Colours come from the live theme tokens, and a tall poster splits into pages of at most 4,096 px.
  • The reader still has uppercase LIVE, a // crumb and a signal meter hidden by CSS, and the old bird icon. space.nim:29, 50, 58, broadcast.nim:15, 34, 43, general.nim:28-39
    Now
    # sidecar/nitter/src/views/space.nim:29, 50, 58
          if isLive: "LIVE"
                tdiv(class="space-live"): text "LIVE"
                  tdiv(class="space-live"): text "LIVE"
    # broadcast.nim:15, 34, 43 repeat all three
    # sidecar/nitter/src/views/general.nim:25-41
            span(class="mast"):
              text "NOISE"
              span(class="slash"): text "//"
              span(class="slug"): text "Reader"
          ...
              icon "bird", title="Open in X", href=canonical
          ...
          tdiv(class="rule")
          tdiv(class="signal"):
            for _ in 0 ..< 12:
              span(class="seg")
    # public/css/themes/noise.css:264, 281
    .mast .slash { display: none; }
    .inner-nav .signal { display: none; }
    The reader's markup at HEAD: an uppercase state in six places, a crumb and a 12-segment meter that the theme hides, and the bird glyph.
    After
    # sidecar/nitter/src/views/space.nim:29, 50, 58
          if isLive: "Live"
                tdiv(class="space-live"): text "Live"
                  tdiv(class="space-live"): text "Live"
    # broadcast.nim:15, 34, 43 change the same way
    # sidecar/nitter/src/views/general.nim:25-38
            span(class="mast"):
              text "NOISE"
              span(class="slug"): text "Reader"
          ...
              icon "link", title="Open in X", href=canonical
          ...
          tdiv(class="rule")
    # public/css/themes/noise.css
    # .mast .slash and the three .inner-nav .signal rules removed
    Sentence-case state in all six places, the crumb and meter removed from the markup and the theme, and the existing link glyph in place of the bird.
  • The color map and the persona builder use a 900 px measure rather than the shared 1,080. map.html:91
    Now The top of the color map at 1440 px: masthead, deck, stats and the atlas head all sit inside a 900 px column with wide sage margins
    The production color map template with a synthetic 60,000-guess map at 1440 px, night. map.html:91 caps .wrap at 900 px.
    After The same crop with the masthead rule, deck, stats and atlas head spanning the shared 1,080 px measure, matching help and debug
    The same render with the 900 px override removed, so .wrap keeps the shared 1,080 px measure. The persona builder's matching line is not shown.

The system

A thousand lines of rules with no scale under them.

The bans are enforced and the scales aren't there. Every restyle and review round works out the same sizes, gaps and states again by hand, because the contract describes surfaces instead of defining primitives.

Fix first

There are no type steps as tokens, and no spacing scale, layer order or breakpoint set.

Production styles use 23 fixed pixel font sizes and 15 distinct clamp()s (the guide has 2), 34 pixel values if clamp bounds count. About a quarter of all size declarations (78 of 325) fall outside the guide's list, and 14 and 15 px account for half of those, which is a missing “small” role. lounge.css alone uses 17 fixed sizes and 11 clamps. The same styles use 37 spacing values (every integer from 1 to 12 px), 15 z-index values and 13 breakpoints (480, 560, 561, 600, 640, 700, 760, 761, 850, 899, 900, 1000 and 1180). The guide names five.

Fix. Eight type steps, eight space steps, three breakpoints, a named layer order and two durations, as tokens. No raw pixels in component rules.

Regex scan of the production style surfaces listed abovefrontend/cljs/resources/styles/lounge.css (17 fixed sizes, 11 clamps, 10 private)

Now
/* frontend/cljs/resources/styles/lounge.css */
34   .rail .foot{…gap:8px;padding:14px 20px 0;…
       font-size:14px}
41   .bar{position:sticky;…z-index:4;…gap:12px 24px;…}
146  .counter{position:sticky;…z-index:5;…}
274  .toast{position:fixed;…z-index:12;…
       padding:10px 16px;…}
276  .pops{position:fixed;top:14px;right:14px;
       z-index:15;…gap:8px;…}
280  .overlay{position:fixed;…z-index:20;…
       padding:30px;…}
373  .board-head h3{
       font:400 clamp(30px,3vw,38px)/1 var(--serif);…}
385  .seg button{…padding:6px 10px;…font-size:14px;…}
627  .svc-headline{margin-top:12px;
       font:400 clamp(26px,3vw,34px)/1.02 var(--serif);…}
678  @media (max-width:1180px){ … .pane{…z-index:8;…} }
684  @media (min-width:561px){ … }
687  @media (min-width:761px){ … }
691  @media (max-width:760px){ … }
808  @media (max-width:560px){ … }
827  @media (max-width:480px){ … }

/* design-guide.md §4.2 is a table of roles with
   ten sizes and two clamps. There is no spacing
   scale, layer order or duration token. */
Trimmed rules from lounge.css at the lines shown. Every size, gap, layer and breakpoint is a raw value chosen per rule.
After
/* resources/dispatch/themes/noise.css,
   generated from resources/design/tokens.edn */
:root{
  --type-caption:12.5px; --type-mono:13px;
  --type-small:15px; --type-read:17px;
  --type-lead:21px; --type-head:26px;
  --type-statement:clamp(30px,4vw,52px);
  --type-display:clamp(48px,7.2vw,88px);
  --space-1:4px; --space-2:8px; --space-3:12px;
  --space-4:16px; --space-5:24px; --space-6:32px;
  --space-7:48px; --space-8:72px;
  --layer-sticky:1; --layer-drawer:2; --layer-nav:3;
  --layer-veil:4; --layer-pane:5; --layer-toast:6;
  --layer-overlay:7;
  --motion-color:120ms; --motion-sheet:200ms;
}

/* lounge.css, the same rules on the steps */
.rail .foot{…gap:var(--space-2);
  padding:var(--space-3) var(--space-4) 0;…
  font-size:var(--type-small)}
.bar{position:sticky;…z-index:var(--layer-sticky);…
  gap:var(--space-3) var(--space-5);…}
.counter{position:sticky;…z-index:var(--layer-drawer);…}
.toast{position:fixed;…z-index:var(--layer-toast);…
  padding:var(--space-3) var(--space-4);…}
.pops{position:fixed;top:var(--space-3);
  right:var(--space-3);z-index:var(--layer-toast);…
  gap:var(--space-2);…}
.overlay{position:fixed;…z-index:var(--layer-overlay);…
  padding:var(--space-6);…}
.board-head h3{
  font:400 var(--type-statement)/1 var(--serif);…}
.seg button{…padding:var(--space-1) var(--space-3);…
  font-size:var(--type-small);…}
.svc-headline{margin-top:var(--space-3);
  font:400 var(--type-statement)/1.02 var(--serif);…}

/* Custom properties can't sit in a media query,
   so the generator writes the three breakpoints
   in: phone 600, tablet 900, wide 1180. */
@media (max-width:1180px){ …
  .pane{…z-index:var(--layer-pane);…} }
@media (min-width:601px){ … }  /* was 561 */
@media (min-width:901px){ … }  /* was 761 */
@media (max-width:900px){ … }  /* was 760 */
@media (max-width:600px){ … }  /* was 560, 480 */
Eight type steps, eight space steps, seven named layers, two durations and three breakpoints as tokens, from the proposal's tokens.edn plus a mono step. Raw values snap to the nearest step; the mapping is an example.

Fix first

Lime means fifteen things and disappears by day.

The guide gives lime three jobs in §1, eight in §3.1, and then adds the Offers count, In progress, Ready, Yours to give and the print card's Finish. At night, lime against ink is 1.03:1 in luminance. By day, lime text is 1.35:1 against ink. The guide bans colour-only status, and lime is how the system shows status.

Fix. Cap lime at three roles (the primary action, live, and money that is yours), and give every lime mark a cue that isn't colour: weight, a filled shape or a word.

design-guide.md:47, 140-142, 885, 1015-1016noise.css:59

Now A trader's two Furb cards in the lounge room, at night and by day. At night the price, For you and the offer amount are lime; by day the same lines are sage and look like the ink around them.
Lounge preview at 1440 px, night then day, the same trader cards. At night lime is 1.03:1 against ink in luminance, so only hue sets these lines apart. By day --acid-text is 1.35:1 against ink, and they read as plain text.
After The same cards with the price, For you and the offer amount in ink. Only the filled live square beside the trader's name stays lime.
The same preview with the fix injected as CSS. None of these lines is the primary action, live or money that is yours, so they turn ink. The live square stays lime and filled; off, it would be hollow, which this crop doesn't show.

Fix soon

The underline rule contradicts itself.

“An underline goes somewhere, an outline acts on this page.” But the quiet button is underlined and acts, the theme toggle is an underlined button, lounge captions carry a resting hairline and open a pane, and pane rows underline their verb. §2.6 already lists eight classes of exception. Current has four looks and pressed two, across §5.1, §6.4, §6.14 and §6.15.

Fix. Write one table of roles (navigate, act, primary, toggle, current, dismiss, disabled, error), each with exactly one treatment.

design-guide.md:73-77, 332-335, 450-452, 473-476, 652, 833

Now
73-76  Links are visible. … Only the wordmark, masthead
       nav, tabs, minting days, collection rail rows,
       register headlines, piece captions and buttons
       have the exceptions in §5.1.

326-335  … Masthead nav, tabs, minting days and
       collection rail rows have no underline until
       current or hovered; current items use an ink
       underline … except in the Lounge where a resting
       hairline marks the touch target (§6.14) … Buttons
       have no ordinary link underline except
       `.btn.quiet`. One vocabulary holds everywhere: an
       underline goes somewhere, an outline acts on this
       page, one lime fill marks the primary action, a
       pressed toggle fills with ink …

450-452  … an underlined `.theme-toggle` text button
       labelled Day at night and Night by day …

473-476  `.btn.quiet` is underlined text with no border.
       … A pressed toggle (`aria-pressed`, an open
       `aria-expanded` control or the sheet's `.on`)
       fills with `--ink` and sets `--ground` text …

652-654  The current destination fills with `--deep` and
       underlines its word in ink (never its count);
       hover fills alone.

722-723  … hover underlines the verb alone.

743-745  … the current row fills with `--deep`.

833-834  … the current row gets a 2 px `--acid-text` left
       edge, and the pressed census rarity takes the
       same edge.
docs/design/design-guide.md at the lines shown, trimmed. Underlined buttons act. Current has four looks across §5.1, §6.14 and §6.15, and pressed has two, in §6.4 and §6.15.
After
### 5.1 Links and controls

Every control has one role, and each role has one
treatment. Nothing else draws an underline, an
outline or a fill.

Navigate  Goes to a page or opens a record, and
          changes nothing. Ink text with a 1 px --mute
          underline at .16em; hover turns it --ink.
Act       Changes something here, at once or after a
          confirm step. A 1 px --ink outline, 7px 12px;
          pane rows show it on hover.
Primary   The one act the view is for. --acid fill,
          --acid-ink text, one per view.
Toggle    Holds on or off (aria-pressed, an open
          aria-expanded). The act outline; on fills
          --ink with --ground text.
Current   Where you are in a set (aria-current,
          aria-selected). A 2 px --ink underline under
          the word, with no fill and no edge.
Dismiss   Leaves without acting (Never mind). The
          navigate underline, alone on its own line.
          .btn.quiet is kept for this role only.
Disabled  Can't be used now. --mute text, no outline,
          and a reason line in the mute mono.
Error     Something failed. An ink rule, the alert
          icon and a 600-weight first word.

The wordmark stays outside the table: it goes home
and is never underlined. Nav rows, tabs and minting
days carry the navigate underline at rest. Captions
that open a record navigate, so the lounge's --rule
hairline becomes the navigate underline. The theme
control is a toggle. Pane rows start an act, so the
verb is never underlined. The collection rail and
census use current and toggle, not the lime edge.
A role table that replaces the exception lists in §2.6, §5.1, §6.2, §6.4, §6.14 and §6.15. The closing paragraph resolves each conflicting case from the Now side.

Fix soon

Errors have no look.

There is no red by design, and nothing replaces it. A lounge error is .svc-action-error{margin:8px 0}, indistinguishable from a note, and the toast is a --deep box. Disabled rows with their reasons are the one state the lounge already does well.

Fix. Define an error state as an ink rule, the alert icon and a 600-weight first word. Codify disabled as mute plus a reason line.

lounge.css:274, 626

Now Two lounge notices from two pages, split by a pale band. Above, a Sportsbook status note between two hairlines. Below, a Death Roll wager error, also between two hairlines, in the same type.
Lounge preview at 1440 px, night. Above is a status note on Sportsbook, and below is the error after submitting “abc” as a Death Roll wager. Both are .casino-notice (lounge.css:397), and the error adds only .svc-action-error{margin:8px 0} (lounge.css:626).
After The same two notices. The note is unchanged. The error now sits under an ink rule with a circle alert icon, its first word, Wager, in 600 weight, and ends by saying no tokens moved.
The same states with the error look injected. role="alert" notices take an ink rule, Lucide's circle-alert and a 600-weight first word. The copy adds “No tokens moved.” because this is a money path. Notes are unchanged.

Fix soon

The guide has become the lounge's changelog.

§6.14 is 187 lines, 17% of the guide, with product behaviour, copy strings and dated decisions (“stay by decision (2026-09-09)”, “moved before the name 2026-09-25”, “Picking mode is gone”). About 54% of the guide describes specific surfaces. The charter restates the guide in 116 of its 206 lines (its Type, Material rules and Components sections), 29 of them with pixel, hex or token values, which is why it has changed 26 times since Verdant landed.

Fix. Split the docs by job (see the proposals). The guide keeps ten lines on the app-shell pattern.

design-guide.md:641-827noise.md

Now
641-827  ### 6.14 Lounge (187 lines)

734-736  … the one lime primary naming the act and
      its price, and Never mind alone on its own line.
      Picking mode is gone: no hint strip, no page
      jump, no card outlines.

776-779  … on phones a Preferences row for Alerts,
      Day/Night and The collection. No emoji, middle
      dot or em dash carries the mascot; effects read
      “Consolation mascot, +0.25% of every losing
      play back”.

783-785  Counts, names, prices and timers come from
      the real state. … the ✅ and ❌ breeding marks
      stay by decision (2026-09-09).

815-816  … The marks stay by decision (2026-09-09,
      moved before the name 2026-09-25), the one
      colour glyph in the system …
docs/design/design-guide.md §6.14, trimmed: 17% of the guide, holding product behaviour, player copy and dated decisions. noise.md has changed in 26 commits since Verdant landed in 532a6408.
After
## design-guide.md

### 6.14 App shell
An app is one shell: a rail of destinations, a page
bar, one tab row per destination and an inspector pane.
The rail is 220 px; each destination is a row with its
icon before its word and any count in the mono. The
page bar names the destination and holds one live word.
The pane opens beside the page, never as a dialog:
steps push, Back pops, Never mind closes. One drawer
may sit at the foot, folded until asked. At the phone
break the rail becomes a bottom tab bar and the pane a
bottom sheet. The lounge spec is surfaces/lounge.md.

## decisions.md

### 2026-09-09 Breeding marks
The ✅ and ❌ marks stay, the one colour glyph in the
system. Moved before the name on 2026-09-25.

### 2026-09-15 Picking mode removed
Choosers are pane steps. No hint strip, page jump or
card outlines.
The guide keeps ten lines on the pattern. Today's §6.14 moves as it is to surfaces/lounge.md, and its dated calls move to decisions.md. The picking-mode date comes from commit 07360677.

Fix soon

Surfaces have shipped outside the contract, and nothing checks Verdant.

The chain site, added September 24, is missing from the guide's surface table and the charter's file table, and it brings 11 font sizes and 2 clamps of its own. The lounge's “verbatim” token block includes an undocumented --plate-ink. 29 of the 35 compatibility aliases have no consumer. The only design check in the verify suite reads the retired Zilla and MAI templates.

Fix. Generate the tokens (see the proposals), delete the unused aliases, and add the chain site to both docs.

sidecar/chain-site/site/chain.cssfrontend/cljs/resources/styles/base.css:8dev/bot/dev/verify_command_surface.clj:878-886

Now
# design-guide.md:10-19, the surface table
| News site | sidecar/news-site/, … |
| Furb lounge and collection | frontend/cljs/… |
| … eight rows, no chain site …

# noise.md:175-197, the files table
… 21 rows, no sidecar/chain-site/ …

/* frontend/cljs/resources/styles/base.css:8,
   inside the block the guide calls verbatim */
--ink:#f2f4ee; --mute:#d3e0d6; --plate-ink:#f2f4ee;

/* base.css:26-40, 35 aliases. 29 have no consumer
   in any production style surface. */
--bg:var(--ground); --paper:var(--ground);
--text:var(--ink); --body:var(--ink);
--muted:var(--mute); --panel-mute:var(--mute); …
--faint:var(--guide); --bulb-dim:var(--guide); …
--glow:none; --dotglow:none; …

;; dev/bot/dev/verify_command_surface.clj:878-886,
;; the only design check in -M:verify
(when-not zilla-template
  ["The portable Zilla template is missing"])
…
(when-not mai-template
  ["The portable MAI template is missing"])
…
The chain site (sidecar/chain-site/, added in 2946e7f1 on September 24) is in neither table. --plate-ink exists only in the lounge copy. The verify suite checks retired templates.
After
# design-guide.md, surface table
| The chain | `sidecar/chain-site/site/` | Production page | Read-only casino ledger for Lounge seats |

# noise.md, files table
| `sidecar/chain-site/site/{index.html,chain.css}` | The chain: the casino's live ledger |

;; resources/design/tokens.edn
:color {… :plate "#2d4638" :on-plate "#f2f4ee" …}

/* base.css, generated. --plate-ink becomes the
   documented --on-plate, and art-status uses it. */
--ink:#f2f4ee; --mute:#d3e0d6; --on-plate:#f2f4ee;
.art-status{…;color:var(--on-plate);background:var(--plate)}

/* Aliases: none in base.css or noise.css. lounge.css
   swaps its two var(--muted) for var(--mute). The
   reader keeps the five it reads, in its own
   generated file: --paper --body --hairline
   --accent --font-body */
Both tables name the chain site, the lounge's extra token becomes the documented on-plate pair, and the alias block goes. The Zilla and MAI checks go with the doc split.

Also

  • One word, many meanings: “register” has four, “plate” three, “instrument” three (the lockup, the news instruments and the instrument pages), and the hand is both a “sheet” and a “drawer”. The contract still says “bulb” twelve times.
    Now
    register
      78   Stories form a register.
      719  the Sell, Keep, Mascot and Let go registers
      646  opens the casino register in a new tab
      240  Register each with font-display:swap
    plate
      85   Pieces are plates and captions.
      934  A plate is a dithered painting
      16   Fire-control document and board plate
    instrument
      41   Instrument Serif carries the single display …
      461  6.3 Instrument lockup (NOISE + slug)
      590  6.10 Instruments  |  852  6.16 Instrument pages
    the hand
      137  --well is the hand sheet
      680  Your hand is a drawer
    bulb, twelve times
      68   … gradients or bulb rows.
      162  --bulb, --meter … 1010 Bulb spans
    design-guide.md at the lines shown.
    After
    ## Vocabulary
    
    Register    The news story table: Desk, Story,
                Sources, Updated. Nothing else.
    Action list A pane's Sell, Keep, Mascot or
                Let go rows.
    The chain   The casino's live ledger.
    Load        What @font-face does to a face.
    Plate       A dithered painting (§6.22).
    Art square  The --plate square behind Furb art.
    Board       Battleship's grid.
    Lockup      The wordmark and its page slug.
    Instruments Signal read and Markets.
    Tool pages  Help, debug, color map and the
                persona builder.
    Drawer      Your hand, at the foot of the room.
    Sheet       The pane as a phone bottom sheet.
    
    Retired words (bulb, slug plate, acrylic)
    leave the contract. Class and token names
    such as .art-plate and --plate stay.
    A glossary in the guide, one meaning per word. Doc wording only; no code names change.
  • The grain sits “behind everything”, except that the lounge, collection, help, debug, color map and reader don't have it yet. Rules with waivers belong in a known-deviations list with an owner.
    Now
    design-guide.md:207-208
    The ground is not flat: a soft grey grain
    sits behind every element …
    
    design-guide.md:222-223
    Adopted by the news site, the kit and the
    landing; other surfaces take it on their
    next pass.
    
    design-guide.md:347-348
    The lounge's places row, room tabs and footer
    preferences use trailing pipes. Collection
    still carries the older leading-pipe form …
    
    noise.md:99-100
    An 8% grey grain sits on the ground behind
    everything …
    The waivers sit mid-paragraph. The chain site has the grain but isn't listed.
    After
    ## Known deviations
    
    Each row is a rule a surface doesn't meet
    yet. A row closes when the file changes.
    
    §3.3 grain
      Lounge, collection
        frontend/cljs/resources/styles/base.css
      Help, debug
        help/page.clj, debug/help_page.clj
      Color map
        resources/color-guesser/map.html
      Reader
        sidecar/nitter/public/css/themes/noise.css
    §5.1 trailing pipes
      Collection minting days
        gallery.css:82
      Collection rail rows and Census, below 900 px
        gallery.css:127
    
    §3.3 then reads: “The grain sits behind
    every page except those listed in Known
    deviations.”
    One list in the guide. Each owner is the file that has to change.
  • The token block is hand-copied into 14 files, 40 times counting both themes (13 and 39 without the edition card's partial day block). Help and debug carry an extra day block, keyed to data-theme, that they can never apply, because they have no script and never set it. help/page.clj:159
    Now
    ;; src/bot/modules/help/page.clj:135-174
    ":root{\n"
    "  color-scheme:dark;\n"
    "  --ground:#43604d; --deep:#2d4638; …\n"
    "}\n"
    ":root{\n"
    "  --bg:var(--ground);--paper:var(--ground); …\n"
    "}\n"
    ;; 159: the CSP at 125 allows no script,
    ;; so nothing ever sets data-theme
    ":root[data-theme=\"light\"]{\n"
    "  --ground:#f2f4ee; --deep:#e3e8df; …\n"
    "}\n"
    "@media(prefers-color-scheme:light){:root:not([data-theme=\"dark\"]){\n"
    "  --ground:#f2f4ee; --deep:#e3e8df; …\n"
    "}}\n"
    One of 14 files with a hand-kept copy; debug/help_page.clj:112-151 is the same.
    After
    ;; src/bot/modules/help/page.clj
    "<style>\n"
    … ;; @font-face lines as today
    (tokens/css {:day :system})
    "*{box-sizing:border-box}\n" …
    
    ;; which renders, from
    ;; resources/design/tokens.edn:
    :root{color-scheme:dark;
      --ground:#43604d; --deep:#2d4638; …}
    @media(prefers-color-scheme:light){:root{
      color-scheme:light;
      --ground:#f2f4ee; --deep:#e3e8df; …}}
    Mock of the proposed generator call; bot.core.tokens doesn't exist yet. Script-free pages get only the system-preference day block, with no aliases.

Proposals

Build the picture out of the name.

None of these asks for a new palette or a new typeface. The first two take material NOISE already owns and make it the system; the rest fix the shape of each surface. This page is set in both: the wordmark's cut for every heading, and dither for the one picture it needs.

Every surface

Make the dither the house material.

A dither is noise shaped into an image, in exactly two inks. NOISE already owns fifty of them. Make it the one texture: replace the feTurbulence grain with a sparse Atkinson tile in the page's own inks, draw Signal read as density rather than bar height, give Static a dithered edge, and sign every export with a band of noise that resolves into the wordmark. It stays still. The animated static and glyph scramble stay retired; this is a print texture, not an effect.

Reuses scripts/plates/dither.py (Atkinson, two inks).The plate tokens don't change: paper #f2f4ee on plate #2d4638.

Ground texture
Now: fractal noise, 8%
Proposed: a two-ink dither, 3%
The same sage. On the left, the texture anyone gets from an SVG filter. On the right, the plates' two inks as a dithered tone at very low density.
Signal read as density

Now

The current meter: seven small bars of 3 px height steps, the last in lime.

Proposed

The seven levels as tone rather than 3 px height steps. Today is framed, not coloured, so the state survives day mode and forced colours.
Static with an edge of interference

Now

The current Static strip: a thin deep band with a small lime Static word and a 16 px headline.

Proposed

StaticBoth delegations sign the ceasefire accord in Doha

10:40 EDT, 6 outlets. Read the bulletin

The chip is a lime fill in both themes. The dithered edge is still; it marks the band as a break in the signal.
The export signature

Now

A current chart at 300 px: centred serif title, and a small NOISE at the bottom right over an ink rule.

Proposed

AAPL 213.10

+0.45%, Sep 25, 16:00 ET

NOISE
An ink rule, a band of noise resolving into the wordmark, at 4% or more of the image width. At 300 px it still reads as a mark.

Every surface

Let the wordmark set the headlines.

Instrument Serif is the display face 2025's templates reach for first. The wordmark already has a voice: Bricolage at 800 and 75% width. Set display and statements in that cut and drop the serif from display, so the type identity comes from the wordmark. That is a subtraction, not a new face. A condensed heavy grotesque is also the wire-desk headline tradition, which suits editions assembled from wire copy. In the lead column below it sets the headline in five lines where the serif takes six, and the blurb reaches the first screen. Keep the serif italic for exactly one job, the voice of cursed things.

Four font files become three: Bricolage variable, Fragment Mono and Instrument Serif italic.The AWT wordmark TTF is already this cut: bricolage-grotesque-800-condensed.ttf.

Now

World, updated 2h, 5 sources.

Ceasefire talks produce a draft accord as both sides agree to a ninety-day timetable

Instrument Serif at the production clamp, in the 600 px lead column.
Proposed

World, 08:00, 5 outlets.

Ceasefire talks produce a draft accord as both sides agree to a ninety-day timetable

Negotiators worked through the night on the text, officials said, with the remaining gaps narrowed to verification and the timetable.

Bricolage 800 at 75%, the wordmark's cut, sized to the column. The blurb fits on the first screen.

News

Make the front page a log.

NOISE publishes on a clock: 8:00, 18:00, updates between, and Static when outlets converge. Make the page time-first, newest on top. A live Static takes the top entry at statement size on the deep band, updates follow, and the edition sits beneath with its lead one step down. Relative times go, and absolute times give the page its left edge. This fixes the backwards hierarchy by structure rather than by exception.

Fixes the Static, Latest and stale-times findings together.The register below it keeps its disclosure structure, with Time, Story and Sources as its columns.

Now The current front page at 1440 px: a thin Static band above a six-line serif lead and its plate.
The production template today, 1440 px, night. The breaking story is the 16 px line.
Proposed
NOISEMorning NOISE, Sep 26
StaticBoth delegations sign the ceasefire accord in Doha

6 outlets: Reuters, AP, BBC, Al Jazeera, NYT and FT. Read the bulletin

Latest, 2 stories

Delia makes landfall near Morgan City as a category 2 storm

Oil extends losses after the accord is signed

Morning NOISE, 20 stories

Ceasefire talks produce a draft accord as both sides agree to a ninety-day timetable

World, 5 outlets.

Newest first: the bulletin, then updates, then the edition with its lead one step down.

News and exports

Let the plates be the day's weather.

Re-sort the fifty plates by mood instead of desk: still water and mown meadow for calm and steady days, valley mist and estuary tide for busy and heavy ones, storm clearing and sea stacks for tense through chaotic. Each edition gets one plate, chosen from the pool for its Signal read. The meter becomes the plate's caption, the share card inherits it, and the register openers drop their plates, which saves about 800 px of scroll and every mismatch between story and art.

plates.clj keeps its hash for stability, keyed on the release id within the level's pool.

Now A current desk opener: a storm-light plate beside a budget headline, captioned Illustration: NOISE.
Today a plate sits on every desk opener, chosen by hashing the story id. The fixture assigns this one by hand, but the hash has no more reason to match the story.

Proposed

Dithered plate of a still lake with a wooded island.

Signal read, calm. Island lake. Illustration: NOISE.

Dithered plate of a valley filled with mist.

Signal read, heavy. Valley mist. Illustration: NOISE.

Dithered plate of a sunbeam breaking through storm clouds.

Signal read, chaotic. Storm clearing. Illustration: NOISE.

Chat

Give chat a type system of six styles and one small vocabulary.

In chat, NOISE controls bold, italic, monospace, spoiler, strikethrough and emoji, and nothing else. Give each a job: the first line is a bold slug, sentences are plain, records (amounts in receipts, ids, grids) are mono, narration is italic, reveals are spoilers. Define six message shapes (menu, receipt, result, rejection, wait and news), each built by one helper that emits its text and styles together. Close the emoji vocabulary so each glyph means one thing. Then NOISE sounds like one entity in a busy group chat.

New helpers in one namespace, used by every module.Needs the operation.clj fix first, or receipts lose their styles.

Now

NOISE

🎰 Fruit 5x5 SLOTS 🎰 ``` ⭐ 👑 🍋 🍋 🍊 🍒 🍋 🔔 ⭐ 🍒 🍋 🍋 🍋 🍋 🍋 🔔 🍊 👑 🍒 ⭐ 🍊 ⭐ 🍒 🔔 👑 ``` 🌟 MEGA WIN! 5 lines hit! 🌟 Line 3: 5x 🍋 (x69 = 2156) Line 13: 3x 🍋 (x11 = 343) Line 17: 3x 🍋 (x11 = 343) Line 29: 3x 🍋 (x11 = 343) Line 31: 3x 🍋 (x11 = 343) 🎯 Bet: 1,000 · Jackpot contribution: 50 · Total cost: 1,050 → Won: 3,528 (Net: +2,478) 💰 Balance: 1,234,567 tokens 👑 Progressive Jackpot: 48,210,733 tokens 🧾 Ledger confirmed: 3fa9c2b1
Weather lookup failed 🌡️ Weather lookup failed. Check logs.
#7F · ✅ · C#1234 · Cosmic Lava Moth · house 12,000,000 tokens
⏳ Vaccine slot 2 can change again at 4am Eastern, in 1187m 15s.
From the real slots engine for this grid (the finding's pair shows a loss too), weather.clj, furb.clj and cache.clj. The fences arrive as literal backticks on Signal.
Proposed

NOISE

🎰 Fruit 5x5, 1,000 tokens⭐ 👑 🍋 🍋 🍊 🍒 🍋 🔔 ⭐ 🍒 🍋 🍋 🍋 🍋 🍋 🔔 🍊 👑 🍒 ⭐ 🍊 ⭐ 🍒 🔔 👑Line 3: five 🍋 at 69x, paid 2,156. Line 13: three 🍋 at 11x, paid 343. Line 17: three 🍋 at 11x, paid 343. Line 29: three 🍋 at 11x, paid 343. Line 31: three 🍋 at 11x, paid 343. Jackpot contribution: 50 tokens Progressive jackpot: 48,210,733 tokens ✅ Mega win, 5 lines. Paid 3,528 (net +2,478). 💰 Balance: 1,234,567 tokens 🧾 Ledger confirmed: 3fa9c2b1
⚠️ Weather for 10001 didn't load. The forecast service didn't answer. Send !w 10001 again in a minute.
7F Cosmic Lava Moth, C#1234 Ready to breed. The house pays 12M.
⏳ Vaccine slot 2 unlocks at 4:00 ET, in 19h 47m.
The same four messages in the receipt, rejection, result and wait shapes. Mocked in a neutral client, because chat renders in the reader's system font.
Chat type roles
RoleSignal, DiscordJob
SlugBold, **First line only: emoji and subject. One per message.
BodyPlainSentences.
RecordMono, ` or a blockReceipt amounts, ids, grids, tickers.
AsideItalic, *Narration and flavour, kept apart from facts.
HiddenSpoiler, ||Hints, answers and reveals.
StruckStrikethrough, ~~A superseded value, such as the old price.
Chat emoji vocabulary
JobEmoji, one meaning each
Rejection⚠️ bad input, ⛔ not allowed, 💸 short of money, ⏳ wait, 🤷 not found, 📭 empty
Outcome✅ win or done, ❌ loss or failed, ➖ push
Money💰 your balance only, 🪙 a payout, 🧾 a receipt, 🏦 a lender, 💎 Goatinum
News☀️ Morning, 🌆 Evening, 🗞️ Sunday, 📡 Static and nothing else, 📈 tracked stocks
Progress👀 then ⏳ then ✅ or ❌, on both transports
Retired💀 as an error, stacked emoji, trailing emoji, meme runs

Chat

Let desks sign the replies, and keep NOISE as the newsroom.

Today the bot calls itself “I” in 42 files and “Bot” in six busy messages. Give each part of the town a desk with one emoji and one voice sheet: the Cashier (terse, exact, mono), the Floor (one line of hype allowed), the Registry for Furbs (dry and archival), the Town (italic narration) and the Front Desk (help and errors, plain and kind). NOISE itself speaks only as the newsroom. The emoji vocabulary becomes an index of desks, and the town canon (the Promotions Office, Meadowhorn Trust, the Cloven Hoof) gets a voice it already half has.

lotto/ambient.clj:92work.clj:808lead.clj:1143osrsbond.clj:41deferred.clj:15expert.clj:426

Now

NOISE

OSRS bond lookup queued. I’ll post the result shortly.
Bot workers are busy. Try again shortly.
LLM workers are busy. Try again shortly.
The floor supervisor is busy right now. Try your shift again shortly.
The spirits are busy right now. Try the seance again shortly.
Real strings from osrsbond.clj:41, deferred.clj:15, expert.clj:426, work.clj:808 and lead.clj:1143. Three speakers: “I”, “Bot” and the town.
Proposed

NOISE

📚 Front desk ⏳ Checking the OSRS bond price…
📚 Front desk ⏳ Too many requests right now. Send !osrsbond again in a minute.
📚 Front desk ⏳ The experts are all answering someone. Ask again in a minute.
💼 The floor ⏳ The floor supervisor is busy right now. Try your shift again in a minute.
🏘️ Town The spirits are busy right now. Try the seance again in a minute.
The same five moments, each signed by one desk with one voice. Commands and outcomes are unchanged; the wording is illustrative.

Lounge

Give breeding a bench.

Breed, implant and mutate confirmations become a small lineage: the parents as plates at half the pane's width, the child as an empty plate with its odds typed beside it, one lime button naming the price, and Never mind alone beneath. After the mint the empty plate fills with the art. It uses only the plate, the display cut, mono and one lime.

actions.cljs:92-96 split into odds, rest and vaccine lines.

Now The current breeding confirmation pane with small thumbnails and the odds folded away.
The current pane.
Proposed

#1 Cobalt Bandana
Ready

#2 Paper Lantern
Holographic, ready

33% Holographic

Otherwise the finish is rolled fresh.

Both parents rest until 4:00 ET.

Breed for 200,000,000 tokens Keep them as they are
The odds are the largest type in the step. The rest rule is one line.

Lounge

Put the casino on felt.

Replace the 240 px service list with a lobby wall of tables on the same grid as the Furb walls. Each game is a plate square showing its live shape (two cards, a reel window, the last number, three dice, a scratch grid, a firing grid), named beneath in the display cut, with one mono line of stakes. Choosing a table opens its board full width. The scratch coating is the dither.

Battleship already sits on the plate; this extends that to every game.

Now The current Slots board: a machine dropdown, a segmented control, a wager field and a table of machine settings.
Games today: a list of services beside a board of form fields.

Proposed

A♠7♥

Blackjack

1,000 to 50,000,000

7♣★◆◆◆★7♣

Slots

15 machines, 87 spins today

17Black, odd, low

Roulette

Last spin 2 minutes ago

Sic Bo

Big pays 1:1

50050050

Scratch

Four tiers from 1,000

Battleship

49 fired, 1 sunk

Exports

One print family, sized for chat.

Every export uses one of three frames on paper, since exports travel into chats: a card at 1200 × 630 (editions, Static, charts), a square at 1080 × 1080 (Furb prints, Battleship crops) and a tall frame at 1080 × 1350 (showcases, the full fire-control sheet). Each has a deep-sage window for its picture, a statement in the display cut, one mono record line and the signature at 4% or more of the width. The finish sits beside the name. A cursed Furb is the one dark print in a paper feed, with its name in the serif italic.

Shown here at chat size: 360 px and 216 px wide.Template hash in every saved key, so a template fix reaches old prints.

Now Every current export at 300 and 120 px: chart, edition card, two Furb cards and the Battleship print.
Every export today at 300 px and 120 px. Two grounds, four signature sizes, and a cursed card that looks like day mode.

Proposed

Morning NOISE, Sep 26

Ceasefire talks produce a draft accord

NOISE

Card, edition

#1035 Cobalt Bandana, Holographic

Generated mint, Sep 7

NOISE

Square, Furb print

#1036 Paper Lantern, cursed

Bred from #1 and #2

NOISE

Square, cursed

Showcase, page 1 of 3

12 Furbs, Preview collector

NOISE

Tall, showcase

The system

Make the tokens data, and generate every copy.

One tokens.edn holds colour roles with their on-colour pairs, eight type steps, eight space steps, layers, two durations and three breakpoints. It generates noise.css, the inline blocks in help, debug and color map at render time, a __TOKENS__ placeholder in the print templates (like __FONT_SANS__), the chart colours, the showcase map, the reader's variables and the guide's token tables. The thirteen hand-kept copies and the “verbatim” rule go away. This is enforcement by construction. A read-only token census in the verify suite could come later, if you ask for one.

Also collapses the font routes to one manifest with a path prefix per surface.

Now
;; the night block, copied by hand: 18 copies in 13 files
resources/dispatch/themes/noise.css
frontend/cljs/resources/styles/base.css   ;; adds --plate-ink
src/bot/modules/help/page.clj
src/bot/modules/debug/help_page.clj
resources/color-guesser/map.html
resources/furb/card.html
resources/battleship-battle-ui.html
sidecar/nitter/public/css/themes/noise.css
docs/site/index.html
docs/design/splash.html
docs/design/manual.html
docs/design/style-guide.html
docs/design/persona-builder.html
;; plus hard-coded hex in charts.clj:133-137,
;; png.cljs:66 and the battleship raster
Found by searching for --ground:#43604d. Nothing checks that the copies agree.
Proposed
;; resources/design/tokens.edn
{:color {:ground {:night "#43604d" :day "#f2f4ee"} :on-ground {:night "#f2f4ee" :day "#2d4638"}
         :plate  "#2d4638"                          :on-plate  "#f2f4ee"
         :mute   {:night "#d3e0d6" :day "#516a59"}  :rule {:alpha .30} :guide {:alpha .14}
         :lime   "#dcff4f"                          :on-lime   "#1f2f25"}
 :type  {:caption 12.5 :small 15 :read 17 :lead 21 :head 26 :statement [30 52] :display [48 88]}
 :space [4 8 12 16 24 32 48 72]
 :break {:phone 600 :tablet 900 :wide 1180}
 :layer [:page :drawer :pane :veil :toast]
 :motion {:color 120 :sheet 200}}
One source. Every copy on the left is generated from it at build or render time.

The system

Split the docs by job, with budgets.

The charter says what NOISE is and holds no pixel values. The contract holds tokens, roles, shared components, voice (including chat) and the checklist. Each surface gets its own spec, and dated decisions get a log of their own, so the guide stops being rewritten every time the lounge changes.

Moving the historical records means editing verify_command_surface.clj:827-828, and that check should go anyway.

Now
File, sectionLines
noise.md206, of which 116 restate the guide's values
design-guide.md1,071
§1 to §5, aesthetic to layout400
§6 Components514
§6.14 Lounge alone187
§6.8 Register rows49
§7 to §10, theming to checklist122
Two files, 1,277 lines. 37 guide commits and 26 charter commits since Verdant landed.
Proposed
FileHoldsBudget
noise.mdThe idea from the name, the three locked elements, the vocabulary rule, the owned assets, ten invariants with no pixels70 lines
design-guide.mdToken tables, type roles, the role table for links and controls, shared components, states, exports, theming, voice for web and chat, checklist350 lines
surfaces/news.mdFront page, register, editions, Static, archive, card160 lines
surfaces/lounge.mdToday's §6.14, moved as it is200 lines
surfaces/prints.md, instruments.md, collection.mdPrints and charts; help, debug, color map, reader, chain; the collection170 lines
decisions.mdDated decisions: the breeding marks, the colophon's scope, no red or green, Lucide60 lines
Seven files, about 1,010 lines, and the guide stops changing when a surface does.

Not asked for

  • A new palette. Sage and paper stay, and so does the plate.
  • A new typeface. The proposal removes one from display.
  • Motion. The retired static and scramble stay retired.
  • Any filter on Furb art. It stays in true colour on its plate.
  • Changes to commands, odds, payouts or outcome names.
  • Re-deciding the ✅ and ❌ marks, the colophon's scope or the Lounge's chain link.

Order of work

Fix what misleads players first. Repaint last, if at all.

  1. Stop the page and the chat from misleading people

    Absolute times on news, the hours unit in durations, the Battleship rulers and hidden hits, “Check logs.”, “tokens tokens”, “Noise Help”, the kit's black triangle, the lounge wordmark underline and the template hash in the card key.

    Small, independent fixes, each checkable against a render.

  2. Chat type system

    Keep styles through the ledger path, then the receipt, rejection and wait helpers, the emoji vocabulary, one duration formatter and the casino header without the dash.

    Touches the most players per line changed.

  3. Scales as tokens

    Write tokens.edn, generate the copies, and move every raw size and gap onto the steps.

    Makes every later step cheaper.

  4. News hierarchy

    Static and Latest above the lead, the lead sized by its column, the register as Time, Story and Sources, then the log.

    Deploys with the news image.

  5. Lounge shapes

    Real boards for Slots and Roulette, the breeding bench, destructive confirmations with their plates and prices, and sticky phone actions.

    Build-gated by shadow-cljs.

  6. One print family

    The three frames, the signature at 4%, the finish beside the name, Cursed dark, and chart titles as statements.

    Old cards regenerate once the key carries the template hash.

  7. Dither and the wordmark's display cut

    Only after the above, and through the usual one-page specimen for approval.

    The only step that changes the look.

  8. Split the docs

    Do it last, so the new guide describes the system as it ends up, not the system as it was.

    About 1,010 lines across seven files, down from 1,277 in two.