Skip to main content

max / balanced_breakfast

Rename the btn family to button, matching the shared vocabulary makeover-webview emits .button, and goingson already calls it that, so this app was the one holding a private name for a shared component. Renames the family and moves its modifiers onto the -- form goingson uses: btn-primary to button--primary and so on. button--success and button--stacked have no goingson counterpart and stay as they are. Pure rename, no declaration changed. Two neighbouring things that also spell btn are deliberately untouched: the sixteen one-off *-btn classes this file's charter wants collapsed onto the family later, and the 208 camelCase *Btn identifiers in the JS, which are variables rather than classes.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-30 03:37 UTC
Signed with PGP, not checked
Commit: 04a2a18ee139c5e453e1290fd3cbabed0b60f228
Parent: 29c0c3f
13 files changed, +156 insertions, -156 deletions
@@ -19,18 +19,18 @@
19 19 <input type="text" id="search-input" placeholder="Search... (/) | ? for help" class="search-input">
20 20 <span id="search-spinner" class="search-spinner" aria-hidden="true"></span>
21 21 <label for="sort-select" class="sr-only">Sort order</label>
22 - <button id="unread-toggle" class="btn btn-sm" title="Show unread only (U)" aria-pressed="false">Unread</button>
22 + <button id="unread-toggle" class="button button--sm" title="Show unread only (U)" aria-pressed="false">Unread</button>
23 23 <select id="sort-select" class="sort-select" title="Sort order (S)">
24 24 <option value="chronological">Newest First</option>
25 25 <option value="score">By Score</option>
26 26 <option value="unread">Unread First</option>
27 27 <option value="starred">Starred First</option>
28 28 </select>
29 - <button id="mark-all-read-btn" class="btn btn-sm" title="Mark all as read (Shift+A)">Mark All Read</button>
30 - <button id="refresh-btn" class="btn btn-primary" title="Refresh all feeds (R)">Refresh</button>
31 - <button id="add-feed-btn" class="btn btn-success" title="Add a new feed source">+ Add Feed</button>
32 - <button id="help-btn" class="btn btn-sm" title="Keyboard shortcuts (?)" aria-label="Help">?</button>
33 - <button id="settings-btn" class="btn btn-sm" title="Settings" aria-label="Settings">&#9881;</button>
29 + <button id="mark-all-read-btn" class="button button--sm" title="Mark all as read (Shift+A)">Mark All Read</button>
30 + <button id="refresh-btn" class="button button--primary" title="Refresh all feeds (R)">Refresh</button>
31 + <button id="add-feed-btn" class="button button--success" title="Add a new feed source">+ Add Feed</button>
32 + <button id="help-btn" class="button button--sm" title="Keyboard shortcuts (?)" aria-label="Help">?</button>
33 + <button id="settings-btn" class="button button--sm" title="Settings" aria-label="Settings">&#9881;</button>
34 34 </div>
35 35 </header>
36 36
@@ -52,7 +52,7 @@
52 52 <span id="saved-count" class="source-count">0</span>
53 53 </div>
54 54 <div class="sidebar-footer">
55 - <button id="sync-settings-btn" class="btn btn-sm" title="Cloud Sync Settings">Sync</button>
55 + <button id="sync-settings-btn" class="button button--sm" title="Cloud Sync Settings">Sync</button>
56 56 </div>
57 57 </aside>
58 58
@@ -69,15 +69,15 @@
69 69 </div>
70 70 <ul id="items-list" class="items-list well" aria-live="polite"></ul>
71 71 <div id="load-more" class="load-more" style="display: none;">
72 - <button id="load-more-btn" class="btn">Load More</button>
72 + <button id="load-more-btn" class="button">Load More</button>
73 73 </div>
74 74 </section>
75 75
76 76 <!-- Item detail panel -->
77 77 <aside class="detail-panel" id="detail-panel">
78 78 <div class="detail-header">
79 - <button id="save-detail" class="btn btn-sm" aria-label="Add to Reading List" title="Add to Reading List">Bookmark</button>
80 - <button id="close-detail" class="btn btn-sm" aria-label="Close">&times;</button>
79 + <button id="save-detail" class="button button--sm" aria-label="Add to Reading List" title="Add to Reading List">Bookmark</button>
80 + <button id="close-detail" class="button button--sm" aria-label="Close">&times;</button>
81 81 </div>
82 82 <article id="item-detail" class="item-detail"></article>
83 83 </aside>
@@ -107,7 +107,7 @@
107 107 <div class="modal-content" role="dialog" aria-modal="true" aria-labelledby="modal-title">
108 108 <div class="modal-header">
109 109 <h2 id="modal-title">Modal</h2>
110 - <button id="close-modal" class="btn btn-sm" aria-label="Close">&times;</button>
110 + <button id="close-modal" class="button button--sm" aria-label="Close">&times;</button>
111 111 </div>
112 112 <div id="modal-body" class="modal-body"></div>
113 113 </div>
@@ -13,11 +13,11 @@
13 13 section}, .stack +
14 14 .stack-{peer,group,
15 15 section})
16 - 3. Use a COMPONENT PRIMITIVE for a UI element (.btn, .card, .modal,
16 + 3. Use a COMPONENT PRIMITIVE for a UI element (.button, .card, .modal,
17 17 .form-input,
18 18 .form-select, .badge,
19 19 .tag, .toast)
20 - 4. Extend a primitive with a modifier (.btn-primary, .btn-sm,
20 + 4. Extend a primitive with a modifier (.button--primary, .button--sm,
21 21 .form-select--ghost)
22 22 5. ONLY THEN consider a new class, and add it to the right BAND below
23 23
@@ -62,7 +62,7 @@
62 62 8. Detail panel
63 63
64 64 BAND: COMPONENT PRIMITIVES
65 - 9. Buttons: .btn family
65 + 9. Buttons: .button family
66 66 10. Forms: .form-group, .form-input
67 67 11. Modal: .modal-overlay/.modal-content
68 68 12. Toast: .toast
@@ -171,7 +171,7 @@
171 171 Geometry is app-local by contract: a theme overrides color only, so
172 172 border width, bevel thickness and radius cannot be reached from a
173 173 theme file. */
174 - --accent-red-hover: color-mix(in oklch, var(--danger) 90%, white); /* composition: .btn-danger:hover */
174 + --accent-red-hover: color-mix(in oklch, var(--danger) 90%, white); /* composition: .button--danger:hover */
175 175 --accent-yellow-light: color-mix(in oklch, var(--category-four) 82%, white); /* composition */
176 176 --shadow-color: color-mix(in oklch, var(--content-muted) 88%, black); /* composition */
177 177 --border-width: 1px; /* invariant */
@@ -429,7 +429,7 @@
429 429 <div class="empty-state">
430 430 <div class="empty-state-icon">...</div>
431 431 <p class="empty-state-text">No items</p>
432 - <button class="btn btn-primary">Add one</button>
432 + <button class="button button--primary">Add one</button>
433 433 </div>
434 434 */
435 435 .empty-state {
@@ -572,11 +572,11 @@
572 572 }
573 573
574 574 /* === 9. Buttons ========================================================
575 - Canonical: .btn (+ .btn-primary, .btn-secondary, .btn-success, .btn-danger,
576 - .btn-icon, .btn-text, .btn-link; size .btn-sm; state .btn-loading).
575 + Canonical: .button (+ .button--primary, .button--secondary, .button--success, .button--danger,
576 + .button--icon, .button--text, .button--link; size .button--sm; state .button--loading).
577 577 Charter target: collapse remaining one-off *-btn classes onto this family.
578 578 ========================================================================== */
579 - .btn {
579 + .button {
580 580 padding: var(--gap-peer) var(--gap-group);
581 581 border: var(--border-width) solid var(--border);
582 582 border-radius: var(--radius-sm);
@@ -587,49 +587,49 @@
587 587 font-size: var(--font-size-base);
588 588 box-shadow: var(--bevel-raised);
589 589 }
590 - .btn:hover {
590 + .button:hover {
591 591 background-color: var(--surface-sunken);
592 592 border-color: var(--border-strong);
593 593 }
594 594 /* Pressed inverts the bevel and drops the fill a step. No offset, no
595 595 translate: the button reads as pushed into the page, and every modifier
596 596 below inherits the state for free. */
597 - .btn:active {
597 + .button:active {
598 598 background-color: var(--surface-sunken);
599 599 box-shadow: var(--bevel-inset);
600 600 }
601 601 /* Disabled keeps its bevel. The object is still there; greying the content
602 602 is what says the action is unavailable. */
603 - .btn:disabled {
603 + .button:disabled {
604 604 color: var(--content-muted);
605 605 border-color: var(--content-muted);
606 606 cursor: not-allowed;
607 607 }
608 - .btn:disabled:active { box-shadow: var(--bevel-raised); }
609 - .btn-primary {
608 + .button:disabled:active { box-shadow: var(--bevel-raised); }
609 + .button--primary {
610 610 background-color: var(--action);
611 611 border-color: var(--action);
612 612 color: var(--content-on-action);
613 613 }
614 - .btn-primary:hover { background-color: var(--action-hover); border-color: var(--action-hover); }
615 - .btn-success {
614 + .button--primary:hover { background-color: var(--action-hover); border-color: var(--action-hover); }
615 + .button--success {
616 616 background-color: var(--category-four);
617 617 border-color: var(--category-four);
618 618 color: var(--content-on-action);
619 619 }
620 - .btn-success:hover { background-color: var(--accent-yellow-light); border-color: var(--accent-yellow-light); }
621 - /* .btn-sm: canonical small-button size (see wiki note `bb-design-system`). */
622 - .btn-sm { padding: var(--gap-bound) var(--gap-peer); font-size: var(--font-size-md); }
623 - .btn-sm.active { background: var(--category-four); color: var(--content-on-action); border-color: var(--category-four); }
620 + .button--success:hover { background-color: var(--accent-yellow-light); border-color: var(--accent-yellow-light); }
621 + /* .button--sm: canonical small-button size (see wiki note `bb-design-system`). */
622 + .button--sm { padding: var(--gap-bound) var(--gap-peer); font-size: var(--font-size-md); }
623 + .button--sm.active { background: var(--category-four); color: var(--content-on-action); border-color: var(--category-four); }
624 624
625 - /* .btn-danger: destructive action. Consumes the previously-unused
625 + /* .button--danger: destructive action. Consumes the previously-unused
626 626 --accent-red-hover token. Replaces .hp-action-danger in F5. */
627 - .btn-danger {
627 + .button--danger {
628 628 background-color: var(--danger);
629 629 border-color: var(--danger);
630 630 color: var(--content-on-action);
631 631 }
632 - .btn-danger:hover { background-color: var(--accent-red-hover); border-color: var(--accent-red-hover); }
632 + .button--danger:hover { background-color: var(--accent-red-hover); border-color: var(--accent-red-hover); }
633 633
634 634 /* === N. Row primitive (F2-sub addition, 2026-06-02) ===================
635 635 Canonical slot layout: [icon] [primary · badges] [meta] [actions].
@@ -923,12 +923,12 @@
923 923
924 924 /* mobile.js action sheets: stacked full-width buttons. Replaces inline
925 925 `style.display='block'; style.width='100%'; style.marginBottom='0.5rem'`. */
926 - .btn-stacked {
926 + .button--stacked {
927 927 display: block;
928 928 width: 100%;
929 929 margin-bottom: var(--gap-peer);
930 930 }
931 - .btn-stacked:last-child { margin-bottom: 0; }
931 + .button--stacked:last-child { margin-bottom: 0; }
932 932
933 933 /* app.js settings theme-import/export row. Layout-only; class-ifying. */
934 934 .theme-actions {
@@ -1239,7 +1239,7 @@
1239 1239 }
1240 1240
1241 1241 /* === 19. Health popover ================================================
1242 - Floating panel from gear icon. Actions use the .btn family (.hp-actions .btn
1242 + Floating panel from gear icon. Actions use the .button family (.hp-actions .button
1243 1243 fills the row). .hp-row is a one-off label/value line (kept).
1244 1244 ========================================================================== */
1245 1245 .health-popover {
@@ -1295,9 +1295,9 @@
1295 1295 padding-top: var(--gap-peer);
1296 1296 border-top: var(--border-width) solid var(--border);
1297 1297 }
1298 - /* Popover actions are the .btn family (Mark Read / Edit / Tags / Delete);
1298 + /* Popover actions are the .button family (Mark Read / Edit / Tags / Delete);
1299 1299 they fill the row equally. */
1300 - .hp-actions .btn { flex: 1; }
1300 + .hp-actions .button { flex: 1; }
1301 1301
1302 1302
1303 1303 /* === 20. Sources list: sidebar entries ================================
@@ -1667,7 +1667,7 @@
1667 1667 for inputs (avoids layout shift). Rule: every new interactive
1668 1668 primitive must appear in one of these selector lists.
1669 1669 ========================================================================== */
1670 - .btn:focus-visible,
1670 + .button:focus-visible,
1671 1671 .source-item:focus-visible,
1672 1672 .star-btn:focus-visible,
1673 1673 .source-delete:focus-visible,
@@ -1953,9 +1953,9 @@
1953 1953 .source-item.active:hover { background-color: var(--surface-sunken); }
1954 1954 .sidebar-saved:hover { background-color: transparent; }
1955 1955 .sidebar-saved.active:hover { background-color: var(--surface-sunken); }
1956 - .btn:hover { background-color: var(--surface-page); border-color: var(--border); }
1957 - .btn-primary:hover { background-color: var(--action); border-color: var(--action); }
1958 - .btn-success:hover { background-color: var(--category-four); border-color: var(--category-four); }
1956 + .button:hover { background-color: var(--surface-page); border-color: var(--border); }
1957 + .button--primary:hover { background-color: var(--action); border-color: var(--action); }
1958 + .button--success:hover { background-color: var(--category-four); border-color: var(--category-four); }
1959 1959 .star-btn:hover { color: var(--content-muted); transform: none; }
1960 1960 .star-btn.starred:hover { color: var(--category-four); }
1961 1961 .tag-chip:hover { border-color: var(--border); color: var(--content-secondary); }
@@ -111,10 +111,10 @@
111 111 * Toggle the "unread only" filter on/off.
112 112 */
113 113 function toggleUnreadFilter() {
114 - const btn = document.getElementById('unread-toggle');
115 - const active = btn.getAttribute('aria-pressed') === 'true';
116 - btn.setAttribute('aria-pressed', !active);
117 - btn.classList.toggle('active', !active);
114 + const button = document.getElementById('unread-toggle');
115 + const active = button.getAttribute('aria-pressed') === 'true';
116 + button.setAttribute('aria-pressed', !active);
117 + button.classList.toggle('active', !active);
118 118 BB.state.set('unreadOnly', !active);
119 119 BB.state.resetPagination(true);
120 120 BB.items.load();
@@ -369,13 +369,13 @@
369 369 themeActions.className = 'theme-actions';
370 370
371 371 const themeImportBtn = document.createElement('button');
372 - themeImportBtn.className = 'btn btn-sm';
372 + themeImportBtn.className = 'button button--sm';
373 373 themeImportBtn.textContent = 'Import Theme';
374 374 themeImportBtn.onclick = () => BB.themes.importTheme();
375 375 themeActions.appendChild(themeImportBtn);
376 376
377 377 const themeExportBtn = document.createElement('button');
378 - themeExportBtn.className = 'btn btn-sm';
378 + themeExportBtn.className = 'button button--sm';
379 379 themeExportBtn.textContent = 'Export Current';
380 380 themeExportBtn.onclick = () => BB.themes.exportTheme();
381 381 themeActions.appendChild(themeExportBtn);
@@ -394,13 +394,13 @@
394 394 dataActions.className = 'row-flex row-flex-peer';
395 395
396 396 const importBtn = document.createElement('button');
397 - importBtn.className = 'btn btn-sm';
397 + importBtn.className = 'button button--sm';
398 398 importBtn.textContent = 'Import OPML';
399 399 importBtn.onclick = () => { BB.ui.closeModal(); BB.feeds.importOpml(); };
400 400 dataActions.appendChild(importBtn);
401 401
402 402 const exportBtn = document.createElement('button');
403 - exportBtn.className = 'btn btn-sm';
403 + exportBtn.className = 'button button--sm';
404 404 exportBtn.textContent = 'Export OPML';
405 405 exportBtn.onclick = () => { BB.ui.closeModal(); BB.feeds.exportOpml(); };
406 406 dataActions.appendChild(exportBtn);
@@ -416,7 +416,7 @@
416 416 syncLabel.textContent = 'Cloud Sync';
417 417 syncGroup.appendChild(syncLabel);
418 418 const syncBtn = document.createElement('button');
419 - syncBtn.className = 'btn btn-sm';
419 + syncBtn.className = 'button button--sm';
420 420 syncBtn.textContent = 'Sync Settings';
421 421 syncBtn.onclick = () => { BB.ui.closeModal(); BB.sync.openSettings(); };
422 422 syncGroup.appendChild(syncBtn);
@@ -426,7 +426,7 @@
426 426 const actions = document.createElement('div');
427 427 actions.className = 'form-actions';
428 428 const closeBtn = document.createElement('button');
429 - closeBtn.className = 'btn';
429 + closeBtn.className = 'button';
430 430 closeBtn.textContent = 'Close';
431 431 closeBtn.addEventListener('click', BB.ui.closeModal);
432 432 actions.appendChild(closeBtn);
@@ -492,11 +492,11 @@
492 492 const cta = document.createElement('div');
493 493 cta.className = 'welcome-cta';
494 494 const addBtn = document.createElement('button');
495 - addBtn.className = 'btn btn-success';
495 + addBtn.className = 'button button--success';
496 496 addBtn.textContent = 'Add Your First Feed';
497 497 addBtn.addEventListener('click', BB.feeds.openAddFeed);
498 498 const exploreBtn = document.createElement('button');
499 - exploreBtn.className = 'btn';
499 + exploreBtn.className = 'button';
500 500 exploreBtn.textContent = 'Explore First';
501 501 exploreBtn.addEventListener('click', BB.ui.closeModal);
502 502 cta.appendChild(addBtn);
@@ -103,12 +103,12 @@
103 103
104 104 // Actions slot: Open + more (hover-revealed via .row-actions).
105 105 const openBtn = document.createElement('button');
106 - openBtn.className = 'btn btn-sm';
106 + openBtn.className = 'button button--sm';
107 107 openBtn.title = 'Open URL';
108 108 openBtn.textContent = 'Open';
109 109 openBtn.onclick = (e) => { e.stopPropagation(); openUrl(bm.url); };
110 110 const moreBtn = document.createElement('button');
111 - moreBtn.className = 'btn btn-sm';
111 + moreBtn.className = 'button button--sm';
112 112 moreBtn.title = 'More actions';
113 113 moreBtn.textContent = '\u22EF';
114 114 moreBtn.onclick = (e) => { e.stopPropagation(); showContextMenu(e, bm.id); };
@@ -168,16 +168,16 @@
168 168 bar.appendChild(allBtn);
169 169
170 170 for (const tag of tags) {
171 - const btn = document.createElement('button');
172 - btn.className = 'tag-chip' + (currentTag === tag ? ' active' : '');
173 - btn.textContent = tag;
174 - btn.onclick = () => load(tag);
175 - bar.appendChild(btn);
171 + const button = document.createElement('button');
172 + button.className = 'tag-chip' + (currentTag === tag ? ' active' : '');
173 + button.textContent = tag;
174 + button.onclick = () => load(tag);
175 + bar.appendChild(button);
176 176 }
177 177
178 178 // "Add Bookmark" button
179 179 const addBtn = document.createElement('button');
180 - addBtn.className = 'btn btn-sm btn-primary';
180 + addBtn.className = 'button button--sm button--primary';
181 181 addBtn.textContent = '+ Add';
182 182 addBtn.onclick = () => openAddModal();
183 183 bar.appendChild(addBtn);
@@ -243,8 +243,8 @@
243 243 <textarea id="bm-notes" class="form-input" rows="3" placeholder="Personal notes..."></textarea>
244 244 </div>
245 245 <div class="form-actions">
246 - <button class="btn" id="bm-cancel">Cancel</button>
247 - <button class="btn btn-primary" id="bm-save">Save</button>
246 + <button class="button" id="bm-cancel">Cancel</button>
247 + <button class="button button--primary" id="bm-save">Save</button>
248 248 </div>
249 249 `;
250 250
@@ -317,8 +317,8 @@
317 317 <label><input type="checkbox" id="bm-edit-pinned" ${bm.isPinned ? 'checked' : ''}> Pin to top</label>
318 318 </div>
319 319 <div class="form-actions">
320 - <button class="btn" id="bm-edit-cancel">Cancel</button>
321 - <button class="btn btn-primary" id="bm-edit-save">Save</button>
320 + <button class="button" id="bm-edit-cancel">Cancel</button>
321 + <button class="button button--primary" id="bm-edit-save">Save</button>
322 322 </div>
323 323 `;
324 324
@@ -370,8 +370,8 @@
370 370 <input type="text" id="bm-tags-input" class="form-input" value="${escapeAttr(bm.tags.join(', '))}">
371 371 </div>
372 372 <div class="form-actions">
373 - <button class="btn" id="bm-tags-cancel">Cancel</button>
374 - <button class="btn btn-primary" id="bm-tags-save">Save</button>
373 + <button class="button" id="bm-tags-cancel">Cancel</button>
374 + <button class="button button--primary" id="bm-tags-save">Save</button>
375 375 </div>
376 376 `;
377 377
@@ -22,11 +22,11 @@
22 22 toast.textContent = message;
23 23
24 24 if (opts.action) {
25 - const btn = document.createElement('button');
26 - btn.className = 'toast-action';
27 - btn.textContent = opts.action.label;
28 - btn.onclick = () => { toast.remove(); opts.action.fn(); };
29 - toast.appendChild(btn);
25 + const button = document.createElement('button');
26 + button.className = 'toast-action';
27 + button.textContent = opts.action.label;
28 + button.onclick = () => { toast.remove(); opts.action.fn(); };
29 + toast.appendChild(button);
30 30 }
31 31
32 32 container.appendChild(toast);
@@ -90,14 +90,14 @@
90 90
91 91 const cancelBtn = document.createElement('button');
92 92 cancelBtn.type = 'button';
93 - cancelBtn.className = 'btn';
93 + cancelBtn.className = 'button';
94 94 cancelBtn.textContent = 'Cancel';
95 95 cancelBtn.onclick = closeModal;
96 96 actions.appendChild(cancelBtn);
97 97
98 98 const submitBtn = document.createElement('button');
99 99 submitBtn.type = 'submit';
100 - submitBtn.className = 'btn btn-primary';
100 + submitBtn.className = 'button button--primary';
101 101 submitBtn.textContent = opts.submitLabel || 'Save';
102 102 actions.appendChild(submitBtn);
103 103
@@ -176,14 +176,14 @@
176 176
177 177 const cancelBtn = document.createElement('button');
178 178 cancelBtn.type = 'button';
179 - cancelBtn.className = 'btn';
179 + cancelBtn.className = 'button';
180 180 cancelBtn.textContent = 'Cancel';
181 181 cancelBtn.onclick = () => { overlay.style.display = 'none'; resolve(false); };
182 182 actions.appendChild(cancelBtn);
183 183
184 184 const confirmBtn = document.createElement('button');
185 185 confirmBtn.type = 'button';
186 - confirmBtn.className = danger ? 'btn btn-primary' : 'btn btn-success';
186 + confirmBtn.className = danger ? 'button button--primary' : 'button button--success';
187 187 confirmBtn.textContent = confirmLabel;
188 188 confirmBtn.onclick = () => { overlay.style.display = 'none'; resolve(true); };
189 189 actions.appendChild(confirmBtn);
@@ -225,14 +225,14 @@
225 225
226 226 const cancelBtn = document.createElement('button');
227 227 cancelBtn.type = 'button';
228 - cancelBtn.className = 'btn';
228 + cancelBtn.className = 'button';
229 229 cancelBtn.textContent = 'Cancel';
230 230 cancelBtn.onclick = () => { overlay.style.display = 'none'; resolve(false); };
231 231 actions.appendChild(cancelBtn);
232 232
233 233 const confirmBtn = document.createElement('button');
234 234 confirmBtn.type = 'button';
235 - confirmBtn.className = danger ? 'btn btn-danger' : 'btn btn-primary';
235 + confirmBtn.className = danger ? 'button button--danger' : 'button button--primary';
236 236 confirmBtn.textContent = confirmLabel;
237 237 confirmBtn.onclick = () => { overlay.style.display = 'none'; resolve(true); };
238 238 actions.appendChild(confirmBtn);
@@ -299,11 +299,11 @@
299 299 el.appendChild(text);
300 300
301 301 if (opts.buttonLabel && opts.onClick) {
302 - const btn = document.createElement('button');
303 - btn.className = 'btn btn-primary';
304 - btn.textContent = opts.buttonLabel;
305 - btn.onclick = opts.onClick;
306 - el.appendChild(btn);
302 + const button = document.createElement('button');
303 + button.className = 'button button--primary';
304 + button.textContent = opts.buttonLabel;
305 + button.onclick = opts.onClick;
306 + el.appendChild(button);
307 307 }
308 308
309 309 container.appendChild(el);
@@ -329,11 +329,11 @@
329 329 menu.style.top = event.clientY + 'px';
330 330
331 331 for (const item of items) {
332 - const btn = document.createElement('button');
333 - btn.className = 'context-menu-item' + (item.danger ? ' context-menu-item--danger' : '');
334 - btn.textContent = item.label;
335 - btn.onclick = () => { menu.remove(); item.fn(); };
336 - menu.appendChild(btn);
332 + const button = document.createElement('button');
333 + button.className = 'context-menu-item' + (item.danger ? ' context-menu-item--danger' : '');
334 + button.textContent = item.label;
335 + button.onclick = () => { menu.remove(); item.fn(); };
336 + menu.appendChild(button);
337 337 }
338 338
339 339 document.body.appendChild(menu);
@@ -375,10 +375,10 @@
375 375 msgEl.textContent = message;
376 376 toast.appendChild(msgEl);
377 377
378 - const btn = document.createElement('button');
379 - btn.className = 'undo-btn';
380 - btn.textContent = 'Undo';
381 - toast.appendChild(btn);
378 + const button = document.createElement('button');
379 + button.className = 'undo-btn';
380 + button.textContent = 'Undo';
381 + toast.appendChild(button);
382 382
383 383 const countdown = document.createElement('span');
384 384 countdown.className = 'undo-countdown';
@@ -392,7 +392,7 @@
392 392 }, 1000);
393 393
394 394 const dismiss = () => { clearInterval(tick); toast.remove(); };
395 - btn.onclick = () => { dismiss(); undoFn(); };
395 + button.onclick = () => { dismiss(); undoFn(); };
396 396
397 397 container.appendChild(toast);
398 398 setTimeout(dismiss, duration);
@@ -107,26 +107,26 @@
107 107 actions.className = 'detail-actions';
108 108
109 109 const readBtn = document.createElement('button');
110 - readBtn.className = 'btn';
110 + readBtn.className = 'button';
111 111 readBtn.textContent = item.isRead ? 'Mark Unread' : 'Mark Read';
112 112 readBtn.addEventListener('click', toggleRead);
113 113 actions.appendChild(readBtn);
114 114
115 115 const starBtn = document.createElement('button');
116 - starBtn.className = 'btn';
116 + starBtn.className = 'button';
117 117 starBtn.textContent = item.isStarred ? 'Unstar' : 'Star';
118 118 starBtn.addEventListener('click', toggleStar);
119 119 actions.appendChild(starBtn);
120 120
121 121 if (item.url) {
122 122 const readerBtn = document.createElement('button');
123 - readerBtn.className = 'btn';
123 + readerBtn.className = 'button';
124 124 readerBtn.textContent = 'Reader View';
125 125 readerBtn.addEventListener('click', readerView);
126 126 actions.appendChild(readerBtn);
127 127
128 128 const openBtn = document.createElement('button');
129 - openBtn.className = 'btn btn-primary';
129 + openBtn.className = 'button button--primary';
130 130 openBtn.textContent = 'Open';
131 131 openBtn.addEventListener('click', openUrl);
132 132 actions.appendChild(openBtn);
@@ -134,7 +134,7 @@
134 134
135 135 if (item.supernoteEligible) {
136 136 const snBtn = document.createElement('button');
137 - snBtn.className = 'btn';
137 + snBtn.className = 'button';
138 138 snBtn.textContent = 'Send to Supernote';
139 139 snBtn.addEventListener('click', pushToSupernote);
140 140 actions.appendChild(snBtn);
@@ -143,17 +143,17 @@
143 143 // Plugin-declared custom action buttons
144 144 if (item.actions && item.actions.length > 0) {
145 145 for (const action of item.actions) {
146 - const btn = document.createElement('button');
147 - btn.className = 'btn';
148 - btn.textContent = action.label;
146 + const button = document.createElement('button');
147 + button.className = 'button';
148 + button.textContent = action.label;
149 149 if (action.actionType === 'open') {
150 - btn.addEventListener('click', () => {
150 + button.addEventListener('click', () => {
151 151 window.__TAURI__.shell.open(action.url).catch(() => {
152 152 BB.ui.showToast('Failed to open URL', 'error');
153 153 });
154 154 });
155 155 } else if (action.actionType === 'download') {
156 - btn.addEventListener('click', async () => {
156 + button.addEventListener('click', async () => {
157 157 BB.ui.showToast('Downloading...');
158 158 try {
159 159 await BB.api.actions.downloadAndOpen(action.url);
@@ -162,7 +162,7 @@
162 162 }
163 163 });
164 164 }
165 - actions.appendChild(btn);
165 + actions.appendChild(button);
166 166 }
167 167 }
168 168
@@ -187,7 +187,7 @@
187 187 const header = document.querySelector('.detail-header');
188 188 if (!header.querySelector('.reader-back-btn')) {
189 189 const backBtn = document.createElement('button');
190 - backBtn.className = 'btn btn-sm reader-back-btn';
190 + backBtn.className = 'button button--sm reader-back-btn';
191 191 backBtn.textContent = '\u2190 Back';
192 192 backBtn.addEventListener('click', collapseReader);
193 193 header.prepend(backBtn);
@@ -44,14 +44,14 @@
44 44
45 45 const cancelBtn = document.createElement('button');
46 46 cancelBtn.type = 'button';
47 - cancelBtn.className = 'btn';
47 + cancelBtn.className = 'button';
48 48 cancelBtn.textContent = 'Cancel';
49 49 cancelBtn.onclick = () => { BB.ui.closeModal(); resolve(false); };
50 50 actions.appendChild(cancelBtn);
51 51
52 52 const acceptBtn = document.createElement('button');
53 53 acceptBtn.type = 'button';
54 - acceptBtn.className = 'btn btn-primary';
54 + acceptBtn.className = 'button button--primary';
55 55 acceptBtn.textContent = 'I Understand';
56 56 acceptBtn.onclick = () => {
57 57 localStorage.setItem('bb_plugin_warning_ack', '1');
@@ -189,11 +189,11 @@
189 189 * @returns {Promise<void>}
190 190 */
191 191 async function refresh() {
192 - const btn = document.getElementById('refresh-btn');
192 + const button = document.getElementById('refresh-btn');
193 193 // Lock button width before changing text to prevent header reflow
194 - btn.style.width = btn.offsetWidth + 'px';
195 - btn.disabled = true;
196 - btn.textContent = 'Refreshing...';
194 + button.style.width = button.offsetWidth + 'px';
195 + button.disabled = true;
196 + button.textContent = 'Refreshing...';
197 197
198 198 const header = document.querySelector('.header');
199 199 const progress = BB.ui.showProgress(header);
@@ -233,9 +233,9 @@
233 233 } finally {
234 234 if (unlisten) unlisten();
235 235 setTimeout(() => progress.remove(), 400);
236 - btn.disabled = false;
237 - btn.textContent = 'Refresh';
238 - btn.style.width = '';
236 + button.disabled = false;
237 + button.textContent = 'Refresh';
238 + button.style.width = '';
239 239 }
240 240 }
241 241
@@ -273,14 +273,14 @@
273 273 { label: 'Delete', action: () => BB.sources.deleteFeed(source), danger: true },
274 274 ];
275 275 // F4 (2026-06-02): inline styles + JS-set color retired in favor
276 - // of .btn-stacked utility and .btn-danger modifier (charter rule:
276 + // of .button--stacked utility and .button--danger modifier (charter rule:
277 277 // destructive intent expressed via class, not inline color).
278 278 for (const a of actions) {
279 - const btn = document.createElement('button');
280 - btn.className = 'btn btn-stacked' + (a.danger ? ' btn-danger' : '');
281 - btn.textContent = a.label;
282 - btn.addEventListener('click', () => { BB.ui.closeModal(); a.action(); });
283 - body.appendChild(btn);
279 + const button = document.createElement('button');
280 + button.className = 'button button--stacked' + (a.danger ? ' button--danger' : '');
281 + button.textContent = a.label;
282 + button.addEventListener('click', () => { BB.ui.closeModal(); a.action(); });
283 + body.appendChild(button);
284 284 }
285 285 BB.ui.openModal();
286 286 }
@@ -305,14 +305,14 @@
305 305 actions.push({ label: 'Open in Browser', action: () => BB.detail.openUrl() });
306 306 }
307 307
308 - // F4 (2026-06-02): .btn-stacked utility retires the inline
308 + // F4 (2026-06-02): .button--stacked utility retires the inline
309 309 // style.display/width/marginBottom triple.
310 310 for (const a of actions) {
311 - const btn = document.createElement('button');
312 - btn.className = 'btn btn-stacked';
313 - btn.textContent = a.label;
314 - btn.addEventListener('click', () => { BB.ui.closeModal(); a.action(); });
315 - body.appendChild(btn);
311 + const button = document.createElement('button');
312 + button.className = 'button button--stacked';
313 + button.textContent = a.label;
314 + button.addEventListener('click', () => { BB.ui.closeModal(); a.action(); });
315 + body.appendChild(button);
316 316 }
317 317
318 318 BB.ui.openModal();
@@ -61,9 +61,9 @@
61 61 const popover = document.getElementById('mobile-more-popover');
62 62 if (popover) {
63 63 popover.addEventListener('click', (e) => {
64 - const btn = e.target.closest('[data-action]');
65 - if (btn) {
66 - handleMoreAction(btn.dataset.action);
64 + const button = e.target.closest('[data-action]');
65 + if (button) {
66 + handleMoreAction(button.dataset.action);
67 67 closeMorePopover();
68 68 }
69 69 });
@@ -163,7 +163,7 @@
163 163 const header = document.querySelector('.detail-header');
164 164 if (header && !header.querySelector('.mobile-back-btn')) {
165 165 const backBtn = document.createElement('button');
166 - backBtn.className = 'btn btn-sm mobile-back-btn';
166 + backBtn.className = 'button button--sm mobile-back-btn';
167 167 backBtn.textContent = '\u2190 Back';
168 168 backBtn.addEventListener('click', () => BB.detail.close());
169 169 header.prepend(backBtn);
@@ -68,12 +68,12 @@
68 68 <div class="form-group">
69 69 <label>Conditions (all must match)</label>
70 70 <div id="qf-conditions"></div>
71 - <button class="btn btn-sm add-condition-btn" type="button">+ Add Condition</button>
71 + <button class="button button--sm add-condition-btn" type="button">+ Add Condition</button>
72 72 </div>
73 73 <div class="match-preview" id="qf-match-preview"></div>
74 74 <div class="modal-actions">
75 - <button class="btn" id="qf-cancel">Cancel</button>
76 - <button class="btn btn-primary" id="qf-save">${isEdit ? 'Save' : 'Create'}</button>
75 + <button class="button" id="qf-cancel">Cancel</button>
76 + <button class="button button--primary" id="qf-save">${isEdit ? 'Save' : 'Create'}</button>
77 77 </div>
78 78 </div>
79 79 `;
@@ -123,7 +123,7 @@
123 123 }
124 124
125 125 row.innerHTML = `${fieldSelect} ${opSelect} ${valueInput}
126 - <button class="btn btn-sm cond-remove" data-idx="${idx}" title="Remove" aria-label="Remove condition">&times;</button>`;
126 + <button class="button button--sm cond-remove" data-idx="${idx}" title="Remove" aria-label="Remove condition">&times;</button>`;
127 127
128 128 // Field change -> update operators and re-render
129 129 row.querySelector('.cond-field').onchange = (e) => {
@@ -61,11 +61,11 @@
61 61 div.innerHTML =
62 62 '<p>Sync your feeds and preferences across devices via Makenot.work.</p>' +
63 63 '<p>All data is encrypted on your device before it leaves.</p>';
64 - const btn = document.createElement('button');
65 - btn.className = 'btn btn-primary';
66 - btn.textContent = 'Connect to Makenot.work';
67 - btn.onclick = () => startAuth();
68 - div.appendChild(btn);
64 + const button = document.createElement('button');
65 + button.className = 'button button--primary';
66 + button.textContent = 'Connect to Makenot.work';
67 + button.onclick = () => startAuth();
68 + div.appendChild(button);
69 69 container.appendChild(div);
70 70 }
71 71
@@ -136,7 +136,7 @@
136 136
137 137 const submitBtn = document.createElement('button');
138 138 submitBtn.type = 'submit';
139 - submitBtn.className = 'btn btn-primary';
139 + submitBtn.className = 'button button--primary';
140 140 submitBtn.textContent = 'Complete Auth';
141 141 form.appendChild(submitBtn);
142 142
@@ -271,7 +271,7 @@
271 271 actions.className = 'form-actions';
272 272 const submitBtn = document.createElement('button');
273 273 submitBtn.type = 'submit';
274 - submitBtn.className = 'btn btn-primary';
274 + submitBtn.className = 'button button--primary';
275 275 submitBtn.textContent = hasKey ? 'Unlock' : 'Set Password';
276 276 actions.appendChild(submitBtn);
277 277 form.appendChild(actions);
@@ -331,7 +331,7 @@
331 331
332 332 // Sync now button
333 333 const syncBtn = document.createElement('button');
334 - syncBtn.className = 'btn btn-primary sync-action-spaced';
334 + syncBtn.className = 'button button--primary sync-action-spaced';
335 335 syncBtn.textContent = 'Sync Now';
336 336 syncBtn.onclick = async () => {
337 337 syncBtn.disabled = true;
@@ -408,7 +408,7 @@
408 408
409 409 // Disconnect
410 410 const disconnectBtn = document.createElement('button');
411 - disconnectBtn.className = 'btn sync-disconnect';
411 + disconnectBtn.className = 'button sync-disconnect';
412 412 disconnectBtn.textContent = 'Disconnect';
413 413 disconnectBtn.onclick = async () => {
414 414 // F6 fix (2026-06-02): was native confirm(), replaced per
@@ -469,8 +469,8 @@
469 469 'On a monthly plan we pay that fee twelve times a year; annual pays it once.' +
470 470 '</p>' +
471 471 '<div class="sync-sub-actions">' +
472 - '<button class="btn btn-primary" id="sync-sub-annual">Subscribe (' + annual + '/year)</button>' +
473 - '<button class="btn" id="sync-sub-monthly">' + monthly + '/month</button>' +
472 + '<button class="button button--primary" id="sync-sub-annual">Subscribe (' + annual + '/year)</button>' +
473 + '<button class="button" id="sync-sub-monthly">' + monthly + '/month</button>' +
474 474 '</div>';
475 475 }
476 476 } catch (_) { /* fall through */ }
@@ -311,10 +311,10 @@
311 311
312 312 // Action buttons
313 313 const actions = `<div class="hp-actions">
314 - <button class="btn btn-sm" data-action="mark-read">Mark All Read</button>
315 - <button class="btn btn-sm" data-action="edit">Edit Feed</button>
316 - <button class="btn btn-sm" data-action="tags">Edit Tags</button>
317 - <button class="btn btn-sm btn-danger" data-action="delete">Delete</button>
314 + <button class="button button--sm" data-action="mark-read">Mark All Read</button>
315 + <button class="button button--sm" data-action="edit">Edit Feed</button>
316 + <button class="button button--sm" data-action="tags">Edit Tags</button>
317 + <button class="button button--sm button--danger" data-action="delete">Delete</button>
318 318 </div>`;
319 319
320 320 pop.innerHTML = `<div class="hp-title">${escapeHtml(source.name)}</div>${rows}${actions}`;
@@ -460,11 +460,11 @@
460 460 bar.appendChild(allBtn);
461 461
462 462 allTags.forEach(t => {
463 - const btn = document.createElement('button');
464 - btn.className = 'tag-chip' + (currentTag === t ? ' active' : '');
465 - btn.textContent = t;
466 - btn.addEventListener('click', () => selectTag(t));
467 - bar.appendChild(btn);
463 + const button = document.createElement('button');
464 + button.className = 'tag-chip' + (currentTag === t ? ' active' : '');
465 + button.textContent = t;
466 + button.addEventListener('click', () => selectTag(t));
467 + bar.appendChild(button);
468 468 });
469 469 }
470 470
@@ -705,7 +705,7 @@
705 705 const readyDiv = body.children.find(c => c.className === 'sync-ready');
706 706 assert(readyDiv, 'Should have sync-ready div');
707 707 const hasDisconnect = readyDiv.children.some(c =>
708 - c.className === 'btn sync-disconnect' && c._text === 'Disconnect'
708 + c.className === 'button sync-disconnect' && c._text === 'Disconnect'
709 709 );
710 710 assert(hasDisconnect, 'Should have Disconnect button');
711 711 });