@octane-xplat/ui 0.7.3 → 0.9.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 (195) hide show
  1. package/README.md +4 -3
  2. package/dist/native/Button.tsrx.js +1 -1
  3. package/dist/native/KeyboardAvoiding.mobile.tsrx.js +2 -2
  4. package/dist/native/Markdown.tsrx.js +233 -0
  5. package/dist/native/Overlay.tsrx.js +15 -26
  6. package/dist/native/PinInput.tsrx.js +25 -7
  7. package/dist/native/Popover.tsrx.js +17 -18
  8. package/dist/native/Pressable.tsrx.js +4 -1
  9. package/dist/native/SearchInput.tsrx.js +6 -3
  10. package/dist/native/Sheet.tsrx.js +22 -37
  11. package/dist/native/TextArea.tsrx.js +6 -3
  12. package/dist/native/TextInput.tsrx.js +6 -3
  13. package/dist/native/VirtualList.tsrx.js +305 -121
  14. package/dist/native/index.shared.js +7 -5
  15. package/dist/native/keyboard-inset.mobile.js +5 -5
  16. package/dist/native/navigation-request.js +26 -0
  17. package/dist/native/octane-client-build.json +1 -1
  18. package/dist/native/overlay-lifetime.mobile.js +65 -0
  19. package/dist/native/pan.tsrx.js +16 -2
  20. package/dist/native/route-table.js +90 -10
  21. package/dist/native/route.js +26 -16
  22. package/dist/native/shared.js +7 -5
  23. package/dist/native/sheet-service.js +1 -1
  24. package/dist/native/svg.mobile.js +1 -1
  25. package/dist/native/text-focus.js +10 -0
  26. package/dist/native/text-write.js +18 -11
  27. package/dist/native/toast-anchor.tsrx.js +1 -1
  28. package/dist/native/ui.js +7 -6
  29. package/dist/native/vendor/ui-svg/index.android.js +2 -2
  30. package/dist/native/vendor/ui-svg/index.ios.js +2 -2
  31. package/dist/native/vendor/ui-svg/{index.common.js → src/index.common.js} +1 -1
  32. package/dist/native/virtual-list-cell-pool.js +63 -0
  33. package/dist/web/Button.web.tsrx.js +1 -1
  34. package/dist/web/KeyboardAvoiding.web.tsrx.js +78 -0
  35. package/dist/web/Markdown.tsrx.js +98 -0
  36. package/dist/web/Overlay.web.tsrx.js +81 -32
  37. package/dist/web/PinInput.web.tsrx.js +24 -10
  38. package/dist/web/Pressable.web.tsrx.js +169 -53
  39. package/dist/web/SearchInput.web.tsrx.js +2 -2
  40. package/dist/web/Sheet.web.tsrx.js +20 -7
  41. package/dist/web/TextArea.web.tsrx.js +22 -17
  42. package/dist/web/TextInput.web.tsrx.js +2 -2
  43. package/dist/web/VirtualList.web.tsrx.js +220 -109
  44. package/dist/web/WebView.web.tsrx.js +3 -3
  45. package/dist/web/{index.shared.js → index.shared.web.js} +7 -5
  46. package/dist/web/modal-focus.web.js +95 -0
  47. package/dist/web/navigation-request.js +26 -0
  48. package/dist/web/octane-client-build.json +1 -1
  49. package/dist/web/pan.web.tsrx.js +27 -10
  50. package/dist/web/route-table.js +90 -10
  51. package/dist/web/route.web.js +88 -22
  52. package/dist/web/sheet-service.web.js +8 -0
  53. package/dist/web/theme/colorScheme.web.js +31 -4
  54. package/dist/web/ui.js +8 -6
  55. package/dist/web/virtual-list-cell-pool.js +63 -0
  56. package/package.json +21 -19
  57. package/platforms/android/native-api-usage.json +7 -7
  58. package/src/CommandPalette.tsrx +1 -0
  59. package/src/Grid.tsrx +2 -0
  60. package/src/Icon.macos.tsrx +1 -0
  61. package/src/KeyboardAvoiding.macos.tsrx +7 -1
  62. package/src/KeyboardAvoiding.mobile.tsrx +2 -2
  63. package/src/KeyboardAvoiding.tsrx +15 -0
  64. package/src/KeyboardAvoiding.web.tsrx +15 -0
  65. package/src/Markdown.tsrx +121 -0
  66. package/src/Meter.tsrx +1 -0
  67. package/src/Overlay.tsrx +15 -30
  68. package/src/Overlay.web.tsrx +12 -3
  69. package/src/PinInput.macos.tsrx +20 -6
  70. package/src/PinInput.mobile.test.ts +107 -0
  71. package/src/PinInput.tsrx +20 -6
  72. package/src/PinInput.web.test.tsrx +42 -0
  73. package/src/PinInput.web.tsrx +20 -6
  74. package/src/Popover.tsrx +17 -17
  75. package/src/Pressable.tsrx +1 -1
  76. package/src/Pressable.web.test.tsrx +36 -0
  77. package/src/Pressable.web.tsrx +37 -6
  78. package/src/RouteHost.macos.tsrx +1 -0
  79. package/src/SearchInput.tsrx +4 -3
  80. package/src/SearchInput.web.tsrx +1 -1
  81. package/src/Sheet.tsrx +20 -41
  82. package/src/Sheet.web.tsrx +9 -1
  83. package/src/Text.tsrx +2 -0
  84. package/src/TextArea.tsrx +6 -5
  85. package/src/TextArea.web.test.tsrx +39 -0
  86. package/src/TextArea.web.tsrx +7 -5
  87. package/src/TextInput.tsrx +6 -6
  88. package/src/TextInput.web.tsrx +1 -1
  89. package/src/VirtualList.macos.tsrx +93 -6
  90. package/src/VirtualList.mobile.test.tsrx +232 -0
  91. package/src/VirtualList.tsrx +285 -81
  92. package/src/VirtualList.web.tsrx +131 -25
  93. package/src/WebView.tsrx +1 -0
  94. package/src/WebView.web.tsrx +3 -0
  95. package/src/android/BottomNavigationView.android.tsrx +1 -0
  96. package/src/android/MaterialDialog.android.tsrx +1 -0
  97. package/src/anim.macos.tsrx +1 -0
  98. package/src/apply-native-modifiers.native.ts +4 -1
  99. package/src/child-array.ts +12 -7
  100. package/src/colorScheme.macos.ts +1 -0
  101. package/src/controls.macos.tsrx +4 -1
  102. package/src/data-driver.mobile.test.ts +120 -0
  103. package/src/data-lifecycle.mobile.test.ts +251 -0
  104. package/src/escape-props.ts +2 -0
  105. package/src/grid-auto-placement.ts +54 -12
  106. package/src/grid-placement.ts +1 -1
  107. package/src/hosted-unsupported.macos.tsrx +1 -0
  108. package/src/index.macos.ts +23 -3
  109. package/src/index.mobile.ts +0 -1
  110. package/src/index.shared.ts +22 -1
  111. package/src/index.shared.web.ts +291 -0
  112. package/src/index.web.ts +1 -1
  113. package/src/input-readiness.web.test.tsrx +82 -0
  114. package/src/ios/UITabBar.ios.tsrx +1 -0
  115. package/src/keyboard-inset.mobile.test.ts +62 -0
  116. package/src/keyboard-inset.mobile.ts +63 -43
  117. package/src/layout-shells.macos.tsrx +1 -5
  118. package/src/macos-extras.macos.tsrx +2 -0
  119. package/src/modal-focus.web.test.tsrx +63 -0
  120. package/src/modal-focus.web.ts +162 -0
  121. package/src/navigation-request.ts +37 -0
  122. package/src/overlay-lifecycle.mobile.test.ts +316 -0
  123. package/src/overlay-lifetime.mobile.ts +107 -0
  124. package/src/pan.mobile.test.tsrx +116 -0
  125. package/src/pan.tsrx +20 -2
  126. package/src/pan.web.tsrx +35 -7
  127. package/src/platformProps.ts +1 -0
  128. package/src/popover.mobile.test.ts +9 -1
  129. package/src/pressable-accessibility.mobile.test.ts +30 -0
  130. package/src/props.ts +163 -3
  131. package/src/refresh.ts +6 -1
  132. package/src/refresh.web.test.ts +77 -77
  133. package/src/route-android.test.ts +162 -0
  134. package/src/route-history.web.test.ts +80 -0
  135. package/src/route-navigation.web.test.ts +173 -0
  136. package/src/route-table.test.ts +188 -9
  137. package/src/route-table.ts +138 -10
  138. package/src/route.macos.ts +56 -13
  139. package/src/route.ts +54 -24
  140. package/src/route.web.ts +141 -24
  141. package/src/sheet-detents.ts +5 -3
  142. package/src/sheet-service.macos.ts +5 -1
  143. package/src/sheet-service.web.ts +8 -0
  144. package/src/sheet-snap.test.ts +23 -23
  145. package/src/sheet-snap.ts +6 -1
  146. package/src/svg.mobile.ts +9 -3
  147. package/src/tap-to-blur.ios.ts +48 -32
  148. package/src/tap-to-blur.mobile.ts +23 -15
  149. package/src/text-focus.ts +12 -0
  150. package/src/text-input.mobile.test.ts +89 -0
  151. package/src/text-write.ts +21 -11
  152. package/src/theme/colorScheme.linux.ts +2 -4
  153. package/src/theme/colorScheme.web.ts +51 -4
  154. package/src/theme/theme-scheme.macos.ts +8 -2
  155. package/src/theme/tokens.css +1736 -1730
  156. package/src/translate.macos.ts +4 -1
  157. package/src/virtual-list-cell-pool.test.ts +53 -0
  158. package/src/virtual-list-cell-pool.ts +81 -0
  159. package/src/virtual-list-layout.test.ts +11 -8
  160. package/src/virtual-list-layout.ts +4 -8
  161. package/src/windows.linux.ts +7 -2
  162. package/src/windows.macos.ts +1 -0
  163. package/types/generated/.tsrx-typegen-manifest.json +13 -3
  164. package/types/generated/KeyboardAvoiding.d.ts +3 -0
  165. package/types/generated/KeyboardAvoiding.web.d.ts +2 -0
  166. package/types/generated/Markdown.d.ts +59 -0
  167. package/types/generated/TextArea.d.ts +2 -2
  168. package/types/generated/TextArea.web.d.ts +1 -1
  169. package/types/generated/TextInput.d.ts +2 -3
  170. package/types/generated/VirtualList.d.ts +3 -3
  171. package/types/generated/index.mobile.d.ts +0 -1
  172. package/types/generated/index.shared.d.ts +6 -3
  173. package/types/generated/index.shared.web.d.ts +114 -0
  174. package/types/generated/index.web.d.ts +1 -1
  175. package/types/generated/modal-focus.web.d.ts +3 -0
  176. package/types/generated/navigation-request.d.ts +10 -0
  177. package/types/generated/overlay-lifetime.mobile.d.ts +10 -0
  178. package/types/generated/props.d.ts +111 -3
  179. package/types/generated/route-table.d.ts +14 -3
  180. package/types/generated/svg.mobile.d.ts +2 -2
  181. package/types/generated/text-focus.d.ts +3 -0
  182. package/types/generated/text-write.d.ts +3 -1
  183. package/types/generated/theme/colorScheme.web.d.ts +5 -0
  184. package/types/generated/virtual-list-cell-pool.d.ts +18 -0
  185. package/types/index.macos.d.ts +15 -3
  186. /package/src/vendor/ui-svg/{index.android.ts → src/index.android.ts} +0 -0
  187. /package/src/vendor/ui-svg/{index.common.ts → src/index.common.ts} +0 -0
  188. /package/src/vendor/ui-svg/{index.ios.ts → src/index.ios.ts} +0 -0
  189. /package/src/vendor/ui-svg/{index.ts → src/index.ts} +0 -0
  190. /package/src/vendor/ui-svg/{typings → src/typings}/SVGKit.d.ts +0 -0
  191. /package/src/vendor/ui-svg/{typings → src/typings}/android.d.ts +0 -0
  192. /package/src/vendor/ui-svg/{typings → src/typings}/ios.d.ts +0 -0
  193. /package/types/generated/vendor/ui-svg/{index.common.d.ts → src/index.common.d.ts} +0 -0
  194. /package/types/generated/vendor/ui-svg/{index.d.ts → src/index.d.ts} +0 -0
  195. /package/types/generated/vendor/ui-svg/{index.ios.d.ts → src/index.ios.d.ts} +0 -0
@@ -1,48 +1,53 @@
1
1
  /* Design tokens — NS reads :root/.ns-root; web reads :root/.dark.
2
2
  Numbers are dip (native) = px (web) by leaf convention. */
3
3
 
4
- :root,
5
- .ns-root {
6
- --color-primary: #171717;
7
- /* Token names double as the future Tailwind theme keys: text-onprimary
4
+ @layer xplat.reset, xplat.base, xplat.tokens, xplat.components, xplat.recipes, xplat.utilities;
5
+
6
+ @layer xplat.tokens {
7
+ :root,
8
+ .ns-root {
9
+ --color-primary: #171717;
10
+ /* Token names double as the future Tailwind theme keys: text-onprimary
8
11
  resolves --color-onprimary, bg-danger resolves --color-danger. Keep
9
12
  class-name segments and var-name segments identical. */
10
- --color-onprimary: #fafafa;
11
- --color-surface: #ffffff;
12
- --color-surface-secondary: #fafafa;
13
- --color-text: #0a0a0a;
14
- --color-text-secondary: #666666;
15
- --color-border: #eaeaea;
16
- --color-border-strong: #d4d4d4;
17
- --color-danger: #dc2626;
18
- /* Family lists are deliberately generic. Apps override these with their
13
+ --color-onprimary: #fafafa;
14
+ --color-surface: #ffffff;
15
+ --color-surface-secondary: #fafafa;
16
+ --color-text: #0a0a0a;
17
+ --color-text-secondary: #666666;
18
+ --color-border: #eaeaea;
19
+ --color-border-strong: #d4d4d4;
20
+ --color-danger: #dc2626;
21
+ /* Family lists are deliberately generic. Apps override these with their
19
22
  web @font-face family and the platform's registered native name. */
20
- --font-sans: system-ui, sans-serif;
21
- --font-mono: ui-monospace, monospace;
22
- --space-4: 16px;
23
- /* Corner scale — rounded-rectangle radii. Smooth (squircle) corners are
23
+ --font-sans: system-ui, sans-serif;
24
+ --font-mono: ui-monospace, monospace;
25
+ --space-4: 16px;
26
+ /* Corner scale — rounded-rectangle radii. Smooth (squircle) corners are
24
27
  the default corner treatment: every class that consumes a token also
25
28
  declares `corner-shape: squircle`. Full-radius circles opt out —
26
29
  `corner-shape` would reshape them into squircle disks on iOS/Chromium. */
27
- --radius-sm: 6px;
28
- --radius-md: 8px;
29
- --radius-lg: 12px;
30
- --radius-xl: 16px;
31
- }
30
+ --radius-sm: 6px;
31
+ --radius-md: 8px;
32
+ --radius-lg: 12px;
33
+ --radius-xl: 16px;
34
+ }
32
35
 
33
- .ns-dark,
34
- .dark {
35
- --color-primary: #ededed;
36
- --color-onprimary: #171717;
37
- --color-surface: #0a0a0a;
38
- --color-surface-secondary: #141414;
39
- --color-text: #ededed;
40
- --color-text-secondary: #a1a1a1;
41
- --color-border: #2e2e2e;
42
- --color-border-strong: #4a4a4a;
36
+ .ns-dark,
37
+ .dark {
38
+ --color-primary: #ededed;
39
+ --color-onprimary: #171717;
40
+ --color-surface: #0a0a0a;
41
+ --color-surface-secondary: #141414;
42
+ --color-text: #ededed;
43
+ --color-text-secondary: #a1a1a1;
44
+ --color-border: #2e2e2e;
45
+ --color-border-strong: #4a4a4a;
46
+ }
43
47
  }
44
48
 
45
- /* xplat-web-only:start — DOM normalization. NativeScript is the reference
49
+ @layer xplat.components {
50
+ /* xplat-web-only:start — DOM normalization. NativeScript is the reference
46
51
  box model: dip sizes are outer boxes, and a border paints from
47
52
  width+color alone. The DOM's UA stylesheet diverges on both counts, so
48
53
  normalize the deltas once instead of patching individual rules:
@@ -54,31 +59,31 @@
54
59
  phantom ~3px edges wherever a rule sets only one side's width.
55
60
  - <img> is a leaf, not inline text (no baseline descender gap).
56
61
  - .vx-app columns are flex like their native screen containers. */
