Skip to main content

max / makenotwork

Retire --space-* and put every spacing value on the gap scale Completes the conversion. --space-* is gone from the tree: its definitions, its 800-odd usages, the utility classes that wrapped it, and the charter text describing it. No value opts out. The earlier pass kept nine literals above the 3rem scale top and wrote comments explaining each, which was wrong: five footers all reaching for 4rem and four containers all repeating 3rem 1.5rem 5rem are two patterns done in several places, not nine intentional exceptions. They snap to the scale like everything else. The utilities take relationship names too, so there is one vocabulary rather than three. .mt-3 and .mt-4 had both collapsed onto --gap-section, which made the numeric names actively misleading; they are now .mt-section, and 134 template usages follow. Two converter bugs found and fixed while doing this, both of which had produced wrong output. It treated everything after a colon as the value, so on a line packing several declarations it rewrote a following font-size as a spacing token. And it only matched a property at line start or after a semicolon, never after a selector's brace, so the first declaration of every single-line rule was skipped, leaving rules like .my-2 holding two names for one value.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-02 20:18 UTC
Signed with PGP, not checked
Commit: 82fd934b1a99d3363aa10acb3d9c1c7f0fc3a59a
Parent: 0c419e3
42 files changed, +227 insertions, -251 deletions
@@ -12,7 +12,7 @@
12 12 |---|---|---|
13 13 | Color | `--background`, `--detail`, `--highlight`, `--light-background`, surface family, `--text-muted`, `--border`, semantic (`--success/-bg`, `--warning/-bg/-border`, `--danger/-bg`, `--error/-bg`), `--stripe`, health (`--health-ok/-warn/-error/-unknown`), diff (`--diff-add/-bg`, `--diff-del/-bg`), `--focus-ring`, `--highlight-faint`, `--overlay` | `style.css:40-95` |
14 14 | Type | `--font-heading` (Young Serif), `--font-mono` (IBM Plex Mono), `--font-body` (Lato) | `style.css:42-44` |
15 - | Spacing | `--space-1`...`--space-6` mapping to `0.25 / 0.5 / 0.75 / 1 / 1.5 / 2 rem` | `style.css:96-103` |
15 + | Spacing | `--gap-bound` / `--gap-peer` / `--gap-group` / `--gap-section` / `--gap-pane` / `--gap-page`, named for the relationship they express, not the size | `geometry.css` (generated by makeover-build) |
16 16 | Radius | `--radius-sm` (4px, controls: buttons, inputs, chips, badges), `--radius-md` (0, containers: cards, panels, dropdowns), `--radius-round` (50%) | `style.css:216-222` |
17 17 | Shadow | Everyday depth: `--shadow-raised` (buttons, tabs), `--shadow-card` (cards, panels), `--shadow-inset` (inputs, recessed). All three alias the bevel pair `--bevel-raised` / `--bevel-inset` from `layout.css`, which makeover derives from each theme's own raised surface. Blurred (true elevation, floats over page): `--shadow-1` (subtle), `--shadow-2` (raised), `--shadow-3` (modal/overlay). | `style.css:224-243` |
18 18
@@ -53,7 +53,7 @@
53 53 | Heading | `.brand-h1` (wordmark), `.page-title` (h1), `.subtitle-h2` (auth/wizard h2), `.subsection-title` (default h2), `.section-header` (h2 with bottom border) | — | — |
54 54 | Input shape | (base `input`/`textarea`/`select`) | `.input--xs`, `.input--sm`, `.input--mono`, `.input--upper`, `.input--numeric` | `:focus`, `:disabled` |
55 55 | Form field | `.form-group` + `.hint` | `.form-group--error` + `.field-error` | (via `_ui.html` macro `form_field`) |
56 - | Section lead | `.section-lead` | with `.mb-3` / `.text-sm` / `.dimmed` utilities | — |
56 + | Section lead | `.section-lead` | with `.mb-section` / `.text-sm` / `.dimmed` utilities | — |
57 57 | Section divider | `.section-divider` | — | — |
58 58 | Section grouping label | `.section-group-label` | — | — |
59 59 | Badge | `.badge` | `.badge-success`, `.badge-warning`, `.badge-danger`, `.free` | `.is-selected`, `.is-faded`, `.active` (= completion, not selection) |
@@ -80,7 +80,7 @@
80 80
81 81 Build new UI by composing primitives, not by writing fresh CSS. Order of preference:
82 82
83 - 1. **Use a utility class** for one-off spacing/sizing, `.mb-4`, `.text-sm`, `.nowrap`, `.danger-text`.
83 + 1. **Use a utility class** for one-off spacing/sizing, `.mb-section`, `.text-sm`, `.nowrap`, `.danger-text`.
84 84 2. **Use a layout primitive** to position content, `.container`, `.stack-row`, `.field-row`, `.list-row`.
85 85 3. **Use a component primitive** for a UI element, `.card`, `.callout`, `.badge`, `.progress-bar`, etc.
86 86 4. **Extend a primitive with a modifier**: `.card--bordered`, `.callout--warning`, `.stack-row--bordered`, `.input--sm`.
@@ -140,7 +140,7 @@
140 140 5. **No new typefaces.** Three tiers, no exceptions.
141 141 6. **Empty / error / loading states use the shared partial.** Never assemble these inline.
142 142 7. **Destructive actions** use `.danger` button class plus the `confirm_dialog` macro. No bare destructive buttons.
143 - 8. **Spacing values come from `--space-*` tokens.** Off-scale values are bugs.
143 + 8. **Spacing values come from `--gap-*` tokens.** Pick the one that names what the rule separates: `bound` inside a thing, `peer` between siblings, `group` between related controls, `section` between parts of a pane, `pane` between panes, `page` at page level. Raw values and off-scale values are bugs. The tokens are generated from makeover-geometry and shift under a coarse pointer, so a hardcoded value silently opts that rule out of touch density.
144 144 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.
145 145 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.
146 146 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.
@@ -4,9 +4,7 @@
4 4 .media-container {
5 5 max-width: 680px;
6 6 margin: 0 auto;
7 - /* Bottom is 5rem, above the scale top of 3rem: deliberate long-scroll
8 - runout under the footer, not a relationship. Kept literal. */
9 - padding: var(--gap-page) var(--gap-pane) 5rem;
7 + padding: var(--gap-page) var(--gap-pane) var(--gap-page);
10 8 }
11 9
12 10 /* Author header */
@@ -203,16 +203,6 @@
203 203 --diff-del: #a83232;
204 204 --diff-del-bg: rgba(168, 50, 50, 0.08);
205 205
206 - /* Spacing scale (charter: docs/design-system.md) */
207 - --space-1: 0.25rem;
208 - --space-2: 0.5rem;
209 - --space-3: 0.75rem;
210 - --space-4: 1rem;
211 - --space-5: 1.5rem;
212 - --space-6: 2rem;
213 - --space-7: 3rem;
214 - --space-8: 4rem;
215 -
216 206 /* Radius scale. Containers are square and controls keep a corner: the
217 207 rounding survives only where a thing is meant to be pressed, so the
218 208 corner reads as an affordance rather than as decoration. --radius-md is
@@ -940,26 +930,24 @@
940 930 .unstyled-link { text-decoration: none; color: inherit; }
941 931 .square-cover { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }
942 932
943 - /* Spacing utilities (mapped to --space-* scale).
944 - mt = margin-top, mb = margin-bottom, my = vertical, mx = horizontal,
945 - m0 = margin reset. Use sparingly; prefer parent layout primitives. */
933 + /* Spacing utilities.
934 + mt = margin-top, mb = margin-bottom, my = vertical, py = vertical padding.
935 + Named for the relationship, same vocabulary as the --gap-* tokens they
936 + read. Use sparingly; prefer parent layout primitives. */
946 937 .m-0 { margin: 0; }
947 938 .mt-0 { margin-top: 0; }
948 - .py-3 { padding-block: var(--space-3); }
949 - .mt-2 { margin-top: var(--space-2); }
950 - .mt-3 { margin-top: var(--space-3); }
951 - .mt-4 { margin-top: var(--space-4); }
952 - .mt-5 { margin-top: var(--space-5); }
953 - .mt-6 { margin-top: var(--space-6); }
954 939 .mb-0 { margin-bottom: 0; }
955 - .mb-2 { margin-bottom: var(--space-2); }
956 - .mb-3 { margin-bottom: var(--space-3); }
957 - .mb-4 { margin-bottom: var(--space-4); }
958 - .mb-5 { margin-bottom: var(--space-5); }
959 - .mb-6 { margin-bottom: var(--space-6); }
960 - .my-2 { margin-top: var(--space-2); margin-bottom: var(--space-2); }
961 - .my-3 { margin-top: var(--space-3); margin-bottom: var(--space-3); }
962 - .my-4 { margin-top: var(--space-4); margin-bottom: var(--space-4); }
940 + .mt-peer { margin-top: var(--gap-peer); }
941 + .mt-section { margin-top: var(--gap-section); }
942 + .mt-pane { margin-top: var(--gap-pane); }
943 + .mt-page { margin-top: var(--gap-page); }
944 + .mb-peer { margin-bottom: var(--gap-peer); }
945 + .mb-section { margin-bottom: var(--gap-section); }
946 + .mb-pane { margin-bottom: var(--gap-pane); }
947 + .mb-page { margin-bottom: var(--gap-page); }
948 + .my-peer { margin-block: var(--gap-peer); }
949 + .my-section { margin-block: var(--gap-section); }
950 + .py-section { padding-block: var(--gap-section); }
963 951
964 952 /* Min-width utilities for tables inside .scroll-x wrappers. */
965 953 .minw-300 { min-width: 300px; }
@@ -972,15 +960,15 @@
972 960 /* Compact button sizes. Canonical: .small (broad use, 0.25rem 0.6rem 0.8rem).
973 961 The tiny / row-btn / cart-row-btn aliases are visually equivalent, within
974 962 sub-pixel of each other in the original templates, and folded into .small. */
975 - .small { padding: 0.25rem 0.6rem; font-size: 0.8rem; }
963 + .small { padding: var(--gap-bound) var(--gap-group); font-size: 0.8rem; }
976 964
977 965 /* Canonical empty-state primitive. Use modifiers for tight/sized contexts.
978 966 Markup: <div class="empty-state">…</div> (optionally with .empty-state--compact
979 967 inside dropdowns or .empty-state--chart inside a fixed-height chart slot). */
980 - .empty-state { text-align: center; padding: 2rem; opacity: 0.6; }
981 - .empty-state--compact { padding: 0.75rem; font-size: 0.9rem; }
968 + .empty-state { text-align: center; padding: var(--gap-page); opacity: 0.6; }
969 + .empty-state--compact { padding: var(--gap-section); font-size: 0.9rem; }
982 970 .empty-state--chart { height: 200px; padding: 0; display: flex; align-items: center; justify-content: center; opacity: 0.5; }
983 - .empty-state-hint { font-family: var(--font-mono); font-size: 0.8rem; margin-top: 0.5rem; opacity: 0.6; }
971 + .empty-state-hint { font-family: var(--font-mono); font-size: 0.8rem; margin-top: var(--gap-peer); opacity: 0.6; }
984 972
985 973 .tab-docs {
986 974 display: flex;
@@ -1185,8 +1173,8 @@
1185 1173 border: 1px solid var(--border);
1186 1174 margin-bottom: 0;
1187 1175 }
1188 - .use-case-card { padding: 1.25rem; }
1189 - .fork-card { padding: 1.5rem; display: flex; flex-direction: column; }
1176 + .use-case-card { padding: var(--gap-section); }
1177 + .fork-card { padding: var(--gap-pane); display: flex; flex-direction: column; }
1190 1178
1191 1179 .card-title {
1192 1180 font-size: 1.2rem;
@@ -1608,13 +1596,13 @@
1608 1596 not a selection, kept as .active. */
1609 1597 .section-panel { display: none; }
1610 1598 .section-panel.active { display: block; }
1611 - .section-panel p { margin-bottom: var(--space-4); }
1599 + .section-panel p { margin-bottom: var(--gap-section); }
1612 1600 .section-panel ul,
1613 1601 .section-panel ol {
1614 1602 margin-left: var(--gap-pane);
1615 1603 margin-bottom: var(--gap-section);
1616 1604 }
1617 - .section-panel li { margin-bottom: var(--space-2); }
1605 + .section-panel li { margin-bottom: var(--gap-peer); }
1618 1606 .section-panel h1,
1619 1607 .section-panel h2,
1620 1608 .section-panel h3 {
@@ -1669,10 +1657,10 @@
1669 1657 }
1670 1658
1671 1659 .item-page .meta-item { font-size: 0.9rem; }
1672 - .item-page .meta-label { opacity: 0.7; margin-bottom: var(--space-1); }
1660 + .item-page .meta-label { opacity: 0.7; margin-bottom: var(--gap-bound); }
1673 1661 .item-page .meta-value { font-weight: bold; }
1674 1662
1675 - .item-page .item-tags { margin-bottom: var(--space-5); }
1663 + .item-page .item-tags { margin-bottom: var(--gap-pane); }
1676 1664
1677 1665 /* purchase-box uses .card-muted for the box; only its h3/ul/li specifics remain. */
1678 1666 .purchase-box h3 {
@@ -1680,8 +1668,8 @@
1680 1668 font-weight: normal;
1681 1669 margin-bottom: var(--gap-section);
1682 1670 }
1683 - .purchase-box ul { list-style: none; margin-bottom: var(--space-4); }
1684 - .purchase-box li { padding: var(--space-1) 0; }
1671 + .purchase-box ul { list-style: none; margin-bottom: var(--gap-section); }
1672 + .purchase-box li { padding: var(--gap-bound) 0; }
1685 1673 .purchase-box li::before {
1686 1674 content: "→ ";
1687 1675 opacity: 0.5;
@@ -1706,7 +1694,7 @@
1706 1694 }
1707 1695
1708 1696 /* bundle-child uses .list-row co-class; only the gap-4 override remains. */
1709 - .bundle-child { gap: var(--space-4); padding: var(--space-3) 0; }
1697 + .bundle-child { gap: var(--gap-section); padding: var(--gap-section) 0; }
1710 1698
1711 1699 .item-page .bundle-child-type {
1712 1700 font-size: 0.8rem;
@@ -1728,9 +1716,9 @@
1728 1716
1729 1717 .item-page .bundle-notice a { color: var(--content); }
1730 1718
1731 - .item-page .item-description p { margin-bottom: var(--space-4); text-align: left; }
1732 - .item-page .item-description ul { margin-left: var(--space-5); margin-bottom: var(--space-4); }
1733 - .item-page .item-description li { margin-bottom: var(--space-2); }
1719 + .item-page .item-description p { margin-bottom: var(--gap-section); text-align: left; }
1720 + .item-page .item-description ul { margin-left: var(--gap-pane); margin-bottom: var(--gap-section); }
1721 + .item-page .item-description li { margin-bottom: var(--gap-peer); }
1734 1722
1735 1723 .item-page .features-grid {
1736 1724 display: grid;
@@ -1757,20 +1745,20 @@
1757 1745 @media (max-width: 480px) {
1758 1746 .item-page .item-title { font-size: 1.5rem; }
1759 1747 .item-page .item-price { font-size: 1.8rem; }
1760 - .item-page .item-meta { grid-template-columns: 1fr; gap: var(--space-2); }
1748 + .item-page .item-meta { grid-template-columns: 1fr; gap: var(--gap-peer); }
1761 1749 .item-page .item-media,
1762 - .item-page .item-details { padding: var(--space-4); }
1763 - .item-page .item-description { padding: var(--space-4); }
1750 + .item-page .item-details { padding: var(--gap-section); }
1751 + .item-page .item-description { padding: var(--gap-section); }
1764 1752 .item-page .item-description h2 { font-size: 1.2rem; }
1765 - .item-page .purchase-box { padding: var(--space-4); }
1753 + .item-page .purchase-box { padding: var(--gap-section); }
1766 1754 }
1767 1755
1768 1756 /* Project store-front page (templates/pages/project.html). Scoped under
1769 1757 .project-page body class, `.item-card`, `.item-title`, `.item-meta`,
1770 1758 `.item-price`, etc. here mean store-front item cards, distinct from
1771 1759 the item-detail-page versions scoped under .item-page above. */
1772 - .project-page .store-header { margin-bottom: 3rem; }
1773 - .project-page .store-title { font-size: 3rem; margin-bottom: var(--space-2); }
1760 + .project-page .store-header { margin-bottom: var(--gap-page); }
1761 + .project-page .store-title { font-size: 3rem; margin-bottom: var(--gap-peer); }
1774 1762 .project-page .store-meta {
1775 1763 font-size: 0.9rem;
1776 1764 opacity: 0.7;
@@ -1789,7 +1777,7 @@
1789 1777 flex-wrap: wrap;
1790 1778 }
1791 1779
1792 - .project-page .items-section { margin-bottom: 3rem; }
1780 + .project-page .items-section { margin-bottom: var(--gap-page); }
1793 1781
1794 1782 .project-page .items-grid {
1795 1783 display: grid;
@@ -1845,7 +1833,7 @@
1845 1833 margin-bottom: var(--gap-section);
1846 1834 }
1847 1835
1848 - .project-page .item-tags { margin-bottom: var(--space-4); }
1836 + .project-page .item-tags { margin-bottom: var(--gap-section); }
1849 1837
1850 1838 .project-page .item-description {
1851 1839 font-size: 0.9rem;
@@ -1866,11 +1854,7 @@
1866 1854 .project-page .item-stats { font-size: 0.85rem; opacity: 0.6; }
1867 1855
1868 1856 .project-page .store-footer {
1869 - /* 4rem sits above the scale top of 3rem. The gap vocabulary ends at
1870 - --gap-page, and this wants more separation than a page: it is the last
1871 - rule on a store before the footer. Kept literal until the scale grows a
1872 - name for it. Pairs with .article-page .article-footer. */
1873 - margin-top: 4rem;
1857 + margin-top: var(--gap-page);
1874 1858 padding-top: var(--gap-page);
1875 1859 border-top: 1px solid var(--border);
1876 1860 opacity: 0.6;
@@ -1887,7 +1871,7 @@
1887 1871 /* project.html allows .section-tabs to wrap (other pages don't). */
1888 1872 .project-page .section-tabs { flex-wrap: wrap; }
1889 1873
1890 - .project-page .tiers-section { margin-bottom: 3rem; }
1874 + .project-page .tiers-section { margin-bottom: var(--gap-page); }
1891 1875
1892 1876 .project-page .tiers-grid {
1893 1877 display: grid;
@@ -2096,10 +2080,7 @@
2096 2080 .article-page .article-container {
2097 2081 max-width: 680px;
2098 2082 margin: 0 auto;
2099 - /* Bottom is 5rem, above the scale top: long-scroll runout under the
2100 - footer, not a relationship. Same shape as .media-container in
2101 - media-player.css; the two should move together if either changes. */
2102 - padding: var(--gap-page) var(--gap-pane) 5rem;
2083 + padding: var(--gap-page) var(--gap-pane) var(--gap-page);
2103 2084 }
2104 2085
2105 2086 .article-page .author-header {
@@ -2167,7 +2148,7 @@
2167 2148 line-height: 1.9;
2168 2149 }
2169 2150
2170 - .article-page .article-body p { margin-bottom: var(--space-5); }
2151 + .article-page .article-body p { margin-bottom: var(--gap-pane); }
2171 2152
2172 2153 .article-page .article-body h1 {
2173 2154 font-family: var(--font-heading);
@@ -2211,7 +2192,7 @@
2211 2192 padding-left: var(--gap-pane);
2212 2193 }
2213 2194
2214 - .article-page .article-body li { margin-bottom: var(--space-2); }
2195 + .article-page .article-body li { margin-bottom: var(--gap-peer); }
2215 2196
2216 2197 .article-page .article-body a {
2217 2198 color: var(--action);
@@ -2259,8 +2240,7 @@
2259 2240 }
2260 2241
2261 2242 .article-page .article-footer {
2262 - /* Above the scale top, same reason as .project-page .store-footer. */
2263 - margin-top: 4rem;
2243 + margin-top: var(--gap-page);
2264 2244 padding-top: var(--gap-page);
2265 2245 border-top: 1px solid var(--border);
2266 2246 }
@@ -2497,7 +2477,7 @@
2497 2477
2498 2478 .import-page .back-link:hover { opacity: 1; }
2499 2479
2500 - .import-page .form-group { margin-bottom: var(--space-5); }
2480 + .import-page .form-group { margin-bottom: var(--gap-pane); }
2501 2481
2502 2482 .import-page .form-group label {
2503 2483 display: block;
@@ -2539,7 +2519,7 @@
2539 2519 font-family: var(--font-mono);
2540 2520 }
2541 2521
2542 - .import-page .column-mapping { margin-top: var(--space-4); }
2522 + .import-page .column-mapping { margin-top: var(--gap-section); }
2543 2523
2544 2524 .import-page .mapping-row {
2545 2525 display: flex;
@@ -2590,7 +2570,7 @@
2590 2570 white-space: pre-wrap;
2591 2571 }
2592 2572
2593 - .import-page .import-history { margin-top: var(--space-6); }
2573 + .import-page .import-history { margin-top: var(--gap-page); }
2594 2574
2595 2575 .import-page .history-table {
2596 2576 width: 100%;
@@ -2658,7 +2638,7 @@
2658 2638 font-size: 0.9rem;
2659 2639 }
2660 2640
2661 - .delete-account-page .warning-box li { margin-bottom: var(--space-1); }
2641 + .delete-account-page .warning-box li { margin-bottom: var(--gap-bound); }
2662 2642
2663 2643 .delete-account-page .export-prompt {
2664 2644 background: var(--surface-sunken);
@@ -2682,7 +2662,7 @@
2682 2662 padding: var(--gap-pane);
2683 2663 }
2684 2664
2685 - .delete-account-page .delete-form .form-group { margin-bottom: var(--space-5); }
2665 + .delete-account-page .delete-form .form-group { margin-bottom: var(--gap-pane); }
2686 2666
2687 2667 .delete-account-page .delete-form label {
2688 2668 display: block;
@@ -2855,12 +2835,12 @@
2855 2835 }
2856 2836
2857 2837 /* Fan+ subscription page (templates/pages/fan_plus.html). */
2858 - .fan-plus-page h1 { font-size: 2.5rem; margin-bottom: var(--space-2); }
2859 - .fan-plus-page h2 { font-size: 1.5rem; margin-top: var(--space-6); margin-bottom: var(--space-4); }
2838 + .fan-plus-page h1 { font-size: 2.5rem; margin-bottom: var(--gap-peer); }
2839 + .fan-plus-page h2 { font-size: 1.5rem; margin-top: var(--gap-page); margin-bottom: var(--gap-section); }
2860 2840
2861 - .fan-plus-page .fan-plus-section { margin-bottom: var(--space-6); line-height: 1.7; }
2862 - .fan-plus-page .fan-plus-section ul { padding-left: var(--space-5); margin: var(--space-3) 0; }
2863 - .fan-plus-page .fan-plus-section li { margin-bottom: var(--space-2); }
2841 + .fan-plus-page .fan-plus-section { margin-bottom: var(--gap-page); line-height: 1.7; }
2842 + .fan-plus-page .fan-plus-section ul { padding-left: var(--gap-pane); margin: var(--gap-section) 0; }
2843 + .fan-plus-page .fan-plus-section li { margin-bottom: var(--gap-peer); }
2864 2844
2865 2845 .fan-plus-page .price-callout {
2866 2846 font-size: 1.3rem;
@@ -2868,7 +2848,7 @@
2868 2848 margin: var(--gap-pane) 0;
2869 2849 }
2870 2850
2871 - .fan-plus-page .subscribe-form { margin: var(--space-6) 0; }
2851 + .fan-plus-page .subscribe-form { margin: var(--gap-page) 0; }
2872 2852
2873 2853 .fan-plus-page .subscribe-btn {
2874 2854 display: inline-block;
@@ -2889,7 +2869,7 @@
2889 2869 margin: var(--gap-pane) 0;
2890 2870 }
2891 2871
2892 - .fan-plus-page .status-box p { margin: var(--space-2) 0; }
2872 + .fan-plus-page .status-box p { margin: var(--gap-peer) 0; }
2893 2873
2894 2874 .fan-plus-page .success-banner {
2895 2875 padding: var(--gap-section) var(--gap-pane);
@@ -2914,9 +2894,9 @@
2914 2894 text-align: center;
2915 2895 }
2916 2896
2917 - .purchase-page .checkout-box { margin-bottom: var(--space-5); }
2897 + .purchase-page .checkout-box { margin-bottom: var(--gap-pane); }
2918 2898
2919 - .purchase-page h2 { font-size: 1.8rem; margin-bottom: var(--space-2); }
2899 + .purchase-page h2 { font-size: 1.8rem; margin-bottom: var(--gap-peer); }
2920 2900
2921 2901 .purchase-page .step-indicator {
2922 2902 font-size: 0.85rem;
@@ -2968,7 +2948,7 @@
2968 2948 margin-bottom: var(--gap-pane);
2969 2949 }
2970 2950
2971 - .purchase-page .price-breakdown h2 { font-size: 1.1rem; margin-bottom: var(--space-4); }
2951 + .purchase-page .price-breakdown h2 { font-size: 1.1rem; margin-bottom: var(--gap-section); }
2972 2952
2973 2953 .purchase-page .price-row {
2974 2954 display: flex;
@@ -2987,11 +2967,11 @@
2987 2967
2988 2968 .purchase-page .price-row.total { font-weight: bold; }
2989 2969
2990 - .purchase-page .payment-section { margin-bottom: var(--space-5); }
2991 - .purchase-page .payment-section h2 { font-size: 1.1rem; margin-bottom: var(--space-4); }
2970 + .purchase-page .payment-section { margin-bottom: var(--gap-pane); }
2971 + .purchase-page .payment-section h2 { font-size: 1.1rem; margin-bottom: var(--gap-section); }
2992 2972
2993 2973 .purchase-page .btn-primary { width: 100%; }
2994 - .purchase-page .btn-secondary { width: 100%; margin-top: var(--space-4); }
2974 + .purchase-page .btn-secondary { width: 100%; margin-top: var(--gap-section); }
2995 2975
2996 2976 .purchase-page .security-note {
2997 2977 font-size: 0.85rem;
@@ -3048,7 +3028,7 @@
3048 3028 background: var(--surface-sunken);
3049 3029 padding: var(--gap-section);
3050 3030 }
3051 - .purchase-page .pending-checkout p { margin: 0 0 var(--space-3); }
3031 + .purchase-page .pending-checkout p { margin: 0 0 var(--gap-section); }
3052 3032 .purchase-page .pending-checkout p.is-sub {
3053 3033 font-size: 0.9rem;
3054 3034 opacity: 0.8;
@@ -3056,7 +3036,7 @@
3056 3036 .purchase-page .pending-checkout form { margin: 0; }
3057 3037
3058 3038 /* Checkout form field wrappers. */
3059 - .purchase-page .checkout-field { margin-bottom: var(--space-4); }
3039 + .purchase-page .checkout-field { margin-bottom: var(--gap-section); }
3060 3040 .purchase-page .checkout-field-label {
3061 3041 font-size: 0.85rem;
3062 3042 display: block;
@@ -3114,7 +3094,7 @@
3114 3094 .proj-image-hint {
3115 3095 font-size: 0.85rem;
3116 3096 opacity: 0.7;
3117 - margin: 0 0 var(--space-2);
3097 + margin: 0 0 var(--gap-peer);
3118 3098 }
3119 3099
3120 3100 /* Canonical inline form-status text following a Save / Change button.
@@ -3122,7 +3102,7 @@
3122 3102 States via .success / .error / .saving modifier. */
3123 3103 .field-status,
3124 3104 .save-status {
3125 - margin-left: var(--space-2);
3105 + margin-left: var(--gap-peer);
3126 3106 font-size: 0.85rem;
3127 3107 }
3128 3108 .field-status.success, .save-status.success { color: var(--success); }
@@ -3131,7 +3111,7 @@
3131 3111
3132 3112 /* Muted lead paragraph above a form button (monetization / features / delete). */
3133 3113 .section-lead {
3134 - margin-bottom: var(--space-4);
3114 + margin-bottom: var(--gap-section);
3135 3115 opacity: 0.7;
3136 3116 text-align: left;
3137 3117 }
@@ -3139,7 +3119,7 @@
3139 3119 /* Large intro paragraph under a page h1 (creators, policy, changelog, fan_plus). */
3140 3120 .page-intro {
3141 3121 font-size: 1.1rem;
3142 - margin-bottom: var(--space-6);
3122 + margin-bottom: var(--gap-page);
3143 3123 line-height: 1.6;
3144 3124 }
3145 3125
@@ -3149,18 +3129,18 @@
3149 3129 }
3150 3130
3151 3131 .features-grid-status {
3152 - margin-top: var(--space-2);
3132 + margin-top: var(--gap-peer);
3153 3133 display: inline-block;
3154 3134 }
3155 3135
3156 3136 /* "Pages" disclosure (project-level markdown sections). */
3157 - .pages-toggle { margin-top: var(--space-6); }
3137 + .pages-toggle { margin-top: var(--gap-page); }
3158 3138 .pages-toggle > summary { cursor: pointer; }
3159 3139 .pages-toggle > summary h2 { display: inline; }
3160 3140 .pages-intro {
3161 3141 font-size: 0.85rem;
3162 3142 opacity: 0.7;
3163 - margin: 0.75rem 0 var(--space-4);
3143 + margin: var(--gap-section) 0 var(--gap-section);
3164 3144 }
3165 3145
3166 3146 /* psection-* mirrors section-mgmt-* (used by item_details) but psection
@@ -3171,25 +3151,25 @@
3171 3151
3172 3152 .psection-edit-btn,
3173 3153 .psection-del-btn {
3174 - padding: 0.25rem 0.6rem;
3154 + padding: var(--gap-bound) var(--gap-group);
3175 3155 font-size: 0.8rem;
3176 3156 }
3177 3157
3178 - .psection-add-details { margin-top: var(--space-4); }
3158 + .psection-add-details { margin-top: var(--gap-section); }
3179 3159 .psection-add-details > summary {
3180 3160 cursor: pointer;
3181 3161 font-size: 0.95rem;
3182 3162 }
3183 - .psection-add-fields { margin-top: var(--space-3); }
3163 + .psection-add-fields { margin-top: var(--gap-section); }
3184 3164
3185 3165 .psection-edit-modal {
3186 - margin-top: var(--space-4);
3187 - padding: var(--space-4);
3166 + margin-top: var(--gap-section);
3167 + padding: var(--gap-section);
3188 3168 background: var(--surface-sunken);
3189 3169 }
3190 3170 .psection-edit-actions {
3191 3171 display: flex;
3192 - gap: var(--space-2);
3172 + gap: var(--gap-peer);
3193 3173 }
3194 3174
3195 3175 /* ===========================================
@@ -3198,38 +3178,38 @@
3198 3178
3199 3179 .cart-empty {
3200 3180 text-align: center;
3201 - padding: 3rem 1rem;
3181 + padding: var(--gap-page) var(--gap-section);
3202 3182 }
3203 3183 .cart-empty-hint {
3204 3184 font-size: 0.9rem;
3205 3185 opacity: 0.7;
3206 - margin-bottom: var(--space-5);
3186 + margin-bottom: var(--gap-pane);
3207 3187 }
3208 3188
3209 3189 /* Multi-creator summary bar (only renders when seller_groups.len() > 1). */
3210 3190 .cart-multi-bar-note {
3211 3191 opacity: 0.7;
3212 - margin-left: var(--space-2);
3192 + margin-left: var(--gap-peer);
3213 3193 }
3214 3194 .cart-multi-bar-form {
3215 3195 display: flex;
3216 3196 align-items: center;
3217 - gap: var(--space-3);
3197 + gap: var(--gap-section);
3218 3198 }
3219 3199 .cart-share-label {
3220 3200 font-size: 0.85rem;
3221 3201 display: flex;
3222 3202 align-items: center;
3223 - gap: var(--space-2);
3203 + gap: var(--gap-peer);
3224 3204 }
3225 3205
3226 3206 /* Per-seller group card. */
3227 - .cart-group { margin-bottom: var(--space-6); }
3228 - .cart-group-title { margin-bottom: 0.25rem; }
3207 + .cart-group { margin-bottom: var(--gap-page); }
3208 + .cart-group-title { margin-bottom: var(--gap-bound); }
3229 3209 .cart-group-count {
3230 3210 font-size: 0.85rem;
3231 3211 opacity: 0.6;
3232 - margin-bottom: var(--space-4);
3212 + margin-bottom: var(--gap-section);
3233 3213 }
3234 3214
3235 3215 /* PWYW editable price cell. */
@@ -3237,7 +3217,7 @@
3237 3217 display: flex;
3238 3218 align-items: center;
3239 3219 justify-content: flex-end;
3240 - gap: 0.25rem;
3220 + gap: var(--gap-bound);
3241 3221 }
3242 3222 .cart-pwyw-symbol { font-size: 0.9rem; }
Lines truncated
@@ -278,9 +278,7 @@
278 278
279 279 .preview-checklist li {
280 280 padding: var(--gap-bound) 0;
281 - /* Reserves the label column for the Done/Todo pseudo-element. A fixed
282 - column width, not a relationship, so it stays off the gap scale. */
283 - padding-left: 3.5rem;
281 + padding-left: var(--gap-page);
284 282 position: relative;
285 283 font-size: 0.9rem;
286 284 }
@@ -101,9 +101,9 @@
101 101 <span class="dimmed text-sm">last 7 days, {{ history_total }} scan{% if history_total != 1 %}s{% endif %}</span>
102 102 </summary>
103 103 {% if recent_history.is_empty() %}
104 - <div class="dimmed text-sm py-3">No scans recorded in the last 7 days.</div>
104 + <div class="dimmed text-sm py-section">No scans recorded in the last 7 days.</div>
105 105 {% else %}
106 - <div class="scroll-x mt-3">
106 + <div class="scroll-x mt-section">
107 107 <table class="compact-table" aria-label="Recent scan results">
108 108 <thead>
109 109 <tr>
@@ -25,7 +25,7 @@
25 25 <li>Your profile and account settings</li>
26 26 <li>Your custom links</li>
27 27 </ul>
28 - <p class="mt-4 text-sm">
28 + <p class="mt-section text-sm">
29 29 <strong>Note:</strong> Purchases made by others will remain accessible to them.
30 30 </p>
31 31 </div>
@@ -71,7 +71,7 @@
71 71
72 72 <div class="import-progress hidden" id="import-progress">
73 73 <p class="import-progress-label">Importing...</p>
74 - <div class="progress-bar-container my-4">
74 + <div class="progress-bar-container my-section">
75 75 <div class="progress-bar progress-bar--highlight" id="progress-bar"></div>
76 76 </div>
77 77 <div class="progress-stats">
@@ -10,14 +10,14 @@
10 10 <h1 class="page-title">Your Cart</h1>
11 11
12 12 {% if checkout_status == "partial" %}
13 - <div class="callout callout--solid-warning mb-5">
13 + <div class="callout callout--solid-warning mb-pane">
14 14 <strong>Some purchases completed.</strong> Part of your checkout succeeded, but one or more creators could not be processed. The items below are still in your cart; you can try checking out again.
15 15 </div>
16 16 {% endif %}
17 17
18 18 {% if seller_groups.is_empty() %}
19 19 <div class="content-section cart-empty">
20 - <p class="muted mb-4">Your cart is empty.</p>
20 + <p class="muted mb-section">Your cart is empty.</p>
21 21 <p class="cart-empty-hint">Add items from the Discover page or from your wishlist.</p>
22 22 <a href="/discover" class="btn-primary">Browse Discover</a>
23 23 </div>
@@ -39,7 +39,7 @@
39 39 </section>
40 40 {% endif %}
41 41
42 - <footer class="profile-footer mt-6">
42 + <footer class="profile-footer mt-page">
43 43 <p><a href="/u/{{ owner_username }}">View profile</a> &middot; <a href="" data-copy-link>Copy link</a></p>
44 44 </footer>
45 45 </div>
@@ -28,7 +28,7 @@
28 28 </button>
29 29 </form>
30 30
31 - <div class="foot-link mt-4">
31 + <div class="foot-link mt-section">
32 32 <a href="/dashboard">Cancel and go to dashboard<span class="dot">.</span></a>
33 33 </div>
34 34 </div>
@@ -23,13 +23,13 @@
23 23 <li><strong>Get approved</strong>: we review applications individually, usually within a few days</li>
24 24 </ol>
25 25
26 - <p class="mt-4">
26 + <p class="mt-section">
27 27 We review applications to make sure applicants are here to share and sell creative work.
28 28 If you make something and want to sell it, you'll likely get in.
29 29 Link to your existing work (a portfolio, channel, or profile elsewhere) to speed things up.
30 30 </p>
31 31
32 - <p class="mt-4">
32 + <p class="mt-section">
33 33 <strong>Important:</strong> Sales are priced and charged in <strong>US dollars</strong>, and receiving
34 34 payouts requires a <a href="https://stripe.com/global">Stripe</a> account in a supported country that
35 35 can accept USD charges. Check both with Stripe before applying if you're outside the US, EU, or UK.
@@ -44,7 +44,7 @@
44 44 </div>
45 45
46 46 <h2 class="section-header">Pricing</h2>
47 - <p class="mb-4">Flat monthly fee. 0% cut of your revenue. The only deduction from fan payments is the payment processor's fee (~3%).</p>
47 + <p class="mb-section">Flat monthly fee. 0% cut of your revenue. The only deduction from fan payments is the payment processor's fee (~3%).</p>
48 48 <table class="wave-table" aria-label="Creator plan pricing">
49 49 <thead>
50 50 <tr>
@@ -205,7 +205,7 @@
205 205
206 206 <div class="tier-section">
207 207 <h2 class="section-label">Stay in the loop</h2>
208 - <p class="landing-prose mb-4">Get notified when something ships.</p>
208 + <p class="landing-prose mb-section">Get notified when something ships.</p>
209 209 <form class="notify-form" id="notify-form" data-submit="onNotifySubmit">
210 210 <input type="email" name="email" placeholder="you@example.com" required class="notify-input" aria-label="Email address" autocomplete="email">
211 211 <button type="submit" class="btn-primary">Notify Me</button>