Skip to main content

max / makenotwork

ux: phase-3 CSS dedup — collapse page-scoped duplicates into shared primitives Pass per the brand goal "MNW should render like an OS": single source of truth per primitive, page-scoped rules only where genuinely page-specific. Page-scoped duplicates eliminated by adding canonical co-classes to templates and dropping the .foo-page .bar { ... } rules whose bodies were 1:1 with a global primitive. item.html (item-page scope: 84 -> 63 rules) - .item-media + .item-details now `class="... content-section"` instead of being styled via .item-page .item-media. Same for .item-sections, .item-description, .item-features, .item-specs, .item-versions, .bundle-contents. - .purchase-box uses .card-muted; only the h3/ul/li bullet styling stays. - .feature-item uses .card-muted. - .bundle-child uses .list-row; only the gap-4 override stays. - The 5-way grouped selector for section h2s replaced by the global .section-header class on each <h2>. purchase.html - .checkout-box adds .content-section; only its margin-bottom override stays. The nested .item-summary / .price-breakdown remain page-scoped because they use --background (one shade darker than the outer card) as an intentional layering. Cross-page consolidations - Page-level h1 (left-aligned, 2rem, mb-2) — 7 standalone rules (.admin-page h1, .health-page h1, .import-page h1, .delete-account- page h1, .export-page h1, .receipt-page h1, .dashboard-page h1) collapsed into one grouped selector. Side effect: dashboard h1 drops from 2.5rem to 2rem, matching the rest. .delete-account-page h1 keeps its danger color via a single-line override. - Page subtitle (muted, mb-6, left-aligned) — 4 standalone rules collapsed into one grouped selector. .health-page subtitle keeps its text-align: center override. - Page container max-width — 9 standalone rules grouped by width tier: narrow (600px: delete-account, receipt, user), medium (800px: import, export), wide (900px: fan-plus, feed, creators). New .container--narrow / --medium / --wide modifier classes available for future use. style.css 10,885 -> 10,835 (-50 lines, ~30 rules eliminated). More importantly: 4 patterns now have one canonical definition instead of 7-9 page-scoped repeats. Adding a new page no longer requires redefining h1/subtitle/container — pick a body class and the rules apply. 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:15 UTC
Commit: 24b15525e4a51d31ee5b9a30ae26a4d00ebcb95b
Parent: e139d91
3 files changed, +80 insertions, -130 deletions
@@ -198,12 +198,30 @@ a:hover {
198 198 color: var(--highlight);
199 199 }
200 200
201 - /* Container */
201 + /* Container — default 1200px max-width. Modifier classes for narrower pages,
202 + or comma-grouped page-scoped overrides below for body-class-based control. */
202 203 .container {
203 204 max-width: 1200px;
204 205 margin: 0 auto;
205 206 padding: 2rem;
206 207 }
208 + .container--narrow { max-width: 600px; }
209 + .container--medium { max-width: 800px; }
210 + .container--wide { max-width: 900px; }
211 +
212 + /* Per-page container width overrides (selected via body class). One rule
213 + per width tier; new pages should add their body class here rather than
214 + defining a new .foo-page .container rule. */
215 + .delete-account-page .container,
216 + .receipt-page .container,
217 + .user-page .container { max-width: 600px; margin: 0 auto; }
218 +
219 + .import-page .container,
220 + .export-page .container { max-width: 800px; margin: 0 auto; }
221 +
222 + .fan-plus-page .container,
223 + .feed-page .container,
224 + .creators-page .container { max-width: 900px; margin: 0 auto; }
207 225
208 226 /* ===========================================
209 227 BUTTONS
@@ -1379,11 +1397,7 @@ form button:hover {
1379 1397
1380 1398 .item-page .item-layout.no-media { grid-template-columns: 1fr; }
1381 1399
1382 - .item-page .item-media,
1383 - .item-page .item-details {
1384 - background: var(--light-background);
1385 - padding: var(--space-6);
1386 - }
1400 + /* (.item-media + .item-details handled by .content-section co-class) */
1387 1401
1388 1402 .item-page .item-title {
1389 1403 font-size: 2rem;
@@ -1418,21 +1432,15 @@ form button:hover {
1418 1432
1419 1433 .item-page .item-tags { margin-bottom: var(--space-5); }
1420 1434
1421 - .item-page .purchase-box {
1422 - background: var(--surface-muted);
1423 - padding: var(--space-5);
1424 - margin-bottom: var(--space-5);
1425 - }
1426 -
1427 - .item-page .purchase-box h3 {
1435 + /* purchase-box uses .card-muted for the box; only its h3/ul/li specifics remain. */
1436 + .purchase-box h3 {
1428 1437 font-size: 1rem;
1429 1438 font-weight: normal;
1430 1439 margin-bottom: var(--space-4);
1431 1440 }
1432 -
1433 - .item-page .purchase-box ul { list-style: none; margin-bottom: var(--space-4); }
1434 - .item-page .purchase-box li { padding: var(--space-1) 0; }
1435 - .item-page .purchase-box li::before {
1441 + .purchase-box ul { list-style: none; margin-bottom: var(--space-4); }
1442 + .purchase-box li { padding: var(--space-1) 0; }
1443 + .purchase-box li::before {
1436 1444 content: "→ ";
1437 1445 opacity: 0.5;
1438 1446 }
@@ -1455,42 +1463,12 @@ form button:hover {
1455 1463 margin-top: var(--space-4);
1456 1464 }
1457 1465
1458 - .item-page .item-sections {
1459 - background: var(--light-background);
1460 - padding: var(--space-6);
1461 - margin-bottom: var(--space-6);
1462 - }
1463 -
1464 - .item-page .item-description,
1465 - .item-page .item-features,
1466 - .item-page .item-specs,
1467 - .item-page .item-versions,
1468 - .item-page .bundle-contents {
1469 - background: var(--light-background);
1470 - padding: var(--space-6);
1471 - margin-bottom: var(--space-6);
1472 - }
1473 -
1474 - .item-page .item-description h2,
1475 - .item-page .item-features h2,
1476 - .item-page .item-specs h2,
1477 - .item-page .item-versions h2,
1478 - .item-page .bundle-contents h2 {
1479 - font-size: 1.5rem;
1480 - margin-bottom: var(--space-4);
1481 - padding-bottom: var(--space-2);
1482 - border-bottom: 1px solid var(--border);
1483 - }
1466 + /* (.item-sections / .item-description / .item-features / .item-specs / .item-versions / .bundle-contents handled by .content-section co-class) */
1484 1467
1485 - .item-page .bundle-child {
1486 - display: flex;
1487 - align-items: center;
1488 - gap: var(--space-4);
1489 - padding: var(--space-3) 0;
1490 - border-bottom: 1px solid var(--border);
1491 - }
1468 + /* (.item-page section h2 uses the global .section-header class — add it to the h2 in the template) */
1492 1469
1493 - .item-page .bundle-child:last-child { border-bottom: none; }
1470 + /* bundle-child uses .list-row co-class; only the gap-4 override remains. */
1471 + .bundle-child { gap: var(--space-4); padding: var(--space-3) 0; }
1494 1472
1495 1473 .item-page .bundle-child-type {
1496 1474 font-size: 0.8rem;
@@ -1522,10 +1500,7 @@ form button:hover {
1522 1500 gap: var(--space-5);
1523 1501 }
1524 1502
1525 - .item-page .feature-item {
1526 - background: var(--surface-muted);
1527 - padding: var(--space-5);
1528 - }
1503 + /* (.feature-item handled by .card-muted co-class) */
1529 1504
1530 1505 .item-page .feature-title {
1531 1506 font-size: 1.1rem;
@@ -2169,10 +2144,7 @@ form button:hover {
2169 2144 /* Admin dashboards (templates/dashboards/admin-*.html). Every admin
2170 2145 page had a duplicate `h1 { font-size: 2rem; margin-bottom: 0.5rem; }`
2171 2146 inline — consolidated here. */
2172 - .admin-page h1 {
2173 - font-size: 2rem;
2174 - margin-bottom: var(--space-2);
2175 - }
2147 + /* (.admin-page h1 in canonical grouped rule) */
2176 2148
2177 2149 /* System health dashboard (templates/pages/health.html). */
2178 2150 .health-page {
@@ -2184,15 +2156,9 @@ form button:hover {
2184 2156 margin: 0 auto;
2185 2157 }
2186 2158
2187 - .health-page h1 {
2188 - font-size: 2rem;
2189 - margin-bottom: var(--space-2);
2190 - }
2159 + /* (.health-page h1 in canonical grouped rule) */
2191 2160
2192 - .health-page .subtitle {
2193 - opacity: 0.7;
2194 - margin-bottom: var(--space-6);
2195 - }
2161 + /* (.health-page .subtitle in canonical grouped rule) */
2196 2162
2197 2163 .health-page .health-grid {
2198 2164 display: grid;
@@ -2351,19 +2317,11 @@ form button:hover {
2351 2317 Page-specific upload + column-mapping + progress flow. Migration also
2352 2318 fixed token bypasses: --border-color → --border, --accent → --highlight,
2353 2319 --success-background → --success-bg, --error-background → --error-bg. */
2354 - .import-page .container { max-width: 800px; margin: 0 auto; }
2320 + /* (.import-page .container in canonical grouped rule near .container) */
2355 2321
2356 - .import-page h1 {
2357 - font-size: 2rem;
2358 - margin-bottom: var(--space-2);
2359 - text-align: left;
2360 - }
2322 + /* (.import-page h1 in canonical grouped rule) */
2361 2323
2362 - .import-page .subtitle {
2363 - opacity: 0.7;
2364 - margin-bottom: var(--space-6);
2365 - text-align: left;
2366 - }
2324 + /* (.import-page .subtitle in canonical grouped rule) */
2367 2325
2368 2326 .import-page .back-link {
2369 2327 display: inline-block;
@@ -2536,20 +2494,11 @@ form button:hover {
2536 2494 Overrides .warning-box to use --danger (delete is danger, not warning)
2537 2495 and the .form-status partial styling. Scoped to keep the global versions
2538 2496 intact for other pages. */
2539 - .delete-account-page .container { max-width: 600px; margin: 0 auto; }
2497 + /* (.delete-account-page .container in canonical grouped rule) */
2540 2498
2541 - .delete-account-page h1 {
2542 - font-size: 2rem;
2543 - margin-bottom: var(--space-2);
2544 - text-align: left;
2545 - color: var(--danger);
2546 - }
2499 + /* (.delete-account-page h1 in canonical grouped rule; danger color applied separately) */
2547 2500
2548 - .delete-account-page .subtitle {
2549 - opacity: 0.7;
2550 - margin-bottom: var(--space-6);
2551 - text-align: left;
2552 - }
2501 + /* (.delete-account-page .subtitle in canonical grouped rule) */
2553 2502
2554 2503 .delete-account-page .warning-box {
2555 2504 background: var(--danger);
@@ -2641,19 +2590,8 @@ form button:hover {
2641 2590 .delete-account-page .form-status.success { color: var(--success); }
2642 2591
2643 2592 /* Export data page (templates/dashboards/dashboard-export.html). */
2644 - .export-page .container { max-width: 800px; margin: 0 auto; }
2645 -
2646 - .export-page h1 {
2647 - font-size: 2rem;
2648 - margin-bottom: var(--space-2);
2649 - text-align: left;
2650 - }
2651 2593
2652 - .export-page .subtitle {
2653 - opacity: 0.7;
2654 - margin-bottom: var(--space-6);
2655 - text-align: left;
2656 - }
2594 + /* (.export-page h1 + .subtitle in canonical grouped rules) */
2657 2595
2658 2596 .export-page .export-cards {
2659 2597 display: grid;
@@ -2722,8 +2660,7 @@ form button:hover {
2722 2660 .export-page .export-status.error { color: var(--danger); }
2723 2661
2724 2662 /* Receipt page (templates/pages/receipt.html). */
2725 - .receipt-page .container { max-width: 600px; margin: 0 auto; }
2726 - .receipt-page h1 { font-size: 2rem; margin-bottom: var(--space-2); text-align: left; }
2663 + /* (.receipt-page h1 in canonical grouped rule) */
2727 2664
2728 2665 .receipt-page .receipt-box {
2729 2666 background: var(--light-background);
@@ -2782,7 +2719,6 @@ form button:hover {
2782 2719 }
2783 2720
2784 2721 /* Fan+ subscription page (templates/pages/fan_plus.html). */
2785 - .fan-plus-page .container { max-width: 900px; margin: 0 auto; }
2786 2722 .fan-plus-page h1 { font-size: 2.5rem; margin-bottom: var(--space-2); }
2787 2723 .fan-plus-page h2 { font-size: 1.5rem; margin-top: var(--space-6); margin-bottom: var(--space-4); }
2788 2724
@@ -2848,11 +2784,8 @@ form button:hover {
2848 2784 text-align: center;
2849 2785 }
2850 2786
2851 - .purchase-page .checkout-box {
2852 - background: var(--light-background);
2853 - padding: var(--space-6);
2854 - margin-bottom: var(--space-5);
2855 - }
2787 + /* (.purchase-page .checkout-box handled by .content-section co-class; only its margin override remains) */
2788 + .purchase-page .checkout-box { margin-bottom: var(--space-5); }
2856 2789
2857 2790 .purchase-page h2 { font-size: 1.8rem; margin-bottom: var(--space-2); }
2858 2791
@@ -3349,7 +3282,6 @@ form button:hover {
3349 3282
3350 3283 /* Feed page (templates/pages/feed.html). Compact tabular layout of
3351 3284 items from followed users / projects / tags. */
3352 - .feed-page .container { max-width: 900px; margin: 0 auto; }
3353 3285 .feed-page .page-title { font-size: 1.5rem; margin-bottom: var(--space-4); }
3354 3286 .feed-page .feed-meta { font-size: 0.8rem; opacity: 0.6; margin-bottom: var(--space-3); }
3355 3287
@@ -3545,7 +3477,6 @@ form button:hover {
3545 3477 }
3546 3478
3547 3479 /* User profile page (templates/pages/user.html). */
3548 - .user-page .container { max-width: 600px; }
3549 3480 .user-page .profile-header { text-align: center; margin-bottom: 3rem; }
3550 3481
3551 3482 .user-page .profile-avatar {
@@ -3603,7 +3534,6 @@ form button:hover {
3603 3534 .user-page .project-description { font-size: 0.9rem; opacity: 0.8; }
3604 3535
3605 3536 /* Creators / become-a-creator page (templates/pages/creators.html). */
3606 - .creators-page .container { max-width: 900px; margin: 0 auto; }
3607 3537 .creators-page h1 { font-size: 2.5rem; margin-bottom: var(--space-2); }
3608 3538 .creators-page h2 { font-size: 1.5rem; margin-top: var(--space-6); margin-bottom: var(--space-4); }
3609 3539
@@ -3866,13 +3796,33 @@ form button:hover {
3866 3796
3867 3797 .project-blog-page .empty-state { padding: 3rem 0; opacity: 0.6; }
3868 3798
3869 - /* Shared dashboard h1 for dashboard-user / dashboard-project /
3870 - dashboard-item (and other dashboard surfaces). */
3871 - .dashboard-page h1 {
3872 - font-size: 2.5rem;
3799 + /* Canonical "page-level h1" — left-aligned, smaller than the global centered
3800 + default. Used by dashboards, admin, health, import, delete-account, export,
3801 + receipt. Aliased via comma-grouped selectors so each page picks it up via
3802 + its body class without redefining. */
3803 + .dashboard-page h1,
3804 + .admin-page h1,
3805 + .health-page h1,
3806 + .import-page h1,
3807 + .delete-account-page h1,
3808 + .export-page h1,
3809 + .receipt-page h1 {
3810 + font-size: 2rem;
3873 3811 margin-bottom: var(--space-2);
3874 3812 text-align: left;
3875 3813 }
3814 + .delete-account-page h1 { color: var(--danger); }
3815 +
3816 + /* Canonical subtitle under a page h1 — muted, with bottom space. */
3817 + .health-page .subtitle,
3818 + .import-page .subtitle,
3819 + .delete-account-page .subtitle,
3820 + .export-page .subtitle {
3821 + opacity: 0.7;
3822 + margin-bottom: var(--space-6);
3823 + text-align: left;
3824 + }
3825 + .health-page .subtitle { text-align: center; }
3876 3826
3877 3827 /* User-level dashboard (templates/dashboards/dashboard-user.html). */
3878 3828 .stat-value { font-size: 2rem; margin-bottom: var(--space-1); }
@@ -147,7 +147,7 @@
147 147 <div class="item-layout{% if item.cover_image_url.is_none() && item.item_type != "video" %} no-media{% endif %}">
148 148 {% match item.content %}
149 149 {% when ItemContent::Video with { video_s3_key, cover_url, .. } %}
150 - <div class="item-media">
150 + <div class="item-media content-section">
151 151 <div id="video-container">
152 152 <video id="item-player" controls preload="metadata"
153 153 {% if let Some(url) = cover_url %}poster="{{ url }}"{% endif %}
@@ -158,7 +158,7 @@
158 158 </div>
159 159 {% when _ %}
160 160 {% if item.cover_image_url.is_some() %}
161 - <div class="item-media">
161 + <div class="item-media content-section">
162 162 {% if let Some(img) = item.cover_image_url %}
163 163 <img src="{{ img }}" alt="{{ item.title }}" class="item-cover-img">
164 164 {% endif %}
@@ -166,7 +166,7 @@
166 166 {% endif %}
167 167 {% endmatch %}
168 168
169 - <div class="item-details">
169 + <div class="item-details content-section">
170 170 <h1 class="item-title">{{ item.title }}</h1>
171 171 <div class="item-creator">
172 172 by <a href="/u/{{ creator_username }}">{{ creator_username }}</a>
@@ -207,7 +207,7 @@
207 207 {% endif %}
208 208 </div>
209 209 {% else %}
210 - <div class="purchase-box">
210 + <div class="purchase-box card-muted">
211 211 <h3>What's included:</h3>
212 212 <ul>
213 213 {% if item.item_type == "bundle" %}
@@ -270,15 +270,15 @@
270 270 </div>
271 271 </div>
272 272
273 - <section class="item-description">
274 - <h2>Description</h2>
273 + <section class="item-description content-section">
274 + <h2 class="section-header">Description</h2>
275 275 <div>
276 276 <p>{{ item.description }}</p>
277 277 </div>
278 278 </section>
279 279
280 280 {% if !sections.is_empty() %}
281 - <section class="item-sections">
281 + <section class="item-sections content-section">
282 282 <div class="section-tabs">
283 283 {% for section in sections %}
284 284 <button class="section-tab{% if loop.first %} active{% endif %}"
@@ -295,8 +295,8 @@
295 295 {% endif %}
296 296
297 297 {% if item.license_preset.is_some() %}
298 - <section class="item-description">
299 - <h2>License</h2>
298 + <section class="item-description content-section">
299 + <h2 class="section-header">License</h2>
300 300 <p class="license-preset-name">
301 301 {% if item.license_preset.as_deref() == Some("personal_use") %}Personal Use Only
302 302 {% else if item.license_preset.as_deref() == Some("royalty_free") %}Royalty-Free Commercial
@@ -335,10 +335,10 @@
335 335 {% endif %}
336 336
337 337 {% if !bundle_items.is_empty() %}
338 - <section class="bundle-contents">
339 - <h2>What's Included ({{ bundle_items.len() }} items)</h2>
338 + <section class="bundle-contents content-section">
339 + <h2 class="section-header">What's Included ({{ bundle_items.len() }} items)</h2>
340 340 {% for child in bundle_items %}
341 - <div class="bundle-child">
341 + <div class="bundle-child list-row">
342 342 <span class="bundle-child-type">{{ child.item_type }}</span>
343 343 <span class="bundle-child-title">
344 344 {% if child.listed %}
@@ -8,7 +8,7 @@
8 8 <h1>Makenot<span class="dot">.</span>work</h1>
9 9 <p class="tagline">Fair distribution for creatives of all kinds<span class="dot">.</span></p>
10 10
11 - <div class="checkout-box">
11 + <div class="checkout-box content-section">
12 12 <div class="step-indicator">Step 2 of 2</div>
13 13 <h2>Confirm Purchase</h2>
14 14