Skip to main content

max / balanced_breakfast

css: F5 D — reorder styles.css sections into monotonic BAND order The file was in feature-add order (sections bounced around; 4/20/23 appeared as scattered duplicates). Reordered every `=== N. ===` block into the numeric order the header FILE STRUCTURE map already defined, via a stable sort that also consolidates the split sections (the three "20. Sources" blocks, etc.). Pure block-move: the sorted-line multiset is byte-identical before and after (no rule added, removed, or changed), and the diff is exactly 1108 insertions / 1108 deletions. The RESPONSIVE LAYERS band (39-41) stays contiguous and ascending, and now sits after all base sections (correct base-before-override cascade) instead of mid-file. Dropped the stale "BAND map is the TARGET" header caveat. CSS parses; 55 JS tests pass; lint clean. Cascade order is load-bearing only within the responsive/touch band (preserved), but a narrow-viewport eyeball is prudent since sections 14/18/30/33-37 moved ahead of the @media blocks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-12 19:18 UTC
Commit: f8856ceeb3c65e6980e7e1c351a86a6cfd0057e6
Parent: d2c00aa
1 file changed, +342 insertions, -111 deletions
@@ -41,10 +41,9 @@
41 41 not because order matters. Bands group related sections; cascade order is
42 42 load-bearing only inside the RESPONSIVE LAYERS and TOUCH OVERRIDES bands.
43 43
44 - Note (2026-06-02): the BAND map below is the TARGET layout. Today's file
45 - still has rules grouped by their original feature-add order. Phase F5 (dedup
46 - sweep) moves rules into the correct BAND. Until then, the numbered section
47 - markers below are stable anchors regardless of file position.
44 + Sections are laid out in monotonic order matching this map (F5 D, 2026-07-12).
45 + Gaps in the numbering (3, 21, 25) are sections not yet needed; add them in
46 + their numbered slot. Keep new sections in order.
48 47
49 48 BAND: FOUNDATIONS
50 49 1. Design System Variables — tokens (:root)
@@ -243,6 +242,13 @@ body {
243 242 height: 100vh;
244 243 }
245 244
245 + /* === 4. Layout shell — main =========================================== */
246 + .main {
247 + display: flex;
248 + flex: 1;
249 + overflow: hidden;
250 + }
251 +
246 252 /* === 5. Header ========================================================== */
247 253 .header {
248 254 display: flex;
@@ -315,6 +321,219 @@ body {
315 321 }
316 322 .sort-select:focus { outline: none; border-color: var(--category-four); }
317 323
324 + /* === 6. Sidebar ======================================================== */
325 + .sidebar {
326 + width: 220px;
327 + min-width: 220px;
328 + background-color: var(--surface-overlay);
329 + border-right: var(--border-width) solid var(--border);
330 + display: flex;
331 + flex-direction: column;
332 + overflow-y: auto;
333 + scrollbar-gutter: stable;
334 + }
335 +
336 + .sidebar h2 {
337 + padding: 0.75rem 1rem;
338 + font-size: 0.75rem;
339 + font-weight: 700;
340 + color: var(--content-muted);
341 + text-transform: uppercase;
342 + letter-spacing: 0.5px;
343 + border-bottom: var(--border-width) solid var(--border);
344 + }
345 +
346 + /* === 7. Items panel ==================================================== */
347 + .items-panel {
348 + flex: 1;
349 + display: flex;
350 + flex-direction: column;
351 + overflow-y: auto;
352 + background-color: var(--surface-page);
353 + }
354 +
355 + .items-list { list-style: none; }
356 +
357 + /* Article rows: feed items + reading-list bookmarks. Uses the renderRow .row
358 + layout (icon / content / actions, flex + gap) plus article appearance +
359 + read/unread/selected/pinned state. Replaces the legacy .item-* layout. */
360 + .row--article {
361 + padding: 0.75rem 1.25rem;
362 + border-bottom: 1px solid var(--border);
363 + border-left: 3px solid transparent;
364 + cursor: pointer;
365 + transition: background-color 0.15s;
366 + }
367 + .row--article:hover { background-color: var(--surface-overlay); }
368 + .row--article.is-unread {
369 + border-left-color: var(--danger);
370 + background-color: color-mix(in srgb, var(--danger) 6%, var(--surface-page));
371 + }
372 + .row--article.is-read { color: var(--content-secondary); }
373 + .row--article.is-selected {
374 + background-color: var(--surface-sunken);
375 + border-left-color: var(--category-four);
376 + }
377 + .row--article.is-unread.is-selected {
378 + border-left-color: var(--danger);
379 + background-color: color-mix(in srgb, var(--danger) 12%, var(--surface-page));
380 + }
381 + .row--article.is-pinned { border-left-color: var(--action); }
382 + .empty-icon {
383 + font-size: 2.5rem;
384 + margin-bottom: 0.75rem;
385 + opacity: 0.6;
386 + }
387 +
388 + /* Standalone empty-state block. The items/bookmarks empty rows use
389 + `<li class="empty-state">`; new surfaces use BB.ui.renderEmptyState().
390 + <div class="empty-state">
391 + <div class="empty-state-icon">...</div>
392 + <p class="empty-state-text">No items</p>
393 + <button class="btn btn-primary">Add one</button>
394 + </div>
395 + */
396 + .empty-state {
397 + display: flex;
398 + flex-direction: column;
399 + align-items: center;
400 + justify-content: center;
401 + text-align: center;
402 + padding: 3rem 2rem;
403 + color: var(--content-muted);
404 + line-height: 1.6;
405 + gap: 0.75rem;
406 + }
407 + .empty-state-icon {
408 + font-size: 2.5rem;
409 + opacity: 0.6;
410 + }
411 + .empty-state-text {
412 + font-size: 0.9rem;
413 + color: var(--content-secondary);
414 + margin: 0;
415 + }
416 + .empty-state--compact {
417 + padding: 1.5rem 1rem;
418 + gap: 0.5rem;
419 + }
420 + .empty-state--compact .empty-state-icon { font-size: 1.5rem; }
421 +
422 + /* Control stack (star / pin + read dot) passed into renderRow's icon slot.
423 + The .row layout provides the gap to the content; no margin needed here. */
424 + .article-controls {
425 + display: flex;
426 + flex-direction: column;
427 + align-items: center;
428 + min-width: 24px;
429 + }
430 +
431 + .star-btn {
432 + background: none;
433 + border: none;
434 + cursor: pointer;
435 + font-size: 1.1rem;
436 + color: var(--content-muted);
437 + transition: color 0.2s, transform 0.15s;
438 + padding: 0.25rem 0.4rem;
439 + margin: -0.25rem -0.4rem;
440 + line-height: 1;
441 + }
442 + .star-btn:hover { color: var(--category-four); transform: scale(1.1); }
443 + .star-btn.starred { color: var(--category-four); }
444 +
445 + .read-indicator {
446 + width: 7px;
447 + height: 7px;
448 + border-radius: 50%;
449 + margin-top: 0.4rem;
450 + }
451 + .read-indicator.unread { background-color: var(--danger); }
452 +
453 + /* Content column is renderRow's .row-content (flex:1; min-width:0). */
454 +
455 + /* Header slot holds the author/time meta line (+ mobile source badge). */
456 + .article-meta {
457 + display: flex;
458 + justify-content: space-between;
459 + align-items: flex-start;
460 + margin-bottom: 0.15rem;
461 + }
462 + .article-author {
463 + font-weight: 700;
464 + color: var(--danger);
465 + font-size: var(--font-size-md);
466 + }
467 + .article-time { color: var(--content-muted); font-size: var(--font-size-sm); }
468 +
469 + /* Title: two-line clamp (overrides .row-primary's single flex line). */
470 + .row--article .row-primary {
471 + display: -webkit-box;
472 + -webkit-line-clamp: 2;
473 + -webkit-box-orient: vertical;
474 + overflow: hidden;
475 + font-weight: 400;
476 + color: var(--content);
477 + line-height: 1.4;
478 + font-size: var(--font-size-base);
479 + }
480 + .row--article .row-secondary {
481 + color: var(--content-secondary);
482 + font-size: var(--font-size-md);
483 + margin-top: 0.15rem;
484 + }
485 + /* .article-source-badge (mobile-only) lives in section 38. */
486 +
487 + .load-more { padding: 1rem; text-align: center; }
488 +
489 + /* === 8. Detail panel =================================================== */
490 + .detail-panel {
491 + width: 400px;
492 + min-width: 400px;
493 + background-color: var(--surface-overlay);
494 + border-left: var(--border-width) solid var(--border);
495 + display: none;
496 + flex-direction: column;
497 + overflow-y: auto;
498 + }
499 +
500 + .detail-header {
501 + display: flex;
502 + justify-content: flex-end;
503 + gap: 0.3rem;
504 + padding: 0.4rem;
505 + border-bottom: var(--border-width) solid var(--border);
506 + background-color: var(--surface-sunken);
507 + }
508 +
509 + .item-detail { padding: 1.25rem; }
510 +
511 + .detail-title {
512 + font-size: 1.15rem;
513 + margin-bottom: 0.75rem;
514 + color: var(--content);
515 + font-weight: 700;
516 + line-height: 1.4;
517 + }
518 +
519 + .detail-meta {
520 + display: flex;
521 + flex-wrap: wrap;
522 + gap: 0.6rem;
523 + margin-bottom: 0.75rem;
524 + color: var(--content-secondary);
525 + font-size: 0.8rem;
526 + padding-bottom: 0.75rem;
527 + border-bottom: var(--border-width) solid var(--border);
528 + }
529 +
530 + .detail-tags {
531 + display: flex;
532 + flex-wrap: wrap;
533 + gap: 0.4rem;
534 + margin-bottom: 0.75rem;
535 + }
536 +
318 537 /* === 9. Buttons ========================================================
319 538 Canonical: .btn (+ .btn-primary, .btn-secondary, .btn-success, .btn-danger,
320 539 .btn-icon, .btn-text, .btn-link; size .btn-sm; state .btn-loading).
@@ -578,310 +797,330 @@ body {
578 797 font-weight: 600;
579 798 }
580 799
581 - /* === 4. Layout shell — main =========================================== */
582 - .main {
583 - display: flex;
584 - flex: 1;
585 - overflow: hidden;
586 - }
587 -
588 - /* === 6. Sidebar ======================================================== */
589 - .sidebar {
590 - width: 220px;
591 - min-width: 220px;
592 - background-color: var(--surface-overlay);
593 - border-right: var(--border-width) solid var(--border);
594 - display: flex;
595 - flex-direction: column;
596 - overflow-y: auto;
597 - scrollbar-gutter: stable;
800 + /* === 10. Forms =========================================================
801 + Canonical: .form-group + .form-input. Charter target (F5): split
802 + .form-input into .form-input / .form-select / .form-textarea so visual
803 + treatment per kind is targetable; add .form-label class on labels;
804 + add BB.ui.renderFormField helper.
805 + ========================================================================== */
806 + .form-group { margin-bottom: 0.75rem; }
807 + .form-group label {
808 + display: block;
809 + margin-bottom: 0.35rem;
810 + color: var(--content-secondary);
811 + font-size: 0.8rem;
812 + font-weight: 700;
598 813 }
599 814
600 - .sidebar h2 {
601 - padding: 0.75rem 1rem;
602 - font-size: 0.75rem;
603 - font-weight: 700;
604 - color: var(--content-muted);
605 - text-transform: uppercase;
606 - letter-spacing: 0.5px;
607 - border-bottom: var(--border-width) solid var(--border);
815 + .form-input {
816 + width: 100%;
817 + padding: 0.6rem;
818 + border: var(--border-width) solid var(--border);
819 + border-radius: var(--radius-sm);
820 + background-color: var(--surface-page);
821 + color: var(--content);
822 + font-size: 0.875rem;
823 + transition: border-color 0.2s, box-shadow 0.2s;
824 + }
825 + .form-input:focus {
826 + outline: none;
827 + border-color: var(--category-four);
828 + box-shadow: 0 0 0 2px var(--category-four);
608 829 }
609 830
610 - /* === 20. Sources list — sidebar entries ================================
611 - Charter target (F5): .source-item collapses onto .card --row.
612 - ========================================================================== */
613 - .sources-list { list-style: none; padding: 0.25rem 0; }
614 -
615 - .source-item {
616 - display: flex;
617 - justify-content: space-between;
618 - align-items: center;
619 - padding: 0.6rem 1rem;
831 + /* F2 additions (2026-06-02) — split .form-input into kind-specific
832 + primitives so visual treatment per kind is targetable. .form-select and
833 + .form-textarea inherit .form-input today; future surface-specific tweaks
834 + land on the kind-specific class. .form-label replaces the unclassed
835 + `<label>` under `.form-group`. */
836 + .form-label {
837 + display: block;
838 + margin-bottom: 0.35rem;
839 + color: var(--content-secondary);
840 + font-size: 0.8rem;
841 + font-weight: 700;
842 + }
843 + .form-select {
844 + width: 100%;
845 + padding: 0.6rem;
846 + border: var(--border-width) solid var(--border);
847 + border-radius: var(--radius-sm);
848 + background-color: var(--surface-page);
849 + color: var(--content);
850 + font-size: 0.875rem;
620 851 cursor: pointer;
621 - transition: background-color 0.15s;
622 - min-height: 2.5rem;
623 - border-left: 3px solid transparent;
852 + transition: border-color 0.2s, box-shadow 0.2s;
624 853 }
625 - .source-item:hover { background-color: var(--surface-sunken); }
626 - .source-item.active {
627 - background-color: var(--surface-sunken);
628 - border-left-color: var(--category-four);
854 + .form-select:focus {
855 + outline: none;
856 + border-color: var(--category-four);
857 + box-shadow: 0 0 0 2px var(--category-four);
629 858 }
630 -
631 - .source-name {
632 - flex: 1;
633 - white-space: nowrap;
634 - overflow: hidden;
635 - text-overflow: ellipsis;
636 - font-weight: 600;
859 + .form-textarea {
860 + width: 100%;
861 + padding: 0.6rem;
862 + border: var(--border-width) solid var(--border);
863 + border-radius: var(--radius-sm);
864 + background-color: var(--surface-page);
865 + color: var(--content);
637 866 font-size: 0.875rem;
867 + font-family: inherit;
868 + resize: vertical;
869 + min-height: 4rem;
870 + transition: border-color 0.2s, box-shadow 0.2s;
638 871 }
639 -
640 - .source-actions {
872 + .form-textarea:focus {
873 + outline: none;
874 + border-color: var(--category-four);
875 + box-shadow: 0 0 0 2px var(--category-four);
876 + }
877 + /* .form-row — horizontal grouping of two related fields (e.g. label + button) */
878 + .form-row {
641 879 display: flex;
880 + gap: 0.5rem;
642 881 align-items: center;
643 - gap: 0.3rem;
644 882 }
645 883
646 - .source-count {
647 - background-color: var(--surface-page);
648 - padding: 0.1rem 0.4rem;
649 - border-radius: var(--radius-xl);
884 + .form-actions {
885 + display: flex;
886 + justify-content: flex-end;
887 + gap: 0.5rem;
888 + margin-top: 1rem;
889 + }
890 +
891 + .form-hint {
650 892 font-size: 0.7rem;
651 893 color: var(--content-muted);
652 - border: var(--border-width) solid var(--border);
653 - min-width: 1.8rem;
654 - text-align: center;
655 - }
656 - .source-count.all-read {
657 - color: var(--success);
658 - border-color: var(--success);
894 + margin-top: 0.2rem;
659 895 }
660 896
661 - .source-delete {
897 + /* === 11. Modal =========================================================
898 + Canonical: .modal-overlay (single global at #modal-overlay) +
899 + .modal-content (will rename to .modal-container for parity in F5).
900 + .tag block (13. Tag) lives below this block in file order.
901 + ========================================================================== */
902 + .modal-overlay {
903 + position: fixed;
904 + top: 0; left: 0; right: 0; bottom: 0;
905 + background-color: color-mix(in srgb, var(--content) 40%, transparent);
662 906 display: none;
663 - background: none;
664 - border: none;
665 - color: var(--content-muted);
666 - cursor: pointer;
667 - font-size: 0.9rem;
668 - padding: 0 0.2rem;
669 - line-height: 1;
907 + align-items: center;
908 + justify-content: center;
909 + z-index: 1000;
670 910 }
671 - .source-delete:hover { color: var(--danger); }
672 - .source-item:hover .source-delete { display: inline; }
673 911
674 - /* === 7. Items panel ==================================================== */
675 - .items-panel {
676 - flex: 1;
677 - display: flex;
678 - flex-direction: column;
679 - overflow-y: auto;
912 + .modal-content {
680 913 background-color: var(--surface-page);
914 + border-radius: var(--radius-lg);
915 + width: 90%;
916 + max-width: 500px;
917 + max-height: 80vh;
918 + overflow-y: auto;
919 + box-shadow: 6px 6px 0 var(--shadow-color);
681 920 }
682 921
683 - .items-list { list-style: none; }
684 -
685 - /* Article rows: feed items + reading-list bookmarks. Uses the renderRow .row
686 - layout (icon / content / actions, flex + gap) plus article appearance +
687 - read/unread/selected/pinned state. Replaces the legacy .item-* layout. */
688 - .row--article {
922 + .modal-header {
923 + display: flex;
924 + justify-content: space-between;
925 + align-items: center;
689 926 padding: 0.75rem 1.25rem;
690 - border-bottom: 1px solid var(--border);
691 - border-left: 3px solid transparent;
692 - cursor: pointer;
693 - transition: background-color 0.15s;
694 - }
695 - .row--article:hover { background-color: var(--surface-overlay); }
696 - .row--article.is-unread {
697 - border-left-color: var(--danger);
698 - background-color: color-mix(in srgb, var(--danger) 6%, var(--surface-page));
927 + border-bottom: var(--border-width) solid var(--border);
928 + background-color: var(--surface-overlay);
929 + border-radius: var(--radius-lg) var(--radius-lg) 0 0;
699 930 }
700 - .row--article.is-read { color: var(--content-secondary); }
701 - .row--article.is-selected {
931 + .modal-header h2 { font-size: 1.1rem; font-weight: 700; color: var(--content); }
932 +
933 + /* F3 (2026-06-02): step indicator for multi-step modal flows (OAuth,
934 + plugin import, encryption setup). Set via BB.ui.setModalStep(n, of). */
935 + .modal-step-indicator {
936 + margin-left: 0.75rem;
937 + padding: 0.1rem 0.5rem;
702 938 background-color: var(--surface-sunken);
703 - border-left-color: var(--category-four);
704 - }
705 - .row--article.is-unread.is-selected {
706 - border-left-color: var(--danger);
707 - background-color: color-mix(in srgb, var(--danger) 12%, var(--surface-page));
708 - }
709 - .row--article.is-pinned { border-left-color: var(--action); }
710 - .empty-icon {
711 - font-size: 2.5rem;
712 - margin-bottom: 0.75rem;
713 - opacity: 0.6;
939 + border: var(--border-width) solid var(--border);
940 + border-radius: var(--radius-xl);
941 + font-size: 0.7rem;
942 + font-weight: 600;
943 + color: var(--content-muted);
944 + white-space: nowrap;
Lines truncated