@setsquare/ui 0.2.3 → 0.4.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/CHANGELOG.md +71 -0
- package/NOTICE +1 -1
- package/README.md +13 -6
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/page-items.d.ts +10 -0
- package/dist/lib/page-items.d.ts.map +1 -0
- package/dist/lib/page-items.js +26 -0
- package/dist/lib/page-items.js.map +1 -0
- package/dist/lint/index.d.ts.map +1 -1
- package/dist/lint/index.js +4 -0
- package/dist/lint/index.js.map +1 -1
- package/dist/shell/sidebar.d.ts +23 -0
- package/dist/shell/sidebar.d.ts.map +1 -1
- package/dist/shell/sidebar.js +22 -3
- package/dist/shell/sidebar.js.map +1 -1
- package/dist/styles/theme.css +655 -0
- package/dist/styles/tokens.css +9 -0
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +30 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/tokens.json +25 -0
- package/dist/ui/feature-card.d.ts +85 -0
- package/dist/ui/feature-card.d.ts.map +1 -0
- package/dist/ui/feature-card.js +65 -0
- package/dist/ui/feature-card.js.map +1 -0
- package/dist/ui/pagination.d.ts +69 -14
- package/dist/ui/pagination.d.ts.map +1 -1
- package/dist/ui/pagination.js +177 -32
- package/dist/ui/pagination.js.map +1 -1
- package/dist/ui/tabs.d.ts +82 -15
- package/dist/ui/tabs.d.ts.map +1 -1
- package/dist/ui/tabs.js +274 -16
- package/dist/ui/tabs.js.map +1 -1
- package/dist/ui/time-of-day-icon.d.ts +18 -0
- package/dist/ui/time-of-day-icon.d.ts.map +1 -0
- package/dist/ui/time-of-day-icon.js +26 -0
- package/dist/ui/time-of-day-icon.js.map +1 -0
- package/dist/ui/tree.d.ts +53 -0
- package/dist/ui/tree.d.ts.map +1 -0
- package/dist/ui/tree.js +36 -0
- package/dist/ui/tree.js.map +1 -0
- package/package.json +21 -25
- package/templates/next/page-transition.tsx +1 -1
package/dist/styles/theme.css
CHANGED
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
--color-border: var(--border);
|
|
39
39
|
--color-input: var(--input);
|
|
40
40
|
--color-ring: var(--ring);
|
|
41
|
+
--color-edge: var(--edge);
|
|
42
|
+
--color-edge-selected: var(--edge-selected);
|
|
41
43
|
--color-destructive: var(--destructive);
|
|
42
44
|
--color-destructive-foreground: var(--destructive-foreground);
|
|
43
45
|
--color-destructive-subtle: var(--destructive-subtle);
|
|
@@ -164,6 +166,20 @@
|
|
|
164
166
|
--site-gutter: clamp(20px, calc(3.2 * var(--site-vw, 1vw)), 44px);
|
|
165
167
|
--section-y: clamp(56px, calc(7 * var(--site-vw, 1vw)), 112px);
|
|
166
168
|
--section-y-tight: clamp(40px, calc(5 * var(--site-vw, 1vw)), 80px);
|
|
169
|
+
/* A preview isn't the page, so its shell leaves the page's view-transition names (site-header) to the page's
|
|
170
|
+
own. A name may be on one element only: two, and the browser abandons every page transition. Important,
|
|
171
|
+
to win over the name the header sets inline. */
|
|
172
|
+
& * {
|
|
173
|
+
view-transition-name: none !important;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* The same for a dashboard shell, or a part of one, shown inside a page that has its own, such as a documentation
|
|
178
|
+
site: its header, sidebar and right panel leave shell-header, shell-sidebar and shell-panel to the page's. */
|
|
179
|
+
@utility shell-preview {
|
|
180
|
+
& * {
|
|
181
|
+
view-transition-name: none !important;
|
|
182
|
+
}
|
|
167
183
|
}
|
|
168
184
|
|
|
169
185
|
/* The content width of a public page, centred, with the gutter either side. */
|
|
@@ -259,6 +275,7 @@
|
|
|
259
275
|
}
|
|
260
276
|
}
|
|
261
277
|
@utility card-surface {
|
|
278
|
+
--tabs-surface: var(--card);
|
|
262
279
|
position: relative;
|
|
263
280
|
padding: var(--space-6);
|
|
264
281
|
border: 1px solid var(--border);
|
|
@@ -429,6 +446,644 @@
|
|
|
429
446
|
@keyframes setsquare-sheet { from { opacity: 0; translate: var(--sheet-from, 0 0); } }
|
|
430
447
|
@keyframes setsquare-toast { from { opacity: 0; translate: 0 var(--motion-distance); } }
|
|
431
448
|
|
|
449
|
+
/* ---------- arrivals: a FeatureCard and its parts, once, as it first renders ----------
|
|
450
|
+
Text and controls never fade: they are readable from the first frame, so nothing waits on an animation to be
|
|
451
|
+
seen. arrive-rise only travels --motion-distance, so at AAA (0) and under reduced motion it does nothing. The
|
|
452
|
+
rest is decoration (the icon tile, the icon's strokes): it may fade in, and at AAA it only fades, as everything
|
|
453
|
+
that moves in this system does; 2.3.3's Understanding page doesn't count a change of opacity as motion. All of
|
|
454
|
+
it is over in under three seconds. Under reduced motion nothing animates. */
|
|
455
|
+
@keyframes setsquare-rise { from { translate: 0 var(--motion-distance); } }
|
|
456
|
+
@keyframes setsquare-pop { 0% { opacity: 0; scale: 0.6; } 60% { opacity: 1; scale: 1.06; } }
|
|
457
|
+
@keyframes setsquare-sunrise { from { opacity: 0.2; translate: 0 var(--motion-distance); } }
|
|
458
|
+
@keyframes setsquare-spin-in { from { opacity: 0; rotate: -75deg; scale: 0.7; } }
|
|
459
|
+
@keyframes setsquare-moon-in { from { opacity: 0; rotate: -35deg; scale: 0.85; } }
|
|
460
|
+
@keyframes setsquare-twinkle { 0% { opacity: 0; scale: 0.2; } 30% { opacity: 1; scale: 1.25; } 55% { opacity: 0.35; scale: 0.8; } 80% { opacity: 1; scale: 1.08; } }
|
|
461
|
+
@keyframes setsquare-ripple { from { --feature-ripple-radius: 0px; opacity: 0.72; } 70% { opacity: 0.72; } to { --feature-ripple-radius: 1600px; opacity: 0; } }
|
|
462
|
+
@utility arrive-rise {
|
|
463
|
+
animation: setsquare-rise var(--duration-arrive) var(--ease-standard) both;
|
|
464
|
+
@media (prefers-reduced-motion: reduce) { animation: none; }
|
|
465
|
+
}
|
|
466
|
+
@utility arrive-pop {
|
|
467
|
+
animation: setsquare-pop calc(var(--duration-arrive) * 1.1) var(--ease-standard) both;
|
|
468
|
+
transform-box: fill-box;
|
|
469
|
+
transform-origin: center;
|
|
470
|
+
@variant aaa { animation-name: setsquare-fade; }
|
|
471
|
+
@media (prefers-reduced-motion: reduce) { animation: none; }
|
|
472
|
+
}
|
|
473
|
+
@utility arrive-sunrise {
|
|
474
|
+
animation: setsquare-sunrise calc(var(--duration-arrive) * 2.3) var(--ease-standard) both;
|
|
475
|
+
transform-box: fill-box;
|
|
476
|
+
transform-origin: center;
|
|
477
|
+
@variant aaa { animation-name: setsquare-fade; }
|
|
478
|
+
@media (prefers-reduced-motion: reduce) { animation: none; }
|
|
479
|
+
}
|
|
480
|
+
@utility arrive-spin-in {
|
|
481
|
+
animation: setsquare-spin-in calc(var(--duration-arrive) * 2.1) var(--ease-standard) both;
|
|
482
|
+
transform-box: view-box;
|
|
483
|
+
transform-origin: 16px 16px;
|
|
484
|
+
@variant aaa { animation-name: setsquare-fade; }
|
|
485
|
+
@media (prefers-reduced-motion: reduce) { animation: none; }
|
|
486
|
+
}
|
|
487
|
+
@utility arrive-moon-in {
|
|
488
|
+
animation: setsquare-moon-in calc(var(--duration-arrive) * 1.9) var(--ease-standard) both;
|
|
489
|
+
transform-box: view-box;
|
|
490
|
+
transform-origin: 16px 16px;
|
|
491
|
+
@variant aaa { animation-name: setsquare-fade; }
|
|
492
|
+
@media (prefers-reduced-motion: reduce) { animation: none; }
|
|
493
|
+
}
|
|
494
|
+
@utility arrive-twinkle {
|
|
495
|
+
animation: setsquare-twinkle calc(var(--duration-arrive) * 2.9) var(--ease-standard) both;
|
|
496
|
+
transform-box: fill-box;
|
|
497
|
+
transform-origin: center;
|
|
498
|
+
@variant aaa { animation-name: setsquare-fade; }
|
|
499
|
+
@media (prefers-reduced-motion: reduce) { animation: none; }
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* ---------- Tabs: icons, and the selected tab opens to show its name ----------
|
|
503
|
+
Tab (the target) > tabs-paint (the pill) > tabs-icon and tabs-name. The paint's data-display says what a closed
|
|
504
|
+
tab shows: expanding (the name opens when selected), text, or icon. Widths and heights animate at AA only; at
|
|
505
|
+
AAA the name still fades in (opacity is not motion), and under reduced motion nothing animates. */
|
|
506
|
+
@keyframes setsquare-tab-pop { 0% { scale: 0.82; } 60% { scale: 1.08; } 100% { scale: 1; } }
|
|
507
|
+
@keyframes setsquare-badge-beat { 0%, 40%, 100% { scale: 1; } 15% { scale: 1.15; } }
|
|
508
|
+
@keyframes setsquare-badge-halo { 0% { scale: 0.5; opacity: 0.6; } 100% { scale: 1; opacity: 0; } }
|
|
509
|
+
@keyframes setsquare-badge-glow { 0%, 100% { opacity: 0; } 50% { opacity: 0.45; } }
|
|
510
|
+
@keyframes setsquare-badge-out { to { opacity: 0; } }
|
|
511
|
+
@keyframes setsquare-tip-below { from { opacity: 0; translate: -50% calc(var(--motion-distance) * -0.5); } }
|
|
512
|
+
@keyframes setsquare-tip-beside { from { opacity: 0; translate: calc(var(--motion-distance) * -0.5) -50%; } }
|
|
513
|
+
@keyframes setsquare-panel-in { from { opacity: 0; translate: var(--tabs-from, 0 0); } }
|
|
514
|
+
@utility tabs-list {
|
|
515
|
+
position: relative;
|
|
516
|
+
display: flex;
|
|
517
|
+
flex-wrap: wrap;
|
|
518
|
+
align-items: center;
|
|
519
|
+
gap: 4px;
|
|
520
|
+
/* The rail: 104px of icon tiles, so it never changes width; the selected tile grows downwards. */
|
|
521
|
+
&[data-orientation='vertical'] {
|
|
522
|
+
flex: none;
|
|
523
|
+
flex-direction: column;
|
|
524
|
+
flex-wrap: nowrap;
|
|
525
|
+
align-items: stretch;
|
|
526
|
+
width: 104px;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
@utility tabs-paint {
|
|
530
|
+
/* The badge's cut-out is the tab's own surface: the page (or --tabs-surface), the hover ground, the fill. */
|
|
531
|
+
--tabs-cut: var(--tabs-surface, var(--background));
|
|
532
|
+
position: relative;
|
|
533
|
+
display: inline-grid;
|
|
534
|
+
grid-template-columns: 20px 0fr;
|
|
535
|
+
align-items: center;
|
|
536
|
+
column-gap: 0;
|
|
537
|
+
height: var(--control-height);
|
|
538
|
+
padding: 0 8px;
|
|
539
|
+
border-radius: 999px;
|
|
540
|
+
font-size: 14px;
|
|
541
|
+
line-height: 20px;
|
|
542
|
+
font-weight: 500;
|
|
543
|
+
color: var(--muted-foreground);
|
|
544
|
+
white-space: nowrap;
|
|
545
|
+
transition: grid-template-columns 280ms var(--ease-standard), column-gap 280ms var(--ease-standard),
|
|
546
|
+
padding 280ms var(--ease-standard), background-color var(--duration-fast) var(--ease-standard),
|
|
547
|
+
color var(--duration-fast) var(--ease-standard);
|
|
548
|
+
[data-hovered] > & {
|
|
549
|
+
--tabs-cut: var(--accent);
|
|
550
|
+
background-color: var(--accent);
|
|
551
|
+
color: var(--foreground);
|
|
552
|
+
}
|
|
553
|
+
[data-selected] > & {
|
|
554
|
+
--tabs-cut: var(--brand-subtle);
|
|
555
|
+
grid-template-columns: 20px 1fr;
|
|
556
|
+
column-gap: 8px;
|
|
557
|
+
padding: 0 14px 0 10px;
|
|
558
|
+
background-color: var(--brand-subtle);
|
|
559
|
+
color: var(--foreground);
|
|
560
|
+
}
|
|
561
|
+
/* Text has no icon column: the name is the only one. */
|
|
562
|
+
&[data-display='text'] {
|
|
563
|
+
grid-template-columns: 1fr;
|
|
564
|
+
column-gap: 0;
|
|
565
|
+
padding: 0 14px;
|
|
566
|
+
}
|
|
567
|
+
[data-selected] > &[data-display='icon'] {
|
|
568
|
+
grid-template-columns: 20px 0fr;
|
|
569
|
+
column-gap: 0;
|
|
570
|
+
padding: 0 8px;
|
|
571
|
+
}
|
|
572
|
+
/* Where the name doesn't open to mark the selected tab, a brand marker does: selection never rests on the fill
|
|
573
|
+
alone (1.4.1, 1.4.11). */
|
|
574
|
+
[data-selected] > &:is([data-display='text'], [data-display='icon'])::after {
|
|
575
|
+
content: '';
|
|
576
|
+
position: absolute;
|
|
577
|
+
left: 10px;
|
|
578
|
+
right: 10px;
|
|
579
|
+
bottom: 3px;
|
|
580
|
+
height: 3px;
|
|
581
|
+
border-radius: 2px;
|
|
582
|
+
background-color: var(--brand);
|
|
583
|
+
}
|
|
584
|
+
@variant aaa {
|
|
585
|
+
transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard);
|
|
586
|
+
}
|
|
587
|
+
[role='tablist'][data-orientation='vertical'] & {
|
|
588
|
+
display: grid;
|
|
589
|
+
grid-template-columns: none;
|
|
590
|
+
grid-template-rows: 20px 0fr;
|
|
591
|
+
justify-items: center;
|
|
592
|
+
row-gap: 0;
|
|
593
|
+
width: 100%;
|
|
594
|
+
height: auto;
|
|
595
|
+
min-height: var(--control-height);
|
|
596
|
+
padding: 8px 4px;
|
|
597
|
+
border-radius: var(--radius-lg);
|
|
598
|
+
white-space: normal;
|
|
599
|
+
transition: grid-template-rows 280ms var(--ease-standard), row-gap 280ms var(--ease-standard),
|
|
600
|
+
background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard);
|
|
601
|
+
@variant aaa {
|
|
602
|
+
transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
[role='tablist'][data-orientation='vertical'] [data-selected] > & {
|
|
606
|
+
grid-template-rows: 20px 1fr;
|
|
607
|
+
row-gap: 6px;
|
|
608
|
+
}
|
|
609
|
+
[role='tablist'][data-orientation='vertical'] &[data-display='text'] {
|
|
610
|
+
grid-template-rows: 1fr;
|
|
611
|
+
row-gap: 0;
|
|
612
|
+
}
|
|
613
|
+
[role='tablist'][data-orientation='vertical'] [data-selected] > &[data-display='icon'] {
|
|
614
|
+
grid-template-rows: 20px 0fr;
|
|
615
|
+
row-gap: 0;
|
|
616
|
+
}
|
|
617
|
+
[role='tablist'][data-orientation='vertical'] [data-selected] > &:is([data-display='text'], [data-display='icon'])::after {
|
|
618
|
+
left: 3px;
|
|
619
|
+
right: auto;
|
|
620
|
+
top: 10px;
|
|
621
|
+
bottom: 10px;
|
|
622
|
+
width: 3px;
|
|
623
|
+
height: auto;
|
|
624
|
+
}
|
|
625
|
+
@media (prefers-reduced-motion: reduce) {
|
|
626
|
+
transition: none !important;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
@utility tabs-icon {
|
|
630
|
+
position: relative;
|
|
631
|
+
display: block;
|
|
632
|
+
width: 20px;
|
|
633
|
+
height: 20px;
|
|
634
|
+
& > :not(.tabs-badge) {
|
|
635
|
+
display: block;
|
|
636
|
+
}
|
|
637
|
+
/* The selected tab's icon turns brand and pops (at AA). */
|
|
638
|
+
[data-selected] & > :not(.tabs-badge) {
|
|
639
|
+
color: var(--brand);
|
|
640
|
+
animation: setsquare-tab-pop 300ms var(--ease-standard);
|
|
641
|
+
@variant aaa {
|
|
642
|
+
animation: none;
|
|
643
|
+
}
|
|
644
|
+
@media (prefers-reduced-motion: reduce) {
|
|
645
|
+
animation: none;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
@utility tabs-name {
|
|
650
|
+
min-width: 0;
|
|
651
|
+
/* Clipped sideways only, so an important badge's ring after the name isn't cut off above or below. */
|
|
652
|
+
overflow-x: clip;
|
|
653
|
+
overflow-y: visible;
|
|
654
|
+
opacity: 0;
|
|
655
|
+
transition: opacity 160ms var(--ease-standard);
|
|
656
|
+
[data-selected] & {
|
|
657
|
+
opacity: 1;
|
|
658
|
+
transition-delay: 80ms;
|
|
659
|
+
}
|
|
660
|
+
[data-display='text'] > & {
|
|
661
|
+
opacity: 1;
|
|
662
|
+
}
|
|
663
|
+
[data-selected] [data-display='icon'] > & {
|
|
664
|
+
opacity: 0;
|
|
665
|
+
}
|
|
666
|
+
/* In the rail it fills its row, which opens from nothing, so a closing name is clipped below the icon rather
|
|
667
|
+
than spilling up over it. */
|
|
668
|
+
[role='tablist'][data-orientation='vertical'] & {
|
|
669
|
+
align-self: stretch;
|
|
670
|
+
width: 100%;
|
|
671
|
+
min-height: 0;
|
|
672
|
+
overflow: hidden;
|
|
673
|
+
}
|
|
674
|
+
[role='tablist'][data-orientation='vertical'] [data-display='text'] > & {
|
|
675
|
+
overflow: visible;
|
|
676
|
+
}
|
|
677
|
+
@media (prefers-reduced-motion: reduce) {
|
|
678
|
+
transition: none !important;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
@utility tabs-name-row {
|
|
682
|
+
display: inline-flex;
|
|
683
|
+
align-items: center;
|
|
684
|
+
gap: 6px;
|
|
685
|
+
/* In the rail, names wrap and hyphenate inside the tile (1.4.12). */
|
|
686
|
+
[role='tablist'][data-orientation='vertical'] & {
|
|
687
|
+
display: block;
|
|
688
|
+
text-align: center;
|
|
689
|
+
font-size: 13px;
|
|
690
|
+
line-height: 16px;
|
|
691
|
+
hyphens: auto;
|
|
692
|
+
overflow-wrap: anywhere;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
/* The name, over a hidden bold copy that holds the bold width from the start. */
|
|
696
|
+
@utility tabs-name-text {
|
|
697
|
+
display: inline-grid;
|
|
698
|
+
& > * {
|
|
699
|
+
grid-area: 1 / 1;
|
|
700
|
+
}
|
|
701
|
+
& > :last-child {
|
|
702
|
+
font-weight: 700;
|
|
703
|
+
visibility: hidden;
|
|
704
|
+
}
|
|
705
|
+
[data-selected] & > :first-child {
|
|
706
|
+
font-weight: 700;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
@utility tabs-count {
|
|
710
|
+
font-size: 13px;
|
|
711
|
+
line-height: 20px;
|
|
712
|
+
font-weight: 500;
|
|
713
|
+
color: var(--muted-foreground);
|
|
714
|
+
font-variant-numeric: tabular-nums;
|
|
715
|
+
[role='tablist'][data-orientation='vertical'] & {
|
|
716
|
+
display: none;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/* A badge: pinned to the icon's top-right while the tab is closed, anchored by its right edge so a wide count
|
|
720
|
+
grows back over its own icon and never reaches the next tab, with a 2px cut-out in the tab's own surface. After
|
|
721
|
+
the name when the name shows beside the icon, so it never sits over text. */
|
|
722
|
+
@utility tabs-badge {
|
|
723
|
+
position: absolute;
|
|
724
|
+
top: -8px;
|
|
725
|
+
right: -8px;
|
|
726
|
+
isolation: isolate;
|
|
727
|
+
display: inline-flex;
|
|
728
|
+
align-items: center;
|
|
729
|
+
justify-content: center;
|
|
730
|
+
min-width: 18px;
|
|
731
|
+
height: 18px;
|
|
732
|
+
padding: 0 5px;
|
|
733
|
+
border-radius: 999px;
|
|
734
|
+
background-color: var(--foreground);
|
|
735
|
+
color: var(--background);
|
|
736
|
+
font-size: 13px;
|
|
737
|
+
line-height: 18px;
|
|
738
|
+
font-weight: 700;
|
|
739
|
+
font-variant-numeric: tabular-nums;
|
|
740
|
+
white-space: nowrap;
|
|
741
|
+
box-shadow: 0 0 0 2px var(--tabs-cut, var(--background));
|
|
742
|
+
pointer-events: none;
|
|
743
|
+
&[data-dot] {
|
|
744
|
+
top: -3px;
|
|
745
|
+
right: -4px;
|
|
746
|
+
min-width: 10px;
|
|
747
|
+
width: 10px;
|
|
748
|
+
height: 10px;
|
|
749
|
+
padding: 0;
|
|
750
|
+
}
|
|
751
|
+
&[data-place='inline'] {
|
|
752
|
+
position: relative;
|
|
753
|
+
top: auto;
|
|
754
|
+
right: auto;
|
|
755
|
+
box-shadow: none;
|
|
756
|
+
}
|
|
757
|
+
[role='tablist'][data-orientation='vertical'] &[data-place='inline'] {
|
|
758
|
+
margin-left: 6px;
|
|
759
|
+
vertical-align: middle;
|
|
760
|
+
}
|
|
761
|
+
/* Important: the destructive fill and a ring that stays, so importance never rests on colour or motion alone. */
|
|
762
|
+
&[data-important] {
|
|
763
|
+
background-color: var(--destructive);
|
|
764
|
+
color: var(--destructive-foreground);
|
|
765
|
+
}
|
|
766
|
+
&[data-important]::after {
|
|
767
|
+
content: '';
|
|
768
|
+
position: absolute;
|
|
769
|
+
inset: -4px;
|
|
770
|
+
border: 2px solid var(--destructive);
|
|
771
|
+
border-radius: inherit;
|
|
772
|
+
pointer-events: none;
|
|
773
|
+
}
|
|
774
|
+
/* The halo the pulse swells: 8px past the badge and no further, so it stays off the next tab and any text. */
|
|
775
|
+
&[data-important]::before {
|
|
776
|
+
content: '';
|
|
777
|
+
position: absolute;
|
|
778
|
+
inset: -8px;
|
|
779
|
+
z-index: -1;
|
|
780
|
+
border-radius: 999px;
|
|
781
|
+
background-color: var(--destructive);
|
|
782
|
+
opacity: 0;
|
|
783
|
+
pointer-events: none;
|
|
784
|
+
}
|
|
785
|
+
/* After a name: 10px from the text, a 5px halo and 6px of room on its right. */
|
|
786
|
+
&[data-important][data-place='inline'] {
|
|
787
|
+
margin: 0 6px 0 4px;
|
|
788
|
+
}
|
|
789
|
+
[role='tablist'][data-orientation='vertical'] &[data-important][data-place='inline'] {
|
|
790
|
+
margin-left: 10px;
|
|
791
|
+
}
|
|
792
|
+
&[data-important][data-place='inline']::after {
|
|
793
|
+
inset: -3px;
|
|
794
|
+
}
|
|
795
|
+
&[data-important][data-place='inline']::before {
|
|
796
|
+
inset: -5px;
|
|
797
|
+
}
|
|
798
|
+
/* The pulse: three times in 4.5s, so it stops by itself inside the 5 seconds 2.2.2 allows. At AAA nothing
|
|
799
|
+
grows: the halo glows in and out at its full size. */
|
|
800
|
+
&[data-pulse] {
|
|
801
|
+
animation: setsquare-badge-beat 1500ms var(--ease-standard) 3;
|
|
802
|
+
}
|
|
803
|
+
&[data-pulse]::before {
|
|
804
|
+
animation: setsquare-badge-halo 1500ms cubic-bezier(0, 0, 0.2, 1) 3;
|
|
805
|
+
}
|
|
806
|
+
@variant aaa {
|
|
807
|
+
&[data-pulse] {
|
|
808
|
+
animation: none;
|
|
809
|
+
}
|
|
810
|
+
&[data-pulse]::before {
|
|
811
|
+
animation: setsquare-badge-glow 1500ms ease-in-out 3;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
/* Back on the icon as its tab closes: it waits for the name to fade, so it never lands on the text. */
|
|
815
|
+
&[data-returning] {
|
|
816
|
+
animation: setsquare-fade var(--duration-fast) var(--ease-standard) 200ms both;
|
|
817
|
+
}
|
|
818
|
+
&[data-clearing] {
|
|
819
|
+
animation: setsquare-badge-out 300ms var(--ease-standard) 700ms both;
|
|
820
|
+
}
|
|
821
|
+
/* Clearing and its tab closed before it went: it doesn't come back to the icon just to leave. */
|
|
822
|
+
&[data-clearing][data-returning] {
|
|
823
|
+
opacity: 0;
|
|
824
|
+
animation: none;
|
|
825
|
+
}
|
|
826
|
+
@media (prefers-reduced-motion: reduce) {
|
|
827
|
+
&, &::before {
|
|
828
|
+
animation: none !important;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
/* A tooltip for a tab whose name isn't showing. It sits inside the tab, so the pointer can move onto it across an
|
|
833
|
+
8px bridge without leaving. Hidden from assistive tech: the name is the tab's own. */
|
|
834
|
+
@utility tabs-tip {
|
|
835
|
+
position: absolute;
|
|
836
|
+
z-index: var(--z-popover);
|
|
837
|
+
padding: 4px 8px;
|
|
838
|
+
border-radius: var(--radius-md);
|
|
839
|
+
background-color: var(--foreground);
|
|
840
|
+
color: var(--background);
|
|
841
|
+
font-size: 13px;
|
|
842
|
+
line-height: 20px;
|
|
843
|
+
font-weight: 500;
|
|
844
|
+
white-space: nowrap;
|
|
845
|
+
box-shadow: var(--shadow-lg);
|
|
846
|
+
&::before {
|
|
847
|
+
content: '';
|
|
848
|
+
position: absolute;
|
|
849
|
+
}
|
|
850
|
+
&[data-side='bottom'] {
|
|
851
|
+
top: calc(100% + 6px);
|
|
852
|
+
left: 50%;
|
|
853
|
+
translate: -50% 0;
|
|
854
|
+
animation: setsquare-tip-below var(--duration-fast) var(--ease-standard) both;
|
|
855
|
+
}
|
|
856
|
+
&[data-side='bottom']::before {
|
|
857
|
+
left: 0;
|
|
858
|
+
right: 0;
|
|
859
|
+
top: -8px;
|
|
860
|
+
height: 8px;
|
|
861
|
+
}
|
|
862
|
+
&[data-side='right'] {
|
|
863
|
+
left: calc(100% + 6px);
|
|
864
|
+
top: 50%;
|
|
865
|
+
translate: 0 -50%;
|
|
866
|
+
animation: setsquare-tip-beside var(--duration-fast) var(--ease-standard) both;
|
|
867
|
+
}
|
|
868
|
+
&[data-side='right']::before {
|
|
869
|
+
top: 0;
|
|
870
|
+
bottom: 0;
|
|
871
|
+
left: -8px;
|
|
872
|
+
width: 8px;
|
|
873
|
+
}
|
|
874
|
+
@media (prefers-reduced-motion: reduce) {
|
|
875
|
+
animation: none !important;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
/* A panel comes in from the side the selection moved towards: --motion-distance and a fade, a fade alone at AAA. */
|
|
879
|
+
@utility tabs-panel {
|
|
880
|
+
&[data-travel] {
|
|
881
|
+
animation: setsquare-panel-in var(--duration-enter) var(--ease-standard) both;
|
|
882
|
+
}
|
|
883
|
+
&[data-travel='forward'] {
|
|
884
|
+
--tabs-from: var(--motion-distance) 0;
|
|
885
|
+
}
|
|
886
|
+
&[data-travel='back'] {
|
|
887
|
+
--tabs-from: calc(var(--motion-distance) * -1) 0;
|
|
888
|
+
}
|
|
889
|
+
[data-orientation='vertical'] > &[data-travel='forward'] {
|
|
890
|
+
--tabs-from: 0 var(--motion-distance);
|
|
891
|
+
}
|
|
892
|
+
[data-orientation='vertical'] > &[data-travel='back'] {
|
|
893
|
+
--tabs-from: 0 calc(var(--motion-distance) * -1);
|
|
894
|
+
}
|
|
895
|
+
@media (prefers-reduced-motion: reduce) {
|
|
896
|
+
animation: none !important;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/* ---------- Pagination: the results footer ----------
|
|
901
|
+
The page links' paint (Layer 2), the range's figures coming in from the way you paged, the Go button arriving,
|
|
902
|
+
and the mark on a control whose page is slow to come. At AAA all of it fades; under reduced motion none of it
|
|
903
|
+
moves, and the pending mark holds still. */
|
|
904
|
+
@keyframes setsquare-current-pop { 0% { scale: 0.86; } 60% { scale: 1.04; } 100% { scale: 1; } }
|
|
905
|
+
@keyframes setsquare-roll-in { from { opacity: 0; translate: 0 var(--pagination-from, 0); } }
|
|
906
|
+
@keyframes setsquare-go-in { from { opacity: 0; translate: calc(var(--motion-distance) * -0.75) 0; } }
|
|
907
|
+
@keyframes setsquare-pending { 0%, 100% { opacity: 0.2; scale: 0.4 1; } 50% { opacity: 1; scale: 1 1; } }
|
|
908
|
+
@keyframes setsquare-pending-fade { 0%, 100% { opacity: 0.2; } 50% { opacity: 1; } }
|
|
909
|
+
@keyframes setsquare-pending-hold { to { opacity: 0.6; scale: 1 1; } }
|
|
910
|
+
@utility pagination-paint {
|
|
911
|
+
position: relative;
|
|
912
|
+
display: inline-flex;
|
|
913
|
+
align-items: center;
|
|
914
|
+
justify-content: center;
|
|
915
|
+
gap: 4px;
|
|
916
|
+
height: var(--control-height);
|
|
917
|
+
min-width: var(--control-height);
|
|
918
|
+
padding: 0 8px;
|
|
919
|
+
border-radius: var(--radius-md);
|
|
920
|
+
font-size: 14px;
|
|
921
|
+
line-height: 20px;
|
|
922
|
+
font-weight: 500;
|
|
923
|
+
font-variant-numeric: tabular-nums;
|
|
924
|
+
white-space: nowrap;
|
|
925
|
+
color: var(--foreground);
|
|
926
|
+
transition: background-color var(--duration-fast) var(--ease-standard);
|
|
927
|
+
[data-hovered] > & {
|
|
928
|
+
background-color: var(--accent);
|
|
929
|
+
}
|
|
930
|
+
[aria-current='page'] > & {
|
|
931
|
+
background-color: var(--primary);
|
|
932
|
+
color: var(--primary-foreground);
|
|
933
|
+
font-weight: 700;
|
|
934
|
+
}
|
|
935
|
+
/* The new current page pops, at AA, once the page has changed. */
|
|
936
|
+
[data-moved] [aria-current='page'] > & {
|
|
937
|
+
animation: setsquare-current-pop 240ms var(--ease-standard);
|
|
938
|
+
}
|
|
939
|
+
[data-pending] > &::after {
|
|
940
|
+
content: '';
|
|
941
|
+
position: absolute;
|
|
942
|
+
left: 8px;
|
|
943
|
+
right: 8px;
|
|
944
|
+
bottom: 4px;
|
|
945
|
+
height: 2px;
|
|
946
|
+
border-radius: 2px;
|
|
947
|
+
background-color: currentColor;
|
|
948
|
+
opacity: 0;
|
|
949
|
+
/* Five beats, then it holds still: it stops by itself inside the five seconds 2.2.2 allows. */
|
|
950
|
+
animation:
|
|
951
|
+
setsquare-pending 900ms var(--ease-standard) var(--duration-loading-delay) 5,
|
|
952
|
+
setsquare-pending-hold 0s calc(var(--duration-loading-delay) + 4500ms) forwards;
|
|
953
|
+
}
|
|
954
|
+
@variant aaa {
|
|
955
|
+
[data-moved] [aria-current='page'] > & {
|
|
956
|
+
animation: none;
|
|
957
|
+
}
|
|
958
|
+
[data-pending] > &::after {
|
|
959
|
+
animation-name: setsquare-pending-fade, setsquare-pending-hold;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
@media (prefers-reduced-motion: reduce) {
|
|
963
|
+
transition: none;
|
|
964
|
+
[data-moved] [aria-current='page'] > & {
|
|
965
|
+
animation: none;
|
|
966
|
+
}
|
|
967
|
+
[data-pending] > &::after {
|
|
968
|
+
animation: none;
|
|
969
|
+
opacity: 0.6;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
/* The page field's mark when its page is slow: the same underline, under the number. */
|
|
974
|
+
@utility pagination-field-pending {
|
|
975
|
+
position: relative;
|
|
976
|
+
&::after {
|
|
977
|
+
content: '';
|
|
978
|
+
position: absolute;
|
|
979
|
+
left: calc(var(--control-pad-x) + 2px);
|
|
980
|
+
right: calc(var(--control-pad-x) + 2px);
|
|
981
|
+
bottom: calc(var(--field-band, 0px) + 4px);
|
|
982
|
+
height: 2px;
|
|
983
|
+
border-radius: 2px;
|
|
984
|
+
background-color: currentColor;
|
|
985
|
+
opacity: 0;
|
|
986
|
+
pointer-events: none;
|
|
987
|
+
animation:
|
|
988
|
+
setsquare-pending 900ms var(--ease-standard) var(--duration-loading-delay) 5,
|
|
989
|
+
setsquare-pending-hold 0s calc(var(--duration-loading-delay) + 4500ms) forwards;
|
|
990
|
+
@media (prefers-reduced-motion: reduce) {
|
|
991
|
+
animation: none;
|
|
992
|
+
opacity: 0.6;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
@variant aaa {
|
|
996
|
+
&::after {
|
|
997
|
+
animation-name: setsquare-pending-fade, setsquare-pending-hold;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/* The range's figures come in from the way you paged: up for later pages, down for earlier ones. */
|
|
1002
|
+
@utility pagination-roll {
|
|
1003
|
+
display: inline-block;
|
|
1004
|
+
&[data-direction] {
|
|
1005
|
+
animation: setsquare-roll-in 220ms var(--ease-standard);
|
|
1006
|
+
}
|
|
1007
|
+
&[data-direction='forward'] {
|
|
1008
|
+
--pagination-from: calc(var(--motion-distance) * 0.75);
|
|
1009
|
+
}
|
|
1010
|
+
&[data-direction='back'] {
|
|
1011
|
+
--pagination-from: calc(var(--motion-distance) * -0.75);
|
|
1012
|
+
}
|
|
1013
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1014
|
+
animation: none !important;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
@utility pagination-go {
|
|
1018
|
+
animation: setsquare-go-in 180ms var(--ease-standard) both;
|
|
1019
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1020
|
+
animation: none;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
/* A new page's rows fade in (no movement, at every level). Key the rows by page so they are new elements. */
|
|
1024
|
+
@utility rows-arrive {
|
|
1025
|
+
& > * {
|
|
1026
|
+
animation: setsquare-fade 200ms var(--ease-standard);
|
|
1027
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1028
|
+
animation: none;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/* ---------- FeatureCard's ground: dots fading out from the icon, and one ripple across them ----------
|
|
1034
|
+
The dots sit behind the title and the muted text, so they are light enough for that text to keep 4.5:1 on a
|
|
1035
|
+
dot as well as on the card (scripts/lib/contrast.mjs measures it on the darkest dot). At AAA there are none:
|
|
1036
|
+
7:1 leaves a product's muted text too little room over a pattern. The ripple brightens them as it passes,
|
|
1037
|
+
once, in about two seconds; it doesn't run at AAA or under reduced motion. Each layer's strength is its opacity,
|
|
1038
|
+
not a color-mix(), which Tailwind would give a full-strength fallback. */
|
|
1039
|
+
@property --feature-ripple-radius { syntax: "<length>"; inherits: false; initial-value: 0px; }
|
|
1040
|
+
@utility feature-dots {
|
|
1041
|
+
position: absolute;
|
|
1042
|
+
inset: 0;
|
|
1043
|
+
pointer-events: none;
|
|
1044
|
+
opacity: 0.2;
|
|
1045
|
+
background-image: radial-gradient(circle, var(--input) 1px, transparent 1.5px);
|
|
1046
|
+
background-size: 16px 16px;
|
|
1047
|
+
mask-image: radial-gradient(120% 170% at 0% 0%, black 0%, rgb(0 0 0 / 0.45) 38%, transparent 72%);
|
|
1048
|
+
@variant aaa { display: none; }
|
|
1049
|
+
}
|
|
1050
|
+
@utility feature-ripple {
|
|
1051
|
+
position: absolute;
|
|
1052
|
+
inset: 0;
|
|
1053
|
+
pointer-events: none;
|
|
1054
|
+
opacity: 0;
|
|
1055
|
+
background-image: radial-gradient(circle, var(--foreground) 1.1px, transparent 1.6px);
|
|
1056
|
+
background-size: 16px 16px;
|
|
1057
|
+
mask-image: radial-gradient(circle at var(--feature-ripple-origin, 48px 44px), transparent calc(var(--feature-ripple-radius) - 170px), black calc(var(--feature-ripple-radius) - 70px), transparent var(--feature-ripple-radius));
|
|
1058
|
+
animation: setsquare-ripple calc(var(--duration-arrive) * 4.4) cubic-bezier(0.3, 0.1, 0.3, 1) 120ms both;
|
|
1059
|
+
@variant aaa { display: none; }
|
|
1060
|
+
@media (prefers-reduced-motion: reduce) { display: none; }
|
|
1061
|
+
}
|
|
1062
|
+
/* An update's picture: a muted, dotted frame for an illustration or a screenshot. */
|
|
1063
|
+
@utility feature-media {
|
|
1064
|
+
position: relative;
|
|
1065
|
+
overflow: clip;
|
|
1066
|
+
background-color: var(--muted);
|
|
1067
|
+
&::before {
|
|
1068
|
+
content: '';
|
|
1069
|
+
position: absolute;
|
|
1070
|
+
inset: 0;
|
|
1071
|
+
opacity: 0.26;
|
|
1072
|
+
background-image: radial-gradient(circle, var(--input) 1px, transparent 1.5px);
|
|
1073
|
+
background-size: 14px 14px;
|
|
1074
|
+
}
|
|
1075
|
+
& > :is(svg, img, video, picture) {
|
|
1076
|
+
position: absolute;
|
|
1077
|
+
inset: 0;
|
|
1078
|
+
width: 100%;
|
|
1079
|
+
height: 100%;
|
|
1080
|
+
display: block;
|
|
1081
|
+
}
|
|
1082
|
+
& > img, & > video {
|
|
1083
|
+
object-fit: cover;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
432
1087
|
/* ---------- view transitions: the shell is the anchor, content moves ---------- */
|
|
433
1088
|
::view-transition { pointer-events: none; }
|
|
434
1089
|
::view-transition-group(shell-header), ::view-transition-group(shell-sidebar), ::view-transition-group(site-header) { animation: none; z-index: 100; }
|