{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"glow-border","type":"registry:ui","title":"GlowBorder","description":"Liquid-metal ring for a container's edge: two metallic conic fields turning against each other make iridescent reflections flow along a dark chrome border, while a glint rides around it and spills a soft glow; chromatic, silver and gold presets or custom tints","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["effects"],"registryDependencies":["https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/glow-border/GlowBorder.tsx","content":"\"use client\";\n\nimport type { CSSProperties } from \"react\";\nimport { cn } from \"../../utils\";\nimport \"./glow-border.css\";\n\nexport type GlowBorderPreset = \"chromatic\" | \"silver\" | \"gold\";\n\nexport interface GlowBorderProps {\n\t/** Additional classes on the overlay */\n\tclassName?: string;\n\t/** Border radius in pixels (the overlay also inherits its parent's radius) */\n\tborderRadius?: number;\n\t/**\n\t * Custom metal tints: one colour or several. Overrides `preset`; neutral\n\t * silver and shadow tones are woven in so it still reads as metal.\n\t */\n\tcolor?: string | string[];\n\t/** Width of the metal ring in pixels */\n\tborderWidth?: number;\n\t/** Length of one flow cycle of the metal, in seconds. The glint laps in 0.4× that. */\n\tduration?: number;\n\t/** Metal palette: iridescent chrome, cool steel or warm gold */\n\tpreset?: GlowBorderPreset;\n\t/** Intensity of the glint and its glow, 0–1 */\n\tstrength?: number;\n}\n\ninterface Palette {\n\t/** Bright tints, the colours the reflections flash. */\n\ttints: string[];\n\t/** The metal's body between reflections. */\n\tbody: string;\n\t/** The shadow a reflection falls off into. */\n\tshadow: string;\n}\n\n/** Palettes per preset, one per theme: light pages need deeper tones to read. */\nexport const GLOW_BORDER_PRESETS: Record<GlowBorderPreset, { dark: Palette; light: Palette }> = {\n\tchromatic: {\n\t\tdark: {\n\t\t\ttints: [\"#ffffff\", \"#f5b8d0\", \"#8fe3d9\", \"#b9a8ff\", \"#f3d58a\"],\n\t\t\tbody: \"#8a9099\",\n\t\t\tshadow: \"#2a2d33\",\n\t\t},\n\t\tlight: {\n\t\t\ttints: [\"#5b636d\", \"#d9669a\", \"#2fa89a\", \"#7a62e6\", \"#c99a2e\"],\n\t\t\tbody: \"#9aa3ad\",\n\t\t\tshadow: \"#e3e6ea\",\n\t\t},\n\t},\n\tsilver: {\n\t\tdark: {\n\t\t\ttints: [\"#ffffff\", \"#dfe4ea\", \"#b8c2cf\", \"#f2f5f8\"],\n\t\t\tbody: \"#7d848f\",\n\t\t\tshadow: \"#2b2f35\",\n\t\t},\n\t\tlight: {\n\t\t\ttints: [\"#4b525c\", \"#7d8793\", \"#5f6975\", \"#2f353d\"],\n\t\t\tbody: \"#aab2bc\",\n\t\t\tshadow: \"#e6e9ed\",\n\t\t},\n\t},\n\tgold: {\n\t\tdark: {\n\t\t\ttints: [\"#fff4cf\", \"#f7d98b\", \"#ffe7a3\", \"#e9b95c\"],\n\t\t\tbody: \"#c8973f\",\n\t\t\tshadow: \"#4a3616\",\n\t\t},\n\t\tlight: {\n\t\t\ttints: [\"#8a6424\", \"#b8862f\", \"#6e4d17\", \"#c99a2e\"],\n\t\t\tbody: \"#d9b56a\",\n\t\t\tshadow: \"#f3e8cf\",\n\t\t},\n\t},\n};\n\n/** A custom `color` turned into a metal palette: the tints, over neutral body and shadow. */\nexport function customPalette(color: string | string[], theme: \"dark\" | \"light\"): Palette {\n\tconst list = (Array.isArray(color) ? color : [color]).filter(Boolean);\n\tconst tints = list.length ? list : [\"#ffffff\"];\n\treturn theme === \"dark\"\n\t\t? { tints: [\"#ffffff\", ...tints], body: \"#7d848f\", shadow: \"#2a2d33\" }\n\t\t: { tints, body: \"#9aa3ad\", shadow: \"#e3e6ea\" };\n}\n\n/**\n * One metal field as a conic gradient: reflections (tints) alternating\n * with body and shadow, spread around the turn so no two flashes touch.\n * Two of these, centred apart and turning against each other, are what\n * make the surface look liquid rather than spun.\n */\nexport function metalField(p: Palette, angleVar: string, at: string, offset = 0): string {\n\tconst stops: string[] = [];\n\tconst n = p.tints.length;\n\tconst step = 100 / n;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst base = i * step;\n\t\tconst tint = p.tints[(i + offset) % n];\n\t\t// Mostly shadow, a little body, one sharp reflection: sparse flashes\n\t\t// on a dark ring read as polished metal, an even tone as plastic.\n\t\tstops.push(`${p.shadow} ${base.toFixed(1)}%`);\n\t\tstops.push(`${p.shadow} ${(base + step * 0.22).toFixed(1)}%`);\n\t\tstops.push(`${p.body} ${(base + step * 0.4).toFixed(1)}%`);\n\t\tstops.push(`${tint} ${(base + step * 0.5).toFixed(1)}%`);\n\t\tstops.push(`${p.body} ${(base + step * 0.6).toFixed(1)}%`);\n\t\tstops.push(`${p.shadow} ${(base + step * 0.8).toFixed(1)}%`);\n\t}\n\tstops.push(`${p.shadow} 100%`);\n\treturn `conic-gradient(from var(${angleVar}) at ${at}, ${stops.join(\", \")})`;\n}\n\n/** Both metal fields, stacked for `background` (blended in CSS). */\nexport function metalBackground(p: Palette): string {\n\treturn `${metalField(p, \"--gb-a1\", \"30% 40%\")}, ${metalField(p, \"--gb-a2\", \"72% 65%\", 2)}`;\n}\n\n/** The glint's colour trail: a short arc that brightens to white. */\nexport function glintArc(p: Palette): string {\n\tconst lead = p.tints[1] ?? p.tints[0];\n\t// a one-colour custom palette is [white, colour]: trail in the colour, not white\n\tconst tail = p.tints[2] ?? p.tints[p.tints.length - 1];\n\treturn `conic-gradient(from var(--gb-a3), transparent 0%, transparent 72%, ${tail} 84%, #ffffff 90%, ${lead} 94%, transparent 99%)`;\n}\n\nexport function GlowBorder({\n\tclassName = \"\",\n\tborderRadius = 10,\n\tcolor,\n\tborderWidth = 1.5,\n\tduration = 10,\n\tpreset = \"chromatic\",\n\tstrength = 0.8,\n}: GlowBorderProps) {\n\tconst palettes =\n\t\tcolor !== undefined\n\t\t\t? { dark: customPalette(color, \"dark\"), light: customPalette(color, \"light\") }\n\t\t\t: (GLOW_BORDER_PRESETS[preset] ?? GLOW_BORDER_PRESETS.chromatic);\n\n\tconst strengthC = Math.min(1, Math.max(0, Number.isFinite(strength) ? strength : 0.8));\n\n\t// Computed during render (a pure function of the props), so the server\n\t// markup already carries every declaration. The Svelte source writes one\n\t// style string; React takes an object, so the declarations are transposed\n\t// one for one.\n\tconst styles = {\n\t\t\"--glow-border-radius\": `${borderRadius}px`,\n\t\t\"--glow-border-width\": `${borderWidth}px`,\n\t\t\"--glow-duration\": `${duration}s`,\n\t\t\"--glow-strength\": `${strengthC}`,\n\t\t\"--gb-metal-dark\": metalBackground(palettes.dark),\n\t\t\"--gb-metal-light\": metalBackground(palettes.light),\n\t\t\"--gb-glint-dark\": glintArc(palettes.dark),\n\t\t\"--gb-glint-light\": glintArc(palettes.light),\n\t\tborderRadius: \"var(--glow-border-radius)\",\n\t} as CSSProperties;\n\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\n\t\t\t\t\"glow-border animate-glow pointer-events-none absolute inset-0 size-full rounded-[inherit]\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tstyle={styles}\n\t\t\taria-hidden=\"true\"\n\t\t>\n\t\t\t<span className=\"glow-border__halo\">\n\t\t\t\t<span></span>\n\t\t\t</span>\n\t\t\t<span className=\"glow-border__metal\"></span>\n\t\t\t<span className=\"glow-border__glint\"></span>\n\t\t</div>\n\t);\n}\n","type":"registry:ui","target":"@ui/fancy/components/glow-border/GlowBorder.tsx"},{"path":"react/src/components/glow-border/glow-border.css","content":"@property --gb-a1 {\n\tsyntax: \"<angle>\";\n\tinherits: true;\n\tinitial-value: 0deg;\n}\n\n@property --gb-a2 {\n\tsyntax: \"<angle>\";\n\tinherits: true;\n\tinitial-value: 120deg;\n}\n\n@property --gb-a3 {\n\tsyntax: \"<angle>\";\n\tinherits: true;\n\tinitial-value: 0deg;\n}\n\n/* Light pages get the deeper palette; `.dark` switches to the bright one. */\n.glow-border {\n\t--gb-metal: var(--gb-metal-light);\n\t--gb-glint: var(--gb-glint-light);\n\t/* on a light page a coloured halo reads as fog: keep it faint */\n\t--gb-halo-opacity: 0.35;\n}\n\n.dark .glow-border {\n\t--gb-metal: var(--gb-metal-dark);\n\t--gb-glint: var(--gb-glint-dark);\n\t--gb-halo-opacity: 0.7;\n}\n\n.glow-border > span,\n.glow-border__halo > span {\n\tposition: absolute;\n\tborder-radius: inherit;\n\tpointer-events: none;\n}\n\n/* keep only the padding ring: the element minus its content box */\n.glow-border__metal,\n.glow-border__glint,\n.glow-border__halo > span {\n\t-webkit-mask:\n\t\tlinear-gradient(#000 0 0) content-box,\n\t\tlinear-gradient(#000 0 0);\n\t-webkit-mask-composite: xor;\n\tmask:\n\t\tlinear-gradient(#000 0 0) content-box,\n\t\tlinear-gradient(#000 0 0);\n\tmask-composite: exclude;\n}\n\n/* Liquid metal: two fields, centred apart and turning against each other. */\n.glow-border__metal {\n\tinset: 0;\n\tpadding: var(--glow-border-width);\n\tbackground: var(--gb-metal);\n\tbackground-blend-mode: soft-light;\n}\n\n/* The glint riding the ring. */\n.glow-border__glint {\n\tinset: 0;\n\tpadding: var(--glow-border-width);\n\tbackground: var(--gb-glint);\n\tmix-blend-mode: screen;\n\topacity: var(--glow-strength);\n}\n\n/* The light it spills on both sides of the edge, blurred after masking. */\n.glow-border__halo {\n\tinset: 0;\n\tfilter: blur(6px);\n\topacity: calc(var(--glow-strength) * var(--gb-halo-opacity));\n}\n\n.glow-border__halo > span {\n\tinset: -5px;\n\tpadding: calc(var(--glow-border-width) + 9px);\n\tborder-radius: calc(var(--glow-border-radius) + 5px);\n\tbackground: var(--gb-glint);\n}\n\n.glow-border.animate-glow {\n\tanimation:\n\t\tgb-flow-1 var(--glow-duration) linear infinite,\n\t\tgb-flow-2 calc(var(--glow-duration) * 1.7) linear infinite reverse,\n\t\tgb-glint calc(var(--glow-duration) * 0.4) linear infinite;\n}\n\n@keyframes gb-flow-1 {\n\tto {\n\t\t--gb-a1: 360deg;\n\t}\n}\n\n@keyframes gb-flow-2 {\n\tfrom {\n\t\t--gb-a2: 120deg;\n\t}\n\tto {\n\t\t--gb-a2: 480deg;\n\t}\n}\n\n@keyframes gb-glint {\n\tto {\n\t\t--gb-a3: 360deg;\n\t}\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.glow-border.animate-glow {\n\t\tanimation: none;\n\t\t--gb-a3: 210deg;\n\t}\n}\n","type":"registry:ui","target":"@ui/fancy/components/glow-border/glow-border.css"},{"path":"react/src/components/glow-border/index.ts","content":"\"use client\";\n\nexport { GlowBorder, GLOW_BORDER_PRESETS } from \"./GlowBorder\";\nexport type { GlowBorderPreset, GlowBorderProps } from \"./GlowBorder\";\n","type":"registry:ui","target":"@ui/fancy/components/glow-border/index.ts"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/glow-border?framework=react"}}