8.0 KiB
Indifferent Broccoli brand application — changelog 2026-05-01
Frontend-only. No backend edits. No DB migrations. No spec changes. No git commits — working tree dirty for review.
Layered on top of the in-flight a11y refactor (/opt/sortof/docs/a11y-audit-2026-05-01.md + /opt/sortof/docs/a11y-changes-2026-05-01.md). Where brand and a11y collide, a11y won — see "Deferred to a11y" below.
Files touched
/opt/sortof/frontend/index.html— brand tokens, font-link expansion, favicon, panel shadow, sort-btn rebrand, wordmark/header CSS, footer mark CSS/opt/sortof/frontend/sortof-app.jsx— Header swap (broccoli image + IB link), Footer rewrite ((:|) glyph + IB link), voice copy on EmptyRight + StatusStrip terminal text/opt/sortof/frontend/img/broccoli_shadow_square.png— new file, 19075 bytes, fetched fromhttps://indifferentbroccoli.com/img/broccoli_shadow_square.png
Brand tokens added (CSS vars)
--brand-primary: #5EFF0D; /* IB anchor green */
--brand-primary-rgb: 94 255 13;
--brand-primary-bg: rgb(var(--brand-primary-rgb) / 0.14);
--brand-anchor-bg: #0A141E; /* IB navy */
--brand-shadow-card: 0 4px 12px rgba(0, 0, 0, 0.3); /* IB card lift */
--display: 'Sora', 'Geist', ui-sans-serif, system-ui, ...;
--sans: 'Open Sans', 'Geist', ui-sans-serif, system-ui, ...;
--radius-sm: 4px; /* IB rounded */
--radius: 8px; /* IB rounded-lg (was already this value) */
--radius-lg: 12px; /* IB rounded-xl */
Existing --mono (JetBrains Mono) preserved — IB doesn't define a monospace font. Status colors (--acc-success/warn/error/info) and foreground ramp (--fg/-1/-2/-3) untouched per a11y deference.
File-by-file
/opt/sortof/frontend/index.html
| Where | Before | After |
|---|---|---|
<title> |
"sortof - sorted. sort of." | **"sortof (: |
<head> |
no favicon | <link rel="icon" type="image/png" href="/img/broccoli_shadow_square.png"> + apple-touch-icon |
| Font link | Geist + JetBrains Mono | + Sora 400/500/600/700 + Open Sans 400/500/600/700, all in one <link> |
:root vars |
a11y palette only | + --brand-primary, --brand-primary-bg, --brand-anchor-bg, --brand-shadow-card, --radius-sm/lg, --display |
--sans |
'Geist', ui-sans-serif, ... |
'Open Sans', 'Geist', ui-sans-serif, ... |
.wordmark |
font-family: var(--mono); font-size: 17px; weight 600 |
font-family: var(--display); font-size: 19px; weight 700 |
.wordmark .dot |
color: var(--acc-green) |
color: var(--brand-primary) |
.brand-mark |
did not exist | 28×28 circle, lifts on hover |
.brand-mark-link |
did not exist | wrapper anchor with rotation hover |
.ib-mark |
did not exist | small mono `(: |
.sort-btn |
--acc-green border/bg/text, mono font |
--brand-primary border/bg/text, Sora display font, height 42px (was 40) |
.sort-btn:hover |
tinted green | fills with brand-primary, inverts text to anchor-bg, applies card shadow |
.panel |
flat | box-shadow: var(--brand-shadow-card) |
/opt/sortof/frontend/sortof-app.jsx
| Component | Before | After |
|---|---|---|
<Header> |
wordmark + tagline only | <a href=ib.com><img broccoli/></a> + wordmark + tagline |
<Footer> |
"a thing by [indifferent broccoli]" no link | `indifferent broccoli (: |
<EmptyRight variant="bare"> |
"paste workshop ids on the left, then hit sort." | "no mods. or maybe loads of them. hard to tell." + sub-line "paste workshop ids on the left, hit sort. output lands here." |
| StatusStrip · idle | "ready when you are" | "ready when you are. or not." |
| StatusStrip · success/done | "done. N mods, W warnings" | "sorted. N mods, W warnings. or close enough." |
| StatusStrip · error | "something went sideways" | "something went sideways. that happens." |
| StatusStrip · failed | "job failed" | "that didn't work. try again or don't." |
| StatusStrip · cold | "cache miss - be patient" | "cache miss. take your time, no rush." |
Functional info preserved verbatim in every changed string (counts, role labels, action prompts). Voice flavor is additive.
Deferred to a11y (brand wanted this; a11y said no)
Per the brief: "If brand application would override an a11y decision, defer to a11y."
| Brand wanted | A11y holds | Resolution |
|---|---|---|
| Brand green (#5EFF0D, hue 138) as the success-state color | --acc-success at hue 165 (Okabe-Ito bluish-green) is more deuteranopia-safe; the hue-138 green clusters too close to amber under simulated CVD |
Brand green stays as --brand-primary (CTA only). Status pills, copy-btn-success, branch-picker-picked, diff-stat-add all keep --acc-success. |
| IB blue (#0050FF) for links | --acc-info (oklch 0.78 0.13 230) clears AAA against dark bg; #0050FF would be 3.0:1 (AA fail) |
Documented IB blue for completeness; not adopted. Links continue to use --acc-info. |
| Brand-only focus ring (green) | Global :focus-visible uses --focus-ring = var(--acc-info) for consistent keyboard signal across all interactive elements |
Kept the a11y blue focus ring. Brand green appears as button fill/border, not as focus indicator. |
| Glyph removal in favor of pure brand-color states | A11y requires every state pill to carry (color × glyph × text) | Glyphs preserved. Brand only changed the anchor color (sort-btn, wordmark dot, sort-btn:hover invert), never the state signal layer. |
Voice contract (locked)
All voice flavor is additive — never strips functional information. The pattern from IB's hero ("Host your own game server / Or not... we don't care") is: bold functional claim, then immediate self-undercut.
Applied as: <existing functional text>. <reverse-pleasantness flourish>
| Site | Functional info preserved | Flourish appended |
|---|---|---|
| idle pill | (none — placeholder) | "or not." |
| done pill | "sorted. N mods, W warnings" | "or close enough." |
| error pill | "something went sideways" | "that happens." |
| failed pill | (replaced) | "that didn't work. try again or don't." |
| cold pill | "cache miss" | "take your time, no rush." |
| empty-bare big | (replaced) | "no mods. or maybe loads of them. hard to tell." |
The <title> follows the same pattern with the (:|) glyph as separator.
Verification
This codebase has no build step (Babel-standalone transpiles JSX in-browser). Verification is via curl + visual inspection.
Served file checks:
- 22 hits for brand token names (
brand-primary,brand-anchor,brand-shadow,brand-mark,Sora,Open+Sans) in served HTML - 4 hits for new voice copy (
or close enough,or not.,try again or don.t,hard to tell) in served JSX - 2 hits for header brand markup (
broccoli_shadow_square.png,className="ib-mark") - Public mirror serves
/img/broccoli_shadow_square.pngHTTP 200 - Public mirror
<title>readssortof (:|) sorted. or close enough.
Contrast verification (computed via oklch L deltas; AA = ΔL ≥0.42; AAA = ΔL ≥0.55):
| Pair | ΔL | Approx ratio | WCAG |
|---|---|---|---|
--brand-primary (L 0.88) vs --bg-1 (L 0.21) |
0.67 | ~10:1 | AAA |
--brand-primary vs --brand-anchor-bg (L 0.20) — sort-btn:hover inverted |
0.68 | ~11:1 | AAA |
--acc-info (L 0.78) vs --bg-1 — link/button text |
0.57 | ~7.3:1 | AAA |
--fg (L 0.95) vs --bg-1 — display headings, body |
0.74 | ~14:1 | AAA |
--fg-2 (L 0.72) vs --bg-1 — secondary text (lifted in a11y pass) |
0.51 | ~5.7:1 | AA |
No brand color required luminance adjustment. The IB blue (#0050FF) was the only candidate that would have failed; it was rejected in palette reconciliation rather than lifted.
Backups (working-tree dirty)
/opt/sortof/frontend/index.html.bak-...-brand/opt/sortof/frontend/sortof-app.jsx.bak-...-brand- Plus a11y siblings (
-a11y-full,-a11y) and earlier session backups.
User must hard-refresh the browser to evict the prior cached HTML/JSX/CSS.