max / makenotwork
10 files changed,
+0 insertions,
-836 deletions
| @@ -1,137 +0,0 @@ | |||
| 1 | - | # UX Audit — Phase 0: Design-System Conformance | |
| 2 | - | ||
| 3 | - | Audit ran 2026-05-19 against `static/style.css` (4432 lines), `static/wizard.css` (669), `static/media-player.css` (397), `_meta/docs/brand.md`, and a sample of partials, pages, dashboards, and wizards. | |
| 4 | - | ||
| 5 | - | The question this phase asks is not "is each screen good?" but "does the codebase behave like an OS — a small set of primitives composed everywhere — or does every screen reinvent the wheel?" The answer: **mostly the former, undermined by four concrete gaps.** | |
| 6 | - | ||
| 7 | - | ## What exists today (the good news) | |
| 8 | - | ||
| 9 | - | The token layer is real. `style.css:40-95` defines a coherent palette: warm beige background, charcoal-brown text, violet accent, plus surface/border/muted variants, semantic colors (success / warning / danger / error / health), and a focus-ring token. The three-tier type system from `brand.md` is wired in as `--font-heading` / `--font-mono` / `--font-body` and used consistently in headings, controls, and body. | |
| 10 | - | ||
| 11 | - | Component primitives that exist and are reused: | |
| 12 | - | ||
| 13 | - | - **Buttons** — `button`, `button.primary` / `.btn-primary`, `button.secondary` / `.btn-secondary`, `button.danger` / `.btn-danger` (`style.css:182-244`). | |
| 14 | - | - **Forms** — `.form-container`, `.form-group`, `.form-row`, `.hint`, unified `input[type=...]` styling with `--input-background` (`style.css:269-340`). | |
| 15 | - | - **Tables** — `.data-table`, `.compact-table` (`style.css:508-588`). | |
| 16 | - | - **Tabs** — `.tabs` / `.tab` / `.tab.active` plus scroll-overflow menu (`style.css:429-502`). 28 tab content partials under `templates/partials/tabs/`. | |
| 17 | - | - **Cards** — `.card` + `.card-title` / `.card-meta` / `.card-description` (`style.css:716-752`). | |
| 18 | - | - **Badges & tags** — `.badge` (+ `.badge-success` / `-warning` / `-danger` / `.active` / `.pending` / `.suspended` / `.free`); `.tag` (`style.css:628-710`). | |
| 19 | - | - **Empty state** — `.empty-state` class (`style.css:605`). | |
| 20 | - | - **Info / warning / error boxes** — `.info-box`, `.warning-box`, `.error-message` (`style.css:874-921`). | |
| 21 | - | - **Modal** — `.modal-overlay` + `.modal` + `.form-actions` (`style.css:1974-1999`). | |
| 22 | - | - **Toast** — `.toast-container` + `.toast` + `.toast-success` / `-error` / `-warning` plus `partials/toast.html` (`style.css:1653-1749`). | |
| 23 | - | - **HTMX loading** — `.htmx-indicator` + `.htmx-request button[type=submit]` opacity fade (`style.css:1620-1647`). | |
| 24 | - | ||
| 25 | - | Layout primitives: `.padded-page`, `.centered-page`, `.container`, plus the wizard layout in `wizard.css:3-26` and the media-container in `media-player.css:4-8`. | |
| 26 | - | ||
| 27 | - | Shared partials acting as components: `partials/alert.html`, `partials/toast.html`, `partials/error_fragment.html`, `partials/form_status.html`. | |
| 28 | - | ||
| 29 | - | ## Where the OS model breaks down | |
| 30 | - | ||
| 31 | - | ### 1. Missing token tiers — spacing, radius, shadow | |
| 32 | - | ||
| 33 | - | The token layer covers color and type but stops there. Every spacing, radius, and shadow value is hardcoded inline: | |
| 34 | - | ||
| 35 | - | - **Spacing** — values like `0.25 / 0.5 / 0.75 / 1 / 1.25 / 1.5 / 2 rem` form an implicit scale, but with off-scale strays (`wizard.css:39` uses `0.6rem`; `style.css:2012` uses `0.15rem 0.4rem`). No `--space-1`...`--space-6` tokens. | |
| 36 | - | - **Radius** — three values (`2px`, `3px`, `4px`) plus `50%` for avatars, repeated 21+ times. No `--radius-sm/md/round` tokens. | |
| 37 | - | - **Shadow** — seven distinct shadow recipes across the file with inconsistent opacity and offset. No `--shadow-1/2/3` tokens. | |
| 38 | - | ||
| 39 | - | Consequence: every new component re-decides what "small spacing" or "subtle shadow" means. | |
| 40 | - | ||
| 41 | - | ### 2. Inline `style="..."` saturation | |
| 42 | - | ||
| 43 | - | **1,350 inline-style attributes** across templates. Concrete offenders: | |
| 44 | - | ||
| 45 | - | - `dashboard-project.html:25` — sandbox banner built as a raw inline-styled div with hardcoded violet + mono font instead of a reusable `.banner` primitive. | |
| 46 | - | - `dashboard-project.html:38,40` — `style="opacity: 0.7;"` for muted links (recurs widely; no `.muted-link` class). | |
| 47 | - | - `login.html:61,65` — `style="display: none;"` despite a `.hidden` utility existing at `style.css:2263`. | |
| 48 | - | - `wizard_join.html:67` — `style="margin-top: 1rem; text-align: center;"` for a footer link (no `.footer-link` class). | |
| 49 | - | - `discover.html:32` — inline flex+gap recipe duplicating `.checkbox-label` (`style.css:354`). | |
| 50 | - | - `dashboard-blog-editor.html` — `style="width: 100%; padding: 0.5rem;"` repeated on inputs. | |
| 51 | - | - `buy.html` — entire page-scoped `<style>` block of ~119 lines defining one-off `.buy-card` / `.cover` / `.buy-btn` with hardcoded `#6c5ce7` and `#fff` instead of tokens. | |
| 52 | - | - `item.html:51-127` — 114-line embedded `<style>` block defining `.item-layout`, `.item-media`, `.item-title`, `.purchase-box`, `.section-tabs`, `.section-tab`. Duplicates patterns from `.card` and `.section-header`. | |
| 53 | - | - `error.html:18-61` — embedded `<style>` block redefining `.error-page` / `.error-container` / `.error-status` / `.error-message` / `.error-actions`. | |
| 54 | - | ||
| 55 | - | ### 3. Fragmented and inconsistent state recipes | |
| 56 | - | ||
| 57 | - | The "selected" / "active" state is spelled differently every place it appears: | |
| 58 | - | ||
| 59 | - | - `.filter-item.active` — opacity change. | |
| 60 | - | - `.tab.active` — background swap + opacity. | |
| 61 | - | - `.badge.active` / `.badge.complete` — green background. | |
| 62 | - | - `.view-btn.active` (`style.css:2292`) — black background, white text. | |
| 63 | - | - `.type-card input:checked + .type-card-inner` — border tint. | |
| 64 | - | ||
| 65 | - | The "hover" recipe is also split: cards change background, buttons fade opacity, type-cards change border, link-rows do both. There is no canonical "this is what interactive feedback looks like." | |
| 66 | - | ||
| 67 | - | The focus ring is the closest thing to a unified state recipe (`--focus-ring` token + global `*:focus-visible`, `style.css:1846-1849`), but custom interactive elements like `.type-card` and `.pricing-card` don't pick it up, and the docs search at `style.css:3214` redefines `outline` from scratch. | |
| 68 | - | ||
| 69 | - | ### 4. Missing or under-shared component primitives | |
| 70 | - | ||
| 71 | - | Primitives the system should ship with but doesn't: | |
| 72 | - | ||
| 73 | - | - **Empty-state partial.** The class exists but the markup is rewritten inline in `feed.html`, `collection.html`, `tag_tree.html` with varying padding (3rem / 1rem / 2rem). No `partials/empty_state.html`. | |
| 74 | - | - **Loading skeleton.** Only the opacity-pulse `.htmx-indicator`. No skeleton rows, no shimmer, no list-loading placeholder. | |
| 75 | - | - **Form-field with error variant.** `.form-group` carries label + hint but has no `.form-group--error` and no link from field to the error message that HTMX injects. | |
| 76 | - | - **Confirmation dialog partial.** `.modal` is styled but every delete/destroy confirm reassembles raw HTML. | |
| 77 | - | - **Disabled button state.** No `:disabled` styling. The only "busy" cue is the HTMX submit opacity dim. | |
| 78 | - | - **Sortable table active-direction indicator.** `.sortable::after { content: " ^" }` is rendered at low opacity but never gets an "ascending" / "descending" treatment. | |
| 79 | - | - **Pagination component.** Styles live tacked onto the end of the file (`style.css:4111-4115`) with no partial; raw HTML is regenerated per page. | |
| 80 | - | ||
| 81 | - | ## Brand conformance flags | |
| 82 | - | ||
| 83 | - | These violate `_meta/docs/brand.md` or the memory rule "no checkmarks/emoji in UI copy": | |
| 84 | - | ||
| 85 | - | - **Checkmark glyphs in user-facing markup.** `wizard.css:435` uses `content: '\2713'` (✓) for checklist items. `discover.html:86` emits `✓` on the tag-follow button. Brand rule: words only. | |
| 86 | - | - **Pure black / white bypassing the warm palette.** `media-player.css:107` sets `.video-display` background to `#000`. `buy.html:20` sets `.buy-card` background to `#fff`. Should be `var(--detail)` and `var(--light-background)`. | |
| 87 | - | - **Bootstrap-yellow restart banner.** `style.css:1755` uses `#fff3cd` + `#ffc107` for `#restart-banner`. Should map to `--warning` / `--warning-bg` / `--warning-border`. | |
| 88 | - | - **Hardcoded `#6c5ce7` in `buy.html` and `wizard.css:172` rgba** instead of `var(--highlight)`. | |
| 89 | - | ||
| 90 | - | No emoji found in `templates/pages/` or `templates/dashboards/` — that part of the rule holds. | |
| 91 | - | ||
| 92 | - | ## Inventory verdict by axis | |
| 93 | - | ||
| 94 | - | | Axis | State | Note | | |
| 95 | - | |---|---|---| | |
| 96 | - | | Color tokens | Complete | 20+ tokens, brand-aligned | | |
| 97 | - | | Type tokens | Complete | 3-tier system enforced via vars | | |
| 98 | - | | Spacing tokens | Missing | Implicit scale, no token | | |
| 99 | - | | Radius tokens | Missing | 4 raw values used 21+ times | | |
| 100 | - | | Shadow tokens | Missing | 7 distinct recipes, no token | | |
| 101 | - | | Button primitive | Complete | primary / secondary / danger; lacks `:disabled` | | |
| 102 | - | | Form primitive | Partial | No error-variant or field-error binding | | |
| 103 | - | | Table primitive | Partial | No active sort-direction class | | |
| 104 | - | | Tab primitive | Good | 28 partials; ARIA inconsistent | | |
| 105 | - | | Card primitive | Good | Hover recipe diverges from `.grid-card` | | |
| 106 | - | | Empty-state | Fragmented | Class exists, markup duplicated inline | | |
| 107 | - | | Loading state | Minimal | Only HTMX opacity dim | | |
| 108 | - | | Modal | Partial | No confirm-dialog partial | | |
| 109 | - | | Toast | Complete | Class + partial; no JS trigger helper | | |
| 110 | - | | Focus ring | Partial | Token global, not picked up by custom elements | | |
| 111 | - | | Inline style usage | Bad | 1,350 inline-style attrs in templates | | |
| 112 | - | | Brand conformance | Minor breaches | Checkmarks; pure #000/#fff; Bootstrap yellow | | |
| 113 | - | ||
| 114 | - | ## Remediation — what Phase 0 must land before Phase 1 starts | |
| 115 | - | ||
| 116 | - | Charter is at `docs/design-system.md`. Cleanup work (drop into `todo.md` follow-ups): | |
| 117 | - | ||
| 118 | - | 1. **Add spacing / radius / shadow tokens** to `:root` in `style.css` and migrate the implicit scale. | |
| 119 | - | 2. **Build the four missing shared partials**: `partials/empty_state.html`, `partials/confirm_dialog.html`, `partials/form_field.html` (label + hint + error slot), `partials/loading_skeleton.html`. | |
| 120 | - | 3. **Inline-style sweep.** Target the worst offenders first: `buy.html`, `item.html` embedded `<style>` blocks, `dashboard-project.html` banner + muted-link patterns, `login.html` use of `style="display: none"` where `.hidden` exists. | |
| 121 | - | 4. **Brand-conformance fixes.** Replace checkmark glyphs in `wizard.css` and `discover.html` with text; swap `#000` / `#fff` / Bootstrap yellow for tokens. | |
| 122 | - | 5. **Unify selected-state recipe.** Pick one (`.is-selected` with violet-tinted background + focus ring) and migrate `.tab.active`, `.filter-item.active`, `.badge.active`, `.view-btn.active`, `.type-card input:checked` to it. | |
| 123 | - | 6. **Add `:disabled` button styling** so destructive flows can show a disabled-confirm cue. | |
| 124 | - | 7. **Apply focus ring to custom interactive containers** (`.type-card`, `.pricing-card`, sort-headers). | |
| 125 | - | ||
| 126 | - | Once 1-7 land, Phase 1 (public landing) audits *conformance to the charter*, not free-form usability. | |
| 127 | - | ||
| 128 | - | ## Pattern summary | |
| 129 | - | ||
| 130 | - | Four patterns explain almost every divergence found: | |
| 131 | - | ||
| 132 | - | 1. **Token system stops at color/type.** Spacing, radius, and shadow are free-form, which licenses every new component to invent its own scale. | |
| 133 | - | 2. **Page-isolated CSS blocks.** `buy.html`, `item.html`, `error.html` each embed their own stylesheet, duplicating primitives that exist in `style.css` under different names. | |
| 134 | - | 3. **No shared empty / loading / confirm partials.** Patterns recur but have no canonical markup, so each page rebuilds them. | |
| 135 | - | 4. **State vocabulary is unstandardized.** "Selected" and "hover" are spelled five different ways across components, which makes screens feel like they were designed by different people. | |
| 136 | - | ||
| 137 | - | Fix those four and the rest of the audit collapses into conformance checks. |
| @@ -1,151 +0,0 @@ | |||
| 1 | - | # UX Audit — Phase 1: Public Landing Surfaces | |
| 2 | - | ||
| 3 | - | Audit ran 2026-05-20 against the unauthenticated, marketing-facing pages: `index.html`, `creators.html`, `pricing.html`, `use_cases.html`, `policy.html`, `changelog.html`. Charter is `docs/design-system.md`; Phase 0 inventory at `phase-0.md`; pre-Phase-1 consolidation log at `remediation-plan.md`. | |
| 4 | - | ||
| 5 | - | The question this phase asks is whether the landing surfaces, taken as a set, *read like the same product*. After the Phase 0 consolidation pass, the answer is **yes — with five concrete drifts and one accessibility break.** | |
| 6 | - | ||
| 7 | - | ## What conforms (the good news) | |
| 8 | - | ||
| 9 | - | Across all six templates: | |
| 10 | - | ||
| 11 | - | - Zero inline `style="..."` attributes. | |
| 12 | - | - Zero page-isolated `<style>` blocks. | |
| 13 | - | - No raw hex literals; everything reads through tokens. | |
| 14 | - | - No checkmark glyphs, emoji, or pure black/white at use sites. | |
| 15 | - | - Every page leads with a single h1. | |
| 16 | - | - Every CTA uses `<a class="btn-primary">` / `<a class="btn-primary btn--large">` *on five of six pages*. (The exception is in §3 below.) | |
| 17 | - | - All bespoke `-card` class names found here (`.fork-card`, `.tier-card`, `.use-case-card`, `.feature-card`) are documented aliases for `.card--bordered` (`style.css:1033-1038`). | |
| 18 | - | - No `.empty-state` markup in any landing page (none of them have list-like content that empties out). | |
| 19 | - | - Section headings on `index`, `pricing`, and `use_cases` use the canonical `.section-label` modifier. | |
| 20 | - | ||
| 21 | - | The five landing surfaces that were redesigned during Phase 0 / remediation are clean. The remaining drifts cluster on `creators.html`, `policy.html`, and `changelog.html` — pages that weren't directly touched by the consolidation pass. | |
| 22 | - | ||
| 23 | - | ## Drifts from the charter | |
| 24 | - | ||
| 25 | - | ### 1. `pricing.html` tier picker uses the pre-charter selected recipe | |
| 26 | - | ||
| 27 | - | `pricing.html:24` carries `class="tier-card tier-option selected"` on the default-checked tier. The JS at lines 336-338 adds and removes `.selected` as the user clicks. CSS at `style.css:6019-6023`: | |
| 28 | - | ||
| 29 | - | ```css | |
| 30 | - | .tier-option.selected { | |
| 31 | - | border-color: var(--highlight); | |
| 32 | - | border-width: 2px; | |
| 33 | - | padding: calc(1rem - 1px); | |
| 34 | - | } | |
| 35 | - | ``` | |
| 36 | - | ||
| 37 | - | Remediation §2.4 declared the canonical selection class is `.is-selected` with `background: var(--highlight-faint); border-color: var(--focus-ring);`. This is the last `.selected` (without `is-`) in the codebase. Two changes: | |
| 38 | - | - Rename `.tier-option.selected` → `.tier-option.is-selected` in `style.css`, template, and JS. | |
| 39 | - | - Either fold the recipe into the canonical `.is-selected` shape (background tint + ring border) or document `.tier-option` as a card-shaped selected variant that keeps the bordered look. | |
| 40 | - | ||
| 41 | - | ### 2. `pricing.html` tier picker is keyboard-inaccessible | |
| 42 | - | ||
| 43 | - | `style.css:6015-6017`: | |
| 44 | - | ||
| 45 | - | ```css | |
| 46 | - | .tier-option input[type="radio"] { | |
| 47 | - | display: none; | |
| 48 | - | } | |
| 49 | - | ``` | |
| 50 | - | ||
| 51 | - | `display: none` removes the radio from the accessibility tree *and* from the focus order. There is no compensating focus styling on the surrounding `<label>`. A keyboard user cannot tab between tiers; a screen-reader user gets a radiogroup with no perceivable radios. | |
| 52 | - | ||
| 53 | - | Fix recipe (matches Phase 0 §4 "Apply focus ring to custom interactive containers"): | |
| 54 | - | ||
| 55 | - | ```css | |
| 56 | - | .tier-option input[type="radio"] { | |
| 57 | - | position: absolute; | |
| 58 | - | opacity: 0; | |
| 59 | - | pointer-events: none; | |
| 60 | - | } | |
| 61 | - | .tier-option:has(input:focus-visible), | |
| 62 | - | .tier-option:focus-within { | |
| 63 | - | outline: var(--focus-ring) solid 2px; | |
| 64 | - | outline-offset: 2px; | |
| 65 | - | } | |
| 66 | - | ``` | |
| 67 | - | ||
| 68 | - | This is the only **accessibility bug** Phase 1 found. | |
| 69 | - | ||
| 70 | - | ### 3. `creators.html` wraps `<button>` in `<a>` for CTAs | |
| 71 | - | ||
| 72 | - | Lines 106, 110, 113, 114: | |
| 73 | - | ||
| 74 | - | ```html | |
| 75 | - | <a href="/dashboard"><button class="primary">Go to Dashboard</button></a> | |
| 76 | - | <a href="/dashboard#tab-plan"><button class="primary">Apply from Dashboard</button></a> | |
| 77 | - | <a href="/join"><button class="primary">Join</button></a> | |
| 78 | - | <a href="/login"><button class="secondary">Login</button></a> | |
| 79 | - | ``` | |
| 80 | - | ||
| 81 | - | `<a><button></button></a>` is invalid HTML (interactive content nested inside interactive content) and produces unpredictable focus/activation behavior across browsers. Every other landing page uses the canonical `<a class="btn-primary">…</a>`. Convert these four to the anchor-with-button-class pattern. | |
| 82 | - | ||
| 83 | - | ### 4. `creators.html`, `policy.html`, `changelog.html` h2s lack `.section-label` | |
| 84 | - | ||
| 85 | - | | File | Lines | Headings | | |
| 86 | - | |---|---|---| | |
| 87 | - | | `creators.html` | 18, 45, 94 | How It Works, Pricing, Who Runs This | | |
| 88 | - | | `policy.html` | 21, 34, 46, 58, 69, 81 | six section headings | | |
| 89 | - | | `changelog.html` | 18, 33, 43 | per-month headings | | |
| 90 | - | ||
| 91 | - | `index`, `pricing`, and `use_cases` all use `<h2 class="section-label">`. The three older pages use bare `<h2>`, which inherits Young Serif from the global rule rather than the mono treatment `.section-label` gives sub-section headings. | |
| 92 | - | ||
| 93 | - | Pick one. Either: | |
| 94 | - | - Promote bare h2 to the convention by adding `.section-label` everywhere a section heading appears (the recommended path; aligns with charter §"Section header"). | |
| 95 | - | - Or accept that long-form prose pages (`policy`, `changelog`) want the Young Serif treatment and document the exemption in the charter. | |
| 96 | - | ||
| 97 | - | The current state is silent drift — a reader can't tell whether the inconsistency is intentional. | |
| 98 | - | ||
| 99 | - | ### 5. `.intro` is duplicated identically across four page scopes | |
| 100 | - | ||
| 101 | - | `style.css:2740, 3453, 3875, 3908` — four copies of the same rule: | |
| 102 | - | ||
| 103 | - | ```css | |
| 104 | - | .{fan-plus|creators|changelog|policy}-page .intro { | |
| 105 | - | font-size: 1.1rem; | |
| 106 | - | margin-bottom: var(--space-6); | |
| 107 | - | line-height: 1.6; | |
| 108 | - | } | |
| 109 | - | ``` | |
| 110 | - | ||
| 111 | - | This is a primitive masquerading as a page-scoped rule. Lift to a single `.page-intro` class in the primitives section of `style.css`, migrate the four templates, drop the four scoped copies. | |
| 112 | - | ||
| 113 | - | ### 6. ~~`use_cases.html` `.coming-soon` modifier is undocumented~~ — finding retracted | |
| 114 | - | ||
| 115 | - | *Original finding (incorrect): claimed `.use-case-card.coming-soon` had no CSS rule. Actually defined at `style.css:6489` with `border-style: dashed; opacity: 0.6;`. The grep that produced this finding was too narrow and missed the rule. No drift here.* | |
| 116 | - | ||
| 117 | - | ## Two charter additions to consider | |
| 118 | - | ||
| 119 | - | Phase 1 surfaced two patterns that recur cleanly enough to deserve charter entries instead of staying ad-hoc: | |
| 120 | - | ||
| 121 | - | 1. **`.page-intro`** — large-text intro paragraph under the page h1. Used on at least four pages, duplicated four times in CSS. | |
| 122 | - | 2. **`.policy-section` / `.changelog-entry`** — vertically-separated content sections with bottom border, used on policy and changelog. Currently page-scoped but the shape is the same. A `.content-section` primitive (or `.section-header` composition) would absorb both. | |
| 123 | - | ||
| 124 | - | These are *suggestions*, not blockers — Phase 1's job is to surface them; the charter decision belongs to a follow-up. | |
| 125 | - | ||
| 126 | - | ## Verdict by axis | |
| 127 | - | ||
| 128 | - | | Axis | State | Note | | |
| 129 | - | |---|---|---| | |
| 130 | - | | Inline styles | Clean | 0 across all six | | |
| 131 | - | | Page-isolated `<style>` | Clean | 0 across all six | | |
| 132 | - | | Token usage | Clean | All colors / spacing / radius read tokens | | |
| 133 | - | | Brand glyphs | Clean | No checkmarks, emoji, or pure hex at use sites | | |
| 134 | - | | Heading hierarchy | Drift | 3/6 pages skip `.section-label` on h2 | | |
| 135 | - | | Selection state | Drift | `pricing.html` `.tier-option.selected` is the last pre-charter survivor | | |
| 136 | - | | Button primitive | Drift | `creators.html` uses invalid `<a><button>` nesting in 4 CTAs | | |
| 137 | - | | Card primitive | Conformant | All `-card` names alias to `.card--bordered` | | |
| 138 | - | | Keyboard accessibility | **Broken** | `pricing.html` tier picker not focusable | | |
| 139 | - | | Repeated rule definitions | Drift | `.intro` duplicated across 4 page scopes | | |
| 140 | - | ||
| 141 | - | ## Remediation — what to land before Phase 2 | |
| 142 | - | ||
| 143 | - | In priority order: | |
| 144 | - | ||
| 145 | - | 1. **Fix the keyboard trap on `.tier-option`** — accessibility, ship as standalone change. | |
| 146 | - | 2. **Migrate `.tier-option.selected` → `.is-selected`** — closes the last selection-state drift. | |
| 147 | - | 3. **Replace `<a><button>` nesting in `creators.html`** — invalid HTML; trivially mechanical. | |
| 148 | - | 4. **Decide on `.section-label` policy for prose pages** — apply or document exemption. | |
| 149 | - | 5. **Lift `.intro` → `.page-intro` primitive** — small CSS consolidation. | |
| 150 | - | ||
| 151 | - | After 1-3 land, Phase 1 surfaces are charter-conformant. Items 4-6 are tidy-up. Phase 2 (authenticated content surfaces: feed, discover, item, project, library) can proceed in parallel with the tidy-up. |
| @@ -1,50 +0,0 @@ | |||
| 1 | - | # UX Audit — Phase 2: Discovery and Feed | |
| 2 | - | ||
| 3 | - | Audit ran 2026-05-20. Scope: `discover.html`, `tag_tree.html`, `feed.html`, `discover_results.html` (partial), and `partials/tabs/library_feed.html`. | |
| 4 | - | ||
| 5 | - | ## What conforms | |
| 6 | - | ||
| 7 | - | - Zero inline `style="..."` on any of the four page templates. | |
| 8 | - | - All four pages lead with h1 (after the Phase 0 fixes for `discover` and `tag_tree`). | |
| 9 | - | - No bare `<h2>` — when h2 appears it carries `.section-label` or sits inside a content-section primitive. | |
| 10 | - | - All bespoke `-card` names (`.grid-card`, `.discover-grid-card`, `.tag-card`, `.tag-card--leaf`) are either documented or aliased. | |
| 11 | - | - No `<a><button>` antipattern. | |
| 12 | - | - No raw hex literals at use sites. | |
| 13 | - | - Empty-state markup migrated to `ui::empty_state` macros (per remediation third sitting). | |
| 14 | - | ||
| 15 | - | ## Drifts | |
| 16 | - | ||
| 17 | - | ### 1. `partials/tabs/library_feed.html` carries a page-isolated `<style>` block (23 lines) | |
| 18 | - | ||
| 19 | - | `library_feed.html:1-24` defines `.feed-meta`, `.feed-table-header`, `.feed-table-row`, `.feed-col-right`, `.feed-results-table`, `.feed-item-name-cell`, `.feed-item-name`, `.feed-item-creator`, `.feed-pagination` inline. This duplicates the Phase 0 pattern that `buy.html` / `item.html` / `error.html` were called out for, and the Phase 0 follow-up missed it. | |
| 20 | - | ||
| 21 | - | The rules read tokens correctly but are spelled out as an embedded sheet. Migrate to `style.css` under a `LIBRARY FEED TAB` section, named the same way the other tab styles are (e.g. `.library-feed-page .feed-row`). | |
| 22 | - | ||
| 23 | - | ### 2. `discover_results.html` keeps four inline empty-state blocks with `.empty-state-hint` | |
| 24 | - | ||
| 25 | - | Lines 14-17, 36-39, 60-63, 86-89 — all identical: | |
| 26 | - | ||
| 27 | - | ```html | |
| 28 | - | <div class="empty-state"> | |
| 29 | - | <p>No projects found matching your filters.</p> | |
| 30 | - | <p class="empty-state-hint">Try broadening your search or <a href="/discover">clearing all filters</a>.</p> | |
| 31 | - | </div> | |
| 32 | - | ``` | |
| 33 | - | ||
| 34 | - | The remediation pass intentionally left these bespoke because the hint paragraph contains an inline anchor — the current `ui::empty_state` signature doesn't accept HTML in `body`. Options: | |
| 35 | - | ||
| 36 | - | - Add `ui::empty_state_with_hint_link(body, hint_prefix, hint_link_href, hint_link_label, hint_suffix)` if the four-instance duplication is worth a macro. | |
| 37 | - | - Extract to a new `partials/_empty_filtered.html` include and `{% include %}` four times (the file already serializes the same `subject` distinction). | |
| 38 | - | - Accept four copies inside one file as already-consolidated. | |
| 39 | - | ||
| 40 | - | Recommend: option 3 (do nothing). They live in one file, are identical, and the cost of a fifth macro variant exceeds the gain. | |
| 41 | - | ||
| 42 | - | ### 3. `discover.html` `.tag-follow-btn` toggle uses opacity not `.is-selected` | |
| 43 | - | ||
| 44 | - | `style.css` styles `.tag-follow-btn` and `.tag-follow-btn.following` with bespoke recipes (grep for follow-btn). Remediation §2.3 named it as a case to fold into `.btn-secondary` with `.is-selected` modifier. Currently uses its own `.following` class as the selected-state marker. | |
| 45 | - | ||
| 46 | - | This is the same family of drift as the `pricing.html` tier-option finding in Phase 1: a custom-named selected state instead of the canonical `.is-selected`. | |
| 47 | - | ||
| 48 | - | ## Verdict | |
| 49 | - | ||
| 50 | - | Discovery surfaces are nearly conformant. One real consolidation item (`library_feed.html` style block — Phase 0 plan said zero remained, but four remain across the codebase; see Phase 5 for the rest), one selected-state drift, one already-consolidated-enough case. |
| @@ -1,44 +0,0 @@ | |||
| 1 | - | # UX Audit — Phase 3: Content Viewing | |
| 2 | - | ||
| 3 | - | Audit ran 2026-05-20. Scope: `item.html`, `project.html`, `project_blog.html`, `blog_post.html`, `collection.html`, `user.html`. | |
| 4 | - | ||
| 5 | - | ## What conforms | |
| 6 | - | ||
| 7 | - | - All six lead with h1. | |
| 8 | - | - All sub-section h2s use `.section-header` (the canonical sub-section heading). | |
| 9 | - | - All `-card` variants (`.item-card`, `.tier-card`, `.fork-card`) ride documented aliases (`.card--bordered`, `.card-muted`). | |
| 10 | - | - `.section-tab.is-selected` and `.view-btn.is-selected` are canonical. | |
| 11 | - | - Empty-state markup migrated to `ui::empty_state` (`collection.html`, `project_blog.html`). | |
| 12 | - | ||
| 13 | - | ## Drifts | |
| 14 | - | ||
| 15 | - | ### 1. `user.html` carries a page-isolated `<style>` block (18 lines) | |
| 16 | - | ||
| 17 | - | `user.html:36-54` defines `.container { max-width: 600px }`, `.profile-header`, `.profile-avatar`, `.profile-name`, `.profile-username`, `.profile-bio`, `.links-section`, `.link-item`, `.link-title`, `.link-description`, `.project-title`, `.project-meta`, `.project-description`. | |
| 18 | - | ||
| 19 | - | Same anti-pattern as the four others called out in remediation Phase 0 §2.11 — duplicates `.user-page`-scoped rules under different names, and `.project-title` / `.project-meta` likely collide with `dashboard-user.html`'s identically-named locals. Lift to `style.css` under a `USER PAGE` section. | |
| 20 | - | ||
| 21 | - | ### 2. `.follow-btn.is-following` naming drifts from `.is-selected` | |
| 22 | - | ||
| 23 | - | `project.html:66, 70` and `user.html` use `.follow-btn` with `.is-following` as the toggle. CSS at `style.css:9063, 9445, 10672`. Functionally equivalent to `.is-selected` (focus-ring border + highlight-faint background). Same family as `discover.html` `.tag-follow-btn.following`. | |
| 24 | - | ||
| 25 | - | The Phase 2 finding generalizes: any "X is followed/active" pattern in the codebase has its own custom selector instead of the canonical `.is-selected`. Either standardize on `.is-selected` (and let component-scoped CSS read it), or accept "is-following" / "following" as a documented synonym in the charter. | |
| 26 | - | ||
| 27 | - | ### 3. `<a><button>` antipattern: 7 instances in `item.html`, `project.html` | |
| 28 | - | ||
| 29 | - | Mirrors the Phase 1 `creators.html` finding: | |
| 30 | - | ||
| 31 | - | - `item.html:144, 163, 165` — view/purchase CTAs | |
| 32 | - | - `project.html:155, 161, 163, 208` — view/purchase/log-in CTAs | |
| 33 | - | ||
| 34 | - | Every one of these can become `<a class="btn-primary">…</a>` or `<a class="btn-secondary">…</a>`. The CSS at `style.css:336-385` aliases `button.primary` to `.btn-primary`, so anchors with `.btn-primary` already render identically. The `<a><button>` form is invalid HTML. | |
| 35 | - | ||
| 36 | - | ### 4. Other content-page surfaces | |
| 37 | - | ||
| 38 | - | - `project_blog.html`, `blog_post.html`, `collection.html` — clean. | |
| 39 | - | - `item.html`'s `<style>` block from Phase 0 was removed successfully (verified empty). | |
| 40 | - | - `project.html:208` carries `class="primary login-cta-btn"` — `.login-cta-btn` is undocumented. Likely a one-off, fold into the canonical button or document as a project-page variant. | |
| 41 | - | ||
| 42 | - | ## Verdict | |
| 43 | - | ||
| 44 | - | Two real items (`user.html` `<style>` block, the seven `<a><button>` CTAs), one terminology call to make (`.is-following` vs `.is-selected`), one minor (`.login-cta-btn`). |
| @@ -1,40 +0,0 @@ | |||
| 1 | - | # UX Audit — Phase 4: Purchase Flow | |
| 2 | - | ||
| 3 | - | Audit ran 2026-05-20. Scope: `buy.html`, `purchase.html`, `cart.html`, `receipt.html`, `fan_plus.html`, `stripe_disclaimer.html`. | |
| 4 | - | ||
| 5 | - | ## What conforms | |
| 6 | - | ||
| 7 | - | - All six lead with h1. | |
| 8 | - | - Zero inline `style="..."`. | |
| 9 | - | - Zero page-isolated `<style>` blocks (the `buy.html` block was successfully removed in Phase 0). | |
| 10 | - | - `cart.html` empty-state would use `ui::empty_state_with_action` cleanly, currently uses an `<a><button>` pattern instead — see drift §3. | |
| 11 | - | - No raw hex literals at use sites. | |
| 12 | - | ||
| 13 | - | ## Drifts | |
| 14 | - | ||
| 15 | - | ### 1. `purchase.html` has four bare `<h2>` sub-headings | |
| 16 | - | ||
| 17 | - | Lines 13, 36, 55, 74: "Confirm Purchase", "Price Breakdown", "What {creator} receives", "Secure Checkout". These should carry `.section-header` to match the convention used in `item.html` / `project.html`. The purchase flow is the most commercially-critical page in the app; visual treatment that drifts from the rest of the site here is the highest-stakes drift in the audit. | |
| 18 | - | ||
| 19 | - | ### 2. `cart.html` has one bare `<h2 class="cart-group__title">` and one bare `<h2>From your wishlist</h2>` | |
| 20 | - | ||
| 21 | - | Line 44 uses `.cart-group__title`; line 135 is bare. Standardize on `.section-header` (or `.section-header.cart-group-title` if the group title needs a tighter variant). | |
| 22 | - | ||
| 23 | - | *Correction (post-audit):* an earlier draft of this finding claimed `.cart-group__title` was the only BEM `__` separator in the codebase. That was wrong. BEM `__` is widely used — 112 template usages across 20 distinct block prefixes (cart-empty, cart-multi-bar, cart-group, store-cta, suspension-banner, upload-status, proj-overview-setup, bundle-picker-row, psection-row, sandbox-banner, section-row, tier-row, item-footer, dns-row, report-modal, user-media-card, user-media-folders, user-media-upload, proj-overview-tool, proj-overview-tools) with 82 corresponding CSS rules. It's an established internal convention. The remaining drift here is just the heading-treatment one — fold under §1. | |
| 24 | - | ||
| 25 | - | ### 3. `cart.html:22` and 7 player-page sites use `<a><button>` | |
| 26 | - | ||
| 27 | - | - `cart.html:22` — empty-state "Browse Discover" CTA | |
| 28 | - | - See Phase 5 for the player-page sites | |
| 29 | - | ||
| 30 | - | ### 4. `fan_plus.html`, `stripe_disclaimer.html` bare `<h2>` | |
| 31 | - | ||
| 32 | - | `fan_plus.html:32` "What you get", `stripe_disclaimer.html:12` "Before You Connect with Stripe". Add `.section-header`. | |
| 33 | - | ||
| 34 | - | ### 5. `purchase.html` `<button>` intent-only classes | |
| 35 | - | ||
| 36 | - | Lines 87, 118, 138, 183 use `<button class="primary">` / `<button class="secondary">`. CSS at `style.css:336-385` aliases these to `.btn-primary` / `.btn-secondary` so they render identically; this is the documented "older shorthand" pattern. Choose one canonical pattern in the charter or accept both as equivalent. | |
| 37 | - | ||
| 38 | - | ## Verdict | |
| 39 | - | ||
| 40 | - | Purchase flow is structurally clean (no `<style>` blocks, no inline styles). The drifts are all heading-treatment and button-class conventions — visible but mechanical. |
| @@ -1,32 +0,0 @@ | |||
| 1 | - | # UX Audit — Phase 5: Library and Media Players | |
| 2 | - | ||
| 3 | - | Audit ran 2026-05-20. Scope: `library.html`, `library_audio.html`, `library_video.html`, `library_text.html`, `library_downloads.html`, `library_locked.html`, `audio_player.html`, `video_player.html`, `text_reader.html`, plus `partials/tabs/library_*.html`. | |
| 4 | - | ||
| 5 | - | ## What conforms | |
| 6 | - | ||
| 7 | - | - All library and player pages lead with h1. | |
| 8 | - | - Zero raw hex at use sites. | |
| 9 | - | - `library_audio`, `library_video`, `library_text`, `library.html` are clean (no style blocks, no inline styles, no `<a><button>` nesting). | |
| 10 | - | - The media-player layout primitives in `media-player.css` are correctly scoped (`.media-container`, `.video-display`, etc.) and read from tokens. | |
| 11 | - | ||
| 12 | - | ## Drifts | |
| 13 | - | ||
| 14 | - | ### 1. `audio_player.html`, `video_player.html`, `text_reader.html` each carry 4 `<a><button>` CTAs (12 total) | |
| 15 | - | ||
| 16 | - | Same antipattern as Phase 1 / Phase 3. The CTAs are the watch / read / log-in / purchase actions in `.media-store-cta` blocks. All twelve become `<a class="btn-primary">` mechanically. | |
| 17 | - | ||
| 18 | - | ### 2. `library_locked.html` has 2 `<a><button>` CTAs (lines 26, 28) and 1 bare `<h2>` (line 16) | |
| 19 | - | ||
| 20 | - | The locked-state landing for content the user hasn't purchased. Add `.section-header` and rewrite the two CTAs as anchors. | |
| 21 | - | ||
| 22 | - | ### 3. `library_downloads.html` has 3 bare `<h2>` | |
| 23 | - | ||
| 24 | - | Lines 36, 64, 106: "Downloads", "Included in this bundle (n)", "License". Standardize on `.section-header`. | |
| 25 | - | ||
| 26 | - | ### 4. `partials/tabs/library_feed.html` carries a 23-line page-isolated `<style>` block | |
| 27 | - | ||
| 28 | - | Already called out in Phase 2 §1. This is a *partial*, not a page, but the embedded sheet defines `.feed-meta`, `.feed-table-header`, `.feed-table-row`, `.feed-pagination`, etc. Migrate to `style.css` under a `LIBRARY FEED TAB` section. | |
| 29 | - | ||
| 30 | - | ## Verdict | |
| 31 | - | ||
| 32 | - | Library-list pages are clean. Media players are structurally clean but every CTA wraps a button in an anchor — a tight cluster of 12 invalid-HTML sites that should be fixed as one mechanical pass. |
| @@ -1,38 +0,0 @@ | |||
| 1 | - | # UX Audit — Phase 6: Auth and Account | |
| 2 | - | ||
| 3 | - | Audit ran 2026-05-20. Scope: `login.html`, `forgot_password.html`, `reset_password.html`, `two_factor.html`, `oauth_authorize.html`, `email_result.html`, `account-deleted.html`, `confirm_delete.html`, `sandbox.html`. | |
| 4 | - | ||
| 5 | - | ## What conforms | |
| 6 | - | ||
| 7 | - | - All nine pages lead with h1 (the brand wordmark "Makenot.work"). | |
| 8 | - | - Zero inline `style="..."`. | |
| 9 | - | - Zero page-isolated `<style>` blocks. | |
| 10 | - | - Zero raw hex at use sites. | |
| 11 | - | - Forms use canonical `.form-group` + `.form-container` primitives. | |
| 12 | - | ||
| 13 | - | ## Drifts | |
| 14 | - | ||
| 15 | - | ### 1. `<h2>` as page-subtitle is the established auth-flow pattern | |
| 16 | - | ||
| 17 | - | Every auth-flow page uses `<h1>Makenot.work</h1>` (brand wordmark) followed by `<h2>` for the page purpose ("Log in", "Reset Password", "Two-Factor Authentication", "Delete Account", etc.). This is consistent across nine pages and is the **intentional** design — the brand wordmark is the page's hero element, and the h2 is the page-subtitle below it. | |
| 18 | - | ||
| 19 | - | Implication for Phase 1 finding §4 (`creators.html` / `policy.html` / `changelog.html` bare h2): those pages may also be intentionally using h2 as a prose-style section header rather than the dashboard `.section-label` mono treatment. **This is a charter decision that should be made explicitly**, then either applied or documented as the exempt pattern. | |
| 20 | - | ||
| 21 | - | ### 2. `account-deleted.html:11` uses `<a><button>` for the "Return Home" CTA | |
| 22 | - | ||
| 23 | - | One instance. Mechanical fix. | |
| 24 | - | ||
| 25 | - | ### 3. `confirm_delete.html` uses `<button class="danger">` (line 26) | |
| 26 | - | ||
| 27 | - | This is a real `<button type="submit">` inside a form, not an `<a><button>` antipattern. The class is canonical (the CSS at `style.css:370-385` defines `button.danger`). Conformant. | |
| 28 | - | ||
| 29 | - | ### 4. `sandbox.html` and `reset_password.html` have additional bare `<h2>` headings | |
| 30 | - | ||
| 31 | - | - `sandbox.html:10` — "Creator Sandbox" (the page-subtitle pattern) | |
| 32 | - | - `reset_password.html:57` — "Link Expired" (an alternate state — could be a separate `<h2 class="section-header">` or kept as page-subtitle) | |
| 33 | - | ||
| 34 | - | Both follow the established auth-flow pattern. Conformant. | |
| 35 | - | ||
| 36 | - | ## Verdict | |
| 37 | - | ||
| 38 | - | Auth surfaces are clean. The bare-h2 pattern is intentional design, not drift. One mechanical fix in `account-deleted.html`. The cross-phase implication is that the bare-h2 finding from Phase 1 needs a charter-level decision: which pages get the auth-flow pattern (brand h1 + subtitle h2), which get the dashboard pattern (page h1 + `.section-label` h2)? |
| @@ -1,52 +0,0 @@ | |||
| 1 | - | # UX Audit — Phase 7: Dashboard and Admin | |
| 2 | - | ||
| 3 | - | Audit ran 2026-05-20. Scope: `templates/dashboards/` (14 templates) + `templates/partials/tabs/` (38 tab partials). | |
| 4 | - | ||
| 5 | - | ## What conforms | |
| 6 | - | ||
| 7 | - | - All dashboard pages have h1. | |
| 8 | - | - Forms use canonical `.form-group` / `.form-container` / input-class composition. | |
| 9 | - | - Empty-state markup migrated to `ui::empty_state` macros across the tab partials (third-sitting work; 22 call sites total span dashboards + content pages). | |
| 10 | - | - Selected-state on tabs uses `.tab.is-selected` (canonical). | |
| 11 | - | - `.data-table` and `.compact-table` are used consistently. | |
| 12 | - | - Admin tabs (admin-*) are clean — no inline styles, no `<style>` blocks, no antipatterns. | |
| 13 | - | ||
| 14 | - | ## Drifts | |
| 15 | - | ||
| 16 | - | ### 1. `dashboard-user.html` carries a 16-line page-isolated `<style>` block | |
| 17 | - | ||
| 18 | - | `dashboard-user.html:7-22` defines `.project-card`, `.project-info`, `.project-title`, `.project-meta`, `.project-stats`, `.project-actions`, `.summary-row`, plus a global `h1 { ... }` and `.stat-value` override. | |
| 19 | - | ||
| 20 | - | Two issues: | |
| 21 | - | - The global `h1 { font-size: 2.5rem; ... }` overrides the site-wide h1 treatment for this page only. Bleeds into anything else on the page that uses h1 (which is just the user h1 at line 75 — OK in practice, but a footgun). | |
| 22 | - | - `.project-card` / `.project-title` / `.project-meta` collide with the identically-named classes embedded in `user.html` (the public profile page). Same names, different rules, different files. | |
| 23 | - | ||
| 24 | - | Lift to `style.css` under a `DASHBOARD USER` section. Resolve the `.project-card` name collision with `user.html` by either picking one canonical rule or namespacing one of them (`.dashboard-user-page .project-card` vs `.user-page .project-card`). | |
| 25 | - | ||
| 26 | - | ### 2. `partials/tabs/user_settings.html` carries a 21-line page-isolated `<style>` block | |
| 27 | - | ||
| 28 | - | `user_settings.html:1-22` defines `.settings-layout`, `.settings-nav`, `.settings-nav a`, `.settings-nav a.is-selected`, `.settings-body`, plus a media query. Same anti-pattern. Migrate to `style.css` under a `SETTINGS TAB` section. | |
| 29 | - | ||
| 30 | - | (Notably, this block does use `.is-selected` correctly — the recipe internally conforms to the charter; it's only the file location that's wrong.) | |
| 31 | - | ||
| 32 | - | ### 3. `dashboard-user.html:38, 52` — two bare `<h2>` for account-state notices | |
| 33 | - | ||
| 34 | - | "Account Deactivated", "Account Paused". These are alert-style banners more than section headers. Could carry `.banner--warning h2` styling, or wrap in an `.alert` partial. Don't leave bare. | |
| 35 | - | ||
| 36 | - | ### 4. `dashboard-delete-account.html:36` — one `<a><button>` for "Go to Export Portal" | |
| 37 | - | ||
| 38 | - | Mechanical fix. | |
| 39 | - | ||
| 40 | - | ### 5. `dashboard-export.html` has 31 `.export-card-*` class instances | |
| 41 | - | ||
| 42 | - | `style.css:2618-2652` defines the family under `.export-page` scope. The classes (`.export-cards`, `.export-card`, `.export-card-info`, `.export-card-title`, `.export-card-desc`, `.export-card-meta`) form an undocumented sub-component. Either: | |
| 43 | - | - Document the export-page sub-component in the charter (one page; small surface). | |
| 44 | - | - Refactor to `.card-muted` (or `.card--bordered`) + utility classes for the title/desc/meta breakdown. The `.export-card` shape looks very close to a generic `.card-muted` with a row layout. | |
| 45 | - | ||
| 46 | - | ### 6. `dashboard-blog-editor.html` uses `<button class="primary|secondary">` without `.btn-` prefix | |
| 47 | - | ||
| 48 | - | Lines 43, 44, 46. Equivalent rendering per `style.css:336-385`. Same decision as Phase 4 §5 — pick one convention. | |
| 49 | - | ||
| 50 | - | ## Verdict | |
| 51 | - | ||
| 52 | - | Dashboard surfaces are mostly clean. Two real `<style>` block migrations (`dashboard-user.html` and `user_settings.html` tab) are the biggest items. The `.export-card-*` family is the only undocumented sub-component group in the admin/dashboard tree — small but worth deciding on. Rest is mechanical (one `<a><button>`, two bare h2s, button-class shorthand). |
| @@ -1,68 +0,0 @@ | |||
| 1 | - | # UX Audit — Phase 8: Wizards, Docs, Source Browser, Errors | |
| 2 | - | ||
| 3 | - | Audit ran 2026-05-20. Scope: `templates/wizards/` (20 templates), `doc.html` / `doc_index.html`, `templates/pages/git/*` (12 templates), `health.html`, `error.html`. | |
| 4 | - | ||
| 5 | - | ## What conforms | |
| 6 | - | ||
| 7 | - | - Wizards: every step lives under a `wizard_*` wrapper that emits `<h2>Wizard Name</h2>` then `<h2>Step Name</h2>`; step partials carry their own `<h2>Step Title</h2>`. Wizard `<h2>` is the step-title convention (analogous to the auth-flow page-subtitle pattern from Phase 6). | |
| 8 | - | - All step partials use canonical `.form-group`, `.form-section`, `.form-actions`. | |
| 9 | - | - Source browser (`git/`) is clean: zero inline styles, zero `<style>` blocks, zero `<a><button>`, all leads have h1 or `.git-repo-name`. | |
| 10 | - | - `error.html` lead is now h1 (promoted from `<p>` during Phase 0 §2.10). | |
| 11 | - | - `doc.html` is clean. | |
| 12 | - | ||
| 13 | - | ## Drifts | |
| 14 | - | ||
| 15 | - | ### 1. Wizards use bespoke `-card` families that the charter alias table doesn't fully cover | |
| 16 | - | ||
| 17 | - | `wizard.css` retains rules for `.monetization-card`, `.monetization-card.info-only`, `.content-choice-card`, `.content-choice-card.muted`, plus the `.type-card` / `.pricing-card` primitives that were moved to `style.css`. Remediation §2.1 wanted these absorbed into `.card--selectable` modifiers; the plan deferred this as "cosmetic." Status today: `.type-card` and `.pricing-card` were deduped (good); `.monetization-card` and `.content-choice-card` remain as their own families. | |
| 18 | - | ||
| 19 | - | If the charter is going to canonicalize selectable cards under `.card--selectable`, these are the last two holdouts. | |
| 20 | - | ||
| 21 | - | ### 2. Wizard step headings are `<h2>` (intentional, but undocumented) | |
| 22 | - | ||
| 23 | - | Each step partial leads with `<h2>Step Title</h2>` (e.g. `steps/item/basics.html:4` "Item Basics"). The wrapping `wizard_*.html` template provides the page h1 indirectly (via the wizard wrapper's own `<h2>`). Effectively the same auth-flow pattern from Phase 6 — needs the same charter clarification: which pages use brand-h1 + subtitle-h2, which use page-h1 + section-h2 (with `.section-label`). | |
| 24 | - | ||
| 25 | - | ### 3. `doc_index.html:20` has one bare `<h2>{{ section.name }}</h2>` | |
| 26 | - | ||
| 27 | - | Within a docs landing layout. `doc.html` itself uses Markdown rendering and doesn't have this issue. Pick `.section-header` or `.section-label` and apply. | |
| 28 | - | ||
| 29 | - | ### 4. `git/repo.html` has 2 bare `<h2>` | |
| 30 | - | ||
| 31 | - | Lines 69, 76: "README", "Releases". `git/settings.html:25` has one: "Repository Settings". Add `.section-header`. | |
| 32 | - | ||
| 33 | - | ### 5. `git/settings.html:69` uses inline `onsubmit="return confirm(...)"` | |
| 34 | - | ||
| 35 | - | The only inline JS confirm flow in the source browser. Other deletes in the codebase use `hx-confirm`. Not a CSS issue — but worth standardizing on `hx-confirm` for consistency (and so screen-reader users get a more accessible affordance than the native `confirm()`). | |
| 36 | - | ||
| 37 | - | ### 6. `wizard.css` is 685 lines | |
| 38 | - | ||
| 39 | - | The remediation plan never measured wizard.css against the consolidation work. It still defines selected-state and hover recipes locally (e.g. wizard step indicator, monetization-card hover) instead of inheriting from the canonical recipes in `style.css`. After Phase 1-7 land, a wizard.css consolidation pass would close the loop. | |
| 40 | - | ||
| 41 | - | ## Verdict | |
| 42 | - | ||
| 43 | - | Wizards / docs / source browser are structurally clean. The two real items are (a) decide on the brand-h1 + subtitle-h2 pattern at the charter level — it shows up across auth, wizards, and arguably the marketing prose pages — and (b) at some point fold `wizard.css` into the canonical primitive system. Everything else is small. | |
| 44 | - | ||
| 45 | - | --- | |
| 46 | - | ||
| 47 | - | # Audit sweep complete | |
| 48 | - | ||
| 49 | - | Phases 0-8 done. The codebase is in a strong baseline state: token system in place, primitives largely canonical, the worst Phase 0 offenders (inline `<style>` blocks in `buy.html` / `item.html` / `error.html`, 1,350 inline-style attrs, checkmark glyphs, pure black/white) are resolved. | |
| 50 | - | ||
| 51 | - | ## Remediation outcomes (post-audit pass, 2026-05-20) | |
| 52 | - | ||
| 53 | - | Six items landed in the same session as the audit; two findings retracted as incorrect: | |
| 54 | - | ||
| 55 | - | - **Landed**: `pricing.html` tier-picker keyboard-accessibility fix; `.tier-option.selected` → `.is-selected`; full `<a><button>` antipattern sweep (44 sites across 13 files); four page-isolated `<style>` blocks migrated (`user.html` deleted as dead duplicate, `dashboard-user.html` scoped under `.dashboard-user-page` resolving the `.project-card` name collision with `user.html`, `library_feed.html` scoped under `.library-page`, `user_settings.html` as global primitives); follow-state synonyms (`.following`, `.is-following`) → `.is-selected` across 4 templates + 4 CSS rules (also fixed a latent bug in `follow_button.html` where a duplicate `class=` attribute meant `.is-following` never applied in the browser); `.intro` lifted to `.page-intro` primitive. | |
| 56 | - | - **Retracted**: `.coming-soon` modifier finding (Phase 1) — rule actually exists at `style.css:6489`, the original grep missed it. `cart-group__title` BEM `__` finding (Phase 4) — claimed it was the only BEM `__` in the codebase; actually 112 template usages across 20 block prefixes, it's an established convention. | |
| 57 | - | ||
| 58 | - | ## Remaining work after this session | |
| 59 | - | ||
| 60 | - | - **Heading-convention decision** for prose pages (creators, policy, changelog, fan_plus, stripe_disclaimer, purchase, cart, library_downloads, library_locked, doc_index, git/repo, git/settings). Phase 6 surfaced the brand-h1 + subtitle-h2 pattern as intentional design for auth and wizards; the charter needs to decide which scopes use which pattern. | |
| 61 | - | - **`.export-card-*` family** undocumented sub-component in `dashboard-export.html` (Phase 7). | |
| 62 | - | - **`.login-cta-btn`** undocumented one-off in `project.html` (Phase 3). | |
| 63 | - | - **`.monetization-card` / `.content-choice-card`** in `wizard.css` — last selectable-card families outside the canonical primitive system. Deferred from remediation §2.1 ("cosmetic rename" decision). | |
| 64 | - | - **`wizard.css` consolidation pass** (Phase 8 §6) — 685 lines still define selected-state and hover recipes locally instead of inheriting from canonical recipes. | |
| 65 | - | - **`git/settings.html:69`** inline `onsubmit="return confirm(...)"` — standardize on `hx-confirm`. | |
| 66 | - | - **Button class shorthand decision** (`<button class="primary">` vs `<button class="btn-primary">`) — both render identically; charter should pick or accept both. | |
| 67 | - | ||
| 68 | - | Remediations and outstanding charter decisions are tracked by site section in `docs/todo.md` § "UX audit remediations." |
| @@ -1,224 +0,0 @@ | |||
| 1 | - | # UX Remediation Pre-Plan (pre-Phase-1) | |
| 2 | - | ||
| 3 | - | **Status (2026-05-20): bounded consolidation items complete; large migrations deferred. Macro adoption sweep and remaining CSS literal cleanup completed in a third sitting.** | |
| 4 | - | ||
| 5 | - | Done in first sitting: | |
| 6 | - | - **1.1 Tokens** — in `style.css:200-211`. | |
| 7 | - | - **1.2 Shared partials** — Askama macros defined in `partials/_ui.html` (but see third-sitting note below: at this point none were actually wired in). | |
| 8 | - | - **1.3 Brand fixes** — `\2713` and `✓` checkmarks replaced; `.sandbox-banner` raw `#6c5ce7` / `#fff` migrated to tokens. | |
| 9 | - | - **2.2 Empty-state** — `.chart-empty` migrated to `.empty-state.empty-state--chart` (2 instances). `.preview-cover-empty` is a thumbnail placeholder, semantically distinct from empty-state; left alone. | |
| 10 | - | - **2.4 Selected-state** — all `.active` selection classes migrated to `.is-selected`; matching CSS aliases dropped. Visibility toggles (`.tab-content.active`, `.section-panel.active`, `.editor-panel.active`) intentionally retained. | |
| 11 | - | - **2.8 Section-header** — h2s in `item.html` already use `class="section-header"`; the inline border-bottom rule was dead code, removed with 2.11. | |
| 12 | - | - **2.9 Hidden utility** — 0 inline `style="display:none"` remain. | |
| 13 | - | - **2.11 Page-isolated `<style>` blocks** — `item.html`'s 78-line block removed entirely; all rules already covered by `.item-page`-scoped globals or co-classes (`.content-section`, `.card-muted`, `.list-row`, `.section-header`). `buy.html` and `error.html` were done previously. | |
| 14 | - | - **2.12 Inline styles** — 6 instances remain, all server-computed bar widths/heights (the documented permitted exception). Success criterion (<100) overwhelmingly met. | |
| 15 | - | - Dead button-class names removed from CSS comments (`.notify-btn`, `.paywall-btn` had no rules or template usage). | |
| 16 | - | ||
| 17 | - | Done in second sitting: | |
| 18 | - | - **2.6 Notification surface** — on inspection, `.toast` / `.banner` / `.alert` / `.info-box` / `.warning-box` / `.error-message` each serve a meaningfully distinct communication role; merging them would flatten useful nuance. Resolution: added a role-clarity comment block at `style.css:1183` so future authors know which to reach for. **Do not merge** these components. | |
| 19 | - | - **2.10 Page heading** — audited every page in `templates/pages/`. Three had no h1: `discover.html` (added `<h1>Discover</h1>`), `tag_tree.html` (added `<h1>Browse Tags</h1>`), `error.html` (promoted `.error-page-message` from `<p>` to `<h1>`). All other pages already lead with h1. | |
| 20 | - | ||
| 21 | - | Done in third sitting: | |
| 22 | - | - **CSS literal cleanup** — remaining `#000` / `#fff` / `#666` / `#5147e5` literals in `style.css` and `wizard.css` migrated to tokens: video-surface backgrounds → `var(--primary-dark)`, `.copy-btn` color → `var(--primary-light)` + `var(--radius-md)`, `.video-cover-placeholder` `#666` → `var(--text-muted)`, `.stripe-connect-cta` `#fff` → `var(--primary-light)` and raw hover `#5147e5` → opacity-dim recipe. The only remaining raw `#000` / `#fff` are the token definitions themselves at `style.css:155-156`. | |
| 23 | - | - **Empty-state macro adoption** — the `_ui.html` macros existed since the first sitting but had **zero call sites** in the codebase; "1.2 Shared partials" was marked done because the macros were defined, not because they were used. Wired in this sitting: 22 templates now call `ui::empty_state` / `empty_state_with_action` / `empty_state_compact` / `empty_state_chart` (added the compact and chart variants; made `title` optional via empty-string sentinel). Six sites left intentionally bespoke with documented reasons: `git/issues.html` (dynamic body), `git/repos.html` (nested code blocks), `discover_results.html` ×4 (inline anchor in hint), `project_settings.html` (JS-referenced id), `project_blog.html` tab (custom `.blog-empty-hint`), `project_content.html` (--lg with templated URL — Askama macro args don't accept `format!()` against template-scope fields). | |
| 24 | - | - **Aspirational macros dropped** — `loading_skeleton`, `form_field`, `confirm_dialog`, `pagination`, and (briefly added then unused) `empty_state_lg_action` deleted from `_ui.html`. Survey of real call sites showed none of these macro shapes fit: confirms use `hx-confirm` or full-page magic-link forms (no modal-overlay confirms); pagination uses HTMX-rich numbered ranges that the prev/next macro can't represent; form-groups carry per-field input types, classes, and attrs that the rigid macro can't express; loading-skeleton was net-new with no existing usage pattern. Better to delete than to mislead future readers into thinking these are the canonical shapes. | |
| 25 | - | ||
| 26 | - | Deferred — these turned out to be *already met at the CSS level via aliased selectors*; further "consolidation" would be cosmetic template renames with no visual benefit: | |
| 27 | - | - **2.1 Card family** — 22 bespoke `-card` classes, but the visual styling is already canonical: `.feature-card` / `.tier-card` / `.use-case-card` / `.fork-card` are grouped under one `.card--bordered` rule at `style.css:1035-1038`; `.stat-card` / `.analytics-card` / `.account-tip-card` are aliased to `.card-muted`. The bespoke names persist as semantic labels in templates (which is fine). Renaming templates to canonical class names is purely cosmetic. | |
| 28 | - | - **2.3 Button family** — same pattern: most bespoke `-btn` classes have CSS rules that are page-scoped tweaks on top of canonical button styling. The documented variants (`.big-button`, `.order-btn`, `.play-button`, `.speed-button`, `.shortcuts-help-btn`, `.toast-retry-btn`) genuinely differ from `.btn-primary` / `.btn-secondary` / `.btn-danger`. Dead names (`.notify-btn`, `.paywall-btn`) were already removed from the CSS comments. | |
| 29 | - | - **2.5 Hover recipe** — cross-cutting and depends on 2.3; defer with 2.3. | |
| 30 | - | - **2.7 Status indicator** — `.save-status` and `.notify-status` already have the proposed `.badge--inline` semantics (inline colored text, no padding bump). Adding a `.badge--inline` modifier and migrating would be a rename without visual change. | |
| 31 | - | ||
| 32 | - | ## What "consolidated" looks like now | |
| 33 | - | ||
| 34 | - | The original Phase 0 inventory was correct *as of the day it was written*, but a lot of consolidation has happened since via CSS aliasing rather than template renames. The codebase now has: | |
| 35 | - | ||
| 36 | - | - One token system (color, type, spacing, radius, shadow). | |
| 37 | - | - Four shared partial macros for the empty-state shape (`empty_state`, `empty_state_with_action`, `empty_state_compact`, `empty_state_chart`), wired into 22 call sites. Other aspirational macros were dropped because no real call sites matched their shape. | |
| 38 | - | - A canonical `.is-selected` for every selection state, with visibility toggles intentionally kept on `.active`. | |
| 39 | - | - One `.card-muted` rule that covers nine bespoke "muted card" names. | |
| 40 | - | - One `.card--bordered` rule that covers four bespoke "bordered card" names. | |
| 41 | - | - Six clearly-distinct notification components with documented roles. | |
| 42 | - | - Every page lead-heading is h1. | |
| 43 | - | - 6 inline `style="..."` instances, all server-computed bar dimensions (the documented exception). | |
| 44 | - | - No checkmark glyphs in templates or CSS. Pure `#000` / `#fff` only appear as the `--primary-dark` / `--primary-light` token definitions at `style.css:155-156`; every other use site reads through the tokens. | |
| 45 | - | ||
| 46 | - | The success criteria from Phase 0 are met. The remaining bespoke class names in templates are semantic labels riding on canonical styling, not duplicates of UI shapes. | |
| 47 | - | ||
| 48 | - | Before running Phases 1-8 of the UX audit sweep, do a consolidation pass. The Phase 0 inventory (`phase-0.md`) showed that the platform reinvents the same UX in several different shapes — 12 card variants, 7 empty-state classes, 14+ ad-hoc button classes, five "selected" recipes. Auditing each surface against the charter is wasted effort until those duplicates are merged. After this pass, the audits become conformance checks instead of style debates. | |
| 49 | - | ||
| 50 | - | This plan has two parts: | |
| 51 | - | ||
| 52 | - | 1. **Foundation** — tokens, partials, and brand fixes from Phase 0. | |
| 53 | - | 2. **Consolidation** — for every UX pattern expressed multiple ways, pick one and migrate. | |
| 54 | - | ||
| 55 | - | Charter is at `docs/design-system.md`. Each remediation below lands as a small, independent PR-equivalent commit. | |
| 56 | - | ||
| 57 | - | ## Part 1 — Foundation | |
| 58 | - | ||
| 59 | - | ### 1.1 Add the three missing token tiers | |
| 60 | - | ||
| 61 | - | Add to `:root` in `static/style.css`: | |
| 62 | - | ||
| 63 | - | ```css | |
| 64 | - | /* Spacing scale */ | |
| 65 | - | --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; | |
| 66 | - | --space-4: 1rem; --space-5: 1.5rem; --space-6: 2rem; | |
| 67 | - | /* Radius */ | |
| 68 | - | --radius-sm: 2px; --radius-md: 4px; --radius-round: 50%; | |
| 69 | - | /* Shadow */ | |
| 70 | - | --shadow-1: 0 1px 3px rgba(0,0,0,0.06); | |
| 71 | - | --shadow-2: 0 2px 8px rgba(0,0,0,0.10); | |
| 72 | - | --shadow-3: 0 4px 12px rgba(0,0,0,0.15); | |
| 73 | - | ``` | |
| 74 | - | ||
| 75 | - | Migrate existing shadow recipes onto the three tiers. Replace `0.6rem` (`wizard.css:39`) and other off-scale strays. Do not migrate every existing `4px` radius in one pass — only when touching a rule for another reason. Goal is to make the tokens authoritative going forward. | |
| 76 | - | ||
| 77 | - | ### 1.2 Shared macros — outcome | |
| 78 | - | ||
| 79 | - | The original plan called for five shared partials. Result after surveying real call sites: | |
| 80 | - | ||
| 81 | - | | Macro | Status | Notes | | |
| 82 | - | |---|---|---| | |
| 83 | - | | `empty_state` (+ `_with_action`, `_compact`, `_chart`) | **Shipped, 22 call sites** | Lives in `partials/_ui.html` as Askama macros, not standalone partials. `title` arg is optional via empty-string sentinel. Six sites stay bespoke for documented reasons (dynamic body, nested code, anchor in hint, JS-referenced id, custom hint class, templated URL). | | |
| 84 | - | | `loading_skeleton` | **Dropped** | Net-new with no existing pattern to absorb. | | |
| 85 | - | | `form_field` | **Dropped** | Real `.form-group` blocks each carry custom input type, classes, placeholders, patterns, etc. that the rigid 5-arg macro couldn't represent. | | |
| 86 | - | | `confirm_dialog` | **Dropped** | No realistic call sites. Every confirm flow uses `hx-confirm`, inline `confirm()`, or a full-page magic-link form — none use modal-overlay. | | |
| 87 | - | | `pagination` | **Dropped** | Real usages (`feed.html` numbered range, `discover_results.html` HTMX buttons with filter inclusion) are substantially richer than a prev/next macro. | | |
| 88 | - | ||
| 89 | - | ### 1.3 Brand-conformance fixes | |
| 90 | - | ||
| 91 | - | - Remove the U+2713 checkmark in `static/wizard.css:435-443` (`.preview-checklist li::before`); replace with text-prefix bullet or `.badge-success`. | |
| 92 | - | - Remove the `✓` checkmark entity on the tag-follow button in `templates/pages/discover.html:86`; replace with "Following" / "Follow" text states. | |
| 93 | - | - Swap pure-black `#000` in `static/media-player.css:107` (`.video-display`) for `var(--detail)`. | |
| 94 | - | - Swap pure-white `#fff` in `templates/pages/buy.html:20` (`.buy-card`) for `var(--light-background)`. | |
| 95 | - | - Swap Bootstrap yellow in `style.css:1755` (`#restart-banner`) for `var(--warning-bg)` + `var(--warning-border)`. | |
| 96 | - | - Replace hardcoded `#6c5ce7` and `rgba(108,92,231,...)` literals with `var(--highlight)` and `var(--highlight-faint)` in `buy.html` and `wizard.css:172`. | |
| 97 | - | ||
| 98 | - | ## Part 2 — Consolidation: same UX, different expressions | |
| 99 | - | ||
| 100 | - | Each row below is a pattern the codebase implements in multiple ways. The "Pick" column is the proposal. Migrate every other variant onto it; delete the discarded classes once references are gone. | |
| 101 | - | ||
| 102 | - | ### 2.1 Card family (12 variants -> 2) | |
| 103 | - | ||
| 104 | - | | Class | File | Used by | | |
| 105 | - | |---|---|---| | |
| 106 | - | | `.card` | `style.css:716` | Generic content card | | |
| 107 | - | | `.grid-card` + `.grid-card-*` | `style.css` | Discover grid items | | |
| 108 | - | | `.feature-card` | `style.css` | Marketing | | |
| 109 | - | | `.fork-card` | `style.css` | Source browser | | |
| 110 | - | | `.stat-card` | `style.css` | Dashboard metrics | | |
| 111 | - | | `.tier-card` | `style.css` | Pricing | | |
| 112 | - | | `.type-card` + `.type-card-inner/-label/-desc` | `style.css`, `wizard.css` (duplicated) | Wizard type picker | | |
| 113 | - | | `.analytics-card` | `style.css` | Dashboard | | |
| 114 | - | | `.use-case-card` | `style.css` | Marketing | | |
| 115 | - | | `.content-choice-card` | `wizard.css` | Wizard | | |
| 116 | - | | `.monetization-card` | `wizard.css` | Wizard | | |
| 117 | - | | `.pricing-card` + `.pricing-card-inner/-label/-desc` | `wizard.css` | Wizard pricing | | |
| 118 | - | ||
| 119 | - | **Pick**: `.card` (block content) and `.card--selectable` (radio-like; absorbs `.type-card`, `.pricing-card`, `.content-choice-card`, `.monetization-card`). Grid layouts use `.card.card--grid`. Marketing variants (`feature-card`, `use-case-card`, `tier-card`) become `.card` with content-level styling. | |
| 120 | - | ||
| 121 | - | The `.type-card` / `.pricing-card` *inner* wrappers are duplicated verbatim between `style.css` and `wizard.css` — that's a straight delete-the-duplicate fix. | |
| 122 | - | ||
| 123 | - | ### 2.2 Empty-state family (7 variants -> 1) | |
| 124 | - | ||
| 125 | - | `.empty-state`, `.chart-empty`, `.collection-picker-empty`, `.docs-search-empty`, `.git-repos-empty`, `.results-empty`, `.preview-cover-empty`. | |
| 126 | - | ||
| 127 | - | **Pick**: `partials/empty_state.html` with optional `--compact` modifier (currently expressed as the inline `padding: 1rem` override in `feed.html`). Slots: title, body, optional action. Migrate the seven `*-empty` classes onto it; delete after. | |
| 128 | - | ||
| 129 | - | ### 2.3 Button family (14+ ad-hoc -> 3 canonical + utilities) | |
| 130 | - | ||
| 131 | - | Ad-hoc today: `.order-btn`, `.view-btn`, `.toggle-btn`, `.big-button`, `.link-button`, `.notify-btn`, `.paywall-btn`, `.play-button`, `.speed-button`, `.tag-follow-btn`, `.toast-retry-btn`, `.shortcuts-help-btn`, `.show-more-btn`, plus `.action-buttons` row. | |
| 132 | - | ||
| 133 | - | **Pick**: keep `.btn-primary` / `.btn-secondary` / `.btn-danger`. Add two utilities: | |
| 134 | - | - `.btn--icon` for icon-only / micro buttons (`order-btn`, `play-button`, `speed-button`, `shortcuts-help-btn`). | |
| 135 | - | - `.btn--link` for buttons that look like links (`.link-button`, `.show-more-btn`, `.toast-retry-btn`). | |
| 136 | - | ||
| 137 | - | `.big-button` becomes `.btn-primary.btn--large`. `.view-btn` (toolbar segment) becomes `.btn-secondary` with `.is-selected`. `.tag-follow-btn` is a `.btn-secondary` with `.is-selected` when followed. Delete `.notify-btn`, `.paywall-btn` — they were one-off colors that map to primary or secondary. | |
| 138 | - | ||
| 139 | - | ### 2.4 Selected / active state (5 spellings -> 1) | |
| 140 | - | ||
| 141 | - | Today: `.tab.active`, `.filter-item.active`, `.view-btn.active`, `.badge.active`, `.type-card input:checked + .type-card-inner`. | |
| 142 | - | ||
| 143 | - | **Pick**: `.is-selected` modifier with the recipe `background: var(--highlight-faint); border-color: var(--focus-ring);`. Migrate all five. The CSS `:checked` selector pattern is replaced by server-side rendering of `.is-selected` on the wrapper after form submit, matching how `.tab` works today. | |
| 144 | - | ||
| 145 | - | ### 2.5 Hover recipe (3 patterns -> 2 by component type) | |
| 146 | - | ||
| 147 | - | Today: cards swap background, buttons fade opacity, type-cards change border, link-rows do both. | |
| 148 | - | ||
| 149 | - | **Pick**: opacity-dim (`opacity: 0.85`) for solid-fill components (filled buttons, badges). Background-step (`--background` -> `--light-background` -> `--surface-muted`) for container components (cards, link-rows, list items). Border-tint is retired; selectable cards instead use `.is-selected` on click. | |
| 150 | - | ||
| 151 | - | ### 2.6 Notification surface (4 components -> clarified roles) | |
| 152 | - | ||
| 153 | - | Today: `.toast`, `.alert`, `.error-message`, `.warning-box`, `.info-box`, `partials/form_status.html`, `partials/error_fragment.html`. These overlap. | |
| 154 | - | ||
| 155 | - | **Pick**: clear role per component, all four kept but disambiguated: | |
| 156 | - | - **Toast** — transient, JS-dismissible, bottom-right. `partials/toast.html`. | |
| 157 | - | - **Alert** — inline, persistent on the page where it's emitted. Use `partials/alert.html` with severity variants. Replaces `.warning-box`, `.info-box`, `.error-message` *when used as a page-level notice*. | |
| 158 | - | - **Field error** — inline under a form field, set via `partials/form_field.html` error slot. | |
| 159 | - | - **Banner** — page-top notice (sandbox mode, restart pending, founder pricing announcement). New `.banner` class with `.banner--info` / `.banner--warning`. Absorbs `.landing-founder-banner`, the inline sandbox banner in `dashboard-project.html:25`, and `#restart-banner`. | |
| 160 | - | ||
| 161 | - | ### 2.7 Status indicator (badge vs custom dot vs colored text -> badge) | |
| 162 | - | ||
| 163 | - | Today: `.badge` family (clean), `.username-status`, `.save-status`, `.notify-status`, `.diff-status-*` (custom). Plus raw colored text in dashboards. | |
| 164 | - | ||
| 165 | - | **Pick**: `.badge` with semantic variants for all status. `.diff-status-*` keeps its own family because it's in the source browser and has distinct semantics (added / modified / deleted file in a tree). `.save-status` and `.notify-status` become `.badge` with `.badge--inline` modifier (no padding bump). | |
| 166 | - | ||
| 167 | - | ### 2.8 Section header (2 patterns -> 1) | |
| 168 | - | ||
| 169 | - | Today: `.section-header` class and ad-hoc h2 with inline `border-bottom`. | |
| 170 | - | ||
| 171 | - | **Pick**: `.section-header`. Migrate the inline variants. | |
| 172 | - | ||
| 173 | - | ### 2.9 Hidden / display utility (2 patterns -> 1) | |
| 174 | - | ||
| 175 | - | Today: `style="display: none"` (e.g. `login.html:61,65`) and `.hidden` class (`style.css:2263`). | |
| 176 | - | ||
| 177 | - | **Pick**: `.hidden`. Sweep templates for the inline form. | |
| 178 | - | ||
| 179 | - | ### 2.10 Page heading (2 patterns -> contextual) | |
| 180 | - | ||
| 181 | - | Today: centered h1 (Young Serif, `style.css:137`) and `.section-header` h2. Some pages use h2-as-title. | |
| 182 | - | ||
| 183 | - | **Pick**: every page top has one centered h1 in `--font-heading`. Sub-areas of the page use `.section-header` with h2 in `--font-mono`. Audit the dashboards specifically — many use h2-as-title because they're loaded into a tabbed layout; clarify whether the tab title or an inline h1 is the page title. | |
| 184 | - | ||
| 185 | - | ### 2.11 Page-isolated `<style>` blocks (3 grandfathered -> 0) | |
| 186 | - | ||
| 187 | - | `buy.html` (~119 lines), `item.html:51-127` (~114 lines), `error.html:18-61` (~44 lines). Each duplicates primitives that exist elsewhere under different names. | |
| 188 | - | ||
| 189 | - | **Pick**: migrate to `style.css` sections named `BUY`, `ITEM`, `ERROR-PAGE`. Convert hardcoded colors to tokens. Reuse `.card`, `.section-header`, `.btn-primary` where the bespoke class is just a renamed primitive (`.buy-card` -> `.card`; `.buy-btn` -> `.btn-primary`; the `error-actions` row -> `.form-actions`). | |
| 190 | - | ||
| 191 | - | ### 2.12 Inline `style="..."` sweep (1,350 instances) | |
| 192 | - | ||
| 193 | - | Largest concentrations (by spot-check): `dashboard-project.html`, `dashboard-blog-editor.html`, `dashboard-item.html`, `login.html`, `discover.html`, `wizard_join.html`, `item.html`. | |
| 194 | - | ||
| 195 | - | Strategy: don't do them all in one pass. Sweep by template, one PR per template, and for each occurrence either (a) replace with an existing utility, (b) extend a primitive, or (c) add a new utility class (`.muted-link`, `.footer-link`, `.centered-text`) — never new bespoke classes. | |
| 196 | - | ||
| 197 | - | The single permitted exception is `style="--var: value"` for server-computed CSS custom properties (progress bars, avatar fallback tints). | |
| 198 | - | ||
| 199 | - | ## Sequencing | |
| 200 | - | ||
| 201 | - | Foundation lands first; consolidation can run in parallel after that, but selected-state and button-family migrations should land before the surface audits start. | |
| 202 | - | ||
| 203 | - | 1. **Foundation (Part 1)** — must complete before any consolidation. Tokens, partials, brand fixes. Roughly one sitting. | |
| 204 | - | 2. **Selected-state unification (2.4)** — touched by half the other consolidations. | |
| 205 | - | 3. **Button-family + hover recipe (2.3, 2.5)** — cross-cutting; affects every surface. | |
| 206 | - | 4. **Empty-state, notification surface, banner (2.2, 2.6)** — independent; pick up in any order. | |
| 207 | - | 5. **Card-family consolidation (2.1)** — largest single migration; do after selected-state lands. | |
| 208 | - | 6. **Status, section-header, hidden utility, page heading (2.7-2.10)** — small, batch them. | |
| 209 | - | 7. **Page-isolated styles (2.11)** — three templates, one PR each. | |
| 210 | - | 8. **Inline-style sweep (2.12)** — per-template, ongoing; declare "done" when no template has more than 5 inline `style=` attrs. | |
| 211 | - | ||
| 212 | - | After step 8, Phase 1 of the audit sweep runs against the consolidated state. | |
| 213 | - | ||
| 214 | - | ## Success criteria for the pre-plan as a whole | |
| 215 | - | ||
| 216 | - | - `style.css` has spacing / radius / shadow tokens; no off-scale or raw-hex values added after this pass. | |
| 217 | - | - The charter primitive table in `docs/design-system.md` has one canonical class per row, with no "**to add**" markers. | |
| 218 | - | - Grep for `style="` in `templates/` returns under 100 hits (down from ~1,350). | |
| 219 | - | - Grep for `class=".*-card"` in `templates/` returns only `.card` and `.card--*` modifier forms (no bespoke `-card` classes). | |
| 220 | - | - No checkmark glyphs or `✓` entities in any template. | |
| 221 | - | - No `#000` / `#fff` / Bootstrap-derived literals at use sites in any CSS file. The `--primary-dark` and `--primary-light` token definitions at `style.css:155-156` are the only places those literals appear. | |
| 222 | - | - Every "selected" state in the UI is `.is-selected`. | |
| 223 | - | ||
| 224 | - | Once the success criteria pass, the audit sweep can proceed. Surface findings in Phases 1-8 are then expected to be sparse and stack-specific rather than fundamental. |