@mkbabb/glass-ui 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/README.md +172 -0
  2. package/dist/glass-ui.css +1 -0
  3. package/dist/glass-ui.js +10019 -0
  4. package/dist/index.d.ts +6619 -0
  5. package/package.json +65 -0
  6. package/src/components/custom/aurora/Aurora.vue +34 -0
  7. package/src/components/custom/aurora/composables/color.ts +122 -0
  8. package/src/components/custom/aurora/composables/useAurora.ts +355 -0
  9. package/src/components/custom/aurora/index.ts +8 -0
  10. package/src/components/custom/confirm-dialog/ConfirmDialog.vue +88 -0
  11. package/src/components/custom/confirm-dialog/index.ts +1 -0
  12. package/src/components/custom/controls/DarkModeToggle.vue +96 -0
  13. package/src/components/custom/controls/index.ts +1 -0
  14. package/src/components/custom/dock/DockLayerGroup.vue +21 -0
  15. package/src/components/custom/dock/DockPopover.vue +263 -0
  16. package/src/components/custom/dock/GlassDock.vue +276 -0
  17. package/src/components/custom/dock/composables/index.ts +16 -0
  18. package/src/components/custom/dock/composables/isTeleportedTarget.ts +19 -0
  19. package/src/components/custom/dock/composables/useDockActionBar.ts +33 -0
  20. package/src/components/custom/dock/composables/useDockState.ts +301 -0
  21. package/src/components/custom/dock/composables/useDockTransition.ts +146 -0
  22. package/src/components/custom/dock/composables/useLayerTransition.ts +135 -0
  23. package/src/components/custom/dock/composables/usePopupMutex.ts +83 -0
  24. package/src/components/custom/dock/index.ts +9 -0
  25. package/src/components/custom/expandable-container/ExpandableContainer.vue +64 -0
  26. package/src/components/custom/expandable-container/index.ts +1 -0
  27. package/src/components/custom/glass-panel/GlassPanel.vue +98 -0
  28. package/src/components/custom/glass-panel/index.ts +2 -0
  29. package/src/components/custom/icon-tooltip/IconTooltip.vue +20 -0
  30. package/src/components/custom/icon-tooltip/index.ts +1 -0
  31. package/src/components/custom/index.ts +15 -0
  32. package/src/components/custom/infinite-scroll/InfiniteScroll.vue +55 -0
  33. package/src/components/custom/infinite-scroll/composables/index.ts +2 -0
  34. package/src/components/custom/infinite-scroll/composables/types.ts +23 -0
  35. package/src/components/custom/infinite-scroll/composables/useInfiniteScroll.ts +73 -0
  36. package/src/components/custom/infinite-scroll/index.ts +1 -0
  37. package/src/components/custom/labeled-field/LabeledInput.vue +29 -0
  38. package/src/components/custom/labeled-field/LabeledSelect.vue +59 -0
  39. package/src/components/custom/labeled-field/LabeledSlider.vue +32 -0
  40. package/src/components/custom/labeled-field/LabeledSwitch.vue +27 -0
  41. package/src/components/custom/labeled-field/index.ts +4 -0
  42. package/src/components/custom/metaballs/MetaballCanvas.vue +23 -0
  43. package/src/components/custom/metaballs/index.ts +4 -0
  44. package/src/components/custom/metaballs/shaders.ts +63 -0
  45. package/src/components/custom/metaballs/types.ts +29 -0
  46. package/src/components/custom/metaballs/useMetaballs.ts +252 -0
  47. package/src/components/custom/search/FuzzySearch.vue +589 -0
  48. package/src/components/custom/search/SearchBar.vue +44 -0
  49. package/src/components/custom/search/composables/fuzzySearchIndex.ts +224 -0
  50. package/src/components/custom/search/composables/index.ts +5 -0
  51. package/src/components/custom/search/composables/types.ts +34 -0
  52. package/src/components/custom/search/composables/useFuzzySearch.ts +115 -0
  53. package/src/components/custom/search/index.ts +7 -0
  54. package/src/components/custom/sidebar/ProgressiveSidebar.vue +256 -0
  55. package/src/components/custom/sidebar/composables/index.ts +6 -0
  56. package/src/components/custom/sidebar/composables/useScrollTracker.ts +242 -0
  57. package/src/components/custom/sidebar/composables/useSidebarFollow.ts +247 -0
  58. package/src/components/custom/sidebar/composables/useSidebarState.ts +72 -0
  59. package/src/components/custom/sidebar/composables/useTreeIndex.ts +152 -0
  60. package/src/components/custom/sidebar/index.ts +15 -0
  61. package/src/components/custom/sidebar/types.ts +50 -0
  62. package/src/components/custom/tabs/BouncyTabs.vue +39 -0
  63. package/src/components/custom/tabs/BouncyToggle.vue +352 -0
  64. package/src/components/custom/tabs/UnderlineTabs.vue +115 -0
  65. package/src/components/custom/tabs/index.ts +5 -0
  66. package/src/components/custom/timeline/GlassTimeline.vue +174 -0
  67. package/src/components/custom/timeline/index.ts +1 -0
  68. package/src/components/custom/typewriter/TypewriterText.vue +239 -0
  69. package/src/components/custom/typewriter/composables/index.ts +1 -0
  70. package/src/components/custom/typewriter/composables/useTypewriter.ts +413 -0
  71. package/src/components/custom/typewriter/index.ts +7 -0
  72. package/src/components/custom/typewriter/types.ts +159 -0
  73. package/src/components/custom/typewriter/utils/keyboard.ts +213 -0
  74. package/src/components/custom/typewriter/utils/pausePatterns.ts +55 -0
  75. package/src/components/custom/typewriter/utils/timing.ts +104 -0
  76. package/src/components/custom/typewriter/utils/typoStateMachine.ts +197 -0
  77. package/src/components/index.ts +2 -0
  78. package/src/components/ui/accordion/Accordion.vue +19 -0
  79. package/src/components/ui/accordion/AccordionContent.vue +24 -0
  80. package/src/components/ui/accordion/AccordionItem.vue +24 -0
  81. package/src/components/ui/accordion/AccordionTrigger.vue +39 -0
  82. package/src/components/ui/accordion/index.ts +4 -0
  83. package/src/components/ui/alert/Alert.vue +20 -0
  84. package/src/components/ui/alert/AlertDescription.vue +17 -0
  85. package/src/components/ui/alert/AlertTitle.vue +17 -0
  86. package/src/components/ui/alert/index.ts +23 -0
  87. package/src/components/ui/avatar/Avatar.vue +21 -0
  88. package/src/components/ui/avatar/AvatarFallback.vue +11 -0
  89. package/src/components/ui/avatar/AvatarImage.vue +9 -0
  90. package/src/components/ui/avatar/index.ts +24 -0
  91. package/src/components/ui/badge/Badge.vue +16 -0
  92. package/src/components/ui/badge/index.ts +25 -0
  93. package/src/components/ui/button/Button.vue +26 -0
  94. package/src/components/ui/button/index.ts +43 -0
  95. package/src/components/ui/card/Card.vue +28 -0
  96. package/src/components/ui/card/CardContent.vue +14 -0
  97. package/src/components/ui/card/CardDescription.vue +14 -0
  98. package/src/components/ui/card/CardFooter.vue +14 -0
  99. package/src/components/ui/card/CardHeader.vue +14 -0
  100. package/src/components/ui/card/CardTitle.vue +21 -0
  101. package/src/components/ui/card/index.ts +6 -0
  102. package/src/components/ui/carousel/Carousel.vue +53 -0
  103. package/src/components/ui/carousel/CarouselContent.vue +35 -0
  104. package/src/components/ui/carousel/CarouselItem.vue +24 -0
  105. package/src/components/ui/carousel/CarouselNext.vue +40 -0
  106. package/src/components/ui/carousel/CarouselPrevious.vue +40 -0
  107. package/src/components/ui/carousel/index.ts +10 -0
  108. package/src/components/ui/carousel/interface.ts +26 -0
  109. package/src/components/ui/carousel/useCarousel.ts +56 -0
  110. package/src/components/ui/checkbox/Checkbox.vue +33 -0
  111. package/src/components/ui/checkbox/index.ts +1 -0
  112. package/src/components/ui/collapsible/Collapsible.vue +15 -0
  113. package/src/components/ui/collapsible/CollapsibleContent.vue +11 -0
  114. package/src/components/ui/collapsible/CollapsibleTrigger.vue +11 -0
  115. package/src/components/ui/collapsible/index.ts +3 -0
  116. package/src/components/ui/combobox/Combobox.vue +17 -0
  117. package/src/components/ui/combobox/ComboboxAnchor.vue +23 -0
  118. package/src/components/ui/combobox/ComboboxEmpty.vue +21 -0
  119. package/src/components/ui/combobox/ComboboxGroup.vue +27 -0
  120. package/src/components/ui/combobox/ComboboxInput.vue +41 -0
  121. package/src/components/ui/combobox/ComboboxItem.vue +24 -0
  122. package/src/components/ui/combobox/ComboboxItemIndicator.vue +23 -0
  123. package/src/components/ui/combobox/ComboboxList.vue +29 -0
  124. package/src/components/ui/combobox/ComboboxSeparator.vue +21 -0
  125. package/src/components/ui/combobox/ComboboxViewport.vue +23 -0
  126. package/src/components/ui/combobox/index.ts +12 -0
  127. package/src/components/ui/command/Command.vue +30 -0
  128. package/src/components/ui/command/CommandDialog.vue +21 -0
  129. package/src/components/ui/command/CommandEmpty.vue +20 -0
  130. package/src/components/ui/command/CommandGroup.vue +29 -0
  131. package/src/components/ui/command/CommandInput.vue +33 -0
  132. package/src/components/ui/command/CommandItem.vue +26 -0
  133. package/src/components/ui/command/CommandList.vue +27 -0
  134. package/src/components/ui/command/CommandSeparator.vue +23 -0
  135. package/src/components/ui/command/CommandShortcut.vue +14 -0
  136. package/src/components/ui/command/index.ts +9 -0
  137. package/src/components/ui/context-menu/ContextMenu.vue +15 -0
  138. package/src/components/ui/context-menu/ContextMenuCheckboxItem.vue +40 -0
  139. package/src/components/ui/context-menu/ContextMenuContent.vue +36 -0
  140. package/src/components/ui/context-menu/ContextMenuGroup.vue +11 -0
  141. package/src/components/ui/context-menu/ContextMenuItem.vue +34 -0
  142. package/src/components/ui/context-menu/ContextMenuLabel.vue +25 -0
  143. package/src/components/ui/context-menu/ContextMenuPortal.vue +11 -0
  144. package/src/components/ui/context-menu/ContextMenuRadioGroup.vue +19 -0
  145. package/src/components/ui/context-menu/ContextMenuRadioItem.vue +40 -0
  146. package/src/components/ui/context-menu/ContextMenuSeparator.vue +20 -0
  147. package/src/components/ui/context-menu/ContextMenuShortcut.vue +14 -0
  148. package/src/components/ui/context-menu/ContextMenuSub.vue +19 -0
  149. package/src/components/ui/context-menu/ContextMenuSubContent.vue +35 -0
  150. package/src/components/ui/context-menu/ContextMenuSubTrigger.vue +34 -0
  151. package/src/components/ui/context-menu/ContextMenuTrigger.vue +13 -0
  152. package/src/components/ui/context-menu/index.ts +14 -0
  153. package/src/components/ui/data-table/DataTable.vue +167 -0
  154. package/src/components/ui/data-table/DataTablePagination.vue +112 -0
  155. package/src/components/ui/data-table/index.ts +3 -0
  156. package/src/components/ui/data-table/types.ts +48 -0
  157. package/src/components/ui/dialog/Dialog.vue +14 -0
  158. package/src/components/ui/dialog/DialogClose.vue +11 -0
  159. package/src/components/ui/dialog/DialogContent.vue +61 -0
  160. package/src/components/ui/dialog/DialogDescription.vue +24 -0
  161. package/src/components/ui/dialog/DialogFooter.vue +19 -0
  162. package/src/components/ui/dialog/DialogHeader.vue +16 -0
  163. package/src/components/ui/dialog/DialogScrollContent.vue +65 -0
  164. package/src/components/ui/dialog/DialogTitle.vue +29 -0
  165. package/src/components/ui/dialog/DialogTrigger.vue +11 -0
  166. package/src/components/ui/dialog/index.ts +9 -0
  167. package/src/components/ui/drawer/Drawer.vue +19 -0
  168. package/src/components/ui/drawer/DrawerContent.vue +28 -0
  169. package/src/components/ui/drawer/DrawerDescription.vue +20 -0
  170. package/src/components/ui/drawer/DrawerFooter.vue +14 -0
  171. package/src/components/ui/drawer/DrawerHeader.vue +14 -0
  172. package/src/components/ui/drawer/DrawerOverlay.vue +18 -0
  173. package/src/components/ui/drawer/DrawerTitle.vue +20 -0
  174. package/src/components/ui/drawer/index.ts +8 -0
  175. package/src/components/ui/dropdown-menu/DropdownMenu.vue +14 -0
  176. package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
  177. package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +44 -0
  178. package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
  179. package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +28 -0
  180. package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +24 -0
  181. package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +19 -0
  182. package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
  183. package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +22 -0
  184. package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +14 -0
  185. package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
  186. package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +36 -0
  187. package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +33 -0
  188. package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +13 -0
  189. package/src/components/ui/dropdown-menu/index.ts +16 -0
  190. package/src/components/ui/hover-card/HoverCard.vue +14 -0
  191. package/src/components/ui/hover-card/HoverCardContent.vue +41 -0
  192. package/src/components/ui/hover-card/HoverCardTrigger.vue +11 -0
  193. package/src/components/ui/hover-card/index.ts +3 -0
  194. package/src/components/ui/index.ts +41 -0
  195. package/src/components/ui/input/Input.vue +24 -0
  196. package/src/components/ui/input/index.ts +1 -0
  197. package/src/components/ui/label/Label.vue +27 -0
  198. package/src/components/ui/label/index.ts +1 -0
  199. package/src/components/ui/multi-select/MultiSelect.vue +141 -0
  200. package/src/components/ui/multi-select/index.ts +7 -0
  201. package/src/components/ui/notification/Notification.vue +85 -0
  202. package/src/components/ui/notification/index.ts +1 -0
  203. package/src/components/ui/number-field/NumberField.vue +23 -0
  204. package/src/components/ui/number-field/NumberFieldContent.vue +14 -0
  205. package/src/components/ui/number-field/NumberFieldDecrement.vue +25 -0
  206. package/src/components/ui/number-field/NumberFieldIncrement.vue +25 -0
  207. package/src/components/ui/number-field/NumberFieldInput.vue +8 -0
  208. package/src/components/ui/number-field/index.ts +5 -0
  209. package/src/components/ui/popover/Popover.vue +15 -0
  210. package/src/components/ui/popover/PopoverContent.vue +61 -0
  211. package/src/components/ui/popover/PopoverTrigger.vue +11 -0
  212. package/src/components/ui/popover/index.ts +3 -0
  213. package/src/components/ui/progress/Progress.vue +39 -0
  214. package/src/components/ui/progress/index.ts +1 -0
  215. package/src/components/ui/radio-group/RadioGroup.vue +25 -0
  216. package/src/components/ui/radio-group/RadioGroupItem.vue +39 -0
  217. package/src/components/ui/radio-group/index.ts +2 -0
  218. package/src/components/ui/scroll-area/ScrollArea.vue +29 -0
  219. package/src/components/ui/scroll-area/ScrollBar.vue +30 -0
  220. package/src/components/ui/scroll-area/index.ts +2 -0
  221. package/src/components/ui/scroll-pane/ScrollPane.vue +25 -0
  222. package/src/components/ui/scroll-pane/ScrollPaneHeader.vue +75 -0
  223. package/src/components/ui/scroll-pane/index.ts +2 -0
  224. package/src/components/ui/select/Select.vue +15 -0
  225. package/src/components/ui/select/SelectContent.vue +57 -0
  226. package/src/components/ui/select/SelectGroup.vue +19 -0
  227. package/src/components/ui/select/SelectItem.vue +47 -0
  228. package/src/components/ui/select/SelectItemText.vue +11 -0
  229. package/src/components/ui/select/SelectLabel.vue +13 -0
  230. package/src/components/ui/select/SelectScrollDownButton.vue +24 -0
  231. package/src/components/ui/select/SelectScrollUpButton.vue +24 -0
  232. package/src/components/ui/select/SelectSeparator.vue +17 -0
  233. package/src/components/ui/select/SelectTrigger.vue +45 -0
  234. package/src/components/ui/select/SelectValue.vue +11 -0
  235. package/src/components/ui/select/index.ts +11 -0
  236. package/src/components/ui/separator/Separator.vue +35 -0
  237. package/src/components/ui/separator/index.ts +1 -0
  238. package/src/components/ui/sheet/Sheet.vue +14 -0
  239. package/src/components/ui/sheet/SheetClose.vue +11 -0
  240. package/src/components/ui/sheet/SheetContent.vue +56 -0
  241. package/src/components/ui/sheet/SheetDescription.vue +22 -0
  242. package/src/components/ui/sheet/SheetFooter.vue +19 -0
  243. package/src/components/ui/sheet/SheetHeader.vue +16 -0
  244. package/src/components/ui/sheet/SheetTitle.vue +22 -0
  245. package/src/components/ui/sheet/SheetTrigger.vue +11 -0
  246. package/src/components/ui/sheet/index.ts +31 -0
  247. package/src/components/ui/skeleton/Skeleton.vue +14 -0
  248. package/src/components/ui/skeleton/index.ts +1 -0
  249. package/src/components/ui/slider/Slider.vue +66 -0
  250. package/src/components/ui/slider/index.ts +1 -0
  251. package/src/components/ui/switch/Switch.vue +37 -0
  252. package/src/components/ui/switch/index.ts +1 -0
  253. package/src/components/ui/table/Table.vue +16 -0
  254. package/src/components/ui/table/TableBody.vue +14 -0
  255. package/src/components/ui/table/TableCaption.vue +14 -0
  256. package/src/components/ui/table/TableCell.vue +14 -0
  257. package/src/components/ui/table/TableEmpty.vue +39 -0
  258. package/src/components/ui/table/TableFooter.vue +16 -0
  259. package/src/components/ui/table/TableHead.vue +21 -0
  260. package/src/components/ui/table/TableHeader.vue +14 -0
  261. package/src/components/ui/table/TableRow.vue +21 -0
  262. package/src/components/ui/table/index.ts +9 -0
  263. package/src/components/ui/tabs/Tabs.vue +15 -0
  264. package/src/components/ui/tabs/TabsContent.vue +22 -0
  265. package/src/components/ui/tabs/TabsIndicator.vue +22 -0
  266. package/src/components/ui/tabs/TabsList.vue +25 -0
  267. package/src/components/ui/tabs/TabsTrigger.vue +29 -0
  268. package/src/components/ui/tabs/index.ts +5 -0
  269. package/src/components/ui/tags-input/TagsInput.vue +22 -0
  270. package/src/components/ui/tags-input/TagsInputInput.vue +19 -0
  271. package/src/components/ui/tags-input/TagsInputItem.vue +22 -0
  272. package/src/components/ui/tags-input/TagsInputItemDelete.vue +24 -0
  273. package/src/components/ui/tags-input/TagsInputItemText.vue +19 -0
  274. package/src/components/ui/tags-input/index.ts +5 -0
  275. package/src/components/ui/textarea/Textarea.vue +24 -0
  276. package/src/components/ui/textarea/index.ts +1 -0
  277. package/src/components/ui/toast/Toast.vue +57 -0
  278. package/src/components/ui/toast/ToastAction.vue +30 -0
  279. package/src/components/ui/toast/ToastClose.vue +31 -0
  280. package/src/components/ui/toast/ToastDescription.vue +25 -0
  281. package/src/components/ui/toast/ToastTitle.vue +25 -0
  282. package/src/components/ui/toast/Toaster.vue +31 -0
  283. package/src/components/ui/toast/index.ts +8 -0
  284. package/src/components/ui/toast/use-toast.ts +136 -0
  285. package/src/components/ui/toggle/Toggle.vue +35 -0
  286. package/src/components/ui/toggle/index.ts +27 -0
  287. package/src/components/ui/toggle-group/ToggleGroup.vue +34 -0
  288. package/src/components/ui/toggle-group/ToggleGroupItem.vue +35 -0
  289. package/src/components/ui/toggle-group/index.ts +2 -0
  290. package/src/components/ui/tooltip/Tooltip.vue +14 -0
  291. package/src/components/ui/tooltip/TooltipContent.vue +31 -0
  292. package/src/components/ui/tooltip/TooltipProvider.vue +11 -0
  293. package/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
  294. package/src/components/ui/tooltip/index.ts +4 -0
  295. package/src/composables/glass/index.ts +8 -0
  296. package/src/composables/glass/useGlassRenderer.ts +252 -0
  297. package/src/composables/glass/webgl/frostShader.ts +221 -0
  298. package/src/composables/glass/webgpu/glassShader.wgsl +173 -0
  299. package/src/composables/index.ts +32 -0
  300. package/src/composables/infinite-scroll/index.ts +2 -0
  301. package/src/composables/infinite-scroll/types.ts +25 -0
  302. package/src/composables/infinite-scroll/useInfiniteScroll.ts +101 -0
  303. package/src/composables/interaction/index.ts +5 -0
  304. package/src/composables/interaction/useHeightTransition.ts +82 -0
  305. package/src/composables/interaction/useHoverPopover.ts +64 -0
  306. package/src/composables/interaction/useHoverToggle.ts +103 -0
  307. package/src/composables/interaction/useLeaveTimer.ts +17 -0
  308. package/src/composables/interaction/useTouchGate.ts +207 -0
  309. package/src/composables/pagination/index.ts +2 -0
  310. package/src/composables/pagination/useOffsetPagination.ts +70 -0
  311. package/src/composables/prng.ts +32 -0
  312. package/src/composables/useCharSplit.ts +31 -0
  313. package/src/composables/useClipboard.ts +46 -0
  314. package/src/composables/useGlobalDark.ts +61 -0
  315. package/src/composables/useKeyboardShortcuts.ts +205 -0
  316. package/src/composables/useWatercolorBlob.ts +136 -0
  317. package/src/composables/virtual/index.ts +22 -0
  318. package/src/composables/virtual/useVirtualSectionWindow.ts +338 -0
  319. package/src/composables/virtual/useWindowedStore.ts +86 -0
  320. package/src/composables/virtual/virtualSectionLayout.ts +212 -0
  321. package/src/index.ts +9 -0
  322. package/src/styles/animations.css +233 -0
  323. package/src/styles/cards.css +66 -0
  324. package/src/styles/dock.css +221 -0
  325. package/src/styles/floating-panel.css +49 -0
  326. package/src/styles/glass.css +266 -0
  327. package/src/styles/index.css +26 -0
  328. package/src/styles/scroll-pane.css +10 -0
  329. package/src/styles/theme.css +138 -0
  330. package/src/styles/tokens.css +333 -0
  331. package/src/styles/transitions.css +226 -0
  332. package/src/styles/typography.css +277 -0
  333. package/src/styles/utilities.css +697 -0
  334. package/src/utils/cn.ts +6 -0
  335. package/src/utils/index.ts +1 -0
