123 lines
8.0 KiB
Markdown
123 lines
8.0 KiB
Markdown
# 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.
|