@northstar-js/react 0.1.0 → 0.2.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.
Files changed (127) hide show
  1. package/dist/actions.d.ts +71 -4
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +50 -4
  4. package/dist/actions.js.map +1 -1
  5. package/dist/backdrops/StarBackdrop.d.ts +31 -0
  6. package/dist/backdrops/StarBackdrop.d.ts.map +1 -0
  7. package/dist/backdrops/StarBackdrop.js +84 -0
  8. package/dist/backdrops/StarBackdrop.js.map +1 -0
  9. package/dist/backdrops/index.d.ts +7 -0
  10. package/dist/backdrops/index.d.ts.map +1 -0
  11. package/dist/backdrops/index.js +8 -0
  12. package/dist/backdrops/index.js.map +1 -0
  13. package/dist/backdrops/math.d.ts +39 -0
  14. package/dist/backdrops/math.d.ts.map +1 -0
  15. package/dist/backdrops/math.js +44 -0
  16. package/dist/backdrops/math.js.map +1 -0
  17. package/dist/backdrops/scenes.d.ts +20 -0
  18. package/dist/backdrops/scenes.d.ts.map +1 -0
  19. package/dist/backdrops/scenes.js +417 -0
  20. package/dist/backdrops/scenes.js.map +1 -0
  21. package/dist/backdrops.css +141 -0
  22. package/dist/data.d.ts +14 -3
  23. package/dist/data.d.ts.map +1 -1
  24. package/dist/data.js +16 -7
  25. package/dist/data.js.map +1 -1
  26. package/dist/effects/actions.d.ts +48 -0
  27. package/dist/effects/actions.d.ts.map +1 -0
  28. package/dist/effects/actions.js +80 -0
  29. package/dist/effects/actions.js.map +1 -0
  30. package/dist/effects/controls.d.ts +84 -0
  31. package/dist/effects/controls.d.ts.map +1 -0
  32. package/dist/effects/controls.js +121 -0
  33. package/dist/effects/controls.js.map +1 -0
  34. package/dist/effects/display.d.ts +92 -0
  35. package/dist/effects/display.d.ts.map +1 -0
  36. package/dist/effects/display.js +138 -0
  37. package/dist/effects/display.js.map +1 -0
  38. package/dist/effects/hooks.d.ts +13 -0
  39. package/dist/effects/hooks.d.ts.map +1 -0
  40. package/dist/effects/hooks.js +68 -0
  41. package/dist/effects/hooks.js.map +1 -0
  42. package/dist/effects/index.d.ts +14 -0
  43. package/dist/effects/index.d.ts.map +1 -0
  44. package/dist/effects/index.js +12 -0
  45. package/dist/effects/index.js.map +1 -0
  46. package/dist/effects/loaders.d.ts +67 -0
  47. package/dist/effects/loaders.d.ts.map +1 -0
  48. package/dist/effects/loaders.js +73 -0
  49. package/dist/effects/loaders.js.map +1 -0
  50. package/dist/effects/math.d.ts +23 -0
  51. package/dist/effects/math.d.ts.map +1 -0
  52. package/dist/effects/math.js +56 -0
  53. package/dist/effects/math.js.map +1 -0
  54. package/dist/effects/navigation.d.ts +55 -0
  55. package/dist/effects/navigation.d.ts.map +1 -0
  56. package/dist/effects/navigation.js +103 -0
  57. package/dist/effects/navigation.js.map +1 -0
  58. package/dist/effects/overlays.d.ts +34 -0
  59. package/dist/effects/overlays.d.ts.map +1 -0
  60. package/dist/effects/overlays.js +56 -0
  61. package/dist/effects/overlays.js.map +1 -0
  62. package/dist/effects.css +1659 -0
  63. package/dist/feedback.d.ts +6 -1
  64. package/dist/feedback.d.ts.map +1 -1
  65. package/dist/feedback.js +3 -3
  66. package/dist/feedback.js.map +1 -1
  67. package/dist/flow.d.ts +70 -0
  68. package/dist/flow.d.ts.map +1 -0
  69. package/dist/flow.js +118 -0
  70. package/dist/flow.js.map +1 -0
  71. package/dist/glass.d.ts +74 -0
  72. package/dist/glass.d.ts.map +1 -0
  73. package/dist/glass.js +139 -0
  74. package/dist/glass.js.map +1 -0
  75. package/dist/index.d.ts +14 -5
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +5 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/internal/expand.d.ts +17 -0
  80. package/dist/internal/expand.d.ts.map +1 -0
  81. package/dist/internal/expand.js +23 -0
  82. package/dist/internal/expand.js.map +1 -0
  83. package/dist/internal/interaction.d.ts +21 -0
  84. package/dist/internal/interaction.d.ts.map +1 -0
  85. package/dist/internal/interaction.js +129 -0
  86. package/dist/internal/interaction.js.map +1 -0
  87. package/dist/motion.d.ts +36 -0
  88. package/dist/motion.d.ts.map +1 -0
  89. package/dist/motion.js +80 -0
  90. package/dist/motion.js.map +1 -0
  91. package/dist/navigation.d.ts +43 -6
  92. package/dist/navigation.d.ts.map +1 -1
  93. package/dist/navigation.js +86 -19
  94. package/dist/navigation.js.map +1 -1
  95. package/dist/northstar/ExportMenu.d.ts +3 -1
  96. package/dist/northstar/ExportMenu.d.ts.map +1 -1
  97. package/dist/northstar/ExportMenu.js +2 -2
  98. package/dist/northstar/ExportMenu.js.map +1 -1
  99. package/dist/northstar/ResearchBuilder.d.ts.map +1 -1
  100. package/dist/northstar/ResearchBuilder.js +3 -3
  101. package/dist/northstar/ResearchBuilder.js.map +1 -1
  102. package/dist/northstar/Run.d.ts +3 -1
  103. package/dist/northstar/Run.d.ts.map +1 -1
  104. package/dist/northstar/Run.js +4 -3
  105. package/dist/northstar/Run.js.map +1 -1
  106. package/dist/northstar/StudyShell.d.ts +23 -1
  107. package/dist/northstar/StudyShell.d.ts.map +1 -1
  108. package/dist/northstar/StudyShell.js +6 -2
  109. package/dist/northstar/StudyShell.js.map +1 -1
  110. package/dist/northstar/StudyStatus.d.ts +3 -1
  111. package/dist/northstar/StudyStatus.d.ts.map +1 -1
  112. package/dist/northstar/StudyStatus.js +4 -2
  113. package/dist/northstar/StudyStatus.js.map +1 -1
  114. package/dist/overlays.d.ts +29 -4
  115. package/dist/overlays.d.ts.map +1 -1
  116. package/dist/overlays.js +38 -12
  117. package/dist/overlays.js.map +1 -1
  118. package/dist/provider.d.ts +31 -2
  119. package/dist/provider.d.ts.map +1 -1
  120. package/dist/provider.js +20 -6
  121. package/dist/provider.js.map +1 -1
  122. package/dist/styles.css +1069 -7
  123. package/dist/surface.d.ts +58 -0
  124. package/dist/surface.d.ts.map +1 -0
  125. package/dist/surface.js +39 -0
  126. package/dist/surface.js.map +1 -0
  127. package/package.json +18 -8
package/dist/styles.css CHANGED
@@ -111,7 +111,8 @@
111
111
  height: var(--ns-h, var(--ns-size-control-md));
112
112
  padding: 0 var(--ns-px, var(--ns-space-12));
113
113
  border: 1px solid var(--ns-color-line-2);
114
- border-radius: var(--ns-radius-6);
114
+ --ns-button-radius: var(--ns-radius-control-pill);
115
+ border-radius: var(--ns-button-radius);
115
116
  background: var(--ns-color-surface-2);
116
117
  color: var(--ns-color-text-1);
117
118
  font: inherit;
@@ -136,8 +137,8 @@
136
137
  color: var(--ns-button-primary-fg);
137
138
  }
138
139
  .ns-button--primary:hover:not(:disabled) {
139
- background: var(--ns-glacier-200);
140
- border-color: var(--ns-glacier-200);
140
+ background: var(--ns-button-primary-hover);
141
+ border-color: var(--ns-button-primary-hover);
141
142
  }
