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

123 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 |
|---|---|---|
| `<title>` | "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.