Skip to main content

max / makenotwork

v0.6.9: UX audit charter decisions + sr-only file inputs Resolves the three charter decisions left from the Phase 1-8 UX audit and lands the file-input accessibility upgrade. - Buttons: migrate 67 sites from class="primary|secondary|danger" to class="btn-*"; drop button.shorthand selectors from style.css (kept .btn-* only). Form works on both <button> and <a>. - Headings: add .brand-h1 / .page-title / .subtitle-h2 / .subsection-title; sweep 93 templates so no bare <h1>/<h2> remains outside the JS markdown renderer in item_text_editor.html. - BEM separator: rename 76 distinct block__elem tokens across 15 files to single-dash kebab-case; no collisions. Modifier -- preserved. - File inputs: 13 <input type="file"> sites swapped from .hidden / .wizard-file-input to .sr-only so screen readers and keyboard reach the input; trigger buttons unchanged. .wizard-file-input rule removed from wizard.css. - Charter: docs/design-system.md gains rules 10-12 codifying the heading, button, and kebab-case conventions; primitive table updated. - Todo log: UX audit remediations section refreshed; status updated. - Includes UX-audit phase 1-8 reports (untracked until now).
Co-Authored-By
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-21 01:17 UTC
Commit: 7eff0b4fe96a96610754d89ccca6bd7f335168f0
Parent: 67e74d4
161 files changed, +1624 insertions, -1160 deletions
@@ -3551,7 +3551,7 @@
3551 3551
3552 3552 [[package]]
3553 3553 name = "makenotwork"
3554 - version = "0.6.6"
3554 + version = "0.6.9"
3555 3555 dependencies = [
3556 3556 "anyhow",
3557 3557 "argon2",
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makenotwork"
3 - version = "0.6.6"
3 + version = "0.6.9"
4 4 edition = "2024"
5 5 license-file = "LICENSE"
6 6
@@ -39,7 +39,7 @@
39 39 | Container | `.container` | `--narrow` (600px), `--medium` (800px), `--wide` (900px) | Default `max-width: 1200px` |
40 40 | Toolbar / header bar | `.stack-row` | `--bordered`, `--tight`, `--top` | Title-left + actions-right |
41 41 | Inline form row | `.field-row` | child `.form-group.is-grow` / `.is-grow-2` | Input(s) + button bottom-aligned |
42 - | Vertical list row | `.list-row` | parts: `__title`, `__meta` | Flex with bottom border |
42 + | Vertical list row | `.list-row` | parts: `-title`, `-meta` | Flex with bottom border |
43 43 | Two-col form grid | `.form-row` | (none) | `grid-template-columns: 1fr 1fr` |
44 44 | Cover-image row | `.cover-row` + `.cover-thumb` + `.cover-empty` | (none) | 120×120 thumbnail picker |
45 45
@@ -49,10 +49,10 @@
49 49 |---|---|---|---|
50 50 | Page section box | `.content-section` | (none) | — |
51 51 | Card | `.card` | `.card-muted` (surface-muted fill), `.card--bordered`, `.card--selectable` | `:hover`, `:focus-within`, `.is-selected` |
52 - | Button | `button.primary` / `.secondary` / `.danger` | `.small`, `.btn-compact`, `.btn-link`, `.saved` | `:hover`, `:focus-visible`, `:disabled`, `.htmx-request` |
52 + | Button | `.btn-primary` / `.btn-secondary` / `.btn-danger` | `.btn--large`, `.btn--icon`, `.btn--link`, `.small`, `.saved` | `:hover`, `:focus-visible`, `:disabled`, `.htmx-request` |
53 + | Heading | `.brand-h1` (wordmark), `.page-title` (h1), `.subtitle-h2` (auth/wizard h2), `.subsection-title` (default h2), `.section-header` (h2 with bottom border) | — | — |
53 54 | Input shape | (base `input`/`textarea`/`select`) | `.input--xs`, `.input--sm`, `.input--mono`, `.input--upper`, `.input--numeric` | `:focus`, `:disabled` |
54 55 | Form field | `.form-group` + `.hint` | `.form-group--error` + `.field-error` | (via `_ui.html` macro `form_field`) |
55 - | Section header | `.section-header` (h2 with bottom-border) | — | — |
56 56 | Section lead | `.section-lead` | with `.mb-3` / `.text-sm` / `.dimmed` utilities | — |
57 57 | Section divider | `.section-divider` | — | — |
58 58 | Section grouping label | `.section-group-label` | — | — |
@@ -67,7 +67,7 @@
67 67 | Empty state | `.empty-state` (or `_ui.html` macro) | `--compact`, `--chart`, `--lg` | — |
68 68 | Loading skeleton | `partials/loading_skeleton.html` | row, card, list | — |
69 69 | Progress bar | `.progress-bar-container` + `.progress-bar` | `--slim` (6px), `--rounded`, `.progress-bar--highlight` (purple, default green) | — |
70 - | Upload status block | `.upload-status` + `__row` | `__msg.is-success`, `__msg.is-error` | — |
70 + | Upload status block | `.upload-status` + `-row` | `-msg.is-success`, `-msg.is-error` | — |
71 71 | Status pill | `.field-status` / `.save-status` | `.success`, `.error`, `.saving` | — |
72 72 | Table | `.data-table` (rich), `.compact-table` (small mono) | `.minw-300..800` for horizontal scroll min-width | `.sortable.ascending`, `.sortable.descending` |
73 73 | Tabs | `.tabs` + `.tab` | — | `.tab.is-selected` |
@@ -139,6 +139,9 @@
139 139 7. **Destructive actions** use `.danger` button class plus the `confirm_dialog` macro. No bare destructive buttons.
140 140 8. **Spacing values come from `--space-*` tokens.** Off-scale values are bugs.
141 141 9. **Page-scoped CSS is a last resort.** Default to composing layout + component primitives. Page-scoped sections exist for genuinely page-specific layout (grids, long-form typography, marketing heroes) — not for shapes that are cards or list rows in disguise.
142 + 10. **No bare `<h1>` / `<h2>` in templates.** Pick a heading class: `.brand-h1` (wordmark), `.page-title` (page h1), `.subtitle-h2` (page subtitle on auth/wizards), `.subsection-title` (default h2 inside dashboards/tabs/partials), or `.section-header` (prose sub-section with bottom border). Bare tags inside server-rendered markdown / user content (e.g. `partials/item_text_editor.html` JS preview) are the only exception.
143 + 11. **Button class is `.btn-primary` / `.btn-secondary` / `.btn-danger`.** Works on both `<button>` and `<a>` (the `<a><button>` antipattern is banned — use `<a class="btn-primary">…</a>`). The bare `button.primary` shorthand has been retired.
144 + 12. **Class names use kebab-case only.** No BEM `__` separator (retired 2026-05-20 — was 112 sites across 20 prefixes, now flattened to single-dash). Modifier `--` (e.g. `.card--bordered`) is still allowed.
142 145
143 146 ## How to extend
144 147
@@ -8,7 +8,7 @@
8 8
9 9 **Stripe webhook delivery: RESOLVED 2026-05-17.** `evt_1TY98u0AcRNJbwd4vkSfLZZI` landed at 18:10:25 UTC, transaction completed, 0 pending in DB. Whatever was fixed in the Stripe Dashboard between 2026-05-16 and 2026-05-17 cleared it. No code change involved.
10 10
11 - **Next code work (LLM-doable):** Phase 1 of the UX audit sweep against the consolidated state (see `## UX audit sweep` below). Run `/ux-audit` skill scoped to `pages/index.html`, `use_cases.html`, `pricing.html`, `creators.html`, `fan_plus.html`, `changelog.html`, `policy.html`. The Phase 0 charter + remediation pre-plan are both complete — surface audits can now run.
11 + **Next code work (LLM-doable):** UX audit sweep phases 0-8 complete (2026-05-20). 16 remediations landed across three same-day sessions (see `## UX audit sweep (phased)` § "UX audit remediations" → "Landed 2026-05-20"). All three charter decisions (button class, heading classes, BEM separator) resolved and codified as charter rules 10-12. Two original findings retracted as incorrect data. Remaining items are three cleanup tasks (`.sr-only` upgrade on file inputs, `.landing-founder-banner` fate, orphan CSS sweep). None are launch blockers.
12 12
13 13 ---
14 14
@@ -194,19 +194,56 @@
194 194 - [ ] Delete `.landing-founder-banner` if folded into hero-callout primitive (currently kept separate per charter).
195 195 - [ ] Orphan CSS cleanup — ~30 truly unused class names in `style.css` deferred (e.g. `.tier-grid`, `.search-box`, `.signup-container`, `.button-stack`). Per-class judgement; revisit when bandwidth allows.
196 196
197 - - [ ] **Phase 0 — Design-system conformance audit.** *(Complete 2026-05-19.)* Before auditing surfaces, audit the *primitives*. The site should behave like an OS: a small set of design rules (spacing scale, type scale, color tokens, button/input/card/dialog/table/form/empty-state shapes, hover/focus/selected/disabled state recipes, loading/error/empty triad) that compose every screen. Read `static/style.css`, `static/media-player.css`, `static/wizard.css`, `_meta/docs/brand.md`, and a representative span of `templates/partials/*` + `pages/*`. Produce: (a) the inferred primitive set — what tokens and components *actually* exist in CSS/markup today; (b) divergence map — places where a screen reinvents a primitive (one-off `<div class="...">` shapes, ad-hoc spacing, bespoke buttons, duplicated empty/error states); (c) gaps — primitives the system *should* have but doesn't (e.g. no shared empty-state component, no destructive-button variant, no loading-skeleton recipe); (d) a written design-system charter (≤2 pages, `docs/design-system.md`) that names every primitive and its single canonical class/partial. Output gates the rest of the sweep: Phases 1–8 audit *conformance to the charter*, not just universal principles. Fixes in Phase 0 land as CSS/partial consolidation before any surface phase runs.
197 + - [x] **Phase 0 — Design-system conformance audit.** *(Complete 2026-05-19.)* Produced `docs/ux-audit/phase-0.md` (inventory + divergence map) and `docs/design-system.md` (charter).
198 198
199 - - [ ] **Phase 1 — Public landing & marketing.** `pages/index.html`, `use_cases.html`, `pricing.html`, `creators.html`, `fan_plus.html`, `changelog.html`, `policy.html`. First impression + conversion surface.
200 - - [ ] **Phase 2 — Discovery & browse.** `pages/discover.html`, `feed.html`, `tag_tree.html`, `user.html`, `project.html`, `collection.html`. Public browse path, post-discover funnel.
201 - - [ ] **Phase 3 — Item & library (consume).** `pages/item.html`, `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`, `blog_post.html`, `project_blog.html`, `project_paywall.html`. Soft-launch critical journey end.
202 - - [ ] **Phase 4 — Auth & onboarding.** `pages/login.html`, `forgot_password.html`, `reset_password.html`, `two_factor.html`, `oauth_authorize.html`, `account-deleted.html`, `confirm_delete.html`, `wizards/wizard_join.html`. Account-state transitions; forgiveness + error-message review.
203 - - [ ] **Phase 5 — Checkout & receipts.** `pages/buy.html`, `cart.html`, `purchase.html`, `receipt.html`, `stripe_disclaimer.html`. Commerce flow; feedback + trust cues + Stripe handoff.
204 - - [ ] **Phase 6 — Creator dashboard & wizards.** `dashboards/dashboard-user.html`, `dashboard-project.html`, `dashboard-item.html`, `dashboard-blog-editor.html`, `dashboard-export.html`, `dashboard-import.html`, `dashboard-delete-account.html`, `wizards/wizard_item.html`, `wizard_project.html`, plus `wizards/steps/*` and `wizards/partials/*`. Densest interaction surface — expect the most findings.
205 - - [ ] **Phase 7 — Admin.** `dashboards/admin-appeals.html`, `admin-metrics.html`, `admin-reports.html`, `admin-signups.html`, `admin-uploads.html`, `admin-users.html`, `admin-waitlist.html`. Internal but still has destructive actions — forgiveness + confirmation review matters.
206 - - [ ] **Phase 8 — Cross-cutting & utility.** `pages/error.html`, `health.html`, `sandbox.html`, `doc.html`, `doc_index.html`, `email_result.html`, `pages/git/*` (G1 browser), `partials/*`, `partials/tabs/*`. Plus a full flat-design sweep over `static/style.css`, `static/media-player.css`, `static/wizard.css`. Closes with a roll-up summary across all 8 phases.
199 + - [x] **Phase 1 — Public landing & marketing.** *(Complete 2026-05-20, `docs/ux-audit/phase-1.md`.)*
200 + - [x] **Phase 2 — Discovery & feed.** *(Complete 2026-05-20, `docs/ux-audit/phase-2.md`.)*
201 + - [x] **Phase 3 — Content viewing.** *(Complete 2026-05-20, `docs/ux-audit/phase-3.md`.)*
202 + - [x] **Phase 4 — Purchase flow.** *(Complete 2026-05-20, `docs/ux-audit/phase-4.md`.)*
203 + - [x] **Phase 5 — Library and media players.** *(Complete 2026-05-20, `docs/ux-audit/phase-5.md`.)*
204 + - [x] **Phase 6 — Auth and account.** *(Complete 2026-05-20, `docs/ux-audit/phase-6.md`.)*
205 + - [x] **Phase 7 — Dashboard and admin.** *(Complete 2026-05-20, `docs/ux-audit/phase-7.md`.)*
206 + - [x] **Phase 8 — Wizards, docs, source browser, errors.** *(Complete 2026-05-20, `docs/ux-audit/phase-8.md`.)*
207 207
208 208 Out of scope for this sweep: per-tier capability decisions, brand/aesthetic direction (see `_meta/docs/brand.md`), full WCAG audit (run axe/Lighthouse separately).
209 209
210 + ### UX audit remediations (by site section)
211 +
212 + Findings from phases 1-8, grouped by the website area they affect. Six items landed in the audit session on 2026-05-20; seven more landed in a follow-up session the same day; two original findings were retracted (incorrect data). Remaining items are charter decisions or cleanup tasks.
213 +
214 + **Landed 2026-05-20 (audit session)** *(crossed off; kept here as a record)*
215 + - [x] `pricing.html` tier-picker keyboard-accessibility fix — radio visually-hidden but focusable; `:focus-within` outline on label; JS listens on `change` so keyboard navigation updates styling.
216 + - [x] `pricing.html` `.tier-option.selected` → `.is-selected` (last pre-charter `.selected` survivor; template, JS, and CSS all updated).
217 + - [x] `<a><button>` antipattern sweep — all 44 sites across 13 files converted to `<a class="btn-primary">…</a>`. Added `display: inline-block` and `text-decoration: none` to the button base rules so anchors render correctly.
218 + - [x] Four page-isolated `<style>` blocks migrated — `user.html` (deleted dead duplicate; rules already in style.css), `dashboard-user.html` (scoped under `.dashboard-user-page` to resolve name collision with `user.html` and the user_projects tab partial; body class updated to `padded-page dashboard-page dashboard-user-page`), `library_feed.html` (scoped under `.library-page`; added `library-page` body class to library.html), `user_settings.html` (migrated as global primitives).
219 + - [x] Follow-state synonyms → `.is-selected` — `.tag-follow-btn.following` and `.follow-btn.is-following` across discover.html, user.html, project.html, follow_button.html, tag_follow_toggle.html + 4 CSS rules. Also fixed a latent bug in `follow_button.html` where two `class=` attributes on the same `<button>` meant `.is-following` never actually applied.
220 + - [x] `.intro` lifted to `.page-intro` primitive — canonical rule added near `.section-lead`; four scoped copies dropped; four templates migrated.
221 +
222 + **Landed 2026-05-20 (follow-up session)** *(crossed off; kept here as a record)*
223 + - [x] **Heading convention for prose pages — decided + applied.** Picked `.section-header` over bare `<h2>` for prose-page sub-section headings. ~25 bare h2s converted across creators.html, policy.html, changelog.html, fan_plus.html, stripe_disclaimer.html, purchase.html, cart.html, library_downloads.html, doc_index.html, git/repo.html, git/settings.html. `library_locked.html:22` skipped intentionally (h2 lives inside a `.purchase-box` notification card; section-header mono-bottom-border styling would clash). Auth flow + wizards keep bare `<h2>` as the documented page-subtitle pattern.
224 + - [x] **`dashboard-user.html`** — "Account Deactivated" and "Account Paused" rewrapped from `.card-muted`/`<h2>` to `.alert.alert-warning`/`<p class="alert-title">`. `.banner--warning` was the audit suggestion but it's a single-line page-top primitive and doesn't fit multi-paragraph + action-buttons content.
225 + - [x] **`git/settings.html` delete-repo** — `onsubmit="return confirm(...)"` form replaced with htmx button (`hx-post` + `hx-confirm`). `repo_settings_delete` handler now returns `HX-Redirect` instead of `Redirect::to()`; CSRF flows via the global `htmx:configRequest` X-CSRF-Token wiring in `mnw.js`.
226 + - [x] **`project.html:208` `.login-cta-btn`** — class removed; `width: 100%` folded into the existing `.project-page .tier-card button` rule by extending the selector to also match `.btn-primary` anchors.
227 + - [x] **`dashboard-export.html` `.export-card-*` family** — documented as intentionally page-scoped (audit's other explicit option). Added a CSS comment block explaining the row-layout pattern doesn't fit `.card-muted` cleanly and the `--light-background` fill is deliberate for surface distinction from the surrounding dashboard.
228 + - [x] **`wizard.css` consolidation** — `.monetization-card{,-cards,.info-only}` rules deleted as dead CSS (zero template references; ~25 lines removed including responsive override). `.content-choice-card` anchor-link override + `.muted` modifier moved to style.css under the canonical `.card--selectable` recipe; renamed `.muted` → `.is-disabled` to align with `.is-selected` naming. Sole template using it (`wizards/steps/project/first_content.html`) migrated.
229 + - [x] **`discover_results.html` empty-state hint markup** — accepted as-is per the audit's own recommendation. Four identical inline blocks consolidated within one file; a fifth macro variant for the inline-anchor hint would be overengineered.
230 +
231 + **Retracted findings** *(audit data was wrong; no action needed)*
232 + - ~~`.coming-soon` modifier has no CSS rule~~ — rule exists at `style.css:6489` (`border-style: dashed; opacity: 0.6`). Original Phase 1 grep was too narrow.
233 + - ~~`cart-group__title` BEM `__` is the only `__` separator in the codebase~~ — actually 112 template usages across 20 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, etc.). It's an established convention. Either document in charter or schedule a separate larger rename pass — not a small fix.
234 +
235 + **Remaining items**
236 +
237 + **Charter decisions — resolved 2026-05-20**
238 + - [x] **Button class shorthand → `.btn-primary` / `.btn-secondary` / `.btn-danger`.** Migrated all 67 shorthand sites (`class="primary"` etc.) to `.btn-*` form across templates + JS. Dropped `button.primary` / `.secondary` / `.danger` from CSS selectors (kept `.btn-*` only) — base style.css + four scoped overrides (`.item-page`, `.project-page .item-content`, `.purchase-page`, `.link-row`). Rationale: only `.btn-*` works on both `<button>` and `<a>` (after the `<a><button>` antipattern fix), shorthand was element-scoped. Charter rule 11 codifies.
239 + - [x] **Heading classes — no bare `<h1>` / `<h2>`.** Added four explicit classes: `.brand-h1` (Makenot.work wordmark), `.page-title` (page h1), `.subtitle-h2` (auth/wizard subtitle), `.subsection-title` (default h2 in dashboards/tabs/partials). Existing `.section-header` (h2 with border) kept for prose sub-sections. Migrated 93 templates; only remaining bare tags are inside the JS markdown-renderer string in `partials/item_text_editor.html` (user content). Charter rule 10 codifies.
240 + - [x] **BEM `__` separator retired.** Renamed 76 distinct `block__elem` tokens (15 files: templates + style.css + JS) to single-dash kebab-case. Modifier `--` (`.card--bordered`) preserved. No collisions. Charter rule 12 codifies.
241 +
242 + **Cleanup tasks** (deferred from consolidation pass)
243 + - [x] **`.sr-only` accessibility upgrade on file inputs** *(landed 2026-05-20)* — 13 `<input type="file">` sites swapped from `class="hidden"` / `class="wizard-file-input"` to `class="sr-only"`. Trigger buttons unchanged (they `.click()` the input programmatically); screen readers and keyboard now reach the input. `.wizard-file-input { display: none }` rule removed from `wizard.css`. One file input (`dashboard-import.html#import-file`) intentionally left visible — has a proper visible label.
244 + - [ ] **`.landing-founder-banner`** — delete if folded into hero-callout primitive (currently kept separate per charter).
245 + - [ ] **Orphan CSS cleanup** — ~30 truly unused class names in `style.css` deferred (e.g. `.tier-grid`, `.search-box`, `.signup-container`, `.button-stack`). Per-class judgement; revisit when bandwidth allows.
246 +
210 247 ---
211 248
212 249 ## Infra & Quality
@@ -29,7 +29,7 @@
29 29 + '<div class="collection-picker-create">'
30 30 + '<form>'
31 31 + '<input type="text" name="title" placeholder="New collection" required maxlength="100" autocomplete="off">'
32 - + '<button class="secondary" type="submit">Create</button>'
32 + + '<button class="btn-secondary" type="submit">Create</button>'
33 33 + '</form></div>';
34 34
35 35 wrapper.appendChild(picker);
@@ -62,8 +62,8 @@
62 62 '<input type="text" class="bundle-new-title" placeholder="Item name" autocomplete="off">' +
63 63 '<input type="text" class="bundle-new-desc" placeholder="Description (optional)" autocomplete="off">' +
64 64 '<div style="display: flex; gap: 0.25rem;">' +
65 - '<button type="button" class="primary bundle-create-btn" style="padding: 0.3rem 0.7rem; font-size: 0.85rem;">Create</button>' +
66 - '<button type="button" class="secondary bundle-cancel-btn" style="padding: 0.3rem 0.5rem; font-size: 0.85rem;">Cancel</button>' +
65 + '<button type="button" class="btn-primary bundle-create-btn" style="padding: 0.3rem 0.7rem; font-size: 0.85rem;">Create</button>' +
66 + '<button type="button" class="btn-secondary bundle-cancel-btn" style="padding: 0.3rem 0.5rem; font-size: 0.85rem;">Cancel</button>' +
67 67 '</div>';
68 68 container.appendChild(row);
69 69
@@ -106,7 +106,7 @@
106 106 '<td style="padding: 0.5rem 0.5rem 0.5rem 0;"><a href="/dashboard/item/' + data.item_id + '">' + escapeHtml(data.title) + '</a></td>' +
107 107 '<td style="padding: 0.5rem; font-size: 0.85rem; opacity: 0.8;">' + escapeHtml(desc) + '</td>' +
108 108 '<td style="padding: 0.5rem; font-size: 0.85rem;"><a href="/dashboard/item/' + data.item_id + '" style="font-size: 0.8rem;">Manage files</a></td>' +
109 - '<td style="padding: 0.5rem;"><button type="button" class="secondary bundle-remove-btn" data-child-id="' + data.item_id + '" style="padding: 0.2rem 0.5rem; font-size: 0.75rem;">Remove</button></td>';
109 + '<td style="padding: 0.5rem;"><button type="button" class="btn-secondary bundle-remove-btn" data-child-id="' + data.item_id + '" style="padding: 0.2rem 0.5rem; font-size: 0.75rem;">Remove</button></td>';
110 110 tbody.appendChild(tr);
111 111 attachBundleRowHandlers(tr, bundleId);
112 112 updateBundleCount(1);
@@ -191,8 +191,8 @@
191 191 row.innerHTML =
192 192 '<span style="flex:1;font-weight:bold;">' + escapeHtml(sec.title) + '</span>' +
193 193 '<span style="font-size:0.8rem;opacity:0.6;">' + (sec.body || '').length + ' chars</span>' +
194 - '<button type="button" class="secondary section-edit-btn" data-id="' + sec.id + '" style="padding:0.25rem 0.6rem;font-size:0.8rem;">Edit</button>' +
195 - '<button type="button" class="secondary section-del-btn" data-id="' + sec.id + '" style="padding:0.25rem 0.6rem;font-size:0.8rem;">Delete</button>';
194 + '<button type="button" class="btn-secondary section-edit-btn" data-id="' + sec.id + '" style="padding:0.25rem 0.6rem;font-size:0.8rem;">Edit</button>' +
195 + '<button type="button" class="btn-secondary section-del-btn" data-id="' + sec.id + '" style="padding:0.25rem 0.6rem;font-size:0.8rem;">Delete</button>';
196 196 document.getElementById('sections-list').appendChild(row);
197 197 attachSectionRowHandlers(row, itemId);
198 198 updateSectionCount(1);
@@ -180,7 +180,7 @@
180 180 tr.innerHTML =
181 181 '<td style="padding: 0.4rem 0.5rem 0.4rem 0; font-size: 0.85rem; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="' + file.name.replace(/"/g, '&quot;') + '">' + escapeHtml(file.name) + '</td>' +
182 182 '<td style="padding: 0.4rem 0.5rem;"><input type="text" class="version-label-input" data-idx="' + idx + '" value="' + escapeAttr(guessLabel(file.name)) + '" placeholder="e.g., macOS (arm)" style="width: 100%; padding: 0.25rem 0.4rem; font-size: 0.85rem;"></td>' +
183 - '<td style="padding: 0.4rem 0.5rem;"><button type="button" class="secondary version-remove-file" data-idx="' + idx + '" style="padding: 0.2rem 0.5rem; font-size: 0.75rem;">Remove</button></td>';
183 + '<td style="padding: 0.4rem 0.5rem;"><button type="button" class="btn-secondary version-remove-file" data-idx="' + idx + '" style="padding: 0.2rem 0.5rem; font-size: 0.75rem;">Remove</button></td>';
184 184 fileRows.appendChild(tr);
185 185
186 186 tr.querySelector('.version-remove-file').addEventListener('click', function() {
@@ -91,11 +91,11 @@
91 91 row.className = 'psection-row';
92 92 row.dataset.id = sec.id;
93 93 row.innerHTML =
94 - '<span class="psection-row__title">' + escapeHtml(sec.title) + '</span>' +
95 - '<code class="psection-row__anchor">#section-' + escapeHtml(sec.slug) + '</code>' +
96 - '<span class="psection-row__length">' + (sec.body || '').length + ' chars</span>' +
97 - '<button type="button" class="secondary psection-edit-btn" data-id="' + sec.id + '" data-title="' + escapeHtml(sec.title) + '">Edit</button>' +
98 - '<button type="button" class="secondary psection-del-btn" data-id="' + sec.id + '">Delete</button>';
94 + '<span class="psection-row-title">' + escapeHtml(sec.title) + '</span>' +
95 + '<code class="psection-row-anchor">#section-' + escapeHtml(sec.slug) + '</code>' +
96 + '<span class="psection-row-length">' + (sec.body || '').length + ' chars</span>' +
97 + '<button type="button" class="btn-secondary psection-edit-btn" data-id="' + sec.id + '" data-title="' + escapeHtml(sec.title) + '">Edit</button>' +
98 + '<button type="button" class="btn-secondary psection-del-btn" data-id="' + sec.id + '">Delete</button>';
99 99 list.appendChild(row);
100 100 var hidden = document.createElement('textarea');
101 101 hidden.className = 'hidden';
@@ -132,9 +132,9 @@
132 132 .then(function(sec) {
133 133 var row = document.querySelector('.psection-row[data-id="' + id + '"]');
134 134 if (row) {
135 - row.querySelector('.psection-row__title').textContent = sec.title;
136 - row.querySelector('.psection-row__anchor').textContent = '#section-' + sec.slug;
137 - row.querySelector('.psection-row__length').textContent = (sec.body || '').length + ' chars';
135 + row.querySelector('.psection-row-title').textContent = sec.title;
136 + row.querySelector('.psection-row-anchor').textContent = '#section-' + sec.slug;
137 + row.querySelector('.psection-row-length').textContent = (sec.body || '').length + ' chars';
138 138 row.querySelector('.psection-edit-btn').dataset.title = sec.title;
139 139 }
140 140 var hidden = document.querySelector('textarea[data-body-for="' + id + '"]');
@@ -35,7 +35,7 @@
35 35 .container + .container--narrow / --medium / --wide
36 36 .stack-row + --bordered / --tight / --top — toolbar / header bar
37 37 .field-row + .form-group.is-grow — inline form row
38 - .list-row + .list-row__title — vertical item list
38 + .list-row + .list-row-title — vertical item list
39 39 .form-row — 2-col grid form
40 40 .cover-row + .cover-thumb + .cover-empty — image picker
41 41
@@ -271,6 +271,39 @@
271 271 color: var(--detail);
272 272 }
273 273
274 + /* Heading classes (charter: docs/design-system.md — every h1/h2 in a
275 + template must carry one of these so the role is explicit).
276 +
277 + .brand-h1 — the "Makenot.work" wordmark on auth/wizard pages.
278 + .page-title — page-level h1 (Young Serif, centered).
279 + .subtitle-h2 — page subtitle h2 used under .brand-h1 in auth/wizards.
280 + .subsection-title — h2 inside dashboards, tabs, and prose partials
281 + (mono, no border — the default-h2 role made explicit).
282 + .section-header — h2 prose sub-section heading with bottom border
283 + (existing rule, see SECTIONS block). */
284 + .brand-h1,
285 + .page-title {
286 + font-family: var(--font-heading);
287 + font-weight: normal;
288 + color: var(--detail);
289 + text-align: center;
290 + font-size: 2.5rem;
291 + }
292 +
293 + .subtitle-h2 {
294 + font-family: var(--font-mono);
295 + font-weight: normal;
296 + color: var(--detail);
297 + text-align: center;
298 + font-size: 1.5rem;
299 + }
300 +
301 + .subsection-title {
302 + font-family: var(--font-mono);
303 + font-weight: normal;
304 + color: var(--detail);
305 + }
306 +
274 307 p {
275 308 font-family: var(--font-body);
276 309 color: var(--detail);
@@ -333,7 +366,6 @@
333 366 opacity: 0.8;
334 367 }
335 368
336 - button.primary,
337 369 .btn-primary {
338 370 background: var(--primary-dark);
339 371 color: var(--primary-light);
@@ -343,14 +375,15 @@
343 375 font-size: 1rem;
344 376 cursor: pointer;
345 377 transition: opacity 0.2s ease;
378 + display: inline-block;
379 + text-decoration: none;
346 380 }
347 381
348 - button.primary:hover,
349 382 .btn-primary:hover {
350 383 opacity: 0.8;
384 + text-decoration: none;
351 385 }
352 386
353 - button.secondary,
354 387 .btn-secondary {
355 388 background: var(--surface-muted);
356 389 color: var(--detail);
@@ -360,14 +393,15 @@
360 393 font-size: 1rem;
361 394 cursor: pointer;
362 395 transition: opacity 0.2s ease;
396 + display: inline-block;
397 + text-decoration: none;
363 398 }
364 399
365 - button.secondary:hover,
366 400 .btn-secondary:hover {
367 401 opacity: 0.8;
402 + text-decoration: none;
368 403 }
369 404
370 - button.danger,
371 405 .btn-danger {
372 406 background: var(--danger);
373 407 color: var(--primary-light);
@@ -377,11 +411,13 @@
377 411 font-size: 1rem;
378 412 cursor: pointer;
379 413 transition: opacity 0.2s ease;
414 + display: inline-block;
415 + text-decoration: none;
380 416 }
381 417
382 - button.danger:hover,
383 418 .btn-danger:hover {
384 419 opacity: 0.8;
420 + text-decoration: none;
385 421 }
386 422
387 423 /* Disabled state for all button variants (charter rule).
@@ -407,8 +443,6 @@
407 443 Some surfaces have tuned button variants that are NOT compositions
408 444 of these modifiers and keep their own classes:
409 445 .big-button — Young Serif 200×60 anchor on splash pages.
410 - .paywall-btn — wider primary CTA (anchor) on paywalls.
411 - .notify-btn — small inverted notify pill in dashboards.
412 446 .order-btn — list reorder up/down (with active flash).
413 447 .play-button — circular media-player play control.
414 448 .speed-button — segment in media-player speed group.
@@ -1182,6 +1216,21 @@
1182 1216 INFO & WARNING BOXES
1183 1217 =========================================== */
1184 1218
1219 + /* Notification surface roles (charter: docs/design-system.md).
1220 + Five distinct components — keep them distinct, don't merge:
1221 + .toast — transient, JS-dismissible, bottom-right corner.
1222 + .banner — full-bleed page-top notice (sandbox, restart,
1223 + founder pricing). One short sentence + optional link.
1224 + .alert — inline directive callout with uppercase mono title
1225 + (NOTE / TIP / IMPORTANT / WARNING / CAUTION). Used in
1226 + docs and longer-form bodies.
1227 + .info-box — informational headed block with h3 + bullet list
1228 + inside forms / wizards (distinct register from .alert).
1229 + .warning-box — loud yellow-on-yellow attention-grabber for
1230 + page-level warnings (delete confirms, account
1231 + warnings). Louder than .alert-warning intentionally.
1232 + .error-message — inline form-field error; hidden by default, shown
1233 + with .is-active. Distinct from page-level .alert. */
1185 1234 .info-box {
1186 1235 background: var(--surface-muted);
1187 1236 padding: 1rem;
@@ -1526,13 +1575,13 @@
1526 1575 opacity: 0.5;
1527 1576 }
1528 1577
1529 - .item-page button.primary {
1578 + .item-page .btn-primary {
1530 1579 width: 100%;
1531 1580 padding: var(--space-4) var(--space-6);
1532 1581 font-size: 1.1rem;
1533 1582 }
1534 1583
1535 - .item-page button.secondary {
1584 + .item-page .btn-secondary {
1536 1585 width: 100%;
1537 1586 margin-top: var(--space-2);
1538 1587 }
@@ -1751,8 +1800,8 @@
1751 1800
1752 1801 .project-page .store-footer a { color: var(--detail); }
1753 1802
1754 - .project-page .item-content button.primary,
1755 - .project-page .item-content button.secondary {
1803 + .project-page .item-content .btn-primary,
1804 + .project-page .item-content .btn-secondary {
1756 1805 width: 100%;
1757 1806 margin-top: var(--space-4);
1758 1807 }
@@ -1796,7 +1845,8 @@
1796 1845 }
1797 1846
1798 1847 .project-page .tier-card form { margin-top: auto; }
1799 - .project-page .tier-card button { width: 100%; }
1848 + .project-page .tier-card button,
1849 + .project-page .tier-card .btn-primary { width: 100%; }
1800 1850
1801 1851 .project-page .tier-active-badge {
1802 1852 font-size: 0.85rem;
@@ -2600,7 +2650,12 @@
2600 2650 .delete-account-page .form-status.error { color: var(--danger); }
2601 2651 .delete-account-page .form-status.success { color: var(--success); }
2602 2652
2603 - /* Export data page (templates/dashboards/dashboard-export.html). */
2653 + /* Export data page (templates/dashboards/dashboard-export.html).
2654 + `.export-card` and its inner classes (`-info`, `-title`, `-desc`, `-meta`)
2655 + are intentionally page-scoped: this is a row-layout card (info on left,
2656 + action button on right, flex justify-between) that doesn't fit `.card-muted`
2657 + cleanly. Background uses `--light-background` rather than `--surface-muted`
2658 + for visual distinction from the surrounding dashboard surface. */
2604 2659
2605 2660 .export-page .export-cards {
2606 2661 display: grid;
@@ -2724,11 +2779,6 @@
2724 2779 .fan-plus-page h1 { font-size: 2.5rem; margin-bottom: var(--space-2); }
2725 2780 .fan-plus-page h2 { font-size: 1.5rem; margin-top: var(--space-6); margin-bottom: var(--space-4); }
2726 2781
2727 - .fan-plus-page .intro {
2728 - font-size: 1.1rem;
2729 - margin-bottom: var(--space-6);
2730 - line-height: 1.6;
2731 - }
2732 2782
2733 2783 .fan-plus-page .fan-plus-section { margin-bottom: var(--space-6); line-height: 1.7; }
2734 2784 .fan-plus-page .fan-plus-section ul { padding-left: var(--space-5); margin: var(--space-3) 0; }
@@ -2862,8 +2912,8 @@
2862 2912 .purchase-page .payment-section { margin-bottom: var(--space-5); }
2863 2913 .purchase-page .payment-section h2 { font-size: 1.1rem; margin-bottom: var(--space-4); }
2864 2914
2865 - .purchase-page button.primary { width: 100%; }
2866 - .purchase-page button.secondary { width: 100%; margin-top: var(--space-4); }
2915 + .purchase-page .btn-primary { width: 100%; }
2916 + .purchase-page .btn-secondary { width: 100%; margin-top: var(--space-4); }
2867 2917
2868 2918 .purchase-page .security-note {
2869 2919 font-size: 0.85rem;
@@ -3033,6 +3083,13 @@
3033 3083 text-align: left;
3034 3084 }
3035 3085
3086 + /* Large intro paragraph under a page h1 (creators, policy, changelog, fan_plus). */
3087 + .page-intro {
3088 + font-size: 1.1rem;
3089 + margin-bottom: var(--space-6);
3090 + line-height: 1.6;
3091 + }
3092 +
3036 3093 .subscription-note p {
3037 3094 opacity: 0.7;
3038 3095 font-size: 0.9rem;
@@ -3055,9 +3112,9 @@
3055 3112
3056 3113 /* psection-* mirrors section-mgmt-* (used by item_details) but psection
3057 3114 class hooks are also queried by static/project-sections.js. */
3058 - .psection-row__title { flex: 1; font-weight: bold; }
3059 - .psection-row__anchor { font-size: 0.75rem; opacity: 0.6; }
3060 - .psection-row__length { font-size: 0.8rem; opacity: 0.6; }
3115 + .psection-row-title { flex: 1; font-weight: bold; }
3116 + .psection-row-anchor { font-size: 0.75rem; opacity: 0.6; }
3117 + .psection-row-length { font-size: 0.8rem; opacity: 0.6; }
3061 3118
3062 3119 .psection-edit-btn,
3063 3120 .psection-del-btn {
@@ -3090,18 +3147,18 @@
3090 3147 text-align: center;
3091 3148 padding: 3rem 1rem;
3092 3149 }
3093 - .cart-empty__hint {
3150 + .cart-empty-hint {
3094 3151 font-size: 0.9rem;
3095 3152 opacity: 0.7;
3096 3153 margin-bottom: var(--space-5);
3097 3154 }
3098 3155
3099 3156 /* Multi-creator summary bar (only renders when seller_groups.len() > 1). */
3100 - .cart-multi-bar__note {
3157 + .cart-multi-bar-note {
3101 3158 opacity: 0.7;
3102 3159 margin-left: var(--space-2);
3103 3160 }
3104 - .cart-multi-bar__form {
3161 + .cart-multi-bar-form {
3105 3162 display: flex;
3106 3163 align-items: center;
3107 3164 gap: var(--space-3);
@@ -3115,8 +3172,8 @@
3115 3172
3116 3173 /* Per-seller group card. */
3117 3174 .cart-group { margin-bottom: var(--space-6); }
3118 - .cart-group__title { margin-bottom: 0.25rem; }
3119 - .cart-group__count {
3175 + .cart-group-title { margin-bottom: 0.25rem; }
3176 + .cart-group-count {
3120 3177 font-size: 0.85rem;
3121 3178 opacity: 0.6;
3122 3179 margin-bottom: var(--space-4);
@@ -3134,7 +3191,7 @@
3134 3191 /* Compact action button used in cart/wishlist tables. */
3135 3192
3136 3193 /* Bottom summary row of each seller group. */
3137 - .cart-group__summary {
3194 + .cart-group-summary {
3138 3195 margin-top: var(--space-4);
3139 3196 display: flex;
3140 3197 justify-content: space-between;
@@ -3208,16 +3265,16 @@
3208 3265 margin-bottom: var(--space-2);
3209 3266 }
3210 3267 .dns-row:last-of-type { margin-bottom: 0; }
3211 - .dns-row__label {
3268 + .dns-row-label {
3212 3269 font-size: 0.8rem;
3213 3270 opacity: 0.7;
3214 3271 min-width: 70px;
3215 3272 }
3216 - .dns-row__value {
3273 + .dns-row-value {
3217 3274 font-size: 0.85rem;
3218 3275 flex: 1;
3219 3276 }
3220 - .dns-row__copy {
3277 + .dns-row-copy {
3221 3278 padding: 0.15rem 0.5rem;
3222 3279 font-size: 0.75rem;
3223 3280 }
@@ -3437,12 +3494,6 @@
3437 3494 .creators-page h1 { font-size: 2.5rem; margin-bottom: var(--space-2); }
3438 3495 .creators-page h2 { font-size: 1.5rem; margin-top: var(--space-6); margin-bottom: var(--space-4); }
3439 3496
3440 - .creators-page .intro {
3441 - font-size: 1.1rem;
3442 - margin-bottom: var(--space-6);
3443 - line-height: 1.6;
3444 - }
3445 -
3446 3497 .creators-page .how-it-works { margin-bottom: var(--space-6); }
3447 3498 .creators-page .how-it-works ol { padding-left: var(--space-5); line-height: 1.8; }
3448 3499
@@ -3724,10 +3775,117 @@
3724 3775 }
3725 3776 .health-page .subtitle { text-align: center; }
3726 3777
3727 - /* User-level dashboard (templates/dashboards/dashboard-user.html). */
3728 - .stat-value { font-size: 2rem; margin-bottom: var(--space-1); }
3778 + /* User Settings tab (templates/partials/tabs/user_settings.html).
3779 + Left-rail nav layout for the Settings dashboard tab. Selection uses the
3780 + canonical .is-selected modifier. */
3781 + .settings-layout { display: flex; gap: var(--space-6); }
3782 + .settings-nav { flex-shrink: 0; min-width: 140px; }
3783 + .settings-nav a {
3784 + display: block;
3785 + padding: var(--space-2) var(--space-3);
3786 + text-decoration: none;
3787 + color: var(--detail);
3788 + opacity: 0.6;
3789 + font-family: var(--font-mono);
3790 + font-size: 0.9rem;
3791 + transition: opacity 0.15s ease;
3792 + }
3793 + .settings-nav a:hover { opacity: 1; }
3794 + .settings-nav a.is-selected { opacity: 1; background: var(--surface-muted); }
3795 + .settings-body { flex: 1; min-width: 0; }
3796 + @media (max-width: 768px) {
3797 + .settings-layout { flex-direction: column; gap: var(--space-4); }
3798 + .settings-nav { display: flex; flex-wrap: wrap; gap: 0; min-width: 0; }
3799 + .settings-nav a { padding: 0.4rem 0.6rem; font-size: 0.85rem; }
3800 + }
3729 3801
3730 - .project-card {
3802 + /* Library "Feed" tab (templates/partials/tabs/library_feed.html).
3803 + Scoped under .library-page so rules don't bleed onto the standalone
3804 + /feed page (which has its own .feed-page-scoped rules). */
3805 + .library-page .feed-meta { font-size: 0.8rem; opacity: 0.6; margin-bottom: var(--space-3); }
3806 + .library-page .feed-table-header {
3807 + display: grid;
3808 + grid-template-columns: 50px 1fr 100px 70px 70px;
3809 + gap: var(--space-2);
3810 + padding: var(--space-2) var(--space-3);
3811 + background: var(--surface-alt);
3812 + font-size: 0.75rem;
3813 + opacity: 0.7;
3814 + text-transform: uppercase;
3815 + letter-spacing: 0.03em;
3816 + }
3817 + .library-page .feed-col-right { text-align: right; }
3818 + .library-page .feed-results-table { border: 1px solid var(--border); border-top: none; }
3819 + .library-page .feed-table-row {
3820 + display: grid;
3821 + grid-template-columns: 50px 1fr 100px 70px 70px;
3822 + gap: var(--space-2);
3823 + padding: 0.4rem var(--space-3);
3824 + align-items: center;
3825 + text-decoration: none;
3826 + color: var(--detail);
3827 + font-size: 0.85rem;
3828 + border-bottom: 1px solid var(--border);
3829 + transition: background 0.1s ease;
3830 + }
3831 + .library-page .feed-table-row:last-child { border-bottom: none; }
3832 + .library-page .feed-table-row:nth-child(odd) { background: var(--light-background); }
3833 + .library-page .feed-table-row:nth-child(even) { background: var(--surface-alt); }
3834 + .library-page .feed-table-row:hover { background: var(--surface-muted); }
3835 + .library-page .feed-item-name-cell {
3836 + display: flex;
3837 + flex-direction: column;
3838 + gap: 0.1rem;
3839 + min-width: 0;
3840 + }
3841 + .library-page .feed-item-name {
3842 + white-space: nowrap;
3843 + overflow: hidden;
3844 + text-overflow: ellipsis;
3845 + }
3846 + .library-page .feed-item-creator { font-size: 0.75rem; opacity: 0.5; }
3847 + .library-page .feed-pagination {
3848 + display: flex;
3849 + gap: var(--space-1);
3850 + justify-content: center;
3851 + margin-top: var(--space-4);
3852 + }
3853 + .library-page .feed-pagination a,
3854 + .library-page .feed-pagination span {
3855 + padding: 0.4rem 0.7rem;
3856 + font-size: 0.85rem;
3857 + text-decoration: none;
3858 + color: var(--detail);
3859 + border: 1px solid var(--border);
3860 + }
3861 + .library-page .feed-pagination span.current {
3862 + background: var(--primary-dark);
3863 + color: var(--primary-light);
3864 + border-color: var(--primary-dark);
3865 + }
3866 + .library-page .feed-pagination a:hover { background: var(--surface-muted); }
3867 + @media (max-width: 600px) {
3868 + .library-page .feed-table-header,
3869 + .library-page .feed-table-row {
3870 + grid-template-columns: 1fr 70px;
3871 + }
3872 + .library-page .feed-table-header span:nth-child(1),
3873 + .library-page .feed-table-row .badge:first-child,
3874 + .library-page .feed-table-header span:nth-child(3),
3875 + .library-page .feed-table-header span:nth-child(4),
3876 + .library-page .feed-table-row span:nth-child(3),
3877 + .library-page .feed-table-row span:nth-child(4) {
3878 + display: none;
3879 + }
3880 + }
3881 +
3882 + /* User-level dashboard (templates/dashboards/dashboard-user.html).
3883 + Scoped under .dashboard-user-page to avoid colliding with .project-card
3884 + / .project-title / .project-meta on the public profile (user.html) and
3885 + the user_projects tab partial. */
3886 + .dashboard-user-page .stat-value { font-size: 2rem; margin-bottom: var(--space-1); }
3887 +
3888 + .dashboard-user-page .project-card {
3731 3889 background: var(--light-background);
3732 3890 padding: var(--space-5);
3733 3891 margin-bottom: var(--space-4);
@@ -3736,33 +3894,33 @@
3736 3894 align-items: flex-start;
3737 3895 }
3738 3896
3739 - .project-info { flex: 1; }
3897 + .dashboard-user-page .project-info { flex: 1; }
3740 3898
3741 - .project-title {
3899 + .dashboard-user-page .project-title {
3742 3900 font-family: var(--font-heading);
3743 3901 font-weight: bold;
3744 3902 font-size: 1.2rem;
3745 3903 margin-bottom: var(--space-1);
3746 3904 }
3747 3905
3748 - .project-meta {
3906 + .dashboard-user-page .project-meta {
3749 3907 font-size: 0.85rem;
3750 3908 opacity: 0.7;
3751 3909 margin-bottom: var(--space-2);
3752 3910 }
3753 3911
3754 - .project-stats { font-size: 0.9rem; }
3755 - .project-actions {
3912 + .dashboard-user-page .project-stats { font-size: 0.9rem; }
3913 + .dashboard-user-page .project-actions {
3756 3914 display: flex;
3757 3915 gap: var(--space-2);
3758 3916 }
3759 3917
3760 - .project-actions button {
3918 + .dashboard-user-page .project-actions button {
3761 3919 padding: 0.4rem 0.8rem;
3762 3920 font-size: 0.85rem;
3763 3921 }
3764 3922
3765 - .summary-row {
3923 + .dashboard-user-page .summary-row {
3766 3924 background: var(--surface-muted);
3767 3925 padding: var(--space-4);
3768 3926 margin-top: var(--space-4);
@@ -3770,8 +3928,8 @@
3770 3928 }
3771 3929
3772 3930 @media (max-width: 768px) {
3773 - .project-card { flex-direction: column; }
3774 - .project-actions { margin-top: var(--space-3); }
3931 + .dashboard-user-page .project-card { flex-direction: column; }
3932 + .dashboard-user-page .project-actions { margin-top: var(--space-3); }
3775 3933 }
3776 3934
3777 3935 /* Project dashboard (templates/dashboards/dashboard-project.html). */
@@ -3859,11 +4017,6 @@
3859 4017 /* Changelog page (templates/pages/changelog.html). */
3860 4018 .changelog-page .container { max-width: 900px; margin: 0 auto; }
3861 4019 .changelog-page h1 { font-size: 2.5rem; margin-bottom: var(--space-2); }
3862 - .changelog-page .intro {
3863 - font-size: 1.1rem;
3864 - margin-bottom: var(--space-6);
3865 - line-height: 1.6;
3866 - }
3867 4020 .changelog-page .changelog-entry {
3868 4021 margin-bottom: 2.5rem;
3869 4022 padding-bottom: var(--space-6);
@@ -3892,11 +4045,6 @@
3892 4045 margin-top: var(--space-6);
3893 4046 margin-bottom: var(--space-4);
3894 4047 }
3895 - .policy-page .intro {
3896 - font-size: 1.1rem;
3897 - margin-bottom: var(--space-6);
3898 - line-height: 1.6;
3899 - }
3900 4048 .policy-page .policy-section { margin-bottom: var(--space-6); line-height: 1.7; }
3901 4049 .policy-page .policy-section ul {
3902 4050 padding-left: var(--space-5);
@@ -4557,7 +4705,6 @@
4557 4705 opacity: 0.6;
4558 4706 }
4559 4707
4560 - .time-selector button.active,
4561 4708 .time-selector button.is-selected {
4562 4709 opacity: 1;
4563 4710 background: var(--primary-dark);
@@ -4952,11 +5099,10 @@
4952 5099
4953 5100 /* Canonical "selected" recipe (charter: docs/design-system.md).
4954 5101 Apply `.is-selected` to any interactive container to mark it as the
4955 - currently-selected option. Component-specific rules below (`.tab`,
4956 - `.filter-item`, `.view-btn`, `.type-card`/`.pricing-card` :checked)
4957 - also accept `.is-selected` as an alias for `.active` so templates can
4958 - migrate incrementally. `.badge.active` is intentionally NOT aliased
Lines truncated
@@ -186,37 +186,9 @@
186 186 border-top: 1px solid var(--border);
187 187 }
188 188
189 - /* Monetization Cards */
190 -
191 - .monetization-cards {
192 - display: grid;
193 - grid-template-columns: 1fr 1fr;
194 - gap: 1rem;
195 - margin-bottom: 1.5rem;
196 - }
197 -
198 - .monetization-card {
199 - background: var(--background);
200 - padding: 1.25rem;
201 - border: 1px solid var(--border);
202 - border-radius: 4px;
203 - }
204 -
205 - .monetization-card h3 {
206 - font-family: var(--font-mono);
207 - font-size: 0.95rem;
208 - margin-bottom: 0.5rem;
209 - }
210 -
211 - .monetization-card p {
212 - font-size: 0.85rem;
213 - color: var(--text-muted);
214 - margin-bottom: 0.5rem;
215 - }
216 -
217 - .monetization-card.info-only {
218 - opacity: 0.85;
219 - }
189 + /* (`.monetization-card{,-cards,.info-only}` removed 2026-05-20 — dead CSS,
190 + no template references. Selectable monetization choices use the canonical
191 + `.card--selectable` recipe in style.css.) */
220 192
221 193 .tier-row {
222 194 display: flex;
@@ -245,9 +217,8 @@
245 217 margin-bottom: 1.5rem;
246 218 }
247 219
248 - /* (.pricing-card and .content-choice-card primitives live in style.css under
249 - the .card--selectable canonical recipe. The "muted" pointer-events override
250 - and content-choice anchor-link styling remain here.) */
220 + /* (`.pricing-card`, `.content-choice-card`, and the `.is-disabled` modifier
221 + all live in style.css under the canonical `.card--selectable` recipe.) */
251 222 .pricing-fields {
252 223 margin-top: 1rem;
253 224 }
@@ -259,16 +230,6 @@
259 230 margin-bottom: 1.5rem;
260 231 }
261 232
262 - .content-choice-card {
263 - text-decoration: none;
264 - color: inherit;
265 - }
266 -
267 - .content-choice-card.muted {
268 - opacity: 0.7;
269 - pointer-events: none;
270 - }
271 -
272 233 /* Preview Summary */
273 234
274 235 .preview-summary {
@@ -544,7 +505,6 @@
544 505 white-space: nowrap;
545 506 }
546 507
547 - .monetization-cards,
548 508 .pricing-cards,
549 509 .content-choice-cards {
550 510 grid-template-columns: 1fr;
@@ -620,9 +580,6 @@
620 580 object-fit: cover;
621 581 }
622 582
623 - /* Native file input hidden behind a styled "Choose File" button. */
624 - .wizard-file-input { display: none; }
625 -
626 583 /* Stripe-step layout. */
627 584 .stripe-connect-box {
628 585 background: var(--surface-muted);
@@ -646,11 +603,11 @@
646 603 text-decoration: none;
647 604 background: var(--stripe);
648 605 border: none;
649 - color: #fff;
606 + color: var(--primary-light);
650 607 }
651 608
652 609 .stripe-connect-cta:hover {
653 - background: #5147e5;
610 + opacity: 0.85;
654 611 }
655 612
656 613 .stripe-connect-note {
@@ -677,7 +634,7 @@
677 634 }
678 635
679 636 .benefit-item::before {
680 - content: "\2713";
637 + content: "•";
681 638 position: absolute;
682 639 left: 0;
683 640 color: var(--success);