/* ============================================================================ Balanced Breakfast — Neobrute-lite Theme Build new UI by COMPOSING the existing vocabulary, in this order of preference: 1. Use a UTILITY class for one-off adjustments (.hidden, .sr-only, .mb-1, .flex-1 — most still to be added) 2. Use a LAYOUT PRIMITIVE to position content (.row-flex, .row, .stack-{2,3,4} — to be added in F1/F2) 3. Use a COMPONENT PRIMITIVE for a UI element (.btn, .card, .modal, .form-input, .form-select, .badge, .tag, .toast) 4. Extend a primitive with a modifier (.btn-primary, .btn-sm, .form-select--ghost) 5. ONLY THEN consider a new class — and add it to the right BAND below A new class is a smell, not a goal. Before writing one: • grep this file for the visual shape you want; almost everything is here • check whether an existing primitive + modifier composes to your shape • page-scoped rules under a feature class (`.bookmarks .foo`) are a last resort, not a first move NAMED-LAYOUT EXCEPTION: A row of mixed-intent elements (one fills, others size to content) belongs in a NAMED LAYOUT with an explicit `grid-template-columns`, not in `.row-flex` + utilities. Smell test: if you find yourself reaching for `--compact`, `style="width: auto;"`, or `min-width: 0 !important` to make a row lay out right, the layout itself wants a name. See `.condition-row` for the canonical form. See `_private/docs/balanced_breakfast/design-system.md` for the primitive inventory and rules; `styleguide.md` for visual specs. ---------------------------------------------------------------------------- FILE STRUCTURE — search by BAND name (uppercase anchors) to navigate. Per-section titles use `=== N. Title ===` headers; numbered for stability, not because order matters. Bands group related sections; cascade order is load-bearing only inside the RESPONSIVE LAYERS and TOUCH OVERRIDES bands. Note (2026-06-02): the BAND map below is the TARGET layout. Today's file still has rules grouped by their original feature-add order. Phase F5 (dedup sweep) moves rules into the correct BAND. Until then, the numbered section markers below are stable anchors regardless of file position. BAND: FOUNDATIONS 1. Design System Variables — tokens (:root) 2. CSS Reset & Base 3. Keyframes & Animation BAND: APP SHELL & CHROME 4. Layout shell — #app, .main 5. Header 6. Sidebar 7. Items panel 8. Detail panel BAND: COMPONENT PRIMITIVES 9. Buttons — .btn family 10. Forms — .form-group, .form-input 11. Modal — .modal-overlay/.modal-content 12. Toast — .toast 13. Tag — .tag 14. Context menu — .context-menu 15. Health indicator — .health-dot (state-by-color, F3 fix pending) 16. Skeleton & loading — .skeleton-item, .skeleton-line, spinner 17. Progress bar — .progress-bar-container 18. Update banner — .update-banner 19. Health popover — .health-popover BAND: FEATURE SURFACES 20. Sources list / sidebar entries 21. Items list / read indicator / star button 22. Reader-expanded mode 23. Query feeds (builder + sidebar) 24. Sidebar saved articles 25. Plugin list 26. Help shortcuts 27. Welcome modal 28. Settings modal 29. Sync settings (utility classes) 30. Reading list / Bookmarks BAND: UTILITIES 31. Screen-reader only (.sr-only) 32. Focus indicators BAND: MOBILE PRIMITIVES 33. Mobile tab bar 34. Mobile more-popover 35. Mobile search bar 36. Mobile back button 37. Pull-to-refresh indicator 38. Source-badge on items (mobile-only display) BAND: RESPONSIVE LAYERS — cascade-ordered 39. @media (max-width: 768px) — mobile layout 40. @media (hover: none) — touch overrides 41. @media (prefers-reduced-motion: reduce) BAND: SCROLLBAR 42. Webkit scrollbar ============================================================================ */ /* === 1. Design System Variables ========================================= Color tokens are themed via `js/themes.js` (loads shared TOML palettes from MNW/shared/themes/). The fallback hex values below are the `flatwhite` light theme palette, used only for initial render before `themes.js` runs. Annotation legend: themed — set by themes.js COLOR_MAP from TOML derived — computed in themes.js applyTheme() from a themed token invariant — fixed; never changes with theme unused — declared but no CSS rule consumes it; see design-system.md token audit. Phase F1 cleanup: consume or remove. ========================================================================== */ :root { --bg-primary: #faf8f5; /* themed: background.primary */ --bg-secondary: #f5f0e8; /* themed: background.secondary */ --bg-tertiary: #ebe4d8; /* themed: background.tertiary */ --text-primary: #3d3225; /* themed: foreground.primary */ --text-secondary: #6b5d4d; /* themed: foreground.secondary */ --text-muted: #9a8b78; /* themed: foreground.muted */ --accent-red: #c94b4b; /* themed: accent.red */ --accent-red-hover: #d65d5d; /* derived in themes.js — consumed by .btn-danger:hover (F2) */ --accent-green: #6b9b5a; /* themed: accent.green */ --accent-blue: #4a8ebd; /* themed: accent.blue */ --accent-blue-hover: #5e9dca; /* derived in themes.js */ --accent-yellow: #e8a841; /* themed: accent.yellow */ --accent-yellow-light: #f4c56d; /* derived in themes.js */ --accent-purple: #8b6bbf; /* themed: accent.purple — consumed by .tag/.badge[data-color="purple"] (F2) */ --accent-cyan: #5ab5b5; /* themed: accent.cyan — consumed by .tag/.badge[data-color="cyan"] (F2) */ --border: #e0d6c8; /* themed: border.default */ --border-dark: #d4c8b5; /* derived in themes.js */ --text-on-accent: #ffffff; /* derived in themes.js (contrast vs accent.blue) */ --border-width: 2px; /* invariant */ --radius-sm: 5px; /* invariant */ --radius-lg: 10px; /* invariant */ --radius-xl: 20px; /* invariant */ --shadow-offset: 3px; /* invariant — BB neobrute signature */ --shadow-color: #6b5d4d; /* derived in themes.js (darken --text-muted) */ --shadow-brutal: var(--shadow-offset) var(--shadow-offset) 0 var(--shadow-color); /* invariant composition */ } /* F5 cleanup (2026-06-02): dropped --bg-surface (themed but never consumed), --radius-md (duplicate of --radius-sm), --shadow / --shadow-hover (derived but never consumed). themes.js still sets --bg-surface / --shadow / --shadow-hover in applyTheme — these will be removed in the F5 themes.js cleanup. */ /* === 2. CSS Reset & Base ================================================ */ * { margin: 0; padding: 0; box-sizing: border-box; } /* === 4. Layout shell — body & #app ==================================== */ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background-color: var(--bg-primary); color: var(--text-primary); line-height: 1.5; overflow: hidden; } #app { display: flex; flex-direction: column; height: 100vh; } /* === 5. Header ========================================================== */ .header { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 1.25rem; background-color: var(--bg-secondary); border-bottom: var(--border-width) solid var(--border); box-shadow: 0 var(--shadow-offset) 0 var(--shadow-color); -webkit-user-select: none; user-select: none; position: relative; } .header h1 { font-size: 1.25rem; font-weight: 700; color: var(--accent-blue); letter-spacing: -0.5px; } .header-actions { display: flex; gap: 0.5rem; align-items: center; } .search-input { padding: 0.4rem 0.75rem; border: var(--border-width) solid var(--border); border-radius: var(--radius-sm); background-color: var(--bg-primary); color: var(--text-primary); width: 180px; font-size: 0.875rem; transition: border-color 0.2s, box-shadow 0.2s; } /* Search spinner */ .search-spinner { display: none; width: 14px; height: 14px; border: 2px solid var(--border); border-top-color: var(--accent-yellow); border-radius: 50%; animation: spin 0.6s linear infinite; } .search-spinner.active { display: inline-block; } @keyframes spin { to { transform: rotate(360deg); } } .search-input::placeholder { color: var(--text-muted); } .search-input:focus { outline: none; border-color: var(--accent-yellow); box-shadow: 0 0 0 2px var(--accent-yellow); } .sort-select { padding: 0.4rem 0.75rem; border: var(--border-width) solid var(--border); border-radius: var(--radius-sm); background-color: var(--bg-primary); color: var(--text-primary); cursor: pointer; font-size: 0.875rem; } .sort-select:focus { outline: none; border-color: var(--accent-yellow); } /* === 9. Buttons ======================================================== Canonical: .btn (+ .btn-primary, .btn-success, .btn-small). Charter target: collapse bespoke *-btn classes (.tag-filter-btn, .toast-action, .hp-action, .mobile-tab, etc.) onto this family in F5. ========================================================================== */ .btn { padding: 0.4rem 0.75rem; border: var(--border-width) solid var(--border); border-radius: var(--radius-sm); background-color: var(--bg-primary); color: var(--text-primary); cursor: pointer; font-weight: 600; font-size: 0.875rem; transition: background-color 0.2s, border-color 0.2s; } .btn:hover { background-color: var(--bg-tertiary); border-color: var(--border-dark); } .btn-primary { background-color: var(--accent-blue); border-color: var(--accent-blue); color: var(--text-on-accent); } .btn-primary:hover { background-color: var(--accent-blue-hover); border-color: var(--accent-blue-hover); } .btn-success { background-color: var(--accent-yellow); border-color: var(--accent-yellow); color: var(--text-on-accent); } .btn-success:hover { background-color: var(--accent-yellow-light); border-color: var(--accent-yellow-light); } .btn-small { padding: 0.2rem 0.5rem; font-size: 0.8rem; } .btn-small.active { background: var(--accent-yellow); color: var(--text-on-accent); border-color: var(--accent-yellow); } /* F2 additions (2026-06-02) — see design-system.md §Button. .btn-sm is the parity name; .btn-small kept as alias until F5 dedup. */ .btn-sm { padding: 0.2rem 0.5rem; font-size: 0.8rem; } .btn-sm.active { background: var(--accent-yellow); color: var(--text-on-accent); border-color: var(--accent-yellow); } /* .btn-secondary — explicit neutral; today's default `.btn` already plays this role, but having a named modifier lets future surfaces declare intent. */ .btn-secondary { background-color: var(--bg-secondary); border-color: var(--border); color: var(--text-primary); } .btn-secondary:hover { background-color: var(--bg-tertiary); border-color: var(--border-dark); } /* .btn-danger — destructive action. Consumes the previously-unused --accent-red-hover token. Replaces .hp-action-danger in F5. */ .btn-danger { background-color: var(--accent-red); border-color: var(--accent-red); color: var(--text-on-accent); } .btn-danger:hover { background-color: var(--accent-red-hover); border-color: var(--accent-red-hover); } /* .btn-icon — square, no padding label space; used for close buttons, icon-only toolbar buttons (gear, star, more). */ .btn-icon { padding: 0.25rem; line-height: 1; min-width: 1.75rem; min-height: 1.75rem; display: inline-flex; align-items: center; justify-content: center; } /* .btn-text — borderless, no background. Used for inline actions inside text or as a low-emphasis alternative to .btn-secondary. */ .btn-text { background: none; border: none; padding: 0.25rem 0.5rem; color: var(--text-primary); } .btn-text:hover { background-color: var(--bg-secondary); border-color: transparent; } /* .btn-link — looks like an anchor, behaves like a button. */ .btn-link { background: none; border: none; padding: 0; color: var(--accent-blue); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; } .btn-link:hover { color: var(--accent-blue-hover); background: none; border-color: transparent; } /* .btn-loading — spinner-replacement state. Pair with disabled attr. */ .btn-loading { color: transparent !important; pointer-events: none; position: relative; } .btn-loading::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0.875rem; height: 0.875rem; margin: -0.4375rem 0 0 -0.4375rem; border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%; animation: spin 0.6s linear infinite; color: var(--text-primary); opacity: 0.6; } .btn-primary.btn-loading::after, .btn-success.btn-loading::after, .btn-danger.btn-loading::after { color: var(--text-on-accent); opacity: 1; } /* === N. Card (F2 addition, 2026-06-02) ================================ Canonical: .card. Charter target — F5 collapses .source-item, .plugin-item, .sidebar-saved, .bookmark-item onto .card with the modifier variants below. .card — default: white-ish surface, 2px border, 3px brutal shadow .card--row — row-shaped, no shadow, used in sidebars and lists .card--shell — frame-only, no fill (for grouping) .card--muted — secondary-surface variant .card--clickable — adds hover lift Container: .cards-grid for grid layouts. ========================================================================== */ .card { background-color: var(--bg-primary); border: var(--border-width) solid var(--border); border-radius: var(--radius-sm); padding: 0.75rem; box-shadow: var(--shadow-brutal); } .card--row { border-radius: 0; border-left: 0; border-right: 0; border-top: 0; box-shadow: none; padding: 0.6rem 1rem; display: flex; align-items: center; gap: 0.75rem; } .card--shell { background: none; box-shadow: none; } .card--muted { background-color: var(--bg-secondary); } .card--clickable { cursor: pointer; transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s; } .card--clickable:hover { border-color: var(--accent-yellow); transform: translate(-1px, -1px); box-shadow: calc(var(--shadow-offset) + 1px) calc(var(--shadow-offset) + 1px) 0 var(--shadow-color); } .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; } .card-title { font-size: 0.95rem; font-weight: 700; color: var(--text-primary); margin: 0; } .card-description { font-size: 0.85rem; color: var(--text-secondary); line-height: 1.4; } .card-meta { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.5rem; font-size: 0.75rem; color: var(--text-muted); } .card-badge { flex-shrink: 0; } .cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.75rem; } /* === N. Row primitive (F2-sub addition, 2026-06-02) =================== Canonical slot layout: [icon] [primary · badges] [meta] [actions]. Used via BB.ui.renderRow(model). Per-surface classes (.plugin-item, .source-item, .item, .bookmark-item, etc.) sit alongside .row on the outer element and add their own padding / border / hover treatment. F5 retires per-surface name/desc classes (.plugin-name, .plugin-desc) that duplicate .row-primary / .row-secondary. ========================================================================== */ .row { display: flex; align-items: flex-start; gap: 0.75rem; min-width: 0; } .row-icon { flex-shrink: 0; display: flex; align-items: center; justify-content: center; } .row-content { flex: 1; min-width: 0; } .row-primary { font-weight: 600; color: var(--text-primary); font-size: 0.9rem; line-height: 1.3; display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; } .row-secondary { font-size: 0.8rem; color: var(--text-secondary); line-height: 1.4; margin-top: 0.15rem; } .row-meta { flex-shrink: 0; font-size: 0.75rem; color: var(--text-muted); align-self: center; } .row-actions { flex-shrink: 0; display: flex; align-items: center; gap: 0.25rem; } /* Hover-reveal pattern: actions appear when the row (or any descendant) is hovered or focused. Per-surface override with `.row-actions.always` keeps them visible. */ .row .row-actions:not(.always) { opacity: 0; transition: opacity 0.15s; } .row:hover .row-actions:not(.always), .row:focus-within .row-actions:not(.always) { opacity: 1; } /* F2-sub addition: form-hint error variant (used by renderFormField when passed a `field.error`). */ .form-hint--error { color: var(--accent-red); font-weight: 600; } /* === 4. Layout shell — main =========================================== */ .main { display: flex; flex: 1; overflow: hidden; } /* === 6. Sidebar ======================================================== */ .sidebar { width: 220px; min-width: 220px; background-color: var(--bg-secondary); border-right: var(--border-width) solid var(--border); display: flex; flex-direction: column; overflow-y: auto; scrollbar-gutter: stable; } .sidebar h2 { padding: 0.75rem 1rem; font-size: 0.75rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; border-bottom: var(--border-width) solid var(--border); } /* === 20. Sources list — sidebar entries ================================ Charter target (F5): .source-item collapses onto .card --row. ========================================================================== */ .sources-list { list-style: none; padding: 0.25rem 0; } .source-item { display: flex; justify-content: space-between; align-items: center; padding: 0.6rem 1rem; cursor: pointer; transition: background-color 0.15s; min-height: 2.5rem; border-left: 3px solid transparent; } .source-item:hover { background-color: var(--bg-tertiary); } .source-item.active { background-color: var(--bg-tertiary); border-left-color: var(--accent-yellow); } .source-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 600; font-size: 0.875rem; } .source-actions { display: flex; align-items: center; gap: 0.3rem; } .source-count { background-color: var(--bg-primary); padding: 0.1rem 0.4rem; border-radius: var(--radius-xl); font-size: 0.7rem; color: var(--text-muted); border: var(--border-width) solid var(--border); min-width: 1.8rem; text-align: center; } .source-count.all-read { color: var(--accent-green); border-color: var(--accent-green); } .source-delete { display: none; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 0.9rem; padding: 0 0.2rem; line-height: 1; } .source-delete:hover { color: var(--accent-red); } .source-item:hover .source-delete { display: inline; } /* === 7. Items panel ==================================================== */ .items-panel { flex: 1; display: flex; flex-direction: column; overflow-y: auto; background-color: var(--bg-primary); } .items-list { list-style: none; } .item { display: flex; padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--border); border-left: 3px solid transparent; cursor: pointer; transition: background-color 0.15s; } .item:hover { background-color: var(--bg-secondary); } .item.unread { border-left-color: var(--accent-red); background-color: color-mix(in srgb, var(--accent-red) 6%, var(--bg-primary)); } .item.read { color: var(--text-secondary); } .item.selected { background-color: var(--bg-tertiary); border-left-color: var(--accent-yellow); } .item.unread.selected { border-left-color: var(--accent-red); background-color: color-mix(in srgb, var(--accent-red) 12%, var(--bg-primary)); } .item.empty-state { text-align: center; color: var(--text-muted); padding: 3rem 2rem; cursor: default; line-height: 1.6; flex-direction: column; align-items: center; } .empty-icon { font-size: 2.5rem; margin-bottom: 0.75rem; opacity: 0.6; } /* F2 addition (2026-06-02) — .empty-state as its own block. Today's .item.empty-state (modifier-on-row) stays as legacy until F5 retires call sites. Use this block + BB.ui.renderEmptyState() for new surfaces.
...

No items

*/ .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 3rem 2rem; color: var(--text-muted); line-height: 1.6; gap: 0.75rem; } .empty-state-icon { font-size: 2.5rem; opacity: 0.6; } .empty-state-text { font-size: 0.9rem; color: var(--text-secondary); margin: 0; } .empty-state--compact { padding: 1.5rem 1rem; gap: 0.5rem; } .empty-state--compact .empty-state-icon { font-size: 1.5rem; } .item-indicators { display: flex; flex-direction: column; align-items: center; margin-right: 0.75rem; min-width: 24px; } .star-btn { background: none; border: none; cursor: pointer; font-size: 1.1rem; color: var(--text-muted); transition: color 0.2s, transform 0.15s; padding: 0.25rem 0.4rem; margin: -0.25rem -0.4rem; line-height: 1; } .star-btn:hover { color: var(--accent-yellow); transform: scale(1.1); } .star-btn.starred { color: var(--accent-yellow); } .read-indicator { width: 7px; height: 7px; border-radius: 50%; margin-top: 0.4rem; } .read-indicator.unread { background-color: var(--accent-red); } .item-content { flex: 1; min-width: 0; } .item-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.15rem; } .item-author { font-weight: 700; color: var(--accent-red); font-size: 0.8rem; } .item-time { color: var(--text-muted); font-size: 0.7rem; } .item-text { color: var(--text-primary); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.4; font-size: 0.875rem; } .item-secondary { color: var(--text-secondary); font-size: 0.8rem; margin-top: 0.15rem; } .load-more { padding: 1rem; text-align: center; } /* === 22. Reader-expanded mode ========================================= Hides items panel and expands detail to full width. */ .main.reader-expanded .items-panel { display: none; } .main.reader-expanded .detail-panel { flex: 1; width: auto; min-width: 0; border-left: none; } .main.reader-expanded .item-detail { max-width: 800px; margin: 0 auto; } .reader-back-btn { margin-right: auto; } /* === 8. Detail panel =================================================== */ .detail-panel { width: 400px; min-width: 400px; background-color: var(--bg-secondary); border-left: var(--border-width) solid var(--border); display: none; flex-direction: column; overflow-y: auto; } .detail-header { display: flex; justify-content: flex-end; gap: 0.3rem; padding: 0.4rem; border-bottom: var(--border-width) solid var(--border); background-color: var(--bg-tertiary); } .item-detail { padding: 1.25rem; } .detail-title { font-size: 1.15rem; margin-bottom: 0.75rem; color: var(--text-primary); font-weight: 700; line-height: 1.4; } .detail-meta { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: 0.75rem; color: var(--text-secondary); font-size: 0.8rem; padding-bottom: 0.75rem; border-bottom: var(--border-width) solid var(--border); } .detail-tags { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.75rem; } /* === 13. Tag =========================================================== Canonical: .tag — generic chip used in detail panel. Charter target (F5): add [data-color] variants; collapse .tag-chip, .tag-filter-btn, .source-tag-chip, .bookmark-tag onto this primitive. ========================================================================== */ .tag { background-color: var(--bg-tertiary); border: var(--border-width) solid var(--border); border-radius: var(--radius-sm); padding: 0.1rem 0.5rem; font-size: 0.75rem; color: var(--text-secondary); } /* F2 additions (2026-06-02) — color variants via [data-color] attribute. Used for both .tag and .badge so the same color contract works for both. Consumes previously-unused --accent-cyan and --accent-purple. */ .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); } .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); } .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); } .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); } .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); } .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); } .tag[data-color="muted"], .badge[data-color="muted"] { border-color: var(--border); color: var(--text-muted); background-color: var(--bg-tertiary); } /* .badge — smaller and more compact than .tag; used for counts and inline status indicators. Same color contract via [data-color]. */ .badge { display: inline-flex; align-items: center; gap: 0.2rem; background-color: var(--bg-tertiary); border: var(--border-width) solid var(--border); border-radius: var(--radius-sm); padding: 0.05rem 0.4rem; font-size: 0.7rem; font-weight: 600; color: var(--text-secondary); line-height: 1.3; white-space: nowrap; } .badge--xs { font-size: 0.6rem; padding: 0 0.3rem; } .badge--pill { border-radius: var(--radius-xl); } .badge--filled[data-color="green"] { background-color: var(--accent-green); color: var(--text-on-accent); } .badge--filled[data-color="yellow"] { background-color: var(--accent-yellow); color: var(--text-primary); } .badge--filled[data-color="red"] { background-color: var(--accent-red); color: var(--text-on-accent); } .badge--filled[data-color="blue"] { background-color: var(--accent-blue); color: var(--text-on-accent); } .detail-body { color: var(--text-primary); line-height: 1.7; font-size: 0.9rem; overflow-wrap: break-word; word-break: break-word; } .detail-body p { margin-bottom: 0.5em; } .detail-body pre { white-space: pre-wrap; background-color: var(--bg-tertiary); padding: 0.5rem; border-radius: 4px; overflow-x: auto; } .detail-actions { display: flex; gap: 0.5rem; margin-top: 1.25rem; padding-top: 0.75rem; border-top: var(--border-width) solid var(--border); } /* === 11. Modal ========================================================= Canonical: .modal-overlay (single global at #modal-overlay) + .modal-content (will rename to .modal-container for parity in F5). .tag block (13. Tag) lives below this block in file order. ========================================================================== */ .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: color-mix(in srgb, var(--text-primary) 40%, transparent); display: none; align-items: center; justify-content: center; z-index: 1000; } .modal-content { background-color: var(--bg-primary); border-radius: var(--radius-lg); width: 90%; max-width: 500px; max-height: 80vh; overflow-y: auto; box-shadow: 6px 6px 0 var(--shadow-color); } .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 1.25rem; border-bottom: var(--border-width) solid var(--border); background-color: var(--bg-secondary); border-radius: var(--radius-lg) var(--radius-lg) 0 0; } .modal-header h2 { font-size: 1.1rem; font-weight: 700; color: var(--text-primary); } /* F3 (2026-06-02): step indicator for multi-step modal flows (OAuth, plugin import, encryption setup). Set via BB.ui.setModalStep(n, of). */ .modal-step-indicator { margin-left: 0.75rem; padding: 0.1rem 0.5rem; background-color: var(--bg-tertiary); border: var(--border-width) solid var(--border); border-radius: var(--radius-xl); font-size: 0.7rem; font-weight: 600; color: var(--text-muted); white-space: nowrap; } .modal-body { padding: 1.25rem; } .plugin-list { list-style: none; } .plugin-item { padding: 0.75rem; border: var(--border-width) solid var(--border); border-radius: var(--radius-sm); margin-bottom: 0.5rem; cursor: pointer; transition: all 0.2s; background-color: var(--bg-primary); } .plugin-item:hover { border-color: var(--accent-yellow); background-color: var(--bg-secondary); box-shadow: var(--shadow-brutal); transform: translate(-1px, -1px); } /* F5 (2026-06-02): dropped .plugin-name and .plugin-desc — plugin-list migrated to BB.ui.renderRow in F2-sub; .row-primary / .row-secondary carry the typography now. */ /* F2-sub (2026-06-02): intro paragraph at the top of multi-step modals (replaces inline `style="margin-bottom:1rem;color:var(--text-secondary)"` in feeds.js plugin picker). */ .modal-intro { margin-bottom: 1rem; color: var(--text-secondary); font-size: 0.875rem; } /* F4 additions (2026-06-02) — replace inline styles previously hard-coded in JS render paths. Each rule below retires a specific style="..." or style.cssText violation flagged in the F4 catalogue. */ /* sources.js empty-state (no feeds yet). Was inline cssText + emoji wrapper. F5 may further consolidate onto the .empty-state block once the row primitive migration lands; this fix lives in the sidebar list so it stays a styled
  • . */ .source-item--empty { cursor: default; flex-direction: column; align-items: center; text-align: center; padding: 1.5rem 1rem; color: var(--text-muted); font-size: 0.8rem; line-height: 1.5; } .source-item--empty:hover { background-color: transparent; } .source-item-empty-icon { font-size: 1.5rem; margin-bottom: 0.5rem; opacity: 0.5; } .source-item-empty-strong { color: var(--accent-yellow); font-weight: 700; } .source-item-empty-link { color: var(--accent-yellow); } /* sources.js "+ Saved Filter" button label. Was inline color. */ .source-name--muted { color: var(--text-muted); } /* settings-sync.js renderConnect block. Was three inline styles. */ .sync-connect { text-align: center; padding: 2rem 0; } .sync-connect p { margin-bottom: 1rem; } .sync-connect p + p { margin-bottom: 1.5rem; color: var(--text-secondary); } /* settings-sync.js showCodeEntry spinner. Was three inline styles. */ .sync-auth-spinner { text-align: center; padding: 1rem 0; color: var(--text-secondary); } /* settings-sync.js pricing fine-print. Class already existed but carried inline styles; rules moved here. */ .sync-sub-fine-print { font-size: 0.85rem; opacity: 0.75; margin-top: 0.4rem; } /* settings-sync.js subscribe button row. Layout-only; class-ifying for consistency. */ .sync-sub-actions { display: flex; gap: 0.5rem; margin-top: 0.5rem; } /* mobile.js action sheets — stacked full-width buttons. Replaces inline `style.display='block'; style.width='100%'; style.marginBottom='0.5rem'`. */ .btn-stacked { display: block; width: 100%; margin-bottom: 0.5rem; } .btn-stacked:last-child { margin-bottom: 0; } /* app.js settings theme-import/export row. Layout-only; class-ifying. */ .theme-actions { display: flex; gap: 0.5rem; margin-top: 0.5rem; } /* Default body paragraph rhythm — retires the per-`

    ` inline `margin-bottom: 0.75rem` pattern in feeds.js plugin-warning. */ .modal-body p + p { margin-top: 0.75rem; } /* === 10. Forms ========================================================= Canonical: .form-group + .form-input. Charter target (F5): split .form-input into .form-input / .form-select / .form-textarea so visual treatment per kind is targetable; add .form-label class on labels; add BB.ui.renderFormField helper. ========================================================================== */ .form-group { margin-bottom: 0.75rem; } .form-group label { display: block; margin-bottom: 0.35rem; color: var(--text-secondary); font-size: 0.8rem; font-weight: 700; } .form-input { width: 100%; padding: 0.6rem; border: var(--border-width) solid var(--border); border-radius: var(--radius-sm); background-color: var(--bg-primary); color: var(--text-primary); font-size: 0.875rem; transition: border-color 0.2s, box-shadow 0.2s; } .form-input:focus { outline: none; border-color: var(--accent-yellow); box-shadow: 0 0 0 2px var(--accent-yellow); } /* F2 additions (2026-06-02) — split .form-input into kind-specific primitives so visual treatment per kind is targetable. .form-select and .form-textarea inherit .form-input today; future surface-specific tweaks land on the kind-specific class. .form-label replaces the unclassed `