/* ============================================================================
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.
*/
.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
`