Latest, 2 stories
Delia makes landfall near Morgan City as a category 2 storm
Oil extends losses after the accord is signed
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.
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:
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.
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.
--acide0e656a4)History
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.
IBM Plex Sans. A broadsheet picked up by a weak antenna, with animated TV static and scrambling glyphs.
Seven themes with no shared system. Zune went to production.
IBM Plex Sans, with violet as the only accent.
Newsreader, IBM Plex Sans and IBM Plex Mono. Static became an ink inversion.
Bungee Shade, Bungee and Barlow. Neon, bulbs and ten palettes. It lasted two days.
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
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 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
NOISE
NOISE
200 (net +100).
💰 Balance: 12,450 tokens
🧾 Ledger confirmed: 3fa9c2b1(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
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
NOISE
!help footer (help.clj:23), rendered from the real functions. Balance and house price are example values.NOISE
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
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)
NOISE
!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.NOISE
Fix first
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
NOISE
NOISE
⭐ 👑 🍋 🍋 🍊
🍒 🍋 🔔 ⭐ 🍒
🍋 🍋 🍋 🍋 🍋
🔔 🍊 👑 🍒 ⭐
🍊 ⭐ 🍒 🔔 👑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⭐ 👑 🍋 🍇 🍊
🍒 🍇 🔔 ⭐ 🍒
🍋 🍊 💎 🍇 🔔
🔔 🍊 👑 🍒 ⭐
🍊 ⭐ 🍒 🔔 👑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:code-block grid, one outcome line from the casino helpers and mono amounts. Scratch renderer; mocked in a neutral client.Fix soon
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
(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 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.
Fix soon
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
NOISE
NOISE
Fix soon
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
NOISE
;; 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
…
NOISE
;; 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)
Fix soon
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
!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
!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…
Also
furb.clj:1584, 2627-2645NOISE
NOISE
!furb grind A3 to confirm.!furb grind A3 confirms at the quote current when it runs. This changes a player-facing control.!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-158Furbs 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. …
!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.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. …
" tokens" after the call is dropped. The help card, reference page and !help <question> all read this one string.help/docs.clj:21-57NOISE
!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.NOISE
!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.** reaches Signal from a day-job ability, and !bj chart is fenced. dayjob_ability.clj:698, blackjack.clj:1115NOISE
star-chart! (dayjob_ability.clj:698) and !bj chart (blackjack.clj:1115), trimmed. The game is an example value.NOISE
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]: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.casino/text.clj:74, 85💸 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.💸 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.
!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-213NOISE
format-live-quote (stock.clj:77-90) called with a synthetic quote. Prices are example values.NOISE
format-market-value and format-market-change (dispatch/markets.clj:200-213). Same quote, rendered from a patched scratch copy.vip.clj:46-48, bank.clj:114, casino.clj:44Use !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.
!vip help-docs prose (vip.clj:46-58) from a REPL, trimmed. Bold marks where the names mix.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.
blackjack.clj:603NOISE
format-active-response on a synthetic Signal hand (blackjack.clj:597-603, 716). The spoilered strategy hint below it is not shown.NOISE
News
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
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
Fix first
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
freshness-label text. The last row comes from Friday's Evening edition and still says Updated 2h.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 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
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
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
7.5rem minmax(0,1fr) 7.5rem 8.5rem.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
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
Fix soon
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
Fix soon
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
Also
index.html:37, model.py:742Added since the Morning NOISE at 8:00 EDT. Updated Sat, 11:30 AM EDT.
header-time-formatter (common.clj:326), the second eastern_time (model.py:742).Added since the Morning NOISE at 08:00 EDT. Updated 11:30 EDT.
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.publication.html:4, story.html:41archive.html:11body{min-height:100vh}. base.css:8, 1225750fb5); update §6.11 to match. sports-board.html:15, design-guide.md:619-621sports-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
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
base.css:272base.html:48{% 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
{% 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
Lounge
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
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
Fix first
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
Fix first
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
Fix soon
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
Fix soon
“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
Fix soon
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
Also
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.mast a{text-decoration:none;color:inherit} injected.lounge.cljs:183-186, furb_ui.cljs:428ui.cljs:6-14furb_ui.cljs:109, 116, 297, 345, 362h1 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
furb_ui.cljs:109, 116, 297, 345.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
furb_ui.cljs:263-279furb_ui.cljs:408 counts with fmt/hand-count, which skips ascended Furbs.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.
Exports and instruments
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 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
Fix first
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
Fix soon
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
(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 ;; …
(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")))
Fix soon
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
Fix soon
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
Fix soon
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
Also
!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/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.;; 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"]
;; 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)
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# 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"),
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
battleship.clj:585, 663, 667battleship.clj:896png.cljs:66(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")
;; 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")))))))))
// 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# 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; }
# 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
map.html:91The system
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
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)
/* 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. */
/* 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 */
Fix first
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
Fix soon
“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
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.
### 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.
Fix soon
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
Fix soon
§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
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 …
## 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.
Fix soon
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
# 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"]) …
# 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 */
Also
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
## 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.
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 …
## 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.”
data-theme, that they can never apply, because they have no script and never set it. help/page.clj:159;; 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"
;; 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; …}}
Proposals
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
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.
Now
Proposed
Now
Proposed
Now
Proposed
AAPL 213.10
+0.45%, Sep 25, 16:00 ET
Every surface
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.
World, updated 2h, 5 sources.
Ceasefire talks produce a draft accord as both sides agree to a ninety-day timetable
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.
News
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.
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
News and exports
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.
Proposed
Signal read, calm. Island lake. Illustration: NOISE.
Signal read, heavy. Valley mist. Illustration: NOISE.
Signal read, chaotic. Storm clearing. Illustration: NOISE.
Chat
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.
NOISE
NOISE
⭐ 👑 🍋 🍋 🍊
🍒 🍋 🔔 ⭐ 🍒
🍋 🍋 🍋 🍋 🍋
🔔 🍊 👑 🍒 ⭐
🍊 ⭐ 🍒 🔔 👑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: 3fa9c2b17F Cosmic Lava Moth, C#1234
Ready to breed. The house pays 12M.| Role | Signal, Discord | Job |
|---|---|---|
| Slug | Bold, ** | First line only: emoji and subject. One per message. |
| Body | Plain | Sentences. |
| Record | Mono, ` or a block | Receipt amounts, ids, grids, tickers. |
| Aside | Italic, * | Narration and flavour, kept apart from facts. |
| Hidden | Spoiler, || | Hints, answers and reveals. |
| Struck | Strikethrough, ~~ | A superseded value, such as the old price. |
| Job | Emoji, 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
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
NOISE
NOISE
Lounge
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.
#1 Cobalt Bandana
Ready
#2 Paper Lantern
Holographic, ready
33% Holographic
Otherwise the finish is rolled fresh.
Both parents rest until 4:00 ET.
Lounge
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.
Proposed
1,000 to 50,000,000
15 machines, 87 spins today
Last spin 2 minutes ago
Big pays 1:1
Four tiers from 1,000
49 fired, 1 sunk
Exports
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.
Proposed
Morning NOISE, Sep 26
Ceasefire talks produce a draft accord
NOISE
#1035 Cobalt Bandana, Holographic
Generated mint, Sep 7
NOISE
#1036 Paper Lantern, cursed
Bred from #1 and #2
NOISE
Showcase, page 1 of 3
12 Furbs, Preview collector
NOISE
The system
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.
;; 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
--ground:#43604d. Nothing checks that the copies agree.;; 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}}
The system
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.
| File, section | Lines |
|---|---|
| noise.md | 206, of which 116 restate the guide's values |
| design-guide.md | 1,071 |
| §1 to §5, aesthetic to layout | 400 |
| §6 Components | 514 |
| §6.14 Lounge alone | 187 |
| §6.8 Register rows | 49 |
| §7 to §10, theming to checklist | 122 |
| File | Holds | Budget |
|---|---|---|
| noise.md | The idea from the name, the three locked elements, the vocabulary rule, the owned assets, ten invariants with no pixels | 70 lines |
| design-guide.md | Token tables, type roles, the role table for links and controls, shared components, states, exports, theming, voice for web and chat, checklist | 350 lines |
| surfaces/news.md | Front page, register, editions, Static, archive, card | 160 lines |
| surfaces/lounge.md | Today's §6.14, moved as it is | 200 lines |
| surfaces/prints.md, instruments.md, collection.md | Prints and charts; help, debug, color map, reader, chain; the collection | 170 lines |
| decisions.md | Dated decisions: the breeding marks, the colophon's scope, no red or green, Lucide | 60 lines |
Not asked for
Order of work
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.
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.
Write tokens.edn, generate the copies, and move every raw size and gap onto the steps.
Makes every later step cheaper.
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.
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.
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.
Only after the above, and through the usual one-page specimen for approval.
The only step that changes the look.
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.