142
143
  .ns-button--ghost {
143
144
  background: transparent;
@@ -152,6 +153,9 @@
152
153
  .ns-button[data-loading] .ns-button__label { opacity: 0.7; }
153
154
  .ns-icon-button { width: var(--ns-h, var(--ns-size-control-md)); padding: 0; }
154
155
  .ns-icon-button svg { width: var(--ns-size-icon-sm); height: var(--ns-size-icon-sm); }
156
+ /* Icon-only: a perfect circle (pill radius on a square), the icon centred on both axes. */
157
+ .ns-icon-button:not(.ns-expand-host) { aspect-ratio: 1; flex: none; }
158
+ .ns-icon-button:not(.ns-expand-host) > .ns-button__label { display: inline-flex; align-items: center; justify-content: center; line-height: 0; }
155
159
 
156
160
  .ns-spinner {
157
161
  width: 12px;
@@ -167,8 +171,9 @@
167
171
  .ns-button-group--attached { gap: 0; }
168
172
  .ns-button-group--attached > .ns-button { border-radius: 0; }
169
173
  .ns-button-group--attached > .ns-button + .ns-button { margin-left: -1px; }
170
- .ns-button-group--attached > .ns-button:first-child { border-radius: var(--ns-radius-6) 0 0 var(--ns-radius-6); }
171
- .ns-button-group--attached > .ns-button:last-child { border-radius: 0 var(--ns-radius-6) var(--ns-radius-6) 0; }
174
+ .ns-button-group--attached > .ns-button:first-child { border-radius: var(--ns-button-radius) 0 0 var(--ns-button-radius); }
175
+ .ns-button-group--attached > .ns-button:last-child { border-radius: 0 var(--ns-button-radius) var(--ns-button-radius) 0; }
176
+ .ns-button-group--attached > .ns-button:only-child { border-radius: var(--ns-button-radius); }
172
177
  .ns-button-group--attached > .ns-button:focus-visible { position: relative; z-index: 1; }
173
178
 
174
179
  .ns-link {
@@ -657,7 +662,7 @@
657
662
 
658
663
  .ns-pagination { display: flex; align-items: center; gap: var(--ns-space-4); }
659
664
  .ns-pagination ul { display: flex; align-items: center; gap: var(--ns-space-2); margin: 0; padding: 0; list-style: none; }
660
- .ns-pagination__page { min-width: var(--ns-size-control-sm); padding: 0 var(--ns-space-6); font-family: var(--ns-font-family-mono); font-variant-numeric: var(--ns-font-numeric-figures); }
665
+ .ns-pagination__page { min-width: var(--ns-h, var(--ns-size-control-sm)); padding: 0 var(--ns-space-6); font-family: var(--ns-font-family-mono); font-variant-numeric: var(--ns-font-numeric-figures); }
661
666
  .ns-pagination__gap { color: var(--ns-color-text-3); padding: 0 var(--ns-space-4); }
662
667
 
663
668
  /* -------------------------------------------------------- data display */
@@ -791,6 +796,394 @@
791
796
  }
792
797
  .ns-stale__detail { color: var(--ns-color-text-3); font-weight: var(--ns-font-weight-regular); }
793
798
 
799
+ /* ================================================ interaction layer
800
+ Material (glass), pointer light, springs. Pointer effects have a keyboard
801
+ twin (:focus-visible / :has(:focus-visible)); every movement stops under
802
+ reduced motion, glass turns solid under reduced transparency, without
803
+ backdrop-filter, and in forced colours. */
804
+
805
+ @property --ns-orbit { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
806
+
807
+ /* ---------------------------------------------------- surface, card, panel */
808
+
809
+ .ns-surface,
810
+ .ns-card,
811
+ .ns-panel {
812
+ position: relative;
813
+ isolation: isolate;
814
+ border: 1px solid var(--ns-color-line-1);
815
+ color: var(--ns-color-text-1);
816
+ }
817
+ .ns-surface.ns-material--solid { background: var(--ns-color-surface-1); }
818
+ .ns-radius-6 { border-radius: var(--ns-radius-6); }
819
+ .ns-radius-8 { border-radius: var(--ns-radius-8); }
820
+ .ns-radius-10 { border-radius: var(--ns-radius-glass-panel); }
821
+ .ns-radius-12 { border-radius: var(--ns-radius-12); }
822
+ .ns-surface.ns-material--solid.ns-depth-1 { box-shadow: var(--ns-elevation-1); }
823
+ .ns-surface.ns-material--solid.ns-depth-2 { box-shadow: var(--ns-elevation-2); }
824
+ .ns-surface.ns-material--solid.ns-depth-3 { box-shadow: var(--ns-elevation-3); background: var(--ns-color-surface-2); }
825
+
826
+ .ns-card {
827
+ display: flex;
828
+ flex-direction: column;
829
+ min-width: 0;
830
+ border-radius: var(--ns-radius-12);
831
+ background: var(--ns-color-surface-1);
832
+ box-shadow: var(--ns-elevation-1);
833
+ transition: transform var(--ns-motion-duration-release) var(--ns-motion-spring-release),
834
+ box-shadow var(--ns-motion-duration-200) var(--ns-motion-easing-settle),
835
+ border-color var(--ns-motion-duration-200) var(--ns-motion-easing-settle);
836
+ }
837
+ .ns-card__header { display: flex; align-items: flex-start; gap: var(--ns-space-12); padding: var(--ns-space-16) var(--ns-space-16) 0; }
838
+ .ns-card__heading { flex: 1; min-width: 0; }
839
+ .ns-card__title { margin: 0; font-size: var(--ns-font-size-15); line-height: var(--ns-font-line-height-15); font-weight: var(--ns-font-weight-semibold); }
840
+ .ns-card__description { margin: var(--ns-space-4) 0 0; color: var(--ns-color-text-2); }
841
+ .ns-card__actions { display: flex; gap: var(--ns-space-6); }
842
+ .ns-card__body { flex: 1; padding: var(--ns-space-16); }
843
+ .ns-card__header + .ns-card__body { padding-top: var(--ns-space-12); }
844
+ .ns-card__footer { display: flex; justify-content: flex-end; gap: var(--ns-space-8); padding: var(--ns-space-12) var(--ns-space-16); border-top: 1px solid var(--ns-color-line-1); }
845
+ .ns-card--interactive { cursor: pointer; }
846
+ .ns-card--interactive:hover,
847
+ .ns-card--interactive:has(:focus-visible) {
848
+ transform: translateY(var(--ns-material-lift));
849
+ border-color: var(--ns-color-line-2);
850
+ box-shadow: var(--ns-elevation-2), 0 12px 32px -18px var(--ns-material-glow);
851
+ }
852
+ .ns-card--interactive:active {
853
+ transform: translateY(0) scale(0.995);
854
+ transition-duration: var(--ns-motion-duration-press);
855
+ transition-timing-function: var(--ns-motion-spring-press);
856
+ }
857
+
858
+ .ns-panel { border-radius: var(--ns-radius-8); background: var(--ns-color-surface-1); overflow: hidden; }
859
+ .ns-panel__head { display: flex; align-items: center; gap: var(--ns-space-8); min-height: 40px; padding: 0 var(--ns-space-12); border-bottom: 1px solid var(--ns-color-line-1); }
860
+ .ns-panel__title { margin: 0; font-size: var(--ns-font-size-13); line-height: var(--ns-font-line-height-13); font-weight: var(--ns-font-weight-medium); }
861
+ .ns-panel__actions { margin-left: auto; display: flex; gap: var(--ns-space-6); }
862
+ .ns-panel__body { padding: var(--ns-space-12); }
863
+ .ns-panel__body--flush { padding: 0; }
864
+
865
+ /* -------------------------------------------------------------- glass */
866
+
867
+ .ns-material--glass,
868
+ .ns-button--glass.ns-button--secondary,
869
+ .ns-button--glass.ns-button--ghost:hover:not(:disabled),
870
+ .ns-tabs--glass .ns-tabs__list {
871
+ background: var(--ns-material-glass-specular),
872
+ linear-gradient(var(--ns-material-glass-tint), var(--ns-material-glass-tint)),
873
+ var(--ns-material-glass-fill);
874
+ border-color: var(--ns-material-glass-edge);
875
+ -webkit-backdrop-filter: blur(var(--ns-material-blur-md)) saturate(var(--ns-material-saturate));
876
+ backdrop-filter: blur(var(--ns-material-blur-md)) saturate(var(--ns-material-saturate));
877
+ box-shadow: inset 0 1px 0 var(--ns-material-glass-rim), var(--ns-material-shadow);
878
+ }
879
+ .ns-button--glass.ns-button--secondary,
880
+ .ns-button--glass.ns-button--ghost:hover:not(:disabled),
881
+ .ns-tabs--glass .ns-tabs__list {
882
+ -webkit-backdrop-filter: blur(var(--ns-material-blur-sm)) saturate(var(--ns-material-saturate));
883
+ backdrop-filter: blur(var(--ns-material-blur-sm)) saturate(var(--ns-material-saturate));
884
+ box-shadow: inset 0 1px 0 var(--ns-material-glass-rim);
885
+ }
886
+ .ns-depth-1.ns-material--glass {
887
+ -webkit-backdrop-filter: blur(var(--ns-material-blur-sm)) saturate(var(--ns-material-saturate));
888
+ backdrop-filter: blur(var(--ns-material-blur-sm)) saturate(var(--ns-material-saturate));
889
+ box-shadow: inset 0 1px 0 var(--ns-material-glass-rim);
890
+ }
891
+ .ns-depth-3.ns-material--glass,
892
+ .ns-dialog.ns-material--glass,
893
+ .ns-sheet.ns-material--glass {
894
+ -webkit-backdrop-filter: blur(var(--ns-material-blur-lg)) saturate(var(--ns-material-saturate));
895
+ backdrop-filter: blur(var(--ns-material-blur-lg)) saturate(var(--ns-material-saturate));
896
+ }
897
+ .ns-material--glass .ns-panel__head,
898
+ .ns-material--glass .ns-card__footer,
899
+ .ns-material--glass .ns-layer__footer,
900
+ .ns-material--glass .ns-menu__separator { border-color: var(--ns-material-glass-edge); }
901
+ .ns-material--glass .ns-menu__separator { background: var(--ns-material-glass-edge); }
902
+ .ns-material--glass .ns-menu__item:focus,
903
+ .ns-material--glass .ns-menu__item:hover { background: var(--ns-material-spotlight); }
904
+ .ns-material--glass .ns-menu__item[aria-disabled='true'] { background: transparent; }
905
+ .ns-overlay:has(+ .ns-material--glass) {
906
+ opacity: 1;
907
+ background: color-mix(in srgb, var(--ns-color-ground) 48%, transparent);
908
+ -webkit-backdrop-filter: blur(var(--ns-material-blur-sm));
909
+ backdrop-filter: blur(var(--ns-material-blur-sm));
910
+ }
911
+
912
+ /* ----------------------------------------------------- pointer light */
913
+
914
+ .ns-reactive::before,
915
+ .ns-button--glass::before {
916
+ content: '';
917
+ position: absolute;
918
+ inset: 0;
919
+ z-index: -1;
920
+ border-radius: inherit;
921
+ pointer-events: none;
922
+ background: radial-gradient(360px circle at var(--ns-mx, 50%) var(--ns-my, 0%), var(--ns-material-spotlight), transparent 62%);
923
+ opacity: 0;
924
+ transition: opacity var(--ns-motion-duration-200) var(--ns-motion-easing-settle);
925
+ }
926
+ .ns-button--glass::before { background: radial-gradient(80px circle at var(--ns-mx, 50%) var(--ns-my, 0%), var(--ns-material-spotlight), transparent 70%); }
927
+ .ns-reactive[data-pointer]::before,
928
+ .ns-reactive:has(:focus-visible)::before,
929
+ .ns-reactive:focus-visible::before,
930
+ .ns-button--glass[data-pointer]::before,
931
+ .ns-button--glass:focus-visible::before { opacity: 1; }
932
+
933
+ /* Spotlight: the border lights up where the pointer is (keyboard: along the top). */
934
+ .ns-card--spotlight::after,
935
+ .ns-card--orbit::after {
936
+ content: '';
937
+ position: absolute;
938
+ inset: -1px;
939
+ border-radius: inherit;
940
+ padding: 1px;
941
+ pointer-events: none;
942
+ background: radial-gradient(200px circle at var(--ns-mx, 50%) var(--ns-my, 0%), var(--ns-color-signal), transparent 70%);
943
+ -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
944
+ -webkit-mask-composite: xor;
945
+ mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
946
+ opacity: 0;
947
+ transition: opacity var(--ns-motion-duration-200) var(--ns-motion-easing-settle);
948
+ }
949
+ .ns-card--spotlight[data-pointer]::after,
950
+ .ns-card--spotlight:has(:focus-visible)::after,
951
+ .ns-card--spotlight:focus-visible::after { opacity: 1; }
952
+
953
+ /* Orbit: a short arc of light travels the border. */
954
+ .ns-card--orbit::after {
955
+ background: conic-gradient(from var(--ns-orbit), transparent 0 70%, var(--ns-color-signal) 86%, transparent 96%);
956
+ opacity: 0.9;
957
+ animation: ns-orbit 7s linear infinite;
958
+ }
959
+ @keyframes ns-orbit { to { --ns-orbit: 360deg; } }
960
+
961
+ /* ------------------------------------------------------------ buttons */
962
+
963
+ .ns-button {
964
+ position: relative;
965
+ isolation: isolate;
966
+ transform: translate(var(--ns-mag-x, 0px), var(--ns-mag-y, 0px));
967
+ transition: background-color var(--ns-motion-duration-120) var(--ns-motion-easing-settle),
968
+ border-color var(--ns-motion-duration-120) var(--ns-motion-easing-settle),
969
+ color var(--ns-motion-duration-120) var(--ns-motion-easing-settle),
970
+ box-shadow var(--ns-motion-duration-200) var(--ns-motion-easing-settle),
971
+ transform var(--ns-motion-duration-release) var(--ns-motion-spring-release);
972
+ }
973
+ .ns-button:active:not(:disabled) {
974
+ transform: translate(var(--ns-mag-x, 0px), calc(var(--ns-mag-y, 0px) + 0.5px)) scale(var(--ns-material-press));
975
+ transition-duration: var(--ns-motion-duration-press);
976
+ transition-timing-function: var(--ns-motion-spring-press);
977
+ }
978
+ .ns-button--primary { box-shadow: inset 0 1px 0 var(--ns-material-glass-rim); }
979
+ .ns-button--primary:hover:not(:disabled) { box-shadow: inset 0 1px 0 var(--ns-material-glass-rim), 0 6px 20px -8px var(--ns-material-glow); }
980
+ .ns-button--primary:active:not(:disabled) { box-shadow: inset 0 1px 2px var(--ns-color-signal), 0 0 0 3px var(--ns-material-glow); }
981
+ .ns-button--secondary:active:not(:disabled) { box-shadow: inset 0 1px 0 var(--ns-color-line-1); }
982
+ .ns-button--glass.ns-button--secondary:hover:not(:disabled) { border-color: var(--ns-color-line-2); }
983
+
984
+ /* Focus: the ring snaps in from a little way out, with a glacier glow behind it. */
985
+ .ns-button:focus-visible,
986
+ .ns-switch:focus-visible,
987
+ .ns-tabs__trigger:focus-visible,
988
+ .ns-link:focus-visible {
989
+ box-shadow: 0 0 0 4px var(--ns-color-focus-halo), 0 0 18px 2px var(--ns-material-glow);
990
+ animation: ns-ring-in var(--ns-motion-duration-200) var(--ns-motion-spring-settle);
991
+ }
992
+ @keyframes ns-ring-in { from { outline-offset: 5px; outline-color: transparent; } }
993
+
994
+ /* The north star holds still and breathes while its orbit turns round it. */
995
+ .ns-spinner--star { width: 14px; height: 14px; border: 0; border-radius: 0; animation: none; overflow: visible; }
996
+ .ns-spinner__star { transform-box: view-box; transform-origin: 50% 50%; animation: ns-star-breathe 1200ms var(--ns-motion-easing-travel) infinite; }
997
+ .ns-spinner__orbit { opacity: 0.45; stroke-dasharray: 10 31; stroke-linecap: round; transform-box: view-box; transform-origin: 50% 50%; animation: ns-spin 1100ms linear infinite; }
998
+ @keyframes ns-star-breathe { 50% { transform: scale(0.72); opacity: 0.7; } }
999
+ .ns-button[data-success] { border-color: var(--ns-color-signal-edge); }
1000
+ .ns-button--primary[data-success] { border-color: var(--ns-button-primary-bg); }
1001
+ .ns-button__check { flex: none; animation: ns-check-pop var(--ns-motion-duration-release) var(--ns-motion-spring-release); }
1002
+ .ns-button__check path { stroke-dasharray: 1; stroke-dashoffset: 1; animation: ns-draw var(--ns-motion-duration-320) var(--ns-motion-easing-settle) 60ms forwards; }
1003
+ .ns-button:not(.ns-button--primary) .ns-button__check { color: var(--ns-color-signal); }
1004
+ @keyframes ns-draw { to { stroke-dashoffset: 0; } }
1005
+ @keyframes ns-check-pop { from { transform: scale(0.4) rotate(-20deg); opacity: 0; } }
1006
+
1007
+ /* -------------------------------------------------------------- links */
1008
+
1009
+ .ns-link {
1010
+ text-decoration: none;
1011
+ background-image: linear-gradient(currentColor, currentColor), linear-gradient(var(--ns-color-signal-edge), var(--ns-color-signal-edge));
1012
+ background-size: 0% 1px, 100% 1px;
1013
+ background-position: 0 100%, 0 100%;
1014
+ background-repeat: no-repeat;
1015
+ padding-bottom: 1px;
1016
+ transition: background-size var(--ns-motion-duration-320) var(--ns-motion-easing-settle), color var(--ns-motion-duration-120) var(--ns-motion-easing-settle);
1017
+ }
1018
+ .ns-link--neutral { background-image: linear-gradient(currentColor, currentColor), linear-gradient(var(--ns-color-line-2), var(--ns-color-line-2)); }
1019
+ .ns-link:hover,
1020
+ .ns-link:focus-visible { background-size: 100% 1px, 100% 1px; }
1021
+
1022
+ /* -------------------------------------------------------------- forms */
1023
+
1024
+ .ns-input,
1025
+ .ns-select,
1026
+ .ns-textarea {
1027
+ transition: border-color var(--ns-motion-duration-120) var(--ns-motion-easing-settle),
1028
+ box-shadow var(--ns-motion-duration-200) var(--ns-motion-easing-settle);
1029
+ }
1030
+ .ns-input:focus-within,
1031
+ .ns-select:focus-within,
1032
+ .ns-textarea:focus-visible {
1033
+ border-color: var(--ns-color-signal-edge);
1034
+ box-shadow: 0 0 0 4px var(--ns-color-focus-halo), 0 0 22px -4px var(--ns-material-glow);
1035
+ }
1036
+
1037
+ .ns-switch { transition: background-color var(--ns-motion-duration-200) var(--ns-motion-easing-settle), border-color var(--ns-motion-duration-200) var(--ns-motion-easing-settle), box-shadow var(--ns-motion-duration-200) var(--ns-motion-easing-settle); }
1038
+ .ns-switch[aria-checked='true'] { box-shadow: 0 0 12px -3px var(--ns-material-glow); }
1039
+ .ns-switch__thumb {
1040
+ transition: transform var(--ns-motion-duration-release) var(--ns-motion-spring-release),
1041
+ width var(--ns-motion-duration-press) var(--ns-motion-spring-press),
1042
+ background-color var(--ns-motion-duration-120) var(--ns-motion-easing-settle);
1043
+ }
1044
+ .ns-switch:active:not(:disabled) .ns-switch__thumb { width: 15px; }
1045
+ .ns-switch[aria-checked='true']:active:not(:disabled) .ns-switch__thumb { transform: translateX(9px); }
1046
+
1047
+ /* --------------------------------------------------------------- tabs */
1048
+
1049
+ .ns-tabs__list { position: relative; }
1050
+ .ns-tabs__trigger { z-index: 1; transition: color var(--ns-motion-duration-120) var(--ns-motion-easing-settle); }
1051
+ .ns-tabs__indicator { position: absolute; top: 0; left: 0; width: 0; height: 0; z-index: 0; pointer-events: none; opacity: 0; }
1052
+ .ns-tabs__list[data-indicator] .ns-tabs__indicator { opacity: 1; }
1053
+ .ns-tabs__list[data-indicator='ready'] .ns-tabs__indicator {
1054
+ transition: transform var(--ns-motion-duration-settle) var(--ns-motion-spring-settle),
1055
+ width var(--ns-motion-duration-settle) var(--ns-motion-spring-settle),
1056
+ height var(--ns-motion-duration-settle) var(--ns-motion-spring-settle);
1057
+ }
1058
+ .ns-tabs__list[data-indicator] .ns-tabs__trigger[aria-selected='true']::after { display: none; }
1059
+ .ns-tabs__indicator::after { content: ''; position: absolute; left: var(--ns-space-8); right: var(--ns-space-8); bottom: -1px; height: 2px; border-radius: 1px; background: var(--ns-color-signal); }
1060
+ .ns-tabs--vertical .ns-tabs__indicator { border-radius: var(--ns-radius-4); background: var(--ns-color-surface-2); }
1061
+ .ns-tabs--vertical .ns-tabs__indicator::after { left: 0; right: auto; top: var(--ns-space-6); bottom: var(--ns-space-6); width: 2px; height: auto; }
1062
+ .ns-tabs--vertical .ns-tabs__list[data-indicator] .ns-tabs__trigger[aria-selected='true'] { background: transparent; }
1063
+
1064
+ .ns-tabs--pill .ns-tabs__list { display: inline-flex; gap: var(--ns-space-2); padding: 3px; border: 1px solid var(--ns-color-line-1); border-radius: var(--ns-radius-control-pill); background: var(--ns-color-surface-1); }
1065
+ .ns-tabs--pill.ns-tabs--vertical .ns-tabs__list { padding-right: 3px; border-right: 1px solid var(--ns-color-line-1); }
1066
+ .ns-tabs--pill .ns-tabs__trigger { height: calc(var(--ns-control-h, var(--ns-size-control-md)) - 6px); padding: 0 var(--ns-space-12); border-radius: var(--ns-radius-control-pill); }
1067
+ .ns-tabs--pill .ns-tabs__trigger[aria-selected='true']::after,
1068
+ .ns-tabs--pill .ns-tabs__indicator::after { display: none; }
1069
+ .ns-tabs--pill .ns-tabs__list:not([data-indicator]) .ns-tabs__trigger[aria-selected='true'],
1070
+ .ns-tabs--pill .ns-tabs__indicator { background: var(--ns-color-surface-3); box-shadow: inset 0 0 0 1px var(--ns-color-line-2); border-radius: var(--ns-radius-control-pill); }
1071
+ .ns-tabs--glass .ns-tabs__indicator {
1072
+ background: var(--ns-material-glass-specular), linear-gradient(var(--ns-material-spotlight), var(--ns-material-spotlight)), var(--ns-material-glass-fill);
1073
+ box-shadow: inset 0 1px 0 var(--ns-material-glass-rim), inset 0 0 0 1px var(--ns-material-glass-edge), 0 4px 14px -6px var(--ns-material-glow);
1074
+ }
1075
+
1076
+ /* ---------------------------------------------------- layers: springs */
1077
+
1078
+ .ns-pop[data-state] { --ns-pop-x: 0px; --ns-pop-y: -4px; transform-origin: top center; }
1079
+ .ns-pop[data-side='top'],
1080
+ .ns-tooltip[data-side='top'] { --ns-pop-y: 4px; transform-origin: bottom center; }
1081
+ .ns-pop[data-side='left'],
1082
+ .ns-tooltip[data-side='left'] { --ns-pop-x: 4px; --ns-pop-y: 0px; transform-origin: center right; }
1083
+ .ns-pop[data-side='right'],
1084
+ .ns-tooltip[data-side='right'] { --ns-pop-x: -4px; --ns-pop-y: 0px; transform-origin: center left; }
1085
+ .ns-tooltip[data-side='bottom'] { --ns-pop-y: -4px; }
1086
+ .ns-pop[data-state='open'],
1087
+ .ns-tooltip[data-state='open'] { animation: ns-layer-in var(--ns-motion-duration-release) var(--ns-motion-spring-settle); }
1088
+ .ns-pop[data-state='closed'],
1089
+ .ns-tooltip[data-state='closed'] { animation: ns-layer-out var(--ns-motion-duration-120) var(--ns-motion-easing-exit) forwards; pointer-events: none; }
1090
+ @keyframes ns-layer-in { from { opacity: 0; transform: translate(var(--ns-pop-x, 0px), var(--ns-pop-y, 4px)) scale(0.96); } }
1091
+ @keyframes ns-layer-out { to { opacity: 0; transform: translate(var(--ns-pop-x, 0px), var(--ns-pop-y, 4px)) scale(0.97); } }
1092
+
1093
+ .ns-overlay[data-state='closed'] { animation: ns-fade-out var(--ns-motion-duration-200) var(--ns-motion-easing-exit) forwards; }
1094
+ @keyframes ns-fade-out { to { opacity: 0; } }
1095
+ .ns-dialog[data-state='open'] { animation: ns-dialog-in var(--ns-motion-duration-settle) var(--ns-motion-spring-settle); }
1096
+ .ns-dialog[data-state='closed'] { animation: ns-dialog-out var(--ns-motion-duration-200) var(--ns-motion-easing-exit) forwards; pointer-events: none; }
1097
+ @keyframes ns-dialog-in { from { opacity: 0; transform: translate(-50%, calc(-50% + 12px)) scale(0.96); } }
1098
+ @keyframes ns-dialog-out { to { opacity: 0; transform: translate(-50%, calc(-50% + 6px)) scale(0.98); } }
1099
+ .ns-sheet[data-state='open'] { animation: ns-sheet-in var(--ns-motion-duration-320) var(--ns-motion-easing-enter); }
1100
+ .ns-sheet[data-state='closed'] { animation: ns-sheet-in var(--ns-motion-duration-200) var(--ns-motion-easing-exit) reverse forwards; pointer-events: none; }
1101
+ .ns-sheet--right { --ns-sheet-from: translateX(32px); }
1102
+ .ns-sheet--left { --ns-sheet-from: translateX(-32px); }
1103
+ .ns-sheet--bottom { --ns-sheet-from: translateY(32px); }
1104
+ @keyframes ns-sheet-in { from { opacity: 0; transform: var(--ns-sheet-from); } }
1105
+
1106
+ .ns-toaster__item { animation: ns-toast-in var(--ns-motion-duration-release) var(--ns-motion-spring-release); }
1107
+ @keyframes ns-toast-in { from { opacity: 0; transform: translateY(12px) scale(0.96); } }
1108
+ .ns-toast { transition: transform var(--ns-motion-duration-release) var(--ns-motion-spring-release), border-color var(--ns-motion-duration-200) var(--ns-motion-easing-settle); }
1109
+ .ns-toaster__item:hover .ns-toast,
1110
+ .ns-toaster__item:focus-within .ns-toast { transform: translateY(-1px); border-color: var(--ns-color-line-2); }
1111
+ .ns-toast.ns-material--glass.ns-tone-signal { border-left-color: var(--ns-color-signal); }
1112
+
1113
+ .ns-menu__item,
1114
+ .ns-option { transition: background-color var(--ns-motion-duration-120) var(--ns-motion-easing-settle); }
1115
+
1116
+ /* --------------------------------------------------------- data, tags */
1117
+
1118
+ .ns-table--interactive tbody .ns-table__row {
1119
+ transition: transform var(--ns-motion-duration-release) var(--ns-motion-spring-release),
1120
+ box-shadow var(--ns-motion-duration-200) var(--ns-motion-easing-settle),
1121
+ background-color var(--ns-motion-duration-120) var(--ns-motion-easing-settle);
1122
+ }
1123
+ .ns-table--interactive tbody .ns-table__row:hover,
1124
+ .ns-table--interactive tbody .ns-table__row:has(:focus-visible) {
1125
+ position: relative;
1126
+ z-index: 1;
1127
+ background: var(--ns-color-surface-2);
1128
+ transform: translateY(-1px);
1129
+ box-shadow: 0 1px 0 var(--ns-color-line-2), 0 8px 20px -14px var(--ns-material-glow);
1130
+ }
1131
+ .ns-table--interactive tbody .ns-table__row:hover > :first-child,
1132
+ .ns-table--interactive tbody .ns-table__row:has(:focus-visible) > :first-child { box-shadow: inset 2px 0 0 var(--ns-color-signal); }
1133
+
1134
+ .ns-tag { transition: border-color var(--ns-motion-duration-120) var(--ns-motion-easing-settle), box-shadow var(--ns-motion-duration-200) var(--ns-motion-easing-settle); }
1135
+ .ns-tag:has(.ns-tag__remove):hover { border-color: var(--ns-color-text-3); }
1136
+ .ns-tag.ns-tone-signal:has(.ns-tag__remove):hover { border-color: var(--ns-color-signal); box-shadow: 0 0 10px -3px var(--ns-material-glow); }
1137
+ .ns-tag__remove { transition: transform var(--ns-motion-duration-release) var(--ns-motion-spring-release); }
1138
+ .ns-tag__remove:active { transform: scale(0.82); transition-duration: var(--ns-motion-duration-press); }
1139
+
1140
+ /* ------------------------------------------------------------ fallbacks */
1141
+
1142
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
1143
+ .ns-material--glass,
1144
+ .ns-button--glass.ns-button--secondary,
1145
+ .ns-button--glass.ns-button--ghost:hover:not(:disabled),
1146
+ .ns-tabs--glass .ns-tabs__list,
1147
+ .ns-tabs--glass .ns-tabs__indicator { background: var(--ns-material-solid); }
1148
+ }
1149
+ @media (prefers-reduced-transparency: reduce) {
1150
+ .ns-material--glass,
1151
+ .ns-button--glass.ns-button--secondary,
1152
+ .ns-button--glass.ns-button--ghost:hover:not(:disabled),
1153
+ .ns-tabs--glass .ns-tabs__list,
1154
+ .ns-tabs--glass .ns-tabs__indicator,
1155
+ .ns-overlay:has(+ .ns-material--glass) {
1156
+ background: var(--ns-material-solid);
1157
+ -webkit-backdrop-filter: none;
1158
+ backdrop-filter: none;
1159
+ }
1160
+ .ns-overlay:has(+ .ns-material--glass) { background: var(--ns-color-ground); opacity: 0.72; }
1161
+ }
1162
+ @media (forced-colors: active) {
1163
+ .ns-material--glass,
1164
+ .ns-button--glass,
1165
+ .ns-tabs--glass .ns-tabs__list,
1166
+ .ns-card,
1167
+ .ns-panel,
1168
+ .ns-surface {
1169
+ background: Canvas;
1170
+ -webkit-backdrop-filter: none;
1171
+ backdrop-filter: none;
1172
+ border-color: CanvasText;
1173
+ box-shadow: none;
1174
+ }
1175
+ .ns-reactive::before,
1176
+ .ns-button--glass::before,
1177
+ .ns-card--spotlight::after,
1178
+ .ns-card--orbit::after { display: none; }
1179
+ .ns-tabs__indicator::after { background: Highlight; }
1180
+ .ns-tabs--pill .ns-tabs__indicator { background: Highlight; box-shadow: none; }
1181
+ .ns-tabs--pill .ns-tabs__trigger[aria-selected='true'] { color: HighlightText; }
1182
+ .ns-link { text-decoration: underline; background-image: none; }
1183
+ .ns-table--interactive tbody .ns-table__row:hover > :first-child,
1184
+ .ns-table--interactive tbody .ns-table__row:has(:focus-visible) > :first-child { box-shadow: none; outline: 2px solid Highlight; outline-offset: -2px; }
1185
+ }
1186
+
794
1187
  /* ------------------------------------------------------ reduced motion */
795
1188
 
796
1189
  @media (prefers-reduced-motion: reduce) {
@@ -803,6 +1196,18 @@
803
1196
  }
804
1197
  .ns-skeleton { animation: none !important; }
805
1198
  .ns-spinner { animation: none !important; border-right-color: currentColor; border-style: dotted; }
1199
+ /* No movement: presses, lifts, magnets, the orbit and the turning star all
1200
+ hold still. State changes (selected tab, switch, success) still show. */
1201
+ .ns-button,
1202
+ .ns-button:active:not(:disabled),
1203
+ .ns-card--interactive,
1204
+ .ns-toast,
1205
+ .ns-tag__remove,
1206
+ .ns-table--interactive tbody .ns-table__row { transform: none !important; }
1207
+ .ns-spinner__star,
1208
+ .ns-spinner__orbit,
1209
+ .ns-card--orbit::after { animation: none !important; }
1210
+ .ns-tabs__indicator { transition: none !important; }
806
1211
  }
807
1212
 
808
1213
  /* ============================================== northstar: study workspace */
@@ -889,7 +1294,7 @@
889
1294
  .ns-study-shell__tabs { display: flex; flex-direction: column; flex: 1; }
890
1295
  .ns-study-shell__tabbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ns-space-8); min-height: 40px; padding: 0 var(--ns-space-12); border-bottom: 1px solid var(--ns-color-line-1); }