57
- *,
58
- *::before,
59
- *::after {
60
- box-sizing: border-box;
61
- }
62
- * {
63
- border-width: 0;
64
- border-style: solid;
65
- }
66
- img {
67
- display: block;
68
- }
69
- .vx-app {
70
- display: flex;
71
- flex-direction: column;
72
- }
73
- /* Native has no scroll chaining — a ScrollView just bounces inside
62
+ *,
63
+ *::before,
64
+ *::after {
65
+ box-sizing: border-box;
66
+ }
67
+ * {
68
+ border-width: 0;
69
+ border-style: solid;
70
+ }
71
+ img {
72
+ display: block;
73
+ }
74
+ .vx-app {
75
+ display: flex;
76
+ flex-direction: column;
77
+ }
78
+ /* Native has no scroll chaining — a ScrollView just bounces inside
74
79
  itself because the parent is never a scroller. contain is the DOM
75
80
  equivalent: scroll stays in the leaf it started in. */
76
- .vx-scroll {
77
- overscroll-behavior: contain;
78
- }
79
- /* xplat-web-only:end */
81
+ .vx-scroll {
82
+ overscroll-behavior: contain;
83
+ }
84
+ /* xplat-web-only:end */
80
85
 
81
- /* Shared normalization — NS FlexboxLayout defaults flex children to
86
+ /* Shared normalization — NS FlexboxLayout defaults flex children to
82
87
  shrink:1 with NO min-content floor (its shrink pass clamps at
83
88
  effectiveMinWidth/Height = the explicit min-* prop, default 0), so an
84
89
  overflowing column crushes intrinsic children toward zero where the
@@ -86,1865 +91,1866 @@ img {
86
91
  reason — do the same on both engines. `*` is zero-specificity: flex-1
87
92
  (the shorthand sets shrink:1) and .shrink still opt back in, and
88
93
  truncation patterns pair them with min-w-0/min-h-0. */
89
- * {
90
- flex-shrink: 0;
91
- }
94
+ * {
95
+ flex-shrink: 0;
96
+ }
92
97
 
93
- /* vocabulary base styles */
98
+ /* vocabulary base styles */
94
99
 
95
- .vx-view,
96
- .vx-row {
97
- display: flex;
98
- }
100
+ .vx-view,
101
+ .vx-row {
102
+ display: flex;
103
+ }
99
104
 
100
- .vx-view {
101
- flex-direction: column;
102
- }
105
+ .vx-view {
106
+ flex-direction: column;
107
+ }
103
108
 
104
- /* Safe-area values are zero when the viewport has no display cutout. */
105
- .vx-safe-area {
106
- padding-top: constant(safe-area-inset-top);
107
- padding-right: constant(safe-area-inset-right);
108
- padding-bottom: constant(safe-area-inset-bottom);
109
- padding-left: constant(safe-area-inset-left);
110
- }
109
+ /* Safe-area values are zero when the viewport has no display cutout. */
110
+ .vx-safe-area {
111
+ padding-top: constant(safe-area-inset-top);
112
+ padding-right: constant(safe-area-inset-right);
113
+ padding-bottom: constant(safe-area-inset-bottom);
114
+ padding-left: constant(safe-area-inset-left);
115
+ }
111
116
 
112
- .vx-safe-area {
113
- padding-top: env(safe-area-inset-top);
114
- padding-right: env(safe-area-inset-right);
115
- padding-bottom: env(safe-area-inset-bottom);
116
- padding-left: env(safe-area-inset-left);
117
- }
117
+ .vx-safe-area {
118
+ padding-top: env(safe-area-inset-top);
119
+ padding-right: env(safe-area-inset-right);
120
+ padding-bottom: env(safe-area-inset-bottom);
121
+ padding-left: env(safe-area-inset-left);
122
+ }
118
123
 
119
- .vx-keyboard-avoiding {
120
- display: flex;
121
- flex-direction: column;
122
- }
123
- /* Self-drawn drawer — shared geometry. Native grid children overlay the
124
+ .vx-keyboard-avoiding {
125
+ display: flex;
126
+ flex-direction: column;
127
+ }
128
+ /* Self-drawn drawer — shared geometry. Native grid children overlay the
124
129
  cell (panel pins left via horizontalAlignment in the leaf); DOM
125
130
  positioning extras are web-only below. */
126
- .vx-drawer-backdrop {
127
- background-color: rgba(0, 0, 0, 0.4);
128
- }
129
- .vx-drawer-panel {
130
- width: 280px;
131
- background-color: var(--color-surface);
132
- }
133
- /* xplat-web-only:start */
134
- .vx-drawer {
135
- display: flex;
136
- flex-direction: row;
137
- position: relative;
138
- overflow: hidden;
139
- }
140
- .vx-drawer-main {
141
- display: flex;
142
- flex: 1;
143
- min-width: 0;
144
- }
145
- .vx-drawer-backdrop {
146
- position: absolute;
147
- inset: 0;
148
- }
149
- .vx-drawer-panel {
150
- position: absolute;
151
- z-index: 1;
152
- top: 0;
153
- bottom: 0;
154
- left: 0;
155
- overflow: auto;
156
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
157
- }
158
- /* xplat-web-only:end */
131
+ .vx-drawer-backdrop {
132
+ background-color: rgba(0, 0, 0, 0.4);
133
+ }
134
+ .vx-drawer-panel {
135
+ width: 280px;
136
+ background-color: var(--color-surface);
137
+ }
138
+ /* xplat-web-only:start */
139
+ .vx-drawer {
140
+ display: flex;
141
+ flex-direction: row;
142
+ position: relative;
143
+ overflow: hidden;
144
+ }
145
+ .vx-drawer-main {
146
+ display: flex;
147
+ flex: 1;
148
+ min-width: 0;
149
+ }
150
+ .vx-drawer-backdrop {
151
+ position: absolute;
152
+ inset: 0;
153
+ }
154
+ .vx-drawer-panel {
155
+ position: absolute;
156
+ z-index: 1;
157
+ top: 0;
158
+ bottom: 0;
159
+ left: 0;
160
+ overflow: auto;
161
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
162
+ }
163
+ /* xplat-web-only:end */
159
164
 
160
- .vx-row {
161
- flex-direction: row;
162
- }
165
+ .vx-row {
166
+ flex-direction: row;
167
+ }
163
168
 
164
- .vx-grid,
165
- .vx-stack {
166
- display: grid;
167
- }
169
+ .vx-grid,
170
+ .vx-stack {
171
+ display: grid;
172
+ }
168
173
 
169
- .vx-stack > * {
170
- grid-area: 1 / 1;
171
- horizontal-align: left;
172
- vertical-align: top;
173
- }
174
+ .vx-stack > * {
175
+ grid-area: 1 / 1;
176
+ horizontal-align: left;
177
+ vertical-align: top;
178
+ }
174
179
 
175
- /* xplat-web-only:start — Absolute's native leaf is absolutelayout; CSS
180
+ /* xplat-web-only:start — Absolute's native leaf is absolutelayout; CSS
176
181
  positioning is web-only (children position via left/top props) */
177
- .vx-absolute {
178
- position: relative;
179
- }
182
+ .vx-absolute {
183
+ position: relative;
184
+ }
180
185
 
181
- .vx-absolute > * {
182
- position: absolute;
183
- }
184
- /* xplat-web-only:end */
186
+ .vx-absolute > * {
187
+ position: absolute;
188
+ }
189
+ /* xplat-web-only:end */
185
190
 
186
- .vx-spacer {
187
- flex-grow: 1;
188
- }
191
+ .vx-spacer {
192
+ flex-grow: 1;
193
+ }
189
194
 
190
- .vx-pressable {
191
- display: flex;
192
- flex-direction: column;
193
- user-select: none;
194
- }
195
+ .vx-pressable {
196
+ display: flex;
197
+ flex-direction: column;
198
+ user-select: none;
199
+ }
195
200
 
196
- /* web press feedback */
197
- .vx-pressable:active {
198
- opacity: 0.85;
199
- }
200
- /* native press feedback (NS pseudo — dead rule on web, and vice versa) */
201
- .vx-pressable:pressed {
202
- opacity: 0.85;
203
- }
201
+ /* web press feedback */
202
+ .vx-pressable:active {
203
+ opacity: 0.85;
204
+ }
205
+ /* native press feedback (NS pseudo — dead rule on web, and vice versa) */
206
+ .vx-pressable:pressed {
207
+ opacity: 0.85;
208
+ }
204
209
 
205
- /* xplat-web-only:start — overlays/toasts/popovers render through RootLayout
210
+ /* xplat-web-only:start — overlays/toasts/popovers render through RootLayout
206
211
  or showModal on native; these portal/positioning rules are web-only */
207
- .vx-overlay {
208
- z-index: 1000;
209
- }
212
+ .vx-overlay {
213
+ z-index: 1000;
214
+ }
210
215
 
211
- .vx-overlay-shade {
212
- position: absolute;
213
- inset: 0;
214
- background-color: rgba(0, 0, 0, 0.4);
215
- }
216
+ .vx-overlay-shade {
217
+ position: absolute;
218
+ inset: 0;
219
+ background-color: rgba(0, 0, 0, 0.4);
220
+ }
216
221
 
217
- .vx-overlay-content {
218
- position: relative;
219
- z-index: 1;
220
- }
222
+ .vx-overlay-content {
223
+ position: relative;
224
+ z-index: 1;
225
+ }
221
226
 
222
- .vx-popover {
223
- z-index: 1001;
224
- }
227
+ .vx-popover {
228
+ z-index: 1001;
229
+ }
225
230
 
226
- .vx-popover-backdrop {
227
- background-color: transparent;
228
- }
231
+ .vx-popover-backdrop {
232
+ background-color: transparent;
233
+ }
229
234
 
230
- .vx-tooltip {
231
- padding: 4px 8px;
232
- border-width: 1px;
233
- border-color: var(--color-border);
234
- border-radius: var(--radius-sm);
235
- corner-shape: squircle;
236
- background-color: var(--color-surface);
237
- color: var(--color-text);
238
- font-size: 12px;
239
- line-height: 16px;
240
- }
235
+ .vx-tooltip {
236
+ padding: 4px 8px;
237
+ border-width: 1px;
238
+ border-color: var(--color-border);
239
+ border-radius: var(--radius-sm);
240
+ corner-shape: squircle;
241
+ background-color: var(--color-surface);
242
+ color: var(--color-text);
243
+ font-size: 12px;
244
+ line-height: 16px;
245
+ }
241
246
 
242
- .vx-toast-host {
243
- position: fixed;
244
- z-index: 1002;
245
- left: 0;
246
- right: 0;
247
- display: flex;
248
- justify-content: center;
249
- pointer-events: none;
250
- }
247
+ .vx-toast-host {
248
+ position: fixed;
249
+ z-index: 1002;
250
+ left: 0;
251
+ right: 0;
252
+ display: flex;
253
+ justify-content: center;
254
+ pointer-events: none;
255
+ }
251
256
 
252
- .vx-toast-top {
253
- top: 24px;
254
- }
255
- .vx-toast-bottom {
256
- bottom: 24px;
257
- }
257
+ .vx-toast-top {
258
+ top: 24px;
259
+ }
260
+ .vx-toast-bottom {
261
+ bottom: 24px;
262
+ }
258
263
 
259
- .vx-toast {
260
- pointer-events: auto;
261
- }
262
- /* xplat-web-only:end */
264
+ .vx-toast {
265
+ pointer-events: auto;
266
+ }
267
+ /* xplat-web-only:end */
263
268
 
264
- /* Normalized OS-widget reset — TextInput/TextArea are real
269
+ /* Normalized OS-widget reset — TextInput/TextArea are real
265
270
  <input>/<textarea>/TextField/TextView; the leaves emit vx-input/
266
271
  vx-textarea unconditionally so the platform chrome is stripped before
267
272
  app styles apply. On Android the transparent background replaces the
268
273
  default EditText drawable (kills the Material underline); zero padding
269
274
  removes native insets. Legitimate semantics — IME, secure entry,
270
275
  selection, autofill, caret, focus — stay platform-native by design. */
271
- .vx-input,
272
- .vx-textarea {
273
- padding: 0;
274
- margin: 0;
275
- border-width: 0;
276
- background-color: transparent;
277
- color: var(--color-text);
278
- font-size: 14px;
279
- /* NS placeholder color; web uses the ::placeholder pseudo below. */
280
- placeholder-color: var(--color-text-secondary);
281
- }
282
- /* xplat-web-only:start — DOM-only reset: element UA chrome and the
276
+ .vx-input,
277
+ .vx-textarea {
278
+ padding: 0;
279
+ margin: 0;
280
+ border-width: 0;
281
+ background-color: transparent;
282
+ color: var(--color-text);
283
+ font-size: 14px;
284
+ /* NS placeholder color; web uses the ::placeholder pseudo below. */
285
+ placeholder-color: var(--color-text-secondary);
286
+ }
287
+ /* xplat-web-only:start — DOM-only reset: element UA chrome and the
283
288
  focus outline (normalized to the same thin ring on both). */
284
- .vx-input,
285
- .vx-textarea {
286
- appearance: none;
287
- -webkit-appearance: none;
288
- outline: none;
289
- }
290
- .vx-input:focus,
291
- .vx-textarea:focus {
292
- outline: 1px solid var(--color-primary);
293
- outline-offset: -1px;
294
- }
295
- /* xplat-web-only:end */
289
+ .vx-input,
290
+ .vx-textarea {
291
+ appearance: none;
292
+ -webkit-appearance: none;
293
+ outline: none;
294
+ }
295
+ .vx-input:focus,
296
+ .vx-textarea:focus {
297
+ outline: 1px solid var(--color-primary);
298
+ outline-offset: -1px;
299
+ }
300
+ /* xplat-web-only:end */
296
301
 
297
- /* auto-grow owns height — the manual resize handle has no native analog */
298
- /* xplat-web-only:start */
299
- .vx-textarea {
300
- resize: none;
301
- }
302
- /* xplat-web-only:end */
302
+ /* auto-grow owns height — the manual resize handle has no native analog */
303
+ /* xplat-web-only:start */
304
+ .vx-textarea {
305
+ resize: none;
306
+ }
307
+ /* xplat-web-only:end */
303
308
 
304
- /* WebView chrome reset — the web leaf is a real <iframe>: strip the UA's
309
+ /* WebView chrome reset — the web leaf is a real <iframe>: strip the UA's
305
310
  inset border and the inline-element baseline gap. WKWebView and
306
311
  android.webkit.WebView ship chrome-free, so there is no native reset. */
307
- /* xplat-web-only:start */
308
- .vx-webview {
309
- display: block;
310
- border-width: 0;
311
- }
312
- /* xplat-web-only:end */
312
+ /* xplat-web-only:start */
313
+ .vx-webview {
314
+ display: block;
315
+ border-width: 0;
316
+ }
317
+ /* xplat-web-only:end */
313
318
 
314
- /* Video — surface-hosted leaf: the engine surface fills the frame and the
319
+ /* Video — surface-hosted leaf: the engine surface fills the frame and the
315
320
  self-drawn transport layers stack on top in the same grid cell
316
321
  (vx-stack). NS-side rules only — DOM resets sit in the web block. */
317
- .vx-video {
318
- background-color: #000;
319
- }
322
+ .vx-video {
323
+ background-color: #000;
324
+ }
320
325
 
321
- .vx-video > * {
322
- horizontal-align: stretch;
323
- vertical-align: stretch;
324
- }
326
+ .vx-video > * {
327
+ horizontal-align: stretch;
328
+ vertical-align: stretch;
329
+ }
325
330
 
326
- .vx-video-centerbtn {
327
- width: 56px;
328
- height: 56px;
329
- border-radius: 28px;
330
- background-color: rgba(0, 0, 0, 0.55);
331
- align-items: center;
332
- justify-content: center;
333
- }
331
+ .vx-video-centerbtn {
332
+ width: 56px;
333
+ height: 56px;
334
+ border-radius: 28px;
335
+ background-color: rgba(0, 0, 0, 0.55);
336
+ align-items: center;
337
+ justify-content: center;
338
+ }
334
339
 
335
- .vx-video-bar {
336
- background-color: rgba(0, 0, 0, 0.45);
337
- padding: 4px 8px;
338
- }
340
+ .vx-video-bar {
341
+ background-color: rgba(0, 0, 0, 0.45);
342
+ padding: 4px 8px;
343
+ }
339
344
 
340
- .vx-video-btn {
341
- padding: 4px;
342
- }
345
+ .vx-video-btn {
346
+ padding: 4px;
347
+ }
343
348
 
344
- /* Full-frame tap layer — hit target only, never paints. */
345
- .vx-video-tap {
346
- background-color: transparent;
347
- }
349
+ /* Full-frame tap layer — hit target only, never paints. */
350
+ .vx-video-tap {
351
+ background-color: transparent;
352
+ }
348
353
 
349
- .vx-video-time {
350
- color: #fff;
351
- font-size: 11px;
352
- min-width: 34px;
353
- text-align: center;
354
- }
354
+ .vx-video-time {
355
+ color: #fff;
356
+ font-size: 11px;
357
+ min-width: 34px;
358
+ text-align: center;
359
+ }
355
360
 
