Files
sortof/docs/brand-changes-2026-05-01.md

8.0 KiB
Raw Blame History

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.pngnew file, 19075 bytes, fetched from https://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.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.