@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.
- package/README.md +172 -0
- package/dist/glass-ui.css +1 -0
- package/dist/glass-ui.js +10019 -0
- package/dist/index.d.ts +6619 -0
- package/package.json +65 -0
- package/src/components/custom/aurora/Aurora.vue +34 -0
- package/src/components/custom/aurora/composables/color.ts +122 -0
- package/src/components/custom/aurora/composables/useAurora.ts +355 -0
- package/src/components/custom/aurora/index.ts +8 -0
- package/src/components/custom/confirm-dialog/ConfirmDialog.vue +88 -0
- package/src/components/custom/confirm-dialog/index.ts +1 -0
- package/src/components/custom/controls/DarkModeToggle.vue +96 -0
- package/src/components/custom/controls/index.ts +1 -0
- package/src/components/custom/dock/DockLayerGroup.vue +21 -0
- package/src/components/custom/dock/DockPopover.vue +263 -0
- package/src/components/custom/dock/GlassDock.vue +276 -0
- package/src/components/custom/dock/composables/index.ts +16 -0
- package/src/components/custom/dock/composables/isTeleportedTarget.ts +19 -0
- package/src/components/custom/dock/composables/useDockActionBar.ts +33 -0
- package/src/components/custom/dock/composables/useDockState.ts +301 -0
- package/src/components/custom/dock/composables/useDockTransition.ts +146 -0
- package/src/components/custom/dock/composables/useLayerTransition.ts +135 -0
- package/src/components/custom/dock/composables/usePopupMutex.ts +83 -0
- package/src/components/custom/dock/index.ts +9 -0
- package/src/components/custom/expandable-container/ExpandableContainer.vue +64 -0
- package/src/components/custom/expandable-container/index.ts +1 -0
- package/src/components/custom/glass-panel/GlassPanel.vue +98 -0
- package/src/components/custom/glass-panel/index.ts +2 -0
- package/src/components/custom/icon-tooltip/IconTooltip.vue +20 -0
- package/src/components/custom/icon-tooltip/index.ts +1 -0
- package/src/components/custom/index.ts +15 -0
- package/src/components/custom/infinite-scroll/InfiniteScroll.vue +55 -0
- package/src/components/custom/infinite-scroll/composables/index.ts +2 -0
- package/src/components/custom/infinite-scroll/composables/types.ts +23 -0
- package/src/components/custom/infinite-scroll/composables/useInfiniteScroll.ts +73 -0
- package/src/components/custom/infinite-scroll/index.ts +1 -0
- package/src/components/custom/labeled-field/LabeledInput.vue +29 -0
- package/src/components/custom/labeled-field/LabeledSelect.vue +59 -0
- package/src/components/custom/labeled-field/LabeledSlider.vue +32 -0
- package/src/components/custom/labeled-field/LabeledSwitch.vue +27 -0
- package/src/components/custom/labeled-field/index.ts +4 -0
- package/src/components/custom/metaballs/MetaballCanvas.vue +23 -0
- package/src/components/custom/metaballs/index.ts +4 -0
- package/src/components/custom/metaballs/shaders.ts +63 -0
- package/src/components/custom/metaballs/types.ts +29 -0
- package/src/components/custom/metaballs/useMetaballs.ts +252 -0
- package/src/components/custom/search/FuzzySearch.vue +589 -0
- package/src/components/custom/search/SearchBar.vue +44 -0
- package/src/components/custom/search/composables/fuzzySearchIndex.ts +224 -0
- package/src/components/custom/search/composables/index.ts +5 -0
- package/src/components/custom/search/composables/types.ts +34 -0
- package/src/components/custom/search/composables/useFuzzySearch.ts +115 -0
- package/src/components/custom/search/index.ts +7 -0
- package/src/components/custom/sidebar/ProgressiveSidebar.vue +256 -0
- package/src/components/custom/sidebar/composables/index.ts +6 -0
- package/src/components/custom/sidebar/composables/useScrollTracker.ts +242 -0
- package/src/components/custom/sidebar/composables/useSidebarFollow.ts +247 -0
- package/src/components/custom/sidebar/composables/useSidebarState.ts +72 -0
- package/src/components/custom/sidebar/composables/useTreeIndex.ts +152 -0
- package/src/components/custom/sidebar/index.ts +15 -0
- package/src/components/custom/sidebar/types.ts +50 -0
- package/src/components/custom/tabs/BouncyTabs.vue +39 -0
- package/src/components/custom/tabs/BouncyToggle.vue +352 -0
- package/src/components/custom/tabs/UnderlineTabs.vue +115 -0
- package/src/components/custom/tabs/index.ts +5 -0
- package/src/components/custom/timeline/GlassTimeline.vue +174 -0
- package/src/components/custom/timeline/index.ts +1 -0
- package/src/components/custom/typewriter/TypewriterText.vue +239 -0
- package/src/components/custom/typewriter/composables/index.ts +1 -0
- package/src/components/custom/typewriter/composables/useTypewriter.ts +413 -0
- package/src/components/custom/typewriter/index.ts +7 -0
- package/src/components/custom/typewriter/types.ts +159 -0
- package/src/components/custom/typewriter/utils/keyboard.ts +213 -0
- package/src/components/custom/typewriter/utils/pausePatterns.ts +55 -0
- package/src/components/custom/typewriter/utils/timing.ts +104 -0
- package/src/components/custom/typewriter/utils/typoStateMachine.ts +197 -0
- package/src/components/index.ts +2 -0
- package/src/components/ui/accordion/Accordion.vue +19 -0
- package/src/components/ui/accordion/AccordionContent.vue +24 -0
- package/src/components/ui/accordion/AccordionItem.vue +24 -0
- package/src/components/ui/accordion/AccordionTrigger.vue +39 -0
- package/src/components/ui/accordion/index.ts +4 -0
- package/src/components/ui/alert/Alert.vue +20 -0
- package/src/components/ui/alert/AlertDescription.vue +17 -0
- package/src/components/ui/alert/AlertTitle.vue +17 -0
- package/src/components/ui/alert/index.ts +23 -0
- package/src/components/ui/avatar/Avatar.vue +21 -0
- package/src/components/ui/avatar/AvatarFallback.vue +11 -0
- package/src/components/ui/avatar/AvatarImage.vue +9 -0
- package/src/components/ui/avatar/index.ts +24 -0
- package/src/components/ui/badge/Badge.vue +16 -0
- package/src/components/ui/badge/index.ts +25 -0
- package/src/components/ui/button/Button.vue +26 -0
- package/src/components/ui/button/index.ts +43 -0
- package/src/components/ui/card/Card.vue +28 -0
- package/src/components/ui/card/CardContent.vue +14 -0
- package/src/components/ui/card/CardDescription.vue +14 -0
- package/src/components/ui/card/CardFooter.vue +14 -0
- package/src/components/ui/card/CardHeader.vue +14 -0
- package/src/components/ui/card/CardTitle.vue +21 -0
- package/src/components/ui/card/index.ts +6 -0
- package/src/components/ui/carousel/Carousel.vue +53 -0
- package/src/components/ui/carousel/CarouselContent.vue +35 -0
- package/src/components/ui/carousel/CarouselItem.vue +24 -0
- package/src/components/ui/carousel/CarouselNext.vue +40 -0
- package/src/components/ui/carousel/CarouselPrevious.vue +40 -0
- package/src/components/ui/carousel/index.ts +10 -0
- package/src/components/ui/carousel/interface.ts +26 -0
- package/src/components/ui/carousel/useCarousel.ts +56 -0
- package/src/components/ui/checkbox/Checkbox.vue +33 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/collapsible/Collapsible.vue +15 -0
- package/src/components/ui/collapsible/CollapsibleContent.vue +11 -0
- package/src/components/ui/collapsible/CollapsibleTrigger.vue +11 -0
- package/src/components/ui/collapsible/index.ts +3 -0
- package/src/components/ui/combobox/Combobox.vue +17 -0
- package/src/components/ui/combobox/ComboboxAnchor.vue +23 -0
- package/src/components/ui/combobox/ComboboxEmpty.vue +21 -0
- package/src/components/ui/combobox/ComboboxGroup.vue +27 -0
- package/src/components/ui/combobox/ComboboxInput.vue +41 -0
- package/src/components/ui/combobox/ComboboxItem.vue +24 -0
- package/src/components/ui/combobox/ComboboxItemIndicator.vue +23 -0
- package/src/components/ui/combobox/ComboboxList.vue +29 -0
- package/src/components/ui/combobox/ComboboxSeparator.vue +21 -0
- package/src/components/ui/combobox/ComboboxViewport.vue +23 -0
- package/src/components/ui/combobox/index.ts +12 -0
- package/src/components/ui/command/Command.vue +30 -0
- package/src/components/ui/command/CommandDialog.vue +21 -0
- package/src/components/ui/command/CommandEmpty.vue +20 -0
- package/src/components/ui/command/CommandGroup.vue +29 -0
- package/src/components/ui/command/CommandInput.vue +33 -0
- package/src/components/ui/command/CommandItem.vue +26 -0
- package/src/components/ui/command/CommandList.vue +27 -0
- package/src/components/ui/command/CommandSeparator.vue +23 -0
- package/src/components/ui/command/CommandShortcut.vue +14 -0
- package/src/components/ui/command/index.ts +9 -0
- package/src/components/ui/context-menu/ContextMenu.vue +15 -0
- package/src/components/ui/context-menu/ContextMenuCheckboxItem.vue +40 -0
- package/src/components/ui/context-menu/ContextMenuContent.vue +36 -0
- package/src/components/ui/context-menu/ContextMenuGroup.vue +11 -0
- package/src/components/ui/context-menu/ContextMenuItem.vue +34 -0
- package/src/components/ui/context-menu/ContextMenuLabel.vue +25 -0
- package/src/components/ui/context-menu/ContextMenuPortal.vue +11 -0
- package/src/components/ui/context-menu/ContextMenuRadioGroup.vue +19 -0
- package/src/components/ui/context-menu/ContextMenuRadioItem.vue +40 -0
- package/src/components/ui/context-menu/ContextMenuSeparator.vue +20 -0
- package/src/components/ui/context-menu/ContextMenuShortcut.vue +14 -0
- package/src/components/ui/context-menu/ContextMenuSub.vue +19 -0
- package/src/components/ui/context-menu/ContextMenuSubContent.vue +35 -0
- package/src/components/ui/context-menu/ContextMenuSubTrigger.vue +34 -0
- package/src/components/ui/context-menu/ContextMenuTrigger.vue +13 -0
- package/src/components/ui/context-menu/index.ts +14 -0
- package/src/components/ui/data-table/DataTable.vue +167 -0
- package/src/components/ui/data-table/DataTablePagination.vue +112 -0
- package/src/components/ui/data-table/index.ts +3 -0
- package/src/components/ui/data-table/types.ts +48 -0
- package/src/components/ui/dialog/Dialog.vue +14 -0
- package/src/components/ui/dialog/DialogClose.vue +11 -0
- package/src/components/ui/dialog/DialogContent.vue +61 -0
- package/src/components/ui/dialog/DialogDescription.vue +24 -0
- package/src/components/ui/dialog/DialogFooter.vue +19 -0
- package/src/components/ui/dialog/DialogHeader.vue +16 -0
- package/src/components/ui/dialog/DialogScrollContent.vue +65 -0
- package/src/components/ui/dialog/DialogTitle.vue +29 -0
- package/src/components/ui/dialog/DialogTrigger.vue +11 -0
- package/src/components/ui/dialog/index.ts +9 -0
- package/src/components/ui/drawer/Drawer.vue +19 -0
- package/src/components/ui/drawer/DrawerContent.vue +28 -0
- package/src/components/ui/drawer/DrawerDescription.vue +20 -0
- package/src/components/ui/drawer/DrawerFooter.vue +14 -0
- package/src/components/ui/drawer/DrawerHeader.vue +14 -0
- package/src/components/ui/drawer/DrawerOverlay.vue +18 -0
- package/src/components/ui/drawer/DrawerTitle.vue +20 -0
- package/src/components/ui/drawer/index.ts +8 -0
- package/src/components/ui/dropdown-menu/DropdownMenu.vue +14 -0
- package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
- package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +44 -0
- package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +28 -0
- package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +24 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +19 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +22 -0
- package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +14 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +36 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +33 -0
- package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +13 -0
- package/src/components/ui/dropdown-menu/index.ts +16 -0
- package/src/components/ui/hover-card/HoverCard.vue +14 -0
- package/src/components/ui/hover-card/HoverCardContent.vue +41 -0
- package/src/components/ui/hover-card/HoverCardTrigger.vue +11 -0
- package/src/components/ui/hover-card/index.ts +3 -0
- package/src/components/ui/index.ts +41 -0
- package/src/components/ui/input/Input.vue +24 -0
- package/src/components/ui/input/index.ts +1 -0
- package/src/components/ui/label/Label.vue +27 -0
- package/src/components/ui/label/index.ts +1 -0
- package/src/components/ui/multi-select/MultiSelect.vue +141 -0
- package/src/components/ui/multi-select/index.ts +7 -0
- package/src/components/ui/notification/Notification.vue +85 -0
- package/src/components/ui/notification/index.ts +1 -0
- package/src/components/ui/number-field/NumberField.vue +23 -0
- package/src/components/ui/number-field/NumberFieldContent.vue +14 -0
- package/src/components/ui/number-field/NumberFieldDecrement.vue +25 -0
- package/src/components/ui/number-field/NumberFieldIncrement.vue +25 -0
- package/src/components/ui/number-field/NumberFieldInput.vue +8 -0
- package/src/components/ui/number-field/index.ts +5 -0
- package/src/components/ui/popover/Popover.vue +15 -0
- package/src/components/ui/popover/PopoverContent.vue +61 -0
- package/src/components/ui/popover/PopoverTrigger.vue +11 -0
- package/src/components/ui/popover/index.ts +3 -0
- package/src/components/ui/progress/Progress.vue +39 -0
- package/src/components/ui/progress/index.ts +1 -0
- package/src/components/ui/radio-group/RadioGroup.vue +25 -0
- package/src/components/ui/radio-group/RadioGroupItem.vue +39 -0
- package/src/components/ui/radio-group/index.ts +2 -0
- package/src/components/ui/scroll-area/ScrollArea.vue +29 -0
- package/src/components/ui/scroll-area/ScrollBar.vue +30 -0
- package/src/components/ui/scroll-area/index.ts +2 -0
- package/src/components/ui/scroll-pane/ScrollPane.vue +25 -0
- package/src/components/ui/scroll-pane/ScrollPaneHeader.vue +75 -0
- package/src/components/ui/scroll-pane/index.ts +2 -0
- package/src/components/ui/select/Select.vue +15 -0
- package/src/components/ui/select/SelectContent.vue +57 -0
- package/src/components/ui/select/SelectGroup.vue +19 -0
- package/src/components/ui/select/SelectItem.vue +47 -0
- package/src/components/ui/select/SelectItemText.vue +11 -0
- package/src/components/ui/select/SelectLabel.vue +13 -0
- package/src/components/ui/select/SelectScrollDownButton.vue +24 -0
- package/src/components/ui/select/SelectScrollUpButton.vue +24 -0
- package/src/components/ui/select/SelectSeparator.vue +17 -0
- package/src/components/ui/select/SelectTrigger.vue +45 -0
- package/src/components/ui/select/SelectValue.vue +11 -0
- package/src/components/ui/select/index.ts +11 -0
- package/src/components/ui/separator/Separator.vue +35 -0
- package/src/components/ui/separator/index.ts +1 -0
- package/src/components/ui/sheet/Sheet.vue +14 -0
- package/src/components/ui/sheet/SheetClose.vue +11 -0
- package/src/components/ui/sheet/SheetContent.vue +56 -0
- package/src/components/ui/sheet/SheetDescription.vue +22 -0
- package/src/components/ui/sheet/SheetFooter.vue +19 -0
- package/src/components/ui/sheet/SheetHeader.vue +16 -0
- package/src/components/ui/sheet/SheetTitle.vue +22 -0
- package/src/components/ui/sheet/SheetTrigger.vue +11 -0
- package/src/components/ui/sheet/index.ts +31 -0
- package/src/components/ui/skeleton/Skeleton.vue +14 -0
- package/src/components/ui/skeleton/index.ts +1 -0
- package/src/components/ui/slider/Slider.vue +66 -0
- package/src/components/ui/slider/index.ts +1 -0
- package/src/components/ui/switch/Switch.vue +37 -0
- package/src/components/ui/switch/index.ts +1 -0
- package/src/components/ui/table/Table.vue +16 -0
- package/src/components/ui/table/TableBody.vue +14 -0
- package/src/components/ui/table/TableCaption.vue +14 -0
- package/src/components/ui/table/TableCell.vue +14 -0
- package/src/components/ui/table/TableEmpty.vue +39 -0
- package/src/components/ui/table/TableFooter.vue +16 -0
- package/src/components/ui/table/TableHead.vue +21 -0
- package/src/components/ui/table/TableHeader.vue +14 -0
- package/src/components/ui/table/TableRow.vue +21 -0
- package/src/components/ui/table/index.ts +9 -0
- package/src/components/ui/tabs/Tabs.vue +15 -0
- package/src/components/ui/tabs/TabsContent.vue +22 -0
- package/src/components/ui/tabs/TabsIndicator.vue +22 -0
- package/src/components/ui/tabs/TabsList.vue +25 -0
- package/src/components/ui/tabs/TabsTrigger.vue +29 -0
- package/src/components/ui/tabs/index.ts +5 -0
- package/src/components/ui/tags-input/TagsInput.vue +22 -0
- package/src/components/ui/tags-input/TagsInputInput.vue +19 -0
- package/src/components/ui/tags-input/TagsInputItem.vue +22 -0
- package/src/components/ui/tags-input/TagsInputItemDelete.vue +24 -0
- package/src/components/ui/tags-input/TagsInputItemText.vue +19 -0
- package/src/components/ui/tags-input/index.ts +5 -0
- package/src/components/ui/textarea/Textarea.vue +24 -0
- package/src/components/ui/textarea/index.ts +1 -0
- package/src/components/ui/toast/Toast.vue +57 -0
- package/src/components/ui/toast/ToastAction.vue +30 -0
- package/src/components/ui/toast/ToastClose.vue +31 -0
- package/src/components/ui/toast/ToastDescription.vue +25 -0
- package/src/components/ui/toast/ToastTitle.vue +25 -0
- package/src/components/ui/toast/Toaster.vue +31 -0
- package/src/components/ui/toast/index.ts +8 -0
- package/src/components/ui/toast/use-toast.ts +136 -0
- package/src/components/ui/toggle/Toggle.vue +35 -0
- package/src/components/ui/toggle/index.ts +27 -0
- package/src/components/ui/toggle-group/ToggleGroup.vue +34 -0
- package/src/components/ui/toggle-group/ToggleGroupItem.vue +35 -0
- package/src/components/ui/toggle-group/index.ts +2 -0
- package/src/components/ui/tooltip/Tooltip.vue +14 -0
- package/src/components/ui/tooltip/TooltipContent.vue +31 -0
- package/src/components/ui/tooltip/TooltipProvider.vue +11 -0
- package/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
- package/src/components/ui/tooltip/index.ts +4 -0
- package/src/composables/glass/index.ts +8 -0
- package/src/composables/glass/useGlassRenderer.ts +252 -0
- package/src/composables/glass/webgl/frostShader.ts +221 -0
- package/src/composables/glass/webgpu/glassShader.wgsl +173 -0
- package/src/composables/index.ts +32 -0
- package/src/composables/infinite-scroll/index.ts +2 -0
- package/src/composables/infinite-scroll/types.ts +25 -0
- package/src/composables/infinite-scroll/useInfiniteScroll.ts +101 -0
- package/src/composables/interaction/index.ts +5 -0
- package/src/composables/interaction/useHeightTransition.ts +82 -0
- package/src/composables/interaction/useHoverPopover.ts +64 -0
- package/src/composables/interaction/useHoverToggle.ts +103 -0
- package/src/composables/interaction/useLeaveTimer.ts +17 -0
- package/src/composables/interaction/useTouchGate.ts +207 -0
- package/src/composables/pagination/index.ts +2 -0
- package/src/composables/pagination/useOffsetPagination.ts +70 -0
- package/src/composables/prng.ts +32 -0
- package/src/composables/useCharSplit.ts +31 -0
- package/src/composables/useClipboard.ts +46 -0
- package/src/composables/useGlobalDark.ts +61 -0
- package/src/composables/useKeyboardShortcuts.ts +205 -0
- package/src/composables/useWatercolorBlob.ts +136 -0
- package/src/composables/virtual/index.ts +22 -0
- package/src/composables/virtual/useVirtualSectionWindow.ts +338 -0
- package/src/composables/virtual/useWindowedStore.ts +86 -0
- package/src/composables/virtual/virtualSectionLayout.ts +212 -0
- package/src/index.ts +9 -0
- package/src/styles/animations.css +233 -0
- package/src/styles/cards.css +66 -0
- package/src/styles/dock.css +221 -0
- package/src/styles/floating-panel.css +49 -0
- package/src/styles/glass.css +266 -0
- package/src/styles/index.css +26 -0
- package/src/styles/scroll-pane.css +10 -0
- package/src/styles/theme.css +138 -0
- package/src/styles/tokens.css +333 -0
- package/src/styles/transitions.css +226 -0
- package/src/styles/typography.css +277 -0
- package/src/styles/utilities.css +697 -0
- package/src/utils/cn.ts +6 -0
- 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,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,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
|
+
}
|