356
- /* xplat-web-only:start — the <video> element is inline and overlays would
361
+ /* xplat-web-only:start — the <video> element is inline and overlays would
357
362
  otherwise swallow pointer events; native layers are hit-transparent by
358
363
  default, so none of this exists on NS. */
359
- .vx-video-surface {
360
- display: block;
361
- width: 100%;
362
- height: 100%;
363
- }
364
+ .vx-video-surface {
365
+ display: block;
366
+ width: 100%;
367
+ height: 100%;
368
+ }
364
369
 
365
- .vx-video-poster,
366
- .vx-video-barwrap {
367
- pointer-events: none;
368
- }
370
+ .vx-video-poster,
371
+ .vx-video-barwrap {
372
+ pointer-events: none;
373
+ }
369
374
 
370
- .vx-video-barwrap > * {
371
- pointer-events: auto;
372
- }
375
+ .vx-video-barwrap > * {
376
+ pointer-events: auto;
377
+ }
373
378
 
374
- .vx-video-poster {
375
- object-fit: cover;
376
- }
379
+ .vx-video-poster {
380
+ object-fit: cover;
381
+ }
377
382
 
378
- /* xplat-web-only:end */
383
+ /* xplat-web-only:end */
379
384
 
380
- /* xplat-web-only:start */
381
- /* Modal ROUTES (pushRoute presentation:'modal') — the URL stays underneath,
385
+ /* xplat-web-only:start */
386
+ /* Modal ROUTES (pushRoute presentation:'modal') — the URL stays underneath,
382
387
  so the overlay is a fixed layer over the shell rather than a dialog. */
383
- .vx-modalroute {
384
- position: fixed;
385
- inset: 0;
386
- z-index: 1000;
387
- display: flex;
388
- align-items: stretch;
389
- justify-content: stretch;
390
- }
388
+ .vx-modalroute {
389
+ position: fixed;
390
+ inset: 0;
391
+ z-index: 1000;
392
+ display: flex;
393
+ align-items: stretch;
394
+ justify-content: stretch;
395
+ }
391
396
 
392
- .vx-modalroute-shade {
393
- position: absolute;
394
- inset: 0;
395
- border: 0;
396
- padding: 0;
397
- background: rgba(0, 0, 0, 0.4);
398
- cursor: default;
399
- }
397
+ .vx-modalroute-shade {
398
+ position: absolute;
399
+ inset: 0;
400
+ border: 0;
401
+ padding: 0;
402
+ background: rgba(0, 0, 0, 0.4);
403
+ cursor: default;
404
+ }
400
405
 
401
- .vx-modalroute-body {
402
- position: relative;
403
- flex: 1;
404
- min-width: 0;
405
- overflow: auto;
406
- color: var(--color-text);
407
- background-color: var(--color-surface);
408
- }
409
- /* xplat-web-only:end */
406
+ .vx-modalroute-body {
407
+ position: relative;
408
+ flex: 1;
409
+ min-width: 0;
410
+ overflow: auto;
411
+ color: var(--color-text);
412
+ background-color: var(--color-surface);
413
+ }
414
+ /* xplat-web-only:end */
410
415
 
411
- /* xplat-web-only:start — placeholder pseudos are DOM-only. Muted like
416
+ /* xplat-web-only:start — placeholder pseudos are DOM-only. Muted like
412
417
  real placeholder text, not currentColor (a full-opacity hint reads as
413
418
  a typed value); --placeholder-color is the per-field prop escape. */
414
- input::placeholder,
415
- textarea::placeholder {
416
- color: var(--placeholder-color, var(--color-text-secondary));
417
- }
418
- /* xplat-web-only:end */
419
+ input::placeholder,
420
+ textarea::placeholder {
421
+ color: var(--placeholder-color, var(--color-text-secondary));
422
+ }
423
+ /* xplat-web-only:end */
419
424
 
420
- /* xplat-web-only:start — form controls don't inherit the body font in the
425
+ /* xplat-web-only:start — form controls don't inherit the body font in the
421
426
  DOM (UA stylesheet wins); without this they render in the UA face. */
422
- input,
423
- textarea,
424
- select,
425
- button {
426
- font-family: inherit;
427
- }
428
- /* xplat-web-only:end */
427
+ input,
428
+ textarea,
429
+ select,
430
+ button {
431
+ font-family: inherit;
432
+ }
433
+ /* xplat-web-only:end */
429
434
 
430
- /* Self-drawn spinner — the border ring is shared (native rotates the
435
+ /* Self-drawn spinner — the border ring is shared (native rotates the
431
436
  view via `animate`; DOM spins via the keyframes below). */
432
- .vx-spinner {
433
- width: 24px;
434
- height: 24px;
435
- border-width: 2px;
436
- border-color: var(--color-text-secondary);
437
- border-right-color: transparent;
438
- border-radius: 50%;
439
- }
440
- /* Pull-to-refresh — the overlay strip hosting the spinner. Its height is
437
+ .vx-spinner {
438
+ width: 24px;
439
+ height: 24px;
440
+ border-width: 2px;
441
+ border-color: var(--color-text-secondary);
442
+ border-right-color: transparent;
443
+ border-radius: 50%;
444
+ }
445
+ /* Pull-to-refresh — the overlay strip hosting the spinner. Its height is
441
446
  the default `refreshThreshold` (64px web / 64dip native); the leaf
442
447
  translates it in/out — reveal math lives in refresh.*.ts. */
443
- .vx-refresh {
444
- display: flex;
445
- flex-direction: column;
446
- align-items: center;
447
- justify-content: center;
448
- height: 64px;
449
- }
450
- /* xplat-web-only:start — the DOM leaf needs explicit clipping plus the
448
+ .vx-refresh {
449
+ display: flex;
450
+ flex-direction: column;
451
+ align-items: center;
452
+ justify-content: center;
453
+ height: 64px;
454
+ }
455
+ /* xplat-web-only:start — the DOM leaf needs explicit clipping plus the
451
456
  absolute overlay; native clips via the wrapper grid
452
457
  (clipsToBounds/ViewGroup) and stacks children in one cell. */
453
- .vx-refresh-wrap {
454
- position: relative;
455
- overflow: hidden;
456
- display: flex;
457
- flex-direction: column;
458
- }
459
- .vx-refresh-wrap > .vx-scroll {
460
- flex: 1;
461
- min-height: 0;
462
- }
463
- .vx-refresh {
464
- position: absolute;
465
- top: 0;
466
- left: 0;
467
- right: 0;
468
- }
469
- /* xplat-web-only:end */
470
- /* xplat-web-only:start — DOM needs an inline-block box + CSS animation;
458
+ .vx-refresh-wrap {
459
+ position: relative;
460
+ overflow: hidden;
461
+ display: flex;
462
+ flex-direction: column;
463
+ }
464
+ .vx-refresh-wrap > .vx-scroll {
465
+ flex: 1;
466
+ min-height: 0;
467
+ }
468
+ .vx-refresh {
469
+ position: absolute;
470
+ top: 0;
471
+ left: 0;
472
+ right: 0;
473
+ }
474
+ /* xplat-web-only:end */
475
+ /* xplat-web-only:start — DOM needs an inline-block box + CSS animation;
471
476
  the native leaf drives view.animate instead */
472
- .vx-spinner {
473
- display: inline-block;
474
- box-sizing: border-box;
475
- animation: vx-spinner-turn 0.8s linear infinite;
476
- }
477
+ .vx-spinner {
478
+ display: inline-block;
479
+ box-sizing: border-box;
480
+ animation: vx-spinner-turn 0.8s linear infinite;
481
+ }
477
482
 
