Skip to main content

max / makenotwork

ux: migrate item_version_upload.html inline styles to CSS
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-20 18:44 UTC
Commit: ea5c3a4bef8c5725bea56ea67bd61d1c5d9cd3c2
Parent: ccf2345
2 files changed, +306 insertions, -115 deletions
@@ -92,6 +92,31 @@
92 92 /* Aliases used by git browser */
93 93 --secondary-bg: #f4f0eb;
94 94 --text: #3d3530;
95 +
96 + /* Legacy aliases — defined to end silent var() fallback failures
97 + in inline-styled templates. Consolidate during phase 2.12. */
98 + --accent: var(--highlight);
99 + --accent-color: var(--highlight);
100 + --border-color: var(--border);
101 + --background-color: var(--background);
102 +
103 + /* Spacing scale (charter: docs/design-system.md) */
104 + --space-1: 0.25rem;
105 + --space-2: 0.5rem;
106 + --space-3: 0.75rem;
107 + --space-4: 1rem;
108 + --space-5: 1.5rem;
109 + --space-6: 2rem;
110 +
111 + /* Radius scale */
112 + --radius-sm: 2px;
113 + --radius-md: 4px;
114 + --radius-round: 50%;
115 +
116 + /* Shadow scale */
117 + --shadow-1: 0 1px 3px rgba(0, 0, 0, 0.06);
118 + --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.10);
119 + --shadow-3: 0 4px 12px rgba(0, 0, 0, 0.15);
95 120 }
96 121
97 122 /* Reset */
@@ -129,6 +154,11 @@ body {
129 154 font-size: 4.5rem;
130 155 }
131 156
157 + .message-container {
158 + max-width: 500px;
159 + text-align: center;
160 + }
161 +
132 162 .centered-wrapper {
133 163 justify-content: center;
134 164 text-align: center;
@@ -244,6 +274,65 @@ button.danger:hover,
244 274 opacity: 0.8;
245 275 }
246 276
277 + /* Disabled state for all button variants (charter rule).
278 + Applies whether disabled via attribute or aria-disabled. */
279 + button:disabled,
280 + button[aria-disabled="true"],
281 + .btn-primary:disabled,
282 + .btn-secondary:disabled,
283 + .btn-danger:disabled {
284 + opacity: 0.5;
285 + cursor: not-allowed;
286 + }
287 +
288 + /* Button size + style modifiers (charter: docs/design-system.md).
289 + Use these on top of .btn-primary / -secondary / -danger.
290 +
291 + .btn--large — large CTA padding bump.
292 + .btn--icon — square icon-only / micro button (small padding,
293 + tight line-height).
294 + .btn--link — visually a link, semantically a button (no bg/
295 + border, opacity-fade hover, mono).
296 +
297 + Some surfaces have tuned button variants that are NOT compositions
298 + of these modifiers and keep their own classes:
299 + .big-button — Young Serif 200×60 anchor on splash pages.
300 + .paywall-btn — wider primary CTA (anchor) on paywalls.
301 + .notify-btn — small inverted notify pill in dashboards.
302 + .order-btn — list reorder up/down (with active flash).
303 + .play-button — circular media-player play control.
304 + .speed-button — segment in media-player speed group.
305 + .shortcuts-help-btn — 1.5rem square help glyph in toolbars.
306 + .toast-retry-btn — inline bordered button inside a toast,
307 + uses currentColor to inherit toast tone.
308 + These are documented variants, not deprecation targets. */
309 + .btn--large {
310 + padding: 1rem 2.25rem;
311 + font-size: 1.125rem;
312 + }
313 +
314 + .btn--icon {
315 + padding: var(--space-1) var(--space-2);
316 + font-size: 0.85rem;
317 + line-height: 1;
318 + min-width: 1.75rem;
319 + }
320 +
321 + .btn--link {
322 + background: none;
323 + color: var(--detail);
324 + border: none;
325 + padding: 0;
326 + font-family: var(--font-mono);
327 + font-size: 1rem;
328 + cursor: pointer;
329 + transition: opacity 0.2s ease;
330 + }
331 +
332 + .btn--link:hover {
333 + opacity: 0.6;
334 + }
335 +
247 336 /* ===========================================
248 337 FORMS
249 338 =========================================== */
@@ -351,6 +440,51 @@ form button:hover {
351 440 margin-top: 0.5rem;
352 441 }
353 442
443 + /* Field-level validation error, paired with .form-group--error.
444 + Canonical: `partials/_ui.html` ui::form_field macro. */
445 + .form-group--error label {
446 + color: var(--error);
447 + }
448 +
449 + .form-group--error input,
450 + .form-group--error textarea,
451 + .form-group--error select {
452 + border-color: var(--error);
453 + }
454 +
455 + .field-error {
456 + font-size: 0.85rem;
457 + color: var(--error);
458 + margin-top: var(--space-2);
459 + }
460 +
461 + /* Loading skeleton placeholder.
462 + Canonical: `partials/_ui.html` ui::loading_skeleton macro. */
463 + .loading-skeleton {
464 + background: var(--surface-muted);
465 + border-radius: var(--radius-md);
466 + animation: skeleton-pulse 1.2s ease-in-out infinite;
467 + }
468 +
469 + .loading-skeleton--row {
470 + height: 1.25rem;
471 + margin: var(--space-2) 0;
472 + }
473 +
474 + .loading-skeleton--card {
475 + height: 8rem;
476 + margin: var(--space-3) 0;
477 + }
478 +
479 + .loading-skeleton--list .loading-skeleton--row + .loading-skeleton--row {
480 + margin-top: var(--space-2);
481 + }
482 +
483 + @keyframes skeleton-pulse {
484 + 0%, 100% { opacity: 0.5; }
485 + 50% { opacity: 0.9; }
486 + }
487 +
354 488 /* Checkbox group */
355 489 .checkbox-group {
356 490 display: flex;
@@ -450,7 +584,7 @@ form button:hover {
450 584 flex-shrink: 0;
451 585 }
452 586
453 - .tab.active {
587 + .tab.is-selected {
454 588 background: var(--light-background);
455 589 opacity: 1;
456 590 }
@@ -487,7 +621,7 @@ form button:hover {
487 621 opacity: 1;
488 622 }
489 623
490 - .tab-overflow-menu .tab.active {
624 + .tab-overflow-menu .tab.is-selected {
491 625 opacity: 1;
492 626 }
493 627
@@ -595,14 +729,42 @@ form button:hover {
595 729 .text-xs { font-size: 0.8rem; }
596 730 .muted { opacity: 0.7; }
597 731 .dimmed { opacity: 0.6; }
732 + .dimmer { opacity: 0.5; }
598 733 .meta { font-size: 0.85rem; opacity: 0.7; }
599 734 .nowrap { white-space: nowrap; }
600 735 .scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
601 736 .text-center { text-align: center; }
737 + .text-right { text-align: right; }
738 + .fw-bold { font-weight: bold; }
739 + .unstyled-link { text-decoration: none; color: inherit; }
740 + .square-cover { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }
741 +
742 + /* Spacing utilities (mapped to --space-* scale).
743 + mt = margin-top, mb = margin-bottom, my = vertical, mx = horizontal,
744 + m0 = margin reset. Use sparingly; prefer parent layout primitives. */
745 + .m-0 { margin: 0; }
746 + .mt-0 { margin-top: 0; }
747 + .mt-2 { margin-top: var(--space-2); }
748 + .mt-3 { margin-top: var(--space-3); }
749 + .mt-4 { margin-top: var(--space-4); }
750 + .mt-5 { margin-top: var(--space-5); }
751 + .mt-6 { margin-top: var(--space-6); }
752 + .mb-0 { margin-bottom: 0; }
753 + .mb-2 { margin-bottom: var(--space-2); }
754 + .mb-3 { margin-bottom: var(--space-3); }
755 + .mb-4 { margin-bottom: var(--space-4); }
756 + .mb-5 { margin-bottom: var(--space-5); }
757 + .mb-6 { margin-bottom: var(--space-6); }
602 758
603 759 .small { padding: 0.25rem 0.6rem; font-size: 0.8rem; }
604 760
761 + /* Canonical empty-state primitive. Use modifiers for tight/sized contexts.
762 + Markup: <div class="empty-state">…</div> (optionally with .empty-state--compact
763 + inside dropdowns or .empty-state--chart inside a fixed-height chart slot). */
605 764 .empty-state { text-align: center; padding: 2rem; opacity: 0.6; }
765 + .empty-state--compact { padding: 0.75rem; font-size: 0.9rem; }
766 + .empty-state--chart { height: 200px; padding: 0; display: flex; align-items: center; justify-content: center; opacity: 0.5; }
767 + .empty-state-hint { font-family: var(--font-mono); font-size: 0.8rem; margin-top: 0.5rem; opacity: 0.6; }
606 768
607 769 .tab-docs {
608 770 display: flex;
@@ -713,6 +875,16 @@ form button:hover {
713 875 CARDS
714 876 =========================================== */
715 877
878 + /* Canonical card primitive (charter: docs/design-system.md).
879 + Variants:
880 + .card — filled, hover-step (default content card).
881 + .card.card-muted — surface-muted fill, no hover (dashboard stat / analytics blocks).
882 + .card.card--bordered — bordered, padded, no fill (marketing cards: feature/tier/use-case).
883 + .card.card--selectable — radio-card pattern; pair with .is-selected.
884 + .card.card--grid — grid-cell card (discover grid).
885 + Legacy class names (.stat-card, .analytics-card, .feature-card, .tier-card,
886 + .use-case-card) are aliased to these recipes below — templates migrate at
887 + their own pace. */
716 888 .card {
717 889 background: var(--light-background);
718 890 padding: 1.5rem;
@@ -727,11 +899,20 @@ form button:hover {
727 899 background: var(--surface-muted);
728 900 }
729 901
730 - .card-muted {
902 + .card-muted,
903 + .stat-card,
904 + .analytics-card {
731 905 background: var(--surface-muted);
732 906 padding: 1.5rem;
733 907 }
734 908
909 + .card--bordered {
910 + background: transparent;
911 + padding: 1rem;
912 + border: 1px solid var(--border);
913 + margin-bottom: 0;
914 + }
915 +
735 916 .card-title {
736 917 font-size: 1.2rem;
737 918 margin-bottom: 0.5rem;
@@ -786,10 +967,7 @@ form button:hover {
786 967 opacity: 0.7;
787 968 }
788 969
789 - .stat-card {
790 - background: var(--surface-muted);
791 - padding: 1.5rem;
792 - }
970 + /* .stat-card base recipe defined above with .card-muted */
793 971
794 972 .stat-label {
795 973 font-size: 0.85rem;
@@ -920,3478 +1098,8162 @@ form button:hover {
920 1098 display: none;
921 1099 }
922 1100
923 - /* ===========================================
924 - SECTIONS
925 - =========================================== */
926 -
927 - .section-header {
928 - font-size: 1.5rem;
929 - font-weight: normal;
930 - margin-bottom: 1rem;
931 - padding-bottom: 0.5rem;
932 - border-bottom: 1px solid var(--border);
933 - font-family: var(--font-mono);
934 - }
935 -
936 - .content-section {
937 - background: var(--light-background);
938 - padding: 2rem;
939 - margin-bottom: 2rem;
1101 + /* Full-page error layout (templates/pages/error.html). Distinct from
1102 + the inline `.error-message` form-error class above; named to avoid
1103 + collision. */
1104 + .error-page {
1105 + min-height: 100vh;
1106 + display: flex;
1107 + align-items: center;
1108 + justify-content: center;
1109 + padding: var(--space-6);
1110 + background: var(--background);
940 1111 }
941 1112
942 - .form-section {
943 - margin-bottom: 2rem;
1113 + .error-container {
1114 + text-align: center;
1115 + max-width: 560px;
944 1116 }
945 1117
946 - .section-group-label {
947 - font-family: var(--font-mono);
948 - font-size: 0.8rem;
949 - font-weight: bold;
1118 + .error-status {
1119 + font-size: 0.85rem;
1120 + font-weight: 600;
1121 + letter-spacing: 0.08em;
950 1122 text-transform: uppercase;
951 - letter-spacing: 0.05em;
952 1123 color: var(--text-muted);
953 - margin: 2.5rem 0 1rem 0;
954 - padding-top: 1.5rem;
955 - border-top: 2px solid var(--border);
1124 + margin-bottom: var(--space-4);
956 1125 }
957 1126
958 - .form-section h2 {
959 - font-size: 1.3rem;
960 - font-weight: normal;
961 - margin-bottom: 1rem;
962 - padding-bottom: 0.5rem;
963 - border-bottom: 1px solid var(--border);
1127 + .error-page-message {
1128 + font-size: 1.5rem;
1129 + line-height: 1.4;
1130 + color: var(--detail);
1131 + margin-bottom: var(--space-6);
964 1132 }
965 1133
966 - details.form-section > summary {
967 - cursor: pointer;
968 - list-style: disclosure-closed;
1134 + .error-actions {
1135 + display: flex;
1136 + gap: var(--space-4);
1137 + justify-content: center;
969 1138 }
970 1139
971 - details.form-section[open] > summary {
972 - list-style: disclosure-open;
973 - margin-bottom: 1rem;
1140 + .error-actions a.primary,
1141 + .error-actions a.secondary {
1142 + text-decoration: none;
974 1143 }
975 1144
976 - details.form-section > summary > h2 {
977 - display: inline;
1145 + /* Minimal direct-purchase page (templates/pages/buy.html). Standalone
1146 + page used for link-in-bio sharing — no site chrome, just a card.
1147 + Scoped under .buy-page so the rules don't bleed into the rest of
1148 + the app. Tokenized; previously lived as a page-isolated <style>. */
1149 + .buy-page {
1150 + min-height: 100vh;
1151 + display: flex;
1152 + align-items: center;
1153 + justify-content: center;
1154 + padding: var(--space-6);
978 1155 }
979 1156
980 - details.form-section > summary:hover > h2 {
981 - color: var(--detail);
1157 + .buy-page .buy-card {
1158 + background: var(--light-background);
1159 + border-radius: 12px;
1160 + padding: var(--space-6);
1161 + max-width: 420px;
1162 + width: 100%;
1163 + box-shadow: var(--shadow-3);
982 1164 }
983 1165
984 - /* ===========================================
985 - NAVIGATION
986 - =========================================== */
1166 + .buy-page .cover,
1167 + .buy-page .no-cover {
1168 + width: 100%;
1169 + aspect-ratio: 1;
1170 + max-height: 280px;
1171 + border-radius: 8px;
1172 + margin-bottom: var(--space-5);
1173 + background: var(--surface-raised);
1174 + }
987 1175
988 - nav {
989 - margin-bottom: 2rem;
1176 + .buy-page .cover {
1177 + object-fit: cover;
990 1178 }
991 1179
992 - .nav-links {
1180 + .buy-page .no-cover {
993 1181 display: flex;
994 - gap: 2rem;
995 - flex-wrap: wrap;
1182 + align-items: center;
1183 + justify-content: center;
1184 + font-size: 3rem;
1185 + opacity: 0.2;
996 1186 }
997 1187
998 - .nav-links a {
999 - color: var(--detail);
1000 - text-decoration: none;
1001 - font-family: var(--font-mono);
1002 - transition: opacity 0.2s ease;
1188 + .buy-page h1 {
1189 + text-align: left;
1190 + font-size: 1.4rem;
1191 + margin-bottom: var(--space-1);
1003 1192 }
1004 1193
1005 - .nav-links a:hover {
1194 + .buy-page .creator {
1195 + font-family: var(--font-mono);
1196 + font-size: 0.85rem;
1006 1197 opacity: 0.6;
1198 + margin-bottom: var(--space-5);
1007 1199 }
1008 1200
1009 - .shortcuts-help-btn {
1010 - font-family: var(--font-mono);
1011 - color: var(--text-muted);
1012 - font-size: 0.85rem;
1013 - width: 1.5rem;
1014 - height: 1.5rem;
1015 - border: 1px solid var(--border);
1016 - border-radius: 3px;
1017 - display: inline-flex;
1018 - align-items: center;
1019 - justify-content: center;
1020 - cursor: pointer;
1021 - transition: opacity 0.2s ease;
1022 - padding: 0;
1023 - background: none;
1201 + .buy-page .creator a {
1202 + color: inherit;
1024 1203 }
1025 1204
1026 - .shortcuts-help-btn:hover {
1027 - opacity: 0.6;
1205 + .buy-page .price {
1206 + font-family: var(--font-mono);
1207 + font-size: 1.3rem;
1208 + margin-bottom: var(--space-5);
1028 1209 }
1029 1210
1030 - .breadcrumb {
1211 + .buy-page .description {
1031 1212 font-size: 0.9rem;
1032 - color: var(--text-muted);
1033 - margin-bottom: 0.5rem;
1034 - font-family: var(--font-mono);
1213 + line-height: 1.5;
1214 + opacity: 0.8;
1215 + margin-bottom: var(--space-5);
1216 + max-height: 4.5em;
1217 + overflow: hidden;
1035 1218 }
1036 1219
1037 - .breadcrumb a {
1038 - color: var(--detail);
1220 + .buy-page .buy-btn {
1221 + display: block;
1222 + width: 100%;
1223 + padding: 14px;
1224 + background: var(--highlight);
1225 + color: var(--primary-light);
1226 + border: none;
1227 + border-radius: 6px;
1228 + font-size: 1rem;
1229 + font-weight: 600;
1230 + cursor: pointer;
1231 + text-align: center;
1039 1232 text-decoration: none;
1233 + transition: opacity 0.2s ease;
1040 1234 }
1041 1235
1042 - .breadcrumb a:hover {
1043 - text-decoration: underline;
1236 + .buy-page .buy-btn:hover {
1237 + opacity: 0.85;
1044 1238 }
1045 1239
1046 - /* ===========================================
1047 - HEADER & FOOTER
1048 - =========================================== */
1240 + .buy-page .buy-btn:disabled {
1241 + opacity: 0.6;
1242 + cursor: wait;
1243 + }
1049 1244
1050 - header {
1051 - background: transparent;
1052 - border-bottom: none;
1053 - padding: 0;
Lines truncated
@@ -1,7 +1,7 @@
1 1 <div class="version-upload" id="version-upload" data-item-id="{{ item.id }}">
2 2
3 3 {% if versions.is_empty() %}
4 - <div style="text-align: center; padding: 2rem 1rem; opacity: 0.6;">
4 + <div class="item-version-upload-empty">
5 5 <p>No versions uploaded yet. Create your first version below.</p>
6 6 </div>
7 7 {% else %}
@@ -20,19 +20,19 @@
20 20 {% for version in versions %}
21 21 <tr>
22 22 <td><span class="badge{% if version.is_current %} current{% endif %}">v{{ version.number }}</span></td>
23 - <td style="font-size: 0.85rem;">{% if let Some(label) = version.label %}{{ label }}{% endif %}</td>
24 - <td style="font-size: 0.85rem;">{% if version.has_file %}{% match version.file_name %}{% when Some with (name) %}{{ name }}{% when None %}1 file{% endmatch %}{% else %}<span style="opacity: 0.5;">No file</span>{% endif %}</td>
23 + <td class="item-version-upload-cell-sm">{% if let Some(label) = version.label %}{{ label }}{% endif %}</td>
24 + <td class="item-version-upload-cell-sm">{% if version.has_file %}{% match version.file_name %}{% when Some with (name) %}{{ name }}{% when None %}1 file{% endmatch %}{% else %}<span class="item-version-upload-no-file">No file</span>{% endif %}</td>
25 25 <td>{{ version.size }}</td>
26 26 <td>{{ version.downloads }}</td>
27 27 <td>
28 28 {% if version.has_file %}
29 - <button class="secondary download-version-btn" style="padding: 0.4rem 0.8rem; font-size: 0.85rem;"
29 + <button class="secondary download-version-btn item-version-upload-btn"
30 30 data-version-id="{{ version.id }}">Download</button>
31 31 {% else %}
32 - <button class="secondary upload-to-version-btn" style="padding: 0.4rem 0.8rem; font-size: 0.85rem;"
32 + <button class="secondary upload-to-version-btn item-version-upload-btn"
33 33 data-version-id="{{ version.id }}">Upload File</button>
34 34 {% endif %}
35 - <button class="secondary delete-version-btn" style="padding: 0.4rem 0.8rem; font-size: 0.85rem;"
35 + <button class="secondary delete-version-btn item-version-upload-btn"
36 36 data-version-id="{{ version.id }}">Delete</button>
37 37 </td>
38 38 </tr>
@@ -43,7 +43,7 @@
43 43
44 44 <!-- New Version Form -->
45 45 <div id="new-version-form">
46 - <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
46 + <div class="item-version-upload-grid">
47 47 <div class="form-group">
48 48 <label for="new-version-number">Version Number</label>
49 49 <input type="text" id="new-version-number" placeholder="e.g., 1.0">
@@ -56,23 +56,23 @@
56 56
57 57 <div class="form-group">
58 58 <label>Files</label>
59 - <div class="upload-hint" style="margin-bottom: 0.5rem;">Add one file per platform. Each gets its own label (e.g. "macOS (arm)", "Linux (x86_64)").</div>
59 + <div class="upload-hint item-version-upload-hint">Add one file per platform. Each gets its own label (e.g. "macOS (arm)", "Linux (x86_64)").</div>
60 60
61 - <table style="width: 100%; border-collapse: collapse;" id="version-file-table">
61 + <table class="item-version-upload-file-table" id="version-file-table">
62 62 <thead>
63 - <tr style="text-align: left; font-size: 0.8rem; opacity: 0.7; text-transform: uppercase; letter-spacing: 0.03em;">
64 - <th style="padding: 0.4rem 0.5rem 0.4rem 0;">File</th>
65 - <th style="padding: 0.4rem 0.5rem;">Label</th>
66 - <th style="padding: 0.4rem 0.5rem; width: 40px;"></th>
63 + <tr>
64 + <th>File</th>
65 + <th>Label</th>
66 + <th></th>
67 67 </tr>
68 68 </thead>
69 69 <tbody id="version-file-rows"></tbody>
70 70 </table>
71 71
72 - <div style="margin-top: 0.5rem;">
73 - <input type="file" id="version-file-input" style="display: none;"
72 + <div class="item-version-upload-add-row">
73 + <input type="file" id="version-file-input" class="hidden"
74 74 accept=".zip,.dmg,.exe,.appimage,.deb,.tar.gz,.clap,.vst3" multiple>
75 - <button type="button" class="secondary" id="add-version-file-btn" style="padding: 0.4rem 0.8rem;">Add Files</button>
75 + <button type="button" class="secondary item-version-upload-add-btn" id="add-version-file-btn">Add Files</button>
76 76 </div>
77 77 </div>
78 78
@@ -84,7 +84,7 @@
84 84 <div class="file-upload-area" id="existing-version-dropzone">
85 85 <div class="upload-text">Drop file to upload for this version</div>
86 86 <div class="upload-hint">ZIP, DMG, EXE, AppImage, DEB, tar.gz, CLAP, VST3</div>
87 - <input type="file" id="existing-version-file-input" style="display: none;"
87 + <input type="file" id="existing-version-file-input" class="hidden"
88 88 accept=".zip,.dmg,.exe,.appimage,.deb,.tar.gz,.clap,.vst3">
89 89 </div>
90 90 <button class="secondary" id="cancel-existing-upload-btn">Cancel</button>
@@ -92,7 +92,7 @@
92 92
93 93 <!-- Shared upload progress/status (outside both forms so always visible) -->
94 94 <div class="upload-progress hidden" id="version-upload-progress">
95 - <div id="version-upload-queue" style="margin-bottom: 0.75rem;"></div>
95 + <div class="item-version-upload-queue" id="version-upload-queue"></div>
96 96 <div class="progress-info">
97 97 <span id="version-upload-filename">filename.zip</span>
98 98 <span id="version-upload-percent">0%</span>
@@ -100,7 +100,7 @@
100 100 <div class="progress-bar-container">
101 101 <div class="progress-bar" id="version-progress-bar" style="width: 0%;"></div>
102 102 </div>
103 - <div id="version-upload-speed" style="font-size: 0.8rem; opacity: 0.6; margin-top: 0.25rem;"></div>
103 + <div class="item-version-upload-speed" id="version-upload-speed"></div>
104 104 <button type="button" class="secondary" id="cancel-version-upload-btn">Cancel</button>
105 105 </div>
106 106