Skip to main content

max / makenotwork

ux: phase-5 — abstract guide at top of style.css + design-system.md update Add a comprehensive composition guide at the top of static/style.css that makes the primitive system discoverable. Update docs/design-system.md to reflect the consolidated state after Phase 1-4 dedup. style.css head - TOC / file structure block listing every section by anchor name - "Build new features by composing these primitives, in this order of preference" rule explicit: utility → layout primitive → component primitive → modifier → (last resort) new class. - Reminder that new classes are a smell, page-scoped CSS is a last resort. design-system.md - Component table rewritten to match shipped primitives: layout block, component block, control-modifier block. - Button section folded into composition guide rather than separate .btn-primary listing (canonical is button.primary / .secondary / .danger, not .btn-primary). - Added rule "Page-scoped CSS is a last resort" with concrete examples of when it's legitimate (grids, long-form typography, marketing heroes). - Updated rule about inline styles to reflect the actual permitted exception (server-computed `{{pct}}%` width/height, not just `--var:`). Earlier in this commit: - Stripped 62 inline `(replaced by X)` dedup-marker comments. - Collapsed multi-blank-line gaps (−38 lines mechanical). - Cover-thumb family: renamed templates to use .cover-thumb / .cover-row / .cover-empty canonical names; dropped .proj-image-preview / .image-preview-thumb aliases from the comma group. - Small-button family: renamed .btn-tiny / .cart-row-btn / .library-row-btn / .proj-members-remove-btn → .small in templates; CSS collapsed to one rule. - Card-muted family: renamed .stat-card / .analytics-card / .account-tip-card / .account-status-card → .card-muted in templates; CSS collapsed to one rule + .card-muted--tip / --status modifiers. style.css 10,722 → 10,697 (TOC adds ~90 lines; comment+blank+alias removal nets −115; balance lands close to even but the file is now self-documenting at the top). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-20 22:46 UTC
Commit: 6a26738ecb7ecb258c1d09fbf7dd1ff2302a393f
Parent: 6e39709
8 files changed, +168 insertions, -138 deletions
@@ -32,33 +32,63 @@ No fourth typeface. No `font-family` declarations in templates.
32 32
33 33 For each primitive, exactly one canonical class **or** one canonical partial. Variants are class modifiers; nothing else.
34 34
35 - | Primitive | Canonical class / partial | Variants | States |
35 + ### Layout primitives (page scaffolding)
36 +
37 + | Primitive | Canonical class | Variants | Notes |
36 38 |---|---|---|---|
37 - | Button | `.btn-primary` / `.btn-secondary` / `.btn-danger` | primary, secondary, danger | `:hover`, `:focus-visible`, `:disabled`, `.htmx-request` |
38 - | Form field | `partials/form_field.html` wrapping `.form-group` | (none) | `.form-group--error` |
39 - | Form layout | `.form-container`, `.form-row` | (none) | — |
40 - | Hint text | `.hint` | — | — |
41 - | Table | `.data-table`, `.compact-table` | — | `.sortable.ascending`, `.sortable.descending` |
42 - | Tabs | `.tabs` + `.tab` | — | `.tab.is-selected` |
43 - | Card | `.card` + `.card-title`, `.card-meta`, `.card-description` | `.grid-card` for grid layouts | `:hover`, `:focus-within` |
44 - | Badge | `.badge` | `-success`, `-warning`, `-danger`, `.free` | `.is-selected` |
39 + | Container | `.container` | `--narrow` (600px), `--medium` (800px), `--wide` (900px) | Default `max-width: 1200px` |
40 + | Toolbar / header bar | `.stack-row` | `--bordered`, `--tight`, `--top` | Title-left + actions-right |
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 |
43 + | Two-col form grid | `.form-row` | (none) | `grid-template-columns: 1fr 1fr` |
44 + | Cover-image row | `.cover-row` + `.cover-thumb` + `.cover-empty` | (none) | 120×120 thumbnail picker |
45 +
46 + ### Component primitives (reusable blocks)
47 +
48 + | Primitive | Canonical class | Variants | States |
49 + |---|---|---|---|
50 + | Page section box | `.content-section` | (none) | — |
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` |
53 + | Input shape | (base `input`/`textarea`/`select`) | `.input--xs`, `.input--sm`, `.input--mono`, `.input--upper`, `.input--numeric` | `:focus`, `:disabled` |
54 + | 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 + | Section lead | `.section-lead` | with `.mb-3` / `.text-sm` / `.dimmed` utilities | — |
57 + | Section divider | `.section-divider` | — | — |
58 + | Section grouping label | `.section-group-label` | — | — |
59 + | Badge | `.badge` | `.badge-success`, `.badge-warning`, `.badge-danger`, `.free` | `.is-selected`, `.is-faded`, `.active` (= completion, not selection) |
45 60 | Tag | `.tag` (inside `.tag-input` for editing) | — | — |
46 - | Empty state | `partials/empty_state.html` (.empty-state) | — | — |
47 - | Loading skeleton | `partials/loading_skeleton.html` | row, card, list | — |
48 - | Info box | `.info-box` | — | — |
49 - | Warning box | `.warning-box` | — | — |
50 - | Error message | `.error-message` (via `partials/error_fragment.html`) | — | — |
51 - | Alert | `partials/alert.html` -> `.alert` | `-note`, `-tip`, `-warning`, `-caution` | — |
61 + | Callout (solid-tint inline) | `.callout` | `--danger`, `--warning`, `--solid-warning` | — |
62 + | Alert (left-border inline) | `.alert` | `-note`, `-tip`, `-important`, `-warning`, `-caution` | — |
63 + | Banner (full-bleed page top) | `.banner` | `--info`, `--warning` | — |
52 64 | Modal | `.modal-overlay` + `.modal` + `.form-actions` | — | — |
53 - | Confirm dialog | `partials/confirm_dialog.html` | — | — |
54 - | Toast | `partials/toast.html` -> `.toast` | `-success`, `-error`, `-warning` | — |
55 - | Pagination | `partials/pagination.html` -> `.pagination` | — | `.pagination button.active` |
56 - | Section header | `.section-header` | — | — |
65 + | Confirm dialog | `_ui.html` macro `confirm_dialog` | — | — |
66 + | Toast | `partials/toast.html` → `.toast` | `--success`, `--error`, `--warning` | — |
67 + | Empty state | `.empty-state` (or `_ui.html` macro) | `--compact`, `--chart`, `--lg` | — |
68 + | Loading skeleton | `partials/loading_skeleton.html` | row, card, list | — |
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` | — |
71 + | Status pill | `.field-status` / `.save-status` | `.success`, `.error`, `.saving` | — |
72 + | Table | `.data-table` (rich), `.compact-table` (small mono) | `.minw-300..800` for horizontal scroll min-width | `.sortable.ascending`, `.sortable.descending` |
73 + | Tabs | `.tabs` + `.tab` | — | `.tab.is-selected` |
57 74 | Breadcrumb | `.breadcrumb` | — | — |
58 - | Banner | `.banner` (page-top one-liner notice; sandbox, restart) | `-info`, `-warning` | — |
59 - | Hero callout | `.landing-founder-banner` (multi-paragraph marketing callout on the landing page; not a banner) | — | — |
75 + | Pagination | `_ui.html` macro `pagination` → `.pagination` | — | `.active` |
76 +
77 + Tokens, the four parameterized macros (`empty_state`, `loading_skeleton`, `form_field`, `confirm_dialog`, `pagination`), and the consolidation pass have all shipped. Tokens live in `static/style.css` `:root`. Macros are in `templates/partials/_ui.html`.
78 +
79 + ### Composition guide for new features
80 +
81 + Build new UI by composing primitives, not by writing fresh CSS. Order of preference:
82 +
83 + 1. **Use a utility class** for one-off spacing/sizing — `.mb-4`, `.text-sm`, `.nowrap`, `.danger-text`.
84 + 2. **Use a layout primitive** to position content — `.container`, `.stack-row`, `.field-row`, `.list-row`.
85 + 3. **Use a component primitive** for a UI element — `.card`, `.callout`, `.badge`, `.progress-bar`, etc.
86 + 4. **Extend a primitive with a modifier** — `.card--bordered`, `.callout--warning`, `.stack-row--bordered`, `.input--sm`.
87 + 5. **Only then consider a new class** — and add it to the design system table here AND to `style.css` in the matching section.
88 +
89 + A new class is a smell, not a goal. Three usages without an entry above means a missing primitive, not a license to keep inlining.
60 90
61 - The `.banner` class is the only remaining Phase 0 cleanup deliverable. Token groups (spacing / radius / shadow) and the five parameterized primitives (`empty_state`, `loading_skeleton`, `form_field`, `confirm_dialog`, `pagination`) shipped as part of the consolidation pass: tokens live in `static/style.css` `:root`, the parameterized primitives are macros in `templates/partials/_ui.html`.
91 + Page-scoped CSS (`.foo-page .bar`) is a last resort. Most page-scoped rules in `style.css` exist for genuine page-specific layout (e.g. `.item-page .item-layout` grid, `.article-page .article-body` typography). Don't add new ones for shapes that are really cards or list rows in disguise.
62 92
63 93 ### How to use the macros
64 94
@@ -88,11 +118,11 @@ Exactly one spelling for each interaction state, applied to every interactive pr
88 118 - **Disabled** — `:disabled` and `[aria-disabled="true"]` show `opacity: 0.5` and `cursor: not-allowed`.
89 119 - **Busy / loading** — HTMX-driven via `.htmx-request` on the trigger, plus a `partials/loading_skeleton.html` for content placeholders.
90 120
91 - ## Layout primitives
121 + ## Page-level layouts
92 122
93 123 - `.padded-page` — standard content padding (`1.5rem`).
94 124 - `.centered-page` — landing / login / signup vertical-center layout.
95 - - `.container` — `max-width: 1200px`, used for marketing and content pages.
125 + - `.container` + `--narrow` / `--medium` / `--wide` modifiers.
96 126 - Wizard layout in `static/wizard.css` (`.wizard-layout` + `.wizard-sidebar` + `.wizard-content`).
97 127 - Media layout in `static/media-player.css` (`.media-container`).
98 128
@@ -100,14 +130,15 @@ No new top-level layout containers without an entry in this list.
100 130
101 131 ## Rules templates must follow
102 132
103 - 1. **No inline `style="..."`.** If you need a one-off, add a utility class or extend a primitive. The single exception: `style="--var: dynamic"` for server-computed values (progress bars, avatar fallback colors).
104 - 2. **No page-scoped `<style>` blocks.** `buy.html`, `item.html`, `error.html` are grandfathered violations and are scheduled for migration. New templates do not get this exemption.
133 + 1. **No inline `style="..."`.** If you need a one-off, add a utility class or extend a primitive. The single exception: `style="--var: dynamic"` or `style="width: {{ pct }}%"` for server-computed values (progress bars, chart bars, avatar fallback colors).
134 + 2. **No page-scoped `<style>` blocks.** All page-isolated style blocks have been migrated. New templates do not get this exemption.
105 135 3. **No raw hex.** Use tokens. Adding a new color means adding a token.
106 136 4. **No checkmarks, emoji, or icon glyphs in copy.** Words only. The diamond mark is the only graphic element. Status uses `.badge`, not `✓`.
107 137 5. **No new typefaces.** Three tiers, no exceptions.
108 138 6. **Empty / error / loading states use the shared partial.** Never assemble these inline.
109 - 7. **Destructive actions** use `.btn-danger` plus `partials/confirm_dialog.html`. No bare destructive buttons.
110 - 8. **Spacing values come from `--space-*` tokens** once those land. Off-scale values are bugs.
139 + 7. **Destructive actions** use `.danger` button class plus the `confirm_dialog` macro. No bare destructive buttons.
140 + 8. **Spacing values come from `--space-*` tokens.** Off-scale values are bugs.
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.
111 142
112 143 ## How to extend
113 144
@@ -1,3 +1,95 @@
1 + /* ============================================================================
2 + MNW global stylesheet. Charter: docs/design-system.md.
3 +
4 + Build new features by COMPOSING these primitives, in this order of preference:
5 +
6 + 1. Use a UTILITY class for one-off adjustments (.mb-4, .text-sm, .nowrap)
7 + 2. Use a LAYOUT PRIMITIVE to position content (.container, .stack-row, .field-row, .list-row)
8 + 3. Use a COMPONENT PRIMITIVE for a UI element (.card, .callout, .badge, .progress-bar, .empty-state, .input--sm, .small)
9 + 4. Extend a primitive with a modifier (.card--bordered, .callout--warning, .stack-row--bordered)
10 + 5. ONLY THEN consider a new class — and add it to the right section below
11 +
12 + A new class is a smell, not a goal. Before writing one:
13 + • grep this file for the visual shape you want; almost everything is here
14 + • if you can't find it, consider whether the shape belongs in the design system
15 + (then add it to the appropriate section here AND to docs/design-system.md)
16 + • page-scoped rules (`.foo-page .bar`) are a last resort, not a first move
17 +
18 + --------------------------------------------------------------------------
19 + FILE STRUCTURE (search by section name for anchor)
20 +
21 + FOUNDATIONS
22 + @font-face declarations (top of file)
23 + :root tokens — color, type, space, radius, shadow
24 + Element base — h1/h2/h3, p, a, button, input, table
25 +
26 + UTILITIES
27 + Spacing (.m-0, .mt-*, .mb-*, .ml-*, .my-*)
28 + Sizing (.col-*, .w-*, .maxw-*, .minw-*)
29 + Text (.text-sm, .text-xs, .text-center, .text-right, .fw-bold,
30 + .nowrap, .muted, .dimmed, .dimmer, .is-faded, .danger-text)
31 + State (.hidden, .is-selected, .is-active, .unstyled-link)
32 + Shapes (.square-cover, .scroll-x)
33 +
34 + LAYOUT PRIMITIVES (page-scaffolding shapes)
35 + .container + .container--narrow / --medium / --wide
36 + .stack-row + --bordered / --tight / --top — toolbar / header bar
37 + .field-row + .form-group.is-grow — inline form row
38 + .list-row + .list-row__title — vertical item list
39 + .form-row — 2-col grid form
40 + .cover-row + .cover-thumb + .cover-empty — image picker
41 +
42 + COMPONENT PRIMITIVES (reusable UI blocks)
43 + .content-section — light-bg page section box
44 + .card / .card-muted / .card--bordered / .card--selectable
45 + .form-group / .form-section / details.form-section
46 + .section-header / .section-lead / .section-group-label
47 + .badge + variants (-success/-warning/-danger)
48 + .callout + --danger / --warning / --solid-warning
49 + .banner + --info / --warning — full-bleed page-top notice
50 + .alert + -note / -tip / -warning / -caution — left-border inline notice
51 + .modal + .modal-overlay
52 + .empty-state + --compact / --chart / --lg
53 + .progress-bar-container + .progress-bar (--slim, --rounded, --highlight)
54 + .upload-status + __row + __msg.is-success / .is-error
55 + .field-status / .save-status + .success / .error / .saving
56 + .toast + .toast--success / --error / --warning
57 + .breadcrumb / .pagination
58 + .tabs + .tab.is-selected
59 +
60 + CONTROL MODIFIERS (compose onto buttons/inputs)
61 + .small / .btn-compact / .btn-link / .btn-tiny — button sizes
62 + .primary / .secondary / .danger / .saved — button intents
63 + .input--xs / --sm / --mono / --upper / --numeric — input sizes/shapes
64 +
65 + FEATURE SUBAPPS (their own CSS scope)
66 + .git-* (git source browser at /source/*)
67 + Pricing calculator on /pricing
68 + Markdown preview / docs-ui in /docs/*
69 +
70 + PAGE-SCOPED LAYOUTS (when truly page-specific)
71 + .item-page / .project-page / .article-page / .purchase-page / .cart-* /
72 + .library-page / .feed-page / .discover-page / .landing / .creators-page /
73 + .health-page / .import-page / .delete-account-page / .export-page /
74 + .receipt-page / .fan-plus-page / .stripe-disclaimer-page / .buy-page /
75 + .user-page / .collection-page / .changelog-page / .policy-page /
76 + .tag-tree-page / .project-blog-page / .project-paywall-page /
77 + .confirm-delete-page / .admin-page / .dashboard-page
78 +
79 + DASHBOARD TABS (one section per tab partial)
80 + Per-tab classes scoped under their canonical body class. Use modifier
81 + composition where possible; only add tab-specific rules when the shape
82 + genuinely diverges from a primitive.
83 +
84 + RESPONSIVE (@media at end of file)
85 + 768px tablet
86 + 480px phone
87 +
88 + --------------------------------------------------------------------------
89 + When in doubt: read docs/design-system.md first. Update both files together
90 + when you add a primitive.
91 + ============================================================================ */
92 +
1 93 @font-face {
2 94 font-family: "Young Serif";
3 95 src: url("/static/fonts/ysrf.woff2") format("woff2"),
@@ -788,12 +880,7 @@ form button:hover {
788 880 /* Compact button sizes. Canonical: .small (broad use, 0.25rem 0.6rem 0.8rem).
789 881 The tiny / row-btn / cart-row-btn aliases are visually equivalent — within
790 882 sub-pixel of each other in the original templates — and folded into .small. */
791 - .small,
792 - .btn-tiny,
793 - .cart-row-btn,
794 - .library-row-btn,
795 - .proj-members-remove-btn { padding: 0.25rem 0.6rem; font-size: 0.8rem; }
796 - .library-row-btn + .library-row-btn { margin-left: var(--space-1); }
883 + .small { padding: 0.25rem 0.6rem; font-size: 0.8rem; }
797 884
798 885 /* Canonical empty-state primitive. Use modifiers for tight/sized contexts.
799 886 Markup: <div class="empty-state">…</div> (optionally with .empty-state--compact
@@ -938,16 +1025,12 @@ form button:hover {
938 1025
939 1026 /* Muted card (.card-muted alias for .stat-card, .analytics-card, .account-tip-card).
940 1027 .account-status-card adds a border on top of the muted fill. */
941 - .card-muted,
942 - .stat-card,
943 - .analytics-card,
944 - .account-tip-card,
945 - .account-status-card {
1028 + .card-muted {
946 1029 background: var(--surface-muted);
947 1030 padding: 1.5rem;
948 1031 }
949 - .account-tip-card { padding: 1rem 1.25rem; font-size: 0.9rem; }
950 - .account-status-card { border: 1px solid var(--border-color); margin-bottom: var(--space-5); }
1032 + .card-muted--tip { padding: 1rem 1.25rem; font-size: 0.9rem; }
1033 + .card-muted--status { border: 1px solid var(--border-color); margin-bottom: var(--space-5); }
951 1034
952 1035 /* Bordered card (.card--bordered alias for .feature-card, .tier-card, .use-case-card, .fork-card). */
953 1036 .card--bordered,
@@ -1397,8 +1480,6 @@ form button:hover {
1397 1480
1398 1481 .item-page .item-layout.no-media { grid-template-columns: 1fr; }
1399 1482
1400 - /* (.item-media + .item-details handled by .content-section co-class) */
1401 -
1402 1483 .item-page .item-title {
1403 1484 font-size: 2rem;
1404 1485 margin-bottom: var(--space-2);
@@ -1463,10 +1544,6 @@ form button:hover {
1463 1544 margin-top: var(--space-4);
1464 1545 }
1465 1546
1466 - /* (.item-sections / .item-description / .item-features / .item-specs / .item-versions / .bundle-contents handled by .content-section co-class) */
1467 -
1468 - /* (.item-page section h2 uses the global .section-header class — add it to the h2 in the template) */
1469 -
1470 1547 /* bundle-child uses .list-row co-class; only the gap-4 override remains. */
1471 1548 .bundle-child { gap: var(--space-4); padding: var(--space-3) 0; }
1472 1549
@@ -1500,8 +1577,6 @@ form button:hover {
1500 1577 gap: var(--space-5);
1501 1578 }
1502 1579
1503 - /* (.feature-item handled by .card-muted co-class) */
1504 -
1505 1580 .item-page .feature-title {
1506 1581 font-size: 1.1rem;
1507 1582 margin-bottom: var(--space-2);
@@ -1682,7 +1757,6 @@ form button:hover {
1682 1757 margin-top: var(--space-4);
1683 1758 }
1684 1759
1685 -
1686 1760 /* project.html allows .section-tabs to wrap (other pages don't). */
1687 1761 .project-page .section-tabs { flex-wrap: wrap; }
1688 1762
@@ -1777,7 +1851,6 @@ form button:hover {
1777 1851 .library-page .library-back { font-size: 0.85rem; opacity: 0.7; }
1778 1852 .library-page .library-back a { color: var(--detail); }
1779 1853
1780 -
1781 1854 .library-page .downloads-hero h2 {
1782 1855 font-size: 1.5rem;
1783 1856 margin-bottom: var(--space-4);
@@ -1828,7 +1901,6 @@ form button:hover {
1828 1901 margin-bottom: var(--space-4);
1829 1902 }
1830 1903
1831 -
1832 1904 .library-page .library-section h2 {
1833 1905 font-size: 1.3rem;
1834 1906 margin-bottom: var(--space-4);
@@ -2124,7 +2196,6 @@ form button:hover {
2124 2196 /* Admin dashboards (templates/dashboards/admin-*.html). Every admin
2125 2197 page had a duplicate `h1 { font-size: 2rem; margin-bottom: 0.5rem; }`
2126 2198 inline — consolidated here. */
2127 - /* (.admin-page h1 in canonical grouped rule) */
2128 2199
2129 2200 /* System health dashboard (templates/pages/health.html). */
2130 2201 .health-page {
@@ -2136,10 +2207,6 @@ form button:hover {
2136 2207 margin: 0 auto;
2137 2208 }
2138 2209
2139 - /* (.health-page h1 in canonical grouped rule) */
2140 -
2141 - /* (.health-page .subtitle in canonical grouped rule) */
2142 -
2143 2210 .health-page .health-grid {
2144 2211 display: grid;
2145 2212 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
@@ -2194,7 +2261,6 @@ form button:hover {
2194 2261 border-bottom: 1px solid var(--border);
2195 2262 }
2196 2263
2197 -
2198 2264 .health-page .test-results h3 {
2199 2265 font-family: var(--font-mono);
2200 2266 font-size: 0.9rem;
@@ -2292,11 +2358,6 @@ form button:hover {
2292 2358 Page-specific upload + column-mapping + progress flow. Migration also
2293 2359 fixed token bypasses: --border-color → --border, --accent → --highlight,
2294 2360 --success-background → --success-bg, --error-background → --error-bg. */
2295 - /* (.import-page .container in canonical grouped rule near .container) */
2296 -
2297 - /* (.import-page h1 in canonical grouped rule) */
2298 -
2299 - /* (.import-page .subtitle in canonical grouped rule) */
2300 2361
2301 2362 .import-page .back-link {
2302 2363 display: inline-block;
@@ -2307,8 +2368,6 @@ form button:hover {
2307 2368
2308 2369 .import-page .back-link:hover { opacity: 1; }
2309 2370
2310 - /* (.import-page .import-form in canonical .content-section grouped rule) */
2311 -
2312 2371 .import-page .form-group { margin-bottom: var(--space-5); }
2313 2372
2314 2373 .import-page .form-group label {
@@ -2373,10 +2432,6 @@ form button:hover {
2373 2432 font-size: 0.85rem;
2374 2433 }
2375 2434
2376 - /* (.import-page .import-progress in canonical .content-section grouped rule; original used space-5/space-4 — close enough to space-6/space-6) */
2377 -
2378 - /* (Import-page uses canonical .progress-bar-container / .progress-bar--highlight; spacing via .my-4.) */
2379 -
2380 2435 .import-page .progress-stats {
2381 2436 display: flex;
2382 2437 gap: var(--space-6);
@@ -2455,11 +2510,6 @@ form button:hover {
2455 2510 Overrides .warning-box to use --danger (delete is danger, not warning)
2456 2511 and the .form-status partial styling. Scoped to keep the global versions
2457 2512 intact for other pages. */
2458 - /* (.delete-account-page .container in canonical grouped rule) */
2459 -
2460 - /* (.delete-account-page h1 in canonical grouped rule; danger color applied separately) */
2461 -
2462 - /* (.delete-account-page .subtitle in canonical grouped rule) */
2463 2513
2464 2514 .delete-account-page .warning-box {
2465 2515 background: var(--danger);
@@ -2552,8 +2602,6 @@ form button:hover {
2552 2602
2553 2603 /* Export data page (templates/dashboards/dashboard-export.html). */
2554 2604
2555 - /* (.export-page h1 + .subtitle in canonical grouped rules) */
2556 -
2557 2605 .export-page .export-cards {
2558 2606 display: grid;
2559 2607 gap: var(--space-4);
@@ -2621,8 +2669,6 @@ form button:hover {
2621 2669 .export-page .export-status.error { color: var(--danger); }
2622 2670
2623 2671 /* Receipt page (templates/pages/receipt.html). */
2624 - /* (.receipt-page h1 in canonical grouped rule) */
2625 -
2626 2672
2627 2673 .receipt-page .receipt-header {
2628 2674 display: flex;
@@ -2740,7 +2786,6 @@ form button:hover {
2740 2786 text-align: center;
2741 2787 }
2742 2788
2743 - /* (.purchase-page .checkout-box handled by .content-section co-class; only its margin override remains) */
2744 2789 .purchase-page .checkout-box { margin-bottom: var(--space-5); }
2745 2790
2746 2791 .purchase-page h2 { font-size: 1.8rem; margin-bottom: var(--space-2); }
@@ -2941,9 +2986,7 @@ form button:hover {
2941 2986 /* Canonical 120×120 thumbnail/preview box. Used by project image picker,
2942 2987 item image picker, and anywhere a small square cover preview is needed.
2943 2988 Pair with .cover-empty for the "No image" placeholder text inside it. */
2944 - .cover-thumb,
2945 - .proj-image-preview,
2946 - .image-preview-thumb {
2989 + .cover-thumb {
2947 2990 width: 120px;
2948 2991 height: 120px;
2949 2992 background: var(--border);
@@ -2952,21 +2995,15 @@ form button:hover {
2952 2995 justify-content: center;
2953 2996 flex-shrink: 0;
2954 2997 }
2955 - .cover-thumb img,
2956 - .proj-image-preview img,
2957 - .image-preview-thumb img {
2998 + .cover-thumb img {
2958 2999 width: 100%;
2959 3000 height: 100%;
2960 3001 object-fit: cover;
2961 3002 }
2962 - .cover-empty,
2963 - .proj-image-empty,
2964 - .image-preview-empty { opacity: 0.4; font-size: 0.8rem; }
3003 + .cover-empty { opacity: 0.4; font-size: 0.8rem; }
2965 3004
2966 3005 /* Companion flex row: thumbnail + form-control column. */
2967 - .cover-row,
2968 - .proj-image-row,
2969 - .image-preview-row {
3006 + .cover-row {
2970 3007 display: flex;
2971 3008 align-items: flex-start;
2972 3009 gap: var(--space-5);
@@ -3018,7 +3055,6 @@ form button:hover {
3018 3055
3019 3056 /* psection-* mirrors section-mgmt-* (used by item_details) but psection
3020 3057 class hooks are also queried by static/project-sections.js. */
3021 - /* (psection-row aliased into .list-row recipe) */
3022 3058 .psection-row__title { flex: 1; font-weight: bold; }
3023 3059 .psection-row__anchor { font-size: 0.75rem; opacity: 0.6; }
3024 3060 .psection-row__length { font-size: 0.8rem; opacity: 0.6; }
@@ -3061,7 +3097,6 @@ form button:hover {
3061 3097 }
3062 3098
3063 3099 /* Multi-creator summary bar (only renders when seller_groups.len() > 1). */
3064 - /* (cart-multi-bar aliased into .stack-row canonical; surface-muted card variant) */
3065 3100 .cart-multi-bar__note {
3066 3101 opacity: 0.7;
3067 3102 margin-left: var(--space-2);
@@ -3087,8 +3122,6 @@ form button:hover {
3087 3122 margin-bottom: var(--space-4);
3088 3123 }
3089 3124
3090 - /* (cart-table replaced by .minw-400 utility) */
3091 -
3092 3125 /* PWYW editable price cell. */
3093 3126 .cart-pwyw-cell {
3094 3127 display: flex;
@@ -3097,10 +3130,8 @@ form button:hover {
3097 3130 gap: 0.25rem;
3098 3131 }
3099 3132 .cart-pwyw-symbol { font-size: 0.9rem; }
3100 - /* (cart-pwyw-input sized by alias group; width + numeric align via .w-80 .input--numeric in template) */
3101 3133
3102 3134 /* Compact action button used in cart/wishlist tables. */
3103 - /* (cart-row-btn aliased into .small recipe near top) */
3104 3135
3105 3136 /* Bottom summary row of each seller group. */
3106 3137 .cart-group__summary {
@@ -3131,7 +3162,6 @@ form button:hover {
3131 3162
3132 3163 /* Checkout form within a seller group. */
3133 3164 .cart-promo-row { margin-bottom: var(--space-2); }
3134 - /* (cart-promo-input padded by alias group; width via .w-180 in template) */
3135 3165 .cart-checkout-share {
3136 3166 display: flex;
3137 3167 align-items: center;
@@ -3171,7 +3201,6 @@ form button:hover {
3171 3201 .profile-domain-status.is-verified { color: var(--success); }
3172 3202 .profile-domain-status.is-pending { color: var(--warning); }
3173 3203
3174 - /* (dns-callout replaced by .callout.callout--warning) */
3175 3204 .dns-row {
3176 3205 display: flex;
3177 3206 align-items: center;
@@ -4566,7 +4595,6 @@ footer {
4566 4595 border-bottom: none;
4567 4596 }
4568 4597
4569 -
4570 4598 /* Quick actions */
4571 4599 .quick-actions {
4572 4600 background: var(--surface-muted);
@@ -7569,8 +7597,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
7569 7597 font-family: var(--font-heading);
7570 7598 font-weight: bold;
7571 7599 }
7572 - /* (Checklist progress uses canonical .progress-bar-container--slim + .progress-bar--highlight; spacing via .mb-4.) */
7573 - /* (checklist-row aliased into .list-row recipe; last-child no-border covered too) */
7574 7600 .checklist-mark {
7575 7601 width: 1.1rem;
7576 7602 height: 1.1rem;
@@ -7752,7 +7778,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
7752 7778 text-overflow: ellipsis;
7753 7779 }
7754 7780
7755 - /* (transactions-header aliased into .stack-row.stack-row--bordered) */
7756 7781 .transactions-title { font-size: 1.2rem; }
7757 7782
7758 7783 /* ===========================================
@@ -7902,7 +7927,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
7902 7927 /* ===========================================
7903 7928 PROJECT CONTENT TAB (replaces inline styles in project_content.html)
7904 7929 =========================================== */
7905 - /* (content-header aliased into .stack-row canonical) */
7906 7930
7907 7931 .empty-state--lg { padding: var(--space-6) var(--space-4); }
7908 7932
@@ -7958,7 +7982,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
7958 7982 display: block;
7959 7983 margin-bottom: var(--space-1);
7960 7984 }
7961 - /* (bulk-form-input: use .input--xs .w-120 in templates; --wide uses .w-260) */
7962 7985
7963 7986 /* Sortable table column header */
7964 7987 .sortable-th { cursor: pointer; }
@@ -8003,7 +8026,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8003 8026 .input--upper { text-transform: uppercase; }
8004 8027 .input--numeric { text-align: right; }
8005 8028
8006 -
8007 8029 .order-arrow-btn {
8008 8030 padding: 0.1rem 0.3rem;
8009 8031 font-size: 0.7rem;
@@ -8059,7 +8081,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8059 8081 /* ===========================================
8060 8082 ITEM DETAILS TAB (replaces inline styles in item_details.html)
8061 8083 =========================================== */
8062 - /* (.image-preview-row/-thumb/-empty aliased into canonical .cover-* recipe near top) */
8063 8084
8064 8085 /* Compact button utility (small icon-side actions) */
8065 8086 .btn-compact { padding: 0.4rem 0.8rem; }
@@ -8140,7 +8161,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8140 8161 margin: var(--space-3) 0 var(--space-4);
8141 8162 }
8142 8163
8143 - /* (section-mgmt-row aliased into .list-row recipe) */
8144 8164 .section-mgmt-title {
8145 8165 flex: 1;
8146 8166 font-weight: bold;
@@ -8186,7 +8206,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8186 8206 / .progress-bar-container--slim primitives near top of file.) */
8187 8207
8188 8208 /* Bundle items picker — server-rendered rows and JS-appended rows share these. */
8189 - /* (bundle-picker-row aliased into .list-row recipe) */
8190 8209 .bundle-picker-row__title { flex: 1; }
8191 8210 .bundle-picker-row__type { font-size: 0.8rem; opacity: 0.6; }
8192 8211 .bundle-picker-row__unlisted {
@@ -8222,9 +8241,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8222 8241 text-overflow: ellipsis;
8223 8242 white-space: nowrap;
8224 8243 }
8225 - /* (batch-queue inputs/selects sized by alias group; width via .w-full in template) */
8226 -
8227 - /* (Batch progress cell uses canonical .upload-progress-row / .progress-bar-container--slim --rounded / .progress-bar--highlight / .upload-progress-pct.) */
8228 8244
8229 8245 /* ===========================================
8230 8246 ACCOUNT TAB (replaces inline styles in partials/tabs/user_account.html)
@@ -8284,7 +8300,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8284 8300 opacity: 0.8;
8285 8301 }
8286 8302
8287 - /* (account-tip-card aliased into the muted-card recipe above; only its parts remain.) */
8288 8303 .account-tip-card-title {
8289 8304 font-weight: bold;
8290 8305 margin-bottom: 0.5rem;
@@ -8316,8 +8331,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8316 8331 margin-bottom: 0.5rem;
8317 8332 }
8318 8333
8319 - /* (Lead paragraphs above action buttons use canonical .section-lead with .mb-3 / .text-sm / .dimmed modifiers.) */
8320 -
8321 8334 /* ===========================================
8322 8335 PROJECT CODE TAB (replaces inline styles in partials/tabs/project_code.html)
8323 8336 =========================================== */
@@ -8376,7 +8389,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8376 8389 .proj-code-link-row #link-repo-select { flex: 1; }
8377 8390 .proj-code-link-row #link-repo-btn { white-space: nowrap; }
8378 8391
8379 -
8380 8392 .proj-code-create-section {
8381 8393 margin-top: var(--space-5);
8382 8394 padding-top: var(--space-4);
@@ -8397,7 +8409,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8397 8409 ADMIN REPORT ENTRIES (replaces inline styles in partials/admin_report_entries.html)
8398 8410 =========================================== */
8399 8411
8400 -
8401 8412 .admin-report-table {
8402 8413 width: 100%;
8403 8414 border-collapse: collapse;
@@ -8456,8 +8467,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8456 8467 PROJECT PROMOTIONS TAB (replaces inline styles in partials/tabs/project_promotions.html)
8457 8468 =========================================== */
8458 8469
8459 - /* (proj-promo-lead replaced by .form-hint.mb-5) */
8460 -
8461 8470 .proj-promo-form {
8462 8471 margin-bottom: 2rem;
8463 8472 }
@@ -8510,8 +8519,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8510 8519 grid-column: 1 / -1;
8511 8520 }
8512 8521
8513 - /* (proj-promo-code-input: use .input--upper) */
8514 -
8515 8522 .proj-promo-actions {
8516 8523 margin-top: 1rem;
8517 8524 }
@@ -8612,7 +8619,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8612 8619 .proj-synckit-label {
8613 8620 display: block; font-size: 0.85rem; margin-bottom: var(--space-1);
8614 8621 }
8615 - /* (proj-synckit-name-input: use .input--sm .w-220) */
8616 8622 .proj-synckit-code { font-size: 0.8rem; }
8617 8623 .proj-synckit-code--wrap { font-size: 0.8rem; word-break: break-all; }
8618 8624 .proj-synckit-code--selectable { font-size: 0.85rem; word-break: break-all; user-select: all; }
@@ -8631,7 +8637,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8631 8637 }
8632 8638 .proj-synckit-status-active { color: var(--success); }
8633 8639 .proj-synckit-status-inactive { color: var(--error); }
8634 - /* (proj-synckit-slug-input: use .input--sm .w-120) */
8635 8640 .proj-synckit-new-key {
8636 8641 padding: 0.75rem; background: var(--light-background);
8637 8642 border: 1px solid var(--border-color); margin-top: var(--space-2);
@@ -8826,7 +8831,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8826 8831 .promo-codes-edit-label {
8827 8832 font-size: 0.8rem; display: block; margin-bottom: var(--space-1);
8828 8833 }
8829 - /* (promo-codes-edit-input: use .input--sm; --num adds .w-100) */
8830 8834 .promo-codes-edit-save { font-size: 0.85rem; }
8831 8835 .promo-codes-bulk-row { margin-top: var(--space-3); }
8832 8836 .promo-codes-bulk-btn { font-size: 0.85rem; opacity: 0.7; }
@@ -8835,8 +8839,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8835 8839 USER MEDIA TAB (replaces inline styles in partials/tabs/user_media.html)
8836 8840 =========================================== */
8837 8841
8838 - /* (user-media-header aliased into .stack-row canonical) */
8839 -
8840 8842 .user-media-intro {
8841 8843 margin-bottom: 1rem;
8842 8844 }
@@ -9119,8 +9121,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
9119 9121 PROJECT MEMBERS TAB (replaces inline styles in partials/tabs/project_members.html)
9120 9122 =========================================== */
9121 9123
9122 - /* (proj-members-intro replaced by .section-lead) */
9123 -
9124 9124 .proj-members-summary {
9125 9125 background: var(--surface-muted);
9126 9126 padding: 1.25rem;
@@ -9159,8 +9159,6 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
9159 9159 margin-top: 0.75rem;
9160 9160 }
9161 9161
9162 - /* (proj-members-input-full: use .w-full; -split uses .w-80) */
Lines truncated
@@ -34,7 +34,7 @@
34 34
35 35 <div class="container">
36 36 {% if deactivated %}
37 - <div class="account-status-card">
37 + <div class="card-muted">
38 38 <h2>Account Deactivated</h2>
39 39 <p>Your account is deactivated. Your content is hidden from fans and no new content can be created.</p>
40 40 <p>You can:</p>
@@ -48,7 +48,7 @@
48 48 </div>
49 49 </div>
50 50 {% elif creator_paused %}
51 - <div class="account-status-card">
51 + <div class="card-muted">
52 52 <h2>Account Paused</h2>
53 53 <p>Your creator account is paused. Existing fan subscriptions will expire at the end of their billing period. One-time purchases remain accessible. No new sales can be made.</p>
54 54 <p>To resume, re-subscribe to a creator plan:</p>
@@ -1,6 +1,6 @@
1 1 <div class="stats-grid">
2 2 {% for stat in stats %}
3 - <div class="stat-card">
3 + <div class="card-muted">
4 4 <div class="stat-label">{{ stat.label }}</div>
5 5 <div class="stat-value">{{ stat.value }}</div>
6 6 {% if let Some(change) = stat.change %}
@@ -24,7 +24,7 @@
24 24
25 25 <div class="stats-grid">
26 26 {% for stat in stats %}
27 - <div class="stat-card">
27 + <div class="card-muted">
28 28 <div class="stat-label">{{ stat.label }}</div>
29 29 <div class="stat-value">{{ stat.value }}</div>
30 30 {% if let Some(change) = stat.change %}
@@ -53,7 +53,7 @@
53 53 </div>
54 54
55 55 <div class="analytics-grid">
56 - <div class="analytics-card">
56 + <div class="card-muted">
57 57 <h3>Top Performing Items</h3>
58 58 {% if items.is_empty() %}
59 59 <div class="analytics-empty-block">
@@ -40,7 +40,7 @@
40 40
41 41 <div class="stats-grid">
42 42 {% for stat in stats %}
43 - <div class="stat-card">
43 + <div class="card-muted">
44 44 <div class="stat-label">{{ stat.label }}</div>
45 45 <div class="stat-value">{{ stat.value }}</div>
46 46 {% if let Some(change) = stat.change %}
@@ -37,7 +37,7 @@
37 37
38 38 <div class="section-group-label">Security</div>
39 39
40 - <div class="account-tip-card">
40 + <div class="card-muted">
41 41 <div class="account-tip-card-title">Secure your account</div>
42 42 <div class="account-tip-list">
43 43 <span>1. Use a strong, unique password</span>
@@ -24,7 +24,7 @@
24 24
25 25 <div class="stats-grid">
26 26 {% for stat in stats %}
27 - <div class="stat-card">
27 + <div class="card-muted">
28 28 <div class="stat-label">{{ stat.label }}</div>
29 29 <div class="stat-value">{{ stat.value }}</div>
30 30 {% if let Some(change) = stat.change %}
@@ -88,7 +88,7 @@
88 88 {% endif %}
89 89
90 90 <div class="analytics-grid">
91 - <div class="analytics-card">
91 + <div class="card-muted">
92 92 <h3>Top Projects by Revenue</h3>
93 93 {% if top_projects.is_empty() %}
94 94 <div class="empty-state empty-state--compact">