@mythicalos/ui-core 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/styles.css CHANGED
@@ -327,3 +327,753 @@ a:focus-visible, .link:focus-visible, .is-focus {
327
327
  /* design rule 6 — the shared focus ring, restated rather than added to the BASE selector list
328
328
  above for the same reason the rules above live here */
329
329
  .input-reveal__btn:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; }
330
+
331
+ /* ══════════════════════════════════════════════════════════════════════════════════════════
332
+ SMALL ATOMS — save bar · stat tiles · git status chip. Each was registered in the design
333
+ system but implemented only inside one product; these rules are taken from the cards
334
+ (ds/layouts-settings.html, ds/components-stat-tiles.html, ds/components-git-chip.html) with
335
+ colors/font-sizes remapped onto canonical --my-* tokens. Where the product's local sheet and
336
+ the card differed, the CARD wins and the delta is noted inline.
337
+ ══════════════════════════════════════════════════════════════════════════════════════════ */
338
+
339
+ /* save bar (ds/layouts-settings.html `.save`) — full-bleed bottom, slides up only when dirty.
340
+ The card draws it `position:absolute` inside its own bordered screen frame; a packaged atom has
341
+ no such frame to anchor to, so it is `fixed` to the viewport (what the reference product's
342
+ sheet does too) — a consumer that needs it scoped to a pane overrides `position` via the
343
+ passthrough class. Padding is the card's `9px 16px`; the product's own sheet insets it to 24px
344
+ horizontally, which a consumer can restore from its own sheet. */
345
+ .my-savebar {
346
+ position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
347
+ background: var(--my-surface); border-top: 1px solid var(--my-border);
348
+ display: flex; align-items: center; gap: 10px;
349
+ padding: 9px var(--my-sp-4); font-size: var(--my-fs-caption); /* the card's 9px 16px */
350
+ animation: my-savebar-up 180ms ease-out; /* the card's "slides up (180ms)" */
351
+ }
352
+ @keyframes my-savebar-up { from { transform: translateY(100%); } }
353
+ @media (prefers-reduced-motion: reduce) { .my-savebar { animation: none; } }
354
+ .my-savebar__note { color: var(--my-muted); }
355
+ .my-savebar__count { color: var(--my-ink); font-weight: var(--my-fw-bold); }
356
+ .my-savebar__actions { margin-left: auto; display: flex; gap: var(--my-sp-2); }
357
+
358
+ /* stat tiles (ds/components-stat-tiles.html `.tiles`/`.tile`) — mono, tabular-nums, micro key +
359
+ mono sub-caption. The card sizes tiles by content off a 112px floor; the reference product's
360
+ sheet additionally stretches them with `flex: 1`, which a consumer can restore from its own
361
+ sheet (`.my-stat-tiles > .my-stat-tile { flex: 1 }`). */
362
+ .my-stat-tiles { display: flex; gap: 10px; flex-wrap: wrap; }
363
+ .my-stat-tile {
364
+ min-width: 112px; background: var(--my-surface);
365
+ border: 1px solid var(--my-border); border-radius: var(--my-r-card); padding: 10px 12px;
366
+ }
367
+ .my-stat-tile__label {
368
+ font-size: var(--my-fs-micro); /* card: 10px, no exact token step */
369
+ font-weight: var(--my-fw-bold); text-transform: uppercase; letter-spacing: .4px;
370
+ color: var(--my-muted);
371
+ }
372
+ .my-stat-tile__value {
373
+ font-family: var(--my-font-mono);
374
+ font-size: var(--my-fs-body-lg); /* card: 17px, nearest token step */
375
+ font-weight: var(--my-fw-bold); font-variant-numeric: tabular-nums; margin-top: 2px;
376
+ }
377
+ .my-stat-tile__sub {
378
+ font-family: var(--my-font-mono);
379
+ font-size: var(--my-fs-micro); /* card: 10.5px, no exact token step */
380
+ color: var(--my-muted);
381
+ }
382
+ /* the card's "brag" number (`.tile.save .v` → --my-accent-strong) */
383
+ .my-stat-tile--accent .my-stat-tile__value { color: var(--my-accent-strong); }
384
+ /* band tint (token rule #4 thresholds live with the caller, only the tint is here) */
385
+ .my-stat-tile--warn { border-color: var(--my-warn); }
386
+ .my-stat-tile--warn .my-stat-tile__value { color: var(--my-warn); }
387
+ .my-stat-tile--error { border-color: var(--my-error); }
388
+ .my-stat-tile--error .my-stat-tile__value { color: var(--my-error); }
389
+
390
+ /* git status chip (ds/components-git-chip.html `.gitchip`/`.gitflag`) — a bordered inline mono
391
+ chip. The flags are the card's own squared 4px badge, NOT the pill `.my-chip`: inside a mono
392
+ chip the card gives them a tighter, squared shape, and design rule #10 reserves the pill for
393
+ stand-alone non-interactive tags.
394
+
395
+ Three deliberate deltas from the card, all recorded here (and pinned by
396
+ test/css-small-atoms.test.ts) rather than left to be discovered:
397
+ - `flex-wrap: wrap` on the chip and its flag group. The card demonstrates the chip inside a
398
+ fixed 820px panel where it cannot run out of room; a packaged atom renders at whatever width
399
+ the consumer gives it, and a long branch name plus three flags must wrap rather than
400
+ overflow its container.
401
+ - the branch keeps the card's inherited weight (NOT bold). The product's local sheet bolds it;
402
+ the card does not, so the card wins.
403
+ - the flag weight is `--my-fw-bold` (650), where the card's literal is 700. There is no 700
404
+ step on the canonical --my-fw-* scale, and the additions section above already normalizes
405
+ badge weights onto the nearest token (`.my-chip` does exactly this). */
406
+ .my-gitchip {
407
+ display: inline-flex; gap: 10px; align-items: center; flex-wrap: wrap; /* delta 1 */
408
+ font-family: var(--my-font-mono); font-size: var(--my-fs-caption);
409
+ border: 1px solid var(--my-border); border-radius: var(--my-r-control);
410
+ padding: 6px 10px; background: var(--my-surface);
411
+ }
412
+ .my-gitchip__branch { color: var(--my-ink); } /* delta 2 — card weight, not the product's bold */
413
+ .my-gitchip__flags { display: inline-flex; gap: 6px; flex-wrap: wrap; align-items: center; }
414
+ .my-gitchip__flag {
415
+ font-size: var(--my-fs-micro); /* card: 10px, no exact token step */
416
+ font-weight: var(--my-fw-bold); /* delta 3 — card: 700, no exact token step */
417
+ letter-spacing: .3px; border-radius: 4px; padding: 2px 6px;
418
+ }
419
+ .my-gitchip__flag--warn { background: var(--my-warn-soft); color: var(--my-warn); }
420
+ .my-gitchip__flag--error { background: var(--my-error-soft); color: var(--my-error); }
421
+ .my-gitchip__flag--ok { background: var(--my-ok-soft); color: var(--my-ok); }
422
+ /* the honest unavailable arm: the branch slot is muted and the note carries the reason in UI type */
423
+ .my-gitchip--unavailable .my-gitchip__branch { color: var(--my-muted); }
424
+ .my-gitchip__note { font-family: var(--my-font-ui); color: var(--my-muted); }
425
+ /* a retained (not current) status — flagged on BOTH arms */
426
+ .my-gitchip--stale { border-color: var(--my-warn); }
427
+ .my-gitchip__stale { color: var(--my-warn); font-size: var(--my-fs-micro); }
428
+
429
+ /* ── dropdown popover (ds/components-popover — registry row `popover` v1) ──
430
+ The design card: a chip trigger over a panel anchored 6px below it, --my-shadow-modal, high
431
+ z-index, ✓ marking the current row. Two card values are normalized onto the canonical scale the
432
+ way the additions section above already does: the card's 12.5px/10px body text → --my-fs-caption
433
+ (12px) / --my-fs-micro (11px), and its literal 6px item radius → --my-r-control (6px, the same
434
+ number — now expressed as the token shape rule 10 demands for anything clickable).
435
+ Placement is state-driven, never inline-styled: the bindings toggle .my-pop--above /
436
+ .my-pop--end from @mythicalos/ui-core's resolvePopoverPosition, mirroring <mythical-select>'s
437
+ [data-flip].
438
+ CONSUMER CONTRACT (design card, verbatim): "Ancestors must never clip it." The panel is
439
+ position:absolute inside .my-pop-anchor, so any ancestor with overflow:hidden/auto or its own
440
+ stacking context will crop or bury it — give that ancestor room, not this component a portal. */
441
+ .my-pop-anchor { position: relative; display: inline-block; }
442
+
443
+ .my-pop-trigger {
444
+ display: inline-flex; align-items: center; gap: var(--my-sp-2);
445
+ /* shape rule 10 — the trigger is clickable, so it is SQUARED (--my-r-control); the pill radius
446
+ is reserved for non-interactive status tags */
447
+ border: 1px solid var(--my-control-border); border-radius: var(--my-r-control);
448
+ padding: 5px 12px; font-family: inherit; font-size: var(--my-fs-caption);
449
+ background: var(--my-surface); color: var(--my-ink); cursor: pointer;
450
+ transition: background var(--my-t-fast), border-color var(--my-t-fast);
451
+ }
452
+ .my-pop-trigger:hover:not(:disabled):not(.is-disabled) { background: var(--my-surface-hover); }
453
+ /* Open state — NOT on the design card (which draws a single, already-open popover and so has no
454
+ closed/open pair to distinguish). It is not an invention either: this is verbatim what the
455
+ package's other floating control does, `:host([data-open]) button{border-color:var(--my-accent)}`
456
+ in src/select/mythical-select.js. Two anchored dropdowns in one package must not disagree about
457
+ what an open trigger looks like, so the popover follows the select. */
458
+ .my-pop-trigger.is-open { border-color: var(--my-accent); }
459
+ .my-pop-trigger:disabled, .my-pop-trigger.is-disabled {
460
+ background: var(--my-disabled-bg); color: var(--my-disabled-ink);
461
+ border-color: transparent; cursor: not-allowed;
462
+ }
463
+ .my-pop-trigger__label { color: var(--my-muted); }
464
+ .my-pop-trigger__value { font-weight: var(--my-fw-medium); }
465
+ .my-pop-trigger__caret { color: var(--my-muted); font-size: var(--my-fs-micro); flex: none; }
466
+ .my-pop-trigger:disabled .my-pop-trigger__label,
467
+ .my-pop-trigger.is-disabled .my-pop-trigger__label,
468
+ .my-pop-trigger:disabled .my-pop-trigger__caret,
469
+ .my-pop-trigger.is-disabled .my-pop-trigger__caret { color: var(--my-disabled-ink); }
470
+
471
+ .my-pop {
472
+ position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
473
+ /* min-width only, per the card — the panel grows to its widest row rather than truncating it */
474
+ /* 6px is the card's own panel padding and has no step on the --my-sp-* scale (4 / 8); it is
475
+ kept literal rather than rounded, exactly as the trigger's 5px 12px and the row's 7px 10px
476
+ are — this block's spacing comes from the card, only its colors/radii/fonts are tokenized. */
477
+ min-width: 210px; padding: 6px;
478
+ background: var(--my-surface); border: 1px solid var(--my-border); /* panel edge = a card boundary, not a control one */
479
+ border-radius: var(--my-r-card); box-shadow: var(--my-shadow-modal);
480
+ }
481
+ /* viewport-aware flip — same rule as <mythical-select>'s :host([data-flip]) #pop */
482
+ .my-pop--above { top: auto; bottom: calc(100% + 6px); }
483
+ .my-pop--end { left: auto; right: 0; }
484
+
485
+ .my-pop__head { display: flex; flex-direction: column; gap: 2px; padding: 6px 10px 8px; }
486
+ .my-pop__title { font-size: var(--my-fs-caption); font-weight: var(--my-fw-bold); color: var(--my-ink); }
487
+ .my-pop__caption { font-size: var(--my-fs-micro); color: var(--my-muted); }
488
+
489
+ /* The role="menu" element NESTED in the panel: it owns ONLY the rows, so the head and the
490
+ caller's footer stay siblings of it (a menu may not own arbitrary content). Purely structural —
491
+ it adds no box of its own. */
492
+ .my-pop__menu { display: flex; flex-direction: column; }
493
+
494
+ .my-pop__item {
495
+ display: flex; align-items: center; justify-content: space-between; gap: 14px;
496
+ /* border-box explicitly: this sheet declares no global reset (same reason the .input-reveal rules
497
+ above restate it), so a full-width row's padding must not be left to the UA's or a consumer's
498
+ box model — otherwise the row, its hover fill and its focus ring overhang the panel's padding. */
499
+ width: 100%; box-sizing: border-box; padding: 7px 10px; border: 0; background: none;
500
+ border-radius: var(--my-r-control); /* shape rule 10 — a row is clickable, so squared */
501
+ font-family: inherit; font-size: var(--my-fs-caption); color: var(--my-ink);
502
+ text-align: left; cursor: pointer;
503
+ }
504
+ .my-pop__item:hover:not(:disabled):not(.is-disabled) { background: var(--my-surface-hover); }
505
+ .my-pop__item.is-selected { color: var(--my-accent-strong); font-weight: var(--my-fw-medium); }
506
+ .my-pop__item:disabled, .my-pop__item.is-disabled { color: var(--my-disabled-ink); cursor: not-allowed; }
507
+ /* The panel is min-width-only, so this truncation is INERT by default (the panel widens to fit).
508
+ It engages solely when a consumer constrains the panel's width themselves — then a long label
509
+ ellipsises instead of tearing the row's layout apart. */
510
+ .my-pop__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
511
+ .my-pop__check { flex: none; }
512
+
513
+ .my-pop__div { height: 1px; margin: var(--my-sp-1) 0; background: var(--my-border); }
514
+ .my-pop__foot { display: block; }
515
+
516
+ /* design rule 6 — the shared focus ring, identical in both themes, on every interactive control
517
+ here (restated locally for the same reason the input-reveal rules above are) */
518
+ .my-pop-trigger:focus-visible, .my-pop__item:focus-visible {
519
+ outline: 2px solid var(--my-accent); outline-offset: 2px;
520
+
521
+ /* ── file explorer & markdown preview (ds/components-file-explorer) ────────
522
+ A READ-ONLY two-pane explorer: the rail (header + scope picker + tree) and the preview pane
523
+ (breadcrumb + size/mtime header + rendered body). Every class here is derived by
524
+ src/logic/file-explorer.ts — this sheet only gives those class strings their look.
525
+
526
+ Two tree modes share these rules and differ only in which rows classify as what:
527
+ a bind MOUNT root is mono + muted (⌂); a REPO is accent-strong (⎇). See the logic module.
528
+
529
+ The four honest states (unavailable · empty · too-large · binary) each get their own modifier
530
+ so they are distinctly styleable, but ALL of them inherit the neutral muted note tone: none is
531
+ painted as a failure, because none of them is one. */
532
+
533
+ .my-files__rail {
534
+ display: flex; flex-direction: column; min-height: 0;
535
+ background: var(--my-surface); border: 1px solid var(--my-border);
536
+ border-radius: var(--my-r-card); overflow: hidden;
537
+ }
538
+ .my-files__hd { padding: var(--my-sp-3); border-bottom: 1px solid var(--my-border); flex: none; }
539
+ .my-files__ti {
540
+ display: flex; align-items: center; gap: 7px;
541
+ font-size: var(--my-fs-micro); font-weight: var(--my-fw-bold);
542
+ color: var(--my-muted); text-transform: uppercase; letter-spacing: .4px;
543
+ }
544
+ .my-files__count { font-weight: var(--my-fw-regular); text-transform: none; letter-spacing: 0; }
545
+ .my-files__branch {
546
+ margin-left: auto; display: flex; align-items: center; gap: 5px;
547
+ font-family: var(--my-font-mono); font-size: var(--my-fs-micro);
548
+ font-weight: var(--my-fw-regular); color: var(--my-muted);
549
+ text-transform: none; letter-spacing: 0;
550
+ }
551
+ .my-files__branch-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--my-ok); flex: none; }
552
+
553
+ /* scope picker — a plain disclosure of buttons (no ARIA menu claim) */
554
+ .my-files__scope { position: relative; margin-top: 9px; }
555
+ .my-files__scope-btn, .my-files__scope-static {
556
+ width: 100%; display: flex; align-items: center; gap: var(--my-sp-2);
557
+ border: 1px solid var(--my-border); border-radius: 7px; padding: 6px 10px;
558
+ background: var(--my-bg); color: var(--my-ink);
559
+ font: inherit; font-size: var(--my-fs-caption); text-align: left;
560
+ }
561
+ .my-files__scope-btn { cursor: pointer; transition: border-color var(--my-t-fast); }
562
+ .my-files__scope-btn:hover { border-color: var(--my-control-border); }
563
+ .my-files__scope-btn:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; }
564
+ .my-files__scope-glyph { color: var(--my-accent); font-size: var(--my-fs-micro); flex: none; }
565
+ .my-files__scope-label {
566
+ flex: 1; min-width: 0; white-space: nowrap; overflow: hidden;
567
+ text-overflow: ellipsis; font-weight: var(--my-fw-bold);
568
+ }
569
+ .my-files__scope-caret { color: var(--my-muted); font-size: var(--my-fs-micro); flex: none; }
570
+ .my-files__scope-menu {
571
+ position: absolute; left: 0; right: 0; top: calc(100% + 5px); z-index: 10;
572
+ background: var(--my-surface); border: 1px solid var(--my-border);
573
+ border-radius: 9px; box-shadow: var(--my-shadow-modal); padding: 5px;
574
+ }
575
+ .my-files__scope-it {
576
+ width: 100%; display: flex; align-items: center; gap: var(--my-sp-2);
577
+ border: 0; border-radius: var(--my-r-control); padding: 7px 9px;
578
+ background: none; color: var(--my-ink);
579
+ font: inherit; font-size: var(--my-fs-caption); text-align: left; cursor: pointer;
580
+ }
581
+ .my-files__scope-it:hover { background: var(--my-surface-hover); }
582
+ .my-files__scope-it:focus-visible { outline: 2px solid var(--my-accent); outline-offset: -2px; }
583
+ .my-files__scope-it--on {
584
+ background: var(--my-accent-soft); color: var(--my-accent-strong); font-weight: var(--my-fw-bold);
585
+ }
586
+ .my-files__scope-it-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
587
+ .my-files__scope-it-count { margin-left: auto; font-family: var(--my-font-mono); font-size: var(--my-fs-micro); color: var(--my-muted); }
588
+ .my-files__scope-it--on .my-files__scope-it-count { color: var(--my-accent-strong); }
589
+ .my-files__scope-divider { height: 1px; background: var(--my-border); margin: var(--my-sp-1) 6px; }
590
+
591
+ /* tree + rows — the WHOLE ROW is the hit target */
592
+ .my-files__tree { flex: 1; min-height: 0; overflow: auto; padding: 7px 7px var(--my-sp-3); }
593
+ .my-files__row {
594
+ width: 100%; display: flex; align-items: center; gap: 7px;
595
+ border: 0; border-radius: var(--my-r-control); padding: 5px var(--my-sp-2);
596
+ background: none; color: var(--my-ink);
597
+ font: inherit; font-size: var(--my-fs-caption); text-align: left;
598
+ cursor: pointer; margin-bottom: 1px;
599
+ }
600
+ .my-files__row:hover { background: var(--my-surface-hover); }
601
+ .my-files__row:focus-visible { outline: 2px solid var(--my-accent); outline-offset: -2px; }
602
+ .my-files__row--sel { background: var(--my-accent-soft); color: var(--my-accent-strong); }
603
+ .my-files__row--sel .my-files__fname { font-weight: var(--my-fw-bold); }
604
+ .my-files__chev { color: var(--my-muted); font-size: var(--my-fs-micro); width: 10px; flex: none; }
605
+ .my-files__glyph { color: var(--my-accent); width: 12px; flex: none; text-align: center; }
606
+ .my-files__spacer { width: 14px; flex: none; }
607
+ .my-files__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
608
+ /* a bind MOUNT reads as a path: mono + muted. A REPO reads as a repo: accent-strong. */
609
+ .my-files__row--mount .my-files__name { font-family: var(--my-font-mono); color: var(--my-muted); }
610
+ .my-files__row--repo .my-files__name { font-weight: var(--my-fw-bold); color: var(--my-accent-strong); }
611
+ .my-files__row--dir .my-files__name { font-weight: var(--my-fw-medium); }
612
+ /* a file row is the leaf: no structural emphasis, the mono name carries it */
613
+ .my-files__row--file { font-weight: var(--my-fw-regular); }
614
+ .my-files__fglyph {
615
+ font-family: var(--my-font-mono); font-size: var(--my-fs-micro);
616
+ color: var(--my-muted); width: 15px; flex: none; text-align: center;
617
+ }
618
+ .my-files__fname {
619
+ min-width: 0; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
620
+ font-family: var(--my-font-mono); font-size: var(--my-fs-caption);
621
+ }
622
+ .my-files__badge {
623
+ margin-left: auto; flex: none; border-radius: var(--my-r-pill); padding: 1.5px 7px;
624
+ font-size: var(--my-fs-micro); font-weight: var(--my-fw-bold); white-space: nowrap;
625
+ }
626
+ .my-files__badge--accent { background: var(--my-accent-soft); color: var(--my-accent-strong); }
627
+ .my-files__badge--muted { background: var(--my-disabled-bg); color: var(--my-muted); }
628
+ .my-files__mark { font-family: var(--my-font-mono); font-size: var(--my-fs-micro); font-weight: var(--my-fw-bold); flex: none; }
629
+ .my-files__mark--warn { color: var(--my-warn); }
630
+ .my-files__mark--ok { color: var(--my-ok); }
631
+ .my-files__mark--muted { color: var(--my-muted); }
632
+
633
+ /* notes — the honest states and the two informational rows. ONE neutral tone for all of them. */
634
+ .my-files__note { display: block; padding: 5px var(--my-sp-2); font-size: var(--my-fs-micro); color: var(--my-muted); }
635
+ .my-files__note--loading, .my-files__note--truncated,
636
+ .my-files__note--unavailable, .my-files__note--empty,
637
+ .my-files__note--too-large, .my-files__note--binary { color: var(--my-muted); }
638
+
639
+ /* indent steps — depth-capped by indentClass(); deeper rows share the last step */
640
+ .my-files__d0 { padding-left: var(--my-sp-2); }
641
+ .my-files__d1 { padding-left: 22px; }
642
+ .my-files__d2 { padding-left: 36px; }
643
+ .my-files__d3 { padding-left: 50px; }
644
+ .my-files__d4 { padding-left: 64px; }
645
+ .my-files__d5 { padding-left: 78px; }
646
+ .my-files__d6 { padding-left: 92px; }
647
+ .my-files__d7 { padding-left: 106px; }
648
+ .my-files__d8 { padding-left: 120px; }
649
+ .my-files__d9 { padding-left: 134px; }
650
+ .my-files__d10 { padding-left: 148px; }
651
+
652
+ /* preview pane */
653
+ .my-files__prev {
654
+ background: var(--my-surface); border: 1px solid var(--my-border);
655
+ border-radius: var(--my-r-card); overflow: hidden;
656
+ }
657
+ .my-files__prev-hd {
658
+ padding: 13px 18px; border-bottom: 1px solid var(--my-border);
659
+ display: flex; align-items: center; gap: 14px;
660
+ }
661
+ .my-files__prev-main { min-width: 0; }
662
+ .my-files__crumb {
663
+ font-family: var(--my-font-mono); font-size: var(--my-fs-micro); color: var(--my-muted);
664
+ overflow-wrap: anywhere;
665
+ }
666
+ .my-files__name-row { display: flex; align-items: center; gap: 9px; margin-top: 3px; }
667
+ .my-files__prev-name { font-family: var(--my-font-mono); font-size: var(--my-fs-body-lg); overflow-wrap: anywhere; }
668
+ .my-files__pill {
669
+ border-radius: var(--my-r-pill); padding: 2px 9px; flex: none;
670
+ font-size: var(--my-fs-micro); font-weight: var(--my-fw-bold);
671
+ }
672
+ .my-files__pill--warn { background: var(--my-warn-soft); color: var(--my-warn); }
673
+ .my-files__pill--ok { background: var(--my-ok-soft); color: var(--my-ok); }
674
+ .my-files__pill--muted { background: var(--my-disabled-bg); color: var(--my-muted); }
675
+ .my-files__meta {
676
+ margin-left: auto; display: flex; gap: 14px; flex: none;
677
+ font-family: var(--my-font-mono); font-size: var(--my-fs-micro); color: var(--my-muted);
678
+ }
679
+ .my-files__prev-bd { padding: var(--my-sp-4) 18px; font-size: var(--my-fs-body); }
680
+ .my-files__md { max-width: 720px; }
681
+ .my-files__md h1 { font-size: var(--my-fs-h3); font-weight: var(--my-fw-bold); letter-spacing: -.3px; margin-bottom: 6px; }
682
+ .my-files__md p { color: var(--my-ink); margin: var(--my-sp-2) 0; }
683
+ .my-files__md code {
684
+ font-family: var(--my-font-mono); font-size: .88em;
685
+ background: var(--my-surface-hover); border: 1px solid var(--my-border);
686
+ border-radius: var(--my-sp-1); padding: 1px 5px;
687
+ }
688
+ .my-files__md blockquote {
689
+ margin: 10px 0; padding: 7px 13px;
690
+ border-left: 3px solid var(--my-accent); background: var(--my-accent-soft);
691
+ border-radius: 0 var(--my-sp-2) var(--my-sp-2) 0;
692
+ }
693
+ .my-files__pre {
694
+ max-width: 720px; margin: 0; white-space: pre-wrap; overflow-wrap: anywhere;
695
+ font-family: var(--my-font-mono); font-size: var(--my-fs-mono); color: var(--my-ink);
696
+ }
697
+
698
+ /* ── session-card (design registry `session-card` v1, ds/components-session-card.html) ──
699
+ Five states: default·nominal, selected, warn ≥75%, error ≥90%, disconnected·stale — plus the
700
+ UNKNOWN states the design's five did not enumerate but the doctrine requires (no context
701
+ reading; no activity signal). Every dynamic visual is a class or an SVG presentation
702
+ attribute: the prototype positioned its gauge fill and its 75/90 ticks with inline
703
+ `style="width:62%"` / `style="left:75%"`, which CSP style-src 'self' forbids AND which cannot
704
+ express product-tuned thresholds — so the meter is an SVG whose rects carry the geometry.
705
+
706
+ The status dot deliberately does NOT reuse `.my-status` (StatusLine): that atom's tone axis has
707
+ no `unknown` member and its component takes no `class`, so neither the hollow unknown dot nor
708
+ the ≤1/s pulse could ride on it without reshaping a sibling atom. Same tone vocabulary, own
709
+ elements. */
710
+ .my-session-card {
711
+ display: block; width: 100%; text-align: left;
712
+ background: var(--my-surface); color: var(--my-ink);
713
+ border: 1px solid var(--my-border); /* card outline — a valid --my-border use (rule #11) */
714
+ border-radius: var(--my-r-card); padding: var(--my-sp-4);
715
+ font-family: inherit; font-size: var(--my-fs-body);
716
+ transition: border-color var(--my-t-fast);
717
+ }
718
+ button.my-session-card { cursor: pointer; }
719
+ .my-session-card:hover { border-color: var(--my-control-border); } /* the prototype's "hover deepens the border" */
720
+ /* selected = petrol border doubled (1px border + 1px ring), per the card's caption */
721
+ .my-session-card.is-selected { border-color: var(--my-accent); box-shadow: 0 0 0 1px var(--my-accent); }
722
+ /* disconnected = dashed border, values muted as stale */
723
+ .my-session-card.is-stale { border-style: dashed; color: var(--my-muted); }
724
+ .my-session-card:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; } /* rule #6 */
725
+
726
+ .my-session-card__head { display: flex; gap: var(--my-sp-3); align-items: center; }
727
+ .my-session-card__ident { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
728
+ .my-session-card__line1 { display: flex; align-items: center; gap: var(--my-sp-2); flex-wrap: wrap; }
729
+ .my-session-card__name { font-size: var(--my-fs-body); font-weight: var(--my-fw-bold); }
730
+ .my-session-card__meta { font-size: var(--my-fs-caption); color: var(--my-muted); }
731
+ /* the packaged Avatar rides here; stale mutes it the way the prototype's `.stale .avatar` did */
732
+ .my-session-card.is-stale .my-session-card__avatar {
733
+ background: var(--my-disabled-bg); color: var(--my-disabled-ink);
734
+ }
735
+
736
+ /* status: dot + words (token rule #7 — never colour alone) */
737
+ .my-session-card__status { display: inline-flex; align-items: center; gap: var(--my-sp-1); font-size: var(--my-fs-caption); }
738
+ .my-session-card__status-text { white-space: nowrap; }
739
+ .my-session-card__dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
740
+ .my-session-card__status--ok { color: var(--my-ok); }
741
+ .my-session-card__status--ok .my-session-card__dot { background: var(--my-ok); }
742
+ .my-session-card__status--warn { color: var(--my-warn); }
743
+ .my-session-card__status--warn .my-session-card__dot { background: var(--my-warn); }
744
+ .my-session-card__status--error { color: var(--my-error); }
745
+ .my-session-card__status--error .my-session-card__dot { background: var(--my-error); }
746
+ .my-session-card__status--info { color: var(--my-info); }
747
+ .my-session-card__status--info .my-session-card__dot { background: var(--my-info); }
748
+ .my-session-card__status--muted { color: var(--my-muted); }
749
+ .my-session-card__status--muted .my-session-card__dot { background: var(--my-disabled-ink); }
750
+ /* UNKNOWN is not idle: a HOLLOW dot, so an unreported session can never be read as a solid-dot
751
+ claim. The word beside it says "unknown"; the ring says it a second time, without colour. */
752
+ .my-session-card__status--unknown { color: var(--my-muted); font-style: italic; }
753
+ .my-session-card__status--unknown .my-session-card__dot {
754
+ background: transparent; box-shadow: inset 0 0 0 1.5px var(--my-disabled-ink);
755
+ }
756
+ /* transient states only, ≤1/s */
757
+ .my-session-card__status.is-pulse .my-session-card__dot { animation: my-session-pulse 1s ease-in-out infinite; }
758
+ @keyframes my-session-pulse { 50% { opacity: .4; } }
759
+ @media (prefers-reduced-motion: reduce) {
760
+ .my-session-card__status.is-pulse .my-session-card__dot { animation: none; }
761
+ }
762
+
763
+ /* context meter — rail + fill + fixed threshold ticks (rule #4: whole-hue flip, rail = --my-track) */
764
+ .my-session-card__ctx { display: block; margin-top: var(--my-sp-3); }
765
+ .my-session-card__ctx-bar {
766
+ display: block; height: 8px; border-radius: var(--my-r-pill); overflow: hidden;
767
+ }
768
+ .my-session-card__ctx-svg { display: block; width: 100%; height: 100%; }
769
+ .my-session-card__ctx-track { fill: var(--my-track); }
770
+ /* whole-hue flip at each threshold (rule #4) — never a gradient */
771
+ .my-session-card__ctx-fill,
772
+ .my-session-card__ctx--ok .my-session-card__ctx-fill { fill: var(--my-accent); } /* < warn: fill = accent */
773
+ .my-session-card__ctx--warn .my-session-card__ctx-fill { fill: var(--my-warn); }
774
+ .my-session-card__ctx--error .my-session-card__ctx-fill { fill: var(--my-error); }
775
+ /* no reading ⇒ no fill element is emitted at all; the rail itself also changes, so the unknown
776
+ state cannot be mistaken for a healthy 0% one */
777
+ .my-session-card__ctx--unknown .my-session-card__ctx-track { fill: var(--my-disabled-bg); }
778
+ .my-session-card__ctx-tick { fill: var(--my-surface); }
779
+ /* a stale reading is a LAST-KNOWN value: kept visible, drained of its band hue */
780
+ .my-session-card__ctx.is-stale .my-session-card__ctx-fill { fill: var(--my-disabled-ink); }
781
+ .my-session-card__ctx-legend {
782
+ display: flex; justify-content: space-between; gap: var(--my-sp-2);
783
+ margin-top: var(--my-sp-1); font-size: var(--my-fs-micro); color: var(--my-muted);
784
+ }
785
+ /* a long note truncates rather than pushing the (load-bearing) value off the card */
786
+ .my-session-card__ctx-note,
787
+ .my-session-card__spine-note { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
788
+ .my-session-card__ctx-value {
789
+ font-family: var(--my-font-mono); font-variant-numeric: tabular-nums; /* rule #5 */
790
+ color: var(--my-ink); font-weight: var(--my-fw-medium);
791
+ }
792
+ .my-session-card__ctx--unknown .my-session-card__ctx-value,
793
+ .my-session-card__ctx.is-stale .my-session-card__ctx-value { color: var(--my-disabled-ink); }
794
+
795
+ /* spine strip — filled petrol nodes = distills, hollow node = the live tip */
796
+ .my-session-card__spine { display: flex; align-items: center; margin-top: var(--my-sp-3); }
797
+ .my-session-card__spine-seg { height: 3px; background: var(--my-border); flex: 1; }
798
+ .my-session-card__spine-node {
799
+ width: 13px; height: 13px; border-radius: 50%; flex: none;
800
+ background: var(--my-accent); border: 3px solid var(--my-surface);
801
+ box-shadow: 0 0 0 1px var(--my-border);
802
+ }
803
+ .my-session-card__spine-node--tip { background: var(--my-surface); border-color: var(--my-accent); }
804
+ .my-session-card__spine-value {
805
+ font-family: var(--my-font-mono); font-variant-numeric: tabular-nums;
806
+ color: var(--my-ink); font-weight: var(--my-fw-medium);
807
+ }
808
+ .my-session-card.is-stale .my-session-card__spine-value { color: var(--my-disabled-ink); }
809
+
810
+ TERMINAL SET (ds/components-terminal, spec v2): .my-term · .my-qrow · .my-sendbar.
811
+
812
+ Token rule 3 — "Terminal surfaces always use the --my-term-* set, regardless of app theme" —
813
+ is the load-bearing one here. The --my-term-* tokens are declared ONLY in :root and are
814
+ deliberately NOT overridden by [data-theme="dark"], so they are already theme-invariant; the
815
+ `.my-term` block below additionally re-points every theme-flipping local (--my-bg/--my-surface/
816
+ --my-ink/--my-muted/--my-accent/--my-warn/…) onto that fixed palette for the WHOLE subtree. That
817
+ way an atom nested inside the terminal cannot inherit a light-theme surface either, and the
818
+ terminal stays heritage-dark in both themes without a single hard-coded color.
819
+
820
+ Deriving the two shades the palette does not name (a hairline border and the warn-soft fill for
821
+ the in-terminal banners) uses color-mix over the term tokens rather than a new hex, so the "zero
822
+ hard-coded hex" and "every var(--my-*) exists in canonical tokens.css" guards both still hold.
823
+
824
+ The send bar and the queue rows are APP CHROME, not terminal surface: the design card draws both
825
+ on the light surface, so they use the ordinary theme tokens and follow the app theme. Only the
826
+ pane is pinned dark.
827
+ ══════════════════════════════════════════════════════════════════════════════════════════ */
828
+
829
+ /* ── terminal pane ─────────────────────────────────────────────────────── */
830
+ .my-term {
831
+ /* token rule 3 — the fixed heritage palette, both themes */
832
+ --my-bg: var(--my-term-bg);
833
+ --my-surface: var(--my-term-bg);
834
+ --my-surface-hover: color-mix(in oklab, var(--my-term-ink) 8%, var(--my-term-bg));
835
+ --my-border: color-mix(in oklab, var(--my-term-ink) 14%, var(--my-term-bg));
836
+ --my-control-border: color-mix(in oklab, var(--my-term-ink) 34%, var(--my-term-bg));
837
+ --my-ink: var(--my-term-ink);
838
+ --my-muted: var(--my-term-dim);
839
+ --my-accent: var(--my-term-assistant);
840
+ --my-accent-strong: var(--my-term-assistant);
841
+ --my-accent-soft: color-mix(in oklab, var(--my-term-assistant) 18%, var(--my-term-bg));
842
+ --my-warn: var(--my-term-user);
843
+ --my-warn-soft: color-mix(in oklab, var(--my-term-user) 18%, var(--my-term-bg));
844
+ --my-track: color-mix(in oklab, var(--my-term-ink) 12%, var(--my-term-bg));
845
+ --my-ink-hover: var(--my-term-ink);
846
+ --my-accent-hover: var(--my-term-assistant);
847
+ --my-disabled-bg: color-mix(in oklab, var(--my-term-ink) 6%, var(--my-term-bg));
848
+ --my-disabled-ink: var(--my-term-dim);
849
+ --my-scrim: color-mix(in oklab, var(--my-term-bg) 55%, transparent);
850
+ --my-shadow-modal: 0 16px 48px color-mix(in oklab, var(--my-term-bg) 50%, transparent);
851
+ /* NOT re-pointed, deliberately: --my-ok/--my-error/--my-info and their softs. The five-token
852
+ terminal palette defines no green/red/blue, so any value here would be an invented colour. No
853
+ rule in the terminal set uses them (enforced by test/terminal-css.test.ts, which derives the
854
+ flipping-token set from the canonical tokens.css and rejects any unpinned reference under a
855
+ .my-term selector). A consumer nesting a status-toned atom INSIDE the terminal is the one known
856
+ residual; the terminal's own alert hue is --my-term-user. */
857
+
858
+ display: flex; flex-direction: column;
859
+ background: var(--my-term-bg); color: var(--my-term-ink);
860
+ border: 1px solid var(--my-border); border-radius: var(--my-r-card);
861
+ font-family: var(--my-font-mono); font-size: var(--my-fs-mono); line-height: 1.7;
862
+ overflow: hidden;
863
+ }
864
+
865
+ /* title bar — traffic lights · `{name} · tail · noise {on|off}` · turn/stop cluster */
866
+ .my-term__titlebar {
867
+ display: flex; align-items: center; gap: var(--my-sp-2);
868
+ padding: var(--my-sp-2) var(--my-sp-3);
869
+ border-bottom: 1px solid var(--my-border);
870
+ font-family: var(--my-font-ui); font-size: var(--my-fs-micro);
871
+ color: var(--my-term-dim);
872
+ }
873
+ .my-term__lights { display: inline-flex; gap: 5px; flex: none; }
874
+ /* window-chrome traffic lights. They are DECORATION, so per token rule 2 they may not borrow the
875
+ status hues (--my-error/--my-ok) the way a macOS titlebar would; per token rule 3 they are drawn
876
+ from the terminal's own five-token palette. */
877
+ .my-term__light { width: 9px; height: 9px; border-radius: var(--my-r-pill); background: var(--my-term-dim); }
878
+ .my-term__light--r { background: var(--my-term-dim); }
879
+ .my-term__light--a { background: var(--my-term-user); }
880
+ .my-term__light--g { background: var(--my-term-assistant); }
881
+ .my-term__noise {
882
+ border: 0; background: none; padding: 0; cursor: pointer;
883
+ font: inherit; color: var(--my-term-dim); text-align: left;
884
+ }
885
+ .my-term__noise:hover { color: var(--my-term-ink); }
886
+ .my-term__noise:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; }
887
+ .my-term__tb-right { margin-left: auto; display: inline-flex; align-items: center; gap: var(--my-sp-2); }
888
+ .my-term__turn, .my-term__idle { display: inline-flex; align-items: center; gap: 5px; color: var(--my-term-dim); }
889
+ .my-term__turn-dot {
890
+ width: 7px; height: 7px; border-radius: var(--my-r-pill);
891
+ background: var(--my-term-assistant); animation: my-term-pulse 1s ease-in-out infinite;
892
+ }
893
+ /* the stop control lives INSIDE the terminal, so per token rule 3 it wears the term palette rather
894
+ than the app's danger red (which flips with the app theme). `is-prominent` raises it to the
895
+ terminal's one alert hue — the heritage amber — when a turn is actually in flight. */
896
+ .my-term__stop {
897
+ border: 1px solid var(--my-term-dim); border-radius: var(--my-r-control);
898
+ background: none; color: var(--my-term-ink);
899
+ padding: 2px 9px; font-family: var(--my-font-ui); font-size: var(--my-fs-micro);
900
+ font-weight: var(--my-fw-medium); cursor: pointer;
901
+ }
902
+ .my-term__stop:hover:not(:disabled) { background: color-mix(in oklab, var(--my-term-ink) 8%, var(--my-term-bg)); }
903
+ .my-term__stop:disabled { border-color: var(--my-term-dim); color: var(--my-term-dim); cursor: not-allowed; }
904
+ .my-term__stop:focus-visible { outline: 2px solid var(--my-term-assistant); outline-offset: 2px; }
905
+ .my-term__stop.is-prominent { border-color: var(--my-term-user); color: var(--my-term-user); }
906
+ .my-term__stop.is-prominent:hover:not(:disabled) {
907
+ background: color-mix(in oklab, var(--my-term-user) 18%, var(--my-term-bg));
908
+ }
909
+ .my-term__stop-key { opacity: .7; }
910
+
911
+ /* body */
912
+ .my-term__body { padding: var(--my-sp-3) var(--my-sp-4); overflow: auto; flex: 1; }
913
+ .my-term__body:focus-visible { outline: 2px solid var(--my-accent); outline-offset: -2px; }
914
+
915
+ /* the two in-terminal banners (wake-unavailable · stale). Warn soft + a dot; never a spinner —
916
+ neither banner claims a retry is under way, so neither may look like one. */
917
+ .my-term__banner {
918
+ display: flex; gap: var(--my-sp-2); align-items: center;
919
+ background: var(--my-warn-soft); color: var(--my-warn);
920
+ border-radius: var(--my-r-control); padding: 6px 10px; margin-bottom: 10px;
921
+ font-family: var(--my-font-ui); font-size: var(--my-fs-micro);
922
+ }
923
+ .my-term__banner-dot {
924
+ width: 7px; height: 7px; border-radius: var(--my-r-pill); flex: none;
925
+ background: currentColor; animation: my-term-pulse 1s ease-in-out infinite;
926
+ }
927
+
928
+ /* state copy (loading · missing · unaddressable · failed · no events) */
929
+ .my-term__state { color: var(--my-term-dim); }
930
+
931
+ /* rows */
932
+ .my-term__row { display: block; color: var(--my-term-ink); white-space: pre-wrap; word-break: break-word; }
933
+ .my-term__row--assistant { color: var(--my-term-ink); }
934
+ .my-term__row--user { color: var(--my-term-user); }
935
+ .my-term__row--tool, .my-term__row--result, .my-term__row--system { color: var(--my-term-dim); }
936
+ .my-term__row--boundary { color: var(--my-term-assistant); }
937
+ .my-term__row--dim { color: var(--my-term-dim); }
938
+ /* client-synthetic rows read as UI-originated, never as output from the far side */
939
+ .my-term__row--local { color: var(--my-term-dim); font-style: italic; }
940
+ .my-term__label { color: var(--my-term-assistant); font-weight: var(--my-fw-medium); }
941
+ .my-term__row--user .my-term__label { color: var(--my-term-user); }
942
+ .my-term__row--tool .my-term__label,
943
+ .my-term__row--result .my-term__label,
944
+ .my-term__row--system .my-term__label { color: var(--my-term-dim); }
945
+
946
+ /* expandable rows — collapsed by default, highlight on hover (design card) */
947
+ .my-term__head {
948
+ display: block; width: 100%; text-align: left; cursor: pointer;
949
+ border: 0; background: none; color: inherit; font: inherit;
950
+ border-radius: var(--my-r-control); padding: 0 6px; margin: 0 -6px;
951
+ }
952
+ .my-term__head:hover { background: var(--my-surface-hover); color: var(--my-term-ink); }
953
+ .my-term__head:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; }
954
+ .my-term__expand { text-decoration: underline; }
955
+ .my-term__detail {
956
+ margin: var(--my-sp-1) 0 var(--my-sp-2); padding: var(--my-sp-2);
957
+ background: var(--my-surface-hover); border-radius: var(--my-r-control);
958
+ color: var(--my-term-ink); white-space: pre-wrap; word-break: break-word;
959
+ font-family: var(--my-font-mono);
960
+ }
961
+
962
+ /* foreign transcript segments — behind the toggle, captioned, visually separated */
963
+ .my-term__more {
964
+ border: 0; background: none; padding: 0; cursor: pointer;
965
+ font-family: var(--my-font-ui); font-size: var(--my-fs-micro);
966
+ color: var(--my-term-dim); display: block; margin-bottom: var(--my-sp-2);
967
+ }
968
+ .my-term__more:hover { color: var(--my-term-ink); }
969
+ .my-term__more:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; }
970
+ .my-term__hist { border-left: 2px solid var(--my-border); padding-left: var(--my-sp-3); margin-bottom: var(--my-sp-3); }
971
+ .my-term__hist-cap {
972
+ font-family: var(--my-font-ui); font-size: var(--my-fs-micro);
973
+ color: var(--my-term-dim); margin-bottom: var(--my-sp-1);
974
+ }
975
+
976
+ /* streaming caret — blinks 1/s, paused under prefers-reduced-motion (design card) */
977
+ .my-term__caret {
978
+ display: inline-block; width: 7px; height: 14px; vertical-align: -2px;
979
+ background: var(--my-term-assistant); animation: my-term-blink 1s steps(1) infinite;
980
+ }
981
+ @keyframes my-term-blink { 50% { opacity: 0; } }
982
+ @keyframes my-term-pulse { 50% { opacity: .45; } }
983
+ @media (prefers-reduced-motion: reduce) {
984
+ .my-term__caret, .my-term__banner-dot, .my-term__turn-dot { animation: none; }
985
+ }
986
+
987
+ /* ── queue (app chrome — follows the app theme) ────────────────────────── */
988
+ .my-queue { display: block; }
989
+ .my-queue__list { display: block; }
990
+ .my-queue__state, .my-queue__stale { font-size: var(--my-fs-caption); color: var(--my-muted); margin-top: var(--my-sp-2); }
991
+ .my-queue__stale { color: var(--my-warn); }
992
+ .my-queue__empty {
993
+ display: flex; align-items: center; justify-content: center;
994
+ border: 1px dashed var(--my-border); border-radius: var(--my-r-card);
995
+ padding: 9px 12px; margin-top: var(--my-sp-2);
996
+ font-size: var(--my-fs-caption); color: var(--my-muted); background: none;
997
+ }
998
+
999
+ /* ── queue row ─────────────────────────────────────────────────────────── */
1000
+ .my-qrow {
1001
+ display: flex; align-items: center; gap: 10px;
1002
+ background: var(--my-surface); border: 1px solid var(--my-border);
1003
+ border-radius: 8px; padding: 9px 12px; margin-top: var(--my-sp-2);
1004
+ font-size: var(--my-fs-mono); color: var(--my-ink);
1005
+ transition: background var(--my-t-fast);
1006
+ }
1007
+ .my-qrow:hover { background: var(--my-surface-hover); }
1008
+ .my-qrow__body { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1009
+ .my-qrow__status { color: var(--my-muted); font-size: var(--my-fs-micro); flex: none; }
1010
+ /* per-status presentation. The status WORD is always rendered next to the row (queueStatusLabel),
1011
+ so these tints reinforce it and never carry it alone (token rule 7). `queued` is the default
1012
+ presentation — it takes no modifier styling. */
1013
+ .my-qrow--queued { }
1014
+ .my-qrow--leased .my-qrow__status { color: var(--my-accent-strong); }
1015
+ .my-qrow--delivered .my-qrow__body,
1016
+ .my-qrow--canceled .my-qrow__body { color: var(--my-muted); }
1017
+ .my-qrow--canceled .my-qrow__body { text-decoration: line-through; }
1018
+ .my-qrow__cancel {
1019
+ margin-left: auto; flex: none; cursor: pointer;
1020
+ border: 0; background: none; font: inherit; font-size: var(--my-fs-caption);
1021
+ color: var(--my-muted); padding: 0;
1022
+ }
1023
+ .my-qrow__cancel:hover { color: var(--my-error); }
1024
+ .my-qrow__cancel:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; }
1025
+ /* two-step confirm, inside the row — never a modal for a queue item (design card) */
1026
+ .my-qrow.is-confirm { background: var(--my-error-soft); border-color: var(--my-error); }
1027
+ .my-qrow__ask { color: var(--my-error); font-weight: var(--my-fw-medium); }
1028
+ .my-qrow__acts { margin-left: auto; display: flex; gap: 6px; flex: none; }
1029
+ .my-qmini {
1030
+ border-radius: 5px; padding: 3px 10px; cursor: pointer;
1031
+ font-family: inherit; font-size: var(--my-fs-micro); font-weight: var(--my-fw-bold);
1032
+ border: 1px solid transparent;
1033
+ }
1034
+ .my-qmini:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; }
1035
+ .my-qmini--yes { background: var(--my-error); color: var(--my-surface); }
1036
+ .my-qmini--no { background: var(--my-surface); border-color: var(--my-control-border); color: var(--my-ink); }
1037
+ .my-qbadge {
1038
+ font-size: var(--my-fs-micro); font-weight: var(--my-fw-bold);
1039
+ border-radius: 5px; padding: 2px 7px; letter-spacing: .4px; flex: none;
1040
+ }
1041
+ .my-qbadge--asap { background: var(--my-accent-soft); color: var(--my-accent-strong); }
1042
+ .my-qbadge--done { background: var(--my-disabled-bg); color: var(--my-muted); }
1043
+
1044
+ /* ── send bar (app chrome — follows the app theme) ─────────────────────── */
1045
+ .my-sendbar-wrap { display: block; }
1046
+ .my-sendbar { display: flex; gap: var(--my-sp-2); align-items: flex-start; }
1047
+ .my-sendbar__seg { display: inline-flex; flex: none; }
1048
+ .my-sendbar__cls {
1049
+ border: 1px solid var(--my-control-border); background: var(--my-surface);
1050
+ color: var(--my-muted); cursor: pointer;
1051
+ padding: 8px 10px; font-family: inherit; font-size: var(--my-fs-micro);
1052
+ font-weight: var(--my-fw-bold); letter-spacing: .4px;
1053
+ }
1054
+ .my-sendbar__seg .my-sendbar__cls:first-child { border-radius: var(--my-r-control) 0 0 var(--my-r-control); }
1055
+ .my-sendbar__seg .my-sendbar__cls:last-child { border-radius: 0 var(--my-r-control) var(--my-r-control) 0; margin-left: -1px; }
1056
+ .my-sendbar__cls.is-on { background: var(--my-accent-soft); color: var(--my-accent-strong); border-color: var(--my-accent); z-index: 1; }
1057
+ .my-sendbar__cls:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; z-index: 1; }
1058
+ .my-sendbar__cls:disabled { background: var(--my-disabled-bg); color: var(--my-disabled-ink); cursor: not-allowed; }
1059
+ .my-sendbar__input {
1060
+ flex: 1; min-width: 0; resize: vertical;
1061
+ border: 1px solid var(--my-control-border); border-radius: var(--my-r-control);
1062
+ padding: 8px 12px; font: inherit; font-size: var(--my-fs-body);
1063
+ background: var(--my-surface); color: var(--my-ink);
1064
+ }
1065
+ .my-sendbar__input:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; }
1066
+ .my-sendbar__input:disabled { background: var(--my-disabled-bg); color: var(--my-disabled-ink); cursor: not-allowed; }
1067
+ .my-sendbar__send {
1068
+ border: 0; border-radius: var(--my-r-control); cursor: pointer; flex: none;
1069
+ padding: 8px 14px; font-family: inherit; font-size: var(--my-fs-body);
1070
+ font-weight: var(--my-fw-medium); background: var(--my-ink); color: var(--my-bg);
1071
+ }
1072
+ .my-sendbar__send:hover:not(:disabled) { background: var(--my-ink-hover); }
1073
+ .my-sendbar__send:focus-visible { outline: 2px solid var(--my-accent); outline-offset: 2px; }
1074
+ .my-sendbar__send:disabled { background: var(--my-disabled-bg); color: var(--my-disabled-ink); cursor: not-allowed; }
1075
+ .my-sendbar__hint { font-size: var(--my-fs-micro); color: var(--my-muted); margin-top: 6px; }
1076
+ .my-sendbar__notice { font-size: var(--my-fs-micro); color: var(--my-warn); margin-top: 6px; }
1077
+ /* static title line — used instead of .my-term__noise when the pane has no noise toggle, so a
1078
+ non-interactive caption never gets a pointer cursor it cannot honor */
1079
+ .my-term__title { color: var(--my-term-dim); }