@telepath-computer/television 0.1.116 → 0.1.118

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.
@@ -2,14 +2,13 @@
2
2
  * Canonical artifact stylesheet — the curated subset of the design system
3
3
  * artifacts inherit at /canonical/v1/styles.css.
4
4
  *
5
- * Includes design-system primitives (reset, fonts, tokens, theme) plus
6
- * bare-tag styling (elements). Excludes app-chrome layers (materials and
7
- * components) since artifacts shouldn't render with panel/dropdown/modal
8
- * surfaces.
5
+ * Includes the design-system primitives artifacts should inherit by default:
6
+ * reset, fonts, tokens, theme defaults, and conservative document typography for plain
7
+ * semantic HTML. Artifact-specific structure and rhythm should still be
8
+ * authored explicitly in the artifact HTML so agents can see and control the
9
+ * result, but basic prose should not fall back to browser defaults.
9
10
  *
10
- * Source of truth for what's in v1. Edits here change what artifacts
11
- * inherit; gaps that surface during real authoring (lists, blockquote,
12
- * table, hr) get added to elements.css since they apply to bare tags.
11
+ * Source of truth for what's in v1. Edits here change what artifacts inherit.
13
12
  */
14
13
  *,
15
14
  *::before,
@@ -324,7 +323,7 @@ select {
324
323
 
325
324
  body {
326
325
  font-family: var(--font-sans);
327
- font-size: var(--text-base);
326
+ font-size: calc(var(--text-base) + 0.5px);
328
327
  font-weight: var(--font-weight-body);
329
328
  line-height: var(--leading-base);
330
329
  letter-spacing: 0.015em;
@@ -347,9 +346,44 @@ h4,
347
346
  h5,
348
347
  h6 {
349
348
  font-weight: 600;
349
+ line-height: 1.25;
350
350
  color: inherit;
351
351
  }
352
352
 
353
+ h1 {
354
+ font-size: 1.3em;
355
+ }
356
+
357
+ h2 {
358
+ font-size: 1.1em;
359
+ }
360
+
361
+ h3,
362
+ h4,
363
+ h5,
364
+ h6 {
365
+ font-size: 1em;
366
+ }
367
+
368
+ p,
369
+ ul,
370
+ ol,
371
+ blockquote,
372
+ pre,
373
+ hr,
374
+ table {
375
+ margin: var(--space-12) 0;
376
+ }
377
+
378
+ :is(h1, h2, h3, h4, h5, h6) + :is(p, ul, ol, blockquote, pre, hr, table) {
379
+ margin-top: var(--space-8);
380
+ }
381
+
382
+ ul,
383
+ ol {
384
+ padding-left: var(--space-24);
385
+ }
386
+
353
387
  code {
354
388
  font-family: var(--font-mono);
355
389
  font-size: 0.9em;
@@ -360,8 +394,10 @@ code {
360
394
 
361
395
  pre {
362
396
  font-family: var(--font-mono);
397
+ font-size: var(--text-sm);
363
398
  background: var(--color-bg-muted);
364
399
  padding: var(--space-12);
400
+ border: 1px solid var(--color-border-muted);
365
401
  border-radius: var(--radius-8);
366
402
  overflow-x: auto;
367
403
  }
@@ -383,6 +419,7 @@ hr {
383
419
  }
384
420
 
385
421
  table {
422
+ width: 100%;
386
423
  border-collapse: collapse;
387
424
  }
388
425
 
@@ -398,519 +435,7 @@ th {
398
435
  font-weight: 600;
399
436
  }
400
437
 
401
- /* Recessed text input — same geometry as a button at rest, sunken via a
402
- subtle tint and no drop shadow. Focus deepens the tint one step (more
403
- recessed) and adds a primary-blue ring as the focus signal. Text
404
- inherits its color from the surrounding context so the input reads on
405
- either light or dark surfaces. */
406
- input,
407
- textarea {
408
- border: 0.5px solid var(--tint-300);
409
- border-radius: var(--radius-12);
410
- background: var(--tint-100);
411
- color: inherit;
412
- font: inherit;
413
- box-sizing: border-box;
414
- transition:
415
- background 120ms ease,
416
- border-color 120ms ease,
417
- box-shadow 120ms ease;
418
- }
419
-
420
- /* Single-line input snaps to the `lg` control height — taller than
421
- buttons/segmented controls (`md`) so text-entry fields have visual
422
- presence as the primary affordance in forms. Padding goes
423
- horizontal-only since the explicit height handles vertical centering
424
- via the line-box. */
425
- input {
426
- height: var(--control-height-lg);
427
- padding: 0 12px;
428
- }
429
-
430
- /* Multi-line textarea stays intrinsic — height grows with content (or
431
- max-height on a host) rather than locking to a control unit. */
432
- textarea {
433
- padding: 6px 10px;
434
- resize: none;
435
- }
436
-
437
- input::placeholder,
438
- textarea::placeholder {
439
- color: var(--tint-500);
440
- }
441
-
442
- input:focus,
443
- textarea:focus {
444
- background: color-mix(in srgb, var(--white) var(--alpha-300), transparent);
445
- border-color: var(--color-primary);
446
- outline: none;
447
- box-shadow: 0 0 0 1.5px var(--color-primary);
448
- }
449
-
450
- /* Stacked label + control. Label text is smaller and lighter than body
451
- copy, sits flush with the control. Use as
452
- <label class="field"><span>Name</span><input /></label>. */
453
- label.field {
454
- display: grid;
455
- gap: 4px;
456
- font-size: 13px;
457
- }
458
-
459
- label.field > span {
460
- font-size: 11.5px;
461
- font-weight: 450;
462
- color: var(--tint-800);
463
- }
464
-
465
- /* Universal squircle corners. `corner-shape: squircle` only affects
466
- elements that already declare a non-zero `border-radius`, so applying
467
- to `*` is effectively a no-op everywhere except where corners exist.
468
- Token values in tokens.css bump ~25% inside the same @supports block
469
- to compensate for the perceived tightness difference. */
470
- @supports (corner-shape: squircle) {
471
- * {
472
- corner-shape: squircle;
473
- }
474
- }
475
-
476
- /* Button-only layout — placement of icon + label inside a single
477
- button. Segmented-button doesn't share these (it's a flex row of
478
- children, not a center-aligned label-and-icon stack).
479
- `height: var(--control-height-md)` is explicit so a row of mixed
480
- chrome (text button + icon-only button + segmented-control) lines up
481
- without each variant having to tune its intrinsic content + padding
482
- to match. Padding is purely horizontal — vertical centering is handled
483
- by `align-items: center`. */
484
- button {
485
- display: inline-flex;
486
- align-items: center;
487
- justify-content: center;
488
- gap: var(--space-6);
489
- box-sizing: border-box;
490
- height: var(--control-height-md);
491
- /* 2px extra bottom padding compensates for the visual offset from
492
- font ascender/descender asymmetry — `align-items: center` centers
493
- the line-box but the glyph mass sits below center because the
494
- ascender claims more vertical space than the descender. The
495
- icon-only override resets padding to 0 since icons are already
496
- symmetric. */
497
- padding: 0 14px 2px;
498
- font: inherit;
499
- cursor: default;
500
- }
501
-
502
- /* Default chrome — shared between <button> and <segmented-control> so
503
- the two stay in visual lockstep. Variant overrides (ghost, primary,
504
- danger) and tone-dark / tone-light / disabled cascades live below.
505
- The shared-with-segmented bits are intentional: a segmented control
506
- is a chrome surface that looks like a single button containing two
507
- click targets, so its host adopts the same fill, hairline, radius,
508
- and shadow. */
509
- button,
510
- segmented-control {
511
- background: var(--color-surface);
512
- border: 0.5px solid var(--tint-200);
513
- border-radius: var(--radius-12);
514
- box-shadow: var(--shadow-control);
515
- color: inherit;
516
- }
517
-
518
- button:not([material]):hover:not(:disabled) {
519
- background: color-mix(in srgb, var(--color-surface) 96%, black);
520
- }
521
-
522
- button:not([material]):active:not(:disabled) {
523
- background: color-mix(in srgb, var(--color-surface) 88%, black);
524
- box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.08);
525
- }
526
-
527
- button:focus-visible {
528
- outline: 2px solid var(--color-focus-ring);
529
- outline-offset: 2px;
530
- }
531
-
532
- button[variant="primary"] {
533
- background: var(--color-primary);
534
- border-color: rgb(0 0 0 / 0.18);
535
- color: var(--white);
536
- }
537
-
538
- button[variant="primary"]:hover:not(:disabled) {
539
- background: color-mix(in srgb, var(--color-primary) 92%, black);
540
- }
541
-
542
- button[variant="primary"]:active:not(:disabled) {
543
- background: color-mix(in srgb, var(--color-primary) 82%, black);
544
- box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.18);
545
- }
546
-
547
- button[variant="danger"] {
548
- background: var(--color-danger);
549
- border-color: rgb(0 0 0 / 0.18);
550
- color: var(--white);
551
- }
552
-
553
- button[variant="danger"]:hover:not(:disabled) {
554
- background: color-mix(in srgb, var(--color-danger) 92%, black);
555
- }
556
-
557
- button[variant="danger"]:active:not(:disabled) {
558
- background: color-mix(in srgb, var(--color-danger) 82%, black);
559
- box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.18);
560
- }
561
-
562
- button[variant="ghost"] {
563
- background: transparent;
564
- border-color: transparent;
565
- color: inherit;
566
- box-shadow: none;
567
- }
568
-
569
- /* Ghost is a chromeless overlay — at rest it's invisible and relies on
570
- the surface behind it. The overlay tokens cascade through `tone="dark"`
571
- (set on the button itself by the auto-tone watcher in
572
- `utils/button-tone.ts`, or inherited from a tone-sampled ancestor), so
573
- one rule covers light + dark backdrops.
574
-
575
- Hover/active also bump the backdrop saturation — the same "comes alive"
576
- gesture glass uses. Ghost has no blur or brightness to absorb the
577
- filter, so a much gentler ladder (1.2 / 1.3 / 1.4) reads as the right
578
- weight against ghost's ~8–16% tint overlay. Glass's higher numbers
579
- would punch through too hard here. */
580
- /* Ghost uses currentColor tints directly (auto-flip via tone) rather than
581
- the shared --color-overlay-hover / -active tokens. The shared tokens
582
- read too quietly on ghost's bare surface — bumping ghost a step heavier
583
- (alpha-150 hover, alpha-300 active) gives the hover real presence
584
- without affecting other chrome that uses the overlay tokens. */
585
- button[variant="ghost"]:hover:not(:disabled) {
586
- background: var(--tint-150);
587
- backdrop-filter: saturate(1.2);
588
- -webkit-backdrop-filter: saturate(1.2);
589
- }
590
-
591
- button[variant="ghost"]:active:not(:disabled) {
592
- background: var(--tint-300);
593
- backdrop-filter: saturate(1.4);
594
- -webkit-backdrop-filter: saturate(1.4);
595
- box-shadow: none;
596
- }
597
-
598
- /* Pinned-active state for ghost triggers — used while a dropdown
599
- `[aria-expanded="true"]` is open against this button (set automatically
600
- by `dropdown-menu`), while a consumer manually pins `[data-active]`
601
- (e.g. anchored popover), or while the button is `[aria-pressed="true"]`
602
- (toggle/tab in its on state). All three override hover so the button
603
- stays visibly active even as the cursor moves over it. */
604
- button[variant="ghost"][data-active]:not(:disabled),
605
- button[variant="ghost"][data-active]:hover:not(:disabled),
606
- button[variant="ghost"][aria-pressed="true"]:not(:disabled),
607
- button[variant="ghost"][aria-pressed="true"]:hover:not(:disabled),
608
- button[variant="ghost"][aria-expanded="true"]:not(:disabled),
609
- button[variant="ghost"][aria-expanded="true"]:hover:not(:disabled) {
610
- background: var(--color-overlay-active);
611
- backdrop-filter: saturate(1.3);
612
- -webkit-backdrop-filter: saturate(1.3);
613
- box-shadow: none;
614
- }
615
-
616
- /* Glass variant — translucent surface with backdrop blur. Shared between
617
- <button variant="glass"> and <segmented-control variant="glass"> so a
618
- chrome row of buttons + segmented controls reads as one surface
619
- family. Self-contained per-variant chrome (bg + border + shadow + blur
620
- + hover/active overlays) means no fighting with default-button rules
621
- or tone-driven default fills.
622
-
623
- No explicit `color` here — auto-tone (utils/button-tone.ts) writes
624
- `tone="light"` or `tone="dark"` on the element after sampling its
625
- actual backdrop. The materials.css `[tone="…"]` color rules then set
626
- text color on the element directly (specificity 0,1,0), beating the
627
- shared `button { color: inherit }` (0,0,1). An explicit `color:
628
- inherit` here would be 0,1,1 and would defeat the tone-aware color. */
629
- button[variant="glass"],
630
- segmented-control[variant="glass"] {
631
- background: transparent;
632
- border: 0.5px solid color-mix(in srgb, var(--black) var(--alpha-100), transparent);
633
- box-shadow: none;
634
- }
635
-
636
- /* Glass-variant standalone button: backdrop-filter on the host. The
637
- segmented-control variant moves the filter onto each segment so per-
638
- child hover/active saturation works without nested-filter compounding
639
- issues — see segmented-control.css. */
640
- button[variant="glass"] {
641
- backdrop-filter: blur(20px) brightness(0.95) saturate(1.5);
642
- -webkit-backdrop-filter: blur(20px) brightness(0.95) saturate(1.5);
643
- }
644
-
645
- /* `background:` shorthand (not `background-image`) resets background-color
646
- back to transparent — necessary because the default-button hover rule
647
- `button:not([material]):hover` fires on glass too (glass has no
648
- material) at equal specificity and would otherwise paint a 96% white
649
- surface underneath this overlay.
650
-
651
- Hover and active also crank the backdrop saturate filter (1.5 → 1.8 →
652
- 2.2). On a light backdrop, darkening overlays read as visual weight
653
- rather than feedback; the saturation bump is direction-agnostic — the
654
- surface "comes alive" the same way regardless of tone. The overlay
655
- stays as a quieter backstop. */
656
- button[variant="glass"]:hover:not(:disabled) {
657
- background: linear-gradient(var(--color-overlay-hover), var(--color-overlay-hover));
658
- backdrop-filter: blur(20px) brightness(0.95) saturate(1.8);
659
- -webkit-backdrop-filter: blur(20px) brightness(0.95) saturate(1.8);
660
- }
661
-
662
- button[variant="glass"]:active:not(:disabled) {
663
- background: linear-gradient(var(--color-overlay-active), var(--color-overlay-active));
664
- backdrop-filter: blur(20px) brightness(0.95) saturate(2.2);
665
- -webkit-backdrop-filter: blur(20px) brightness(0.95) saturate(2.2);
666
- }
667
-
668
- /* Persistent active state — `aria-pressed="true"` (tab/toggle that is
669
- "currently on") and `aria-expanded="true"` (popover/menu trigger whose
670
- panel is open). Both stay lit through hover so the active overlay isn't
671
- visually masked by the hover overlay when the cursor is over them.
672
- `<ui-popover>` and `<dropdown-menu>` set `aria-expanded` automatically;
673
- tab-style consumers set `aria-pressed`. */
674
- button[variant="glass"][aria-pressed="true"]:not(:disabled),
675
- button[variant="glass"][aria-pressed="true"]:hover:not(:disabled),
676
- button[variant="glass"][aria-expanded="true"]:not(:disabled),
677
- button[variant="glass"][aria-expanded="true"]:hover:not(:disabled) {
678
- background: linear-gradient(var(--color-overlay-active), var(--color-overlay-active));
679
- backdrop-filter: blur(20px) brightness(0.95) saturate(2);
680
- -webkit-backdrop-filter: blur(20px) brightness(0.95) saturate(2);
681
- }
682
-
683
- [tone="dark"] button[variant="glass"],
684
- [tone="dark"] segmented-control[variant="glass"] {
685
- border-color: var(--tint-400);
686
- }
687
-
688
- /* Material composition with controls (option B in the design notes —
689
- contained override rather than refactoring materials.css globally).
690
- Material is a passive-surface primitive: it locks color, draws a
691
- fixed-alpha hairline, and a default-variant `:hover` would replace
692
- its background entirely. These overrides let `<button material="...">`
693
- and `<segmented-control material="...">` compose:
694
-
695
- - `color: inherit` so the tone-aware text cascade still applies
696
- (material sets a fixed `--color-text` which doesn't flip on dark).
697
- - `box-shadow: none` because both materials want a flat surface; the
698
- default control shadow fights glass especially.
699
- - hover/active paint via `background-image` (a flat linear-gradient)
700
- so the material's `background-color` is preserved underneath.
701
- - dark-tone bumps the hairline to `--tint-400` so the rim reads on
702
- dark backdrops (material's locked black-alpha border vanishes
703
- against tone-dark patches). */
704
- button[material],
705
- segmented-control[material] {
706
- color: inherit;
707
- box-shadow: none;
708
- }
709
-
710
- button[material]:hover:not(:disabled) {
711
- background-image: linear-gradient(var(--color-overlay-hover), var(--color-overlay-hover));
712
- }
713
-
714
- button[material]:active:not(:disabled) {
715
- background-image: linear-gradient(var(--color-overlay-active), var(--color-overlay-active));
716
- }
717
-
718
- [tone="dark"] button[material],
719
- [tone="dark"] segmented-control[material] {
720
- border-color: var(--tint-400);
721
- }
722
-
723
- button:disabled {
724
- background: var(--tint-300);
725
- border-color: transparent;
726
- color: var(--tint-700);
727
- box-shadow: none;
728
- }
729
-
730
- /* Ghost buttons stay chromeless when disabled — they have no
731
- resting chrome to "grey out", so the dimmed foreground (inherited
732
- via `color: var(--tint-700)` above) is the entire signal. The
733
- default disabled bg would otherwise paint a tint-300 surface
734
- that fights ghost's transparent baseline. */
735
- button[variant="ghost"]:disabled {
736
- background: transparent;
737
- }
738
-
739
- /* Default-variant button on a dark surface — drop the chromed light fill,
740
- pick up a brighter white-tint than inputs (15% vs 8%) to read as raised
741
- rather than recessed, and replace the ineffective black drop shadow
742
- with a top-edge inset highlight (macOS NSButton-on-dark convention).
743
- `:not([material])` opts the tone-driven default fill OUT when the
744
- element has explicit `material` — material owns the surface in that
745
- case, regardless of tone. */
746
- :where([tone="dark"]) button:not([variant]):not([material]):not(:disabled),
747
- :where([tone="dark"]) button[variant="default"]:not([material]):not(:disabled),
748
- :where([tone="dark"]) segmented-control:not([variant]):not([material]),
749
- :where([tone="dark"]) segmented-control[variant="default"]:not([material]) {
750
- background: var(--tint-200);
751
- border-color: var(--tint-300);
752
- box-shadow: inset 0 0.5px 0 var(--tint-300);
753
- }
754
-
755
- :where([tone="dark"]) button:not([variant]):hover:not(:disabled),
756
- :where([tone="dark"]) button[variant="default"]:hover:not(:disabled) {
757
- background: var(--tint-300);
758
- }
759
-
760
- :where([tone="dark"]) button:not([variant]):active:not(:disabled),
761
- :where([tone="dark"]) button[variant="default"]:active:not(:disabled) {
762
- background: var(--tint-400);
763
- box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.20);
764
- }
765
-
766
- /* Explicit light-tone button rules — restore the chromed white-card
767
- styling when a `tone="light"` ancestor sits inside a `tone="dark"`
768
- one (e.g. a paper popover anchored inside dark chrome). Same
769
- specificity as the dark-tone rules above; source order makes the
770
- closer light-tone scope win for slotted descendants. */
771
- :where([tone="light"]) button:not([variant]):not([material]):not(:disabled),
772
- :where([tone="light"]) button[variant="default"]:not([material]):not(:disabled),
773
- :where([tone="light"]) segmented-control:not([variant]):not([material]),
774
- :where([tone="light"]) segmented-control[variant="default"]:not([material]) {
775
- background: var(--color-surface);
776
- border-color: var(--tint-200);
777
- box-shadow: var(--shadow-control);
778
- }
779
-
780
- :where([tone="light"]) button:not([variant]):hover:not(:disabled) {
781
- background: color-mix(in srgb, var(--color-surface) 96%, black);
782
- }
783
-
784
- :where([tone="light"]) button:not([variant]):active:not(:disabled) {
785
- background: color-mix(in srgb, var(--color-surface) 88%, black);
786
- box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.08);
787
- }
788
-
789
- /* Size opt-in: `size="sm"` swaps to the smaller control-height token.
790
- Same applies to `<segmented-control size="sm">` so the host stretches
791
- children correctly, and to `<button size="sm" icon-only>` so the
792
- square footprint stays at the smaller control unit. The default
793
- (md, 28px) is set on the base `<button>` rule above; only sm needs
794
- an explicit override. */
795
- button[size="sm"],
796
- segmented-control[size="sm"] {
797
- height: var(--control-height-sm);
798
- }
799
-
800
- button[size="sm"][icon-only] {
801
- width: var(--control-height-sm);
802
- }
803
-
804
- button[size="xs"] {
805
- height: var(--control-height-xs);
806
- padding: 0 var(--space-6) 1px;
807
- border-radius: var(--radius-8);
808
- }
809
-
810
- button[size="xs"][icon-only] {
811
- width: var(--control-height-xs);
812
- padding: 0;
813
- }
814
-
815
- /* Square icon-only buttons. Width = control height so the footprint is
816
- square at the same vertical size as a text-button sibling. Chrome is
817
- determined by the variant (default = chromed pill, ghost = chromeless
818
- overlay). */
819
- button[icon-only] {
820
- width: var(--control-height-md);
821
- padding: 0;
822
- }
823
-
824
- [data-ui-icon] {
825
- display: inline-block;
826
- vertical-align: middle;
827
- }
828
-
829
- /* Attribute conventions — see docs/ui.md § Attribute conventions.
830
- `[selectable]` opts a subtree back into text selection inside an
831
- ancestor that disabled it (the app's main area sets
832
- `user-select: none` to suppress accidental chrome selection).
833
- `[unselectable]` is the inverse — opts a subtree back OUT of
834
- selection inside a `[selectable]` ancestor, for chrome labels
835
- (e.g. "Used 5 tools") that sit alongside copyable content. */
836
- [selectable],
837
- [selectable] * {
838
- user-select: text;
839
- -webkit-user-select: text;
840
- }
841
-
842
- [unselectable],
843
- [unselectable] * {
844
- user-select: none;
845
- -webkit-user-select: none;
846
- }
847
-
848
- /* Prose — opt-in typography for document content */
849
-
850
- .prose {
851
- line-height: var(--leading-base);
852
- }
853
-
854
- .prose h1 {
855
- font-size: var(--text-xl);
856
- margin-bottom: var(--space-8);
857
- }
858
-
859
- .prose h2 {
860
- font-size: var(--text-lg);
861
- margin-top: var(--space-24);
862
- margin-bottom: var(--space-8);
863
- }
864
-
865
- .prose h3,
866
- .prose h4,
867
- .prose h5,
868
- .prose h6 {
869
- font-size: var(--text-base);
870
- margin-top: var(--space-24);
871
- margin-bottom: var(--space-8);
872
- }
873
-
874
- .prose p {
875
- margin: var(--space-12) 0;
876
- }
877
-
878
- .prose ul,
879
- .prose ol {
880
- padding-left: var(--space-24);
881
- margin: var(--space-12) 0;
882
- }
883
-
884
- .prose blockquote {
885
- margin: var(--space-12) 0;
886
- }
887
-
888
- .prose pre {
889
- margin: var(--space-12) 0;
890
- }
891
-
892
- .prose hr {
893
- margin: var(--space-24) 0;
894
- }
895
-
896
- .prose table {
897
- width: 100%;
898
- margin: var(--space-12) 0;
899
- }
900
-
901
- .prose img {
438
+ img {
902
439
  max-width: 100%;
903
440
  height: auto;
904
441
  }
905
-
906
- .prose > :first-child {
907
- margin-top: 0;
908
- }
909
-
910
- .prose > :last-child {
911
- margin-bottom: 0;
912
- }
913
-
914
-
915
-
916
-