478
- @keyframes vx-spinner-turn {
479
- to {
480
- transform: rotate(360deg);
483
+ @keyframes vx-spinner-turn {
484
+ to {
485
+ transform: rotate(360deg);
486
+ }
481
487
  }
482
- }
483
- /* xplat-web-only:end */
488
+ /* xplat-web-only:end */
484
489
 
485
- /* Self-drawn switch — flex track + thumb. `--on` flips justify-content,
490
+ /* Self-drawn switch — flex track + thumb. `--on` flips justify-content,
486
491
  which slides the thumb; identical geometry on web and native. */
487
- .vx-switch {
488
- width: 48px;
489
- height: 28px;
490
- padding-left: 3px;
491
- padding-right: 3px;
492
- border-radius: 14px;
493
- background-color: var(--color-border);
494
- justify-content: flex-start;
495
- align-items: center;
496
- }
497
- .vx-switch--on {
498
- background-color: var(--color-primary);
499
- justify-content: flex-end;
500
- }
501
- .vx-switch-thumb {
502
- width: 22px;
503
- height: 22px;
504
- border-radius: 50%;
505
- background-color: var(--color-surface);
506
- }
507
- /* xplat-web-only:start — the DOM host is a div; flex + cursor live here */
508
- .vx-switch {
509
- display: flex;
510
- cursor: pointer;
511
- }
512
- /* xplat-web-only:end */
492
+ .vx-switch {
493
+ width: 48px;
494
+ height: 28px;
495
+ padding-left: 3px;
496
+ padding-right: 3px;
497
+ border-radius: 14px;
498
+ background-color: var(--color-border);
499
+ justify-content: flex-start;
500
+ align-items: center;
501
+ }
502
+ .vx-switch--on {
503
+ background-color: var(--color-primary);
504
+ justify-content: flex-end;
505
+ }
506
+ .vx-switch-thumb {
507
+ width: 22px;
508
+ height: 22px;
509
+ border-radius: 50%;
510
+ background-color: var(--color-surface);
511
+ }
512
+ /* xplat-web-only:start — the DOM host is a div; flex + cursor live here */
513
+ .vx-switch {
514
+ display: flex;
515
+ cursor: pointer;
516
+ }
517
+ /* xplat-web-only:end */
513
518
 
514
- /* Self-drawn slider — host is a grid (native) / relative div (web);
519
+ /* Self-drawn slider — host is a grid (native) / relative div (web);
515
520
  track + fill + thumb overlay the same box. */
516
- .vx-slider {
517
- height: 28px;
518
- }
519
- .vx-slider-track {
520
- height: 4px;
521
- border-radius: 2px;
522
- background-color: var(--color-border);
523
- vertical-align: middle;
524
- }
525
- .vx-slider-fill {
526
- height: 4px;
527
- border-radius: 2px;
528
- background-color: var(--color-primary);
529
- vertical-align: middle;
530
- }
531
- .vx-slider-thumb {
532
- width: 20px;
533
- height: 20px;
534
- border-radius: 50%;
535
- background-color: var(--color-primary);
536
- vertical-align: middle;
537
- }
538
- /* xplat-web-only:start — absolute positioning is DOM-only; native grid
521
+ .vx-slider {
522
+ height: 28px;
523
+ }
524
+ .vx-slider-track {
525
+ height: 4px;
526
+ border-radius: 2px;
527
+ background-color: var(--color-border);
528
+ vertical-align: middle;
529
+ }
530
+ .vx-slider-fill {
531
+ height: 4px;
532
+ border-radius: 2px;
533
+ background-color: var(--color-primary);
534
+ vertical-align: middle;
535
+ }
536
+ .vx-slider-thumb {
537
+ width: 20px;
538
+ height: 20px;
539
+ border-radius: 50%;
540
+ background-color: var(--color-primary);
541
+ vertical-align: middle;
542
+ }
543
+ /* xplat-web-only:start — absolute positioning is DOM-only; native grid
539
544
  children center via vertical-align and pin via margin-left. */
540
- .vx-slider {
541
- position: relative;
542
- }
543
- .vx-slider-track,
544
- .vx-slider-fill {
545
- position: absolute;
546
- top: 50%;
547
- left: 0;
548
- right: 0;
549
- transform: translateY(-50%);
550
- }
551
- .vx-slider-thumb {
552
- position: absolute;
553
- top: 50%;
554
- transform: translate(-50%, -50%);
555
- }
556
- /* xplat-web-only:end */
545
+ .vx-slider {
546
+ position: relative;
547
+ }
548
+ .vx-slider-track,
549
+ .vx-slider-fill {
550
+ position: absolute;
551
+ top: 50%;
552
+ left: 0;
553
+ right: 0;
554
+ transform: translateY(-50%);
555
+ }
556
+ .vx-slider-thumb {
557
+ position: absolute;
558
+ top: 50%;
559
+ transform: translate(-50%, -50%);
560
+ }
561
+ /* xplat-web-only:end */
557
562
 
558
- /* xplat-web-only:start — Pager's web leaf is a mandatory scroll-snap row:
563
+ /* xplat-web-only:start — Pager's web leaf is a mandatory scroll-snap row:
559
564
  one page per item at 100% host width so scrollLeft / clientWidth is a
560
565
  clean page index. Native pages are sized by the widget itself, so the
561
566
  whole block is DOM-only. Scrollbar hidden (native has none); contain
562
567
  keeps the swipe from chaining to a parent scroller, matching .vx-scroll. */
563
- .vx-pager {
564
- display: flex;
565
- flex-direction: row;
566
- overflow-x: auto;
567
- overflow-y: hidden;
568
- scroll-snap-type: x mandatory;
569
- overscroll-behavior-x: contain;
570
- scrollbar-width: none;
571
- touch-action: pan-x;
572
- }
573
- .vx-pager::-webkit-scrollbar {
574
- display: none;
575
- }
576
- .vx-pager-page {
577
- flex: 0 0 100%;
578
- min-width: 0;
579
- display: flex;
580
- flex-direction: column;
581
- scroll-snap-align: start;
582
- }
583
- /* xplat-web-only:end */
568
+ .vx-pager {
569
+ display: flex;
570
+ flex-direction: row;
571
+ overflow-x: auto;
572
+ overflow-y: hidden;
573
+ scroll-snap-type: x mandatory;
574
+ overscroll-behavior-x: contain;
575
+ scrollbar-width: none;
576
+ touch-action: pan-x;
577
+ }
578
+ .vx-pager::-webkit-scrollbar {
579
+ display: none;
580
+ }
581
+ .vx-pager-page {
582
+ flex: 0 0 100%;
583
+ min-width: 0;
584
+ display: flex;
585
+ flex-direction: column;
586
+ scroll-snap-align: start;
587
+ }
588
+ /* xplat-web-only:end */
584
589
 
585
- /* Phase-1 self-drawn components — functional geometry + token chrome only;
590
+ /* Phase-1 self-drawn components — functional geometry + token chrome only;
586
591
  visual design is the app's job. */
587
592
 
588
- /* Button — row-direction pressable (vx-pressable already supplies display
593
+ /* Button — row-direction pressable (vx-pressable already supplies display
589
594
  and press feedback). */
590
- .vx-button {
591
- flex-direction: row;
592
- align-items: center;
593
- justify-content: center;
594
- }
595
+ .vx-button {
596
+ flex-direction: row;
597
+ align-items: center;
598
+ justify-content: center;
599
+ }
595
600
 
596
- /* Collapsible/Accordion — column stacks. */
597
- .vx-collapsible,
598
- .vx-collapsible-content,
599
- .vx-accordion,
600
- .vx-accordion-item {
601
- flex-direction: column;
602
- }
601
+ /* Collapsible/Accordion — column stacks. */
602
+ .vx-collapsible,
603
+ .vx-collapsible-content,
604
+ .vx-accordion,
605
+ .vx-accordion-item {
606
+ flex-direction: column;
607
+ }
603
608
 
604
- /* Checkbox — 20px box + check glyph; --on fills with primary. */
605
- .vx-checkbox-row {
606
- flex-direction: row;
607
- align-items: center;
608
- }
609
- .vx-checkbox {
610
- width: 20px;
611
- height: 20px;
612
- border-width: 1px;
613
- border-color: var(--color-border);
614
- border-radius: var(--radius-sm);
615
- align-items: center;
616
- justify-content: center;
617
- }
618
- .vx-checkbox--on .vx-checkbox {
619
- background-color: var(--color-primary);
620
- border-color: var(--color-primary);
621
- }
622
- .vx-checkbox-check {
623
- font-size: 14px;
624
- line-height: 20px;
625
- color: var(--color-onprimary);
626
- }
627
- .vx-checkbox--on .vx-checkbox-check {
628
- color: var(--color-onprimary);
629
- }
630
- .vx-checkbox-label {
631
- margin-left: 8px;
632
- }
633
- .vx-checkbox--disabled {
634
- opacity: 0.5;
635
- }
609
+ /* Checkbox — 20px box + check glyph; --on fills with primary. */
610
+ .vx-checkbox-row {
611
+ flex-direction: row;
612
+ align-items: center;
613
+ }
614
+ .vx-checkbox {
615
+ width: 20px;
616
+ height: 20px;
617
+ border-width: 1px;
618
+ border-color: var(--color-border);
619
+ border-radius: var(--radius-sm);
620
+ align-items: center;
621
+ justify-content: center;
622
+ }
623
+ .vx-checkbox--on .vx-checkbox {
624
+ background-color: var(--color-primary);
625
+ border-color: var(--color-primary);
626
+ }
627
+ .vx-checkbox-check {
628
+ font-size: 14px;
629
+ line-height: 20px;
630
+ color: var(--color-onprimary);
631
+ }
632
+ .vx-checkbox--on .vx-checkbox-check {
633
+ color: var(--color-onprimary);
634
+ }
635
+ .vx-checkbox-label {
636
+ margin-left: 8px;
637
+ }
638
+ .vx-checkbox--disabled {
639
+ opacity: 0.5;
640
+ }
636
641
 
637
- /* Radio group — column/row of dot options. */
638
- .vx-radio-group {
639
- flex-direction: column;
640
- }
641
- .vx-radio-group--row {
642
- flex-direction: row;
643
- }
644
- .vx-radio-row {
645
- flex-direction: row;
646
- align-items: center;
647
- }
648
- .vx-radio {
649
- width: 18px;
650
- height: 18px;
651
- border-width: 1px;
652
- border-color: var(--color-border);
653
- border-radius: 50%;
654
- align-items: center;
655
- justify-content: center;
656
- }
657
- .vx-radio--on .vx-radio {
658
- border-color: var(--color-primary);
659
- }
660
- .vx-radio-dot {
661
- width: 8px;
662
- height: 8px;
663
- border-radius: 50%;
664
- background-color: var(--color-primary);
665
- }
666
- .vx-radio-label {
667
- margin-left: 8px;
668
- }
669
- .vx-radio--disabled {
670
- opacity: 0.5;
671
- }
642
+ /* Radio group — column/row of dot options. */
643
+ .vx-radio-group {
644
+ flex-direction: column;
645
+ }
646
+ .vx-radio-group--row {
647
+ flex-direction: row;
648
+ }
649
+ .vx-radio-row {
650
+ flex-direction: row;
651
+ align-items: center;
652
+ }
653
+ .vx-radio {
654
+ width: 18px;
655
+ height: 18px;
656
+ border-width: 1px;
657
+ border-color: var(--color-border);
658
+ border-radius: 50%;
659
+ align-items: center;
660
+ justify-content: center;
661
+ }
662
+ .vx-radio--on .vx-radio {
663
+ border-color: var(--color-primary);
664
+ }
665
+ .vx-radio-dot {
666
+ width: 8px;
667
+ height: 8px;
668
+ border-radius: 50%;
669
+ background-color: var(--color-primary);
670
+ }
671
+ .vx-radio-label {
672
+ margin-left: 8px;
673
+ }
674
+ .vx-radio--disabled {
675
+ opacity: 0.5;
676
+ }
672
677
 
673
- /* SegmentedControl — equal-width segments in a rounded track; `--on`
678
+ /* SegmentedControl — equal-width segments in a rounded track; `--on`
674
679
  raises the selected one. flex-basis is dead on NS (the shorthand parser
675
680
  drops it and the standalone property isn't registered), so width:0 +
676
681
  flex-grow:1 is what distributes segments equally on both engines. */
677
- .vx-segmented {
678
- flex-direction: row;
679
- padding: 3px;
680
- border-width: 1px;
681
- border-color: var(--color-border);
682
- border-radius: var(--radius-md);
683
- corner-shape: squircle;
684
- background-color: var(--color-surface-secondary);
685
- color: var(--color-text);
686
- }
687
- .vx-segment {
688
- flex-grow: 1;
689
- width: 0;
690
- flex-direction: row;
691
- align-items: center;
692
- justify-content: center;
693
- padding-top: 6px;
694
- padding-bottom: 6px;
695
- padding-left: 10px;
696
- padding-right: 10px;
697
- border-radius: var(--radius-sm);
698
- }
699
- .vx-segment--on {
700
- background-color: var(--color-surface);
701
- }
702
- .vx-segment-label {
703
- font-size: 13px;
704
- color: var(--color-text-secondary);
705
- }
706
- .vx-segment--on .vx-segment-label {
707
- color: var(--color-text);
708
- font-weight: 600;
709
- }
710
- .vx-segmented--disabled,
711
- .vx-segment--disabled {
712
- opacity: 0.5;
713
- }
714
- /* xplat-web-only:start — the DOM hosts are divs; flex + cursor live here */
715
- .vx-segmented {
716
- display: flex;
717
- }
718
- .vx-segment {
719
- display: flex;
720
- cursor: pointer;
721
- user-select: none;
722
- }
723
- .vx-segment:focus {
724
- outline: 1px solid var(--color-primary);
725
- outline-offset: -1px;
726
- }
727
- .vx-segment--disabled {
728
- cursor: default;
729
- }
730
- /* xplat-web-only:end */
682
+ .vx-segmented {
683
+ flex-direction: row;
684
+ padding: 3px;
685
+ border-width: 1px;
686
+ border-color: var(--color-border);
687
+ border-radius: var(--radius-md);
688
+ corner-shape: squircle;
689
+ background-color: var(--color-surface-secondary);
690
+ color: var(--color-text);
691
+ }
692
+ .vx-segment {
693
+ flex-grow: 1;
694
+ width: 0;
695
+ flex-direction: row;
696
+ align-items: center;
697
+ justify-content: center;
698
+ padding-top: 6px;
699
+ padding-bottom: 6px;
700
+ padding-left: 10px;
701
+ padding-right: 10px;
702
+ border-radius: var(--radius-sm);
703
+ }
704
+ .vx-segment--on {
705
+ background-color: var(--color-surface);
706
+ }
707
+ .vx-segment-label {
708
+ font-size: 13px;
709
+ color: var(--color-text-secondary);
710
+ }
711
+ .vx-segment--on .vx-segment-label {
712
+ color: var(--color-text);
713
+ font-weight: 600;
714
+ }
715
+ .vx-segmented--disabled,
716
+ .vx-segment--disabled {
717
+ opacity: 0.5;
718
+ }
719
+ /* xplat-web-only:start — the DOM hosts are divs; flex + cursor live here */
720
+ .vx-segmented {
721
+ display: flex;
722
+ }
723
+ .vx-segment {
724
+ display: flex;
725
+ cursor: pointer;
726
+ user-select: none;
727
+ }
728
+ .vx-segment:focus {
729
+ outline: 1px solid var(--color-primary);
730
+ outline-offset: -1px;
731
+ }
732
+ .vx-segment--disabled {
733
+ cursor: default;
734
+ }
735
+ /* xplat-web-only:end */
731
736
 
732
- /* SearchInput — chrome-reset field: bordered shell, leading glyph, the
737
+ /* SearchInput — chrome-reset field: bordered shell, leading glyph, the
733
738
  field owns the remaining width, self-drawn clear. The web leaf hides the
734
739
  engine's own search-cancel button so native's (hand-drawn) matches. */
735
- .vx-search {
736
- flex-direction: row;
737
- align-items: center;
738
- padding-top: 6px;
739
- padding-bottom: 6px;
740
- padding-left: 10px;
741
- padding-right: 6px;
742
- border-width: 1px;
743
- border-color: var(--color-border);
744
- border-radius: var(--radius-md);
745
- corner-shape: squircle;
746
- background-color: var(--color-surface);
747
- color: var(--color-text);
748
- }
749
- .vx-search-icon {
750
- color: var(--color-text-secondary);
751
- }
752
- .vx-search-field {
753
- flex-grow: 1;
754
- padding-left: 8px;
755
- padding-right: 8px;
756
- font-size: 14px;
757
- color: var(--color-text);
758
- }
759
- .vx-search-clear {
760
- width: 24px;
761
- height: 24px;
762
- border-radius: 9999px;
763
- align-items: center;
764
- justify-content: center;
765
- background-color: var(--color-surface-secondary);
766
- color: var(--color-text-secondary);
767
- }
768
- .vx-search--disabled {
769
- opacity: 0.5;
770
- }
771
- /* xplat-web-only:start — the DOM host/field/buttons need display + the UA
740
+ .vx-search {
741
+ flex-direction: row;
742
+ align-items: center;
743
+ padding-top: 6px;
744
+ padding-bottom: 6px;
745
+ padding-left: 10px;
746
+ padding-right: 6px;
747
+ border-width: 1px;
748
+ border-color: var(--color-border);
749
+ border-radius: var(--radius-md);
750
+ corner-shape: squircle;
751
+ background-color: var(--color-surface);
752
+ color: var(--color-text);
753
+ }
754
+ .vx-search-icon {
755
+ color: var(--color-text-secondary);
756
+ }
757
+ .vx-search-field {
758
+ flex-grow: 1;
759
+ padding-left: 8px;
760
+ padding-right: 8px;
761
+ font-size: 14px;
762
+ color: var(--color-text);
763
+ }
764
+ .vx-search-clear {
765
+ width: 24px;
766
+ height: 24px;
767
+ border-radius: 9999px;
768
+ align-items: center;
769
+ justify-content: center;
770
+ background-color: var(--color-surface-secondary);
771
+ color: var(--color-text-secondary);
772
+ }
773
+ .vx-search--disabled {
774
+ opacity: 0.5;
775
+ }
776
+ /* xplat-web-only:start — the DOM host/field/buttons need display + the UA
772
777
  reset; native's flexbox children carry these from the class already. */
773
- .vx-search {
774
- display: flex;
775
- }
776
- .vx-search-icon {
777
- display: inline-flex;
778
- flex: none;
779
- }
780
- .vx-search-field {
781
- min-width: 0;
782
- border: 0;
783
- background: none;
784
- outline: none;
785
- appearance: none;
786
- -webkit-appearance: none;
787
- }
788
- input.vx-search-field::-webkit-search-cancel-button {
789
- display: none;
790
- }
791
- .vx-search-clear {
792
- display: inline-flex;
793
- border: 0;
794
- padding: 0;
795
- cursor: pointer;
796
- }
797
- /* xplat-web-only:end */
798
-
799
- /* Anchored menus — panel + item rows (the Popover leaf positions it). */
800
- .vx-menu {
801
- min-width: 140px;
802
- padding: 4px;
803
- border-width: 1px;
804
- border-color: var(--color-border);
805
- border-radius: var(--radius-md);
806
- corner-shape: squircle;
807
- background-color: var(--color-surface);
808
- color: var(--color-text);
809
- }
810
- .vx-menu-list {
811
- flex-direction: column;
812
- }
813
- .vx-menu-item {
814
- flex-direction: row;
815
- align-items: center;
816
- padding: 6px 10px;
817
- border-radius: var(--radius-sm);
818
- }
819
- .vx-menu-item--disabled {
820
- opacity: 0.5;
821
- }
822
- /* xplat-web-only:start — hover affordance and cursor are DOM-only */
823
- .vx-menu-item {
824
- cursor: pointer;
825
- }
826
- .vx-menu-item:hover {
827
- background-color: var(--color-border);
828
- }
829
- /* xplat-web-only:end */
830
-
831
- /* Badge — inline chip row. */
832
- .vx-badge {
833
- flex-direction: row;
834
- align-items: center;
835
- border-radius: var(--radius-sm);
836
- padding: 1px 6px;
837
- font-size: 12px;
838
- line-height: 16px;
839
- }
840
- /* xplat-web-only:start — span is inline by default; needs inline-flex */
841
- .vx-badge {
842
- display: inline-flex;
843
- }
844
- /* xplat-web-only:end */
778
+ .vx-search {
779
+ display: flex;
780
+ }
781
+ .vx-search-icon {
782
+ display: inline-flex;
783
+ flex: none;
784
+ }
785
+ .vx-search-field {
786
+ min-width: 0;
787
+ border: 0;
788
+ background: none;
789
+ outline: none;
790
+ appearance: none;
791
+ -webkit-appearance: none;
792
+ }
793
+ input.vx-search-field::-webkit-search-cancel-button {
794
+ display: none;
795
+ }
796
+ .vx-search-clear {
797
+ display: inline-flex;
798
+ border: 0;
799
+ padding: 0;
800
+ cursor: pointer;
801
+ }
802
+ /* xplat-web-only:end */
803
+
804
+ /* Anchored menus — panel + item rows (the Popover leaf positions it). */
805
+ .vx-menu {
806
+ min-width: 140px;
807
+ padding: 4px;
808
+ border-width: 1px;
809
+ border-color: var(--color-border);
810
+ border-radius: var(--radius-md);
811
+ corner-shape: squircle;
812
+ background-color: var(--color-surface);
813
+ color: var(--color-text);
814
+ }
815
+ .vx-menu-list {
816
+ flex-direction: column;
817
+ }
818
+ .vx-menu-item {
819
+ flex-direction: row;
820
+ align-items: center;
821
+ padding: 6px 10px;
822
+ border-radius: var(--radius-sm);
823
+ }
824
+ .vx-menu-item--disabled {
825
+ opacity: 0.5;
826
+ }
827
+ /* xplat-web-only:start — hover affordance and cursor are DOM-only */
828
+ .vx-menu-item {
829
+ cursor: pointer;
830
+ }
831
+ .vx-menu-item:hover {
832
+ background-color: var(--color-border);
833
+ }
834
+ /* xplat-web-only:end */
835
+
836
+ /* Badge — inline chip row. */
837
+ .vx-badge {
838
+ flex-direction: row;
839
+ align-items: center;
840
+ border-radius: var(--radius-sm);
841
+ padding: 1px 6px;
842
+ font-size: 12px;
843
+ line-height: 16px;
844
+ }
845
+ /* xplat-web-only:start — span is inline by default; needs inline-flex */
846
+ .vx-badge {
847
+ display: inline-flex;
848
+ }
849
+ /* xplat-web-only:end */
845
850
 
846
- /* Separator — hairline rule; --h spans width, --v spans height. */
847
- .vx-separator--h {
848
- height: 1px;
849
- background-color: var(--color-border);
850
- }
851
- .vx-separator--v {
852
- width: 1px;
853
- align-self: stretch;
854
- background-color: var(--color-border);
855
- }
851
+ /* Separator — hairline rule; --h spans width, --v spans height. */
852
+ .vx-separator--h {
853
+ height: 1px;
854
+ background-color: var(--color-border);
855
+ }
856
+ .vx-separator--v {
857
+ width: 1px;
858
+ align-self: stretch;
859
+ background-color: var(--color-border);
860
+ }
856
861
 
857
- /* Skeleton — placeholder block. */
858
- .vx-skeleton {
859
- background-color: var(--color-border);
860
- border-radius: var(--radius-md);
861
- corner-shape: squircle;
862
- }
862
+ /* Skeleton — placeholder block. */
863
+ .vx-skeleton {
864
+ background-color: var(--color-border);
865
+ border-radius: var(--radius-md);
866
+ corner-shape: squircle;
867
+ }
863
868
 
864
- /* Avatar — centered fallback inside the circle. */
865
- .vx-avatar {
866
- align-items: center;
867
- justify-content: center;
868
- background-color: var(--color-border);
869
- }
870
- .vx-avatar-group {
871
- flex-direction: row;
872
- align-items: center;
873
- }
874
- /* xplat-web-only:start — display:flex for the div-based flex classes */
875
- .vx-collapsible,
876
- .vx-collapsible-content,
877
- .vx-accordion,
878
- .vx-accordion-item,
879
- .vx-checkbox-row,
880
- .vx-checkbox,
881
- .vx-radio-group,
882
- .vx-radio-row,
883
- .vx-radio,
884
- .vx-menu-list,
885
- .vx-menu-item,
886
- .vx-contextmenu,
887
- .vx-avatar,
888
- .vx-avatar-group,
889
- .vx-avatar-group-item {
890
- display: flex;
891
- }
892
- .vx-checkbox-row,
893
- .vx-radio-row {
894
- cursor: pointer;
895
- }
896
- .vx-checkbox-check {
897
- display: block;
898
- }
899
- /* xplat-web-only:end */
869
+ /* Avatar — centered fallback inside the circle. */
870
+ .vx-avatar {
871
+ align-items: center;
872
+ justify-content: center;
873
+ background-color: var(--color-border);
874
+ }
875
+ .vx-avatar-group {
876
+ flex-direction: row;
877
+ align-items: center;
878
+ }
879
+ /* xplat-web-only:start — display:flex for the div-based flex classes */
880
+ .vx-collapsible,
881
+ .vx-collapsible-content,
882
+ .vx-accordion,
883
+ .vx-accordion-item,
884
+ .vx-checkbox-row,
885
+ .vx-checkbox,
886
+ .vx-radio-group,
887
+ .vx-radio-row,
888
+ .vx-radio,
889
+ .vx-menu-list,
890
+ .vx-menu-item,
891
+ .vx-contextmenu,
892
+ .vx-avatar,
893
+ .vx-avatar-group,
894
+ .vx-avatar-group-item {
895
+ display: flex;
896
+ }
897
+ .vx-checkbox-row,
898
+ .vx-radio-row {
899
+ cursor: pointer;
900
+ }
901
+ .vx-checkbox-check {
902
+ display: block;
903
+ }
904
+ /* xplat-web-only:end */
900
905
 
901
- /* Phase-2 form field layer. */
906
+ /* Phase-2 form field layer. */
902
907
 
903
- /* FormField — label / control / message column. */
904
- .vx-field {
905
- flex-direction: column;
906
- }
907
- .vx-field-label {
908
- font-size: 13px;
909
- line-height: 20px;
910
- font-weight: 500;
911
- margin-bottom: 4px;
912
- }
913
- .vx-field-required {
914
- color: var(--color-danger);
915
- }
916
- .vx-field-msg {
917
- font-size: 12px;
918
- line-height: 16px;
919
- margin-top: 4px;
920
- color: var(--color-text-secondary);
921
- }
922
- .vx-field-msg--error {
923
- color: var(--color-danger);
924
- }
925
- .vx-fieldgroup {
926
- flex-direction: column;
927
- }
928
- .vx-fieldgroup-label {
929
- font-size: 13px;
930
- line-height: 20px;
931
- font-weight: 600;
932
- margin-bottom: 8px;
933
- }
908
+ /* FormField — label / control / message column. */
909
+ .vx-field {
910
+ flex-direction: column;
911
+ }
912
+ .vx-field-label {
913
+ font-size: 13px;
914
+ line-height: 20px;
915
+ font-weight: 500;
916
+ margin-bottom: 4px;
917
+ }
918
+ .vx-field-required {
919
+ color: var(--color-danger);
920
+ }
921
+ .vx-field-msg {
922
+ font-size: 12px;
923
+ line-height: 16px;
924
+ margin-top: 4px;
925
+ color: var(--color-text-secondary);
926
+ }
927
+ .vx-field-msg--error {
928
+ color: var(--color-danger);
929
+ }
930
+ .vx-fieldgroup {
931
+ flex-direction: column;
932
+ }
933
+ .vx-fieldgroup-label {
934
+ font-size: 13px;
935
+ line-height: 20px;
936
+ font-weight: 600;
937
+ margin-bottom: 8px;
938
+ }
934
939
 
935
- /* ListItem — a reusable shared row; callers own the surrounding list chrome. */
936
- .vx-listitem {
937
- flex-direction: row;
938
- align-items: center;
939
- width: 100%;
940
- padding: 10px 12px;
941
- gap: 12px;
942
- }
943
- .vx-listitem-leading,
944
- .vx-listitem-trailing {
945
- flex-shrink: 0;
946
- align-items: center;
947
- }
948
- .vx-listitem-content {
949
- flex-direction: column;
950
- flex-grow: 1;
951
- }
952
- .vx-listitem-title {
953
- font-weight: 500;
954
- }
955
- .vx-listitem-supporting {
956
- font-size: 12px;
957
- line-height: 16px;
958
- color: var(--color-text-secondary);
959
- }
940
+ /* ListItem — a reusable shared row; callers own the surrounding list chrome. */
941
+ .vx-listitem {
942
+ flex-direction: row;
943
+ align-items: center;
944
+ width: 100%;
945
+ padding: 10px 12px;
946
+ gap: 12px;
947
+ }
948
+ .vx-listitem-leading,
949
+ .vx-listitem-trailing {
950
+ flex-shrink: 0;
951
+ align-items: center;
952
+ }
953
+ .vx-listitem-content {
954
+ flex-direction: column;
955
+ flex-grow: 1;
956
+ }
957
+ .vx-listitem-title {
958
+ font-weight: 500;
959
+ }
960
+ .vx-listitem-supporting {
961
+ font-size: 12px;
962
+ line-height: 16px;
963
+ color: var(--color-text-secondary);
964
+ }
960
965
 
961
- /* InputNumber — steppers flanking a centered field. */
962
- .vx-inputnumber {
963
- flex-direction: row;
964
- align-items: center;
965
- }
966
- .vx-inputnumber-step {
967
- padding: 4px 10px;
968
- border-width: 1px;
969
- border-color: var(--color-border);
970
- border-radius: var(--radius-sm);
971
- align-items: center;
972
- justify-content: center;
973
- }
974
- .vx-inputnumber-field {
975
- width: 64px;
976
- margin-left: 8px;
977
- margin-right: 8px;
978
- text-align: center;
979
- }
980
- .vx-inputnumber--disabled {
981
- opacity: 0.5;
982
- }
966
+ /* InputNumber — steppers flanking a centered field. */
967
+ .vx-inputnumber {
968
+ flex-direction: row;
969
+ align-items: center;
970
+ }
971
+ .vx-inputnumber-step {
972
+ padding: 4px 10px;
973
+ border-width: 1px;
974
+ border-color: var(--color-border);
975
+ border-radius: var(--radius-sm);
976
+ align-items: center;
977
+ justify-content: center;
978
+ }
979
+ .vx-inputnumber-field {
980
+ width: 64px;
981
+ margin-left: 8px;
982
+ margin-right: 8px;
983
+ text-align: center;
984
+ }
985
+ .vx-inputnumber--disabled {
986
+ opacity: 0.5;
987
+ }
983
988
 
984
- /* PinInput — row of single cells. */
985
- .vx-pininput {
986
- flex-direction: row;
987
- }
988
- .vx-pininput-cell {
989
- width: 40px;
990
- margin-right: 8px;
991
- text-align: center;
992
- }
993
- .vx-pininput--disabled {
994
- opacity: 0.5;
995
- }
989
+ /* PinInput — row of single cells. */
990
+ .vx-pininput {
991
+ flex-direction: row;
992
+ }
993
+ .vx-pininput-cell {
994
+ width: 40px;
995
+ margin-right: 8px;
996
+ text-align: center;
997
+ }
998
+ .vx-pininput--disabled {
999
+ opacity: 0.5;
1000
+ }
996
1001
 
997
- /* Select — trigger row + check column inside the anchored listbox. */
998
- .vx-select-trigger {
999
- flex-direction: row;
1000
- align-items: center;
1001
- justify-content: space-between;
1002
- padding: 6px 10px;
1003
- border-width: 1px;
1004
- border-color: var(--color-border);
1005
- border-radius: var(--radius-sm);
1006
- }
1007
- .vx-select--disabled {
1008
- opacity: 0.5;
1009
- }
1010
- .vx-select-placeholder {
1011
- color: var(--color-text-secondary);
1012
- }
1013
- .vx-select-caret {
1014
- margin-left: 8px;
1015
- }
1016
- .vx-select-check {
1017
- width: 16px;
1018
- }
1019
- .vx-select-search {
1020
- margin-bottom: 4px;
1021
- }
1002
+ /* Select — trigger row + check column inside the anchored listbox. */
1003
+ .vx-select-trigger {
1004
+ flex-direction: row;
1005
+ align-items: center;
1006
+ justify-content: space-between;
1007
+ padding: 6px 10px;
1008
+ border-width: 1px;
1009
+ border-color: var(--color-border);
1010
+ border-radius: var(--radius-sm);
1011
+ }
1012
+ .vx-select--disabled {
1013
+ opacity: 0.5;
1014
+ }
1015
+ .vx-select-placeholder {
1016
+ color: var(--color-text-secondary);
1017
+ }
1018
+ .vx-select-caret {
1019
+ margin-left: 8px;
1020
+ }
1021
+ .vx-select-check {
1022
+ width: 16px;
1023
+ }
1024
+ .vx-select-search {
1025
+ margin-bottom: 4px;
1026
+ }
1022
1027
 
1023
- /* InputTags — wrapped chip row + trailing field. */
1024
- .vx-inputtags {
1025
- flex-direction: row;
1026
- flex-wrap: wrap;
1027
- align-items: center;
1028
- }
1029
- .vx-inputtags-tag {
1030
- flex-direction: row;
1031
- align-items: center;
1032
- padding: 1px 6px;
1033
- margin-right: 4px;
1034
- margin-bottom: 4px;
1035
- border-radius: var(--radius-sm);
1036
- background-color: var(--color-border);
1037
- }
1038
- .vx-inputtags-remove {
1039
- margin-left: 4px;
1040
- }
1041
- .vx-inputtags-field {
1042
- flex: 1;
1043
- min-width: 80px;
1044
- }
1045
- .vx-inputtags--disabled {
1046
- opacity: 0.5;
1047
- }
1028
+ /* InputTags — wrapped chip row + trailing field. */
1029
+ .vx-inputtags {
1030
+ flex-direction: row;
1031
+ flex-wrap: wrap;
1032
+ align-items: center;
1033
+ }
1034
+ .vx-inputtags-tag {
1035
+ flex-direction: row;
1036
+ align-items: center;
1037
+ padding: 1px 6px;
1038
+ margin-right: 4px;
1039
+ margin-bottom: 4px;
1040
+ border-radius: var(--radius-sm);
1041
+ background-color: var(--color-border);
1042
+ }
1043
+ .vx-inputtags-remove {
1044
+ margin-left: 4px;
1045
+ }
1046
+ .vx-inputtags-field {
1047
+ flex: 1;
1048
+ min-width: 80px;
1049
+ }
1050
+ .vx-inputtags--disabled {
1051
+ opacity: 0.5;
1052
+ }
1048
1053
 
1049
- /* InputRating — glyph row; --on lights the cell. */
1050
- .vx-rating {
1051
- flex-direction: row;
1052
- }
1053
- .vx-rating-cell {
1054
- padding: 2px;
1055
- }
1056
- .vx-rating-glyph {
1057
- font-size: 20px;
1058
- line-height: 24px;
1059
- color: var(--color-border);
1060
- }
1061
- .vx-rating-cell--on .vx-rating-glyph {
1062
- color: var(--color-primary);
1063
- }
1064
- .vx-rating--disabled {
1065
- opacity: 0.5;
1066
- }
1054
+ /* InputRating — glyph row; --on lights the cell. */
1055
+ .vx-rating {
1056
+ flex-direction: row;
1057
+ }
1058
+ .vx-rating-cell {
1059
+ padding: 2px;
1060
+ }
1061
+ .vx-rating-glyph {
1062
+ font-size: 20px;
1063
+ line-height: 24px;
1064
+ color: var(--color-border);
1065
+ }
1066
+ .vx-rating-cell--on .vx-rating-glyph {
1067
+ color: var(--color-primary);
1068
+ }
1069
+ .vx-rating--disabled {
1070
+ opacity: 0.5;
1071
+ }
1067
1072
 
1068
- /* CheckboxGroup — column/row of checkboxes. */
1069
- .vx-checkbox-group {
1070
- flex-direction: column;
1071
- }
1072
- .vx-checkbox-group--row {
1073
- flex-direction: row;
1074
- }
1075
- /* xplat-web-only:start — display:flex for the div/span flex classes */
1076
- .vx-field,
1077
- .vx-fieldgroup,
1078
- .vx-listitem,
1079
- .vx-listitem-leading,
1080
- .vx-listitem-content,
1081
- .vx-listitem-trailing,
1082
- .vx-inputnumber,
1083
- .vx-inputnumber-step,
1084
- .vx-select-trigger,
1085
- .vx-select-list,
1086
- .vx-inputtags,
1087
- .vx-inputtags-tag,
1088
- .vx-rating,
1089
- .vx-rating-cell,
1090
- .vx-checkbox-group {
1091
- display: flex;
1092
- }
1093
- .vx-rating-cell {
1094
- cursor: pointer;
1095
- }
1096
- /* xplat-web-only:end */
1073
+ /* CheckboxGroup — column/row of checkboxes. */
1074
+ .vx-checkbox-group {
1075
+ flex-direction: column;
1076
+ }
1077
+ .vx-checkbox-group--row {
1078
+ flex-direction: row;
1079
+ }
1080
+ /* xplat-web-only:start — display:flex for the div/span flex classes */
1081
+ .vx-field,
1082
+ .vx-fieldgroup,
1083
+ .vx-listitem,
1084
+ .vx-listitem-leading,
1085
+ .vx-listitem-content,
1086
+ .vx-listitem-trailing,
1087
+ .vx-inputnumber,
1088
+ .vx-inputnumber-step,
1089
+ .vx-select-trigger,
1090
+ .vx-select-list,
1091
+ .vx-inputtags,
1092
+ .vx-inputtags-tag,
1093
+ .vx-rating,
1094
+ .vx-rating-cell,
1095
+ .vx-checkbox-group {
1096
+ display: flex;
1097
+ }
1098
+ .vx-rating-cell {
1099
+ cursor: pointer;
1100
+ }
1101
+ /* xplat-web-only:end */
1097
1102
 
1098
- /* Phase-3 navigation & wayfinding. */
1103
+ /* Phase-3 navigation & wayfinding. */
1099
1104
 
1100
- /* Breadcrumb — inline trail. */
1101
- .vx-breadcrumb {
1102
- flex-direction: row;
1103
- align-items: center;
1104
- flex-wrap: wrap;
1105
- }
1106
- .vx-breadcrumb-item {
1107
- flex-direction: row;
1108
- align-items: center;
1109
- }
1110
- .vx-breadcrumb-sep {
1111
- margin-left: 6px;
1112
- margin-right: 6px;
1113
- color: var(--color-text-secondary);
1114
- }
1115
- .vx-breadcrumb-link {
1116
- color: var(--color-primary);
1117
- }
1105
+ /* Breadcrumb — inline trail. */
1106
+ .vx-breadcrumb {
1107
+ flex-direction: row;
1108
+ align-items: center;
1109
+ flex-wrap: wrap;
1110
+ }
1111
+ .vx-breadcrumb-item {
1112
+ flex-direction: row;
1113
+ align-items: center;
1114
+ }
1115
+ .vx-breadcrumb-sep {
1116
+ margin-left: 6px;
1117
+ margin-right: 6px;
1118
+ color: var(--color-text-secondary);
1119
+ }
1120
+ .vx-breadcrumb-link {
1121
+ color: var(--color-primary);
1122
+ }
1118
1123
 
1119
- /* Pagination — windowed page buttons. */
1120
- .vx-pagination {
1121
- flex-direction: row;
1122
- align-items: center;
1123
- }
1124
- .vx-page-btn {
1125
- min-width: 28px;
1126
- height: 28px;
1127
- padding: 0 6px;
1128
- margin-right: 4px;
1129
- border-width: 1px;
1130
- border-color: var(--color-border);
1131
- border-radius: var(--radius-sm);
1132
- align-items: center;
1133
- justify-content: center;
1134
- }
1135
- .vx-page-btn--on {
1136
- background-color: var(--color-primary);
1137
- border-color: var(--color-primary);
1138
- color: var(--color-onprimary);
1139
- }
1140
- .vx-page-ellipsis {
1141
- margin-right: 4px;
1142
- color: var(--color-text-secondary);
1143
- }
1144
- .vx-pagination--disabled {
1145
- opacity: 0.5;
1146
- }
1124
+ /* Pagination — windowed page buttons. */
1125
+ .vx-pagination {
1126
+ flex-direction: row;
1127
+ align-items: center;
1128
+ }
1129
+ .vx-page-btn {
1130
+ min-width: 28px;
1131
+ height: 28px;
1132
+ padding: 0 6px;
1133
+ margin-right: 4px;
1134
+ border-width: 1px;
1135
+ border-color: var(--color-border);
1136
+ border-radius: var(--radius-sm);
1137
+ align-items: center;
1138
+ justify-content: center;
1139
+ }
1140
+ .vx-page-btn--on {
1141
+ background-color: var(--color-primary);
1142
+ border-color: var(--color-primary);
1143
+ color: var(--color-onprimary);
1144
+ }
1145
+ .vx-page-ellipsis {
1146
+ margin-right: 4px;
1147
+ color: var(--color-text-secondary);
1148
+ }
1149
+ .vx-pagination--disabled {
1150
+ opacity: 0.5;
1151
+ }
1147
1152
 
1148
- /* Stepper — dot + label cells joined by a hairline. */
1149
- .vx-stepper {
1150
- flex-direction: row;
1151
- align-items: center;
1152
- }
1153
- .vx-stepper-cell {
1154
- flex-direction: row;
1155
- align-items: center;
1156
- }
1157
- .vx-stepper-line {
1158
- width: 24px;
1159
- height: 1px;
1160
- background-color: var(--color-border);
1161
- }
1162
- .vx-stepper-line--done {
1163
- background-color: var(--color-primary);
1164
- }
1165
- .vx-step {
1166
- flex-direction: row;
1167
- align-items: center;
1168
- }
1169
- .vx-step-dot {
1170
- width: 24px;
1171
- height: 24px;
1172
- border-width: 1px;
1173
- border-color: var(--color-border);
1174
- border-radius: 50%;
1175
- text-align: center;
1176
- font-size: 12px;
1177
- line-height: 22px;
1178
- color: var(--color-text-secondary);
1179
- }
1180
- .vx-step--on .vx-step-dot {
1181
- border-color: var(--color-primary);
1182
- color: var(--color-primary);
1183
- }
1184
- .vx-step--done .vx-step-dot {
1185
- background-color: var(--color-primary);
1186
- border-color: var(--color-primary);
1187
- color: var(--color-onprimary);
1188
- }
1189
- .vx-step-label {
1190
- margin-left: 6px;
1191
- }
1192
- .vx-step--disabled {
1193
- opacity: 0.5;
1194
- }
1153
+ /* Stepper — dot + label cells joined by a hairline. */
1154
+ .vx-stepper {
1155
+ flex-direction: row;
1156
+ align-items: center;
1157
+ }
1158
+ .vx-stepper-cell {
1159
+ flex-direction: row;
1160
+ align-items: center;
1161
+ }
1162
+ .vx-stepper-line {
1163
+ width: 24px;
1164
+ height: 1px;
1165
+ background-color: var(--color-border);
1166
+ }
1167
+ .vx-stepper-line--done {
1168
+ background-color: var(--color-primary);
1169
+ }
1170
+ .vx-step {
1171
+ flex-direction: row;
1172
+ align-items: center;
1173
+ }
1174
+ .vx-step-dot {
1175
+ width: 24px;
1176
+ height: 24px;
1177
+ border-width: 1px;
1178
+ border-color: var(--color-border);
1179
+ border-radius: 50%;
1180
+ text-align: center;
1181
+ font-size: 12px;
1182
+ line-height: 22px;
1183
+ color: var(--color-text-secondary);
1184
+ }
1185
+ .vx-step--on .vx-step-dot {
1186
+ border-color: var(--color-primary);
1187
+ color: var(--color-primary);
1188
+ }
1189
+ .vx-step--done .vx-step-dot {
1190
+ background-color: var(--color-primary);
1191
+ border-color: var(--color-primary);
1192
+ color: var(--color-onprimary);
1193
+ }
1194
+ .vx-step-label {
1195
+ margin-left: 6px;
1196
+ }
1197
+ .vx-step--disabled {
1198
+ opacity: 0.5;
1199
+ }
1195
1200
 
1196
- /* NavigationMenu — row/column of nav items. */
1197
- .vx-navmenu,
1198
- .vx-navmenu--row {
1199
- flex-direction: row;
1200
- align-items: center;
1201
- }
1202
- .vx-navmenu--col {
1203
- flex-direction: column;
1204
- align-items: flex-start;
1205
- }
1206
- .vx-navmenu-item {
1207
- flex-direction: row;
1208
- align-items: center;
1209
- padding: 6px 10px;
1210
- border-radius: var(--radius-sm);
1211
- }
1212
- .vx-navmenu-item--on {
1213
- background-color: var(--color-border);
1214
- }
1215
- .vx-navmenu-item--disabled {
1216
- opacity: 0.5;
1217
- }
1218
- .vx-navmenu-label {
1219
- margin-left: 4px;
1220
- }
1201
+ /* NavigationMenu — row/column of nav items. */
1202
+ .vx-navmenu,
1203
+ .vx-navmenu--row {
1204
+ flex-direction: row;
1205
+ align-items: center;
1206
+ }
1207
+ .vx-navmenu--col {
1208
+ flex-direction: column;
1209
+ align-items: flex-start;
1210
+ }
1211
+ .vx-navmenu-item {
1212
+ flex-direction: row;
1213
+ align-items: center;
1214
+ padding: 6px 10px;
1215
+ border-radius: var(--radius-sm);
1216
+ }
1217
+ .vx-navmenu-item--on {
1218
+ background-color: var(--color-border);
1219
+ }
1220
+ .vx-navmenu-item--disabled {
1221
+ opacity: 0.5;
1222
+ }
1223
+ .vx-navmenu-label {
1224
+ margin-left: 4px;
1225
+ }
1221
1226
 
1222
- /* CommandPalette — panel centered by the overlay layer. */
1223
- .vx-commandpalette {
1224
- width: 360px;
1225
- padding: 8px;
1226
- border-width: 1px;
1227
- border-color: var(--color-border);
1228
- border-radius: var(--radius-md);
1229
- corner-shape: squircle;
1230
- background-color: var(--color-surface);
1231
- color: var(--color-text);
1232
- }
1233
- /* xplat-web-only:start — native uses Screen.mainScreen.widthDIPs */
1234
- .vx-commandpalette {
1235
- /* The overlay-content wrapper shrink-wraps the palette, so % would resolve
1227
+ /* CommandPalette — panel centered by the overlay layer. */
1228
+ .vx-commandpalette {
1229
+ width: 360px;
1230
+ padding: 8px;
1231
+ border-width: 1px;
1232
+ border-color: var(--color-border);
1233
+ border-radius: var(--radius-md);
1234
+ corner-shape: squircle;
1235
+ background-color: var(--color-surface);
1236
+ color: var(--color-text);
1237
+ }
1238
+ /* xplat-web-only:start — native uses Screen.mainScreen.widthDIPs */
1239
+ .vx-commandpalette {
1240
+ /* The overlay-content wrapper shrink-wraps the palette, so % would resolve
1236
1241
  * against the palette itself instead of the available viewport. */
1237
- max-width: 90vw;
1238
- }
1239
- /* xplat-web-only:end */
1240
- .vx-cmdk-search {
1241
- margin-bottom: 4px;
1242
- }
1243
- /* xplat-web-only:start — display, centering, and cursor are DOM-only */
1244
- .vx-breadcrumb,
1245
- .vx-breadcrumb-item,
1246
- .vx-pagination,
1247
- .vx-page-btn,
1248
- .vx-stepper,
1249
- .vx-stepper-cell,
1250
- .vx-step,
1251
- .vx-navmenu,
1252
- .vx-navmenu-item {
1253
- display: flex;
1254
- }
1255
- .vx-cmdk-layer .vx-overlay-content {
1256
- position: absolute;
1257
- top: 15%;
1258
- left: 50%;
1259
- transform: translateX(-50%);
1260
- }
1261
- .vx-page-btn,
1262
- .vx-breadcrumb-link,
1263
- .vx-step,
1264
- .vx-navmenu-item {
1265
- cursor: pointer;
1266
- }
1267
- /* xplat-web-only:end */
1242
+ max-width: 90vw;
1243
+ }
1244
+ /* xplat-web-only:end */
1245
+ .vx-cmdk-search {
1246
+ margin-bottom: 4px;
1247
+ }
1248
+ /* xplat-web-only:start — display, centering, and cursor are DOM-only */
1249
+ .vx-breadcrumb,
1250
+ .vx-breadcrumb-item,
1251
+ .vx-pagination,
1252
+ .vx-page-btn,
1253
+ .vx-stepper,
1254
+ .vx-stepper-cell,
1255
+ .vx-step,
1256
+ .vx-navmenu,
1257
+ .vx-navmenu-item {
1258
+ display: flex;
1259
+ }
1260
+ .vx-cmdk-layer .vx-overlay-content {
1261
+ position: absolute;
1262
+ top: 15%;
1263
+ left: 50%;
1264
+ transform: translateX(-50%);
1265
+ }
1266
+ .vx-page-btn,
1267
+ .vx-breadcrumb-link,
1268
+ .vx-step,
1269
+ .vx-navmenu-item {
1270
+ cursor: pointer;
1271
+ }
1272
+ /* xplat-web-only:end */
1268
1273
 
1269
- /* Phase-4 data display. */
1274
+ /* Phase-4 data display. */
1270
1275
 
1271
- /* Table — header + bounded rows (unvirtualized by design). */
1272
- .vx-table {
1273
- flex-direction: column;
1274
- border-width: 1px;
1275
- border-color: var(--color-border);
1276
- border-radius: var(--radius-md);
1277
- }
1278
- .vx-table-head,
1279
- .vx-table-row {
1280
- flex-direction: row;
1281
- }
1282
- .vx-table-th {
1283
- font-weight: 600;
1284
- }
1285
- .vx-table-cell {
1286
- padding: 8px 12px;
1287
- }
1288
- .vx-table-head {
1289
- border-bottom-width: 1px;
1290
- border-bottom-color: var(--color-border);
1291
- }
1292
- .vx-table-empty {
1293
- padding: 16px;
1294
- align-items: center;
1295
- }
1276
+ /* Table — header + bounded rows (unvirtualized by design). */
1277
+ .vx-table {
1278
+ flex-direction: column;
1279
+ border-width: 1px;
1280
+ border-color: var(--color-border);
1281
+ border-radius: var(--radius-md);
1282
+ }
1283
+ .vx-table-head,
1284
+ .vx-table-row {
1285
+ flex-direction: row;
1286
+ }
1287
+ .vx-table-th {
1288
+ font-weight: 600;
1289
+ }
1290
+ .vx-table-cell {
1291
+ padding: 8px 12px;
1292
+ }
1293
+ .vx-table-head {
1294
+ border-bottom-width: 1px;
1295
+ border-bottom-color: var(--color-border);
1296
+ }
1297
+ .vx-table-empty {
1298
+ padding: 16px;
1299
+ align-items: center;
1300
+ }
1296
1301
 
1297
- /* Timeline — dot + connector rail beside content. */
1298
- .vx-timeline {
1299
- flex-direction: column;
1300
- }
1301
- .vx-timeline-item {
1302
- flex-direction: row;
1303
- }
1304
- .vx-timeline-rail {
1305
- flex-direction: column;
1306
- align-items: center;
1307
- width: 20px;
1308
- }
1309
- .vx-timeline-dot {
1310
- width: 10px;
1311
- height: 10px;
1312
- margin-top: 5px;
1313
- border-radius: 50%;
1314
- background-color: var(--color-primary);
1315
- align-items: center;
1316
- justify-content: center;
1317
- }
1318
- .vx-timeline-line {
1319
- width: 1px;
1320
- flex: 1;
1321
- background-color: var(--color-border);
1322
- }
1323
- .vx-timeline-body {
1324
- flex-direction: column;
1325
- padding-left: 10px;
1326
- padding-bottom: 16px;
1327
- }
1328
- .vx-timeline-time {
1329
- font-size: 12px;
1330
- line-height: 16px;
1331
- color: var(--color-text-secondary);
1332
- }
1333
- .vx-timeline-title {
1334
- font-weight: 600;
1335
- }
1336
- .vx-timeline-desc {
1337
- color: var(--color-text-secondary);
1338
- }
1302
+ /* Timeline — dot + connector rail beside content. */
1303
+ .vx-timeline {
1304
+ flex-direction: column;
1305
+ }
1306
+ .vx-timeline-item {
1307
+ flex-direction: row;
1308
+ }
1309
+ .vx-timeline-rail {
1310
+ flex-direction: column;
1311
+ align-items: center;
1312
+ width: 20px;
1313
+ }
1314
+ .vx-timeline-dot {
1315
+ width: 10px;
1316
+ height: 10px;
1317
+ margin-top: 5px;
1318
+ border-radius: 50%;
1319
+ background-color: var(--color-primary);
1320
+ align-items: center;
1321
+ justify-content: center;
1322
+ }
1323
+ .vx-timeline-line {
1324
+ width: 1px;
1325
+ flex: 1;
1326
+ background-color: var(--color-border);
1327
+ }
1328
+ .vx-timeline-body {
1329
+ flex-direction: column;
1330
+ padding-left: 10px;
1331
+ padding-bottom: 16px;
1332
+ }
1333
+ .vx-timeline-time {
1334
+ font-size: 12px;
1335
+ line-height: 16px;
1336
+ color: var(--color-text-secondary);
1337
+ }
1338
+ .vx-timeline-title {
1339
+ font-weight: 600;
1340
+ }
1341
+ .vx-timeline-desc {
1342
+ color: var(--color-text-secondary);
1343
+ }
1339
1344
 
1340
- /* Tree — indented collapsible rows. */
1341
- .vx-tree {
1342
- flex-direction: column;
1343
- }
1344
- .vx-tree-node {
1345
- flex-direction: column;
1346
- }
1347
- .vx-tree-row {
1348
- flex-direction: row;
1349
- align-items: center;
1350
- padding: 4px 0;
1351
- }
1352
- .vx-tree-row--disabled {
1353
- opacity: 0.5;
1354
- }
1355
- .vx-tree-caret {
1356
- width: 14px;
1357
- margin-left: 4px;
1358
- }
1345
+ /* Tree — indented collapsible rows. */
1346
+ .vx-tree {
1347
+ flex-direction: column;
1348
+ }
1349
+ .vx-tree-node {
1350
+ flex-direction: column;
1351
+ }
1352
+ .vx-tree-row {
1353
+ flex-direction: row;
1354
+ align-items: center;
1355
+ padding: 4px 0;
1356
+ }
1357
+ .vx-tree-row--disabled {
1358
+ opacity: 0.5;
1359
+ }
1360
+ .vx-tree-caret {
1361
+ width: 14px;
1362
+ margin-left: 4px;
1363
+ }
1359
1364
 
1360
- /* Alert — icon + body callout; tone picks border/text colors. */
1361
- .vx-alert {
1362
- flex-direction: row;
1363
- padding: 10px 12px;
1364
- border-width: 1px;
1365
- border-radius: var(--radius-md);
1366
- corner-shape: squircle;
1367
- border-color: var(--color-border);
1368
- }
1369
- .vx-alert-body {
1370
- flex-direction: column;
1371
- flex: 1;
1372
- margin-left: 8px;
1373
- }
1374
- .vx-alert-title {
1375
- font-weight: 600;
1376
- }
1377
- .vx-alert--error {
1378
- border-color: var(--color-danger);
1379
- }
1380
- .vx-alert--warning {
1381
- border-color: var(--color-warning, #d9a400);
1382
- }
1383
- .vx-alert--success {
1384
- border-color: var(--color-success, #2e9e5b);
1385
- }
1386
- .vx-alert--info {
1387
- border-color: var(--color-primary);
1388
- }
1365
+ /* Alert — icon + body callout; tone picks border/text colors. */
1366
+ .vx-alert {
1367
+ flex-direction: row;
1368
+ padding: 10px 12px;
1369
+ border-width: 1px;
1370
+ border-radius: var(--radius-md);
1371
+ corner-shape: squircle;
1372
+ border-color: var(--color-border);
1373
+ }
1374
+ .vx-alert-body {
1375
+ flex-direction: column;
1376
+ flex: 1;
1377
+ margin-left: 8px;
1378
+ }
1379
+ .vx-alert-title {
1380
+ font-weight: 600;
1381
+ }
1382
+ .vx-alert--error {
1383
+ border-color: var(--color-danger);
1384
+ }
1385
+ .vx-alert--warning {
1386
+ border-color: var(--color-warning, #d9a400);
1387
+ }
1388
+ .vx-alert--success {
1389
+ border-color: var(--color-success, #2e9e5b);
1390
+ }
1391
+ .vx-alert--info {
1392
+ border-color: var(--color-primary);
1393
+ }
1389
1394
 
1390
- /* Card — sectioned container. */
1391
- .vx-card {
1392
- flex-direction: column;
1393
- border-width: 1px;
1394
- border-color: var(--color-border);
1395
- border-radius: var(--radius-md);
1396
- corner-shape: squircle;
1397
- background-color: var(--color-surface);
1398
- }
1399
- .vx-card-header {
1400
- padding: 12px;
1401
- border-bottom-width: 1px;
1402
- border-bottom-color: var(--color-border);
1403
- }
1404
- .vx-card-body {
1405
- padding: 12px;
1406
- flex-direction: column;
1407
- }
1408
- .vx-card-footer {
1409
- padding: 12px;
1410
- border-top-width: 1px;
1411
- border-top-color: var(--color-border);
1412
- }
1395
+ /* Card — sectioned container. */
1396
+ .vx-card {
1397
+ flex-direction: column;
1398
+ border-width: 1px;
1399
+ border-color: var(--color-border);
1400
+ border-radius: var(--radius-md);
1401
+ corner-shape: squircle;
1402
+ background-color: var(--color-surface);
1403
+ }
1404
+ .vx-card-header {
1405
+ padding: 12px;
1406
+ border-bottom-width: 1px;
1407
+ border-bottom-color: var(--color-border);
1408
+ }
1409
+ .vx-card-body {
1410
+ padding: 12px;
1411
+ flex-direction: column;
1412
+ }
1413
+ .vx-card-footer {
1414
+ padding: 12px;
1415
+ border-top-width: 1px;
1416
+ border-top-color: var(--color-border);
1417
+ }
1413
1418
 
1414
- /* Chip — pressable pill with selected + remove states. */
1415
- .vx-chip {
1416
- flex-direction: row;
1417
- align-items: center;
1418
- padding: 3px 10px;
1419
- border-width: 1px;
1420
- border-color: var(--color-border);
1421
- border-radius: var(--radius-md);
1422
- corner-shape: squircle;
1423
- font-size: 13px;
1424
- }
1425
- .vx-chip--on {
1426
- background-color: var(--color-primary);
1427
- border-color: var(--color-primary);
1428
- color: var(--color-onprimary);
1429
- }
1430
- .vx-chip--disabled {
1431
- opacity: 0.5;
1432
- }
1433
- .vx-chip-remove {
1434
- margin-left: 6px;
1435
- }
1419
+ /* Chip — pressable pill with selected + remove states. */
1420
+ .vx-chip {
1421
+ flex-direction: row;
1422
+ align-items: center;
1423
+ padding: 3px 10px;
1424
+ border-width: 1px;
1425
+ border-color: var(--color-border);
1426
+ border-radius: var(--radius-md);
1427
+ corner-shape: squircle;
1428
+ font-size: 13px;
1429
+ }
1430
+ .vx-chip--on {
1431
+ background-color: var(--color-primary);
1432
+ border-color: var(--color-primary);
1433
+ color: var(--color-onprimary);
1434
+ }
1435
+ .vx-chip--disabled {
1436
+ opacity: 0.5;
1437
+ }
1438
+ .vx-chip-remove {
1439
+ margin-left: 6px;
1440
+ }
1436
1441
 
1437
- /* Kbd — keycap glyph. */
1438
- .vx-kbd {
1439
- padding: 1px 5px;
1440
- border-width: 1px;
1441
- border-color: var(--color-border);
1442
- border-bottom-width: 2px;
1443
- border-radius: var(--radius-sm);
1444
- font-size: 12px;
1445
- }
1442
+ /* Kbd — keycap glyph. */
1443
+ .vx-kbd {
1444
+ padding: 1px 5px;
1445
+ border-width: 1px;
1446
+ border-color: var(--color-border);
1447
+ border-bottom-width: 2px;
1448
+ border-radius: var(--radius-sm);
1449
+ font-size: 12px;
1450
+ }
1446
1451
 
1447
- /* Empty — centered placeholder. */
1448
- .vx-empty {
1449
- flex-direction: column;
1450
- align-items: center;
1451
- padding: 24px;
1452
- }
1453
- .vx-empty-title {
1454
- font-weight: 600;
1455
- margin-top: 8px;
1456
- }
1457
- .vx-empty-desc {
1458
- color: var(--color-text-secondary);
1459
- margin-top: 2px;
1460
- }
1452
+ /* Empty — centered placeholder. */
1453
+ .vx-empty {
1454
+ flex-direction: column;
1455
+ align-items: center;
1456
+ padding: 24px;
1457
+ }
1458
+ .vx-empty-title {
1459
+ font-weight: 600;
1460
+ margin-top: 8px;
1461
+ }
1462
+ .vx-empty-desc {
1463
+ color: var(--color-text-secondary);
1464
+ margin-top: 2px;
1465
+ }
1461
1466
 
1462
- /* Banner — notice strip. */
1463
- .vx-banner {
1464
- flex-direction: row;
1465
- align-items: center;
1466
- padding: 8px 12px;
1467
- border-width: 1px;
1468
- border-color: var(--color-border);
1469
- border-radius: var(--radius-md);
1470
- background-color: var(--color-surface);
1471
- }
1472
- .vx-banner-body {
1473
- flex-direction: column;
1474
- flex: 1;
1475
- margin-left: 8px;
1476
- }
1477
- .vx-banner-dismiss {
1478
- margin-left: 8px;
1479
- }
1467
+ /* Banner — notice strip. */
1468
+ .vx-banner {
1469
+ flex-direction: row;
1470
+ align-items: center;
1471
+ padding: 8px 12px;
1472
+ border-width: 1px;
1473
+ border-color: var(--color-border);
1474
+ border-radius: var(--radius-md);
1475
+ background-color: var(--color-surface);
1476
+ }
1477
+ .vx-banner-body {
1478
+ flex-direction: column;
1479
+ flex: 1;
1480
+ margin-left: 8px;
1481
+ }
1482
+ .vx-banner-dismiss {
1483
+ margin-left: 8px;
1484
+ }
1480
1485
 
1481
- /* User — avatar + text column. */
1482
- .vx-user {
1483
- flex-direction: row;
1484
- align-items: center;
1485
- }
1486
- .vx-user-body {
1487
- flex-direction: column;
1488
- margin-left: 10px;
1489
- }
1490
- .vx-user-name {
1491
- font-weight: 600;
1492
- }
1493
- .vx-user-desc {
1494
- font-size: 12px;
1495
- line-height: 16px;
1496
- color: var(--color-text-secondary);
1497
- }
1486
+ /* User — avatar + text column. */
1487
+ .vx-user {
1488
+ flex-direction: row;
1489
+ align-items: center;
1490
+ }
1491
+ .vx-user-body {
1492
+ flex-direction: column;
1493
+ margin-left: 10px;
1494
+ }
1495
+ .vx-user-name {
1496
+ font-weight: 600;
1497
+ }
1498
+ .vx-user-desc {
1499
+ font-size: 12px;
1500
+ line-height: 16px;
1501
+ color: var(--color-text-secondary);
1502
+ }
1498
1503
 
1499
- /* ProgressGroup — stacked labeled meters. */
1500
- .vx-progressgroup {
1501
- flex-direction: column;
1502
- }
1503
- .vx-progressgroup-row {
1504
- flex-direction: row;
1505
- align-items: center;
1506
- }
1507
- .vx-progressgroup-label {
1508
- margin-left: 8px;
1509
- }
1510
- /* xplat-web-only:start — display:flex for the div-based flex classes */
1511
- .vx-table,
1512
- .vx-table-head,
1513
- .vx-table-row,
1514
- .vx-timeline,
1515
- .vx-timeline-item,
1516
- .vx-timeline-rail,
1517
- .vx-timeline-body,
1518
- .vx-tree,
1519
- .vx-tree-node,
1520
- .vx-tree-row,
1521
- .vx-alert,
1522
- .vx-alert-body,
1523
- .vx-card,
1524
- .vx-card-body,
1525
- .vx-banner,
1526
- .vx-banner-body,
1527
- .vx-user,
1528
- .vx-user-body,
1529
- .vx-progressgroup,
1530
- .vx-progressgroup-row {
1531
- display: flex;
1532
- }
1533
- .vx-table-row--pressable,
1534
- .vx-tree-row,
1535
- .vx-user--pressable,
1536
- .vx-banner-dismiss,
1537
- .vx-chip-remove {
1538
- cursor: pointer;
1539
- }
1540
- /* xplat-web-only:end */
1504
+ /* ProgressGroup — stacked labeled meters. */
1505
+ .vx-progressgroup {
1506
+ flex-direction: column;
1507
+ }
1508
+ .vx-progressgroup-row {
1509
+ flex-direction: row;
1510
+ align-items: center;
1511
+ }
1512
+ .vx-progressgroup-label {
1513
+ margin-left: 8px;
1514
+ }
1515
+ /* xplat-web-only:start — display:flex for the div-based flex classes */
1516
+ .vx-table,
1517
+ .vx-table-head,
1518
+ .vx-table-row,
1519
+ .vx-timeline,
1520
+ .vx-timeline-item,
1521
+ .vx-timeline-rail,
1522
+ .vx-timeline-body,
1523
+ .vx-tree,
1524
+ .vx-tree-node,
1525
+ .vx-tree-row,
1526
+ .vx-alert,
1527
+ .vx-alert-body,
1528
+ .vx-card,
1529
+ .vx-card-body,
1530
+ .vx-banner,
1531
+ .vx-banner-body,
1532
+ .vx-user,
1533
+ .vx-user-body,
1534
+ .vx-progressgroup,
1535
+ .vx-progressgroup-row {
1536
+ display: flex;
1537
+ }
1538
+ .vx-table-row--pressable,
1539
+ .vx-tree-row,
1540
+ .vx-user--pressable,
1541
+ .vx-banner-dismiss,
1542
+ .vx-chip-remove {
1543
+ cursor: pointer;
1544
+ }
1545
+ /* xplat-web-only:end */
1541
1546
 
1542
- .vx-heading {
1543
- margin: 0;
1544
- }
1545
- /* NativeScript reads the numeric prefix of font-size; keep this scale in px. */
1546
- .vx-h1 {
1547
- font-size: 32px;
1548
- font-weight: bold;
1549
- }
1550
- .vx-h2 {
1551
- font-size: 24px;
1552
- font-weight: bold;
1553
- }
1554
- .vx-h3 {
1555
- font-size: 18.72px;
1556
- font-weight: bold;
1557
- }
1558
- .vx-h4 {
1559
- font-size: 16px;
1560
- font-weight: bold;
1561
- }
1562
- .vx-h5 {
1563
- font-size: 13.28px;
1564
- font-weight: bold;
1565
- }
1566
- .vx-h6 {
1567
- font-size: 10.72px;
1568
- font-weight: bold;
1569
- }
1547
+ .vx-heading {
1548
+ margin: 0;
1549
+ }
1550
+ /* NativeScript reads the numeric prefix of font-size; keep this scale in px. */
1551
+ .vx-h1 {
1552
+ font-size: 32px;
1553
+ font-weight: bold;
1554
+ }
1555
+ .vx-h2 {
1556
+ font-size: 24px;
1557
+ font-weight: bold;
1558
+ }
1559
+ .vx-h3 {
1560
+ font-size: 18.72px;
1561
+ font-weight: bold;
1562
+ }
1563
+ .vx-h4 {
1564
+ font-size: 16px;
1565
+ font-weight: bold;
1566
+ }
1567
+ .vx-h5 {
1568
+ font-size: 13.28px;
1569
+ font-weight: bold;
1570
+ }
1571
+ .vx-h6 {
1572
+ font-size: 10.72px;
1573
+ font-weight: bold;
1574
+ }
1570
1575
 
1571
- /* utilities (hand-written subset — do NOT adopt Tailwind until
1576
+ /* utilities (hand-written subset — do NOT adopt Tailwind until
1572
1577
  NativeScript/tailwind#226 is resolved; the plugin strips core-supported
1573
1578
  props incl. gap, drops all @media, and rewrites invisible→collapse) */
1574
1579
 
1575
- /* flex-1 keeps Tailwind v4's exact emission (`flex: 1 1 0%`) — web gets
1580
+ /* flex-1 keeps Tailwind v4's exact emission (`flex: 1 1 0%`) — web gets
1576
1581
  identical semantics, NS parses grow/shrink out of the shorthand and
1577
1582
  ignores the basis token it can't model. */
1578
- .flex-1 {
1579
- flex: 1 1 0%;
1580
- }
1581
- /* Flex divergence guards — NS FlexboxLayout diverges from web flexbox on
1583
+ .flex-1 {
1584
+ flex: 1 1 0%;
1585
+ }
1586
+ /* Flex divergence guards — NS FlexboxLayout diverges from web flexbox on
1582
1587
  * shrinkability: grow children never shrink below intrinsic width, fixed
1583
1588
  * children get crushed, and auto-height rows re-measure grow children at
1584
1589
  * zero height. The shared `* { flex-shrink: 0 }` default (RN semantics)
1585
1590
  * makes .shrink the opt-in for a shrinkable child; min-w-0/min-h-0 lower
1586
1591
  * the min-content floor so it can actually shrink, and shrink-0 remains
1587
1592
  * for local overrides. All four are plain CSS on web. */
1588
- .min-w-0 {
1589
- min-width: 0;
1590
- }
1591
- .min-h-0 {
1592
- min-height: 0;
1593
- }
1594
- .shrink {
1595
- flex-shrink: 1;
1596
- }
1597
- .shrink-0 {
1598
- flex-shrink: 0;
1599
- }
1600
- .grow {
1601
- flex-grow: 1;
1602
- }
1603
- .items-center {
1604
- align-items: center;
1605
- }
1606
- .items-baseline {
1607
- align-items: baseline;
1608
- }
1609
- .items-start {
1610
- align-items: flex-start;
1611
- }
1612
- .justify-center {
1613
- justify-content: center;
1614
- }
1615
- .justify-between {
1616
- justify-content: space-between;
1617
- }
1618
- .flex-wrap {
1619
- flex-wrap: wrap;
1620
- }
1621
- .gap-4 {
1622
- gap: 16px;
1623
- }
1624
- .gap-3 {
1625
- gap: 12px;
1626
- }
1627
- .gap-6 {
1628
- gap: 24px;
1629
- }
1593
+ .min-w-0 {
1594
+ min-width: 0;
1595
+ }
1596
+ .min-h-0 {
1597
+ min-height: 0;
1598
+ }
1599
+ .shrink {
1600
+ flex-shrink: 1;
1601
+ }
1602
+ .shrink-0 {
1603
+ flex-shrink: 0;
1604
+ }
1605
+ .grow {
1606
+ flex-grow: 1;
1607
+ }
1608
+ .items-center {
1609
+ align-items: center;
1610
+ }
1611
+ .items-baseline {
1612
+ align-items: baseline;
1613
+ }
1614
+ .items-start {
1615
+ align-items: flex-start;
1616
+ }
1617
+ .justify-center {
1618
+ justify-content: center;
1619
+ }
1620
+ .justify-between {
1621
+ justify-content: space-between;
1622
+ }
1623
+ .flex-wrap {
1624
+ flex-wrap: wrap;
1625
+ }
1626
+ .gap-4 {
1627
+ gap: 16px;
1628
+ }
1629
+ .gap-3 {
1630
+ gap: 12px;
1631
+ }
1632
+ .gap-6 {
1633
+ gap: 24px;
1634
+ }
1630
1635
 
1631
- /* Native Labels keep their natural font height even with line-height set, so
1636
+ /* Native Labels keep their natural font height even with line-height set, so
1632
1637
  min-height aligns utilities whose natural metrics fall below their leading.
1633
1638
  Tailwind pairs every text-* size with a leading, same here. */
1634
- .text-2xl {
1635
- font-size: 28px;
1636
- line-height: 36px;
1637
- min-height: 36px;
1638
- }
1639
- .text-xl {
1640
- font-size: 20px;
1641
- line-height: 28px;
1642
- min-height: 28px;
1643
- }
1644
- .text-lg {
1645
- font-size: 18px;
1646
- line-height: 28px;
1647
- min-height: 28px;
1648
- }
1649
- .text-sm {
1650
- font-size: 13px;
1651
- line-height: 20px;
1652
- min-height: 20px;
1653
- }
1654
- .font-bold {
1655
- font-weight: bold;
1656
- }
1657
- .font-semibold {
1658
- font-weight: 600;
1659
- }
1660
- .text-muted {
1661
- color: var(--color-text-secondary);
1662
- }
1639
+ .text-2xl {
1640
+ font-size: 28px;
1641
+ line-height: 36px;
1642
+ min-height: 36px;
1643
+ }
1644
+ .text-xl {
1645
+ font-size: 20px;
1646
+ line-height: 28px;
1647
+ min-height: 28px;
1648
+ }
1649
+ .text-lg {
1650
+ font-size: 18px;
1651
+ line-height: 28px;
1652
+ min-height: 28px;
1653
+ }
1654
+ .text-sm {
1655
+ font-size: 13px;
1656
+ line-height: 20px;
1657
+ min-height: 20px;
1658
+ }
1659
+ .font-bold {
1660
+ font-weight: bold;
1661
+ }
1662
+ .font-semibold {
1663
+ font-weight: 600;
1664
+ }
1665
+ .text-muted {
1666
+ color: var(--color-text-secondary);
1667
+ }
1663
1668
 
1664
- /* Corner utilities — consume the --radius-* scale and carry the smooth
1669
+ /* Corner utilities — consume the --radius-* scale and carry the smooth
1665
1670
  (squircle) corner treatment. Circles stay circular: .rounded-full omits
1666
1671
  corner-shape deliberately (squircle at full radius = a squircle disk,
1667
1672
  not a circle). Opt out per element with `corner-shape: round`. */
1668
- .rounded-sm {
1669
- border-radius: var(--radius-sm);
1670
- corner-shape: squircle;
1671
- }
1672
- .rounded-md {
1673
- border-radius: var(--radius-md);
1674
- corner-shape: squircle;
1675
- }
1676
- .rounded-lg {
1677
- border-radius: var(--radius-lg);
1678
- corner-shape: squircle;
1679
- }
1680
- .rounded-xl {
1681
- border-radius: var(--radius-xl);
1682
- corner-shape: squircle;
1683
- }
1684
- .rounded-full {
1685
- border-radius: 9999px;
1686
- }
1673
+ .rounded-sm {
1674
+ border-radius: var(--radius-sm);
1675
+ corner-shape: squircle;
1676
+ }
1677
+ .rounded-md {
1678
+ border-radius: var(--radius-md);
1679
+ corner-shape: squircle;
1680
+ }
1681
+ .rounded-lg {
1682
+ border-radius: var(--radius-lg);
1683
+ corner-shape: squircle;
1684
+ }
1685
+ .rounded-xl {
1686
+ border-radius: var(--radius-xl);
1687
+ corner-shape: squircle;
1688
+ }
1689
+ .rounded-full {
1690
+ border-radius: 9999px;
1691
+ }
1687
1692
 
1688
- .bg-primary {
1689
- background-color: var(--color-primary);
1690
- }
1691
- .text-onprimary {
1692
- color: var(--color-onprimary);
1693
- }
1694
- .bg-danger {
1695
- background-color: var(--color-danger);
1696
- }
1697
- /* xplat-web-only:start — box-shadow is inert on native (elevation is
1693
+ .bg-primary {
1694
+ background-color: var(--color-primary);
1695
+ }
1696
+ .text-onprimary {
1697
+ color: var(--color-onprimary);
1698
+ }
1699
+ .bg-danger {
1700
+ background-color: var(--color-danger);
1701
+ }
1702
+ /* xplat-web-only:start — box-shadow is inert on native (elevation is
1698
1703
  Android-only; no shared mapping yet) */
1699
- .shadow-2 {
1700
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
1701
- }
1702
- /* xplat-web-only:end */
1703
-
1704
- .btn {
1705
- padding-top: 10px;
1706
- padding-right: 16px;
1707
- padding-bottom: 10px;
1708
- padding-left: 16px;
1709
- border-radius: var(--radius-md);
1710
- corner-shape: squircle;
1711
- font-size: 14px;
1712
- font-weight: 500;
1713
- align-items: center;
1714
- justify-content: center;
1715
- text-decoration: none;
1716
- color: var(--color-text);
1717
- }
1704
+ .shadow-2 {
1705
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
1706
+ }
1707
+ /* xplat-web-only:end */
1708
+
1709
+ .btn {
1710
+ padding-top: 10px;
1711
+ padding-right: 16px;
1712
+ padding-bottom: 10px;
1713
+ padding-left: 16px;
1714
+ border-radius: var(--radius-md);
1715
+ corner-shape: squircle;
1716
+ font-size: 14px;
1717
+ font-weight: 500;
1718
+ align-items: center;
1719
+ justify-content: center;
1720
+ text-decoration: none;
1721
+ color: var(--color-text);
1722
+ }
1718
1723
 
1719
- /* Secondary = the same control reading quiet: hairline border, surface
1724
+ /* Secondary = the same control reading quiet: hairline border, surface
1720
1725
  fill, body-text label. Reserve .bg-primary for the one decisive action
1721
1726
  in a group. */
1722
- .btn-secondary {
1723
- background-color: var(--color-surface);
1724
- border-width: 1px;
1725
- border-color: var(--color-border);
1726
- color: var(--color-text);
1727
- }
1727
+ .btn-secondary {
1728
+ background-color: var(--color-surface);
1729
+ border-width: 1px;
1730
+ border-color: var(--color-border);
1731
+ color: var(--color-text);
1732
+ }
1728
1733
 
1729
- /* xplat-web-only:start — controls that render as real <a>/<button> need
1734
+ /* xplat-web-only:start — controls that render as real <a>/<button> need
1730
1735
  inline layout and UA resets; the pressable/nav-link leaves are divs on
1731
1736
  web and don't hit these rules. */
1732
- a.btn {
1733
- display: inline-flex;
1734
- }
1735
- a.chip {
1736
- display: inline-flex;
1737
- align-items: center;
1738
- }
1739
- /* xplat-web-only:end */
1737
+ a.btn {
1738
+ display: inline-flex;
1739
+ }
1740
+ a.chip {
1741
+ display: inline-flex;
1742
+ align-items: center;
1743
+ }
1744
+ /* xplat-web-only:end */
1740
1745
 
1741
- /* Tab shell — web divs/buttons + the Android SwapTabs fallback (iOS draws
1746
+ /* Tab shell — web divs/buttons + the Android SwapTabs fallback (iOS draws
1742
1747
  its own UITabBar and never sees these). */
1743
- .vx-tabbar {
1744
- border-bottom-width: 1px;
1745
- border-bottom-color: var(--color-border);
1746
- background-color: var(--color-surface);
1747
- }
1748
- .vx-tab {
1749
- align-items: center;
1750
- justify-content: center;
1751
- padding-top: 10px;
1752
- padding-right: 16px;
1753
- padding-bottom: 10px;
1754
- padding-left: 16px;
1755
- font-size: 14px;
1756
- color: var(--color-text-secondary);
1757
- }
1758
- .vx-tab.active {
1759
- color: var(--color-text);
1760
- }
1761
- .vx-tabicon {
1762
- margin-right: 6px;
1763
- }
1748
+ .vx-tabbar {
1749
+ border-bottom-width: 1px;
1750
+ border-bottom-color: var(--color-border);
1751
+ background-color: var(--color-surface);
1752
+ }
1753
+ .vx-tab {
1754
+ align-items: center;
1755
+ justify-content: center;
1756
+ padding-top: 10px;
1757
+ padding-right: 16px;
1758
+ padding-bottom: 10px;
1759
+ padding-left: 16px;
1760
+ font-size: 14px;
1761
+ color: var(--color-text-secondary);
1762
+ }
1763
+ .vx-tab.active {
1764
+ color: var(--color-text);
1765
+ }
1766
+ .vx-tabicon {
1767
+ margin-right: 6px;
1768
+ }
1764
1769
 
1765
- /* xplat-web-only:start — DOM defaults are display:block (div) and
1770
+ /* xplat-web-only:start — DOM defaults are display:block (div) and
1766
1771
  inline-block (button); every flex-bearing vx- class needs an explicit
1767
1772
  display or flex-direction/align-items/justify-content are inert. */
1768
- .vx-tabs {
1769
- display: flex;
1770
- flex-direction: column;
1771
- flex: 1;
1772
- min-height: 0;
1773
- }
1774
- .vx-tabbar {
1775
- display: flex;
1776
- flex-direction: row;
1777
- }
1778
- .vx-tabpane {
1779
- flex: 1;
1780
- min-height: 0;
1781
- display: flex;
1782
- flex-direction: column;
1783
- }
1784
- button.vx-tab {
1785
- display: flex;
1786
- background: none;
1787
- cursor: pointer;
1788
- }
1789
- /* xplat-web-only:end */
1773
+ .vx-tabs {
1774
+ display: flex;
1775
+ flex-direction: column;
1776
+ flex: 1;
1777
+ min-height: 0;
1778
+ }
1779
+ .vx-tabbar {
1780
+ display: flex;
1781
+ flex-direction: row;
1782
+ }
1783
+ .vx-tabpane {
1784
+ flex: 1;
1785
+ min-height: 0;
1786
+ display: flex;
1787
+ flex-direction: column;
1788
+ }
1789
+ button.vx-tab {
1790
+ display: flex;
1791
+ background: none;
1792
+ cursor: pointer;
1793
+ }
1794
+ /* xplat-web-only:end */
1790
1795
 
1791
- /* harness experiment styles */
1796
+ /* harness experiment styles */
1792
1797
 
1793
- .vx-app {
1794
- padding: 24px;
1795
- background-color: var(--color-surface);
1796
- color: var(--color-text);
1797
- }
1798
+ .vx-app {
1799
+ padding: 24px;
1800
+ background-color: var(--color-surface);
1801
+ color: var(--color-text);
1802
+ }
1798
1803
 
1799
- .vx-app .text-xl,
1800
- .vx-app Text,
1801
- .vx-app label {
1802
- color: var(--color-text);
1803
- }
1804
+ .vx-app .text-xl,
1805
+ .vx-app Text,
1806
+ .vx-app label {
1807
+ color: var(--color-text);
1808
+ }
1804
1809
 
1805
- /* `.vx-app label` above (descendant + type) outranks single-class text
1810
+ /* `.vx-app label` above (descendant + type) outranks single-class text
1806
1811
  colors inside the app root — roles that must win escalate here. */
1807
- .vx-app .text-onprimary {
1808
- color: var(--color-onprimary);
1809
- }
1810
- .vx-app .text-muted {
1811
- color: var(--color-text-secondary);
1812
- }
1813
- .vx-app .vx-tab label {
1814
- color: var(--color-text-secondary);
1815
- }
1816
- .vx-app .vx-tab.active label {
1817
- color: var(--color-text);
1818
- }
1819
- .vx-app .chip-on label {
1820
- color: var(--color-onprimary);
1821
- }
1822
- /* Danger surfaces keep white text in both themes (on-primary flips). */
1823
- .bg-danger .text-onprimary {
1824
- color: #ffffff;
1825
- }
1812
+ .vx-app .text-onprimary {
1813
+ color: var(--color-onprimary);
1814
+ }
1815
+ .vx-app .text-muted {
1816
+ color: var(--color-text-secondary);
1817
+ }
1818
+ .vx-app .vx-tab label {
1819
+ color: var(--color-text-secondary);
1820
+ }
1821
+ .vx-app .vx-tab.active label {
1822
+ color: var(--color-text);
1823
+ }
1824
+ .vx-app .chip-on label {
1825
+ color: var(--color-onprimary);
1826
+ }
1827
+ /* Danger surfaces keep white text in both themes (on-primary flips). */
1828
+ .bg-danger .text-onprimary {
1829
+ color: #ffffff;
1830
+ }
1826
1831
 
1827
- .input {
1828
- padding-top: 10px;
1829
- padding-right: 12px;
1830
- padding-bottom: 10px;
1831
- padding-left: 12px;
1832
- border-width: 1px;
1833
- border-color: var(--color-border);
1834
- border-radius: var(--radius-md);
1835
- corner-shape: squircle;
1836
- font-size: 14px;
1837
- background-color: var(--color-surface);
1838
- color: var(--color-text);
1839
- /* NS TextField/TextView placeholder color (ignored on web — the
1832
+ .input {
1833
+ padding-top: 10px;
1834
+ padding-right: 12px;
1835
+ padding-bottom: 10px;
1836
+ padding-left: 12px;
1837
+ border-width: 1px;
1838
+ border-color: var(--color-border);
1839
+ border-radius: var(--radius-md);
1840
+ corner-shape: squircle;
1841
+ font-size: 14px;
1842
+ background-color: var(--color-surface);
1843
+ color: var(--color-text);
1844
+ /* NS TextField/TextView placeholder color (ignored on web — the
1840
1845
  ::placeholder pseudo above covers it there). */
1841
- placeholder-color: var(--color-text-secondary);
1842
- }
1846
+ placeholder-color: var(--color-text-secondary);
1847
+ }
1843
1848
 
1844
- .chip {
1845
- background-color: var(--color-surface);
1846
- color: var(--color-text);
1847
- padding-top: 5px;
1848
- padding-right: 12px;
1849
- padding-bottom: 5px;
1850
- padding-left: 12px;
1851
- border-width: 1px;
1852
- border-color: var(--color-border);
1853
- border-radius: var(--radius-md);
1854
- corner-shape: squircle;
1855
- font-size: 13px;
1856
- text-decoration: none;
1857
- }
1849
+ .chip {
1850
+ background-color: var(--color-surface);
1851
+ color: var(--color-text);
1852
+ padding-top: 5px;
1853
+ padding-right: 12px;
1854
+ padding-bottom: 5px;
1855
+ padding-left: 12px;
1856
+ border-width: 1px;
1857
+ border-color: var(--color-border);
1858
+ border-radius: var(--radius-md);
1859
+ corner-shape: squircle;
1860
+ font-size: 13px;
1861
+ text-decoration: none;
1862
+ }
1858
1863
 
1859
- /* Selected chip — the one filled element in a group of bordered peers. */
1860
- .chip-on {
1861
- background-color: var(--color-primary);
1862
- border-color: var(--color-primary);
1863
- color: var(--color-onprimary);
1864
- }
1864
+ /* Selected chip — the one filled element in a group of bordered peers. */
1865
+ .chip-on {
1866
+ background-color: var(--color-primary);
1867
+ border-color: var(--color-primary);
1868
+ color: var(--color-onprimary);
1869
+ }
1865
1870
 
1866
- .cell {
1867
- padding: 12px;
1868
- border-bottom-width: 1px;
1869
- border-bottom-color: var(--color-border);
1870
- }
1871
+ .cell {
1872
+ padding: 12px;
1873
+ border-bottom-width: 1px;
1874
+ border-bottom-color: var(--color-border);
1875
+ }
1871
1876
 
1872
- .gap-2 {
1873
- gap: 8px;
1874
- }
1877
+ .gap-2 {
1878
+ gap: 8px;
1879
+ }
1875
1880
 
1876
- /* In-window sheet — bottom-anchored panel (matches the native RootLayout
1881
+ /* In-window sheet — bottom-anchored panel (matches the native RootLayout
1877
1882
  sheet). Layer is pointer-events:none; backdrop + panel re-enable. */
1878
- .vx-sheet-layer {
1879
- z-index: 1100;
1880
- }
1883
+ .vx-sheet-layer {
1884
+ z-index: 1100;
1885
+ }
1881
1886
 
1882
- .vx-sheet-backdrop {
1883
- background: rgba(0, 0, 0, 0.4);
1884
- }
1887
+ .vx-sheet-backdrop {
1888
+ background: rgba(0, 0, 0, 0.4);
1889
+ }
1885
1890
 
1886
- .vx-sheet {
1887
- max-height: 85%;
1888
- padding: 24px;
1889
- border-width: 1px;
1890
- border-color: var(--color-border);
1891
- border-top-left-radius: var(--radius-xl);
1892
- border-top-right-radius: var(--radius-xl);
1893
- corner-shape: squircle;
1894
- background-color: var(--color-surface);
1895
- color: var(--color-text);
1896
- }
1891
+ .vx-sheet {
1892
+ max-height: 85%;
1893
+ padding: 24px;
1894
+ border-width: 1px;
1895
+ border-color: var(--color-border);
1896
+ border-top-left-radius: var(--radius-xl);
1897
+ border-top-right-radius: var(--radius-xl);
1898
+ corner-shape: squircle;
1899
+ background-color: var(--color-surface);
1900
+ color: var(--color-text);
1901
+ }
1897
1902
 
1898
- /* xplat-web-only:start — DOM positioning + hit-testing for the sheet.
1903
+ /* xplat-web-only:start — DOM positioning + hit-testing for the sheet.
1899
1904
  The native leaf renders on RootLayout with its own geometry. */
1900
- .vx-sheet-layer {
1901
- position: fixed;
1902
- inset: 0;
1903
- pointer-events: none;
1904
- }
1905
+ .vx-sheet-layer {
1906
+ position: fixed;
1907
+ inset: 0;
1908
+ pointer-events: none;
1909
+ }
1905
1910
 
1906
- .vx-sheet-backdrop,
1907
- .vx-sheet {
1908
- position: absolute;
1909
- inset: 0;
1910
- pointer-events: auto;
1911
- }
1911
+ .vx-sheet-backdrop,
1912
+ .vx-sheet {
1913
+ position: absolute;
1914
+ inset: 0;
1915
+ pointer-events: auto;
1916
+ }
1912
1917
 
1913
- .vx-sheet {
1914
- top: auto;
1915
- bottom: 0;
1916
- overflow: auto;
1917
- }
1918
- /* xplat-web-only:end */
1918
+ .vx-sheet {
1919
+ top: auto;
1920
+ bottom: 0;
1921
+ overflow: auto;
1922
+ }
1923
+ /* xplat-web-only:end */
1919
1924
 
1920
- .vx-sheet-grabber {
1921
- display: flex;
1922
- align-items: center;
1923
- justify-content: center;
1924
- height: 28px;
1925
- }
1925
+ .vx-sheet-grabber {
1926
+ display: flex;
1927
+ align-items: center;
1928
+ justify-content: center;
1929
+ height: 28px;
1930
+ }
1926
1931
 
1927
- .vx-sheet-grip {
1928
- width: 36px;
1929
- height: 4px;
1930
- border-radius: 999px;
1931
- background-color: var(--color-text-secondary);
1932
- opacity: 0.4;
1933
- }
1932
+ .vx-sheet-grip {
1933
+ width: 36px;
1934
+ height: 4px;
1935
+ border-radius: 999px;
1936
+ background-color: var(--color-text-secondary);
1937
+ opacity: 0.4;
1938
+ }
1934
1939
 
1935
- /* xplat-web-only:start — detents size the panel to the largest fraction
1940
+ /* xplat-web-only:start — detents size the panel to the largest fraction
1936
1941
  (overriding the 85% cap) and overlay the grabber; native stacks it as a
1937
1942
  top-aligned grid child. touch-action keeps drags off native scroll. */
1938
- .vx-sheet-detents {
1939
- max-height: none;
1940
- }
1941
- .vx-sheet-detents > .vx-sheet-grabber {
1942
- position: absolute;
1943
- top: 0;
1944
- left: 0;
1945
- right: 0;
1946
- z-index: 2;
1947
- cursor: grab;
1948
- touch-action: none;
1943
+ .vx-sheet-detents {
1944
+ max-height: none;
1945
+ }
1946
+ .vx-sheet-detents > .vx-sheet-grabber {
1947
+ position: absolute;
1948
+ top: 0;
1949
+ left: 0;
1950
+ right: 0;
1951
+ z-index: 2;
1952
+ cursor: grab;
1953
+ touch-action: none;
1954
+ }
1955
+ /* xplat-web-only:end */
1949
1956
  }
1950
- /* xplat-web-only:end */