Skip to main content

max / balanced_breakfast

Adopt the Platinum bevel tokens Follows GoingsOn onto the Platinum direction: the two-tone bevel replaces the offset shadow as the edge signal, corners go square except on buttons and inputs, and the frame drops to a hairline. Token deltas against what shipped: border-width 2px to 1px, radius-sm 5px to 4px, radius-lg 10px to 0, radius-xl 20px to 0, radius-xs 3px to 0. --shadow-offset survives on floating surfaces only, which now carry both a bevel and a drop shadow; everything raised is read by its bevel alone. --bevel-light / --bevel-dark are named for the intents makeover emits, so adopting the crate version later is deleting two color-mix lines. Pressed states come free with the inversion, so .btn, .card--clickable, .plugin-item and .tag-chip.active latch instead of translating. Motion was triaged rather than swept: state feedback that only recolors is gone, the progress fill and the toast/spinner animations stay. Folds in the literal sweep (borders and radii that bypassed the tokens) and the stale pointers to the retired design-system.md. Direction and conversion spec: wiki note platinum-appearance.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-28 17:36 UTC
Signed with PGP, not checked
Commit: d2edcd26fb788cfad7dacbf3c909838d53b42271
Parent: 2b3e37b
2 files changed, +129 insertions, -67 deletions
@@ -1,7 +1,7 @@
1 1 #!/bin/bash
2 2 # Frontend design-system lint guards.
3 - # See _private/docs/balanced_breakfast/design-system.md "Inline-style rules"
4 - # and "Cross-cutting rules" for the source of truth.
3 + # See the wiki note `bb-design-system` ("Inline-style rules" and
4 + # "Cross-cutting rules") for the source of truth.
5 5 # Exit 0 = clean. Exit non-zero = violations found (printed with file:line).
6 6
7 7 set -u
@@ -108,17 +108,24 @@
108 108 42. Webkit scrollbar
109 109 ============================================================================ */
110 110
111 - /* === 1. Design System Variables =========================================
112 - Color tokens are themed via `js/themes.js` (loads shared TOML palettes
113 - from MNW/shared/themes/). The fallback hex values below are the
114 - `flatwhite` light theme palette, used only for initial render before
111 + /* === 1. Design System Variables (Platinum-informed) =====================
112 + Mac OS 8 Platinum, informed rather than copied: two-tone bevels, square
113 + corners, instant state changes, native window chrome. Grew out of the
114 + neobrute-lite system it replaces, which is why the token shapes (border
115 + width, radius scale, offset shadow) carry over intact. Direction and the
116 + conversion spec: wiki note `platinum-appearance`.
117 +
118 + Color tokens are themed via `js/themes.js`, which applies the intent set
119 + makeover resolved for the active theme. The fallback hex values below are
120 + the `flatwhite` light theme palette, used only for initial render before
115 121 `themes.js` runs. Annotation legend:
116 122
117 - themed: set by themes.js COLOR_MAP from TOML
118 - derived: computed in themes.js applyTheme() from a themed token
119 - invariant: fixed; never changes with theme
120 - unused: declared but no CSS rule consumes it; see design-system.md
121 - token audit. Phase F1 cleanup: consume or remove.
123 + themed: applied at runtime by themes.js applyTheme()
124 + composition: CSS color-mix / var() derived from other tokens
125 + invariant: fixed; never changes with theme
126 +
127 + The annotation is the contract the `theme-token-coverage` lint rule
128 + checks; the primitive inventory lives in wiki note `bb-design-system`.
122 129 ========================================================================== */
123 130 :root {
124 131 /* === INTENT LAYER (themeable) ===
@@ -155,19 +162,42 @@
155 162 /* === APP-LOCAL: derived from intents (color-mix) or non-color invariants ===
156 163 BB's component CSS references the intent tokens directly. These few are
157 164 BB-specific: the danger-button hover and yellow highlight (no universal
158 - intent), the neobrute shadow color, and the neobrute geometry. All derive
159 - from intents, no color literals. */
165 + intent), the drop-shadow color, and the geometry. All derive from
166 + intents, no color literals.
167 +
168 + Geometry is app-local by contract: a theme overrides color only, so
169 + border width, bevel thickness and radius cannot be reached from a
170 + theme file. */
160 171 --accent-red-hover: color-mix(in oklch, var(--danger) 90%, white); /* composition: .btn-danger:hover */
161 172 --accent-yellow-light: color-mix(in oklch, var(--category-four) 82%, white); /* composition */
162 173 --shadow-color: color-mix(in oklch, var(--content-muted) 88%, black); /* composition */
163 - --border-width: 2px; /* invariant */
164 - --radius-xs: 3px; /* invariant: scrollbars, tiny elements */
165 - --radius-sm: 5px; /* invariant */
166 - --radius-lg: 10px; /* invariant */
167 - --radius-xl: 20px; /* invariant */
174 + --border-width: 1px; /* invariant */
175 +
176 + /* Bevel tones, named for the intents makeover emits. Until BB picks up a
177 + makeover that ships them, color-mix derives the same two colors from
178 + whatever surface the active theme resolved; adopting the crate version
179 + is deleting these two lines, because every consumer already reads the
180 + right name. */
181 + --bevel-light: color-mix(in oklab, var(--surface-raised) 48%, #FFFFFF); /* composition */
182 + --bevel-dark: color-mix(in oklab, var(--surface-raised) 58%, #000000); /* composition */
183 +
184 + /* The two-tone bevel. Raised is lit from the top left; inset is the same
185 + bevel inverted, which is also the pressed state for anything raised.
186 + One token swap per component, which is what makes the idiom cheap. */
187 + --bevel-raised: inset 1px 1px 0 var(--bevel-light), inset -1px -1px 0 var(--bevel-dark); /* composition */
188 + --bevel-inset: inset 1px 1px 0 var(--bevel-dark), inset -1px -1px 0 var(--bevel-light); /* composition */
189 +
190 + --radius-xs: 0; /* invariant: scrollbars, tiny elements */
191 + --radius-sm: 4px; /* invariant: the one place a corner survives */
192 + --radius-lg: 0; /* invariant */
193 + --radius-xl: 0; /* invariant: was pills and capsules */
168 194 --radius-full: 50%; /* invariant: circles */
169 - --shadow-offset: 3px; /* invariant: BB neobrute signature */
170 - --shadow-brutal: var(--shadow-offset) var(--shadow-offset) 0 var(--shadow-color); /* invariant composition */
195 +
196 + /* Drop shadows, floating surfaces only. A raised object is read by its
197 + bevel now; the offset shadow says "above the page", not "raised". */
198 + --shadow-offset: 3px; /* invariant */
199 + --shadow-brutal: var(--shadow-offset) var(--shadow-offset) 0 var(--shadow-color); /* composition */
200 + --shadow-brutal-lg: calc(var(--shadow-offset) * 2) calc(var(--shadow-offset) * 2) 0 var(--shadow-color); /* composition: modals */
171 201
172 202 /* Invariant scales (never themed). Per-line `invariant` markers satisfy
173 203 the lint theme-token-coverage rule. radius-md intentionally absent
@@ -266,7 +296,6 @@
266 296 padding: var(--gap-peer) var(--gap-section);
267 297 background-color: var(--surface-overlay);
268 298 border-bottom: var(--border-width) solid var(--border);
269 - box-shadow: 0 var(--shadow-offset) 0 var(--shadow-color);
270 299 -webkit-user-select: none;
271 300 user-select: none;
272 301 position: relative;
@@ -293,7 +322,7 @@
293 322 color: var(--content);
294 323 width: 180px;
295 324 font-size: 0.875rem;
296 - transition: border-color 0.2s, box-shadow 0.2s;
325 + box-shadow: var(--bevel-inset);
297 326 }
298 327
299 328 /* Search spinner */
@@ -301,9 +330,9 @@
301 330 display: none;
302 331 width: 14px;
303 332 height: 14px;
304 - border: 2px solid var(--border);
333 + border: 2px solid var(--border); /* spinner ring thickness, not a frame */
305 334 border-top-color: var(--category-four);
306 - border-radius: 50%;
335 + border-radius: var(--radius-full);
307 336 animation: spin 0.6s linear infinite;
308 337 }
309 338 .search-spinner.active { display: inline-block; }
@@ -316,7 +345,7 @@
316 345 .search-input:focus {
317 346 outline: none;
318 347 border-color: var(--category-four);
319 - box-shadow: 0 0 0 2px var(--category-four);
348 + box-shadow: var(--bevel-inset), 0 0 0 2px var(--category-four);
320 349 }
321 350
322 351 .sort-select {
@@ -327,6 +356,7 @@
327 356 color: var(--content);
328 357 cursor: pointer;
329 358 font-size: 0.875rem;
359 + box-shadow: var(--bevel-inset);
330 360 }
331 361 .sort-select:focus { outline: none; border-color: var(--category-four); }
332 362
@@ -368,10 +398,10 @@
368 398 read/unread/selected/pinned state. Replaces the legacy .item-* layout. */
369 399 .row--article {
370 400 padding: var(--gap-peer) var(--gap-section);
371 - border-bottom: 1px solid var(--border);
401 + border-bottom: var(--border-width) solid var(--border);
402 + /* 3px, wider than a frame on purpose: this edge is the state marker. */
372 403 border-left: 3px solid transparent;
373 404 cursor: pointer;
374 - transition: background-color 0.15s;
375 405 }
376 406 .row--article:hover { background-color: var(--surface-overlay); }
377 407 .row--article.is-unread {
@@ -445,7 +475,6 @@
445 475 cursor: pointer;
446 476 font-size: 1.1rem;
447 477 color: var(--content-muted);
448 - transition: color 0.2s, transform 0.15s;
449 478 padding: var(--step-tight) var(--step-snug);
450 479 margin: calc(-1 * var(--step-tight)) calc(-1 * var(--step-snug));
451 480 line-height: 1;
@@ -456,7 +485,7 @@
456 485 .read-indicator {
457 486 width: 7px;
458 487 height: 7px;
459 - border-radius: 50%;
488 + border-radius: var(--radius-full);
460 489 margin-top: var(--gap-bound);
461 490 }
462 491 .read-indicator.unread { background-color: var(--danger); }
@@ -559,12 +588,27 @@
559 588 cursor: pointer;
560 589 font-weight: 600;
561 590 font-size: 0.875rem;
562 - transition: background-color 0.2s, border-color 0.2s;
591 + box-shadow: var(--bevel-raised);
563 592 }
564 593 .btn:hover {
565 594 background-color: var(--surface-sunken);
566 595 border-color: var(--border-strong);
567 596 }
597 + /* Pressed inverts the bevel and drops the fill a step. No offset, no
598 + translate: the button reads as pushed into the page, and every modifier
599 + below inherits the state for free. */
600 + .btn:active {
601 + background-color: var(--surface-sunken);
602 + box-shadow: var(--bevel-inset);
603 + }
604 + /* Disabled keeps its bevel. The object is still there; greying the content
605 + is what says the action is unavailable. */
606 + .btn:disabled {
607 + color: var(--content-muted);
608 + border-color: var(--content-muted);
609 + cursor: not-allowed;
610 + }
611 + .btn:disabled:active { box-shadow: var(--bevel-raised); }
568 612 .btn-primary {
569 613 background-color: var(--action);
570 614 border-color: var(--action);
@@ -577,7 +621,7 @@
577 621 color: var(--content-on-action);
578 622 }
579 623 .btn-success:hover { background-color: var(--accent-yellow-light); border-color: var(--accent-yellow-light); }
580 - /* .btn-sm: canonical small-button size (see design-system.md §Button). */
624 + /* .btn-sm: canonical small-button size (see wiki note `bb-design-system`). */
581 625 .btn-sm { padding: var(--gap-bound) var(--gap-peer); font-size: 0.8rem; }
582 626 .btn-sm.active { background: var(--category-four); color: var(--content-on-action); border-color: var(--category-four); }
583 627
@@ -618,8 +662,10 @@
618 662 border: none;
619 663 padding: var(--gap-bound) var(--gap-peer);
620 664 color: var(--content);
665 + box-shadow: none;
621 666 }
622 667 .btn-text:hover { background-color: var(--surface-overlay); border-color: transparent; }
668 + .btn-text:active { box-shadow: none; }
623 669
624 670 /* .btn-link: looks like an anchor, behaves like a button. */
625 671 .btn-link {
@@ -630,8 +676,10 @@
630 676 font-weight: 600;
631 677 text-decoration: underline;
632 678 text-underline-offset: 2px;
679 + box-shadow: none;
633 680 }
634 681 .btn-link:hover { color: var(--action-hover); background: none; border-color: transparent; }
682 + .btn-link:active { box-shadow: none; }
635 683
636 684 /* .btn-loading: spinner-replacement state. Pair with disabled attr. */
637 685 .btn-loading {
@@ -645,9 +693,9 @@
645 693 top: 50%; left: 50%;
646 694 width: 0.875rem; height: 0.875rem;
647 695 margin: -0.4375rem 0 0 -0.4375rem; /* half the spinner, not a gap */
648 - border: 2px solid currentColor;
696 + border: 2px solid currentColor; /* spinner ring thickness, not a frame */
649 697 border-top-color: transparent;
650 - border-radius: 50%;
698 + border-radius: var(--radius-full);
651 699 animation: spin 0.6s linear infinite;
652 700 color: var(--content);
653 701 opacity: 0.6;
@@ -660,11 +708,11 @@
660 708 Canonical: .card. Charter target: F5 collapses .source-item,
661 709 .plugin-item, .sidebar-saved, .bookmark-item onto .card with the
662 710 modifier variants below.
663 - .card: default: white-ish surface, 2px border, 3px brutal shadow
664 - .card--row: row-shaped, no shadow, used in sidebars and lists
711 + .card: default: page surface, 1px border, raised bevel
712 + .card--row: row-shaped, no bevel, used in sidebars and lists
665 713 .card--shell: frame-only, no fill (for grouping)
666 714 .card--muted: secondary-surface variant
667 - .card--clickable: adds hover lift
715 + .card--clickable: pointer + frame recolor on hover, pressed bevel
668 716 Container: .cards-grid for grid layouts.
669 717 ========================================================================== */
670 718 .card {
@@ -672,7 +720,7 @@
672 720 border: var(--border-width) solid var(--border);
673 721 border-radius: var(--radius-sm);
674 722 padding: var(--gap-group);
675 - box-shadow: var(--shadow-brutal);
723 + box-shadow: var(--bevel-raised);
676 724 }
677 725 .card--row {
678 726 border-radius: 0;
@@ -694,12 +742,15 @@
694 742 }
695 743 .card--clickable {
696 744 cursor: pointer;
697 - transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
698 745 }
746 + /* Hover recolors the frame; it does not lift. The lift was the neobrute
747 + signal and the bevel replaces it, so a clickable card that also floated
748 + on hover would read as two objects. */
699 749 .card--clickable:hover {
700 750 border-color: var(--category-four);
701 - transform: translate(-1px, -1px);
702 - box-shadow: calc(var(--shadow-offset) + 1px) calc(var(--shadow-offset) + 1px) 0 var(--shadow-color);
751 + }
752 + .card--clickable:active {
753 + box-shadow: var(--bevel-inset);
703 754 }
704 755
705 756 .card-header {
@@ -794,7 +845,6 @@
794 845 keeps them visible. */
795 846 .row .row-actions:not(.always) {
796 847 opacity: 0;
797 - transition: opacity 0.15s;
798 848 }
799 849 .row:hover .row-actions:not(.always),
800 850 .row:focus-within .row-actions:not(.always) {
@@ -831,12 +881,12 @@
831 881 background-color: var(--surface-page);
832 882 color: var(--content);
833 883 font-size: 0.875rem;
834 - transition: border-color 0.2s, box-shadow 0.2s;
884 + box-shadow: var(--bevel-inset);
835 885 }
836 886 .form-input:focus {
837 887 outline: none;
838 888 border-color: var(--category-four);
839 - box-shadow: 0 0 0 2px var(--category-four);
889 + box-shadow: var(--bevel-inset), 0 0 0 2px var(--category-four);
840 890 }
841 891
842 892 /* F2 additions (2026-06-02): split .form-input into kind-specific
@@ -860,12 +910,12 @@
860 910 color: var(--content);
861 911 font-size: 0.875rem;
862 912 cursor: pointer;
863 - transition: border-color 0.2s, box-shadow 0.2s;
913 + box-shadow: var(--bevel-inset);
864 914 }
865 915 .form-select:focus {
866 916 outline: none;
867 917 border-color: var(--category-four);
868 - box-shadow: 0 0 0 2px var(--category-four);
918 + box-shadow: var(--bevel-inset), 0 0 0 2px var(--category-four);
869 919 }
870 920 .form-textarea {
871 921 width: 100%;
@@ -878,12 +928,12 @@
878 928 font-family: inherit;
879 929 resize: vertical;
880 930 min-height: 4rem;
881 - transition: border-color 0.2s, box-shadow 0.2s;
931 + box-shadow: var(--bevel-inset);
882 932 }
883 933 .form-textarea:focus {
884 934 outline: none;
885 935 border-color: var(--category-four);
886 - box-shadow: 0 0 0 2px var(--category-four);
936 + box-shadow: var(--bevel-inset), 0 0 0 2px var(--category-four);
887 937 }
888 938 /* .form-row: horizontal grouping of two related fields (e.g. label + button) */
889 939 .form-row {
@@ -927,7 +977,9 @@
927 977 max-width: 500px;
928 978 max-height: 80vh;
929 979 overflow-y: auto;
930 - box-shadow: 6px 6px 0 var(--shadow-color);
980 + /* Floating surfaces carry both: the bevel says the panel is a lit
981 + object, the drop shadow says it is above the page. */
982 + box-shadow: var(--bevel-raised), var(--shadow-brutal-lg);
931 983 }
932 984
933 985 .modal-header {
@@ -965,14 +1017,15 @@
965 1017 border-radius: var(--radius-sm);
966 1018 margin-bottom: var(--gap-peer);
967 1019 cursor: pointer;
968 - transition: all 0.2s;
969 1020 background-color: var(--surface-page);
1021 + box-shadow: var(--bevel-raised);
970 1022 }
971 1023 .plugin-item:hover {
972 1024 border-color: var(--category-four);
973 1025 background-color: var(--surface-overlay);
974 - box-shadow: var(--shadow-brutal);
975 - transform: translate(-1px, -1px);
1026 + }
1027 + .plugin-item:active {
1028 + box-shadow: var(--bevel-inset);
976 1029 }
977 1030
978 1031 /* F5 (2026-06-02): dropped .plugin-name and .plugin-desc; plugin-list
@@ -1089,7 +1142,7 @@
1089 1142 border-radius: var(--radius-sm);
1090 1143 padding: var(--gap-peer) var(--gap-group);
1091 1144 margin-top: var(--gap-peer);
1092 - box-shadow: var(--shadow-brutal);
1145 + box-shadow: var(--bevel-raised), var(--shadow-brutal);
1093 1146 animation: slideIn 0.3s ease-out;
1094 1147 font-weight: 500;
1095 1148 font-size: 0.875rem;
@@ -1150,6 +1203,7 @@
1150 1203 padding: var(--step-hair) var(--step-base);
1151 1204 font-size: 0.75rem;
1152 1205 color: var(--content-secondary);
1206 + box-shadow: var(--bevel-raised);
1153 1207 }
1154 1208
1155 1209 /* F2 additions (2026-06-02): color variants via [data-color] attribute.
@@ -1179,9 +1233,11 @@
1179 1233 color: var(--content-secondary);
1180 1234 line-height: 1.3;
1181 1235 white-space: nowrap;
1236 + box-shadow: var(--bevel-raised);
1182 1237 }
1183 1238 .badge--xs { font-size: 0.6rem; padding: 0 var(--step-snug); }
1184 1239 .badge--pill { border-radius: var(--radius-xl); }
1240 + .badge--filled { border: none; box-shadow: none; }
1185 1241 .badge--filled[data-color="green"] { background-color: var(--category-two); color: var(--content-on-action); }
1186 1242 .badge--filled[data-color="yellow"] { background-color: var(--category-four); color: var(--content); }
1187 1243 .badge--filled[data-color="red"] { background-color: var(--category-one); color: var(--content-on-action); }
@@ -1199,7 +1255,7 @@
1199 1255 white-space: pre-wrap;
1200 1256 background-color: var(--surface-sunken);
1201 1257 padding: var(--gap-peer);
1202 - border-radius: 4px;
1258 + border-radius: var(--radius-sm);
1203 1259 overflow-x: auto;
1204 1260 }
1205 1261
@@ -1222,7 +1278,7 @@
1222 1278 background: var(--surface-page);
1223 1279 border: var(--border-width) solid var(--border);
1224 1280 border-radius: var(--radius-sm);
1225 - box-shadow: var(--shadow-brutal);
1281 + box-shadow: var(--bevel-raised), var(--shadow-brutal);
1226 1282 padding: var(--gap-bound);
1227 1283 min-width: 140px;
1228 1284 }
@@ -1250,7 +1306,7 @@
1250 1306 display: inline-block;
1251 1307 width: 8px;
1252 1308 height: 8px;
1253 - border-radius: 50%;
1309 + border-radius: var(--radius-full);
1254 1310 margin-right: var(--gap-bound);
1255 1311 flex-shrink: 0;
1256 1312 }
@@ -1301,14 +1357,14 @@
1301 1357 .skeleton-item {
1302 1358 display: flex;
1303 1359 padding: var(--gap-peer) var(--gap-section);
1304 - border-bottom: 1px solid var(--border);
1360 + border-bottom: var(--border-width) solid var(--border);
1305 1361 animation: shimmer 1.5s infinite;
1306 1362 }
1307 1363 .skeleton-item .skeleton-indicators { width: 24px; margin-right: var(--gap-bound); }
1308 1364 .skeleton-item .skeleton-content { flex: 1; }
1309 1365 .skeleton-line {
1310 1366 height: 0.75rem;
1311 - border-radius: 4px;
1367 + border-radius: var(--radius-sm);
1312 1368 background: linear-gradient(90deg, var(--surface-sunken) 25%, var(--border) 50%, var(--surface-sunken) 75%);
1313 1369 background-size: 200% 100%;
1314 1370 animation: shimmer 1.5s infinite;
@@ -1330,12 +1386,15 @@
1330 1386 right: 0;
1331 1387 height: 3px;
1332 1388 background-color: var(--border);
1389 + box-shadow: var(--bevel-inset);
1333 1390 overflow: hidden;
1334 1391 }
1392 + /* Kept motion: the fill moves through space, which is period-correct. State
1393 + feedback that only recolors is not, and was dropped across the file. */
1335 1394 .progress-bar {
1336 1395 height: 100%;
1337 1396 background-color: var(--category-four);
1338 - transition: width 0.3s ease;
1397 + transition: width var(--transition-slow) ease;
1339 1398 }
1340 1399
1341 1400 /* Toast with action */
@@ -1343,8 +1402,8 @@
1343 1402 display: inline-block;
1344 1403 margin-left: var(--gap-bound);
1345 1404 background: none;
1346 - border: 1px solid currentColor;
1347 - border-radius: 4px;
1405 + border: var(--border-width) solid currentColor;
1406 + border-radius: var(--radius-sm);
1348 1407 color: inherit;
1349 1408 cursor: pointer;
1350 1409 padding: var(--step-hair) var(--step-base);
@@ -1379,7 +1438,7 @@
1379 1438 padding: var(--gap-peer) var(--gap-group);
1380 1439 z-index: 1200;
1381 1440 max-width: 320px;
1382 - box-shadow: var(--shadow-brutal);
1441 + box-shadow: var(--bevel-raised), var(--shadow-brutal);
1383 1442 font-size: 0.875rem;
1384 1443 }
1385 1444
@@ -1393,7 +1452,7 @@
1393 1452 background: var(--surface-overlay);
1394 1453 border: var(--border-width) solid var(--border);
1395 1454 border-radius: var(--radius-sm);
1396 - box-shadow: var(--shadow-brutal);
1455 + box-shadow: var(--bevel-raised), var(--shadow-brutal);
1397 1456 padding: var(--gap-group);
1398 1457 min-width: 220px;
1399 1458 max-width: 320px;
@@ -1403,7 +1462,7 @@
1403 1462 font-weight: 700;
1404 1463 margin-bottom: var(--gap-peer);
1405 1464 padding-bottom: var(--gap-peer);
1406 - border-bottom: 1px solid var(--border);
1465 + border-bottom: var(--border-width) solid var(--border);
1407 1466 }
1408 1467 .hp-row {
1409 1468 display: flex;
@@ -1456,8 +1515,8 @@
1456 1515 align-items: center;
1457 1516 padding: var(--gap-peer) var(--gap-group);
1458 1517 cursor: pointer;
1459 - transition: background-color 0.15s;
1460 1518 min-height: 2.5rem;
1519 + /* 3px, wider than a frame on purpose: this edge is the state marker. */
1461 1520 border-left: 3px solid transparent;
1462 1521 }
1463 1522 .source-item:hover { background-color: var(--surface-sunken); }
@@ -1488,6 +1547,7 @@
1488 1547 font-size: 0.7rem;
1489 1548 color: var(--content-muted);
1490 1549 border: var(--border-width) solid var(--border);
1550 + box-shadow: var(--bevel-raised);
1491 1551 min-width: 1.8rem;
1492 1552 text-align: center;
1493 1553 }
@@ -1526,15 +1586,17 @@
1526 1586 font-size: 0.7rem;
1527 1587 color: var(--content-secondary);
1528 1588 cursor: pointer;
1529 - transition: all 0.15s;
1530 1589 white-space: nowrap;
1590 + box-shadow: var(--bevel-raised);
1531 1591 }
1532 1592 .tag-chip:hover { border-color: var(--category-four); color: var(--content); }
1593 + /* An active filter chip is latched down, same inversion as a pressed button. */
1533 1594 .tag-chip.active {
1534 1595 background-color: var(--category-four);
1535 1596 border-color: var(--category-four);
1536 1597 color: var(--content);
1537 1598 font-weight: 600;
1599 + box-shadow: var(--bevel-inset);
1538 1600 }
1539 1601
1540 1602 /* Source tag chips (small pills under source name) */
@@ -1655,7 +1717,7 @@
1655 1717 gap: var(--gap-peer);
1656 1718 margin-top: var(--gap-section);
1657 1719 padding-top: var(--gap-section);
1658 - border-top: 1px solid var(--border);
1720 + border-top: var(--border-width) solid var(--border);
1659 1721 }
1660 1722
1661 1723 /* === 24. Sidebar saved articles ======================================== */
@@ -1666,8 +1728,8 @@
1666 1728 padding: var(--gap-peer) var(--gap-group);
1667 1729 cursor: pointer;
1668 1730 border-top: var(--border-width) solid var(--border);
1731 + /* 3px, wider than a frame on purpose: this edge is the state marker. */
1669 1732 border-left: 3px solid transparent;
1670 - transition: background-color 0.15s;
Lines truncated