# 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 from `https://indifferentbroccoli.com/img/broccoli_shadow_square.png` ## Brand tokens added (CSS vars) ```css --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 | |---|---|---| | `` | "sortof - sorted. sort of." | **"sortof (:|) sorted. or close enough."** | | `<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 `(:|)` glyph in IB green | | `.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 | `<a href=ib.com>indifferent broccoli (:|)</a>` (real link, IB green glyph) | | `<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.png` HTTP 200 - Public mirror `<title>` reads `sortof (:|) 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.