Skip to main content

max / balanced_breakfast

Take button depth and badge tone from the generated stylesheet Phase A adoption, the markup untouched. Two components hand-wrote what makeover-webview now emits, so the hand-written half goes. Button: the fill and the bevel leave .button, and the whole :active rule goes with them, because the generated rules emit the fill and the edge together and cannot disagree the way two declarations can. What stays is what is not depth. Two visible consequences, both the description's answer rather than this file's: hover reads as --hover-surface instead of --surface-sunken, and pressing reads as a well. The brand modifiers keep their own fills, which the description has no opinion about. Fixed on the way past: .button:disabled:active overrode box-shadow alone, so a disabled button took the pressed fill while keeping the raised edge. It is now a :hover and :active pair setting both properties, which is what goingson landed on for the same reason. Badge: the colour axis moves from [data-color] to the emitted [data-tone], naming the meaning rather than the paint. Nearly free, because three of the four category tokens were already the same value under a second name -- category-one is danger, category-two is success, category-four is warning -- so the one live coloured badge, the yellow Recommended pill in the plugin list, keeps its exact hex as tone warning. Badges also stopped sharing the [data-color] variants with .tag, which keeps them: a tag names a category and a badge names a state, and spelling them the same way was hiding that.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-30 03:40 UTC
Signed with PGP, not checked
Commit: 7f1e52e7f5f7f0871f495bcb0cd7dc45753a4212
Parent: 04a2a18
3 files changed, +59 insertions, -30 deletions
@@ -576,28 +576,27 @@
576 576 .button--icon, .button--text, .button--link; size .button--sm; state .button--loading).
577 577 Charter target: collapse remaining one-off *-btn classes onto this family.
578 578 ========================================================================== */
579 + /* The fill and the bevel are gone from here, along with the whole
580 + :active rule: .button, .button:hover and .button:active come from
581 + makeover-webview, which emits the fill and the edge together so they
582 + cannot disagree. What stays is everything that is not depth -- box
583 + model, type, cursor -- plus the disabled overrides below.
584 +
585 + Two visible consequences, both the description's answer rather than
586 + this file's: hover reads as --hover-surface instead of
587 + --surface-sunken, and pressing reads as a well. */
579 588 .button {
580 589 padding: var(--gap-peer) var(--gap-group);
581 590 border: var(--border-width) solid var(--border);
582 591 border-radius: var(--radius-sm);
583 - background-color: var(--surface-raised);
584 592 color: var(--content);
585 593 cursor: pointer;
586 594 font-weight: 600;
587 595 font-size: var(--font-size-base);
588 - box-shadow: var(--bevel-raised);
589 596 }
590 597 .button:hover {
591 - background-color: var(--surface-sunken);
592 598 border-color: var(--border-strong);
593 599 }
594 - /* Pressed inverts the bevel and drops the fill a step. No offset, no
595 - translate: the button reads as pushed into the page, and every modifier
596 - below inherits the state for free. */
597 - .button:active {
598 - background-color: var(--surface-sunken);
599 - box-shadow: var(--bevel-inset);
600 - }
601 600 /* Disabled keeps its bevel. The object is still there; greying the content
602 601 is what says the action is unavailable. */
603 602 .button:disabled {
@@ -605,7 +604,16 @@
605 604 border-color: var(--content-muted);
606 605 cursor: not-allowed;
607 606 }
608 - .button:disabled:active { box-shadow: var(--bevel-raised); }
607 + /* Inert on both, and both properties on both selectors. Overriding only
608 + box-shadow on :active left a disabled button taking the pressed fill
609 + while keeping the raised edge, which is the fill-and-edge disagreement
610 + the generated rules exist to prevent. Higher specificity than the
611 + generated .button:hover and .button:active, so these win. */
612 + .button:disabled:hover,
613 + .button:disabled:active {
614 + background: var(--surface-raised);
615 + box-shadow: var(--bevel-raised);
616 + }
609 617 .button--primary {
610 618 background-color: var(--action);
611 619 border-color: var(--action);
@@ -1016,19 +1024,36 @@
1016 1024 }
1017 1025
1018 1026 /* F2 additions (2026-06-02): color variants via [data-color] attribute.
1019 - Used for both .tag and .badge so the same color contract works for both.
1020 - Consumes --category-five and --category-six. */
1021 - .tag[data-color="green"], .badge[data-color="green"] { border-color: var(--category-two); color: var(--category-two); background-color: color-mix(in srgb, var(--category-two) 10%, transparent); }
1022 - .tag[data-color="yellow"], .badge[data-color="yellow"] { border-color: var(--category-four); color: var(--content); background-color: color-mix(in srgb, var(--category-four) 12%, transparent); }
1023 - .tag[data-color="red"], .badge[data-color="red"] { border-color: var(--category-one); color: var(--category-one); background-color: color-mix(in srgb, var(--category-one) 10%, transparent); }
1024 - .tag[data-color="blue"], .badge[data-color="blue"] { border-color: var(--category-three); color: var(--category-three); background-color: color-mix(in srgb, var(--category-three) 10%, transparent); }
1025 - .tag[data-color="cyan"], .badge[data-color="cyan"] { border-color: var(--category-six); color: var(--category-six); background-color: color-mix(in srgb, var(--category-six) 10%, transparent); }
1026 - .tag[data-color="purple"], .badge[data-color="purple"] { border-color: var(--category-five); color: var(--category-five); background-color: color-mix(in srgb, var(--category-five) 10%, transparent); }
1027 - .tag[data-color="muted"], .badge[data-color="muted"] { border-color: var(--border); color: var(--content-muted); background-color: var(--surface-sunken); }
1027 + Consumes --category-five and --category-six.
1028 +
1029 + .badge dropped off these selectors when badges moved to [data-tone]:
1030 + nothing sets [data-color] on a badge any more, so the badge halves were
1031 + matching nothing. .tag keeps the axis, since a tag names a category
1032 + rather than a state and green-because-green is the right contract for
1033 + it. That is also why the two stopped sharing: they were never saying
1034 + the same thing, only spelling it the same way. */
1035 + .tag[data-color="green"] { border-color: var(--category-two); color: var(--category-two); background-color: color-mix(in srgb, var(--category-two) 10%, transparent); }
1036 + .tag[data-color="yellow"] { border-color: var(--category-four); color: var(--content); background-color: color-mix(in srgb, var(--category-four) 12%, transparent); }
1037 + .tag[data-color="red"] { border-color: var(--category-one); color: var(--category-one); background-color: color-mix(in srgb, var(--category-one) 10%, transparent); }
1038 + .tag[data-color="blue"] { border-color: var(--category-three); color: var(--category-three); background-color: color-mix(in srgb, var(--category-three) 10%, transparent); }
1039 + .tag[data-color="cyan"] { border-color: var(--category-six); color: var(--category-six); background-color: color-mix(in srgb, var(--category-six) 10%, transparent); }
1040 + .tag[data-color="purple"] { border-color: var(--category-five); color: var(--category-five); background-color: color-mix(in srgb, var(--category-five) 10%, transparent); }
1041 + .tag[data-color="muted"] { border-color: var(--border); color: var(--content-muted); background-color: var(--surface-sunken); }
1028 1042 .tag--xs { font-size: var(--font-size-xxs); padding: 0 var(--step-snug); } /* mirrors .badge--xs */
1029 1043
1030 1044 /* .badge: smaller and more compact than .tag; used for counts and inline
1031 - status indicators. Same color contract via [data-color]. */
1045 + status indicators.
1046 +
1047 + The colour axis is [data-tone], which makeover-webview emits, replacing
1048 + the [data-color] axis this file used to carry. The old axis named the
1049 + paint (green, yellow, red, blue) and the new one names the meaning, so
1050 + a badge now says what it is rather than what colour it came out. The
1051 + swap is close to free here because three of the four category tokens
1052 + were already the same value under a second name: category-one is
1053 + danger, category-two is success, category-four is warning.
1054 +
1055 + The base text colour is the generated one. What stays below is the box:
1056 + the raised treatment, the border, the type. */
1032 1057 .badge {
1033 1058 display: inline-flex;
1034 1059 align-items: center;
@@ -1039,16 +1064,19 @@
1039 1064 padding: 0 var(--step-snug);
1040 1065 font-size: var(--font-size-xs);
1041 1066 font-weight: 600;
1042 - color: var(--content-secondary);
1043 1067 line-height: 1.3;
1044 1068 white-space: nowrap;
1045 1069 box-shadow: var(--bevel-raised);
1046 1070 }
1071 + /* Filled is this app's own variant and has no counterpart in the
1072 + description, which tones the text and leaves the fill alone. It is kept
1073 + because a filled badge is how a recommendation reads at a glance, and it
1074 + is driven off the same tone attribute so there is still one axis. */
1047 1075 .badge--filled { border: none; box-shadow: none; }
1048 - .badge--filled[data-color="green"] { background-color: var(--category-two); color: var(--content-on-action); }
1049 - .badge--filled[data-color="yellow"] { background-color: var(--category-four); color: var(--content); }
1050 - .badge--filled[data-color="red"] { background-color: var(--category-one); color: var(--content-on-action); }
1051 - .badge--filled[data-color="blue"] { background-color: var(--category-three); color: var(--content-on-action); }
1076 + .badge--filled[data-tone="info"] { background-color: var(--info); color: var(--content-on-action); }
1077 + .badge--filled[data-tone="success"] { background-color: var(--success); color: var(--content-on-action); }
1078 + .badge--filled[data-tone="warning"] { background-color: var(--warning); color: var(--content); }
1079 + .badge--filled[data-tone="danger"] { background-color: var(--danger); color: var(--content-on-action); }
1052 1080
1053 1081 .detail-body {
1054 1082 color: var(--content);
@@ -419,8 +419,9 @@
419 419 * @param {HTMLElement|string} [model.primary] - Required-ish top line. String = textContent.
420 420 * @param {HTMLElement|string} [model.secondary] - Sub-line.
421 421 * @param {HTMLElement|string} [model.meta] - Small right-aligned label (date, count).
422 - * @param {Array<{label: string, color?: string, filled?: boolean}>} [model.badges]
423 - * - Rendered as `.badge[data-color]` next to primary.
422 + * @param {Array<{label: string, tone?: string, filled?: boolean}>} [model.badges]
423 + * - Rendered as `.badge[data-tone]` next to primary. `tone` is one of
424 + * info, success, warning, danger; omitting it leaves the badge muted.
424 425 * @param {Array<HTMLElement>} [model.actions] - Right-side buttons (hover-revealed via CSS).
425 426 * @param {function} [model.onClick] - Click handler on the outer element.
426 427 * @param {Object<string,string>} [model.attrs] - Extra HTML attributes (data-*, aria-*).
@@ -467,7 +468,7 @@
467 468 for (const b of model.badges) {
468 469 const badge = document.createElement('span');
469 470 badge.className = 'badge' + (b.filled ? ' badge--filled' : '');
470 - if (b.color) badge.setAttribute('data-color', b.color);
471 + if (b.tone) badge.setAttribute('data-tone', b.tone);
471 472 badge.textContent = b.label;
472 473 primaryLine.appendChild(badge);
473 474 }
@@ -111,7 +111,7 @@
111 111 primary: plugin.name,
112 112 secondary: plugin.description || null,
113 113 badges: recommended.has(plugin.id)
114 - ? [{ label: 'Recommended', color: 'yellow', filled: true }]
114 + ? [{ label: 'Recommended', tone: 'warning', filled: true }]
115 115 : null,
116 116 onClick: () => selectPlugin(plugin.id),
117 117 });