Skip to main content

max / balanced_breakfast

64.9 KB · 2102 lines History Blame Raw
1 /* ============================================================================
2 Balanced Breakfast — Neobrute-lite Theme
3
4 Build new UI by COMPOSING the existing vocabulary, in this order of preference:
5
6 1. Use a UTILITY class for one-off adjustments (.hidden, .sr-only,
7 .mb-1, .flex-1 — most
8 still to be added)
9 2. Use a LAYOUT PRIMITIVE to position content (.row-flex, .row,
10 .stack-{2,3,4} —
11 to be added in F1/F2)
12 3. Use a COMPONENT PRIMITIVE for a UI element (.btn, .card, .modal,
13 .form-input,
14 .form-select, .badge,
15 .tag, .toast)
16 4. Extend a primitive with a modifier (.btn-primary, .btn-sm,
17 .form-select--ghost)
18 5. ONLY THEN consider a new class — and add it to the right
19 BAND below
20
21 A new class is a smell, not a goal. Before writing one:
22 • grep this file for the visual shape you want; almost everything is here
23 • check whether an existing primitive + modifier composes to your shape
24 • page-scoped rules under a feature class (`.bookmarks .foo`) are a last
25 resort, not a first move
26
27 NAMED-LAYOUT EXCEPTION: A row of mixed-intent elements (one fills, others
28 size to content) belongs in a NAMED LAYOUT with an explicit
29 `grid-template-columns`, not in `.row-flex` + utilities. Smell test: if you
30 find yourself reaching for `--compact`, `style="width: auto;"`, or
31 `min-width: 0 !important` to make a row lay out right, the layout itself
32 wants a name. See `.condition-row` for the canonical form.
33
34 See `_private/docs/balanced_breakfast/design-system.md` for the primitive
35 inventory and rules; `styleguide.md` for visual specs.
36
37 ----------------------------------------------------------------------------
38 FILE STRUCTURE — search by BAND name (uppercase anchors) to navigate.
39 Per-section titles use `=== N. Title ===` headers; numbered for stability,
40 not because order matters. Bands group related sections; cascade order is
41 load-bearing only inside the RESPONSIVE LAYERS and TOUCH OVERRIDES bands.
42
43 Note (2026-06-02): the BAND map below is the TARGET layout. Today's file
44 still has rules grouped by their original feature-add order. Phase F5 (dedup
45 sweep) moves rules into the correct BAND. Until then, the numbered section
46 markers below are stable anchors regardless of file position.
47
48 BAND: FOUNDATIONS
49 1. Design System Variables — tokens (:root)
50 2. CSS Reset & Base
51 3. Keyframes & Animation
52
53 BAND: APP SHELL & CHROME
54 4. Layout shell — #app, .main
55 5. Header
56 6. Sidebar
57 7. Items panel
58 8. Detail panel
59
60 BAND: COMPONENT PRIMITIVES
61 9. Buttons — .btn family
62 10. Forms — .form-group, .form-input
63 11. Modal — .modal-overlay/.modal-content
64 12. Toast — .toast
65 13. Tag — .tag
66 14. Context menu — .context-menu
67 15. Health indicator — .health-dot (state-by-color, F3 fix pending)
68 16. Skeleton & loading — .skeleton-item, .skeleton-line, spinner
69 17. Progress bar — .progress-bar-container
70 18. Update banner — .update-banner
71 19. Health popover — .health-popover
72
73 BAND: FEATURE SURFACES
74 20. Sources list / sidebar entries
75 21. Items list / read indicator / star button
76 22. Reader-expanded mode
77 23. Query feeds (builder + sidebar)
78 24. Sidebar saved articles
79 25. Plugin list
80 26. Help shortcuts
81 27. Welcome modal
82 28. Settings modal
83 29. Sync settings (utility classes)
84 30. Reading list / Bookmarks
85
86 BAND: UTILITIES
87 31. Screen-reader only (.sr-only)
88 32. Focus indicators
89
90 BAND: MOBILE PRIMITIVES
91 33. Mobile tab bar
92 34. Mobile more-popover
93 35. Mobile search bar
94 36. Mobile back button
95 37. Pull-to-refresh indicator
96 38. Source-badge on items (mobile-only display)
97
98 BAND: RESPONSIVE LAYERS — cascade-ordered
99 39. @media (max-width: 768px) — mobile layout
100 40. @media (hover: none) — touch overrides
101 41. @media (prefers-reduced-motion: reduce)
102
103 BAND: SCROLLBAR
104 42. Webkit scrollbar
105 ============================================================================ */
106
107 /* === 1. Design System Variables =========================================
108 Color tokens are themed via `js/themes.js` (loads shared TOML palettes
109 from MNW/shared/themes/). The fallback hex values below are the
110 `flatwhite` light theme palette, used only for initial render before
111 `themes.js` runs. Annotation legend:
112
113 themed — set by themes.js COLOR_MAP from TOML
114 derived — computed in themes.js applyTheme() from a themed token
115 invariant — fixed; never changes with theme
116 unused — declared but no CSS rule consumes it; see design-system.md
117 token audit. Phase F1 cleanup: consume or remove.
118 ========================================================================== */
119 :root {
120 --bg-primary: #faf8f5; /* themed: background.primary */
121 --bg-secondary: #f5f0e8; /* themed: background.secondary */
122 --bg-tertiary: #ebe4d8; /* themed: background.tertiary */
123 --text-primary: #3d3225; /* themed: foreground.primary */
124 --text-secondary: #6b5d4d; /* themed: foreground.secondary */
125 --text-muted: #9a8b78; /* themed: foreground.muted */
126 --accent-red: #c94b4b; /* themed: accent.red */
127 --accent-red-hover: #d65d5d; /* derived in themes.js — consumed by .btn-danger:hover (F2) */
128 --accent-green: #6b9b5a; /* themed: accent.green */
129 --accent-blue: #4a8ebd; /* themed: accent.blue */
130 --accent-blue-hover: #5e9dca; /* derived in themes.js */
131 --accent-yellow: #e8a841; /* themed: accent.yellow */
132 --accent-yellow-light: #f4c56d; /* derived in themes.js */
133 --accent-purple: #8b6bbf; /* themed: accent.purple — consumed by .tag/.badge[data-color="purple"] (F2) */
134 --accent-cyan: #5ab5b5; /* themed: accent.cyan — consumed by .tag/.badge[data-color="cyan"] (F2) */
135 --border: #e0d6c8; /* themed: border.default */
136 --border-dark: #d4c8b5; /* derived in themes.js */
137 --text-on-accent: #ffffff; /* derived in themes.js (contrast vs accent.blue) */
138 --border-width: 2px; /* invariant */
139 --radius-sm: 5px; /* invariant */
140 --radius-lg: 10px; /* invariant */
141 --radius-xl: 20px; /* invariant */
142 --shadow-offset: 3px; /* invariant — BB neobrute signature */
143 --shadow-color: #6b5d4d; /* derived in themes.js (darken --text-muted) */
144 --shadow-brutal: var(--shadow-offset) var(--shadow-offset) 0 var(--shadow-color); /* invariant composition */
145 }
146 /* F5 cleanup (2026-06-02): dropped --bg-surface (themed but never consumed),
147 --radius-md (duplicate of --radius-sm), --shadow / --shadow-hover (derived
148 but never consumed). themes.js still sets --bg-surface / --shadow / --shadow-hover
149 in applyTheme — these will be removed in the F5 themes.js cleanup. */
150
151 /* === 2. CSS Reset & Base ================================================ */
152 * { margin: 0; padding: 0; box-sizing: border-box; }
153
154 /* === 4. Layout shell — body & #app ==================================== */
155 body {
156 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
157 background-color: var(--bg-primary);
158 color: var(--text-primary);
159 line-height: 1.5;
160 overflow: hidden;
161 }
162
163 #app {
164 display: flex;
165 flex-direction: column;
166 height: 100vh;
167 }
168
169 /* === 5. Header ========================================================== */
170 .header {
171 display: flex;
172 justify-content: space-between;
173 align-items: center;
174 padding: 0.75rem 1.25rem;
175 background-color: var(--bg-secondary);
176 border-bottom: var(--border-width) solid var(--border);
177 box-shadow: 0 var(--shadow-offset) 0 var(--shadow-color);
178 -webkit-user-select: none;
179 user-select: none;
180 position: relative;
181 }
182
183 .header h1 {
184 font-size: 1.25rem;
185 font-weight: 700;
186 color: var(--accent-blue);
187 letter-spacing: -0.5px;
188 }
189
190 .header-actions {
191 display: flex;
192 gap: 0.5rem;
193 align-items: center;
194 }
195
196 .search-input {
197 padding: 0.4rem 0.75rem;
198 border: var(--border-width) solid var(--border);
199 border-radius: var(--radius-sm);
200 background-color: var(--bg-primary);
201 color: var(--text-primary);
202 width: 180px;
203 font-size: 0.875rem;
204 transition: border-color 0.2s, box-shadow 0.2s;
205 }
206
207 /* Search spinner */
208 .search-spinner {
209 display: none;
210 width: 14px;
211 height: 14px;
212 border: 2px solid var(--border);
213 border-top-color: var(--accent-yellow);
214 border-radius: 50%;
215 animation: spin 0.6s linear infinite;
216 }
217 .search-spinner.active { display: inline-block; }
218
219 @keyframes spin {
220 to { transform: rotate(360deg); }
221 }
222
223 .search-input::placeholder { color: var(--text-muted); }
224 .search-input:focus {
225 outline: none;
226 border-color: var(--accent-yellow);
227 box-shadow: 0 0 0 2px var(--accent-yellow);
228 }
229
230 .sort-select {
231 padding: 0.4rem 0.75rem;
232 border: var(--border-width) solid var(--border);
233 border-radius: var(--radius-sm);
234 background-color: var(--bg-primary);
235 color: var(--text-primary);
236 cursor: pointer;
237 font-size: 0.875rem;
238 }
239 .sort-select:focus { outline: none; border-color: var(--accent-yellow); }
240
241 /* === 9. Buttons ========================================================
242 Canonical: .btn (+ .btn-primary, .btn-success, .btn-small).
243 Charter target: collapse bespoke *-btn classes (.tag-filter-btn,
244 .toast-action, .hp-action, .mobile-tab, etc.) onto this family in F5.
245 ========================================================================== */
246 .btn {
247 padding: 0.4rem 0.75rem;
248 border: var(--border-width) solid var(--border);
249 border-radius: var(--radius-sm);
250 background-color: var(--bg-primary);
251 color: var(--text-primary);
252 cursor: pointer;
253 font-weight: 600;
254 font-size: 0.875rem;
255 transition: background-color 0.2s, border-color 0.2s;
256 }
257 .btn:hover {
258 background-color: var(--bg-tertiary);
259 border-color: var(--border-dark);
260 }
261 .btn-primary {
262 background-color: var(--accent-blue);
263 border-color: var(--accent-blue);
264 color: var(--text-on-accent);
265 }
266 .btn-primary:hover { background-color: var(--accent-blue-hover); border-color: var(--accent-blue-hover); }
267 .btn-success {
268 background-color: var(--accent-yellow);
269 border-color: var(--accent-yellow);
270 color: var(--text-on-accent);
271 }
272 .btn-success:hover { background-color: var(--accent-yellow-light); border-color: var(--accent-yellow-light); }
273 .btn-small { padding: 0.2rem 0.5rem; font-size: 0.8rem; }
274 .btn-small.active { background: var(--accent-yellow); color: var(--text-on-accent); border-color: var(--accent-yellow); }
275
276 /* F2 additions (2026-06-02) — see design-system.md §Button.
277 .btn-sm is the parity name; .btn-small kept as alias until F5 dedup. */
278 .btn-sm { padding: 0.2rem 0.5rem; font-size: 0.8rem; }
279 .btn-sm.active { background: var(--accent-yellow); color: var(--text-on-accent); border-color: var(--accent-yellow); }
280
281 /* .btn-secondary — explicit neutral; today's default `.btn` already plays this
282 role, but having a named modifier lets future surfaces declare intent. */
283 .btn-secondary {
284 background-color: var(--bg-secondary);
285 border-color: var(--border);
286 color: var(--text-primary);
287 }
288 .btn-secondary:hover { background-color: var(--bg-tertiary); border-color: var(--border-dark); }
289
290 /* .btn-danger — destructive action. Consumes the previously-unused
291 --accent-red-hover token. Replaces .hp-action-danger in F5. */
292 .btn-danger {
293 background-color: var(--accent-red);
294 border-color: var(--accent-red);
295 color: var(--text-on-accent);
296 }
297 .btn-danger:hover { background-color: var(--accent-red-hover); border-color: var(--accent-red-hover); }
298
299 /* .btn-icon — square, no padding label space; used for close buttons,
300 icon-only toolbar buttons (gear, star, more). */
301 .btn-icon {
302 padding: 0.25rem;
303 line-height: 1;
304 min-width: 1.75rem;
305 min-height: 1.75rem;
306 display: inline-flex;
307 align-items: center;
308 justify-content: center;
309 }
310
311 /* .btn-text — borderless, no background. Used for inline actions inside
312 text or as a low-emphasis alternative to .btn-secondary. */
313 .btn-text {
314 background: none;
315 border: none;
316 padding: 0.25rem 0.5rem;
317 color: var(--text-primary);
318 }
319 .btn-text:hover { background-color: var(--bg-secondary); border-color: transparent; }
320
321 /* .btn-link — looks like an anchor, behaves like a button. */
322 .btn-link {
323 background: none;
324 border: none;
325 padding: 0;
326 color: var(--accent-blue);
327 font-weight: 600;
328 text-decoration: underline;
329 text-underline-offset: 2px;
330 }
331 .btn-link:hover { color: var(--accent-blue-hover); background: none; border-color: transparent; }
332
333 /* .btn-loading — spinner-replacement state. Pair with disabled attr. */
334 .btn-loading {
335 color: transparent !important;
336 pointer-events: none;
337 position: relative;
338 }
339 .btn-loading::after {
340 content: '';
341 position: absolute;
342 top: 50%; left: 50%;
343 width: 0.875rem; height: 0.875rem;
344 margin: -0.4375rem 0 0 -0.4375rem;
345 border: 2px solid currentColor;
346 border-top-color: transparent;
347 border-radius: 50%;
348 animation: spin 0.6s linear infinite;
349 color: var(--text-primary);
350 opacity: 0.6;
351 }
352 .btn-primary.btn-loading::after,
353 .btn-success.btn-loading::after,
354 .btn-danger.btn-loading::after { color: var(--text-on-accent); opacity: 1; }
355
356 /* === N. Card (F2 addition, 2026-06-02) ================================
357 Canonical: .card. Charter target — F5 collapses .source-item,
358 .plugin-item, .sidebar-saved, .bookmark-item onto .card with the
359 modifier variants below.
360 .card — default: white-ish surface, 2px border, 3px brutal shadow
361 .card--row — row-shaped, no shadow, used in sidebars and lists
362 .card--shell — frame-only, no fill (for grouping)
363 .card--muted — secondary-surface variant
364 .card--clickable — adds hover lift
365 Container: .cards-grid for grid layouts.
366 ========================================================================== */
367 .card {
368 background-color: var(--bg-primary);
369 border: var(--border-width) solid var(--border);
370 border-radius: var(--radius-sm);
371 padding: 0.75rem;
372 box-shadow: var(--shadow-brutal);
373 }
374 .card--row {
375 border-radius: 0;
376 border-left: 0;
377 border-right: 0;
378 border-top: 0;
379 box-shadow: none;
380 padding: 0.6rem 1rem;
381 display: flex;
382 align-items: center;
383 gap: 0.75rem;
384 }
385 .card--shell {
386 background: none;
387 box-shadow: none;
388 }
389 .card--muted {
390 background-color: var(--bg-secondary);
391 }
392 .card--clickable {
393 cursor: pointer;
394 transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
395 }
396 .card--clickable:hover {
397 border-color: var(--accent-yellow);
398 transform: translate(-1px, -1px);
399 box-shadow: calc(var(--shadow-offset) + 1px) calc(var(--shadow-offset) + 1px) 0 var(--shadow-color);
400 }
401
402 .card-header {
403 display: flex;
404 justify-content: space-between;
405 align-items: center;
406 margin-bottom: 0.5rem;
407 }
408 .card-title {
409 font-size: 0.95rem;
410 font-weight: 700;
411 color: var(--text-primary);
412 margin: 0;
413 }
414 .card-description {
415 font-size: 0.85rem;
416 color: var(--text-secondary);
417 line-height: 1.4;
418 }
419 .card-meta {
420 display: flex;
421 flex-wrap: wrap;
422 gap: 0.4rem;
423 margin-top: 0.5rem;
424 font-size: 0.75rem;
425 color: var(--text-muted);
426 }
427 .card-badge {
428 flex-shrink: 0;
429 }
430
431 .cards-grid {
432 display: grid;
433 grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
434 gap: 0.75rem;
435 }
436
437 /* === N. Row primitive (F2-sub addition, 2026-06-02) ===================
438 Canonical slot layout: [icon] [primary · badges] [meta] [actions].
439 Used via BB.ui.renderRow(model). Per-surface classes (.plugin-item,
440 .source-item, .item, .bookmark-item, etc.) sit alongside .row on the
441 outer element and add their own padding / border / hover treatment.
442 F5 retires per-surface name/desc classes (.plugin-name, .plugin-desc)
443 that duplicate .row-primary / .row-secondary.
444 ========================================================================== */
445 .row {
446 display: flex;
447 align-items: flex-start;
448 gap: 0.75rem;
449 min-width: 0;
450 }
451 .row-icon {
452 flex-shrink: 0;
453 display: flex;
454 align-items: center;
455 justify-content: center;
456 }
457 .row-content {
458 flex: 1;
459 min-width: 0;
460 }
461 .row-primary {
462 font-weight: 600;
463 color: var(--text-primary);
464 font-size: 0.9rem;
465 line-height: 1.3;
466 display: flex;
467 align-items: center;
468 gap: 0.4rem;
469 flex-wrap: wrap;
470 }
471 .row-secondary {
472 font-size: 0.8rem;
473 color: var(--text-secondary);
474 line-height: 1.4;
475 margin-top: 0.15rem;
476 }
477 .row-meta {
478 flex-shrink: 0;
479 font-size: 0.75rem;
480 color: var(--text-muted);
481 align-self: center;
482 }
483 .row-actions {
484 flex-shrink: 0;
485 display: flex;
486 align-items: center;
487 gap: 0.25rem;
488 }
489 /* Hover-reveal pattern: actions appear when the row (or any descendant)
490 is hovered or focused. Per-surface override with `.row-actions.always`
491 keeps them visible. */
492 .row .row-actions:not(.always) {
493 opacity: 0;
494 transition: opacity 0.15s;
495 }
496 .row:hover .row-actions:not(.always),
497 .row:focus-within .row-actions:not(.always) {
498 opacity: 1;
499 }
500
501 /* F2-sub addition: form-hint error variant (used by renderFormField when
502 passed a `field.error`). */
503 .form-hint--error {
504 color: var(--accent-red);
505 font-weight: 600;
506 }
507
508 /* === 4. Layout shell — main =========================================== */
509 .main {
510 display: flex;
511 flex: 1;
512 overflow: hidden;
513 }
514
515 /* === 6. Sidebar ======================================================== */
516 .sidebar {
517 width: 220px;
518 min-width: 220px;
519 background-color: var(--bg-secondary);
520 border-right: var(--border-width) solid var(--border);
521 display: flex;
522 flex-direction: column;
523 overflow-y: auto;
524 scrollbar-gutter: stable;
525 }
526
527 .sidebar h2 {
528 padding: 0.75rem 1rem;
529 font-size: 0.75rem;
530 font-weight: 700;
531 color: var(--text-muted);
532 text-transform: uppercase;
533 letter-spacing: 0.5px;
534 border-bottom: var(--border-width) solid var(--border);
535 }
536
537 /* === 20. Sources list — sidebar entries ================================
538 Charter target (F5): .source-item collapses onto .card --row.
539 ========================================================================== */
540 .sources-list { list-style: none; padding: 0.25rem 0; }
541
542 .source-item {
543 display: flex;
544 justify-content: space-between;
545 align-items: center;
546 padding: 0.6rem 1rem;
547 cursor: pointer;
548 transition: background-color 0.15s;
549 min-height: 2.5rem;
550 border-left: 3px solid transparent;
551 }
552 .source-item:hover { background-color: var(--bg-tertiary); }
553 .source-item.active {
554 background-color: var(--bg-tertiary);
555 border-left-color: var(--accent-yellow);
556 }
557
558 .source-name {
559 flex: 1;
560 white-space: nowrap;
561 overflow: hidden;
562 text-overflow: ellipsis;
563 font-weight: 600;
564 font-size: 0.875rem;
565 }
566
567 .source-actions {
568 display: flex;
569 align-items: center;
570 gap: 0.3rem;
571 }
572
573 .source-count {
574 background-color: var(--bg-primary);
575 padding: 0.1rem 0.4rem;
576 border-radius: var(--radius-xl);
577 font-size: 0.7rem;
578 color: var(--text-muted);
579 border: var(--border-width) solid var(--border);
580 min-width: 1.8rem;
581 text-align: center;
582 }
583 .source-count.all-read {
584 color: var(--accent-green);
585 border-color: var(--accent-green);
586 }
587
588 .source-delete {
589 display: none;
590 background: none;
591 border: none;
592 color: var(--text-muted);
593 cursor: pointer;
594 font-size: 0.9rem;
595 padding: 0 0.2rem;
596 line-height: 1;
597 }
598 .source-delete:hover { color: var(--accent-red); }
599 .source-item:hover .source-delete { display: inline; }
600
601 /* === 7. Items panel ==================================================== */
602 .items-panel {
603 flex: 1;
604 display: flex;
605 flex-direction: column;
606 overflow-y: auto;
607 background-color: var(--bg-primary);
608 }
609
610 .items-list { list-style: none; }
611
612 .item {
613 display: flex;
614 padding: 0.75rem 1.25rem;
615 border-bottom: 1px solid var(--border);
616 border-left: 3px solid transparent;
617 cursor: pointer;
618 transition: background-color 0.15s;
619 }
620 .item:hover { background-color: var(--bg-secondary); }
621 .item.unread {
622 border-left-color: var(--accent-red);
623 background-color: color-mix(in srgb, var(--accent-red) 6%, var(--bg-primary));
624 }
625 .item.read { color: var(--text-secondary); }
626 .item.selected {
627 background-color: var(--bg-tertiary);
628 border-left-color: var(--accent-yellow);
629 }
630 .item.unread.selected {
631 border-left-color: var(--accent-red);
632 background-color: color-mix(in srgb, var(--accent-red) 12%, var(--bg-primary));
633 }
634 .item.empty-state {
635 text-align: center;
636 color: var(--text-muted);
637 padding: 3rem 2rem;
638 cursor: default;
639 line-height: 1.6;
640 flex-direction: column;
641 align-items: center;
642 }
643 .empty-icon {
644 font-size: 2.5rem;
645 margin-bottom: 0.75rem;
646 opacity: 0.6;
647 }
648
649 /* F2 addition (2026-06-02) — .empty-state as its own block. Today's
650 .item.empty-state (modifier-on-row) stays as legacy until F5 retires
651 call sites. Use this block + BB.ui.renderEmptyState() for new surfaces.
652 <div class="empty-state">
653 <div class="empty-state-icon">...</div>
654 <p class="empty-state-text">No items</p>
655 <button class="btn btn-primary">Add one</button>
656 </div>
657 */
658 .empty-state {
659 display: flex;
660 flex-direction: column;
661 align-items: center;
662 justify-content: center;
663 text-align: center;
664 padding: 3rem 2rem;
665 color: var(--text-muted);
666 line-height: 1.6;
667 gap: 0.75rem;
668 }
669 .empty-state-icon {
670 font-size: 2.5rem;
671 opacity: 0.6;
672 }
673 .empty-state-text {
674 font-size: 0.9rem;
675 color: var(--text-secondary);
676 margin: 0;
677 }
678 .empty-state--compact {
679 padding: 1.5rem 1rem;
680 gap: 0.5rem;
681 }
682 .empty-state--compact .empty-state-icon { font-size: 1.5rem; }
683
684 .item-indicators {
685 display: flex;
686 flex-direction: column;
687 align-items: center;
688 margin-right: 0.75rem;
689 min-width: 24px;
690 }
691
692 .star-btn {
693 background: none;
694 border: none;
695 cursor: pointer;
696 font-size: 1.1rem;
697 color: var(--text-muted);
698 transition: color 0.2s, transform 0.15s;
699 padding: 0.25rem 0.4rem;
700 margin: -0.25rem -0.4rem;
701 line-height: 1;
702 }
703 .star-btn:hover { color: var(--accent-yellow); transform: scale(1.1); }
704 .star-btn.starred { color: var(--accent-yellow); }
705
706 .read-indicator {
707 width: 7px;
708 height: 7px;
709 border-radius: 50%;
710 margin-top: 0.4rem;
711 }
712 .read-indicator.unread { background-color: var(--accent-red); }
713
714 .item-content { flex: 1; min-width: 0; }
715
716 .item-header {
717 display: flex;
718 justify-content: space-between;
719 align-items: flex-start;
720 margin-bottom: 0.15rem;
721 }
722
723 .item-author {
724 font-weight: 700;
725 color: var(--accent-red);
726 font-size: 0.8rem;
727 }
728
729 .item-time { color: var(--text-muted); font-size: 0.7rem; }
730
731 .item-text {
732 color: var(--text-primary);
733 display: -webkit-box;
734 -webkit-line-clamp: 2;
735 -webkit-box-orient: vertical;
736 overflow: hidden;
737 line-height: 1.4;
738 font-size: 0.875rem;
739 }
740
741 .item-secondary {
742 color: var(--text-secondary);
743 font-size: 0.8rem;
744 margin-top: 0.15rem;
745 }
746
747 .load-more { padding: 1rem; text-align: center; }
748
749 /* === 22. Reader-expanded mode =========================================
750 Hides items panel and expands detail to full width. */
751 .main.reader-expanded .items-panel {
752 display: none;
753 }
754
755 .main.reader-expanded .detail-panel {
756 flex: 1;
757 width: auto;
758 min-width: 0;
759 border-left: none;
760 }
761
762 .main.reader-expanded .item-detail {
763 max-width: 800px;
764 margin: 0 auto;
765 }
766
767 .reader-back-btn {
768 margin-right: auto;
769 }
770
771 /* === 8. Detail panel =================================================== */
772 .detail-panel {
773 width: 400px;
774 min-width: 400px;
775 background-color: var(--bg-secondary);
776 border-left: var(--border-width) solid var(--border);
777 display: none;
778 flex-direction: column;
779 overflow-y: auto;
780 }
781
782 .detail-header {
783 display: flex;
784 justify-content: flex-end;
785 gap: 0.3rem;
786 padding: 0.4rem;
787 border-bottom: var(--border-width) solid var(--border);
788 background-color: var(--bg-tertiary);
789 }
790
791 .item-detail { padding: 1.25rem; }
792
793 .detail-title {
794 font-size: 1.15rem;
795 margin-bottom: 0.75rem;
796 color: var(--text-primary);
797 font-weight: 700;
798 line-height: 1.4;
799 }
800
801 .detail-meta {
802 display: flex;
803 flex-wrap: wrap;
804 gap: 0.6rem;
805 margin-bottom: 0.75rem;
806 color: var(--text-secondary);
807 font-size: 0.8rem;
808 padding-bottom: 0.75rem;
809 border-bottom: var(--border-width) solid var(--border);
810 }
811
812 .detail-tags {
813 display: flex;
814 flex-wrap: wrap;
815 gap: 0.4rem;
816 margin-bottom: 0.75rem;
817 }
818
819 /* === 13. Tag ===========================================================
820 Canonical: .tag — generic chip used in detail panel.
821 Charter target (F5): add [data-color] variants; collapse .tag-chip,
822 .tag-filter-btn, .source-tag-chip, .bookmark-tag onto this primitive.
823 ========================================================================== */
824 .tag {
825 background-color: var(--bg-tertiary);
826 border: var(--border-width) solid var(--border);
827 border-radius: var(--radius-sm);
828 padding: 0.1rem 0.5rem;
829 font-size: 0.75rem;
830 color: var(--text-secondary);
831 }
832
833 /* F2 additions (2026-06-02) — color variants via [data-color] attribute.
834 Used for both .tag and .badge so the same color contract works for both.
835 Consumes previously-unused --accent-cyan and --accent-purple. */
836 .tag[data-color="green"], .badge[data-color="green"] { border-color: var(--accent-green); color: var(--accent-green); background-color: color-mix(in srgb, var(--accent-green) 10%, transparent); }
837 .tag[data-color="yellow"], .badge[data-color="yellow"] { border-color: var(--accent-yellow); color: var(--text-primary); background-color: color-mix(in srgb, var(--accent-yellow) 12%, transparent); }
838 .tag[data-color="red"], .badge[data-color="red"] { border-color: var(--accent-red); color: var(--accent-red); background-color: color-mix(in srgb, var(--accent-red) 10%, transparent); }
839 .tag[data-color="blue"], .badge[data-color="blue"] { border-color: var(--accent-blue); color: var(--accent-blue); background-color: color-mix(in srgb, var(--accent-blue) 10%, transparent); }
840 .tag[data-color="cyan"], .badge[data-color="cyan"] { border-color: var(--accent-cyan); color: var(--accent-cyan); background-color: color-mix(in srgb, var(--accent-cyan) 10%, transparent); }
841 .tag[data-color="purple"], .badge[data-color="purple"] { border-color: var(--accent-purple); color: var(--accent-purple); background-color: color-mix(in srgb, var(--accent-purple) 10%, transparent); }
842 .tag[data-color="muted"], .badge[data-color="muted"] { border-color: var(--border); color: var(--text-muted); background-color: var(--bg-tertiary); }
843
844 /* .badge — smaller and more compact than .tag; used for counts and inline
845 status indicators. Same color contract via [data-color]. */
846 .badge {
847 display: inline-flex;
848 align-items: center;
849 gap: 0.2rem;
850 background-color: var(--bg-tertiary);
851 border: var(--border-width) solid var(--border);
852 border-radius: var(--radius-sm);
853 padding: 0.05rem 0.4rem;
854 font-size: 0.7rem;
855 font-weight: 600;
856 color: var(--text-secondary);
857 line-height: 1.3;
858 white-space: nowrap;
859 }
860 .badge--xs { font-size: 0.6rem; padding: 0 0.3rem; }
861 .badge--pill { border-radius: var(--radius-xl); }
862 .badge--filled[data-color="green"] { background-color: var(--accent-green); color: var(--text-on-accent); }
863 .badge--filled[data-color="yellow"] { background-color: var(--accent-yellow); color: var(--text-primary); }
864 .badge--filled[data-color="red"] { background-color: var(--accent-red); color: var(--text-on-accent); }
865 .badge--filled[data-color="blue"] { background-color: var(--accent-blue); color: var(--text-on-accent); }
866
867 .detail-body {
868 color: var(--text-primary);
869 line-height: 1.7;
870 font-size: 0.9rem;
871 overflow-wrap: break-word;
872 word-break: break-word;
873 }
874 .detail-body p { margin-bottom: 0.5em; }
875 .detail-body pre {
876 white-space: pre-wrap;
877 background-color: var(--bg-tertiary);
878 padding: 0.5rem;
879 border-radius: 4px;
880 overflow-x: auto;
881 }
882
883 .detail-actions {
884 display: flex;
885 gap: 0.5rem;
886 margin-top: 1.25rem;
887 padding-top: 0.75rem;
888 border-top: var(--border-width) solid var(--border);
889 }
890
891 /* === 11. Modal =========================================================
892 Canonical: .modal-overlay (single global at #modal-overlay) +
893 .modal-content (will rename to .modal-container for parity in F5).
894 .tag block (13. Tag) lives below this block in file order.
895 ========================================================================== */
896 .modal-overlay {
897 position: fixed;
898 top: 0; left: 0; right: 0; bottom: 0;
899 background-color: color-mix(in srgb, var(--text-primary) 40%, transparent);
900 display: none;
901 align-items: center;
902 justify-content: center;
903 z-index: 1000;
904 }
905
906 .modal-content {
907 background-color: var(--bg-primary);
908 border-radius: var(--radius-lg);
909 width: 90%;
910 max-width: 500px;
911 max-height: 80vh;
912 overflow-y: auto;
913 box-shadow: 6px 6px 0 var(--shadow-color);
914 }
915
916 .modal-header {
917 display: flex;
918 justify-content: space-between;
919 align-items: center;
920 padding: 0.75rem 1.25rem;
921 border-bottom: var(--border-width) solid var(--border);
922 background-color: var(--bg-secondary);
923 border-radius: var(--radius-lg) var(--radius-lg) 0 0;
924 }
925 .modal-header h2 { font-size: 1.1rem; font-weight: 700; color: var(--text-primary); }
926
927 /* F3 (2026-06-02): step indicator for multi-step modal flows (OAuth,
928 plugin import, encryption setup). Set via BB.ui.setModalStep(n, of). */
929 .modal-step-indicator {
930 margin-left: 0.75rem;
931 padding: 0.1rem 0.5rem;
932 background-color: var(--bg-tertiary);
933 border: var(--border-width) solid var(--border);
934 border-radius: var(--radius-xl);
935 font-size: 0.7rem;
936 font-weight: 600;
937 color: var(--text-muted);
938 white-space: nowrap;
939 }
940
941 .modal-body { padding: 1.25rem; }
942
943 .plugin-list { list-style: none; }
944
945 .plugin-item {
946 padding: 0.75rem;
947 border: var(--border-width) solid var(--border);
948 border-radius: var(--radius-sm);
949 margin-bottom: 0.5rem;
950 cursor: pointer;
951 transition: all 0.2s;
952 background-color: var(--bg-primary);
953 }
954 .plugin-item:hover {
955 border-color: var(--accent-yellow);
956 background-color: var(--bg-secondary);
957 box-shadow: var(--shadow-brutal);
958 transform: translate(-1px, -1px);
959 }
960
961 /* F5 (2026-06-02): dropped .plugin-name and .plugin-desc — plugin-list
962 migrated to BB.ui.renderRow in F2-sub; .row-primary / .row-secondary
963 carry the typography now. */
964
965 /* F2-sub (2026-06-02): intro paragraph at the top of multi-step modals
966 (replaces inline `style="margin-bottom:1rem;color:var(--text-secondary)"`
967 in feeds.js plugin picker). */
968 .modal-intro {
969 margin-bottom: 1rem;
970 color: var(--text-secondary);
971 font-size: 0.875rem;
972 }
973
974 /* F4 additions (2026-06-02) — replace inline styles previously hard-coded
975 in JS render paths. Each rule below retires a specific style="..." or
976 style.cssText violation flagged in the F4 catalogue. */
977
978 /* sources.js empty-state (no feeds yet). Was inline cssText + emoji
979 wrapper. F5 may further consolidate onto the .empty-state block once
980 the row primitive migration lands; this fix lives in the sidebar list
981 so it stays a styled <li>. */
982 .source-item--empty {
983 cursor: default;
984 flex-direction: column;
985 align-items: center;
986 text-align: center;
987 padding: 1.5rem 1rem;
988 color: var(--text-muted);
989 font-size: 0.8rem;
990 line-height: 1.5;
991 }
992 .source-item--empty:hover { background-color: transparent; }
993 .source-item-empty-icon {
994 font-size: 1.5rem;
995 margin-bottom: 0.5rem;
996 opacity: 0.5;
997 }
998 .source-item-empty-strong { color: var(--accent-yellow); font-weight: 700; }
999 .source-item-empty-link { color: var(--accent-yellow); }
1000
1001 /* sources.js "+ Saved Filter" button label. Was inline color. */
1002 .source-name--muted { color: var(--text-muted); }
1003
1004 /* settings-sync.js renderConnect block. Was three inline styles. */
1005 .sync-connect {
1006 text-align: center;
1007 padding: 2rem 0;
1008 }
1009 .sync-connect p { margin-bottom: 1rem; }
1010 .sync-connect p + p { margin-bottom: 1.5rem; color: var(--text-secondary); }
1011
1012 /* settings-sync.js showCodeEntry spinner. Was three inline styles. */
1013 .sync-auth-spinner {
1014 text-align: center;
1015 padding: 1rem 0;
1016 color: var(--text-secondary);
1017 }
1018
1019 /* settings-sync.js pricing fine-print. Class already existed but carried
1020 inline styles; rules moved here. */
1021 .sync-sub-fine-print {
1022 font-size: 0.85rem;
1023 opacity: 0.75;
1024 margin-top: 0.4rem;
1025 }
1026
1027 /* settings-sync.js subscribe button row. Layout-only; class-ifying for
1028 consistency. */
1029 .sync-sub-actions {
1030 display: flex;
1031 gap: 0.5rem;
1032 margin-top: 0.5rem;
1033 }
1034
1035 /* mobile.js action sheets — stacked full-width buttons. Replaces inline
1036 `style.display='block'; style.width='100%'; style.marginBottom='0.5rem'`. */
1037 .btn-stacked {
1038 display: block;
1039 width: 100%;
1040 margin-bottom: 0.5rem;
1041 }
1042 .btn-stacked:last-child { margin-bottom: 0; }
1043
1044 /* app.js settings theme-import/export row. Layout-only; class-ifying. */
1045 .theme-actions {
1046 display: flex;
1047 gap: 0.5rem;
1048 margin-top: 0.5rem;
1049 }
1050
1051 /* Default body paragraph rhythm — retires the per-`<p>` inline
1052 `margin-bottom: 0.75rem` pattern in feeds.js plugin-warning. */
1053 .modal-body p + p { margin-top: 0.75rem; }
1054
1055 /* === 10. Forms =========================================================
1056 Canonical: .form-group + .form-input. Charter target (F5): split
1057 .form-input into .form-input / .form-select / .form-textarea so visual
1058 treatment per kind is targetable; add .form-label class on labels;
1059 add BB.ui.renderFormField helper.
1060 ========================================================================== */
1061 .form-group { margin-bottom: 0.75rem; }
1062 .form-group label {
1063 display: block;
1064 margin-bottom: 0.35rem;
1065 color: var(--text-secondary);
1066 font-size: 0.8rem;
1067 font-weight: 700;
1068 }
1069
1070 .form-input {
1071 width: 100%;
1072 padding: 0.6rem;
1073 border: var(--border-width) solid var(--border);
1074 border-radius: var(--radius-sm);
1075 background-color: var(--bg-primary);
1076 color: var(--text-primary);
1077 font-size: 0.875rem;
1078 transition: border-color 0.2s, box-shadow 0.2s;
1079 }
1080 .form-input:focus {
1081 outline: none;
1082 border-color: var(--accent-yellow);
1083 box-shadow: 0 0 0 2px var(--accent-yellow);
1084 }
1085
1086 /* F2 additions (2026-06-02) — split .form-input into kind-specific
1087 primitives so visual treatment per kind is targetable. .form-select and
1088 .form-textarea inherit .form-input today; future surface-specific tweaks
1089 land on the kind-specific class. .form-label replaces the unclassed
1090 `<label>` under `.form-group`. */
1091 .form-label {
1092 display: block;
1093 margin-bottom: 0.35rem;
1094 color: var(--text-secondary);
1095 font-size: 0.8rem;
1096 font-weight: 700;
1097 }
1098 .form-select {
1099 width: 100%;
1100 padding: 0.6rem;
1101 border: var(--border-width) solid var(--border);
1102 border-radius: var(--radius-sm);
1103 background-color: var(--bg-primary);
1104 color: var(--text-primary);
1105 font-size: 0.875rem;
1106 cursor: pointer;
1107 transition: border-color 0.2s, box-shadow 0.2s;
1108 }
1109 .form-select:focus {
1110 outline: none;
1111 border-color: var(--accent-yellow);
1112 box-shadow: 0 0 0 2px var(--accent-yellow);
1113 }
1114 .form-textarea {
1115 width: 100%;
1116 padding: 0.6rem;
1117 border: var(--border-width) solid var(--border);
1118 border-radius: var(--radius-sm);
1119 background-color: var(--bg-primary);
1120 color: var(--text-primary);
1121 font-size: 0.875rem;
1122 font-family: inherit;
1123 resize: vertical;
1124 min-height: 4rem;
1125 transition: border-color 0.2s, box-shadow 0.2s;
1126 }
1127 .form-textarea:focus {
1128 outline: none;
1129 border-color: var(--accent-yellow);
1130 box-shadow: 0 0 0 2px var(--accent-yellow);
1131 }
1132 /* .form-row — horizontal grouping of two related fields (e.g. label + button) */
1133 .form-row {
1134 display: flex;
1135 gap: 0.5rem;
1136 align-items: center;
1137 }
1138
1139 .form-actions {
1140 display: flex;
1141 justify-content: flex-end;
1142 gap: 0.5rem;
1143 margin-top: 1rem;
1144 }
1145
1146 .form-hint {
1147 font-size: 0.7rem;
1148 color: var(--text-muted);
1149 margin-top: 0.2rem;
1150 }
1151
1152 /* === 12. Toast =========================================================
1153 Canonical: .toast (+ .toast.success / .toast.error today;
1154 rename to .toast-success / .toast-error in F5, add .toast-info,
1155 .toast-undo). Helper: BB.ui.showToast — must inject zero style.cssText.
1156 ========================================================================== */
1157 .toast-container {
1158 position: fixed;
1159 bottom: 1rem;
1160 right: 1rem;
1161 z-index: 1001;
1162 }
1163
1164 .toast {
1165 background-color: var(--bg-primary);
1166 border: var(--border-width) solid var(--border);
1167 border-radius: var(--radius-sm);
1168 padding: 0.75rem 1rem;
1169 margin-top: 0.5rem;
1170 box-shadow: var(--shadow-brutal);
1171 animation: slideIn 0.3s ease-out;
1172 font-weight: 500;
1173 font-size: 0.875rem;
1174 }
1175 .toast.success { border-left: 4px solid var(--accent-green); background-color: color-mix(in srgb, var(--accent-green) 8%, var(--bg-primary)); }
1176 .toast.error { border-left: 4px solid var(--accent-red); background-color: color-mix(in srgb, var(--accent-red) 8%, var(--bg-primary)); }
1177
1178 /* F2 additions (2026-06-02) — parity-named variants. The legacy
1179 `.toast.success` / `.toast.error` stays during the rename window so
1180 existing showToast calls keep working; F5 migrates everything to the
1181 `--variant` form and removes the legacy. */
1182 .toast-success { border-left: 4px solid var(--accent-green); background-color: color-mix(in srgb, var(--accent-green) 8%, var(--bg-primary)); }
1183 .toast-error { border-left: 4px solid var(--accent-red); background-color: color-mix(in srgb, var(--accent-red) 8%, var(--bg-primary)); }
1184 .toast-info { border-left: 4px solid var(--accent-blue); background-color: color-mix(in srgb, var(--accent-blue) 8%, var(--bg-primary)); }
1185 .toast-undo {
1186 border-left: 4px solid var(--accent-yellow);
1187 background-color: color-mix(in srgb, var(--accent-yellow) 10%, var(--bg-primary));
1188 display: flex;
1189 align-items: center;
1190 gap: 0.75rem;
1191 }
1192 .undo-message { flex: 1; }
1193 .undo-btn {
1194 background: none;
1195 border: var(--border-width) solid currentColor;
1196 border-radius: var(--radius-sm);
1197 color: var(--text-primary);
1198 cursor: pointer;
1199 padding: 0.2rem 0.6rem;
1200 font-size: 0.8rem;
1201 font-weight: 600;
1202 }
1203 .undo-btn:hover { background-color: color-mix(in srgb, var(--accent-yellow) 20%, transparent); }
1204 .undo-countdown {
1205 font-size: 0.7rem;
1206 color: var(--text-muted);
1207 min-width: 1.5rem;
1208 text-align: right;
1209 font-variant-numeric: tabular-nums;
1210 }
1211
1212 @keyframes slideIn {
1213 from { transform: translateX(100%); opacity: 0; }
1214 to { transform: translateX(0); opacity: 1; }
1215 }
1216
1217 /* === 26. Help shortcuts ================================================ */
1218 .help-shortcuts {
1219 display: grid;
1220 grid-template-columns: 1fr 1fr;
1221 gap: 1.5rem;
1222 }
1223 .help-shortcuts .help-section:last-child {
1224 grid-column: 1 / -1;
1225 }
1226 .help-section h3 {
1227 font-size: 0.75rem;
1228 font-weight: 600;
1229 color: var(--text-muted);
1230 text-transform: uppercase;
1231 letter-spacing: 0.5px;
1232 margin-bottom: 0.5rem;
1233 }
1234 .help-row {
1235 display: flex;
1236 align-items: center;
1237 gap: 0.5rem;
1238 margin-bottom: 0.4rem;
1239 font-size: 0.875rem;
1240 }
1241 .help-row kbd {
1242 background: var(--bg-tertiary);
1243 border: 1px solid var(--border);
1244 border-radius: 4px;
1245 padding: 0.15rem 0.4rem;
1246 font-family: inherit;
1247 font-size: 0.8rem;
1248 min-width: 1.4rem;
1249 text-align: center;
1250 color: var(--text-primary);
1251 }
1252 .help-row span { color: var(--text-secondary); margin-left: auto; }
1253
1254 /* === 16. Skeleton & loading ============================================
1255 Canonical: .skeleton-item, .skeleton-line.{short,medium,long},
1256 .search-spinner (above). Charter target (F5): add BB.ui.renderSkeleton
1257 helper; widen first-paint usage beyond items panel.
1258 ========================================================================== */
1259 .skeleton-item {
1260 display: flex;
1261 padding: 0.75rem 1.25rem;
1262 border-bottom: 1px solid var(--border);
1263 animation: shimmer 1.5s infinite;
1264 }
1265 .skeleton-item .skeleton-indicators { width: 24px; margin-right: 0.75rem; }
1266 .skeleton-item .skeleton-content { flex: 1; }
1267 .skeleton-line {
1268 height: 0.75rem;
1269 border-radius: 4px;
1270 background: linear-gradient(90deg, var(--bg-tertiary) 25%, var(--border) 50%, var(--bg-tertiary) 75%);
1271 background-size: 200% 100%;
1272 animation: shimmer 1.5s infinite;
1273 }
1274 .skeleton-line.short { width: 40%; }
1275 .skeleton-line.medium { width: 70%; margin-top: 0.5rem; }
1276 .skeleton-line.long { width: 90%; margin-top: 0.5rem; }
1277
1278 @keyframes shimmer {
1279 0% { background-position: 200% 0; }
1280 100% { background-position: -200% 0; }
1281 }
1282
1283 /* === 17. Progress bar ================================================== */
1284 .progress-bar-container {
1285 position: absolute;
1286 bottom: 0;
1287 left: 0;
1288 right: 0;
1289 height: 3px;
1290 background-color: var(--border);
1291 overflow: hidden;
1292 }
1293 .progress-bar {
1294 height: 100%;
1295 background-color: var(--accent-yellow);
1296 transition: width 0.3s ease;
1297 }
1298
1299 /* Toast with action */
1300 .toast-action {
1301 display: inline-block;
1302 margin-left: 0.75rem;
1303 background: none;
1304 border: 1px solid currentColor;
1305 border-radius: 4px;
1306 color: inherit;
1307 cursor: pointer;
1308 padding: 0.15rem 0.5rem;
1309 font-size: 0.8rem;
1310 font-weight: 600;
1311 }
1312 .toast-action:hover { opacity: 0.8; }
1313
1314 /* Error details */
1315 .error-list {
1316 list-style: none;
1317 margin-top: 0.5rem;
1318 font-size: 0.8rem;
1319 color: var(--text-secondary);
1320 }
1321 .error-list li { padding: 0.2rem 0; }
1322 .error-list li::before { content: '\2022 '; color: var(--accent-red); }
1323
1324 /* === 27. Welcome modal ================================================= */
1325 .welcome-content h3 {
1326 font-size: 0.9rem;
1327 font-weight: 600;
1328 color: var(--text-primary);
1329 margin-top: 1rem;
1330 margin-bottom: 0.35rem;
1331 }
1332 .welcome-content h3:first-child { margin-top: 0; }
1333 .welcome-content p {
1334 font-size: 0.85rem;
1335 color: var(--text-secondary);
1336 line-height: 1.5;
1337 }
1338 .welcome-cta {
1339 display: flex;
1340 justify-content: center;
1341 gap: 0.75rem;
1342 margin-top: 1.25rem;
1343 padding-top: 1rem;
1344 border-top: 1px solid var(--border);
1345 }
1346
1347 /* === 32. Focus indicators ==============================================
1348 2px yellow outline for clickable elements; 2px yellow box-shadow ring
1349 for inputs (avoids layout shift). Rule: every new interactive
1350 primitive must appear in one of these selector lists.
1351 ========================================================================== */
1352 .btn:focus-visible,
1353 .source-item:focus-visible,
1354 .star-btn:focus-visible,
1355 .source-delete:focus-visible,
1356 .item:focus-visible,
1357 .tab:focus-visible,
1358 .plugin-item:focus-visible,
1359 .sort-select:focus-visible,
1360 .toast-action:focus-visible {
1361 outline: 2px solid var(--accent-yellow);
1362 outline-offset: 2px;
1363 box-shadow: none;
1364 }
1365
1366 .search-input:focus-visible,
1367 .form-input:focus-visible,
1368 .form-select:focus-visible,
1369 .form-textarea:focus-visible {
1370 outline: none;
1371 border-color: var(--accent-yellow);
1372 box-shadow: 0 0 0 2px var(--accent-yellow);
1373 }
1374
1375 /* === 20. Sources — tag filter bar ====================================== */
1376 .tag-filter-bar {
1377 display: flex;
1378 flex-wrap: wrap;
1379 gap: 0.3rem;
1380 padding: 0.5rem 0.75rem;
1381 border-bottom: var(--border-width) solid var(--border);
1382 background-color: var(--bg-secondary);
1383 }
1384 .tag-chip {
1385 background-color: var(--bg-primary);
1386 border: var(--border-width) solid var(--border);
1387 border-radius: var(--radius-xl);
1388 padding: 0.15rem 0.5rem;
1389 font-size: 0.7rem;
1390 color: var(--text-secondary);
1391 cursor: pointer;
1392 transition: all 0.15s;
1393 white-space: nowrap;
1394 }
1395 .tag-chip:hover { border-color: var(--accent-yellow); color: var(--text-primary); }
1396 .tag-chip.active {
1397 background-color: var(--accent-yellow);
1398 border-color: var(--accent-yellow);
1399 color: var(--text-primary);
1400 font-weight: 600;
1401 }
1402
1403 /* Source tag chips (small pills under source name) */
1404 .source-info { flex: 1; min-width: 0; }
1405 .source-tags {
1406 display: flex;
1407 flex-wrap: wrap;
1408 gap: 0.2rem;
1409 margin-top: 0.15rem;
1410 }
1411 .source-tag-chip {
1412 background-color: var(--bg-primary);
1413 border: var(--border-width) solid var(--border);
1414 border-radius: 8px;
1415 padding: 0 0.35rem;
1416 font-size: 0.6rem;
1417 color: var(--text-muted);
1418 }
1419
1420 /* === 15. Health indicator ==============================================
1421 STATE-BY-COLOR-ONLY VIOLATION (F3): pair color with shape (--broken
1422 = hollow ring, --rate_limited = dashed) + aria-label. F5 fix.
1423 ========================================================================== */
1424 .health-dot {
1425 display: inline-block;
1426 width: 8px;
1427 height: 8px;
1428 border-radius: 50%;
1429 margin-right: 0.3rem;
1430 flex-shrink: 0;
1431 }
1432 /* F5 (2026-06-02): dropped legacy color-only .health-* classes — render
1433 site (sources.js) and tests/run.js:472 updated to use [data-health]. */
1434
1435 /* F3 fix (2026-06-02): state-by-color violation retired. Each state now
1436 pairs color with a SHAPE modifier via [data-health], so the indicator
1437 is distinguishable in monochrome / for color-blind users. Render
1438 site must also pass an `aria-label` for screen readers. */
1439 .health-dot[data-health="green"] { background-color: var(--accent-green); }
1440 .health-dot[data-health="yellow"],
1441 .health-dot[data-health="rate_limited"] {
1442 background-color: var(--accent-yellow);
1443 border: 1.5px dashed var(--shadow-color);
1444 box-sizing: border-box;
1445 }
1446 .health-dot[data-health="red"] { background-color: var(--accent-red); position: relative; }
1447 .health-dot[data-health="red"]::after,
1448 .health-dot[data-health="auth_error"]::after,
1449 .health-dot[data-health="config_error"]::after {
1450 content: '!';
1451 position: absolute;
1452 top: 50%; left: 50%;
1453 transform: translate(-50%, -55%);
1454 color: var(--text-on-accent);
1455 font-size: 7px;
1456 font-weight: 900;
1457 line-height: 1;
1458 pointer-events: none;
1459 }
1460 .health-dot[data-health="auth_error"],
1461 .health-dot[data-health="config_error"] {
1462 background-color: var(--accent-red);
1463 position: relative;
1464 }
1465 .health-dot[data-health="circuit_broken"] {
1466 background-color: transparent;
1467 border: 2px solid var(--text-muted);
1468 box-sizing: border-box;
1469 }
1470
1471 /* === 19. Health popover ================================================
1472 Floating panel from gear icon. Charter target (F5): .hp-action
1473 migrates onto .btn; .hp-row uses filter-bar primitive.
1474 ========================================================================== */
1475 .health-popover {
1476 position: fixed;
1477 z-index: 100;
1478 background: var(--bg-secondary);
1479 border: var(--border-width) solid var(--border);
1480 border-radius: var(--radius-sm);
1481 box-shadow: var(--shadow-brutal);
1482 padding: 0.75rem 1rem;
1483 min-width: 220px;
1484 max-width: 320px;
1485 font-size: 0.8rem;
1486 }
1487 .hp-title {
1488 font-weight: 700;
1489 margin-bottom: 0.5rem;
1490 padding-bottom: 0.4rem;
1491 border-bottom: 1px solid var(--border);
1492 }
1493 .hp-row {
1494 display: flex;
1495 justify-content: space-between;
1496 gap: 0.75rem;
1497 padding: 0.2rem 0;
1498 }
1499 .hp-row-error {
1500 flex-direction: column;
1501 gap: 0.15rem;
1502 }
1503 .hp-label {
1504 color: var(--text-muted);
1505 flex-shrink: 0;
1506 }
1507 .hp-value {
1508 text-align: right;
1509 word-break: break-word;
1510 }
1511 .hp-row-error .hp-value {
1512 text-align: left;
1513 font-size: 0.75rem;
1514 color: var(--text-secondary);
1515 }
1516 .hp-green { color: var(--accent-green); }
1517 .hp-yellow { color: var(--accent-yellow); }
1518 .hp-red { color: var(--accent-red); }
1519 .hp-grey { color: var(--text-muted); }
1520
1521 .hp-actions {
1522 display: flex;
1523 gap: 0.4rem;
1524 margin-top: 0.6rem;
1525 padding-top: 0.5rem;
1526 border-top: var(--border-width) solid var(--border);
1527 }
1528 .hp-action {
1529 flex: 1;
1530 background: none;
1531 border: var(--border-width) solid var(--border);
1532 border-radius: var(--radius-sm);
1533 padding: 0.3rem 0.5rem;
1534 font-size: 0.75rem;
1535 color: var(--text-primary);
1536 cursor: pointer;
1537 }
1538 .hp-action:hover { background-color: var(--bg-tertiary); }
1539 .hp-action-danger { color: var(--accent-red); border-color: var(--accent-red); }
1540 .hp-action-danger:hover { background-color: var(--accent-red); color: #fff; }
1541
1542
1543 /* === 29. Sync settings utilities ======================================= */
1544 .sync-check-btn { margin-right: 0.5rem; }
1545 .sync-manual-entry { margin-top: 1rem; }
1546 .sync-manual-form { margin-top: 0.5rem; }
1547 .sync-action-spaced { margin-bottom: 1rem; }
1548 .sync-toggle-label { display: flex; align-items: center; gap: 0.5rem; }
1549 .sync-disconnect { margin-top: 1rem; }
1550
1551 /* === 20. Sources — edit button ========================================= */
1552 .source-edit {
1553 display: inline;
1554 background: none;
1555 border: none;
1556 color: var(--text-muted);
1557 cursor: pointer;
1558 font-size: 0.8rem;
1559 padding: 0 0.2rem;
1560 line-height: 1;
1561 opacity: 0.4;
1562 }
1563 .source-edit:hover { color: var(--accent-yellow); opacity: 1; }
1564
1565 /* === 23. Query feeds — sidebar divider ================================ */
1566 .source-divider {
1567 padding: 0.6rem 1rem 0.3rem;
1568 font-size: 0.65rem;
1569 font-weight: 600;
1570 color: var(--text-muted);
1571 text-transform: uppercase;
1572 letter-spacing: 0.5px;
1573 border-top: var(--border-width) solid var(--border);
1574 margin-top: 0.25rem;
1575 cursor: default;
1576 }
1577
1578 /* Query feed items — subtle visual distinction */
1579 .source-item.query-feed .source-name {
1580 font-style: italic;
1581 }
1582
1583 /* Add query feed button */
1584 .source-item.add-query-feed-btn {
1585 padding: 0.4rem 1rem;
1586 border-top: var(--border-width) solid var(--border);
1587 }
1588 .source-item.add-query-feed-btn:hover {
1589 background-color: var(--bg-tertiary);
1590 }
1591
1592 /* === 23. Query feeds — builder =========================================
1593 `.condition-row` is the canonical NAMED LAYOUT for mixed-intent rows
1594 (see compose-first rule, named-layout exception).
1595 ========================================================================== */
1596 .qf-builder .form-group { margin-bottom: 1rem; }
1597
1598 .condition-row {
1599 display: flex;
1600 align-items: center;
1601 gap: 0.4rem;
1602 margin-bottom: 0.4rem;
1603 }
1604 .condition-row .sort-select,
1605 .condition-row .search-input {
1606 flex: 1;
1607 min-width: 0;
1608 font-size: 0.8rem;
1609 padding: 0.35rem 0.5rem;
1610 }
1611 .condition-row .cond-field { flex: 0 0 auto; width: 90px; }
1612 .condition-row .cond-op { flex: 0 0 auto; width: 120px; }
1613 .condition-row .cond-val { flex: 1; }
1614 .condition-row .cond-remove {
1615 flex-shrink: 0;
1616 padding: 0.2rem 0.4rem;
1617 font-size: 1rem;
1618 line-height: 1;
1619 }
1620
1621 .add-condition-btn {
1622 margin-top: 0.25rem;
1623 }
1624
1625 .match-preview {
1626 padding: 0.5rem 0;
1627 font-size: 0.8rem;
1628 color: var(--text-muted);
1629 }
1630
1631 .modal-actions {
1632 display: flex;
1633 justify-content: flex-end;
1634 gap: 0.5rem;
1635 margin-top: 1rem;
1636 padding-top: 0.75rem;
1637 border-top: 1px solid var(--border);
1638 }
1639
1640 /* === 28. Settings modal ================================================ */
1641 .settings-content .form-group { margin-bottom: 1rem; }
1642 .settings-content .form-group label {
1643 display: block;
1644 margin-bottom: 0.35rem;
1645 color: var(--text-secondary);
1646 font-size: 0.8rem;
1647 font-weight: 700;
1648 }
1649
1650 /* === 24. Sidebar saved articles ======================================== */
1651 .sidebar-saved {
1652 display: flex;
1653 justify-content: space-between;
1654 align-items: center;
1655 padding: 0.6rem 1rem;
1656 cursor: pointer;
1657 border-top: var(--border-width) solid var(--border);
1658 border-left: 3px solid transparent;
1659 transition: background-color 0.15s;
1660 min-height: 2.5rem;
1661 font-size: 0.875rem;
1662 font-weight: 600;
1663 }
1664 .sidebar-saved:hover { background-color: var(--bg-tertiary); }
1665 .sidebar-saved.active {
1666 background-color: var(--bg-tertiary);
1667 border-left-color: var(--accent-yellow);
1668 }
1669
1670 /* Sidebar footer */
1671 .sidebar-footer {
1672 margin-top: auto;
1673 padding: 0.5rem 0.75rem;
1674 border-top: var(--border-width) solid var(--border);
1675 }
1676
1677 /* F2 additions (2026-06-02) — utility primitives.
1678 .hidden: visibility toggle utility (replaces ad-hoc style.display = 'none').
1679 .confirm-message: retires the inline-style violation in
1680 components.js confirmAction (was setting marginBottom, fontSize, color
1681 via style.cssText). */
1682 .hidden { display: none !important; }
1683 .confirm-message {
1684 margin-bottom: 1rem;
1685 font-size: 0.9rem;
1686 color: var(--text-primary);
1687 }
1688
1689 /* === 31. Screen-reader only (.sr-only) ================================ */
1690 .sr-only {
1691 position: absolute;
1692 width: 1px;
1693 height: 1px;
1694 padding: 0;
1695 margin: -1px;
1696 overflow: hidden;
1697 clip: rect(0, 0, 0, 0);
1698 white-space: nowrap;
1699 border: 0;
1700 }
1701
1702 /* === 38. Source-badge on items (mobile-only display) =================
1703 Hidden on desktop; shown inside the @media (max-width: 768px) block below.
1704 ========================================================================== */
1705 .item-source-badge { display: none; }
1706
1707 /* === 39. @media (max-width: 768px) — mobile layout ==================== */
1708 @media (max-width: 768px) {
1709 body {
1710 padding-top: env(safe-area-inset-top);
1711 padding-bottom: calc(52px + env(safe-area-inset-bottom));
1712 }
1713
1714 .mobile-tab-bar { display: flex; }
1715 .header { display: none; }
1716 .main { flex-direction: column; }
1717
1718 .sidebar {
1719 display: none;
1720 width: 100%;
1721 min-width: 0;
1722 border-right: none;
1723 }
1724 .sidebar.mobile-visible {
1725 display: flex;
1726 flex: 1;
1727 }
1728
1729 .items-panel { width: 100%; }
1730 .items-panel.mobile-hidden { display: none; }
1731
1732 .detail-panel {
1733 position: fixed;
1734 top: 0;
1735 left: 0;
1736 right: 0;
1737 bottom: 0;
1738 width: 100%;
1739 min-width: 0;
1740 z-index: 1050;
1741 border-left: none;
1742 }
1743
1744 body.mobile-detail-open .mobile-tab-bar { display: none; }
1745 body.mobile-detail-open { padding-bottom: 0; }
1746 body.mobile-detail-open .detail-panel {
1747 padding-top: env(safe-area-inset-top);
1748 padding-bottom: env(safe-area-inset-bottom);
1749 }
1750
1751 .mobile-back-btn {
1752 display: inline-flex;
1753 margin-right: auto;
1754 font-size: 0.8rem;
1755 }
1756
1757 .mobile-search-bar { display: flex; }
1758
1759 /* Larger tap targets */
1760 .item { padding: 0.875rem 1rem; }
1761 .star-btn { font-size: 1.3rem; padding: 0.4rem 0.5rem; margin: -0.4rem -0.5rem; }
1762 .source-item { min-height: 3rem; }
1763
1764 /* Modal as bottom sheet */
1765 .modal-overlay { align-items: flex-end; }
1766 .modal-content {
1767 width: 100%;
1768 max-width: 100%;
1769 border-radius: var(--radius-lg) var(--radius-lg) 0 0;
1770 max-height: 85vh;
1771 }
1772 .modal-content::before {
1773 content: '';
1774 display: block;
1775 width: 36px;
1776 height: 4px;
1777 background-color: var(--border-dark);
1778 border-radius: 2px;
1779 margin: 0.5rem auto;
1780 }
1781 .modal-header { border-radius: 0; }
1782
1783 /* Toast above tab bar */
1784 .toast-container {
1785 bottom: calc(60px + env(safe-area-inset-bottom));
1786 right: 0.75rem;
1787 left: 0.75rem;
1788 }
1789
1790 /* Update banner above tab bar */
1791 .update-banner {
1792 bottom: calc(60px + env(safe-area-inset-bottom));
1793 }
1794
1795 /* Source badge on items */
1796 .item-source-badge {
1797 display: inline-block;
1798 background-color: var(--bg-tertiary);
1799 color: var(--text-secondary);
1800 font-size: 0.65rem;
1801 font-weight: 600;
1802 padding: 0.05rem 0.35rem;
1803 border-radius: var(--radius-sm);
1804 margin-top: 0.15rem;
1805 }
1806
1807 /* Bold unread item titles */
1808 .item.unread .item-text { font-weight: 700; }
1809
1810 /* Sticky detail header */
1811 .detail-header {
1812 position: sticky;
1813 top: 0;
1814 z-index: 5;
1815 }
1816 .detail-panel { -webkit-overflow-scrolling: touch; }
1817 .item-detail { padding: 1rem 1.25rem; }
1818 .detail-body { line-height: 1.8; }
1819
1820 /* Horizontal-scroll tag filter bar */
1821 .tag-filter-bar {
1822 flex-wrap: nowrap;
1823 overflow-x: auto;
1824 -webkit-overflow-scrolling: touch;
1825 scrollbar-width: none;
1826 }
1827 .tag-filter-bar::-webkit-scrollbar { display: none; }
1828
1829 /* Help shortcuts single column */
1830 .help-shortcuts { grid-template-columns: 1fr; }
1831 }
1832
1833 /* === 40. @media (hover: none) — touch overrides ====================== */
1834 @media (hover: none) {
1835 .item:hover { background-color: transparent; }
1836 .item.unread:hover { background-color: color-mix(in srgb, var(--accent-red) 6%, var(--bg-primary)); }
1837 .item.selected:hover { background-color: var(--bg-tertiary); }
1838 .item.unread.selected:hover { background-color: color-mix(in srgb, var(--accent-red) 12%, var(--bg-primary)); }
1839 .source-item:hover { background-color: transparent; }
1840 .source-item.active:hover { background-color: var(--bg-tertiary); }
1841 .sidebar-saved:hover { background-color: transparent; }
1842 .sidebar-saved.active:hover { background-color: var(--bg-tertiary); }
1843 .btn:hover { background-color: var(--bg-primary); border-color: var(--border); }
1844 .btn-primary:hover { background-color: var(--accent-blue); border-color: var(--accent-blue); }
1845 .btn-success:hover { background-color: var(--accent-yellow); border-color: var(--accent-yellow); }
1846 .star-btn:hover { color: var(--text-muted); transform: none; }
1847 .star-btn.starred:hover { color: var(--accent-yellow); }
1848 .tag-chip:hover { border-color: var(--border); color: var(--text-secondary); }
1849 .tag-chip.active:hover { border-color: var(--accent-yellow); }
1850 .plugin-item:hover { border-color: var(--border); background-color: var(--bg-primary); box-shadow: none; transform: none; }
1851 .context-menu-item:hover { background-color: transparent; }
1852 }
1853
1854 /* === 41. @media (prefers-reduced-motion: reduce) ====================== */
1855 @media (prefers-reduced-motion: reduce) {
1856 *, *::before, *::after {
1857 animation-duration: 0.01ms !important;
1858 animation-iteration-count: 1 !important;
1859 transition-duration: 0.01ms !important;
1860 }
1861 }
1862
1863 /* === 18. Update banner ================================================
1864 z-index 1200 — above mobile nav (1100/1101) and context menus, below
1865 any future fullscreen overlay. F5: lowered from 9999 (was unnecessarily
1866 high; sat above modals which made it impossible to dismiss when a
1867 modal opened post-banner-show).
1868 ========================================================================== */
1869 .update-banner {
1870 position: fixed;
1871 bottom: 1rem;
1872 right: 1rem;
1873 background: var(--bg-secondary);
1874 border: var(--border-width) solid var(--border);
1875 border-radius: var(--radius-sm);
1876 padding: 0.75rem 1rem;
1877 z-index: 1200;
1878 max-width: 320px;
1879 box-shadow: var(--shadow-brutal);
1880 font-size: 0.875rem;
1881 }
1882
1883 /* === 14. Context menu ==================================================
1884 Canonical: .context-menu (state: .visible). Items: .context-menu-item
1885 (+ .context-menu-item.danger; rename to --danger in F5). Charter
1886 target: add BB.ui.showContextMenu(x, y, items) helper.
1887 ========================================================================== */
1888 .context-menu {
1889 position: fixed;
1890 z-index: 1100;
1891 background: var(--bg-primary);
1892 border: var(--border-width) solid var(--border);
1893 border-radius: var(--radius-sm);
1894 box-shadow: var(--shadow-brutal);
1895 padding: 0.25rem;
1896 min-width: 140px;
1897 }
1898 .context-menu-item {
1899 display: block;
1900 width: 100%;
1901 padding: 0.4rem 0.75rem;
1902 border: none;
1903 border-radius: var(--radius-sm);
1904 background: none;
1905 color: var(--text-primary);
1906 font-size: 0.825rem;
1907 text-align: left;
1908 cursor: pointer;
1909 }
1910 .context-menu-item:hover {
1911 background-color: var(--bg-tertiary);
1912 }
1913
1914 /* === 33. Mobile tab bar ================================================ */
1915 .mobile-tab-bar {
1916 display: none;
1917 position: fixed;
1918 bottom: 0;
1919 left: 0;
1920 right: 0;
1921 z-index: 1100;
1922 height: calc(52px + env(safe-area-inset-bottom));
1923 padding-bottom: env(safe-area-inset-bottom);
1924 background-color: var(--bg-secondary);
1925 border-top: var(--border-width) solid var(--border);
1926 align-items: center;
1927 justify-content: space-around;
1928 }
1929
1930 .mobile-tab {
1931 flex: 1;
1932 display: flex;
1933 align-items: center;
1934 justify-content: center;
1935 height: 52px;
1936 background: none;
1937 border: none;
1938 color: var(--text-muted);
1939 font-family: inherit;
1940 font-size: 0.7rem;
1941 font-weight: 600;
1942 text-transform: uppercase;
1943 letter-spacing: 0.5px;
1944 cursor: pointer;
1945 transition: color 0.15s;
1946 }
1947 .mobile-tab.active { color: var(--accent-blue); }
1948
1949 .mobile-tab-create {
1950 font-size: 1.4rem;
1951 font-weight: 400;
1952 color: var(--accent-green);
1953 text-transform: none;
1954 letter-spacing: 0;
1955 }
1956
1957 /* === 34. Mobile more-popover =========================================== */
1958 .mobile-more-popover {
1959 display: none;
1960 position: fixed;
1961 bottom: calc(52px + env(safe-area-inset-bottom));
1962 right: 0.5rem;
1963 z-index: 1101;
1964 background-color: var(--bg-primary);
1965 border: var(--border-width) solid var(--border);
1966 border-radius: var(--radius-sm);
1967 box-shadow: 0 calc(-1 * var(--shadow-offset)) 0 var(--shadow-color);
1968 padding: 0.25rem;
1969 min-width: 160px;
1970 }
1971 .mobile-more-popover.visible { display: block; }
1972 .mobile-more-popover button {
1973 display: block;
1974 width: 100%;
1975 padding: 0.6rem 0.75rem;
1976 border: none;
1977 border-radius: 4px;
1978 background: none;
1979 color: var(--text-primary);
1980 font-family: inherit;
1981 font-size: 0.85rem;
1982 text-align: left;
1983 cursor: pointer;
1984 }
1985 .mobile-more-popover button:hover {
1986 background-color: var(--bg-tertiary);
1987 }
1988
1989 /* === 35. Mobile search bar ============================================= */
1990 .mobile-search-bar {
1991 display: none;
1992 position: sticky;
1993 top: 0;
1994 z-index: 10;
1995 padding: 0.5rem 0.75rem;
1996 gap: 0.5rem;
1997 background-color: var(--bg-primary);
1998 border-bottom: var(--border-width) solid var(--border);
1999 }
2000 .mobile-search-input {
2001 flex: 1;
2002 padding: 0.5rem 0.75rem;
2003 border: var(--border-width) solid var(--border);
2004 border-radius: var(--radius-sm);
2005 background-color: var(--bg-secondary);
2006 color: var(--text-primary);
2007 font-family: inherit;
2008 font-size: 0.875rem;
2009 }
2010 .mobile-search-input:focus {
2011 outline: none;
2012 border-color: var(--accent-yellow);
2013 box-shadow: 0 0 0 2px var(--accent-yellow);
2014 }
2015 .mobile-sort-select {
2016 padding: 0.5rem 0.5rem;
2017 border: var(--border-width) solid var(--border);
2018 border-radius: var(--radius-sm);
2019 background-color: var(--bg-secondary);
2020 color: var(--text-primary);
2021 font-family: inherit;
2022 font-size: 0.8rem;
2023 }
2024
2025 /* === 36. Mobile back button ============================================ */
2026 .mobile-back-btn { display: none; }
2027
2028 /* === 37. Pull-to-refresh indicator ===================================== */
2029 .pull-to-refresh-indicator {
2030 display: none;
2031 text-align: center;
2032 padding: 0.5rem;
2033 font-size: 0.8rem;
2034 color: var(--text-muted);
2035 }
2036 .pull-to-refresh-indicator.visible {
2037 display: block;
2038 }
2039
2040 /* === 30. Reading list / Bookmarks ====================================== */
2041
2042 .bookmark-tag-bar {
2043 display: flex;
2044 gap: 0.375rem;
2045 padding: 0.5rem 0.75rem;
2046 border-bottom: 1px solid var(--border);
2047 overflow-x: auto;
2048 flex-shrink: 0;
2049 }
2050 .tag-filter-btn {
2051 white-space: nowrap;
2052 font-size: 0.75rem;
2053 }
2054 .tag-filter-btn.active {
2055 background: var(--accent-blue);
2056 color: white;
2057 border-color: var(--accent-blue);
2058 }
2059 .bookmark-item .bookmark-actions {
2060 display: flex;
2061 gap: 0.25rem;
2062 align-items: center;
2063 margin-left: auto;
2064 flex-shrink: 0;
2065 opacity: 0;
2066 transition: opacity 0.15s;
2067 }
2068 .bookmark-item:hover .bookmark-actions,
2069 .bookmark-item:focus-within .bookmark-actions {
2070 opacity: 1;
2071 }
2072 .bookmark-item.pinned {
2073 border-left: 3px solid var(--accent-blue);
2074 }
2075 .bookmark-tags {
2076 display: flex;
2077 gap: 0.25rem;
2078 flex-wrap: wrap;
2079 margin-top: 0.25rem;
2080 }
2081 .bookmark-tag {
2082 cursor: pointer;
2083 font-size: 0.7rem;
2084 }
2085 .bookmark-tag:hover {
2086 background: var(--accent-blue);
2087 color: white;
2088 }
2089 .context-menu-item.danger,
2090 .context-menu-item--danger {
2091 /* F2 (2026-06-02): added --danger parity name; .danger kept as legacy
2092 alias until F5 retires call sites. Inline #dc3545 fallback removed —
2093 --accent-red is themed and always defined. */
2094 color: var(--accent-red);
2095 }
2096
2097 /* === 42. Webkit scrollbar ============================================== */
2098 ::-webkit-scrollbar { width: 6px; height: 6px; }
2099 ::-webkit-scrollbar-track { background: var(--bg-secondary); }
2100 ::-webkit-scrollbar-thumb { background: var(--border-dark); border-radius: 3px; }
2101 ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
2102