891
1296
  .ns-study-shell__tablist.ns-tabs__list { border-bottom: 0; gap: var(--ns-space-2); }
892
- .ns-study-shell__tab.ns-tabs__trigger { display: inline-flex; align-items: center; gap: var(--ns-space-6); height: 26px; padding: 0 var(--ns-space-12); border-radius: var(--ns-radius-6); }
1297
+ .ns-study-shell__tab.ns-tabs__trigger { display: inline-flex; align-items: center; gap: var(--ns-space-6); height: 26px; padding: 0 var(--ns-space-12); border-radius: var(--ns-radius-control-pill); }
893
1298
  .ns-study-shell__tab.ns-tabs__trigger:hover { background: var(--ns-color-surface-2); }
894
1299
  .ns-study-shell__tab.ns-tabs__trigger[aria-selected='true'] { background: var(--ns-color-surface-3); box-shadow: inset 0 0 0 1px var(--ns-color-line-2); }
895
1300
  .ns-study-shell__tab.ns-tabs__trigger[aria-selected='true']::after { display: none; }
@@ -969,3 +1374,660 @@
969
1374
  .ns-study-status[data-motion] .ns-study-status__mark,
970
1375
  .ns-illustration__orbit { animation: none !important; }
971
1376
  }
1377
+
1378
+ /* ===================================================== liquid layer
1379
+ Liquid glass, borderless surfaces, icon-led controls, flows and long,
1380
+ soft motion. Loaded last so it refines everything above. */
1381
+
1382
+ /* ------------------------------------------------- borderless surfaces */
1383
+
1384
+ .ns-surface,
1385
+ .ns-card,
1386
+ .ns-panel { border-color: transparent; }
1387
+ .ns-card.ns-material--solid,
1388
+ .ns-panel.ns-material--solid,
1389
+ .ns-surface.ns-material--solid { box-shadow: inset 0 0 0 1px var(--ns-material-hairline), var(--ns-elevation-1); }
1390
+ .ns-surface.ns-material--solid.ns-depth-3 { box-shadow: inset 0 0 0 1px var(--ns-material-hairline), var(--ns-elevation-3); }
1391
+ .ns-panel__head { border-bottom-color: var(--ns-material-hairline); }
1392
+ .ns-card__footer { border-top: 0; padding-top: 0; }
1393
+ .ns-card {
1394
+ transition: transform var(--ns-motion-duration-layout) var(--ns-motion-easing-glide),
1395
+ box-shadow var(--ns-motion-duration-layout) var(--ns-motion-easing-glide),
1396
+ background-color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover);
1397
+ }
1398
+ .ns-card--interactive:hover,
1399
+ .ns-card--interactive:has(:focus-visible) {
1400
+ border-color: transparent;
1401
+ box-shadow: inset 0 0 0 1px var(--ns-material-hairline), var(--ns-elevation-2), 0 18px 40px -22px var(--ns-material-glow);
1402
+ }
1403
+ .ns-table-wrap { border-color: transparent; box-shadow: inset 0 0 0 1px var(--ns-material-hairline); }
1404
+ .ns-sidebar { border-right: 0; }
1405
+ .ns-sidebar__footer { border-top-color: var(--ns-material-hairline); }
1406
+ .ns-top-bar { border-bottom-color: var(--ns-material-hairline); }
1407
+ .ns-tabs__list { border-bottom-color: var(--ns-material-hairline); }
1408
+ .ns-study-panel { border-color: transparent; box-shadow: inset 0 0 0 1px var(--ns-material-hairline); }
1409
+ .ns-study-shell__bar,
1410
+ .ns-study-shell__tabbar { border-bottom-color: var(--ns-material-hairline); }
1411
+ .ns-study-shell__aside { border-left-color: var(--ns-material-hairline); }
1412
+
1413
+ /* Buttons: tonal, not outlined. The secondary button is a quiet fill. */
1414
+ .ns-button--secondary,
1415
+ .ns-button--secondary:disabled { border-color: transparent; }
1416
+ .ns-button:active:not(:disabled):not(.ns-button--primary) { border-color: transparent; }
1417
+ .ns-button-group--attached { gap: 1px; }
1418
+ .ns-button-group--attached > .ns-button + .ns-button { margin-left: 0; }
1419
+
1420
+ /* Hover in and out on the same soft curve; nothing snaps. */
1421
+ .ns-button {
1422
+ transition: background-color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1423
+ border-color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1424
+ color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1425
+ box-shadow var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1426
+ padding var(--ns-motion-duration-layout) var(--ns-motion-spring-layout),
1427
+ transform var(--ns-motion-duration-release) var(--ns-motion-spring-release);
1428
+ }
1429
+ .ns-sidebar__item,
1430
+ .ns-tabs__trigger,
1431
+ .ns-menu__item,
1432
+ .ns-option,
1433
+ .ns-breadcrumbs a,
1434
+ .ns-tag {
1435
+ transition: background-color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1436
+ color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1437
+ box-shadow var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1438
+ border-color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover);
1439
+ }
1440
+ .ns-tabs__list[data-indicator='ready'] .ns-tabs__indicator {
1441
+ transition: transform var(--ns-motion-duration-layout) var(--ns-motion-spring-layout),
1442
+ width var(--ns-motion-duration-layout) var(--ns-motion-spring-layout),
1443
+ height var(--ns-motion-duration-layout) var(--ns-motion-spring-layout);
1444
+ }
1445
+
1446
+ /* ----------------------------------------------------------- liquid glass */
1447
+
1448
+ .ns-liquid,
1449
+ .ns-material--glass,
1450
+ .ns-button--glass.ns-button--secondary,
1451
+ .ns-button--glass.ns-button--ghost:hover:not(:disabled),
1452
+ .ns-tabs--glass .ns-tabs__list {
1453
+ --ns-liquid-ring: inset 0 1px 0 0 var(--ns-material-liquid-rim), inset 0 -1px 0 0 var(--ns-material-liquid-rim-low),
1454
+ inset 0 0 0 0.5px var(--ns-material-liquid-rim-low), inset 0 0 16px 0 var(--ns-material-liquid-inner);
1455
+ background: var(--ns-material-liquid-highlight),
1456
+ linear-gradient(var(--ns-material-liquid-tint), var(--ns-material-liquid-tint)),
1457
+ var(--ns-material-glass-fill);
1458
+ border-color: transparent;
1459
+ -webkit-backdrop-filter: blur(var(--ns-material-liquid-fallback-blur)) saturate(var(--ns-material-saturate));
1460
+ backdrop-filter: blur(var(--ns-material-liquid-fallback-blur)) saturate(var(--ns-material-saturate));
1461
+ box-shadow: var(--ns-liquid-ring);
1462
+ }
1463
+ .ns-liquid { position: relative; isolation: isolate; border-radius: var(--ns-liquid-radius, var(--ns-radius-glass-panel)); color: var(--ns-color-text-1); }
1464
+ .ns-liquid--tint-none { background: var(--ns-material-liquid-highlight), var(--ns-material-glass-fill); }
1465
+ .ns-liquid--tint-signal { background: var(--ns-material-liquid-highlight), linear-gradient(var(--ns-color-signal-wash), var(--ns-color-signal-wash)), var(--ns-material-glass-fill); }
1466
+ .ns-liquid--floating,
1467
+ .ns-material--glass:not(.ns-depth-1):not(.ns-button) { box-shadow: var(--ns-liquid-ring), var(--ns-material-liquid-shadow); }
1468
+ .ns-liquid__defs { position: absolute; width: 0; height: 0; overflow: hidden; pointer-events: none; }
1469
+ .ns-material--glass .ns-panel__head { border-bottom-color: var(--ns-material-liquid-rim-low); }
1470
+
1471
+ /* True refraction where url() backdrop filters work (Chromium). */
1472
+ html[data-ns-liquid='refract'] :is(.ns-liquid--md, .ns-liquid:not(.ns-liquid--sm):not(.ns-liquid--lg), .ns-material--glass) {
1473
+ -webkit-backdrop-filter: var(--ns-liquid-filter, url(#ns-liquid-md)) saturate(1.25);
1474
+ backdrop-filter: var(--ns-liquid-filter, url(#ns-liquid-md)) saturate(1.25);
1475
+ }
1476
+ html[data-ns-liquid='refract'] :is(.ns-liquid--sm, .ns-depth-1.ns-material--glass, .ns-button--glass.ns-button--secondary, .ns-button--glass.ns-button--ghost:hover:not(:disabled), .ns-tabs--glass .ns-tabs__list, .ns-toast.ns-material--glass, .ns-tooltip.ns-material--glass) {
1477
+ -webkit-backdrop-filter: var(--ns-liquid-filter, url(#ns-liquid-sm)) saturate(1.25);
1478
+ backdrop-filter: var(--ns-liquid-filter, url(#ns-liquid-sm)) saturate(1.25);
1479
+ }
1480
+ html[data-ns-liquid='refract'] :is(.ns-liquid--lg, .ns-depth-3.ns-material--glass, .ns-dialog.ns-material--glass, .ns-sheet.ns-material--glass) {
1481
+ -webkit-backdrop-filter: var(--ns-liquid-filter, url(#ns-liquid-lg)) blur(2px) saturate(1.25);
1482
+ backdrop-filter: var(--ns-liquid-filter, url(#ns-liquid-lg)) blur(2px) saturate(1.25);
1483
+ }
1484
+ .ns-tabs--glass .ns-tabs__indicator {
1485
+ background: var(--ns-material-liquid-highlight), linear-gradient(var(--ns-material-spotlight), var(--ns-material-spotlight)), var(--ns-material-glass-fill);
1486
+ box-shadow: inset 0 1px 0 var(--ns-material-liquid-rim), inset 0 0 0 0.5px var(--ns-material-liquid-rim-low), 0 6px 16px -8px var(--ns-material-glow);
1487
+ }
1488
+
1489
+ /* ------------------------------------------------------ icon-led controls */
1490
+
1491
+ .ns-reveal-label {
1492
+ display: inline-grid;
1493
+ grid-template-columns: 1fr;
1494
+ min-width: 0;
1495
+ transition: grid-template-columns var(--ns-motion-duration-layout) var(--ns-motion-spring-layout),
1496
+ opacity var(--ns-motion-duration-320) var(--ns-motion-easing-hover);
1497
+ }
1498
+ .ns-reveal-label__inner { min-width: 0; overflow: hidden; }
1499
+ /* Icon-first expand (IconButton expand and every other host) lives in the
1500
+ precision layer at the end of this file. */
1501
+
1502
+ .ns-toolbar { display: inline-flex; align-items: center; gap: var(--ns-space-2); padding: var(--ns-space-4); border-radius: var(--ns-radius-glass-bar); }
1503
+ .ns-toolbar--vertical { flex-direction: column; align-items: stretch; }
1504
+ .ns-toolbar.ns-liquid { --ns-liquid-radius: var(--ns-radius-glass-bar); }
1505
+ .ns-toolbar .ns-toolbar__item { border-radius: var(--ns-button-radius); }
1506
+ .ns-toolbar .ns-toolbar__item.ns-button--rounded { --ns-button-radius: var(--ns-radius-glass-inner); }
1507
+
1508
+ /* Sidebar: labels show in full; they wrap rather than end in an ellipsis. */
1509
+ .ns-sidebar__text { flex: 1; overflow: visible; text-overflow: clip; white-space: normal; overflow-wrap: anywhere; }
1510
+ .ns-sidebar__item { min-height: var(--ns-size-control-sm); height: auto; }
1511
+ .ns-sidebar--collapsed { width: auto; min-width: 56px; }
1512
+ .ns-sidebar--collapsed .ns-sidebar__item { gap: 0; }
1513
+ .ns-sidebar--collapsed .ns-sidebar__icon { width: var(--ns-size-icon-sm); justify-content: center; }
1514
+ .ns-sidebar--collapsed .ns-sidebar__text { flex: 0 1 auto; grid-template-columns: 0fr; opacity: 0; }
1515
+ .ns-sidebar--collapsed .ns-sidebar__text .ns-reveal-label__inner { white-space: nowrap; transition: padding var(--ns-motion-duration-layout) var(--ns-motion-spring-layout); }
1516
+ .ns-sidebar--collapsed .ns-sidebar__meta,
1517
+ .ns-sidebar--collapsed .ns-sidebar__title { opacity: 0; white-space: nowrap; transition: opacity var(--ns-motion-duration-320) var(--ns-motion-easing-hover); }
1518
+ .ns-sidebar--collapsed:is(:hover, :focus-within) .ns-sidebar__text { grid-template-columns: 1fr; opacity: 1; }
1519
+ .ns-sidebar--collapsed:is(:hover, :focus-within) .ns-sidebar__text .ns-reveal-label__inner { padding-left: var(--ns-space-8); }
1520
+ .ns-sidebar--collapsed:is(:hover, :focus-within) :is(.ns-sidebar__meta, .ns-sidebar__title) { opacity: 1; }
1521
+
1522
+ /* Secondary actions ease in on hover or focus; always there on touch. */
1523
+ .ns-hover-actions {
1524
+ display: inline-flex;
1525
+ gap: var(--ns-space-6);
1526
+ align-items: center;
1527
+ transition: opacity var(--ns-motion-duration-micro) var(--ns-motion-easing-hover), transform var(--ns-motion-duration-layout) var(--ns-motion-easing-glide);
1528
+ }
1529
+ @media (hover: hover) and (pointer: fine) {
1530
+ .ns-hover-actions { opacity: 0; transform: translateX(4px); }
1531
+ :is(.ns-card, .ns-panel, .ns-table__row, .ns-list__item, .ns-hover-scope, .ns-study-row):is(:hover, :focus-within) .ns-hover-actions,
1532
+ .ns-hover-actions:focus-within { opacity: 1; transform: none; }
1533
+ }
1534
+
1535
+ /* --------------------------------------------------------------- flows */
1536
+
1537
+ .ns-flow { display: flex; flex-direction: column; gap: var(--ns-space-24); }
1538
+ .ns-flow__head { display: flex; flex-direction: column; gap: var(--ns-space-12); }
1539
+ .ns-flow__steps { display: flex; flex-wrap: wrap; gap: var(--ns-space-4); margin: 0; padding: 0; list-style: none; }
1540
+ .ns-flow__step {
1541
+ display: inline-flex;
1542
+ align-items: center;
1543
+ gap: var(--ns-space-8);
1544
+ height: var(--ns-size-control-md);
1545
+ padding: 0 var(--ns-space-12) 0 var(--ns-space-6);
1546
+ border: 0;
1547
+ border-radius: var(--ns-radius-full);
1548
+ background: transparent;
1549
+ color: var(--ns-color-text-3);
1550
+ font: inherit;
1551
+ font-weight: var(--ns-font-weight-medium);
1552
+ cursor: pointer;
1553
+ transition: background-color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover), color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover);
1554
+ }
1555
+ .ns-flow__step:hover:not(:disabled) { background: var(--ns-color-surface-2); color: var(--ns-color-text-1); }
1556
+ .ns-flow__step:disabled { cursor: default; }
1557
+ .ns-flow__step:focus-visible { outline: 2px solid var(--ns-color-focus); outline-offset: 2px; }
1558
+ .ns-flow__item[data-state='complete'] .ns-flow__step { color: var(--ns-color-text-2); }
1559
+ .ns-flow__item[data-state='current'] .ns-flow__step { color: var(--ns-color-text-1); background: var(--ns-color-surface-2); }
1560
+ .ns-flow__mark {
1561
+ display: inline-grid;
1562
+ place-items: center;
1563
+ width: 20px;
1564
+ height: 20px;
1565
+ flex: none;
1566
+ border-radius: var(--ns-radius-full);
1567
+ background: var(--ns-color-surface-3);
1568
+ color: var(--ns-color-text-2);
1569
+ font-family: var(--ns-font-family-mono);
1570
+ font-size: var(--ns-font-size-11);
1571
+ font-variant-numeric: var(--ns-font-numeric-figures);
1572
+ transition: background-color var(--ns-motion-duration-320) var(--ns-motion-easing-glide), color var(--ns-motion-duration-320) var(--ns-motion-easing-glide),
1573
+ transform var(--ns-motion-duration-layout) var(--ns-motion-spring-release);
1574
+ }
1575
+ .ns-flow__mark svg { width: 12px; height: 12px; }
1576
+ .ns-flow__item[data-state='complete'] .ns-flow__mark { background: var(--ns-color-signal-wash); color: var(--ns-color-signal); }
1577
+ .ns-flow__item[data-state='current'] .ns-flow__mark { background: var(--ns-button-primary-bg); color: var(--ns-button-primary-fg); transform: scale(1.06); }
1578
+ .ns-flow__rail { position: relative; height: 2px; border-radius: 1px; background: var(--ns-color-surface-3); overflow: hidden; }
1579
+ .ns-flow__fill { position: absolute; inset: 0; transform-origin: 0 50%; background: var(--ns-color-signal); border-radius: inherit; transition: transform var(--ns-motion-duration-layout) var(--ns-motion-spring-layout); }
1580
+ .ns-flow__stage { position: relative; min-width: 0; }
1581
+ .ns-flow__panel { display: flex; flex-direction: column; gap: var(--ns-space-16); animation: ns-step-in var(--ns-motion-duration-layout) var(--ns-motion-easing-glide); }
1582
+ .ns-flow[data-direction='back'] .ns-flow__panel { animation-name: ns-step-in-back; }
1583
+ .ns-flow--vt .ns-flow__panel { animation: none; }
1584
+ .ns-flow__panel-head { display: flex; flex-direction: column; gap: var(--ns-space-4); }
1585
+ .ns-flow__eyebrow { color: var(--ns-color-text-3); font-family: var(--ns-font-family-mono); font-size: var(--ns-font-size-11); font-variant-numeric: var(--ns-font-numeric-figures); }
1586
+ .ns-flow__title { margin: 0; font-size: var(--ns-font-size-18); line-height: var(--ns-font-line-height-18); font-weight: var(--ns-font-weight-semibold); outline: none; }
1587
+ .ns-flow__description { margin: 0; color: var(--ns-color-text-2); }
1588
+ .ns-flow--compact .ns-flow__head { gap: 0; }
1589
+ @keyframes ns-step-in { from { opacity: 0; transform: translateX(20px); } }
1590
+ @keyframes ns-step-in-back { from { opacity: 0; transform: translateX(-20px); } }
1591
+
1592
+ .ns-next-action { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--ns-space-8); }
1593
+ .ns-next-action__hint { margin-right: auto; color: var(--ns-color-text-3); font-size: var(--ns-font-size-12); }
1594
+ .ns-next-action__back svg { width: 14px; height: 14px; }
1595
+ .ns-next-action__back .ns-button__label,
1596
+ .ns-next-action__go .ns-button__label { display: inline-flex; align-items: center; gap: var(--ns-space-6); }
1597
+ .ns-next-action__arrow { display: inline-flex; transition: transform var(--ns-motion-duration-layout) var(--ns-motion-easing-glide); }
1598
+ .ns-next-action__arrow svg { width: 14px; height: 14px; }
1599
+ .ns-next-action__go:is(:hover, :focus-visible) .ns-next-action__arrow { transform: translateX(3px); }
1600
+
1601
+ /* View transitions for steps and tabs: the old view slips away, the new one glides in. */
1602
+ ::view-transition-old(ns-step) { animation: ns-vt-out var(--ns-motion-duration-micro) var(--ns-motion-easing-exit) both; }
1603
+ ::view-transition-new(ns-step) { animation: ns-vt-in var(--ns-motion-duration-layout) var(--ns-motion-easing-glide) both; }
1604
+ ::view-transition-group(ns-step) { animation-duration: var(--ns-motion-duration-layout); animation-timing-function: var(--ns-motion-spring-layout); }
1605
+ html[data-ns-flow-dir='back']::view-transition-new(ns-step) { animation-name: ns-vt-in-back; }
1606
+ html[data-ns-flow-dir='back']::view-transition-old(ns-step) { animation-name: ns-vt-out-back; }
1607
+ @keyframes ns-vt-in { from { opacity: 0; transform: translateX(16px); filter: blur(2px); } }
1608
+ @keyframes ns-vt-in-back { from { opacity: 0; transform: translateX(-16px); filter: blur(2px); } }
1609
+ @keyframes ns-vt-out { to { opacity: 0; transform: translateX(-8px); } }
1610
+ @keyframes ns-vt-out-back { to { opacity: 0; transform: translateX(8px); } }
1611
+
1612
+ /* StudyShell: a sliding pill under the tabs, and the guided flow. */
1613
+ .ns-study-shell__tablist .ns-tabs__indicator { border-radius: var(--ns-radius-control-pill); background: var(--ns-color-surface-3); }
1614
+ .ns-study-shell__tablist .ns-tabs__indicator::after { display: none; }
1615
+ .ns-study-shell__tablist[data-indicator] .ns-study-shell__tab.ns-tabs__trigger[aria-selected='true'] { background: transparent; box-shadow: none; }
1616
+ .ns-study-shell__flow { flex: 1; padding: var(--ns-space-20) var(--ns-space-32) var(--ns-space-32); }
1617
+ .ns-study-shell__next { margin-top: var(--ns-space-8); padding-top: var(--ns-space-16); }
1618
+
1619
+ /* ------------------------------------------------------------- reveal */
1620
+
1621
+ .ns-reveal,
1622
+ .ns-reveal--stagger > * {
1623
+ transition: opacity var(--ns-motion-duration-reveal) var(--ns-motion-easing-glide),
1624
+ transform var(--ns-motion-duration-reveal) var(--ns-motion-easing-glide),
1625
+ filter var(--ns-motion-duration-reveal) var(--ns-motion-easing-glide);
1626
+ transition-delay: var(--ns-reveal-delay, 0ms);
1627
+ }
1628
+ .ns-reveal[data-reveal='pending']:not(.ns-reveal--stagger) { opacity: 0; }
1629
+ .ns-reveal--rise[data-reveal='pending']:not(.ns-reveal--stagger) { transform: translateY(16px); }
1630
+ .ns-reveal--scale[data-reveal='pending']:not(.ns-reveal--stagger) { transform: scale(0.97); filter: blur(4px); }
1631
+ .ns-reveal--stagger[data-reveal='pending'] > * { opacity: 0; transform: translateY(10px); }
1632
+ .ns-reveal--stagger > * { transition-delay: calc(var(--ns-reveal-delay, 0ms) + var(--ns-i, 0) * var(--ns-motion-duration-stagger)); }
1633
+ .ns-reveal--stagger > :nth-child(2) { --ns-i: 1; }
1634
+ .ns-reveal--stagger > :nth-child(3) { --ns-i: 2; }
1635
+ .ns-reveal--stagger > :nth-child(4) { --ns-i: 3; }
1636
+ .ns-reveal--stagger > :nth-child(5) { --ns-i: 4; }
1637
+ .ns-reveal--stagger > :nth-child(6) { --ns-i: 5; }
1638
+ .ns-reveal--stagger > :nth-child(7) { --ns-i: 6; }
1639
+ .ns-reveal--stagger > :nth-child(8) { --ns-i: 7; }
1640
+ .ns-reveal--stagger > :nth-child(n + 9) { --ns-i: 8; }
1641
+
1642
+ /* ---------------------------------------------------------- fallbacks */
1643
+
1644
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
1645
+ .ns-liquid,
1646
+ .ns-material--glass { background: var(--ns-material-liquid-highlight), var(--ns-material-solid); }
1647
+ }
1648
+ @media (prefers-reduced-transparency: reduce) {
1649
+ .ns-liquid,
1650
+ .ns-material--glass,
1651
+ html[data-ns-liquid='refract'] :is(.ns-liquid, .ns-material--glass) {
1652
+ background: var(--ns-material-solid);
1653
+ -webkit-backdrop-filter: none;
1654
+ backdrop-filter: none;
1655
+ }
1656
+ }
1657
+ @media (forced-colors: active) {
1658
+ .ns-liquid,
1659
+ html[data-ns-liquid='refract'] :is(.ns-liquid, .ns-material--glass) {
1660
+ background: Canvas;
1661
+ -webkit-backdrop-filter: none;
1662
+ backdrop-filter: none;
1663
+ border: 1px solid CanvasText;
1664
+ box-shadow: none;
1665
+ }
1666
+ .ns-card,
1667
+ .ns-panel,
1668
+ .ns-surface,
1669
+ .ns-button--secondary { border-color: CanvasText; }
1670
+ .ns-flow__item[data-state='current'] .ns-flow__mark { background: Highlight; color: HighlightText; }
1671
+ .ns-flow__fill { background: Highlight; }
1672
+ .ns-hover-actions { opacity: 1; transform: none; }
1673
+ }
1674
+ @media (prefers-reduced-motion: reduce) {
1675
+ .ns-reveal[data-reveal],
1676
+ .ns-reveal--stagger[data-reveal] > * { opacity: 1 !important; transform: none !important; filter: none !important; }
1677
+ .ns-flow__panel { animation: none !important; }
1678
+ .ns-flow__mark,
1679
+ .ns-next-action__arrow,
1680
+ .ns-hover-actions,
1681
+ .ns-card--interactive { transform: none !important; }
1682
+ ::view-transition-group(*),
1683
+ ::view-transition-old(*),
1684
+ ::view-transition-new(*) { animation: none !important; }
1685
+ }
1686
+
1687
+ /* ==================================================== precision layer
1688
+ Icon-first expand everywhere and tighter glass corners (radius.glass.*). */
1689
+
1690
+ /* ------------------------------------------------------ icon-first expand
1691
+ A host (`ns-expand-host`) shows its icon; its ExpandLabel is collapsed to
1692
+ 0fr and eases to 1fr, so layout measures the whole label (no ellipsis, no
1693
+ width guessing, no jank). Hover, keyboard focus, selection, an open menu
1694
+ and data-expand="always" open it; touch screens always show it. */
1695
+
1696
+ .ns-expand-icon { display: inline-flex; flex: none; align-items: center; justify-content: center; }
1697
+ .ns-button__icon { display: inline-flex; flex: none; }
1698
+ .ns-expand-host { --ns-expand-gap: var(--ns-space-6); }
1699
+ .ns-expand-host .ns-expand {
1700
+ grid-template-columns: 0fr;
1701
+ opacity: 0;
1702
+ transition: grid-template-columns var(--ns-motion-duration-layout) var(--ns-motion-easing-glide),
1703
+ opacity var(--ns-motion-duration-320) var(--ns-motion-easing-hover);
1704
+ }
1705
+ .ns-expand-host .ns-expand__inner {
1706
+ white-space: nowrap;
1707
+ padding-inline: 0;
1708
+ transition: padding var(--ns-motion-duration-layout) var(--ns-motion-easing-glide);
1709
+ }
1710
+ .ns-expand-host:is(:hover, :focus-visible, :has(:focus-visible), [data-expand='always'], [aria-selected='true'], [aria-expanded='true']) .ns-expand { grid-template-columns: 1fr; opacity: 1; }
1711
+ .ns-expand-host:is(:hover, :focus-visible, :has(:focus-visible), [data-expand='always'], [aria-selected='true'], [aria-expanded='true']) .ns-expand__inner { padding-inline: var(--ns-expand-gap) var(--ns-expand-tail, 0px); }
1712
+ .ns-expand-host:is(:hover, :focus-visible, :has(:focus-visible), [data-expand='always'], [aria-selected='true'], [aria-expanded='true']) .ns-expand--end .ns-expand__inner { padding-inline: var(--ns-expand-tail, 0px) var(--ns-expand-gap); }
1713
+ @media (hover: none) {
1714
+ .ns-expand-host .ns-expand { grid-template-columns: 1fr; opacity: 1; }
1715
+ .ns-expand-host .ns-expand__inner { padding-inline: var(--ns-expand-gap) var(--ns-expand-tail, 0px); }
1716
+ .ns-expand-host .ns-expand--end .ns-expand__inner { padding-inline: var(--ns-expand-tail, 0px) var(--ns-expand-gap); }
1717
+ }
1718
+
1719
+ /* Buttons, icon buttons, menu triggers, pagination steps: square while closed. */
1720
+ .ns-button.ns-expand-host {
1721
+ --ns-expand-tail: var(--ns-space-2);
1722
+ gap: 0;
1723
+ min-width: var(--ns-h, var(--ns-size-control-md));
1724
+ padding-inline: calc((var(--ns-h, var(--ns-size-control-md)) - var(--ns-size-icon-sm)) / 2);
1725
+ }
1726
+ .ns-icon-button.ns-expand-host { width: auto; }
1727
+ .ns-button.ns-expand-host .ns-expand-icon { min-width: var(--ns-size-icon-sm); }
1728
+ .ns-button.ns-expand-host .ns-expand-icon svg { width: var(--ns-size-icon-sm); height: var(--ns-size-icon-sm); }
1729
+ .ns-run-cost__button--expand { width: auto; align-self: flex-start; }
1730
+
1731
+ /* Tabs: icon tabs open to their labels; the selected one stays open. */
1732
+ .ns-tabs__trigger--icon { display: inline-flex; align-items: center; gap: var(--ns-space-6); }
1733
+ .ns-tabs__trigger--icon.ns-expand-host { gap: 0; }
1734
+ .ns-tabs__icon { color: var(--ns-color-text-3); transition: color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover); }
1735
+ .ns-tabs__trigger:is(:hover, [aria-selected='true']) .ns-tabs__icon { color: currentColor; }
1736
+
1737
+ /* Breadcrumbs: collapsed crumbs show an icon or an ellipsis and open in full. */
1738
+ .ns-breadcrumbs--collapse li > :is(a, span):not(.ns-breadcrumbs__sep) { display: inline-flex; align-items: center; }
1739
+ .ns-breadcrumbs__more {
1740
+ display: inline-block;
1741
+ overflow: hidden;
1742
+ max-width: 1.25em;
1743
+ transition: max-width var(--ns-motion-duration-layout) var(--ns-motion-easing-glide), opacity var(--ns-motion-duration-micro) var(--ns-motion-easing-hover);
1744
+ }
1745
+ .ns-breadcrumbs li.ns-expand-host { --ns-expand-gap: 0px; }
1746
+ .ns-breadcrumbs li.ns-expand-host:has(.ns-breadcrumbs__icon svg) { --ns-expand-gap: var(--ns-space-4); }
1747
+ .ns-breadcrumbs--collapse:has(:focus-visible) .ns-expand { grid-template-columns: 1fr; opacity: 1; }
1748
+ .ns-breadcrumbs--collapse:has(:focus-visible) .ns-breadcrumbs__more,
1749
+ .ns-expand-host:is(:hover, :has(:focus-visible)) .ns-breadcrumbs__more { max-width: 0; opacity: 0; }
1750
+ .ns-breadcrumbs--collapse:has(:focus-visible) .ns-expand__inner { padding-inline: var(--ns-expand-gap) 0; }
1751
+ @media (hover: none) { .ns-breadcrumbs__more { max-width: 0; opacity: 0; } }
1752
+
1753
+ /* Tags, badges and the study status chip. */
1754
+ .ns-tag.ns-expand-host,
1755
+ .ns-badge.ns-expand-host,
1756
+ .ns-study-status.ns-expand-host { gap: 0; --ns-expand-gap: var(--ns-space-4); }
1757
+ .ns-study-status.ns-expand-host { --ns-expand-gap: var(--ns-space-6); }
1758
+ .ns-badge.ns-expand-host { padding-inline: var(--ns-space-4); }
1759
+ .ns-tag.ns-expand-host .ns-tag__remove { margin-left: var(--ns-space-4); }
1760
+ .ns-tag__icon svg,
1761
+ .ns-badge__icon svg { width: 12px; height: 12px; }
1762
+ .ns-badge__icon { margin-right: var(--ns-space-2); }
1763
+ .ns-badge.ns-expand-host .ns-badge__icon { margin-right: 0; }
1764
+
1765
+ /* Back in a flow: the arrow leads, the word follows on hover and focus. */
1766
+ .ns-next-action__back.ns-expand-host .ns-expand-icon svg { width: 14px; height: 14px; }
1767
+
1768
+ /* Ghost buttons on glass hover with light, not an opaque fill. */
1769
+ :where(.ns-liquid, .ns-material--glass, [data-material='glass']) .ns-button--ghost:not(.ns-button--glass):hover:not(:disabled) { background: var(--ns-material-spotlight); }
1770
+
1771
+ /* ---------------------------------------------------------- glass radii */
1772
+
1773
+ .ns-card.ns-material--glass,
1774
+ .ns-panel.ns-material--glass,
1775
+ .ns-dialog.ns-material--glass,
1776
+ :where(.ns-surface.ns-material--glass) { border-radius: var(--ns-radius-glass-panel); }
1777
+ .ns-sheet--bottom.ns-material--glass { border-radius: var(--ns-radius-glass-panel) var(--ns-radius-glass-panel) 0 0; }
1778
+ .ns-tabs--glass .ns-tabs__list { border-radius: var(--ns-radius-control-pill); }
1779
+ .ns-tabs--glass .ns-tabs__trigger,
1780
+ .ns-tabs--glass .ns-tabs__indicator { border-radius: var(--ns-radius-control-pill); }
1781
+ .ns-button--glass { border-radius: var(--ns-button-radius); }
1782
+
1783
+ /* --------------------------------------------- precision layer fallbacks */
1784
+
1785
+ @media (forced-colors: active) {
1786
+ .ns-expand-host .ns-expand { opacity: 1; }
1787
+ }
1788
+ @media (prefers-reduced-motion: reduce) {
1789
+ .ns-expand-host .ns-expand,
1790
+ .ns-expand-host .ns-expand__inner,
1791
+ .ns-breadcrumbs__more { transition: none !important; }
1792
+ }
1793
+
1794
+ /* ==================================================== shape and texture
1795
+ Every button is a pill (radius.control.pill): a capsule, or a circle when
1796
+ it is square (icon-only, or an icon-first button while its label is
1797
+ closed). `shape="rounded"` (.ns-button--rounded) opts out: radius.control.
1798
+ rounded off glass, radius.glass.control on it. Ends of a pill take a
1799
+ little more padding so the label does not crowd the curve. */
1800
+
1801
+ .ns-button:where(:not(.ns-icon-button, .ns-expand-host, .ns-button--rounded)) { padding-inline: calc(var(--ns-px, var(--ns-space-12)) + var(--ns-space-2)); }
1802
+ .ns-button.ns-button--rounded,
1803
+ .ns-pagination--rounded .ns-button { --ns-button-radius: var(--ns-radius-control-rounded); }
1804
+ :where(.ns-liquid, .ns-material--glass, [data-material='glass']) .ns-button.ns-button--rounded,
1805
+ .ns-button.ns-button--rounded.ns-button--glass { --ns-button-radius: var(--ns-radius-glass-control); }
1806
+ .ns-tabs--rounded.ns-tabs--pill .ns-tabs__list { border-radius: var(--ns-radius-8); }
1807
+ .ns-tabs--rounded.ns-tabs--pill .ns-tabs__trigger,
1808
+ .ns-tabs--rounded.ns-tabs--pill .ns-tabs__indicator { border-radius: var(--ns-radius-6); }
1809
+ .ns-tabs--rounded.ns-tabs--glass .ns-tabs__list { border-radius: var(--ns-radius-glass-bar); }
1810
+ .ns-tabs--rounded.ns-tabs--glass :is(.ns-tabs__trigger, .ns-tabs__indicator) { border-radius: var(--ns-radius-glass-control); }
1811
+
1812
+ /* ------------------------------------------------------ tactile texture
1813
+ A fine brushed micro-texture on every button, under the label: two
1814
+ periods of horizontal lines (3px light, 5px dark, coprime so they never
1815
+ band), a soft highlight across the top and a low inner bevel. It lives in
1816
+ ::after (z-index -1 inside the button's own stacking context), so it never
1817
+ fights the button's background or box-shadow. Per variant: secondary and
1818
+ primary always (primary in its Glacier-tuned signal-* set), ghost only on
1819
+ hover and focus, disabled never. This is the soft finish
1820
+ (finish="soft"); the machined spin finish below and glass's specular
1821
+ switch it off. Per theme through
1822
+ material.texture.*; text on every textured body is gated in
1823
+ tooling/contrast at its lightest and darkest point. */
1824
+
1825
+ .ns-button {
1826
+ --ns-tex-on: 0;
1827
+ --ns-tex-light: var(--ns-material-texture-light);
1828
+ --ns-tex-dark: var(--ns-material-texture-dark);
1829
+ --ns-tex-top: var(--ns-material-texture-top);
1830
+ --ns-tex-hi: var(--ns-material-texture-bevel-hi);
1831
+ --ns-tex-lo: var(--ns-material-texture-bevel-lo);
1832
+ }
1833
+ .ns-button--primary {
1834
+ --ns-tex-light: var(--ns-material-texture-signal-light);
1835
+ --ns-tex-dark: var(--ns-material-texture-signal-dark);
1836
+ --ns-tex-top: var(--ns-material-texture-signal-top);
1837
+ --ns-tex-hi: var(--ns-material-texture-signal-bevel-hi);
1838
+ --ns-tex-lo: var(--ns-material-texture-signal-bevel-lo);
1839
+ }
1840
+ .ns-button::after {
1841
+ content: '';
1842
+ position: absolute;
1843
+ inset: 0;
1844
+ z-index: -1;
1845
+ border-radius: inherit;
1846
+ pointer-events: none;
1847
+ background:
1848
+ linear-gradient(180deg, var(--ns-tex-top), transparent 56%),
1849
+ repeating-linear-gradient(180deg, var(--ns-tex-light) 0 1px, transparent 1px 3px),
1850
+ repeating-linear-gradient(180deg, var(--ns-tex-dark) 0 1px, transparent 1px 5px);
1851
+ box-shadow: inset 0 1px 0 var(--ns-tex-hi), inset 0 -1px 0 var(--ns-tex-lo);
1852
+ opacity: var(--ns-tex-on);
1853
+ transition: opacity var(--ns-motion-duration-micro) var(--ns-motion-easing-hover);
1854
+ }
1855
+ .ns-button:is(.ns-button--secondary, .ns-button--primary) { --ns-tex-on: 1; }
1856
+ .ns-button.ns-button--ghost:is(:hover, :focus-visible, [aria-expanded='true'], [aria-current='page']):not(:disabled, .ns-button--glass) { --ns-tex-on: 1; }
1857
+ .ns-button:disabled,
1858
+ .ns-button.ns-button--glass { --ns-tex-on: 0; }
1859
+ /* Pressed: the light comes from below, so the face reads as pushed in. */
1860
+ .ns-button:active:not(:disabled)::after { box-shadow: inset 0 1px 2px var(--ns-tex-lo), inset 0 -1px 0 var(--ns-tex-hi); }
1861
+ /* ConstellationButton is a button too (effects.css carries its texture). */
1862
+
1863
+ /* --------------------------------------------- shape and texture fallbacks */
1864
+
1865
+ @media (prefers-reduced-transparency: reduce) {
1866
+ .ns-button::after { background: none; }
1867
+ }
1868
+ @media (prefers-contrast: more) {
1869
+ .ns-button::after { display: none; }
1870
+ .ns-button:is(.ns-button--secondary, .ns-button--ghost:hover) { border-color: var(--ns-color-text-2); }
1871
+ }
1872
+ @media (forced-colors: active) {
1873
+ .ns-button::after { display: none; }
1874
+ .ns-button { border: 1px solid ButtonText; }
1875
+ }
1876
+ @media (prefers-reduced-motion: reduce) {
1877
+ .ns-button::after { transition: none; }
1878
+ }
1879
+
1880
+ /* ===================================================== machined spin
1881
+ The default finish of every button, on glass and off it: Button,
1882
+ IconButton, ToolbarItem, Menu.Trigger, pagination, toasts, layer close
1883
+ buttons and the effects ConstellationButton. finish="soft" (or
1884
+ NorthstarProvider buttonFinish="soft", which marks its root with
1885
+ data-button-finish) keeps the soft finish above: tonal fill and tactile
1886
+ texture. tone="silver" is an alias for spin. Glass buttons keep glass.
1887
+
1888
+ ::before is the metal: a conic sweep of seven machined stops under a fine
1889
+ concentric grain, both centred on the button, so an icon-only circle is
1890
+ a turned knob. It sits one pixel past the border, so the bevel ring is
1891
+ flush. ::after is the light band that slides across on hover and focus.
1892
+ While hovered, the sweep turns a few degrees with the pointer
1893
+ (--ns-pointer-x/-y, written by usePointerTracking; @property eases it;
1894
+ without a pointer it holds still). Primary and tone="signal" are
1895
+ Glacier-anodized. Ghost has no metal at rest; a faint wash of it fades in
1896
+ on hover, focus and selection. Disabled is matte and flat. Pressed drops
1897
+ 1px, dims a little and flips the bevel. Colours per theme through
1898
+ material.spin.*, motion through the material.spin scale; ink on the
1899
+ lightest and darkest stop of every body is gated in tooling/contrast. */
1900
+
1901
+ @property --ns-spin-angle { syntax: '<angle>'; inherits: false; initial-value: 200deg; }
1902
+
1903
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])) {
1904
+ --ns-tex-on: 0;
1905
+ --ns-spin-on: 1;
1906
+ --ns-spin-1: var(--ns-material-spin-stop-1);
1907
+ --ns-spin-2: var(--ns-material-spin-stop-2);
1908
+ --ns-spin-3: var(--ns-material-spin-stop-3);
1909
+ --ns-spin-4: var(--ns-material-spin-stop-4);
1910
+ --ns-spin-5: var(--ns-material-spin-stop-5);
1911
+ --ns-spin-6: var(--ns-material-spin-stop-6);
1912
+ --ns-spin-7: var(--ns-material-spin-stop-7);
1913
+ --ns-spin-ink: var(--ns-material-spin-ink);
1914
+ --ns-spin-grain: var(--ns-material-spin-grain-light);
1915
+ --ns-spin-grain-dark: var(--ns-material-spin-grain-dark);
1916
+ border-color: transparent;
1917
+ transition: color var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1918
+ box-shadow var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1919
+ filter var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1920
+ padding var(--ns-motion-duration-layout) var(--ns-motion-spring-layout),
1921
+ transform var(--ns-motion-duration-release) var(--ns-motion-spring-release);
1922
+ }
1923
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):is(.ns-button--primary, .ns-fx-cbutton--primary, .ns-tone-signal:not(.ns-button--ghost)) {
1924
+ --ns-spin-1: var(--ns-material-spin-signal-stop-1);
1925
+ --ns-spin-2: var(--ns-material-spin-signal-stop-2);
1926
+ --ns-spin-3: var(--ns-material-spin-signal-stop-3);
1927
+ --ns-spin-4: var(--ns-material-spin-signal-stop-4);
1928
+ --ns-spin-5: var(--ns-material-spin-signal-stop-5);
1929
+ --ns-spin-6: var(--ns-material-spin-signal-stop-6);
1930
+ --ns-spin-7: var(--ns-material-spin-signal-stop-7);
1931
+ --ns-spin-ink: var(--ns-material-spin-signal-ink);
1932
+ --ns-spin-grain: var(--ns-material-spin-signal-grain-light);
1933
+ }
1934
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):not(.ns-button--ghost) {
1935
+ color: var(--ns-spin-ink);
1936
+ text-shadow: 0 1px 0 var(--ns-material-spin-emboss);
1937
+ box-shadow: var(--ns-material-spin-shadow);
1938
+ }
1939
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin']))::before {
1940
+ content: '';
1941
+ position: absolute;
1942
+ inset: -1px;
1943
+ z-index: -1;
1944
+ border-radius: inherit;
1945
+ pointer-events: none;
1946
+ --ns-spin-angle: var(--ns-material-spin-angle);
1947
+ background:
1948
+ repeating-radial-gradient(circle at 50% 50%, var(--ns-spin-grain-dark) 0 1px, var(--ns-spin-grain) 1px 2px),
1949
+ conic-gradient(from var(--ns-spin-angle) at 50% 50%, var(--ns-spin-1), var(--ns-spin-2) 8%, var(--ns-spin-3) 22%, var(--ns-spin-4) 38%, var(--ns-spin-5) 52%, var(--ns-spin-6) 68%, var(--ns-spin-7) 84%, var(--ns-spin-1));
1950
+ opacity: var(--ns-spin-on);
1951
+ transition: opacity var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1952
+ --ns-spin-angle var(--ns-motion-duration-layout) var(--ns-motion-easing-glide);
1953
+ }
1954
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin']))::after {
1955
+ inset: -1px;
1956
+ box-shadow: none;
1957
+ background: linear-gradient(100deg, transparent 30%, var(--ns-material-spin-sheen) 50%, transparent 70%) 120% 0 / 260% 100% no-repeat;
1958
+ opacity: var(--ns-spin-on);
1959
+ transition: opacity var(--ns-motion-duration-micro) var(--ns-motion-easing-hover),
1960
+ background-position var(--ns-material-spin-sweep) var(--ns-material-spin-sweep-easing);
1961
+ }
1962
+ /* Hover: the band slides across, the metal brightens a touch and the sweep turns with the pointer. */
1963
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):is(:hover, :focus-visible):not(:disabled)::after { background-position: -20% 0; }
1964
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):hover:not(:disabled) { filter: brightness(1.04); }
1965
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):hover:not(:disabled)::before {
1966
+ --ns-spin-angle: calc(var(--ns-material-spin-angle) + var(--ns-pointer-x, 0) * var(--ns-material-spin-turn) / 2 + var(--ns-pointer-y, 0) * var(--ns-material-spin-turn) / 6);
1967
+ }
1968
+ /* Pressed: down a pixel, a little smaller and darker, and the bevel flips. */
1969
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):active:not(:disabled) {
1970
+ transform: translate(var(--ns-mag-x, 0px), calc(var(--ns-mag-y, 0px) + 1px)) scale(var(--ns-material-spin-press));
1971
+ filter: brightness(0.96);
1972
+ transition-duration: var(--ns-motion-duration-press);
1973
+ transition-timing-function: var(--ns-motion-spring-press);
1974
+ }
1975
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):not(.ns-button--ghost):active:not(:disabled) { box-shadow: var(--ns-material-spin-shadow-press); }
1976
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):active:not(:disabled)::after { box-shadow: none; }
1977
+ /* Focus: the Glacier ring (color.focus), 2px, standing 3px off the metal. */
1978
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):focus-visible { outline: 2px solid var(--ns-color-focus); outline-offset: 3px; }
1979
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):not(.ns-button--ghost):focus-visible { box-shadow: var(--ns-material-spin-shadow); }
1980
+ /* Ghost: no metal at rest; the faint wash on hover, focus, an open menu and the current page. */
1981
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])).ns-button--ghost { --ns-spin-on: 0; }
1982
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])).ns-button--ghost:is(:hover, :focus-visible, [aria-expanded='true'], [aria-current='page']):not(:disabled) {
1983
+ --ns-spin-on: var(--ns-material-spin-ghost);
1984
+ background: transparent;
1985
+ box-shadow: inset 0 0 0 1px var(--ns-material-spin-ghost-edge);
1986
+ }
1987
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])) .ns-button__check { color: currentColor; }
1988
+ /* Disabled: matte and flat, no grain, sheen or bevel. Loading keeps its metal. */
1989
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):disabled:not([data-loading]) {
1990
+ --ns-spin-on: 1;
1991
+ color: var(--ns-material-spin-matte-ink);
1992
+ text-shadow: none;
1993
+ box-shadow: inset 0 0 0 1px var(--ns-color-line-2);
1994
+ filter: none;
1995
+ opacity: 1;
1996
+ }
1997
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):disabled:not([data-loading])::before { background: var(--ns-material-spin-matte); }
1998
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):disabled:not([data-loading])::after { display: none; }
1999
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])).ns-button--ghost:disabled:not([data-loading]) { --ns-spin-on: 0; color: var(--ns-color-text-3); }
2000
+
2001
+ /* ----------------------------------------------- machined spin fallbacks */
2002
+
2003
+ @media (prefers-reduced-transparency: reduce) {
2004
+ /* No grain: the sweep alone. */
2005
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])) { --ns-spin-grain: transparent; --ns-spin-grain-dark: transparent; }
2006
+ }
2007
+ @media (prefers-contrast: more) {
2008
+ /* A flat fill at the lightest stop, outlined in ink. */
2009
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin']))::before { background: var(--ns-spin-2); }
2010
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin']))::after { display: none; }
2011
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):not(.ns-button--ghost) { box-shadow: 0 0 0 1px var(--ns-spin-ink); text-shadow: none; }
2012
+ }
2013
+ @media (forced-colors: active) {
2014
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin']))::before,
2015
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin']))::after { display: none; }
2016
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])) {
2017
+ background: ButtonFace;
2018
+ color: ButtonText;
2019
+ border: 1px solid ButtonText;
2020
+ box-shadow: none;
2021
+ text-shadow: none;
2022
+ filter: none;
2023
+ }
2024
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):disabled,
2025
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):disabled:not([data-loading]) { background: ButtonFace; color: GrayText; border-color: GrayText; box-shadow: none; }
2026
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):focus-visible { outline-color: Highlight; }
2027
+ }
2028
+ @media (prefers-reduced-motion: reduce) {
2029
+ /* No sliding band and no turning sweep; the press holds still too. */
2030
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin']))::after { display: none; }
2031
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin'])):hover:not(:disabled)::before { --ns-spin-angle: var(--ns-material-spin-angle); }
2032
+ :is(.ns-button, .ns-fx-cbutton):not(.ns-button--glass, [data-finish='soft'], [data-button-finish='soft'] :not([data-finish='spin']))::before { transition: none; }
2033
+ }