@@ -0,0 +1,27 @@
1
+ import { type VariantProps, cva } from 'class-variance-authority'
2
+
3
+ export { default as Toggle } from './Toggle.vue'
4
+
5
+ export const toggleVariants = cva(
6
+ 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: 'bg-transparent',
11
+ outline:
12
+ 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
13
+ },
14
+ size: {
15
+ default: 'h-10 px-3',
16
+ sm: 'h-9 px-2.5',
17
+ lg: 'h-11 px-5',
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: 'default',
22
+ size: 'default',
23
+ },
24
+ },
25
+ )
26
+
27
+ export type ToggleVariants = VariantProps<typeof toggleVariants>
@@ -0,0 +1,34 @@
1
+ <script setup lang="ts">
2
+ import type { VariantProps } from 'class-variance-authority'
3
+ import { type HTMLAttributes, computed, provide } from 'vue'
4
+ import { ToggleGroupRoot, type ToggleGroupRootEmits, type ToggleGroupRootProps, useForwardPropsEmits } from 'reka-ui'
5
+ import type { toggleVariants } from '../toggle'
6
+ import { cn } from '../../../utils'
7
+
8
+ type ToggleGroupVariants = VariantProps<typeof toggleVariants>
9
+
10
+ const props = defineProps<ToggleGroupRootProps & {
11
+ class?: HTMLAttributes['class']
12
+ variant?: ToggleGroupVariants['variant']
13
+ size?: ToggleGroupVariants['size']
14
+ }>()
15
+ const emits = defineEmits<ToggleGroupRootEmits>()
16
+
17
+ provide('toggleGroup', {
18
+ variant: props.variant,
19
+ size: props.size,
20
+ })
21
+
22
+ const delegatedProps = computed(() => {
23
+ const { class: _, ...delegated } = props
24
+ return delegated
25
+ })
26
+
27
+ const forwarded = useForwardPropsEmits(delegatedProps, emits)
28
+ </script>
29
+
30
+ <template>
31
+ <ToggleGroupRoot v-bind="forwarded" :class="cn('flex items-center justify-center gap-1', props.class)">
32
+ <slot />
33
+ </ToggleGroupRoot>
34
+ </template>
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ import type { VariantProps } from 'class-variance-authority'
3
+ import { type HTMLAttributes, computed, inject } from 'vue'
4
+ import { ToggleGroupItem, type ToggleGroupItemProps, useForwardProps } from 'reka-ui'
5
+ import { toggleVariants } from '../toggle'
6
+ import { cn } from '../../../utils'
7
+
8
+ type ToggleGroupVariants = VariantProps<typeof toggleVariants>
9
+
10
+ const props = defineProps<ToggleGroupItemProps & {
11
+ class?: HTMLAttributes['class']
12
+ variant?: ToggleGroupVariants['variant']
13
+ size?: ToggleGroupVariants['size']
14
+ }>()
15
+
16
+ const context = inject<ToggleGroupVariants>('toggleGroup')
17
+
18
+ const delegatedProps = computed(() => {
19
+ const { class: _, variant, size, ...delegated } = props
20
+ return delegated
21
+ })
22
+
23
+ const forwardedProps = useForwardProps(delegatedProps)
24
+ </script>
25
+
26
+ <template>
27
+ <ToggleGroupItem
28
+ v-bind="forwardedProps" :class="cn(toggleVariants({
29
+ variant: context?.variant || variant,
30
+ size: context?.size || size,
31
+ }), props.class)"
32
+ >
33
+ <slot />
34
+ </ToggleGroupItem>
35
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as ToggleGroup } from './ToggleGroup.vue'
2
+ export { default as ToggleGroupItem } from './ToggleGroupItem.vue'
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import { TooltipRoot, type TooltipRootEmits, type TooltipRootProps, useForwardPropsEmits } from 'reka-ui'
3
+
4
+ const props = defineProps<TooltipRootProps>()
5
+ const emits = defineEmits<TooltipRootEmits>()
6
+
7
+ const forwarded = useForwardPropsEmits(props, emits)
8
+ </script>
9
+
10
+ <template>
11
+ <TooltipRoot v-bind="forwarded">
12
+ <slot />
13
+ </TooltipRoot>
14
+ </template>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import { type HTMLAttributes, computed } from 'vue'
3
+ import { TooltipContent, type TooltipContentEmits, type TooltipContentProps, TooltipPortal, useForwardPropsEmits } from 'reka-ui'
4
+ import { cn } from '../../../utils'
5
+
6
+ defineOptions({
7
+ inheritAttrs: false,
8
+ })
9
+
10
+ const props = withDefaults(defineProps<TooltipContentProps & { class?: HTMLAttributes['class'] }>(), {
11
+ sideOffset: 4,
12
+ })
13
+
14
+ const emits = defineEmits<TooltipContentEmits>()
15
+
16
+ const delegatedProps = computed(() => {
17
+ const { class: _, ...delegated } = props
18
+
19
+ return delegated
20
+ })
21
+
22
+ const forwarded = useForwardPropsEmits(delegatedProps, emits)
23
+ </script>
24
+
25
+ <template>
26
+ <TooltipPortal>
27
+ <TooltipContent v-bind="{ ...forwarded, ...$attrs }" :class="cn('z-popover overflow-hidden rounded-lg border bg-popover glass-elevated [backdrop-filter:var(--glass-blur-elevated)] px-3 py-1.5 text-sm text-popover-foreground shadow-xl animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', props.class)">
28
+ <slot />
29
+ </TooltipContent>
30
+ </TooltipPortal>
31
+ </template>
@@ -0,0 +1,11 @@
1
+ <script setup lang="ts">
2
+ import { TooltipProvider, type TooltipProviderProps } from 'reka-ui'
3
+
4
+ const props = defineProps<TooltipProviderProps>()
5
+ </script>
6
+
7
+ <template>
8
+ <TooltipProvider v-bind="props">
9
+ <slot />
10
+ </TooltipProvider>
11
+ </template>
@@ -0,0 +1,11 @@
1
+ <script setup lang="ts">
2
+ import { TooltipTrigger, type TooltipTriggerProps } from 'reka-ui'
3
+
4
+ const props = defineProps<TooltipTriggerProps>()
5
+ </script>
6
+
7
+ <template>
8
+ <TooltipTrigger v-bind="props">
9
+ <slot />
10
+ </TooltipTrigger>
11
+ </template>
@@ -0,0 +1,4 @@
1
+ export { default as Tooltip } from './Tooltip.vue'
2
+ export { default as TooltipContent } from './TooltipContent.vue'
3
+ export { default as TooltipTrigger } from './TooltipTrigger.vue'
4
+ export { default as TooltipProvider } from './TooltipProvider.vue'
@@ -0,0 +1,8 @@
1
+ export {
2
+ useGlassRenderer,
3
+ createGlassFilter,
4
+ destroyGlassFilter,
5
+ type GlassTier,
6
+ type GlassRendererOptions,
7
+ type GlassFilterState,
8
+ } from "./useGlassRenderer";
@@ -0,0 +1,252 @@
1
+ import { ref, onMounted } from "vue";
2
+
3
+ export type GlassTier = "svg-filter" | "css" | "fallback";
4
+
5
+ export interface GlassRendererOptions {
6
+ preferredTier?: GlassTier;
7
+ }
8
+
9
+ /**
10
+ * Detect best glass rendering tier.
11
+ *
12
+ * - svg-filter: Chromium — uses SVG feDisplacementMap inside backdrop-filter
13
+ * - css: All browsers — plain backdrop-filter blur
14
+ * - fallback: No backdrop-filter support
15
+ */
16
+ function detectTier(): GlassTier {
17
+ // SVG filter in backdrop-filter only works in Chromium
18
+ const isChromium = !!(window as any).chrome;
19
+ if (isChromium && CSS.supports("backdrop-filter", "url(#x) blur(1px)")) {
20
+ return "svg-filter";
21
+ }
22
+ if (
23
+ CSS.supports("backdrop-filter", "blur(1px)") ||
24
+ CSS.supports("-webkit-backdrop-filter", "blur(1px)")
25
+ ) {
26
+ return "css";
27
+ }
28
+ return "fallback";
29
+ }
30
+
31
+ /**
32
+ * Generate a refraction displacement map on a canvas.
33
+ *
34
+ * The displacement map encodes Snell's law refraction as RGB:
35
+ * - R channel = X displacement (128 = neutral)
36
+ * - G channel = Y displacement (128 = neutral)
37
+ * - B channel = unused (128)
38
+ *
39
+ * Content behind the glass is displaced toward the center,
40
+ * simulating light bending through a convex glass surface.
41
+ */
42
+ function generateDisplacementMap(
43
+ canvas: HTMLCanvasElement,
44
+ width: number,
45
+ height: number,
46
+ refractionStrength: number,
47
+ ): void {
48
+ canvas.width = width;
49
+ canvas.height = height;
50
+ const ctx = canvas.getContext("2d")!;
51
+ const imageData = ctx.createImageData(width, height);
52
+ const data = imageData.data;
53
+
54
+ const cx = width / 2;
55
+ const cy = height / 2;
56
+ const maxR = Math.sqrt(cx * cx + cy * cy);
57
+
58
+ for (let y = 0; y < height; y++) {
59
+ for (let x = 0; x < width; x++) {
60
+ const dx = (x - cx) / cx; // -1 to 1
61
+ const dy = (y - cy) / cy; // -1 to 1
62
+ const dist = Math.sqrt(dx * dx + dy * dy);
63
+
64
+ // Snell's law: displacement increases toward edges
65
+ // Uses a smooth lens profile (parabolic with edge falloff)
66
+ const profile = dist * (1 - dist * 0.3) * refractionStrength;
67
+
68
+ // Displacement direction: toward center
69
+ const dispX = -dx * profile;
70
+ const dispY = -dy * profile;
71
+
72
+ // Encode as 0-255 where 128 = no displacement
73
+ const i = (y * width + x) * 4;
74
+ data[i] = Math.round(128 + dispX * 127); // R = X displacement
75
+ data[i + 1] = Math.round(128 + dispY * 127); // G = Y displacement
76
+ data[i + 2] = 128; // B = unused
77
+ data[i + 3] = 255; // A = full
78
+ }
79
+ }
80
+
81
+ ctx.putImageData(imageData, 0, 0);
82
+ }
83
+
84
+ /**
85
+ * Generate a specular highlight map for Fresnel-based edge lighting.
86
+ */
87
+ function generateSpecularMap(
88
+ canvas: HTMLCanvasElement,
89
+ width: number,
90
+ height: number,
91
+ ): void {
92
+ canvas.width = width;
93
+ canvas.height = height;
94
+ const ctx = canvas.getContext("2d")!;
95
+
96
+ // Radial gradient: bright at edges (Fresnel), dim at center
97
+ const cx = width / 2;
98
+ const cy = height / 2;
99
+ const r = Math.max(cx, cy);
100
+
101
+ const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
102
+ grad.addColorStop(0, "rgba(255,255,255,0)");
103
+ grad.addColorStop(0.5, "rgba(255,255,255,0.02)");
104
+ grad.addColorStop(0.8, "rgba(255,255,255,0.06)");
105
+ grad.addColorStop(1, "rgba(255,255,255,0.12)");
106
+
107
+ ctx.fillStyle = grad;
108
+ ctx.fillRect(0, 0, width, height);
109
+
110
+ // Top-edge specular line (light catch)
111
+ const lineGrad = ctx.createLinearGradient(0, 0, width, 0);
112
+ lineGrad.addColorStop(0, "rgba(255,255,255,0)");
113
+ lineGrad.addColorStop(0.3, "rgba(255,255,255,0.15)");
114
+ lineGrad.addColorStop(0.5, "rgba(255,255,255,0.25)");
115
+ lineGrad.addColorStop(0.7, "rgba(255,255,255,0.15)");
116
+ lineGrad.addColorStop(1, "rgba(255,255,255,0)");
117
+ ctx.fillStyle = lineGrad;
118
+ ctx.fillRect(0, 0, width, 2);
119
+ }
120
+
121
+ let filterCounter = 0;
122
+
123
+ export interface GlassFilterState {
124
+ filterId: string;
125
+ svgEl: SVGSVGElement;
126
+ dispCanvas: HTMLCanvasElement;
127
+ specCanvas: HTMLCanvasElement;
128
+ observer: ResizeObserver | null;
129
+ }
130
+
131
+ /**
132
+ * Create an SVG filter for glass refraction + frost.
133
+ *
134
+ * Architecture (Chromium SVG displacement approach):
135
+ * 1. Hidden canvas generates Snell's law displacement map
136
+ * 2. Inline SVG <filter> references the canvas via <feImage>
137
+ * 3. <feDisplacementMap> warps the backdrop using R/G channels
138
+ * 4. CSS `backdrop-filter: url(#filterId) blur(Xpx) saturate(Y)` applies it
139
+ *
140
+ * This naturally composites with whatever is behind the element —
141
+ * no texture capture, no WebGL context, no scroll sync needed.
142
+ */
143
+ export function createGlassFilter(
144
+ el: HTMLElement,
145
+ opts: {
146
+ blur?: number;
147
+ refraction?: number;
148
+ chromaticAberration?: number;
149
+ } = {},
150
+ ): GlassFilterState {
151
+ const id = `glass-refract-${filterCounter++}`;
152
+ const blur = opts.blur ?? 16;
153
+ const refraction = opts.refraction ?? 0.3;
154
+ const _chromatic = opts.chromaticAberration ?? 0;
155
+
156
+ // Create displacement map canvas
157
+ const dispCanvas = document.createElement("canvas");
158
+ dispCanvas.style.display = "none";
159
+ document.body.appendChild(dispCanvas);
160
+
161
+ // Create specular map canvas
162
+ const specCanvas = document.createElement("canvas");
163
+ specCanvas.style.display = "none";
164
+ document.body.appendChild(specCanvas);
165
+
166
+ // Generate maps at element size
167
+ const rect = el.getBoundingClientRect();
168
+ const mapW = Math.max(Math.ceil(rect.width / 4), 32); // Low-res is fine for displacement
169
+ const mapH = Math.max(Math.ceil(rect.height / 4), 32);
170
+ generateDisplacementMap(dispCanvas, mapW, mapH, refraction);
171
+ generateSpecularMap(specCanvas, mapW, mapH);
172
+
173
+ // Create inline SVG filter
174
+ const svgNS = "http://www.w3.org/2000/svg";
175
+ const svg = document.createElementNS(svgNS, "svg");
176
+ svg.setAttribute("width", "0");
177
+ svg.setAttribute("height", "0");
178
+ svg.style.position = "absolute";
179
+ svg.style.pointerEvents = "none";
180
+
181
+ // Displacement scale maps to pixel displacement
182
+ const dispScale = refraction * 30;
183
+
184
+ svg.innerHTML = `
185
+ <filter id="${id}" x="0%" y="0%" width="100%" height="100%" color-interpolation-filters="sRGB">
186
+ <feImage href="${dispCanvas.toDataURL()}" result="dispMap"
187
+ preserveAspectRatio="none" x="0%" y="0%" width="100%" height="100%" />
188
+ <feDisplacementMap in="SourceGraphic" in2="dispMap" result="refracted"
189
+ scale="${dispScale}" xChannelSelector="R" yChannelSelector="G" />
190
+ <feGaussianBlur in="refracted" stdDeviation="${blur * 0.3}" result="blurred" />
191
+ <feImage href="${specCanvas.toDataURL()}" result="specular"
192
+ preserveAspectRatio="none" x="0%" y="0%" width="100%" height="100%" />
193
+ <feBlend in="blurred" in2="specular" mode="screen" result="final" />
194
+ </filter>
195
+ `;
196
+
197
+ document.body.appendChild(svg);
198
+
199
+ // Apply the filter + additional CSS frost
200
+ el.style.backdropFilter = `url(#${id}) blur(${blur * 0.15}px) saturate(1.6) brightness(1.05)`;
201
+ el.style.setProperty("-webkit-backdrop-filter", el.style.backdropFilter);
202
+
203
+ // Add visible glass border
204
+ el.style.border = "1px solid rgba(255,255,255,0.25)";
205
+ el.style.boxShadow = `
206
+ inset 0 0.5px 0 0 rgba(255,255,255,0.3),
207
+ inset 0 -0.5px 0 0 rgba(0,0,0,0.05),
208
+ 0 4px 16px rgba(0,0,0,0.08)
209
+ `;
210
+
211
+ // Resize handler
212
+ const observer = new ResizeObserver(() => {
213
+ const r = el.getBoundingClientRect();
214
+ const w = Math.max(Math.ceil(r.width / 4), 32);
215
+ const h = Math.max(Math.ceil(r.height / 4), 32);
216
+ generateDisplacementMap(dispCanvas, w, h, refraction);
217
+ generateSpecularMap(specCanvas, w, h);
218
+
219
+ // Update SVG feImage hrefs
220
+ const feImages = svg.querySelectorAll("feImage");
221
+ if (feImages[0]) feImages[0].setAttribute("href", dispCanvas.toDataURL());
222
+ if (feImages[1]) feImages[1].setAttribute("href", specCanvas.toDataURL());
223
+ });
224
+ observer.observe(el);
225
+
226
+ return { filterId: id, svgEl: svg, dispCanvas, specCanvas, observer };
227
+ }
228
+
229
+ /**
230
+ * Remove a glass filter and clean up DOM elements.
231
+ */
232
+ export function destroyGlassFilter(state: GlassFilterState) {
233
+ state.observer?.disconnect();
234
+ state.svgEl.remove();
235
+ state.dispCanvas.remove();
236
+ state.specCanvas.remove();
237
+ }
238
+
239
+ /**
240
+ * Composable for tiered glass rendering.
241
+ */
242
+ export function useGlassRenderer(options?: GlassRendererOptions) {
243
+ const tier = ref<GlassTier>(options?.preferredTier ?? "css");
244
+
245
+ onMounted(() => {
246
+ if (!options?.preferredTier) {
247
+ tier.value = detectTier();
248
+ }
249
+ });
250
+
251
+ return { tier };
252
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * WebGL frost/refraction fragment shader for glass-ui.
3
+ *
4
+ * Features:
5
+ * - Frosted glass blur (Gaussian kernel with spatial variation)
6
+ * - Snell's law refraction displacement
7
+ * - Fresnel-based specular highlights
8
+ * - Chromatic aberration at glass edges
9
+ */
10
+
11
+ /** Vertex shader — full-screen quad */
12
+ export const VERTEX_SHADER = /* glsl */ `#version 300 es
13
+ precision highp float;
14
+
15
+ in vec2 a_position;
16
+ in vec2 a_texCoord;
17
+
18
+ out vec2 v_texCoord;
19
+
20
+ void main() {
21
+ gl_Position = vec4(a_position, 0.0, 1.0);
22
+ v_texCoord = a_texCoord;
23
+ }
24
+ `;
25
+
26
+ /** Fragment shader — frosted glass with refraction and chromatic aberration */
27
+ export const FRAGMENT_SHADER = /* glsl */ `#version 300 es
28
+ precision highp float;
29
+
30
+ uniform sampler2D u_background; // Captured page content behind glass
31
+ uniform vec2 u_resolution; // Canvas resolution (px)
32
+ uniform vec4 u_glassBounds; // Glass panel rect (x, y, w, h) in UV space
33
+ uniform float u_blurRadius; // Base blur radius (0-40)
34
+ uniform float u_refractionStrength; // Refraction displacement (0-1)
35
+ uniform float u_chromaticAberration; // Edge chromatic aberration (0-1)
36
+ uniform vec2 u_lightPos; // Light source position (UV, 0-1)
37
+ uniform float u_time; // Animation time (seconds)
38
+
39
+ in vec2 v_texCoord;
40
+ out vec4 fragColor;
41
+
42
+ // Gaussian weight for blur kernel
43
+ float gaussian(float x, float sigma) {
44
+ return exp(-(x * x) / (2.0 * sigma * sigma));
45
+ }
46
+
47
+ // Distance from point to nearest edge of glass panel (in UV space)
48
+ float edgeDistance(vec2 uv) {
49
+ vec2 glassMin = u_glassBounds.xy;
50
+ vec2 glassMax = u_glassBounds.xy + u_glassBounds.zw;
51
+ vec2 d = min(uv - glassMin, glassMax - uv);
52
+ return min(d.x, d.y);
53
+ }
54
+
55
+ // Fresnel approximation (Schlick)
56
+ float fresnel(vec3 viewDir, vec3 normal, float ior) {
57
+ float r0 = pow((1.0 - ior) / (1.0 + ior), 2.0);
58
+ return r0 + (1.0 - r0) * pow(1.0 - max(dot(viewDir, normal), 0.0), 5.0);
59
+ }
60
+
61
+ void main() {
62
+ vec2 uv = v_texCoord;
63
+ vec2 pixelSize = 1.0 / u_resolution;
64
+
65
+ // Check if fragment is inside the glass panel
66
+ vec2 glassMin = u_glassBounds.xy;
67
+ vec2 glassMax = u_glassBounds.xy + u_glassBounds.zw;
68
+
69
+ if (uv.x < glassMin.x || uv.x > glassMax.x ||
70
+ uv.y < glassMin.y || uv.y > glassMax.y) {
71
+ fragColor = texture(u_background, uv);
72
+ return;
73
+ }
74
+
75
+ // Progressive blur — stronger at center, weaker at edges
76
+ float edgeDist = edgeDistance(uv);
77
+ float maxEdgeDist = min(u_glassBounds.z, u_glassBounds.w) * 0.5;
78
+ float blurFactor = smoothstep(0.0, maxEdgeDist * 0.3, edgeDist);
79
+ float effectiveBlur = u_blurRadius * (0.4 + 0.6 * blurFactor);
80
+
81
+ // Refraction displacement (Snell's law approximation)
82
+ vec2 center = u_glassBounds.xy + u_glassBounds.zw * 0.5;
83
+ vec2 fromCenter = uv - center;
84
+ vec2 refractionOffset = fromCenter * u_refractionStrength * 0.02;
85
+
86
+ // Gaussian blur with refraction offset
87
+ vec3 blurColor = vec3(0.0);
88
+ float totalWeight = 0.0;
89
+
90
+ int samples = int(min(effectiveBlur * 2.0, 20.0));
91
+ float sigma = max(effectiveBlur * 0.5, 0.1);
92
+
93
+ for (int x = -10; x <= 10; x++) {
94
+ for (int y = -10; y <= 10; y++) {
95
+ if (abs(x) > samples || abs(y) > samples) continue;
96
+
97
+ vec2 offset = vec2(float(x), float(y)) * pixelSize * effectiveBlur * 0.1;
98
+ float weight = gaussian(float(x), sigma) * gaussian(float(y), sigma);
99
+
100
+ vec2 sampleUV = uv + offset + refractionOffset;
101
+ blurColor += texture(u_background, sampleUV).rgb * weight;
102
+ totalWeight += weight;
103
+ }
104
+ }
105
+ blurColor /= totalWeight;
106
+
107
+ // Chromatic aberration at edges
108
+ if (u_chromaticAberration > 0.0) {
109
+ float edgeStrength = 1.0 - smoothstep(0.0, maxEdgeDist * 0.15, edgeDist);
110
+ vec2 aberrationDir = normalize(fromCenter) * edgeStrength * u_chromaticAberration * 0.003;
111
+
112
+ float r = texture(u_background, uv + refractionOffset + aberrationDir).r;
113
+ float b = texture(u_background, uv + refractionOffset - aberrationDir).b;
114
+ blurColor.r = mix(blurColor.r, r, edgeStrength * 0.5);
115
+ blurColor.b = mix(blurColor.b, b, edgeStrength * 0.5);
116
+ }
117
+
118
+ // Specular highlight (Fresnel)
119
+ vec3 viewDir = vec3(0.0, 0.0, 1.0);
120
+ vec2 normalXY = fromCenter / (u_glassBounds.zw * 0.5);
121
+ vec3 normal = normalize(vec3(normalXY * 0.3, 1.0));
122
+ float spec = fresnel(viewDir, normal, 1.5);
123
+
124
+ // Light-source-aware specular
125
+ vec2 lightDir = normalize(u_lightPos - uv);
126
+ float lightDot = max(dot(normalXY, lightDir), 0.0);
127
+ spec *= 0.3 + 0.7 * lightDot;
128
+
129
+ // Combine: frosted blur + specular + slight tint
130
+ vec3 tint = vec3(1.0, 1.0, 1.0); // neutral tint
131
+ vec3 result = blurColor * tint + vec3(spec * 0.15);
132
+
133
+ // Glass opacity with frosted appearance
134
+ float alpha = 0.85 + spec * 0.1;
135
+ fragColor = vec4(result, alpha);
136
+ }
137
+ `;
138
+
139
+ /** Shader program setup — compiles and links vertex + fragment shaders */
140
+ export function createFrostProgram(
141
+ gl: WebGL2RenderingContext | WebGLRenderingContext,
142
+ ): WebGLProgram | null {
143
+ const vertShader = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
144
+ const fragShader = compileShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
145
+ if (!vertShader || !fragShader) return null;
146
+
147
+ const program = gl.createProgram();
148
+ if (!program) return null;
149
+
150
+ gl.attachShader(program, vertShader);
151
+ gl.attachShader(program, fragShader);
152
+ gl.linkProgram(program);
153
+
154
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
155
+ console.error(
156
+ "glass-ui: frost shader link error:",
157
+ gl.getProgramInfoLog(program),
158
+ );
159
+ gl.deleteProgram(program);
160
+ return null;
161
+ }
162
+
163
+ return program;
164
+ }
165
+
166
+ function compileShader(
167
+ gl: WebGL2RenderingContext | WebGLRenderingContext,
168
+ type: number,
169
+ source: string,
170
+ ): WebGLShader | null {
171
+ const shader = gl.createShader(type);
172
+ if (!shader) return null;
173
+
174
+ gl.shaderSource(shader, source);
175
+ gl.compileShader(shader);
176
+
177
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
178
+ console.error(
179
+ "glass-ui: shader compile error:",
180
+ gl.getShaderInfoLog(shader),
181
+ );
182
+ gl.deleteShader(shader);
183
+ return null;
184
+ }
185
+
186
+ return shader;
187
+ }
188
+
189
+ /** Uniform locations cache for the frost program */
190
+ export interface FrostUniforms {
191
+ u_background: WebGLUniformLocation | null;
192
+ u_resolution: WebGLUniformLocation | null;
193
+ u_glassBounds: WebGLUniformLocation | null;
194
+ u_blurRadius: WebGLUniformLocation | null;
195
+ u_refractionStrength: WebGLUniformLocation | null;
196
+ u_chromaticAberration: WebGLUniformLocation | null;
197
+ u_lightPos: WebGLUniformLocation | null;
198
+ u_time: WebGLUniformLocation | null;
199
+ }
200
+
201
+ export function getFrostUniforms(
202
+ gl: WebGL2RenderingContext | WebGLRenderingContext,
203
+ program: WebGLProgram,
204
+ ): FrostUniforms {
205
+ return {
206
+ u_background: gl.getUniformLocation(program, "u_background"),
207
+ u_resolution: gl.getUniformLocation(program, "u_resolution"),
208
+ u_glassBounds: gl.getUniformLocation(program, "u_glassBounds"),
209
+ u_blurRadius: gl.getUniformLocation(program, "u_blurRadius"),
210
+ u_refractionStrength: gl.getUniformLocation(
211
+ program,
212
+ "u_refractionStrength",
213
+ ),
214
+ u_chromaticAberration: gl.getUniformLocation(
215
+ program,
216
+ "u_chromaticAberration",
217
+ ),
218
+ u_lightPos: gl.getUniformLocation(program, "u_lightPos"),
219
+ u_time: gl.getUniformLocation(program, "u_time"),
220
+ };
221
+ }