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