Skip to main content

max / makenotwork

ux: phase-2 CSS dedup — input shapes, selectable cards, cover thumb, stack row Five more dedup passes against the consolidated stylesheet. Each replaces a per-tab class family with one canonical primitive plus modifiers, so templates compose instead of redefining. Pass L — Input shapes (~37 *-input classes) - New modifiers .input--xs / --sm / --mono / --upper / --numeric - New width utilities .w-80 .w-100 .w-120 .w-180 .w-220 .w-260 .w-full .maxw-320 (paired pixel-width alternatives to .col-* percent widths) - Per-tab classes retained as JS hooks; styling pulled into the modifier set: cart-pwyw-input, cart-promo-input, batch-queue-*, proj-synckit-*-input, user-synckit-edit-*, promo-codes-edit-*, placement-list-*, blog-editor-input, library-filter-input, bulk-form-input, proj-promo-code-input, item-edit-price-input, proj-members-input-*. Pass M — Selectable cards (4 variants → 1 recipe) - .card--selectable primitive aliased to .type-card, .pricing-card, .content-choice-card via comma-grouped selectors. Hover, :checked, .is-selected states share one rule set. - Killed verbatim-duplicate .type-card block in wizard.css (already defined in style.css). - wizard.css 791 → 685 lines (−106) by pulling the .pricing-card and .content-choice-card recipes back into style.css's canonical group. Pass N — Cover thumbnail (3 variants → 1) - .cover-thumb + .cover-empty + .cover-row canonical recipe aliased to .proj-image-preview / .proj-image-empty / .proj-image-row and .image-preview-thumb / .image-preview-empty / .image-preview-row. Pass O — Header / toolbar rows - .stack-row canonical absorbs .content-header, .user-media-header, .transactions-header, .analytics-range-bar, .cart-multi-bar via comma-grouped selector. Per-context margins / borders carried by modifier classes (.stack-row--bordered, .stack-row--tight, --top). - Per-tab definitions collapsed to the alias group. style.css 10,900 → 10,885 net (smaller net because new canonical recipes are intentionally verbose with comma-grouped selectors; deletions in per-tab sections exceed additions). wizard.css 791 → 685. Combined: −121 lines. Plus a stronger primitive set: OS-like primitive table after dedup phases A-O: progress bar .progress-bar-container + .progress-bar (--slim, --rounded, --highlight) upload status .upload-status + __row + __msg.is-success/.is-error empty state .empty-state (--compact, --chart, --lg) status pill .field-status / .save-status (.success/.error/.saving) section lead .section-lead (+ .mb-3, .text-sm, .dimmed utilities) callout .callout (--danger, --warning, --solid-warning) card .card / .card--bordered / .card-muted / .card--selectable small button .small (aliased to .btn-tiny, .cart-row-btn, etc.) list row .list-row (aliased to .psection-row, .checklist-row, etc.) stack row / toolbar .stack-row (--bordered, --tight, --top) cover thumb .cover-thumb + .cover-empty + .cover-row table .data-table / .compact-table + .minw-300..800 input shape .input--xs / --sm / --mono / --upper / --numeric + .w-80..260 / .w-full / .maxw-320 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-20 21:53 UTC
Commit: e139d912ec77954eca677bcd27005781abcafdf8
Parent: 8c64564
14 files changed, +164 insertions, -285 deletions
@@ -3049,13 +3049,12 @@ form button:hover {
3049 3049 related innerHTML in static/project-sections.js)
3050 3050 =========================================== */
3051 3051
3052 - /* Image picker row: 120×120 preview + hint + button + status. */
3053 - .proj-image-row {
3054 - display: flex;
3055 - align-items: flex-start;
3056 - gap: var(--space-5);
3057 - }
3058 - .proj-image-preview {
3052 + /* Canonical 120×120 thumbnail/preview box. Used by project image picker,
3053 + item image picker, and anywhere a small square cover preview is needed.
3054 + Pair with .cover-empty for the "No image" placeholder text inside it. */
3055 + .cover-thumb,
3056 + .proj-image-preview,
3057 + .image-preview-thumb {
3059 3058 width: 120px;
3060 3059 height: 120px;
3061 3060 background: var(--border);
@@ -3064,12 +3063,25 @@ form button:hover {
3064 3063 justify-content: center;
3065 3064 flex-shrink: 0;
3066 3065 }
3067 - .proj-image-preview img {
3066 + .cover-thumb img,
3067 + .proj-image-preview img,
3068 + .image-preview-thumb img {
3068 3069 width: 100%;
3069 3070 height: 100%;
3070 3071 object-fit: cover;
3071 3072 }
3072 - .proj-image-empty { opacity: 0.4; font-size: 0.8rem; }
3073 + .cover-empty,
3074 + .proj-image-empty,
3075 + .image-preview-empty { opacity: 0.4; font-size: 0.8rem; }
3076 +
3077 + /* Companion flex row: thumbnail + form-control column. */
3078 + .cover-row,
3079 + .proj-image-row,
3080 + .image-preview-row {
3081 + display: flex;
3082 + align-items: flex-start;
3083 + gap: var(--space-5);
3084 + }
3073 3085 .proj-image-hint {
3074 3086 font-size: 0.85rem;
3075 3087 opacity: 0.7;
@@ -3160,17 +3172,7 @@ form button:hover {
3160 3172 }
3161 3173
3162 3174 /* Multi-creator summary bar (only renders when seller_groups.len() > 1). */
3163 - .cart-multi-bar {
3164 - margin-bottom: var(--space-5);
3165 - padding: 1rem 1.25rem;
3166 - background: var(--surface-muted);
3167 - border: 1px solid var(--border-color);
3168 - display: flex;
3169 - justify-content: space-between;
3170 - align-items: center;
3171 - flex-wrap: wrap;
3172 - gap: var(--space-3);
3173 - }
3175 + /* (cart-multi-bar aliased into .stack-row canonical; surface-muted card variant) */
3174 3176 .cart-multi-bar__note {
3175 3177 opacity: 0.7;
3176 3178 margin-left: var(--space-2);
@@ -3206,12 +3208,7 @@ form button:hover {
3206 3208 gap: 0.25rem;
3207 3209 }
3208 3210 .cart-pwyw-symbol { font-size: 0.9rem; }
3209 - .cart-pwyw-input {
3210 - width: 80px;
3211 - padding: 0.25rem;
3212 - font-size: 0.9rem;
3213 - text-align: right;
3214 - }
3211 + /* (cart-pwyw-input sized by alias group; width + numeric align via .w-80 .input--numeric in template) */
3215 3212
3216 3213 /* Compact action button used in cart/wishlist tables. */
3217 3214 /* (cart-row-btn aliased into .small recipe near top) */
@@ -3245,11 +3242,7 @@ form button:hover {
3245 3242
3246 3243 /* Checkout form within a seller group. */
3247 3244 .cart-promo-row { margin-bottom: var(--space-2); }
3248 - .cart-promo-input {
3249 - padding: 0.4rem 0.6rem;
3250 - font-size: 0.85rem;
3251 - width: 180px;
3252 - }
3245 + /* (cart-promo-input padded by alias group; width via .w-180 in template) */
3253 3246 .cart-checkout-share {
3254 3247 display: flex;
3255 3248 align-items: center;
@@ -7397,19 +7390,31 @@ textarea:focus-visible {
7397 7390 margin-bottom: 1rem;
7398 7391 }
7399 7392
7400 - .type-card {
7393 + /* Canonical selectable-card primitive. One recipe; per-context size modifiers.
7394 + Used by wizard type pickers, pricing-model pickers, content-choice pickers,
7395 + monetization-mode pickers — anywhere a radio-like choice is rendered as a card. */
7396 + .card--selectable,
7397 + .type-card,
7398 + .pricing-card,
7399 + .content-choice-card {
7401 7400 cursor: pointer;
7402 7401 display: block;
7403 7402 }
7404 -
7403 + .card--selectable input[type="radio"],
7404 + .card--selectable input[type="checkbox"],
7405 7405 .type-card input[type="radio"],
7406 - .type-card input[type="checkbox"] {
7406 + .type-card input[type="checkbox"],
7407 + .pricing-card input[type="radio"],
7408 + .content-choice-card input[type="radio"] {
7407 7409 position: absolute;
7408 7410 opacity: 0;
7409 7411 pointer-events: none;
7410 7412 }
7411 7413
7412 - .type-card-inner {
7414 + .card--selectable-inner,
7415 + .type-card-inner,
7416 + .pricing-card-inner,
7417 + .content-choice-card-inner {
7413 7418 display: flex;
7414 7419 flex-direction: column;
7415 7420 align-items: center;
@@ -7422,25 +7427,45 @@ textarea:focus-visible {
7422 7427 transition: border-color 0.15s, background 0.15s;
7423 7428 min-height: 60px;
7424 7429 }
7430 + /* Variant: column-aligned content (text-heavy pricing cards). */
7431 + .pricing-card-inner,
7432 + .card--selectable.is-text-heavy .card--selectable-inner {
7433 + align-items: stretch;
7434 + text-align: left;
7435 + padding: 1.25rem;
7436 + }
7425 7437
7438 + .card--selectable input:checked + .card--selectable-inner,
7439 + .card--selectable.is-selected .card--selectable-inner,
7426 7440 .type-card input:checked + .type-card-inner,
7427 - .type-card.is-selected .type-card-inner {
7428 - /* `:checked +` covers the radio-input pattern; `.is-selected` covers
7429 - templates that mark the wrapper directly. */
7441 + .type-card.is-selected .type-card-inner,
7442 + .pricing-card input:checked + .pricing-card-inner,
7443 + .pricing-card.is-selected .pricing-card-inner,
7444 + .content-choice-card input:checked + .content-choice-card-inner,
7445 + .content-choice-card.is-selected .content-choice-card-inner {
7430 7446 border-color: var(--highlight);
7431 7447 background: var(--highlight-faint);
7432 7448 }
7433 7449
7434 - .type-card:hover .type-card-inner {
7450 + .card--selectable:hover .card--selectable-inner,
7451 + .type-card:hover .type-card-inner,
7452 + .pricing-card:hover .pricing-card-inner,
7453 + .content-choice-card:hover .content-choice-card-inner {
7435 7454 background: var(--surface-muted);
7436 7455 }
7437 7456
7438 - .type-card-label {
7457 + .card--selectable-label,
7458 + .type-card-label,
7459 + .pricing-card-label,
7460 + .content-choice-card-label {
7439 7461 font-weight: bold;
7440 7462 font-size: 0.9rem;
7441 7463 }
7442 7464
7443 - .type-card-desc {
7465 + .card--selectable-desc,
7466 + .type-card-desc,
7467 + .pricing-card-desc,
7468 + .content-choice-card-desc {
7444 7469 font-size: 0.75rem;
7445 7470 color: var(--text-muted);
7446 7471 margin-top: 0.25rem;
@@ -7729,7 +7754,15 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
7729 7754 /* ===========================================
7730 7755 PAYMENTS TAB (replaces inline styles in user_payments.html)
7731 7756 =========================================== */
7732 - .stack-row {
7757 + /* Canonical "stack row" — title-on-left + actions-on-right header bar.
7758 + One primitive; per-context modifiers handle gap, wrap, alignment, margins.
7759 + Aliases below cover existing per-tab class names (kept as JS hooks). */
7760 + .stack-row,
7761 + .content-header,
7762 + .user-media-header,
7763 + .transactions-header,
7764 + .analytics-range-bar,
7765 + .cart-multi-bar {
7733 7766 display: flex;
7734 7767 justify-content: space-between;
7735 7768 align-items: center;
@@ -7738,6 +7771,21 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
7738 7771 }
7739 7772 .stack-row--tight { gap: var(--space-3); flex-wrap: nowrap; }
7740 7773 .stack-row--top { align-items: flex-start; }
7774 + .stack-row--bordered,
7775 + .transactions-header {
7776 + margin-bottom: var(--space-4);
7777 + padding-bottom: var(--space-3);
7778 + border-bottom: 1px solid var(--border);
7779 + }
7780 + .content-header,
7781 + .user-media-header,
7782 + .analytics-range-bar { margin-bottom: var(--space-4); }
7783 + .cart-multi-bar {
7784 + margin-bottom: var(--space-5);
7785 + padding: 1rem 1.25rem;
7786 + background: var(--surface-muted);
7787 + border: 1px solid var(--border-color);
7788 + }
7741 7789
7742 7790 /* Canonical list row (flex, gap, padding, bottom border). Aliases below cover
7743 7791 the per-tab variants — they exist as JS hooks but share the same look. */
@@ -7863,14 +7911,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
7863 7911 text-overflow: ellipsis;
7864 7912 }
7865 7913
7866 - .transactions-header {
7867 - display: flex;
7868 - justify-content: space-between;
7869 - align-items: center;
7870 - margin-bottom: var(--space-4);
7871 - padding-bottom: var(--space-3);
7872 - border-bottom: 1px solid var(--border);
7873 - }
7914 + /* (transactions-header aliased into .stack-row.stack-row--bordered) */
7874 7915 .transactions-title { font-size: 1.2rem; }
7875 7916
7876 7917 /* ===========================================
@@ -8020,12 +8061,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8020 8061 /* ===========================================
8021 8062 PROJECT CONTENT TAB (replaces inline styles in project_content.html)
8022 8063 =========================================== */
8023 - .content-header {
8024 - display: flex;
8025 - justify-content: space-between;
8026 - align-items: center;
8027 - margin-bottom: var(--space-4);
8028 - }
8064 + /* (content-header aliased into .stack-row canonical) */
8029 8065
8030 8066 .empty-state--lg { padding: var(--space-6) var(--space-4); }
8031 8067
@@ -8081,11 +8117,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8081 8117 display: block;
8082 8118 margin-bottom: var(--space-1);
8083 8119 }
8084 - .bulk-form-input {
8085 - width: 120px;
8086 - padding: var(--space-1) var(--space-2);
8087 - }
8088 - .bulk-form-input--wide { width: 260px; }
8120 + /* (bulk-form-input: use .input--xs .w-120 in templates; --wide uses .w-260) */
8089 8121
8090 8122 /* Sortable table column header */
8091 8123 .sortable-th { cursor: pointer; }
@@ -8104,6 +8136,33 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8104 8136 .col-32 { width: 32%; }
8105 8137 .col-45 { width: 45%; }
8106 8138
8139 + /* Pixel-width utilities for inputs and narrow fixed-width controls.
8140 + Use on form inputs that can't be sized by their parent column. */
8141 + .w-80 { width: 80px; }
8142 + .w-100 { width: 100px; }
8143 + .w-120 { width: 120px; }
8144 + .w-180 { width: 180px; }
8145 + .w-220 { width: 220px; }
8146 + .w-260 { width: 260px; }
8147 + .w-full { width: 100%; }
8148 + .maxw-320 { max-width: 320px; }
8149 +
8150 + /* Canonical input shape modifiers. One base form-input look (defined globally
8151 + at line ~365 input[type], textarea, select) plus these size + feature variants.
8152 + Apply alongside the per-tab class which acts as a JS hook. */
8153 + .input--xs {
8154 + padding: 0.25rem 0.4rem;
8155 + font-size: 0.85rem;
8156 + }
8157 + .input--sm {
8158 + padding: 0.3rem 0.5rem;
8159 + font-size: 0.85rem;
8160 + }
8161 + .input--mono { font-family: var(--font-mono); }
8162 + .input--upper { text-transform: uppercase; }
8163 + .input--numeric { text-align: right; }
8164 +
8165 +
8107 8166 .order-arrow-btn {
8108 8167 padding: 0.1rem 0.3rem;
8109 8168 font-size: 0.7rem;
@@ -8159,24 +8218,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8159 8218 /* ===========================================
8160 8219 ITEM DETAILS TAB (replaces inline styles in item_details.html)
8161 8220 =========================================== */
8162 - .image-preview-row {
8163 - display: flex;
8164 - align-items: flex-start;
8165 - gap: var(--space-5);
8166 - }
8167 - .image-preview-thumb {
8168 - width: 120px;
8169 - height: 120px;
8170 - background: var(--border);
8171 - display: flex;
8172 - align-items: center;
8173 - justify-content: center;
8174 - flex-shrink: 0;
8175 - }
8176 - .image-preview-empty {
8177 - opacity: 0.4;
8178 - font-size: 0.8rem;
8179 - }
8221 + /* (.image-preview-row/-thumb/-empty aliased into canonical .cover-* recipe near top) */
8180 8222
8181 8223 /* Compact button utility (small icon-side actions) */
8182 8224 .btn-compact { padding: 0.4rem 0.8rem; }
@@ -8339,15 +8381,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8339 8381 text-overflow: ellipsis;
8340 8382 white-space: nowrap;
8341 8383 }
8342 - .batch-queue-title-input {
8343 - width: 100%;
8344 - padding: 0.25rem 0.4rem;
8345 - font-size: 0.85rem;
8346 - }
8347 - .batch-queue-type-select {
8348 - padding: 0.25rem;
8349 - font-size: 0.85rem;
8350 - }
8384 + /* (batch-queue inputs/selects sized by alias group; width via .w-full in template) */
8351 8385
8352 8386 /* (Batch progress cell uses canonical .upload-progress-row / .progress-bar-container--slim --rounded / .progress-bar--highlight / .upload-progress-pct.) */
8353 8387
@@ -8635,9 +8669,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8635 8669 grid-column: 1 / -1;
8636 8670 }
8637 8671
8638 - .proj-promo-code-input {
8639 - text-transform: uppercase;
8640 - }
8672 + /* (proj-promo-code-input: use .input--upper) */
8641 8673
8642 8674 .proj-promo-actions {
8643 8675 margin-top: 1rem;
@@ -8739,9 +8771,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8739 8771 .proj-synckit-label {
8740 8772 display: block; font-size: 0.85rem; margin-bottom: var(--space-1);
8741 8773 }
8742 - .proj-synckit-name-input {
8743 - padding: var(--space-2); font-size: 0.9rem; width: 220px;
8744 - }
8774 + /* (proj-synckit-name-input: use .input--sm .w-220) */
8745 8775 .proj-synckit-code { font-size: 0.8rem; }
8746 8776 .proj-synckit-code--wrap { font-size: 0.8rem; word-break: break-all; }
8747 8777 .proj-synckit-code--selectable { font-size: 0.85rem; word-break: break-all; user-select: all; }
@@ -8760,9 +8790,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8760 8790 }
8761 8791 .proj-synckit-status-active { color: var(--success); }
8762 8792 .proj-synckit-status-inactive { color: var(--error); }
8763 - .proj-synckit-slug-input {
8764 - padding: 0.3rem; font-size: 0.85rem; width: 120px;
8765 - }
8793 + /* (proj-synckit-slug-input: use .input--sm .w-120) */
8766 8794 .proj-synckit-new-key {
8767 8795 padding: 0.75rem; background: var(--light-background);
8768 8796 border: 1px solid var(--border-color); margin-top: var(--space-2);
@@ -8957,10 +8985,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8957 8985 .promo-codes-edit-label {
8958 8986 font-size: 0.8rem; display: block; margin-bottom: var(--space-1);
8959 8987 }
8960 - .promo-codes-edit-input {
8961 - padding: 0.3rem; font-size: 0.85rem;
8962 - }
8963 - .promo-codes-edit-input--num { width: 100px; }
8988 + /* (promo-codes-edit-input: use .input--sm; --num adds .w-100) */
8964 8989 .promo-codes-edit-save { font-size: 0.85rem; }
8965 8990 .promo-codes-bulk-row { margin-top: var(--space-3); }
8966 8991 .promo-codes-bulk-btn { font-size: 0.85rem; opacity: 0.7; }
@@ -8969,11 +8994,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
8969 8994 USER MEDIA TAB (replaces inline styles in partials/tabs/user_media.html)
8970 8995 =========================================== */
8971 8996
8972 - .user-media-header {
8973 - display: flex;
8974 - justify-content: space-between;
8975 - align-items: center;
8976 - }
8997 + /* (user-media-header aliased into .stack-row canonical) */
8977 8998
8978 8999 .user-media-intro {
8979 9000 margin-bottom: 1rem;
@@ -9297,13 +9318,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
9297 9318 margin-top: 0.75rem;
9298 9319 }
9299 9320
9300 - .proj-members-input-full {
9301 - width: 100%;
9302 - }
9303 -
9304 - .proj-members-input-split {
9305 - width: 80px;
9306 - }
9321 + /* (proj-members-input-full: use .w-full; -split uses .w-80) */
9307 9322
9308 9323 .proj-members-add-result {
9309 9324 margin-top: var(--space-2);
@@ -9359,14 +9374,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
9359 9374 font-size: 0.8125rem;
9360 9375 margin-bottom: var(--space-1);
9361 9376 }
9362 - .placement-list-select,
9363 - .placement-list-input {
9364 - font-family: var(--font-body);
9365 - padding: 0.375rem 0.5rem;
9366 - }
9367 - .placement-list-input {
9368 - width: 100px;
9369 - }
9377 + /* (placement-list inputs/selects: use .input--sm .w-100 in template) */
9370 9378 .placement-list-add {
9371 9379 font-family: var(--font-mono);
9372 9380 }
@@ -9424,15 +9432,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
9424 9432 .user-synckit-status-active { color: var(--success); }
9425 9433 .user-synckit-status-inactive { color: var(--error); }
9426 9434
9427 - .user-synckit-edit-input {
9428 - padding: 0.3rem;
9429 - font-size: 0.85rem;
9430 - width: 120px;
9431 - }
9432 - .user-synckit-edit-select {
9433 - padding: 0.3rem;
9434 - font-size: 0.85rem;
9435 - }
9435 + /* (user-synckit-edit-input: use .input--sm .w-120; -select uses .input--sm) */
9436 9436 .user-synckit-edit-btn {
9437 9437 padding: 0.15rem 0.4rem;
9438 9438 font-size: 0.75rem;
@@ -9782,12 +9782,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
9782 9782 =========================================== */
9783 9783
9784 9784 .analytics-export-link { margin-left: var(--space-4); }
9785 - .analytics-range-bar {
9786 - display: flex;
9787 - justify-content: space-between;
9788 - align-items: center;
9789 - margin-bottom: var(--space-4);
9790 - }
9785 + /* (analytics-range-bar aliased into .stack-row canonical) */
9791 9786 .analytics-range-heading {
9792 9787 font-size: 1.1rem;
9793 9788 margin: 0;
@@ -10011,10 +10006,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
10011 10006 min-height: 400px;
10012 10007 resize: vertical;
10013 10008 }
10014 - .blog-editor-input {
10015 - width: 100%;
10016 - padding: var(--space-2);
10017 - }
10009 + /* (blog-editor-input: use .input--sm .w-full) */
10018 10010 .blog-editor-slug-spinner { font-size: 0.85rem; }
10019 10011 .blog-editor-media-btn {
10020 10012 margin-top: var(--space-2);
@@ -10322,13 +10314,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
10322 10314 /* Compact row-action button (wishlist Add to Cart / Remove, etc.). */
10323 10315 /* (library-row-btn aliased into .small recipe near top) */
10324 10316
10325 - /* Filter input above the purchases table. */
10326 - .library-filter-input {
10327 - width: 100%;
10328 - max-width: 320px;
10329 - padding: var(--space-2);
10330 - font-size: 0.9rem;
10331 - }
10317 + /* (library-filter-input: use .input--sm .w-full .maxw-320) */
10332 10318 .library-filter-wrap { margin-bottom: var(--space-4); }
10333 10319
10334 10320 /* Purchases table "Open" action link + context menu trigger group. */
@@ -10863,8 +10849,7 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
10863 10849 /* Faded "Following" follow-button state. */
10864 10850 .follow-btn.is-following { opacity: 0.7; }
10865 10851
10866 - /* Item-edit price input. */
10867 - .item-edit-price-input { width: 80px; }
10852 + /* (item-edit-price-input: use .w-80 .input--numeric) */
10868 10853
10869 10854 /* Project blog footer. */
10870 10855 .project-blog-footer {
@@ -141,52 +141,8 @@
141 141 grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
142 142 }
143 143
144 - .type-card {
145 - cursor: pointer;
146 - display: block;
147 - }
148 -
149 - .type-card input[type="radio"],
150 - .type-card input[type="checkbox"] {
151 - position: absolute;
152 - opacity: 0;
153 - pointer-events: none;
154 - }
155 -
156 - .type-card-inner {
157 - display: flex;
158 - flex-direction: column;
159 - align-items: center;
160 - justify-content: center;
161 - padding: 1rem 0.75rem;
162 - border: 2px solid var(--border);
163 - border-radius: 4px;
164 - background: var(--background);
165 - text-align: center;
166 - transition: border-color 0.15s, background 0.15s;
167 - min-height: 60px;
168 - }
169 -
170 - .type-card input:checked + .type-card-inner,
171 - .type-card.is-selected .type-card-inner {
172 - border-color: var(--highlight);
173 - background: var(--highlight-faint);
174 - }
175 -
176 - .type-card:hover .type-card-inner {
177 - background: var(--surface-muted);
178 - }
179 -
180 - .type-card-label {
181 - font-weight: bold;
182 - font-size: 0.9rem;
183 - }
184 -
185 - .type-card-desc {
186 - font-size: 0.75rem;
187 - color: var(--text-muted);
188 - margin-top: 0.25rem;
189 - }
144 + /* (.type-card primitive lives in style.css under the .card--selectable
145 + canonical recipe; this file only contains wizard-specific layouts.) */
190 146
191 147 /* Suggestion Input (category, tags) */
192 148
@@ -289,54 +245,13 @@
289 245 margin-bottom: 1.5rem;
290 246 }
291 247
292 - .pricing-card {
293 - cursor: pointer;
294 - display: block;
295 - }
296 -
297 - .pricing-card input[type="radio"] {
298 - position: absolute;
299 - opacity: 0;
300 - pointer-events: none;
301 - }
302 -
303 - .pricing-card-inner {
304 - display: flex;
305 - flex-direction: column;
306 - padding: 1.25rem;
307 - border: 2px solid var(--border);
308 - border-radius: 4px;
309 - background: var(--background);
310 - transition: border-color 0.15s;
311 - }
312 -
313 - .pricing-card input:checked + .pricing-card-inner,
314 - .pricing-card.is-selected .pricing-card-inner {
315 - border-color: var(--highlight);
316 - background: var(--highlight-faint);
317 - }
318 -
319 - .pricing-card:hover .pricing-card-inner {
320 - background: var(--surface-muted);
321 - }
322 -
323 - .pricing-card-label {
324 - font-weight: bold;
325 - font-size: 0.95rem;
326 - margin-bottom: 0.25rem;
327 - }
328 -
329 - .pricing-card-desc {
330 - font-size: 0.8rem;
331 - color: var(--text-muted);
332 - }
333 -
248 + /* (.pricing-card and .content-choice-card primitives live in style.css under
249 + the .card--selectable canonical recipe. The "muted" pointer-events override
250 + and content-choice anchor-link styling remain here.) */
334 251 .pricing-fields {
335 252 margin-top: 1rem;
336 253 }
337 254
338 - /* Content Choice Cards */
339 -
340 255 .content-choice-cards {
341 256 display: grid;
342 257 grid-template-columns: 1fr 1fr;
@@ -345,18 +260,8 @@
345 260 }
346 261
347 262 .content-choice-card {
348 - display: block;
349 - padding: 1.5rem;
350 - border: 2px solid var(--border);
351 - border-radius: 4px;
352 - background: var(--background);
353 263 text-decoration: none;
354 264 color: inherit;
355 - transition: border-color 0.15s;
356 - }
357 -
358 - .content-choice-card:hover {
359 - background: var(--surface-muted);
360 265 }
361 266
362 267 .content-choice-card.muted {
@@ -364,17 +269,6 @@
364 269 pointer-events: none;
365 270 }
366 271
367 - .content-choice-card h3 {
368 - font-family: var(--font-mono);
369 - font-size: 0.95rem;
370 - margin-bottom: 0.5rem;
371 - }
372 -
373 - .content-choice-card p {
374 - font-size: 0.85rem;
375 - color: var(--text-muted);
376 - }
377 -
378 272 /* Preview Summary */
379 273
380 274 .preview-summary {
@@ -19,12 +19,12 @@
19 19 <div class="editor-form" id="blog-editor" data-project-id="{{ project_id }}" data-project-slug="{{ project_slug }}"{% if editing %} data-post-id="{{ post_id }}"{% endif %}>
20 20 <div class="form-group">
21 21 <label for="post-title">Title</label>
22 - <input type="text" id="post-title" class="blog-editor-input" placeholder="Post title"
22 + <input type="text" id="post-title" class="blog-editor-input input--sm w-full" placeholder="Post title"
23 23 value="{{ post_title }}">
24 24 </div>
25 25 <div class="form-group">
26 26 <label for="post-slug">Slug (optional, auto-generated from title)</label>
27 - <input type="text" id="post-slug" class="blog-editor-input" placeholder="post-slug"
27 + <input type="text" id="post-slug" class="blog-editor-input input--sm w-full" placeholder="post-slug"
28 28 value="{{ post_slug }}"
29 29 hx-post="/api/validate/blog-slug"
30 30 hx-trigger="keyup changed delay:500ms"
@@ -36,7 +36,7 @@
36 36 </div>
37 37 <div class="form-group">
38 38 <label for="post-body">Content (Markdown)</label>
39 - <textarea id="post-body" rows="20" class="blog-editor-input" placeholder="Write your post in Markdown...">{{ post_body }}</textarea>
39 + <textarea id="post-body" rows="20" class="blog-editor-input input--sm w-full" placeholder="Write your post in Markdown...">{{ post_body }}</textarea>
40 40 <button type="button" class="secondary blog-editor-media-btn" onclick="mediaPickerOpen('post-body')">Insert Image</button>
41 41 </div>
42 42 <div class="blog-editor-actions">
@@ -66,7 +66,7 @@
66 66 <div class="cart-pwyw-cell">
67 67 <span class="cart-pwyw-symbol">$</span>
68 68 <input type="number"
69 - class="pwyw-cart-input cart-pwyw-input"
69 + class="pwyw-cart-input cart-pwyw-input input--xs w-80 input--numeric"
70 70 data-item-id="{{ item.item_id }}"
71 71 value="{{ item.effective_price_cents() / 100 }}.{{ "{:02}"|format(item.effective_price_cents() % 100) }}"
72 72 min="{{ item.pwyw_min_dollars() }}"
@@ -111,7 +111,7 @@
111 111 <div class="cart-promo-row">
112 112 <input type="text" name="promo_code" placeholder="Promo code (optional)"
113 113 autocomplete="off"
114 - class="cart-promo-input">
114 + class="cart-promo-input input--sm w-180">
115 115 </div>
116 116 <label class="cart-checkout-share">
117 117 <input type="checkbox" name="share_contact" value="true">
@@ -13,7 +13,7 @@
13 13 </select>
14 14 </td>
15 15 <td>
16 - <input type="number" step="0.01" min="0" placeholder="0.00" class="edit-input item-edit-price-input"
16 + <input type="number" step="0.01" min="0" placeholder="0.00" class="edit-input item-edit-price-input w-80 input--numeric"
17 17 oninput="this.nextElementSibling.value = Math.round(parseFloat(this.value || 0) * 100)">
18 18 <input type="hidden" name="price_cents" value="0">
19 19 </td>
@@ -37,7 +37,7 @@
37 37 <form id="add-placement-form" class="placement-list-form">
38 38 <div>
39 39 <label class="placement-list-label">Clip</label>
40 - <select name="insertion_id" id="placement-insertion-id" class="placement-list-select" required>
40 + <select name="insertion_id" id="placement-insertion-id" class="placement-list-select input--sm" required>
41 41 {% for ins in available_insertions %}
42 42 <option value="{{ ins.id }}">{{ ins.title }} ({{ ins.duration_display }})</option>
43 43 {% endfor %}
@@ -45,7 +45,7 @@
45 45 </div>
46 46 <div>
47 47 <label class="placement-list-label">Position</label>
48 - <select name="position" id="placement-position" class="placement-list-select" required onchange="MNW.insertions.toggleOffsetInput(this.value)">
48 + <select name="position" id="placement-position" class="placement-list-select input--sm" required onchange="MNW.insertions.toggleOffsetInput(this.value)">
49 49 <option value="pre_roll">Pre-roll</option>
50 50 <option value="mid_roll">Mid-roll</option>
51 51 <option value="post_roll">Post-roll</option>
@@ -53,7 +53,7 @@
53 53 </div>
54 54 <div id="offset-input-group" class="hidden">
55 55 <label class="placement-list-label">Offset (seconds)</label>
56 - <input type="number" name="offset_seconds" id="placement-offset" class="placement-list-input" min="0" step="1">
56 + <input type="number" name="offset_seconds" id="placement-offset" class="placement-list-input input--sm w-100" min="0" step="1">
57 57 </div>
58 58 <button type="button" class="btn btn-sm placement-list-add" onclick="MNW.insertions.addPlacement('{{ item_id }}')">Add</button>
59 59 </form>
@@ -71,19 +71,19 @@
71 71 <label class="promo-codes-edit-label">Max uses</label>
72 72 <input type="number" name="max_uses" min="1" placeholder="Unlimited"
73 73 value="{{ code.max_uses_raw }}"
74 - class="promo-codes-edit-input promo-codes-edit-input--num">
74 + class="promo-codes-edit-input input--sm w-100">
75 75 </div>
76 76 <div>
77 77 <label class="promo-codes-edit-label">Starts</label>
78 78 <input type="date" name="starts_at"
79 79 value="{{ code.starts_at_raw.as_deref().unwrap_or_default() }}"
80 - class="promo-codes-edit-input">
80 + class="promo-codes-edit-input input--sm">
81 81 </div>
82 82 <div>
83 83 <label class="promo-codes-edit-label">Expires</label>
84 84 <input type="date" name="expires_at"
85 85 value="{{ code.expires_at_raw.as_deref().unwrap_or_default() }}"
86 - class="promo-codes-edit-input">
86 + class="promo-codes-edit-input input--sm">
87 87 </div>
88 88 <button class="secondary small promo-codes-edit-save" type="submit">Save</button>
89 89 </form>
@@ -10,7 +10,7 @@
10 10 <div class="library-filter-wrap">
11 11 <input type="search" id="library-search" placeholder="Filter by title or creator..."
12 12 autocomplete="off"
13 - class="library-filter-input"
13 + class="library-filter-input input--sm w-full maxw-320"
14 14 oninput="(function(q){var rows=document.querySelectorAll('#library-table tbody tr');q=q.toLowerCase();rows.forEach(function(r){r.classList.toggle('hidden', r.textContent.toLowerCase().indexOf(q)===-1);});})(this.value)">
15 15 </div>
16 16 <table class="data-table minw-500" id="library-table">
@@ -38,7 +38,7 @@
38 38 <div class="bulk-form-row">
39 39 <div>
40 40 <label class="bulk-form-label">New price ($)</label>
41 - <input type="number" id="bulk-price-input" min="0" step="0.01" placeholder="0.00" class="bulk-form-input">
41 + <input type="number" id="bulk-price-input" min="0" step="0.01" placeholder="0.00" class="bulk-form-input input--xs w-120">
42 42 </div>
43 43 <button class="primary small" onclick="submitBulkPrice()">Apply</button>
44 44 <button class="secondary small" onclick="document.getElementById('bulk-price-form').classList.add('hidden')">Cancel</button>
@@ -49,7 +49,7 @@
49 49 <div class="bulk-form-row">
50 50 <div>
51 51 <label class="bulk-form-label">Tag slug</label>
52 - <input type="text" id="bulk-tag-input" placeholder="e.g. audio.genre.ambient" class="bulk-form-input bulk-form-input--wide">
52 + <input type="text" id="bulk-tag-input" placeholder="e.g. audio.genre.ambient" class="bulk-form-input input--xs w-260">
53 53 </div>
54 54 <button class="primary small" onclick="submitBulkTag()">Apply</button>
55 55 <button class="secondary small" onclick="document.getElementById('bulk-tag-form').classList.add('hidden')">Cancel</button>
@@ -28,12 +28,12 @@
28 28 <div>
29 29 <label for="member-username" class="proj-members-field-label">Username</label>
30 30 <input type="text" id="member-username" name="username" required
31 - placeholder="Enter username" class="proj-members-input-full">
31 + placeholder="Enter username" class="proj-members-input-full w-full">
32 32 </div>
33 33 <div>
34 34 <label for="member-split" class="proj-members-field-label">Split %</label>
35 35 <input type="number" id="member-split" name="split_percent" required
36 - min="1" max="99" value="50" class="proj-members-input-split">
36 + min="1" max="99" value="50" class="proj-members-input-split w-80">
37 37 </div>
38 38 <div>
39 39 <button class="primary" type="submit">Add</button>
@@ -42,7 +42,7 @@
42 42 <div>
43 43 <label for="member-role" class="proj-members-field-label proj-members-field-label--spaced">Role (optional)</label>
44 44 <input type="text" id="member-role" name="role"
45 - placeholder="e.g. Producer, Artist, Engineer" class="proj-members-input-full">
45 + placeholder="e.g. Producer, Artist, Engineer" class="proj-members-input-full w-full">
46 46 </div>
47 47 <div id="member-add-result" class="proj-members-add-result"></div>
48 48 </form>
@@ -28,7 +28,7 @@
28 28 <div class="form-group">
29 29 <label for="pc-code">Code</label>
30 30 <input type="text" id="pc-code" name="code" placeholder="e.g. LAUNCH50"
31 - class="proj-promo-code-input">
31 + class="proj-promo-code-input input--upper">
32 32 </div>
33 33 </div>
34 34
@@ -18,7 +18,7 @@
18 18 <label for="synckit-app-name" class="proj-synckit-label">App Name</label>
19 19 <input type="text" id="synckit-app-name" name="name" required
20 20 placeholder="e.g. GoingsOn Desktop"
21 - class="proj-synckit-name-input">
21 + class="proj-synckit-name-input input--sm w-220">
22 22 </div>
23 23 <button type="submit" class="btn">Create</button>
24 24 </form>
@@ -138,7 +138,7 @@ function syncKitShowSlugForm(appId, currentSlug) {
138 138 input.type = 'text';
139 139 input.value = currentSlug;
140 140 input.placeholder = 'e.g. goingson';
141 - input.className = 'proj-synckit-slug-input';
141 + input.className = 'proj-synckit-slug-input input--sm w-120';
142 142 cell.appendChild(input);
143 143 cell.appendChild(document.createTextNode(' '));
144 144 var saveBtn = document.createElement('button');
@@ -157,7 +157,7 @@ function syncKitShowSlugForm(appId, currentSlug) {
157 157 input.type = 'text';
158 158 input.value = currentSlug;
159 159 input.placeholder = 'e.g. goingson';
160 - input.className = 'user-synckit-edit-input';
160 + input.className = 'user-synckit-edit-input input--sm w-120';
161 161 cell.appendChild(input);
162 162 cell.appendChild(document.createTextNode(' '));
163 163 var saveBtn = document.createElement('button');
@@ -197,7 +197,7 @@ function syncKitShowLinkForm(appId) {
197 197 cell.innerHTML = '';
198 198 var select = document.createElement('select');
199 199 select.id = 'synckit-link-select-' + appId;
200 - select.className = 'user-synckit-edit-select';
200 + select.className = 'user-synckit-edit-select input--sm';
201 201 var noneOpt = document.createElement('option');
202 202 noneOpt.value = '';
203 203 noneOpt.textContent = 'None';
@@ -204,8 +204,8 @@
204 204 tr.id = 'batch-row-' + idx;
205 205 tr.innerHTML =
206 206 '<td class="batch-queue-name" title="' + f.name.replace(/"/g, '&quot;') + '">' + escapeHtml(f.name) + '</td>' +
207 - '<td><input type="text" class="batch-title batch-queue-title-input" data-idx="' + idx + '" value="' + escapeAttr(entry.title) + '"></td>' +
208 - '<td><select class="batch-type batch-queue-type-select" data-idx="' + idx + '"><option value="audio"' + (entry.type === 'audio' ? ' selected' : '') + '>Audio</option><option value="digital"' + (entry.type === 'digital' ? ' selected' : '') + '>Digital</option></select></td>' +
207 + '<td><input type="text" class="batch-title batch-queue-title-input input--xs w-full" data-idx="' + idx + '" value="' + escapeAttr(entry.title) + '"></td>' +
208 + '<td><select class="batch-type batch-queue-type-select input--xs" data-idx="' + idx + '"><option value="audio"' + (entry.type === 'audio' ? ' selected' : '') + '>Audio</option><option value="digital"' + (entry.type === 'digital' ? ' selected' : '') + '>Digital</option></select></td>' +
209 209 '<td class="batch-status" data-idx="' + idx + '">Ready</td>';
210 210 tbody.appendChild(tr);
211 211 }