@flux-ui/visuals 4.0.0-beta.5 → 4.0.0-beta.6
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/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/css/component/Visual.module.scss","../src/component/FluxVisualAnimatedColors.vue","../src/component/FluxVisualAnimatedColors.vue","../src/css/component/Attention.module.scss","../src/component/FluxVisualAttention.vue","../src/component/FluxVisualAttention.vue","../src/composable/private/useBorderBeamPulse.ts","../src/composable/private/useHighlighterGroup.ts","../src/css/component/BorderBeam.module.scss","../src/component/FluxVisualBorderBeam.vue","../src/component/FluxVisualBorderBeam.vue","../src/component/FluxVisualBorderShine.vue","../src/component/FluxVisualBorderShine.vue","../src/css/component/PatternGlow.module.scss","../src/component/FluxVisualDotPattern.vue","../src/component/FluxVisualDotPattern.vue","../src/component/FluxVisualFlickeringGrid.vue","../src/component/FluxVisualFlickeringGrid.vue","../src/component/FluxVisualGridPattern.vue","../src/component/FluxVisualGridPattern.vue","../src/css/component/Highlighter.module.scss","../src/component/FluxVisualHighlighter.vue","../src/component/FluxVisualHighlighter.vue","../src/component/FluxVisualHighlighterGroup.vue","../src/component/FluxVisualHighlighterGroup.vue","../src/css/component/Noise.module.scss","../src/component/FluxVisualNoise.vue","../src/component/FluxVisualNoise.vue","../src/css/component/NumberFlow.module.scss","../src/component/FluxVisualNumberFlow.vue","../src/component/FluxVisualNumberFlow.vue","../src/css/component/PaneIllustration.module.scss","../src/component/FluxVisualPaneIllustration.vue","../src/component/FluxVisualPaneIllustration.vue","../src/css/component/Ping.module.scss","../src/component/FluxVisualPing.vue","../src/component/FluxVisualPing.vue","../src/css/component/SlotText.module.scss","../src/component/FluxVisualSlotText.vue","../src/component/FluxVisualSlotText.vue","../src/css/component/TextScramble.module.scss","../src/component/FluxVisualTextScramble.vue","../src/component/FluxVisualTextScramble.vue","../src/css/component/TextShimmer.module.scss","../src/component/FluxVisualTextShimmer.vue","../src/component/FluxVisualTextShimmer.vue"],"sourcesContent":["@property --shine-degrees {\n syntax: '<angle>';\n initial-value: 0deg;\n inherits: false;\n}\n\n.fillVisual {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n}\n\n.animatedColors {\n composes: fillVisual;\n\n filter: blur(60px) saturate(180%);\n}\n\n.dotPattern {\n composes: fillVisual;\n\n fill: var(--surface-stroke-hover);\n pointer-events: none;\n}\n\n.flickeringGrid {\n composes: fillVisual;\n\n pointer-events: none;\n}\n\n.gridPattern {\n composes: fillVisual;\n\n fill: var(--surface-stroke-muted);\n stroke: var(--surface-stroke-hover);\n pointer-events: none;\n}\n\n.borderShine {\n position: relative;\n\n --shine-radius: var(--radius);\n --shine-mask: linear-gradient(#fff #{0} #{0}) content-box, linear-gradient(#fff #{0} #{0});\n\n &::before {\n position: absolute;\n display: block;\n inset: calc(var(--shine-offset) * -1px);\n padding: calc(var(--shine-width) * 1px);\n content: '';\n background: conic-gradient(from var(--shine-degrees), #{var(--shine-colors)});\n border-radius: var(--shine-radius);\n pointer-events: none;\n animation: borderShinePosition calc(var(--shine-duration) * 1s) linear infinite;\n mask: var(--shine-mask);\n -webkit-mask-composite: xor;\n mask-composite: exclude;\n }\n}\n\n@keyframes borderShinePosition {\n from {\n --shine-degrees: 0deg;\n }\n\n to {\n --shine-degrees: 360deg;\n }\n}\n","<template>\n <canvas\n ref=\"canvas\"\n aria-hidden=\"true\"\n :class=\"$style.animatedColors\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { mulberry32, prefersReducedMotion } from '@basmilius/utils';\n import { computed, onBeforeUnmount, ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n type Polygon = [number, number, string, PolygonPoint[]];\n type PolygonPoint = [number, number, number];\n\n const {\n colors,\n incrementor = 1,\n opacity = .5,\n seed,\n static: isStatic\n } = defineProps<{\n readonly colors?: string[];\n readonly incrementor?: number;\n readonly opacity?: number;\n readonly seed?: number;\n readonly static?: boolean;\n }>();\n\n const canvasRef = useTemplateRef('canvas');\n const contextRef = ref<CanvasRenderingContext2D>();\n const animationFrame = ref(0);\n const tick = ref(0);\n const size = ref<{ width: number; height: number; } | null>(null);\n\n const instanceId = useId();\n const inView = useInView(canvasRef, {initial: true});\n const reducedMotion = prefersReducedMotion();\n\n const polygons = computed(() => {\n if (!colors || colors.length === 0) {\n return [];\n }\n\n const mulberry = mulberry32(seed ?? hashId(instanceId));\n const polygons: Polygon[] = [];\n\n for (const color of colors) {\n const localMulberry = mulberry.fork();\n\n const x = colors.length === 1 ? .5 : localMulberry.next();\n const y = colors.length === 1 ? .5 : localMulberry.next();\n const count = Math.round(localMulberry.nextBetween(6, 9));\n const points: PolygonPoint[] = [];\n\n for (let p = 0; p < count; ++p) {\n points.push([\n localMulberry.next(),\n localMulberry.next(),\n localMulberry.next()\n ]);\n }\n\n polygons.push([x, y, color, points]);\n }\n\n return polygons;\n });\n\n watch(canvasRef, (canvas, _, onCleanup) => {\n if (!canvas) {\n contextRef.value = undefined;\n size.value = null;\n return;\n }\n\n contextRef.value = canvas.getContext('2d', {\n alpha: true,\n colorSpace: 'display-p3'\n })!;\n\n if (typeof ResizeObserver === 'undefined') {\n size.value = {width: canvas.offsetWidth, height: canvas.offsetHeight};\n canvas.width = canvas.offsetWidth;\n canvas.height = canvas.offsetHeight;\n return;\n }\n\n const observer = new ResizeObserver(() => {\n const width = canvas.offsetWidth;\n const height = canvas.offsetHeight;\n\n if (!width || !height || (size.value?.width === width && size.value?.height === height)) {\n return;\n }\n\n canvas.width = width;\n canvas.height = height;\n size.value = {width, height};\n });\n\n observer.observe(canvas);\n\n onCleanup(() => observer.disconnect());\n }, {immediate: true});\n\n watch([polygons, () => opacity, size, inView], () => restart());\n\n onBeforeUnmount(() => cancel());\n\n function cancel(): void {\n cancelAnimationFrame(animationFrame.value);\n animationFrame.value = 0;\n }\n\n function schedule(): void {\n animationFrame.value = requestAnimationFrame(update);\n tick.value += incrementor;\n }\n\n function update(): void {\n render();\n\n if (!isStatic && !reducedMotion && unref(inView)) {\n schedule();\n } else {\n animationFrame.value = 0;\n }\n }\n\n function render(): void {\n const context = unref(contextRef);\n const shapes = unref(polygons);\n const dimensions = unref(size);\n\n if (!context || shapes.length === 0 || !dimensions) {\n return;\n }\n\n const {width, height} = dimensions;\n const widthBasedOpacity = Math.min(1, Math.max(.15, 360 / width));\n\n context.globalAlpha = opacity * widthBasedOpacity;\n context.globalCompositeOperation = 'screen';\n context.clearRect(0, 0, width, height);\n\n for (const [tx, ty, color, shape] of shapes) {\n context.save();\n context.translate(tx * width, ty * height);\n context.beginPath();\n context.fillStyle = color;\n\n for (let i = 0; i < shape.length; ++i) {\n let [x, y, m] = shape[i];\n\n x = Math.cos(x * Math.PI * 2 + tick.value / (m * 200 + 300)) * (width * .8);\n y = Math.sin(y * Math.PI * 2 + tick.value / (m * 100 + 300)) * (height * .8);\n\n if (i === 0) {\n context.moveTo(x, y);\n } else {\n context.lineTo(x, y);\n }\n }\n\n context.closePath();\n context.fill();\n context.restore();\n }\n }\n\n function restart(): void {\n cancel();\n\n if (isStatic || reducedMotion || !unref(inView)) {\n render();\n return;\n }\n\n schedule();\n }\n\n // mulberry32 seeds on a number, so the id becomes one.\n function hashId(value: string): number {\n let hash = 0;\n\n for (let index = 0; index < value.length; index++) {\n hash = (hash * 31 + value.charCodeAt(index)) | 0;\n }\n\n return hash;\n }\n</script>\n","<template>\n <canvas\n ref=\"canvas\"\n aria-hidden=\"true\"\n :class=\"$style.animatedColors\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { mulberry32, prefersReducedMotion } from '@basmilius/utils';\n import { computed, onBeforeUnmount, ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n type Polygon = [number, number, string, PolygonPoint[]];\n type PolygonPoint = [number, number, number];\n\n const {\n colors,\n incrementor = 1,\n opacity = .5,\n seed,\n static: isStatic\n } = defineProps<{\n readonly colors?: string[];\n readonly incrementor?: number;\n readonly opacity?: number;\n readonly seed?: number;\n readonly static?: boolean;\n }>();\n\n const canvasRef = useTemplateRef('canvas');\n const contextRef = ref<CanvasRenderingContext2D>();\n const animationFrame = ref(0);\n const tick = ref(0);\n const size = ref<{ width: number; height: number; } | null>(null);\n\n const instanceId = useId();\n const inView = useInView(canvasRef, {initial: true});\n const reducedMotion = prefersReducedMotion();\n\n const polygons = computed(() => {\n if (!colors || colors.length === 0) {\n return [];\n }\n\n const mulberry = mulberry32(seed ?? hashId(instanceId));\n const polygons: Polygon[] = [];\n\n for (const color of colors) {\n const localMulberry = mulberry.fork();\n\n const x = colors.length === 1 ? .5 : localMulberry.next();\n const y = colors.length === 1 ? .5 : localMulberry.next();\n const count = Math.round(localMulberry.nextBetween(6, 9));\n const points: PolygonPoint[] = [];\n\n for (let p = 0; p < count; ++p) {\n points.push([\n localMulberry.next(),\n localMulberry.next(),\n localMulberry.next()\n ]);\n }\n\n polygons.push([x, y, color, points]);\n }\n\n return polygons;\n });\n\n watch(canvasRef, (canvas, _, onCleanup) => {\n if (!canvas) {\n contextRef.value = undefined;\n size.value = null;\n return;\n }\n\n contextRef.value = canvas.getContext('2d', {\n alpha: true,\n colorSpace: 'display-p3'\n })!;\n\n if (typeof ResizeObserver === 'undefined') {\n size.value = {width: canvas.offsetWidth, height: canvas.offsetHeight};\n canvas.width = canvas.offsetWidth;\n canvas.height = canvas.offsetHeight;\n return;\n }\n\n const observer = new ResizeObserver(() => {\n const width = canvas.offsetWidth;\n const height = canvas.offsetHeight;\n\n if (!width || !height || (size.value?.width === width && size.value?.height === height)) {\n return;\n }\n\n canvas.width = width;\n canvas.height = height;\n size.value = {width, height};\n });\n\n observer.observe(canvas);\n\n onCleanup(() => observer.disconnect());\n }, {immediate: true});\n\n watch([polygons, () => opacity, size, inView], () => restart());\n\n onBeforeUnmount(() => cancel());\n\n function cancel(): void {\n cancelAnimationFrame(animationFrame.value);\n animationFrame.value = 0;\n }\n\n function schedule(): void {\n animationFrame.value = requestAnimationFrame(update);\n tick.value += incrementor;\n }\n\n function update(): void {\n render();\n\n if (!isStatic && !reducedMotion && unref(inView)) {\n schedule();\n } else {\n animationFrame.value = 0;\n }\n }\n\n function render(): void {\n const context = unref(contextRef);\n const shapes = unref(polygons);\n const dimensions = unref(size);\n\n if (!context || shapes.length === 0 || !dimensions) {\n return;\n }\n\n const {width, height} = dimensions;\n const widthBasedOpacity = Math.min(1, Math.max(.15, 360 / width));\n\n context.globalAlpha = opacity * widthBasedOpacity;\n context.globalCompositeOperation = 'screen';\n context.clearRect(0, 0, width, height);\n\n for (const [tx, ty, color, shape] of shapes) {\n context.save();\n context.translate(tx * width, ty * height);\n context.beginPath();\n context.fillStyle = color;\n\n for (let i = 0; i < shape.length; ++i) {\n let [x, y, m] = shape[i];\n\n x = Math.cos(x * Math.PI * 2 + tick.value / (m * 200 + 300)) * (width * .8);\n y = Math.sin(y * Math.PI * 2 + tick.value / (m * 100 + 300)) * (height * .8);\n\n if (i === 0) {\n context.moveTo(x, y);\n } else {\n context.lineTo(x, y);\n }\n }\n\n context.closePath();\n context.fill();\n context.restore();\n }\n }\n\n function restart(): void {\n cancel();\n\n if (isStatic || reducedMotion || !unref(inView)) {\n render();\n return;\n }\n\n schedule();\n }\n\n // mulberry32 seeds on a number, so the id becomes one.\n function hashId(value: string): number {\n let hash = 0;\n\n for (let index = 0; index < value.length; index++) {\n hash = (hash * 31 + value.charCodeAt(index)) | 0;\n }\n\n return hash;\n }\n</script>\n",".pulse {\n animation: visualAttentionPulse calc(var(--attention-duration) * 1ms) ease-in-out;\n}\n\n.shake {\n animation: visualAttentionShake calc(var(--attention-duration) * 1ms) ease-in-out;\n}\n\n.bounce {\n animation: visualAttentionBounce calc(var(--attention-duration) * 1ms) ease;\n}\n\n.tada {\n animation: visualAttentionTada calc(var(--attention-duration) * 1ms) ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .pulse,\n .shake,\n .bounce,\n .tada {\n animation: none;\n }\n}\n\n@keyframes visualAttentionPulse {\n 0% {\n transform: scale(1);\n }\n\n 50% {\n transform: scale(1.06);\n }\n\n 100% {\n transform: scale(1);\n }\n}\n\n@keyframes visualAttentionShake {\n 0%,\n 100% {\n transform: translateX(0);\n }\n\n 20% {\n transform: translateX(-6px);\n }\n\n 40% {\n transform: translateX(6px);\n }\n\n 60% {\n transform: translateX(-3px);\n }\n\n 80% {\n transform: translateX(3px);\n }\n}\n\n@keyframes visualAttentionBounce {\n 0%,\n 100% {\n transform: translateY(0);\n }\n\n 30% {\n transform: translateY(-9px);\n }\n\n 60% {\n transform: translateY(-3px);\n }\n}\n\n@keyframes visualAttentionTada {\n 0% {\n transform: scale(1) rotate(0);\n }\n\n 10%,\n 20% {\n transform: scale(.94) rotate(-3deg);\n }\n\n 30%,\n 50%,\n 70%,\n 90% {\n transform: scale(1.06) rotate(3deg);\n }\n\n 40%,\n 60%,\n 80% {\n transform: scale(1.06) rotate(-3deg);\n }\n\n 100% {\n transform: scale(1) rotate(0);\n }\n}\n","<script lang=\"ts\">\n import { prefersReducedMotion } from '@basmilius/utils';\n import { flattenVNodeTree } from '@flux-ui/internals';\n import { clsx } from 'clsx';\n import { cloneVNode, defineComponent, Fragment, h, onBeforeUnmount, type PropType, ref, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Attention.module.scss';\n\n type AttentionEffect = 'pulse' | 'shake' | 'bounce' | 'tada';\n\n export default defineComponent({\n inheritAttrs: false,\n props: {\n duration: {default: 700, type: Number},\n effect: {default: 'pulse', type: String as PropType<AttentionEffect>},\n trigger: {default: undefined, type: null as unknown as PropType<unknown>}\n },\n emits: {\n finished: () => true\n },\n setup(props, {attrs, emit, expose, slots}) {\n const EFFECT_CLASSES: Record<AttentionEffect, string> = {\n pulse: $style.pulse,\n shake: $style.shake,\n bounce: $style.bounce,\n tada: $style.tada\n };\n\n const isPlaying = ref(false);\n\n let restartFrame = 0;\n\n // Drop the effect class and re-add it two frames later so the animation\n // restarts cleanly, even when the same effect is played back to back.\n function play(): void {\n if (prefersReducedMotion()) {\n emit('finished');\n return;\n }\n\n cancelAnimationFrame(restartFrame);\n isPlaying.value = false;\n\n restartFrame = requestAnimationFrame(() => {\n restartFrame = requestAnimationFrame(() => {\n isPlaying.value = true;\n });\n });\n }\n\n function onAnimationEnd(event: AnimationEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n isPlaying.value = false;\n emit('finished');\n }\n\n watch(() => props.trigger, () => {\n play();\n });\n\n onBeforeUnmount(() => {\n cancelAnimationFrame(restartFrame);\n });\n\n expose({\n play\n });\n\n return () => h(\n Fragment,\n flattenVNodeTree(slots.default?.() ?? []).map(vnode => cloneVNode(vnode, {\n ...attrs,\n class: clsx(\n attrs.class as string,\n isPlaying.value && EFFECT_CLASSES[props.effect]\n ),\n style: {\n '--attention-duration': props.duration\n },\n onAnimationend: onAnimationEnd\n }))\n );\n }\n });\n</script>\n","<script lang=\"ts\">\n import { prefersReducedMotion } from '@basmilius/utils';\n import { flattenVNodeTree } from '@flux-ui/internals';\n import { clsx } from 'clsx';\n import { cloneVNode, defineComponent, Fragment, h, onBeforeUnmount, type PropType, ref, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Attention.module.scss';\n\n type AttentionEffect = 'pulse' | 'shake' | 'bounce' | 'tada';\n\n export default defineComponent({\n inheritAttrs: false,\n props: {\n duration: {default: 700, type: Number},\n effect: {default: 'pulse', type: String as PropType<AttentionEffect>},\n trigger: {default: undefined, type: null as unknown as PropType<unknown>}\n },\n emits: {\n finished: () => true\n },\n setup(props, {attrs, emit, expose, slots}) {\n const EFFECT_CLASSES: Record<AttentionEffect, string> = {\n pulse: $style.pulse,\n shake: $style.shake,\n bounce: $style.bounce,\n tada: $style.tada\n };\n\n const isPlaying = ref(false);\n\n let restartFrame = 0;\n\n // Drop the effect class and re-add it two frames later so the animation\n // restarts cleanly, even when the same effect is played back to back.\n function play(): void {\n if (prefersReducedMotion()) {\n emit('finished');\n return;\n }\n\n cancelAnimationFrame(restartFrame);\n isPlaying.value = false;\n\n restartFrame = requestAnimationFrame(() => {\n restartFrame = requestAnimationFrame(() => {\n isPlaying.value = true;\n });\n });\n }\n\n function onAnimationEnd(event: AnimationEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n isPlaying.value = false;\n emit('finished');\n }\n\n watch(() => props.trigger, () => {\n play();\n });\n\n onBeforeUnmount(() => {\n cancelAnimationFrame(restartFrame);\n });\n\n expose({\n play\n });\n\n return () => h(\n Fragment,\n flattenVNodeTree(slots.default?.() ?? []).map(vnode => cloneVNode(vnode, {\n ...attrs,\n class: clsx(\n attrs.class as string,\n isPlaying.value && EFFECT_CLASSES[props.effect]\n ),\n style: {\n '--attention-duration': props.duration\n },\n onAnimationend: onAnimationEnd\n }))\n );\n }\n });\n</script>\n","import { prefersReducedMotion } from '@basmilius/utils';\nimport type { FluxVisualBorderBeamVariant } from '@flux-ui/types';\nimport { type Ref, unref, watchEffect } from 'vue';\n\ntype PulseOscillator = {\n readonly prop: string;\n readonly a: number;\n readonly b: number;\n readonly delay: number;\n readonly period: number;\n readonly unit: '' | 'px';\n};\n\ntype PulseConfig = {\n readonly huePeriod: number | null;\n readonly oscillators: PulseOscillator[];\n};\n\ntype PulseInstance = {\n readonly config: PulseConfig;\n readonly element: HTMLElement;\n};\n\nconst FRAME_INTERVAL = 1000 / 30 - 2;\nconst TWO_PI = Math.PI * 2;\n\nconst instances = new Set<PulseInstance>();\nlet lastFrame = 0;\nlet rafId: number | null = null;\n\n/**\n * Cosine ease-in-out factor in [0, 1]: 0 at phase 0/1, 1 at phase 0.5.\n *\n * @param phase The current phase within the oscillation period.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nfunction pingPong(phase: number): number {\n return (1 - Math.cos(TWO_PI * phase)) / 2;\n}\n\n/**\n * Single shared requestAnimationFrame loop, throttled to ~30fps, that drives the\n * breathing motion of every registered pulse instance by writing CSS custom\n * properties. The breathing is very slow (1.6–6.4s periods), so a capped JS loop\n * repaints the gradient layers far less often than per-instance CSS keyframes\n * running at the display refresh rate would.\n *\n * @param ts The current timestamp provided by requestAnimationFrame.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nfunction frame(ts: number): void {\n rafId = requestAnimationFrame(frame);\n\n if (ts - lastFrame < FRAME_INTERVAL) {\n return;\n }\n\n lastFrame = ts;\n\n const tSec = ts / 1000;\n\n instances.forEach(({config, element}) => {\n for (const osc of config.oscillators) {\n const phase = (tSec - osc.delay) / osc.period;\n const value = osc.a + (osc.b - osc.a) * pingPong(phase);\n\n element.style.setProperty(osc.prop, osc.unit === 'px' ? `${value.toFixed(2)}px` : value.toFixed(4));\n }\n\n if (config.huePeriod !== null) {\n const value = ((tSec / config.huePeriod) % 1) * 360;\n\n element.style.setProperty('--beam-hue', `${value.toFixed(2)}deg`);\n }\n });\n}\n\n/**\n * Registers an element to be driven by the shared pulse loop and returns a\n * cleanup function that unregisters it again, stopping the loop once no\n * instances remain.\n *\n * @param element The border beam wrapper element.\n * @param config The oscillator configuration for the instance.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nfunction registerPulseInstance(element: HTMLElement, config: PulseConfig): () => void {\n const instance: PulseInstance = {config, element};\n instances.add(instance);\n\n if (rafId === null) {\n lastFrame = 0;\n rafId = requestAnimationFrame(frame);\n }\n\n return () => {\n instances.delete(instance);\n\n if (instances.size === 0 && rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n };\n}\n\n/**\n * Builds the theme/variant/duration-tuned oscillator table for a pulse instance.\n * Kept in sync with the gradient geometry in BorderBeam.module.scss.\n *\n * @param variant The pulse variant.\n * @param isDark Whether the instance is rendered within a dark themed tree.\n * @param duration The breathing duration in seconds.\n * @param staticColors Whether the hue drift is disabled.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nfunction createPulseConfig(variant: 'pulse-inner' | 'pulse-outside', isDark: boolean, duration: number, staticColors: boolean): PulseConfig {\n const durScale = duration / 2.3;\n const isInner = variant === 'pulse-inner';\n\n const sp = isInner ? .28 : (isDark ? .28 : .36);\n const dr = isInner ? (isDark ? 33 : 40) : (isDark ? 14 : 19);\n const op = isInner ? (isDark ? .48 : .45) : (isDark ? .46 : 0);\n const gh = isInner ? (isDark ? .34 : .22) : (isDark ? .16 : .58);\n const bs = (isInner ? (isDark ? 1.9 : 2.6) : (isDark ? 2.3 : 3.7)) * durScale;\n const ss = (isInner ? (isDark ? 2.6 : 4.6) : (isDark ? 6.4 : 4.6)) * durScale;\n const ghs = (isInner ? (isDark ? 2.4 : 5.5) : (isDark ? 2.4 : 3.8)) * durScale;\n const huePeriod = isInner ? 16 : 14;\n\n return {\n huePeriod: staticColors ? null : huePeriod,\n oscillators: [\n {prop: '--beam-bw1', a: 1 - sp, b: 1 + sp * 1.1, period: ss * .9, delay: 0, unit: ''},\n {prop: '--beam-bh1', a: 1 + sp * .9, b: 1 - sp * .85, period: ss * 1.26, delay: 0, unit: ''},\n {prop: '--beam-bx1', a: -dr, b: dr * .9, period: bs * 1.6, delay: 0, unit: 'px'},\n {prop: '--beam-by1', a: dr * .55, b: -dr * .7, period: bs * 1.6, delay: 0, unit: 'px'},\n {prop: '--beam-bw2', a: 1 + sp, b: 1 - sp * .85, period: ss * 1.1, delay: 0, unit: ''},\n {prop: '--beam-bh2', a: 1 - sp * .8, b: 1 + sp * 1.05, period: ss * .81, delay: 0, unit: ''},\n {prop: '--beam-bx2', a: dr * .8, b: -dr * .9, period: bs * 1.88, delay: 0, unit: 'px'},\n {prop: '--beam-by2', a: -dr, b: dr * .65, period: bs * 1.88, delay: 0, unit: 'px'},\n {prop: '--beam-bw3', a: 1 - sp * .6, b: 1 + sp * 1.15, period: ss * .98, delay: 0, unit: ''},\n {prop: '--beam-bh3', a: 1 + sp * .75, b: 1 - sp, period: ss * 1.4, delay: 0, unit: ''},\n {prop: '--beam-bx3', a: -dr * .6, b: dr, period: bs * 1.45, delay: 0, unit: 'px'},\n {prop: '--beam-by3', a: -dr * .85, b: dr * .45, period: bs * 1.45, delay: 0, unit: 'px'},\n {prop: '--beam-bgh', a: 1 - gh, b: 1 + gh, period: ghs, delay: 0, unit: ''},\n {prop: '--beam-bop-tl', a: 1 - op, b: 1, period: bs, delay: 0, unit: ''},\n {prop: '--beam-bop-tr', a: 1 - op, b: 1, period: bs * 1.32, delay: bs * .28, unit: ''},\n {prop: '--beam-bop-bl', a: 1 - op, b: 1, period: bs * .84, delay: bs * .55, unit: ''},\n {prop: '--beam-bop-br', a: 1 - op, b: 1, period: bs * 1.58, delay: bs * .83, unit: ''}\n ]\n };\n}\n\ntype UseBorderBeamPulseOptions = {\n readonly duration: Ref<number>;\n readonly elementRef: Ref<HTMLElement | null>;\n readonly enabled: Ref<boolean>;\n readonly staticColors: Ref<boolean>;\n readonly variant: Ref<FluxVisualBorderBeamVariant>;\n};\n\n/**\n * Drives the breathing of a pulse border beam from the shared, frame-rate-capped\n * animation loop while the instance is enabled. Respects prefers-reduced-motion\n * and resolves the theme from the nearest `[dark]` ancestor.\n *\n * @param options The reactive instance options.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nexport default function useBorderBeamPulse(options: UseBorderBeamPulseOptions): void {\n watchEffect(onCleanup => {\n const variant = unref(options.variant);\n\n if (variant !== 'pulse-inner' && variant !== 'pulse-outside') {\n return;\n }\n\n const element = unref(options.elementRef);\n\n if (!element || !unref(options.enabled)) {\n return;\n }\n\n if (prefersReducedMotion()) {\n return;\n }\n\n const isDark = element.closest('[dark]') !== null;\n const config = createPulseConfig(variant, isDark, unref(options.duration), unref(options.staticColors));\n\n onCleanup(registerPulseInstance(element, config));\n });\n}\n","import type { FluxVisualHighlighterGroupProps } from '@flux-ui/types';\nimport { annotationGroup } from 'rough-notation';\nimport { inject, type InjectionKey, onScopeDispose, provide } from 'vue';\n\ntype Annotation = Parameters<typeof annotationGroup>[0][number];\n\nexport type HighlighterGroupEntry = {\n readonly element: HTMLElement;\n getAnnotation(): Annotation | null;\n};\n\nexport type HighlighterGroupContext = {\n readonly defaults: FluxVisualHighlighterGroupProps;\n add(entry: HighlighterGroupEntry): void;\n remove(entry: HighlighterGroupEntry): void;\n notify(): void;\n};\n\nconst FluxVisualHighlighterGroupInjectionKey: InjectionKey<HighlighterGroupContext> = Symbol('flux-visual-highlighter-group');\n\n/**\n * Injects the enclosing highlighter group, if any. A `FluxVisualHighlighter`\n * that finds a group registers its annotation with it and lets the group drive\n * the cascade instead of drawing itself.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nexport function useHighlighterGroupInjection(): HighlighterGroupContext | null {\n return inject(FluxVisualHighlighterGroupInjectionKey, null);\n}\n\n/**\n * Collects the annotations of the descendant highlighters and reveals them as a\n * single rough-notation group, so they draw one after another in document order\n * rather than all at once. The draw is debounced so the initial burst of child\n * registrations (and any surrounding chrome settling its layout) coalesces into\n * one cascade, and — when `whenInView` is set — it waits until the first\n * highlighter scrolls into view. rough-notation keeps the drawn annotations\n * aligned on later resizes itself.\n *\n * The reactive props object is provided as `defaults` on the group context, so\n * descendant highlighters can inherit annotation props they don't set themselves.\n *\n * @param props The reactive props of the group component.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nexport default function useHighlighterGroup(props: FluxVisualHighlighterGroupProps): void {\n const whenInView = props.whenInView ?? false;\n const entries = new Set<HighlighterGroupEntry>();\n\n let group: ReturnType<typeof annotationGroup> | null = null;\n let timer: number | undefined;\n let settleObserver: ResizeObserver | null = null;\n let inViewObserver: IntersectionObserver | null = null;\n let inView = !whenInView;\n\n function orderedAnnotations(): Annotation[] {\n return [...entries]\n .sort((a, b) => a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1)\n .map(entry => entry.getAnnotation())\n .filter((annotation): annotation is Annotation => annotation !== null);\n }\n\n function draw(): void {\n if (!inView) {\n return;\n }\n\n const annotations = orderedAnnotations();\n\n if (annotations.length === 0) {\n return;\n }\n\n // annotationGroup writes cumulative animation delays onto the annotations,\n // so a fresh group is rebuilt whenever the members change.\n group?.hide();\n group = annotationGroup(annotations);\n group.show();\n\n stopSettleWatch();\n }\n\n function schedule(): void {\n clearTimeout(timer);\n timer = setTimeout(() => draw(), 80);\n }\n\n function stopSettleWatch(): void {\n settleObserver?.disconnect();\n settleObserver = null;\n }\n\n // Draw the first cascade only once the surrounding layout has stopped moving,\n // so the animation plays over the text instead of where it sat pre-layout.\n function watchSettle(): void {\n if (settleObserver || typeof ResizeObserver === 'undefined') {\n return;\n }\n\n settleObserver = new ResizeObserver(() => schedule());\n settleObserver.observe(document.body);\n }\n\n function watchInView(element: HTMLElement): void {\n if (!whenInView || inViewObserver || typeof IntersectionObserver === 'undefined') {\n return;\n }\n\n inViewObserver = new IntersectionObserver(observed => {\n if (observed.some(entry => entry.isIntersecting)) {\n inView = true;\n inViewObserver?.disconnect();\n inViewObserver = null;\n schedule();\n }\n });\n\n inViewObserver.observe(element);\n }\n\n provide(FluxVisualHighlighterGroupInjectionKey, {\n defaults: props,\n add(entry) {\n entries.add(entry);\n watchInView(entry.element);\n watchSettle();\n schedule();\n },\n remove(entry) {\n entries.delete(entry);\n schedule();\n },\n notify() {\n schedule();\n }\n });\n\n onScopeDispose(() => {\n clearTimeout(timer);\n stopSettleWatch();\n inViewObserver?.disconnect();\n inViewObserver = null;\n group?.hide();\n group = null;\n });\n}\n","@use 'sass:list';\n@use 'sass:map';\n@use 'sass:math';\n@use 'sass:string';\n\n@property --beam-angle {\n syntax: '<angle>';\n initial-value: 0deg;\n inherits: true;\n}\n\n@property --beam-hue {\n syntax: '<angle>';\n initial-value: 0deg;\n inherits: true;\n}\n\n@property --beam-opacity {\n syntax: '<number>';\n initial-value: 0;\n inherits: true;\n}\n\n@property --beam-x {\n syntax: '<number>';\n initial-value: 0;\n inherits: true;\n}\n\n@property --beam-w {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n}\n\n@property --beam-h {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n}\n\n@property --beam-edge {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n}\n\n@property --beam-spike {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n}\n\n@property --beam-spike2 {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n}\n\n@each $name in (bw1, bh1, bw2, bh2, bw3, bh3, bgh, bop-tl, bop-tr, bop-bl, bop-br) {\n @property --beam-#{$name} {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n }\n}\n\n@each $name in (bx1, by1, bx2, by2, bx3, by3) {\n @property --beam-#{$name} {\n syntax: '<length>';\n initial-value: 0px;\n inherits: true;\n }\n}\n\n/*\n * Palette data, ported from github.com/Jakubantalik/border-beam. Geometry is\n * shared between palettes; only the colors differ. The ring geometry doubles as\n * the pulse perimeter, where each blob is assigned to a breathing size region\n * (1-3) and an opacity quadrant (tl/tr/bl/br).\n */\n\n$ring-geometry: (\n (x: 33%, y: -7.4%, w: 70, h: 40, region: 1, quad: tl),\n (x: 12%, y: -5%, w: 60, h: 35, region: 2, quad: tl),\n (x: 2.1%, y: 68.3%, w: 40, h: 70, region: 3, quad: bl),\n (x: 2.1%, y: 68.3%, w: 20, h: 35, region: 1, quad: bl),\n (x: 74.4%, y: 100%, w: 180, h: 32, region: 2, quad: br),\n (x: 55%, y: 100%, w: 85, h: 26, region: 3, quad: br),\n (x: 93.9%, y: 0%, w: 74, h: 32, region: 1, quad: tr),\n (x: 100%, y: 27.1%, w: 26, h: 42, region: 2, quad: tr),\n (x: 100%, y: 27.1%, w: 52, h: 48, region: 3, quad: tr)\n);\n\n$ring-colors: (\n colorful: ((255, 50, 100), (40, 140, 255), (50, 200, 80), (30, 185, 170), (100, 70, 255), (40, 140, 255), (255, 120, 40), (240, 50, 180), (180, 40, 240)),\n mono: ((180, 180, 180), (140, 140, 140), (160, 160, 160), (130, 130, 130), (170, 170, 170), (150, 150, 150), (190, 190, 190), (145, 145, 145), (165, 165, 165)),\n ocean: ((100, 80, 220), (60, 120, 255), (80, 100, 200), (50, 140, 220), (120, 80, 255), (70, 130, 255), (140, 100, 240), (90, 110, 230), (130, 70, 255)),\n sunset: ((255, 80, 50), (255, 160, 40), (255, 120, 60), (255, 200, 50), (255, 100, 80), (255, 180, 60), (255, 60, 60), (255, 140, 50), (255, 90, 70))\n);\n\n$sm-geometry: (\n (x: 2%, y: 68%, w: 9, h: 18),\n (x: 2%, y: 68%, w: 4, h: 8),\n (x: 72%, y: -3%, w: 59, h: 9),\n (x: 74%, y: 100%, w: 42, h: 7),\n (x: 100%, y: 27%, w: 10, h: 17),\n (x: 100%, y: 27%, w: 10, h: 18),\n (x: 100%, y: 27%, w: 5, h: 10),\n (x: 100%, y: 27%, w: 11, h: 12)\n);\n\n$sm-colors: (\n colorful: ((50, 200, 80), (30, 185, 170), (255, 120, 40), (100, 70, 255), (240, 50, 180), (180, 40, 240), (40, 140, 255), (255, 50, 100)),\n mono: ((160, 160, 160), (140, 140, 140), (180, 180, 180), (150, 150, 150), (170, 170, 170), (155, 155, 155), (145, 145, 145), (165, 165, 165)),\n ocean: ((60, 140, 200), (50, 120, 180), (100, 80, 220), (80, 100, 255), (120, 70, 240), (90, 80, 220), (70, 110, 255), (110, 90, 230)),\n sunset: ((255, 180, 50), (255, 150, 40), (255, 80, 60), (255, 100, 80), (255, 60, 80), (255, 120, 60), (255, 200, 50), (255, 90, 70))\n);\n\n$sm-inner-alphas: (\n colorful: (.5, .45, .35, .35, .3, .4, .3, .3),\n mono: (.25, .22, .17, .17, .15, .2, .15, .15),\n ocean: (.5, .45, .35, .35, .3, .4, .3, .3),\n sunset: (.5, .45, .35, .35, .3, .4, .3, .3)\n);\n\n$line-geometry-dark: (\n (w: 36, h: 36, ox: 0, oy: 2),\n (w: 30, h: 32, ox: 39, oy: 0),\n (w: 33, h: 28, ox: -36, oy: 2),\n (w: 29, h: 34, ox: -54, oy: 0),\n (w: 27, h: 30, ox: 51, oy: -1),\n (w: 36, h: 24, ox: 21, oy: 1),\n (w: 30, h: 22, ox: -21, oy: 0),\n (w: 25, h: 28, ox: 66, oy: 1),\n (w: 23, h: 30, ox: -66, oy: -1)\n);\n\n$line-geometry-light: (\n (w: 45, h: 36, ox: 0, oy: 2),\n (w: 35, h: 32, ox: 65, oy: 0),\n (w: 40, h: 28, ox: -60, oy: 2),\n (w: 35, h: 34, ox: -90, oy: 0),\n (w: 38, h: 30, ox: 85, oy: -1),\n (w: 50, h: 24, ox: 35, oy: 1),\n (w: 40, h: 22, ox: -35, oy: 0),\n (w: 35, h: 28, ox: 110, oy: 1),\n (w: 30, h: 30, ox: -110, oy: -1)\n);\n\n$line-colors: (\n colorful: (\n dark: ((255, 50, 100), (40, 180, 220), (50, 200, 80), (180, 40, 240), (255, 160, 30), (100, 70, 255), (40, 140, 255), (240, 50, 180), (30, 185, 170)),\n light: ((255, 50, 100), (40, 140, 255), (50, 200, 80), (180, 40, 240), (30, 185, 170), (100, 70, 255), (40, 140, 255), (255, 120, 40), (240, 50, 180))\n ),\n mono: (\n dark: ((200, 200, 200), (170, 170, 170), (155, 155, 155), (185, 185, 185), (165, 165, 165), (180, 180, 180), (160, 160, 160), (175, 175, 175), (190, 190, 190)),\n light: ((100, 100, 100), (80, 80, 80), (90, 90, 90), (70, 70, 70), (85, 85, 85), (95, 95, 95), (75, 75, 75), (105, 105, 105), (65, 65, 65))\n ),\n ocean: (\n dark: ((100, 80, 220), (60, 120, 255), (80, 100, 200), (130, 70, 255), (70, 130, 255), (120, 80, 255), (90, 110, 230), (110, 90, 240), (140, 100, 255)),\n light: ((80, 60, 200), (50, 100, 220), (70, 90, 190), (110, 60, 220), (60, 110, 230), (100, 70, 240), (80, 100, 210), (90, 80, 225), (120, 90, 245))\n ),\n sunset: (\n dark: ((255, 100, 60), (255, 180, 50), (255, 140, 70), (255, 80, 80), (255, 200, 60), (255, 120, 50), (255, 160, 80), (255, 90, 60), (255, 70, 70)),\n light: ((220, 80, 40), (230, 150, 30), (210, 110, 50), (200, 60, 60), (220, 170, 40), (210, 100, 30), (230, 130, 60), (190, 70, 50), (180, 50, 50))\n )\n);\n\n$line-inner-geometry: (\n (w: 33, h: 30, ox: 0, oy: 0),\n (w: 24, h: 26, ox: 39, oy: -3),\n (w: 27, h: 24, ox: -36, oy: 0),\n (w: 23, h: 28, ox: -54, oy: -2),\n (w: 24, h: 24, ox: 51, oy: -1),\n (w: 30, h: 20, ox: 21, oy: 0),\n (w: 25, h: 18, ox: -21, oy: -2),\n (w: 21, h: 24, ox: 66, oy: 0),\n (w: 18, h: 26, ox: -66, oy: -1)\n);\n\n$line-inner-colors: (\n colorful: ((255, 50, 100), (40, 180, 220), (50, 200, 80), (180, 40, 240), (255, 160, 30), (100, 70, 255), (40, 140, 255), (240, 50, 180), (30, 185, 170)),\n mono: ((200, 200, 200), (170, 170, 170), (155, 155, 155), (185, 185, 185), (165, 165, 165), (180, 180, 180), (160, 160, 160), (175, 175, 175), (190, 190, 190)),\n ocean: ((100, 80, 220), (60, 120, 255), (80, 100, 200), (130, 70, 255), (70, 130, 255), (120, 80, 255), (90, 110, 230), (110, 90, 240), (140, 100, 255)),\n sunset: ((255, 100, 60), (255, 180, 50), (255, 140, 70), (255, 80, 80), (255, 200, 60), (255, 120, 50), (255, 160, 80), (255, 90, 60), (255, 70, 70))\n);\n\n$line-inner-alphas: (.48, .42, .48, .42, .5, .45, .4, .45, .52);\n\n$line-spikes: (\n colorful: (\n dark: (primary: ((255, 60, 80), 1), secondary: ((40, 190, 180), .98)),\n light: (primary: ((200, 30, 60), 1), secondary: ((20, 150, 140), 1))\n ),\n mono: (\n dark: (primary: ((200, 200, 200), 1), secondary: ((170, 170, 170), 1)),\n light: (primary: ((80, 80, 80), 1), secondary: ((120, 120, 120), 1))\n ),\n ocean: (\n dark: (primary: ((100, 120, 255), 1), secondary: ((130, 100, 220), .98)),\n light: (primary: ((60, 60, 180), 1), secondary: ((80, 100, 200), 1))\n ),\n sunset: (\n dark: (primary: ((255, 140, 80), 1), secondary: ((255, 100, 60), .98)),\n light: (primary: ((200, 80, 40), 1), secondary: ((220, 120, 30), 1))\n )\n);\n\n$line-bloom-spikes: (\n colorful: (\n dark: ((((100, 70, 255), 1), ((100, 70, 255), 1)), (((255, 170, 40), .59), ((255, 170, 40), .29)), (((50, 200, 100), 1), ((50, 200, 100), 1)), (((200, 50, 240), .91), ((200, 50, 240), .45)), (((40, 140, 255), 1), ((40, 140, 255), 1))),\n light: ((((80, 50, 200), 1), ((80, 50, 200), .8)), (((210, 130, 0), .7), ((210, 130, 0), .46)), (((30, 160, 70), 1), ((30, 160, 70), .82)), (((160, 30, 190), 1), ((160, 30, 190), .7)), (((30, 100, 200), 1), ((30, 100, 200), .78)))\n ),\n mono: (\n dark: ((((200, 200, 200), 1), ((200, 200, 200), 1)), (((180, 180, 180), .59), ((180, 180, 180), .29)), (((190, 190, 190), 1), ((190, 190, 190), 1)), (((170, 170, 170), .91), ((170, 170, 170), .45)), (((185, 185, 185), 1), ((185, 185, 185), 1))),\n light: ((((80, 80, 80), 1), ((80, 80, 80), .8)), (((100, 100, 100), .7), ((100, 100, 100), .46)), (((70, 70, 70), 1), ((70, 70, 70), .82)), (((90, 90, 90), 1), ((90, 90, 90), .7)), (((85, 85, 85), 1), ((85, 85, 85), .78)))\n ),\n ocean: (\n dark: ((((100, 80, 255), 1), ((100, 80, 255), 1)), (((80, 130, 220), .59), ((80, 130, 220), .29)), (((60, 100, 255), 1), ((60, 100, 255), 1)), (((90, 120, 200), .91), ((90, 120, 200), .45)), (((120, 90, 255), 1), ((120, 90, 255), 1))),\n light: ((((50, 40, 180), 1), ((50, 40, 180), .8)), (((40, 80, 200), .7), ((40, 80, 200), .46)), (((30, 50, 190), 1), ((30, 50, 190), .82)), (((60, 90, 180), 1), ((60, 90, 180), .7)), (((70, 60, 200), 1), ((70, 60, 200), .78)))\n ),\n sunset: (\n dark: ((((255, 100, 80), 1), ((255, 100, 80), 1)), (((255, 150, 80), .59), ((255, 150, 80), .29)), (((255, 80, 60), 1), ((255, 80, 60), 1)), (((255, 120, 50), .91), ((255, 120, 50), .45)), (((255, 140, 70), 1), ((255, 140, 70), 1))),\n light: ((((200, 60, 30), 1), ((200, 60, 30), .8)), (((220, 100, 20), .7), ((220, 100, 20), .46)), (((180, 40, 20), 1), ((180, 40, 20), .82)), (((210, 80, 10), 1), ((210, 80, 10), .7)), (((190, 70, 30), 1), ((190, 70, 30), .78)))\n )\n);\n\n$pulse-inner-sizes: ((65, 35), (55, 30), (35, 65), (15, 30), (173, 28), (80, 22), (69, 28), (22, 38), (47, 44));\n\n$pulse-inner-bloom: (\n (ci: 1, region: 1, quad: tl, w: 84, h: 48),\n (ci: 2, region: 2, quad: tl, w: 72, h: 42),\n (ci: 3, region: 3, quad: bl, w: 48, h: 84),\n (ci: 5, region: 2, quad: br, w: 216, h: 38),\n (ci: 6, region: 3, quad: br, w: 102, h: 31),\n (ci: 7, region: 1, quad: tr, w: 89, h: 38),\n (ci: 9, region: 3, quad: tr, w: 62, h: 58)\n);\n\n$pulse-outer-core: (\n (ci: 1, region: 1, quad: tl, w: 80, h: 19, x: 27%, y: 0%),\n (ci: 7, region: 2, quad: tr, w: 74, h: 11, x: 73%, y: -1%),\n (ci: 8, region: 3, quad: tr, w: 15, h: 44, x: 100%, y: 33%),\n (ci: 9, region: 1, quad: br, w: 19, h: 38, x: 101%, y: 72%),\n (ci: 5, region: 2, quad: br, w: 84, h: 13, x: 67%, y: 100%),\n (ci: 2, region: 3, quad: bl, w: 60, h: 21, x: 24%, y: 101%),\n (ci: 3, region: 1, quad: bl, w: 17, h: 40, x: 0%, y: 60%),\n (ci: 4, region: 2, quad: tl, w: 13, h: 32, x: -1%, y: 28%)\n);\n\n$pulse-outer-bloom: (\n (ci: 1, region: 1, quad: tl, w: 110, h: 30, x: 27%, y: 3%),\n (ci: 7, region: 2, quad: tr, w: 100, h: 20, x: 73%, y: 1%),\n (ci: 8, region: 3, quad: tr, w: 26, h: 62, x: 100%, y: 33%),\n (ci: 9, region: 1, quad: br, w: 30, h: 56, x: 101%, y: 72%),\n (ci: 5, region: 2, quad: br, w: 120, h: 22, x: 67%, y: 99%),\n (ci: 2, region: 3, quad: bl, w: 88, h: 32, x: 24%, y: 99%),\n (ci: 3, region: 1, quad: bl, w: 28, h: 58, x: 0%, y: 60%)\n);\n\n@function rgb-str($c, $alpha: null) {\n @if $alpha == null or $alpha == 1 {\n @return string.unquote('rgb(#{list.nth($c, 1)}, #{list.nth($c, 2)}, #{list.nth($c, 3)})');\n }\n @return string.unquote('rgba(#{list.nth($c, 1)}, #{list.nth($c, 2)}, #{list.nth($c, 3)}, #{$alpha})');\n}\n\n@function spike-color($pair, $factor: 1) {\n $alpha: math.div(math.round(list.nth($pair, 2) * $factor * 100), 100);\n @return rgb-str(list.nth($pair, 1), $alpha);\n}\n\n@function offset-str($value) {\n @if $value < 0 {\n @return ' - #{math.abs($value)}px';\n }\n @return ' + #{$value}px';\n}\n\n// Static blobs around the perimeter (md & sm stroke/inner layers).\n@function blobs($colors, $geometry, $alphas: null, $scale: 1) {\n $grads: ();\n @for $i from 1 through list.length($geometry) {\n $g: list.nth($geometry, $i);\n $alpha: null;\n @if $alphas != null {\n @if list.length($alphas) > 1 {\n $alpha: list.nth($alphas, $i);\n } @else {\n $alpha: $alphas;\n }\n }\n $w: math.round(map.get($g, w) * $scale);\n $h: math.round(map.get($g, h) * $scale);\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse #{$w}px #{$h}px at #{map.get($g, x)} #{map.get($g, y)}, #{rgb-str(list.nth($colors, $i), $alpha)}, transparent)'), comma);\n }\n @return $grads;\n}\n\n// Traveling blobs along the bottom edge (line stroke/inner layers).\n@function line-blobs($colors, $geometry, $alphas: null) {\n $grads: ();\n @for $i from 1 through list.length($geometry) {\n $g: list.nth($geometry, $i);\n $alpha: null;\n @if $alphas != null {\n $alpha: list.nth($alphas, $i);\n }\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse calc(#{map.get($g, w)}px * var(--beam-w)) calc(#{map.get($g, h)}px * var(--beam-h)) at calc(var(--beam-x) * 100%#{offset-str(map.get($g, ox))}) calc(100%#{offset-str(map.get($g, oy))}), #{rgb-str(list.nth($colors, $i), $alpha)}, transparent)'), comma);\n }\n @return $grads;\n}\n\n// Breathing blobs whose size, drift and opacity are driven from JS (pulse layers).\n@function pulse-blobs($entries) {\n $grads: ();\n @each $e in $entries {\n $r: map.get($e, region);\n $size: 'calc(#{map.get($e, w)}px * var(--beam-bw#{$r}) * var(--beam-glow-sx, 1)) calc(#{map.get($e, h)}px * var(--beam-bh#{$r}) * var(--beam-bgh) * var(--beam-glow-sy, 1))';\n $at: 'calc(#{map.get($e, x)} + var(--beam-bx#{$r})) calc(#{map.get($e, y)} + var(--beam-by#{$r}))';\n $color: 'rgba(#{list.nth(map.get($e, c), 1)}, #{list.nth(map.get($e, c), 2)}, #{list.nth(map.get($e, c), 3)}, var(--beam-bop-#{map.get($e, quad)}))';\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse #{$size} at #{$at}, #{$color}, transparent)'), comma);\n }\n @return $grads;\n}\n\n// Frozen variant of the pulse blobs: literal sizes/positions with the time-average\n// alpha, so the heavily blurred bloom bitmap is painted once and cached instead of\n// being re-rasterized every frame.\n@function pulse-blobs-frozen($entries) {\n $grads: ();\n @each $e in $entries {\n $size: 'calc(#{map.get($e, w)}px * var(--beam-glow-sx, 1)) calc(#{map.get($e, h)}px * var(--beam-glow-sy, 1))';\n $color: 'rgba(#{list.nth(map.get($e, c), 1)}, #{list.nth(map.get($e, c), 2)}, #{list.nth(map.get($e, c), 3)}, var(--beam-pulse-frozen))';\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse #{$size} at #{map.get($e, x)} #{map.get($e, y)}, #{$color}, transparent)'), comma);\n }\n @return $grads;\n}\n\n// The 9-blob pulse perimeter, with palette geometry and colors.\n@function pulse-ring($palette) {\n $entries: ();\n @for $i from 1 through list.length($ring-geometry) {\n $entries: list.append($entries, map.merge(list.nth($ring-geometry, $i), (c: list.nth(map.get($ring-colors, $palette), $i))), comma);\n }\n @return $entries;\n}\n\n// Same perimeter with the smaller inner sizes.\n@function pulse-ring-resized($palette) {\n $ring: pulse-ring($palette);\n $entries: ();\n @for $i from 1 through list.length($ring) {\n $size: list.nth($pulse-inner-sizes, $i);\n $entries: list.append($entries, map.merge(list.nth($ring, $i), (w: list.nth($size, 1), h: list.nth($size, 2))), comma);\n }\n @return $entries;\n}\n\n// Resolve a gradient table (ci references the palette ring) to emit-ready entries.\n@function pulse-table($palette, $table) {\n $ring: pulse-ring($palette);\n $entries: ();\n @each $t in $table {\n $src: list.nth($ring, map.get($t, ci));\n $x: map.get($src, x);\n $y: map.get($src, y);\n @if map.has-key($t, x) {\n $x: map.get($t, x);\n }\n @if map.has-key($t, y) {\n $y: map.get($t, y);\n }\n $entry: (\n c: map.get($src, c),\n x: $x,\n y: $y,\n w: map.get($t, w),\n h: map.get($t, h),\n region: map.get($t, region),\n quad: map.get($t, quad)\n );\n $entries: list.append($entries, $entry, comma);\n }\n @return $entries;\n}\n\n// The desynced spike/glow stack at the bottom edge (line bloom layer).\n@function line-bloom($palette, $theme) {\n $is-dark: $theme == dark;\n $is-mono: $palette == mono;\n $spikes-def: map.get($line-spikes, $palette, $theme);\n $primary: map.get($spikes-def, primary);\n $secondary: map.get($spikes-def, secondary);\n $pairs: map.get($line-bloom-spikes, $palette, $theme);\n\n // Mono uses uniform gray, so thin spikes at full opacity look like harsh\n // bars. Attenuate the opacity and widen the thin gradients so they appear\n // as soft glows instead.\n $sc1: spike-color($primary);\n $sc2: spike-color($secondary);\n $sc1-mid: $sc1;\n $sc2-mid: rgb-str(list.nth($secondary, 1), .49);\n $thin-w1: .8px;\n $thin-w2: 2px;\n $thin-w3: 1.2px;\n $thin-w4: .6px;\n $thin-h1: 92px;\n $thin-h2: 72px;\n $thin-h3: 85px;\n $thin-h4: 60px;\n $att: 1;\n\n @if not $is-dark {\n $sc1-mid: rgb-str(list.nth($primary, 1), .85);\n $sc2-mid: rgb-str(list.nth($secondary, 1), .7);\n $thin-w4: 1px;\n }\n\n @if $is-mono {\n $sc1: spike-color($primary, .14);\n $sc2: spike-color($secondary, .12);\n $thin-w1: 12px;\n $thin-w2: 14px;\n $thin-w3: 12px;\n $thin-w4: 10px;\n\n // The light theme widens the last thin spike further for mono.\n @if not $is-dark {\n $thin-w4: 12px;\n }\n $thin-h1: 42px;\n $thin-h2: 38px;\n $thin-h3: 40px;\n $thin-h4: 32px;\n $att: .14;\n\n @if $is-dark {\n $sc1-mid: spike-color($primary, .09);\n $sc2-mid: rgb-str(list.nth($secondary, 1), .06);\n } @else {\n $sc1-mid: spike-color($primary, .11);\n $sc2-mid: spike-color($secondary, .09);\n }\n }\n\n $att2: $att;\n @if $is-mono {\n $att2: $att * .7;\n }\n\n $colors1: ();\n $colors2: ();\n @each $pair in $pairs {\n $colors1: list.append($colors1, spike-color(list.nth($pair, 1), $att), comma);\n $colors2: list.append($colors2, spike-color(list.nth($pair, 2), $att2), comma);\n }\n\n $grads: (\n string.unquote('radial-gradient(ellipse calc(#{$thin-w1} * var(--beam-spike)) calc(#{$thin-h1} * var(--beam-h)) at 8% calc(100% - 2px), #{$sc1}, #{$sc1-mid} 30%, transparent 88%)'),\n string.unquote('radial-gradient(ellipse calc(10px * var(--beam-spike2)) calc(35px * var(--beam-h)) at 22% calc(100% - 4px), #{$sc2}, #{$sc2-mid} 50%, transparent 95%)'),\n string.unquote('radial-gradient(ellipse calc(#{$thin-w2} * (2 - var(--beam-spike))) calc(#{$thin-h2} * var(--beam-h)) at 36% calc(100% - 3px), #{list.nth($colors1, 1)}, #{list.nth($colors2, 1)} 40%, transparent 90%)'),\n string.unquote('radial-gradient(ellipse calc(14px * var(--beam-spike2)) calc(28px * var(--beam-h)) at 50% calc(100% - 2px), #{list.nth($colors1, 2)}, #{list.nth($colors2, 2)} 55%, transparent 96%)'),\n string.unquote('radial-gradient(ellipse calc(#{$thin-w3} * (2 - var(--beam-spike2))) calc(#{$thin-h3} * var(--beam-h)) at 64% calc(100% - 4px), #{list.nth($colors1, 3)}, #{list.nth($colors2, 3)} 35%, transparent 89%)'),\n string.unquote('radial-gradient(ellipse calc(7px * var(--beam-spike)) calc(45px * var(--beam-h)) at 78% calc(100% - 2px), #{list.nth($colors1, 4)}, #{list.nth($colors2, 4)} 48%, transparent 94%)'),\n string.unquote('radial-gradient(ellipse calc(#{$thin-w4} * (2 - var(--beam-spike))) calc(#{$thin-h4} * var(--beam-h)) at 92% calc(100% - 3px), #{list.nth($colors1, 5)}, #{list.nth($colors2, 5)} 42%, transparent 91%)')\n );\n\n @if $is-dark {\n $dot-c: 'rgba(255, 255, 255, 1)';\n $dot-20: 'rgba(255, 255, 255, 0.9)';\n $dot-50: 'rgba(255, 255, 255, 0.5)';\n $amb-c: 'rgba(255, 255, 255, 0.3)';\n $amb-25: 'rgba(255, 255, 255, 0.12)';\n $amb-55: 'rgba(255, 255, 255, 0.03)';\n\n @if $is-mono {\n $dot-c: 'rgba(255, 255, 255, 0.5)';\n $dot-20: 'rgba(255, 255, 255, 0.45)';\n $dot-50: 'rgba(255, 255, 255, 0.25)';\n $amb-c: 'rgba(255, 255, 255, 0.15)';\n $amb-25: 'rgba(255, 255, 255, 0.06)';\n $amb-55: 'rgba(255, 255, 255, 0.015)';\n }\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse calc(21px * var(--beam-spike)) calc(15px * var(--beam-spike2)) at calc(var(--beam-x) * 100%) calc(100% + 1px), #{$dot-c} 0%, #{$dot-20} 20%, #{$dot-50} 50%, transparent 100%)'), comma);\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse calc(42px * var(--beam-w)) calc(40px * var(--beam-h)) at calc(var(--beam-x) * 100%) 100%, #{$amb-c} 0%, #{$amb-25} 25%, #{$amb-55} 55%, transparent 80%)'), comma);\n } @else {\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse calc(50px * var(--beam-w)) calc(32px * var(--beam-h)) at calc(var(--beam-x) * 100%) 100%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.18) 30%, rgba(0, 0, 0, 0.03) 60%, transparent 85%)'), comma);\n }\n\n @return $grads;\n}\n\n// Gradients that reference animated custom properties (--beam-angle, --beam-x,\n// --beam-w, ...) must be declared directly on the layer that paints them.\n// Routing them through an intermediate custom property breaks per-frame\n// updates of the dependent value in Chromium and WebKit, which makes the\n// animation stutter.\n$rotate-highlight-light: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 54%, rgba(0, 0, 0, 0.08) 57%, rgba(0, 0, 0, 0.2) 60%, rgba(0, 0, 0, 0.4) 63%, rgba(0, 0, 0, 0.55) 66%, rgba(0, 0, 0, 0.4) 69%, rgba(0, 0, 0, 0.2) 72%, rgba(0, 0, 0, 0.08) 75%, transparent 78%, transparent 100%)');\n$rotate-highlight-dark: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 54%, rgba(255, 255, 255, 0.1) 57%, rgba(255, 255, 255, 0.3) 60%, rgba(255, 255, 255, 0.6) 63%, rgba(255, 255, 255, 0.75) 66%, rgba(255, 255, 255, 0.6) 69%, rgba(255, 255, 255, 0.3) 72%, rgba(255, 255, 255, 0.1) 75%, transparent 78%, transparent 100%)');\n$rotate-bloom-light: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 58%, rgba(0, 0, 0, 0.02) 62%, rgba(0, 0, 0, 0.08) 65%, rgba(0, 0, 0, 0.2) 67%, rgba(0, 0, 0, 0.4) 69%, rgba(0, 0, 0, 0.6) 70%, rgba(0, 0, 0, 0.6) 70.5%, rgba(0, 0, 0, 0.4) 71.5%, rgba(0, 0, 0, 0.2) 73%, rgba(0, 0, 0, 0.08) 75%, rgba(0, 0, 0, 0.02) 78%, transparent 82%)');\n$rotate-bloom-dark: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 58%, rgba(255, 255, 255, 0.03) 62%, rgba(255, 255, 255, 0.08) 65%, rgba(255, 255, 255, 0.2) 67%, rgba(255, 255, 255, 0.45) 69%, rgba(255, 255, 255, 0.85) 70%, rgba(255, 255, 255, 0.85) 70.5%, rgba(255, 255, 255, 0.45) 71.5%, rgba(255, 255, 255, 0.2) 73%, rgba(255, 255, 255, 0.08) 75%, rgba(255, 255, 255, 0.03) 78%, transparent 82%)');\n$line-highlight-light: string.unquote('radial-gradient(ellipse calc(35px * var(--beam-w)) calc(28px * var(--beam-h)) at calc(var(--beam-x) * 100%) calc(100% + 2px), rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.25) 35%, transparent 70%)');\n$line-highlight-dark: string.unquote('radial-gradient(ellipse calc(24px * var(--beam-w)) calc(28px * var(--beam-h)) at calc(var(--beam-x) * 100%) calc(100% + 2px), rgba(255, 255, 255, 0.38) 0%, rgba(255, 255, 255, 0.12) 30%, transparent 65%)');\n$ring-mask: string.unquote('linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)');\n$edge-fade-y: string.unquote('linear-gradient(white, transparent 28px, transparent calc(100% - 28px), white)');\n$edge-fade-x: string.unquote('linear-gradient(to right, white, transparent 28px, transparent calc(100% - 28px), white)');\n$md-window: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 30%, rgba(255, 255, 255, 0.1) 36%, rgba(255, 255, 255, 0.35) 44%, white 52%, white 80%, rgba(255, 255, 255, 0.35) 86%, rgba(255, 255, 255, 0.1) 92%, transparent 95%, transparent 100%)');\n$sm-window: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 22%, rgba(255, 255, 255, 0.12) 28%, rgba(255, 255, 255, 0.4) 36%, white 46%, white 82%, rgba(255, 255, 255, 0.4) 88%, rgba(255, 255, 255, 0.12) 94%, transparent 97%, transparent 100%)');\n$line-window: string.unquote('radial-gradient(ellipse calc(78px * var(--beam-w)) calc(60px * var(--beam-h)) at calc(var(--beam-x) * 100%) 100%, white 0%, rgba(255, 255, 255, 0.5) 45%, transparent 100%)');\n$line-bloom-window: string.unquote('radial-gradient(ellipse calc(84px * var(--beam-w)) calc(110px * var(--beam-h)) at calc(var(--beam-x) * 100%) 100%, white 0%, rgba(255, 255, 255, 0.5) 35%, transparent 100%)');\n\n.borderBeam {\n position: relative;\n width: fit-content;\n border-radius: var(--beam-radius);\n\n --beam-hue-range: 30;\n --beam-mono: 1;\n --beam-radius: var(--radius);\n --beam-strength: 1;\n\n &::before,\n &::after {\n pointer-events: none;\n }\n}\n\n.bloom {\n position: absolute;\n display: none;\n pointer-events: none;\n}\n\n.mono {\n --beam-mono: .5;\n}\n\n@each $palette in (colorful, mono, ocean, sunset) {\n $inner-alpha: .45;\n @if $palette == mono {\n $inner-alpha: .225;\n }\n\n .#{$palette} {\n --beam-rotate-blobs: #{blobs(map.get($ring-colors, $palette), $ring-geometry)};\n --beam-rotate-inner: #{blobs(map.get($ring-colors, $palette), $ring-geometry, $inner-alpha, .9)};\n --beam-sm-blobs: #{blobs(map.get($sm-colors, $palette), $sm-geometry)};\n --beam-sm-inner: #{blobs(map.get($sm-colors, $palette), $sm-geometry, map.get($sm-inner-alphas, $palette))};\n --beam-pulse-ring: #{pulse-blobs(pulse-ring($palette))};\n --beam-pulse-inner-ring: #{pulse-blobs(pulse-ring-resized($palette))};\n --beam-pulse-inner-bloom: #{pulse-blobs-frozen(pulse-table($palette, $pulse-inner-bloom))};\n --beam-pulse-outer-core: #{pulse-blobs(pulse-table($palette, $pulse-outer-core))};\n --beam-pulse-outer-bloom: #{pulse-blobs-frozen(pulse-table($palette, $pulse-outer-bloom))};\n\n &.line::after {\n background: #{$line-highlight-light}, #{line-blobs(map.get($line-colors, $palette, light), $line-geometry-light)};\n }\n\n &.line::before {\n background: #{line-blobs(map.get($line-inner-colors, $palette), $line-inner-geometry, $line-inner-alphas)};\n }\n\n &.line > .bloom {\n background: #{line-bloom($palette, light)};\n }\n\n [dark] &.line::after {\n background: #{$line-highlight-dark}, #{line-blobs(map.get($line-colors, $palette, dark), $line-geometry-dark)};\n }\n\n [dark] &.line > .bloom {\n background: #{line-bloom($palette, dark)};\n }\n }\n}\n\n/*\n * Rotate family — a soft conic window travels around the perimeter and reveals\n * the colorful blobs, with a hot highlight inside the window.\n */\n\n.md,\n.sm {\n overflow: hidden;\n\n &.isActive {\n animation: borderBeamSpin calc(var(--beam-duration) * 1s) linear infinite, borderBeamFadeIn .6s ease forwards;\n }\n\n &.isFading {\n animation: borderBeamSpin calc(var(--beam-duration) * 1s) linear infinite, borderBeamFadeOut .5s ease forwards;\n }\n\n &.isActive::after,\n &.isFading::after {\n position: absolute;\n z-index: 2;\n inset: 0;\n padding: 1px;\n content: '';\n border-radius: calc(var(--beam-radius) - 1px);\n opacity: calc(var(--beam-opacity) * var(--beam-stroke-opacity) * var(--beam-mono) * var(--beam-strength));\n animation: borderBeamHueShift 12s ease-in-out infinite;\n clip-path: inset(0 round var(--beam-radius));\n }\n\n &.isActive::before,\n &.isFading::before {\n position: absolute;\n z-index: 1;\n inset: 0;\n content: '';\n opacity: calc(var(--beam-opacity) * var(--beam-inner-opacity) * var(--beam-mono) * var(--beam-strength));\n animation: borderBeamHueShift 12s ease-in-out infinite;\n clip-path: inset(0 round var(--beam-radius));\n }\n\n &.isStatic::before,\n &.isStatic::after {\n animation: none;\n }\n\n > .bloom {\n z-index: 3;\n inset: 0;\n padding: 1px;\n background: #{$rotate-bloom-light};\n border-radius: calc(var(--beam-radius) - 1px);\n opacity: 0;\n filter: blur(8px) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$ring-mask};\n mask-composite: exclude;\n }\n\n [dark] & > .bloom {\n background: #{$rotate-bloom-dark};\n }\n\n &.isActive > .bloom,\n &.isFading > .bloom {\n display: block;\n opacity: calc(var(--beam-opacity) * var(--beam-bloom-opacity) * var(--beam-mono) * var(--beam-strength));\n }\n}\n\n.md {\n --beam-bloom-opacity: .34;\n --beam-brightness: 1.3;\n --beam-inner-opacity: .26;\n --beam-inner-shadow: rgba(0, 0, 0, .14);\n --beam-saturation: 1.5;\n --beam-stroke-opacity: .12;\n\n [dark] & {\n --beam-bloom-opacity: .24;\n --beam-inner-opacity: .42;\n --beam-inner-shadow: rgba(255, 255, 255, .27);\n --beam-saturation: 1.2;\n --beam-stroke-opacity: .26;\n }\n\n &.isActive::after,\n &.isFading::after {\n background: #{$rotate-highlight-light}, var(--beam-rotate-blobs);\n mask: #{$md-window}, #{$ring-mask};\n mask-composite: intersect, exclude;\n }\n\n [dark] &.isActive::after,\n [dark] &.isFading::after {\n background: #{$rotate-highlight-dark}, var(--beam-rotate-blobs);\n }\n\n &.isActive::before,\n &.isFading::before {\n background: var(--beam-rotate-inner);\n border-radius: var(--beam-radius);\n box-shadow: inset 0 0 9px 1px var(--beam-inner-shadow);\n mask: #{$md-window}, #{$edge-fade-y}, #{$edge-fade-x};\n mask-composite: intersect, add;\n }\n}\n\n.sm {\n --beam-bloom-opacity: .16;\n --beam-brightness: 1.3;\n --beam-inner-opacity: .3;\n --beam-inner-shadow: rgba(0, 0, 0, .14);\n --beam-saturation: 1.8;\n --beam-stroke-opacity: .12;\n\n [dark] & {\n --beam-bloom-opacity: .38;\n --beam-inner-opacity: .24;\n --beam-inner-shadow: rgba(255, 255, 255, .3);\n --beam-saturation: 1.2;\n --beam-stroke-opacity: .46;\n }\n\n &.isActive::after,\n &.isFading::after {\n background: #{$rotate-highlight-light}, var(--beam-sm-blobs);\n mask: #{$sm-window}, #{$ring-mask};\n mask-composite: intersect, exclude;\n }\n\n [dark] &.isActive::after,\n [dark] &.isFading::after {\n background: #{$rotate-highlight-dark}, var(--beam-sm-blobs);\n }\n\n &.isActive::before,\n &.isFading::before {\n background: var(--beam-sm-inner);\n border-radius: var(--beam-radius);\n box-shadow: inset 0 0 5px 1px var(--beam-inner-shadow);\n mask: #{$sm-window};\n mask-composite: add;\n }\n}\n\n/*\n * Line — a glow that travels along the bottom edge, with breathing width/height\n * and desynced bloom spikes.\n */\n\n.line {\n overflow: hidden;\n\n --beam-bloom-opacity: .3;\n --beam-brightness: 1.3;\n --beam-inner-opacity: .32;\n --beam-inner-shadow: rgba(0, 0, 0, .14);\n --beam-saturation: 1.95;\n --beam-stroke-opacity: .16;\n\n [dark] & {\n --beam-bloom-opacity: .8;\n --beam-inner-opacity: .7;\n --beam-inner-shadow: rgba(255, 255, 255, .1);\n --beam-saturation: 1.2;\n --beam-stroke-opacity: 1.14;\n }\n\n &.isActive {\n animation: borderBeamTravel calc(var(--beam-duration) * 1s) linear infinite, borderBeamEdgeFade calc(var(--beam-duration) * 1s) linear infinite, borderBeamBreathe calc(var(--beam-duration) * 1.3s) ease-in-out infinite, borderBeamSpike calc(var(--beam-duration) * 1.33s) ease-in-out infinite, borderBeamSpike2 calc(var(--beam-duration) * 1.7s) ease-in-out infinite, borderBeamFadeIn .6s ease forwards;\n }\n\n &.isFading {\n animation: borderBeamTravel calc(var(--beam-duration) * 1s) linear infinite, borderBeamEdgeFade calc(var(--beam-duration) * 1s) linear infinite, borderBeamBreathe calc(var(--beam-duration) * 1.3s) ease-in-out infinite, borderBeamSpike calc(var(--beam-duration) * 1.33s) ease-in-out infinite, borderBeamSpike2 calc(var(--beam-duration) * 1.7s) ease-in-out infinite, borderBeamFadeOut .5s ease forwards;\n }\n\n &.isActive::after,\n &.isFading::after {\n position: absolute;\n z-index: 2;\n inset: 0;\n padding: 1px;\n content: '';\n border-radius: calc(var(--beam-radius) - 1px);\n opacity: calc(var(--beam-opacity) * var(--beam-edge) * var(--beam-stroke-opacity) * var(--beam-strength));\n animation: borderBeamHueShift 12s ease-in-out infinite;\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$line-window}, #{$ring-mask};\n mask-composite: intersect, exclude;\n }\n\n &.isActive::before,\n &.isFading::before {\n position: absolute;\n z-index: 1;\n inset: 0;\n content: '';\n border-radius: var(--beam-radius);\n box-shadow: inset 0 0 9px 1px var(--beam-inner-shadow);\n opacity: calc(var(--beam-opacity) * var(--beam-edge) * var(--beam-inner-opacity) * var(--beam-strength));\n animation: borderBeamHueShift 12s ease-in-out infinite;\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$line-window}, #{$edge-fade-y}, #{$edge-fade-x};\n mask-composite: intersect, add;\n }\n\n &.isStatic::before,\n &.isStatic::after {\n animation: none;\n }\n\n > .bloom {\n z-index: 3;\n inset: 0;\n border-radius: calc(var(--beam-radius) - 1px);\n opacity: 0;\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$line-bloom-window};\n mask-composite: add;\n }\n\n &.isActive > .bloom,\n &.isFading > .bloom {\n display: block;\n opacity: calc(var(--beam-opacity) * var(--beam-edge) * var(--beam-bloom-opacity) * var(--beam-strength));\n animation: borderBeamHueShiftBloom 8s ease-in-out infinite;\n }\n\n &.isStatic > .bloom {\n animation: none;\n }\n\n &.mono > .bloom {\n filter: blur(6px);\n }\n}\n\n/*\n * Pulse family — a breathing glow without rotation. The motion is driven from a\n * shared, frame-rate-capped JS loop that writes the --beam-b* custom properties.\n */\n\n.pulseInner,\n.pulseOutside {\n isolation: isolate;\n\n &.isActive {\n animation: borderBeamFadeIn .6s ease forwards;\n }\n\n &.isFading {\n animation: borderBeamFadeOut .5s ease forwards;\n }\n}\n\n.pulseInner {\n overflow: hidden;\n\n --beam-bloom-opacity: .8;\n --beam-brightness: 1.3;\n --beam-inner-opacity: .4;\n --beam-pulse-corner: 0, 0, 0;\n --beam-pulse-corner-alpha: .08;\n --beam-pulse-frozen: .775;\n --beam-saturation: .75;\n --beam-stroke-opacity: .32;\n\n [dark] & {\n --beam-bloom-opacity: .66;\n --beam-brightness: .75;\n --beam-inner-opacity: .44;\n --beam-pulse-corner: 255, 255, 255;\n --beam-pulse-corner-alpha: .18;\n --beam-pulse-frozen: .76;\n --beam-saturation: 1.2;\n --beam-stroke-opacity: 1.54;\n }\n\n &.isActive::after,\n &.isFading::after {\n position: absolute;\n z-index: 2;\n inset: 0;\n padding: 1px;\n content: '';\n background: var(--beam-pulse-ring);\n border-radius: var(--beam-radius);\n opacity: calc(var(--beam-opacity) * var(--beam-stroke-opacity) * var(--beam-mono) * var(--beam-strength));\n will-change: opacity, filter;\n filter: hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$ring-mask};\n mask-composite: exclude;\n }\n\n &.isActive::before,\n &.isFading::before {\n position: absolute;\n z-index: 1;\n inset: 0;\n content: '';\n background:\n var(--beam-pulse-inner-ring),\n radial-gradient(ellipse 60px 60px at 0% 0%, rgba(var(--beam-pulse-corner), calc(var(--beam-pulse-corner-alpha) * var(--beam-bop-tl))), transparent 70%),\n radial-gradient(ellipse 60px 60px at 100% 0%, rgba(var(--beam-pulse-corner), calc(var(--beam-pulse-corner-alpha) * var(--beam-bop-tr))), transparent 70%),\n radial-gradient(ellipse 60px 60px at 0% 100%, rgba(var(--beam-pulse-corner), calc(var(--beam-pulse-corner-alpha) * var(--beam-bop-bl))), transparent 70%),\n radial-gradient(ellipse 60px 60px at 100% 100%, rgba(var(--beam-pulse-corner), calc(var(--beam-pulse-corner-alpha) * var(--beam-bop-br))), transparent 70%);\n border-radius: var(--beam-radius);\n opacity: calc(var(--beam-opacity) * var(--beam-inner-opacity) * var(--beam-mono) * var(--beam-strength));\n will-change: opacity, filter;\n filter: hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$edge-fade-y}, #{$edge-fade-x};\n mask-composite: add;\n }\n\n > .bloom {\n z-index: 3;\n inset: 0;\n padding: 1px;\n background: var(--beam-pulse-inner-bloom);\n border-radius: var(--beam-radius);\n opacity: 0;\n will-change: opacity;\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$ring-mask};\n mask-composite: exclude;\n }\n\n &.isActive > .bloom,\n &.isFading > .bloom {\n display: block;\n opacity: calc(var(--beam-opacity) * var(--beam-bloom-opacity) * var(--beam-mono) * var(--beam-strength));\n filter: blur(8px) hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n}\n\n.pulseOutside {\n overflow: visible;\n\n --beam-bloom-opacity: .42;\n --beam-brightness: 1.7;\n --beam-inner-opacity: 1.04;\n --beam-pulse-bloom-blur: 15px;\n --beam-pulse-frozen: 1;\n --beam-pulse-glow-blur: 6px;\n --beam-saturation: .6;\n --beam-stroke-opacity: 1.96;\n\n [dark] & {\n --beam-bloom-opacity: .3;\n --beam-brightness: 1.9;\n --beam-inner-opacity: .34;\n --beam-pulse-bloom-blur: 22.5px;\n --beam-pulse-frozen: .77;\n --beam-pulse-glow-blur: 3px;\n --beam-saturation: 1.2;\n --beam-stroke-opacity: .94;\n }\n\n &.isActive::after,\n &.isFading::after {\n position: absolute;\n z-index: 2;\n inset: 0;\n padding: 1px;\n content: '';\n background: var(--beam-pulse-outer-core);\n border-radius: var(--beam-radius);\n opacity: calc(var(--beam-opacity) * var(--beam-stroke-opacity) * var(--beam-mono) * var(--beam-strength));\n will-change: opacity, filter;\n filter: hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$ring-mask};\n mask-composite: exclude;\n }\n\n &.isActive::before,\n &.isFading::before {\n position: absolute;\n z-index: -1;\n inset: -10px;\n content: '';\n background: var(--beam-pulse-outer-core);\n border-radius: calc(var(--beam-radius) + 10px);\n opacity: calc(var(--beam-opacity) * var(--beam-inner-opacity) * var(--beam-mono) * var(--beam-strength));\n will-change: opacity, filter;\n transform: scale(.95, .9);\n filter: blur(var(--beam-pulse-glow-blur)) hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n\n > .bloom {\n z-index: -1;\n inset: -30px;\n background: var(--beam-pulse-outer-bloom);\n border-radius: calc(var(--beam-radius) + 30px);\n opacity: 0;\n will-change: transform;\n transform: scale(.95, .9);\n }\n\n &.isActive > .bloom,\n &.isFading > .bloom {\n display: block;\n opacity: calc(var(--beam-opacity) * var(--beam-bloom-opacity) * var(--beam-mono) * var(--beam-strength));\n filter: blur(var(--beam-pulse-bloom-blur)) hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n}\n\n.borderBeam.isPaused,\n.borderBeam.isPaused::before,\n.borderBeam.isPaused::after,\n.borderBeam.isPaused > .bloom {\n animation-play-state: paused !important;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .pulseInner,\n .pulseOutside,\n .pulseInner::before,\n .pulseInner::after,\n .pulseOutside::before,\n .pulseOutside::after,\n .pulseInner > .bloom,\n .pulseOutside > .bloom {\n animation: none !important;\n }\n}\n\n@keyframes borderBeamSpin {\n to {\n --beam-angle: 360deg;\n }\n}\n\n@keyframes borderBeamFadeIn {\n to {\n --beam-opacity: 1;\n }\n}\n\n@keyframes borderBeamFadeOut {\n from {\n --beam-opacity: 1;\n }\n\n to {\n --beam-opacity: 0;\n }\n}\n\n@keyframes borderBeamHueShift {\n 0%, 100% {\n filter: hue-rotate(calc(var(--beam-hue-range) * -1deg)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n\n 50% {\n filter: hue-rotate(calc(var(--beam-hue-range) * 1deg)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n}\n\n@keyframes borderBeamHueShiftBloom {\n 0%, 100% {\n filter: blur(8px) hue-rotate(calc((var(--beam-hue-range) + 10) * -1deg)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n\n 50% {\n filter: blur(8px) hue-rotate(calc((var(--beam-hue-range) + 10) * 1deg)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n}\n\n@keyframes borderBeamTravel {\n 0% {\n --beam-x: .06;\n --beam-w: .5;\n }\n\n 10% {\n --beam-x: .15;\n --beam-w: .8;\n }\n\n 20% {\n --beam-x: .25;\n --beam-w: 1.1;\n }\n\n 30% {\n --beam-x: .35;\n --beam-w: 1.3;\n }\n\n 40% {\n --beam-x: .44;\n --beam-w: 1.45;\n }\n\n 50% {\n --beam-x: .5;\n --beam-w: 1.5;\n }\n\n 60% {\n --beam-x: .56;\n --beam-w: 1.45;\n }\n\n 70% {\n --beam-x: .65;\n --beam-w: 1.3;\n }\n\n 80% {\n --beam-x: .75;\n --beam-w: 1.1;\n }\n\n 90% {\n --beam-x: .85;\n --beam-w: .8;\n }\n\n 100% {\n --beam-x: .94;\n --beam-w: .5;\n }\n}\n\n@keyframes borderBeamEdgeFade {\n 0%, 100% {\n --beam-edge: 0;\n }\n\n 12.5% {\n --beam-edge: 0;\n }\n\n 32.5%, 67.5% {\n --beam-edge: 1;\n }\n\n 87.5% {\n --beam-edge: 0;\n }\n}\n\n@keyframes borderBeamBreathe {\n 0%, 100% {\n --beam-h: .8;\n }\n\n 25% {\n --beam-h: 1.25;\n }\n\n 55% {\n --beam-h: .85;\n }\n\n 80% {\n --beam-h: 1.3;\n }\n}\n\n@keyframes borderBeamSpike {\n 0%, 100% {\n --beam-spike: .8;\n }\n\n 25% {\n --beam-spike: 1.3;\n }\n\n 50% {\n --beam-spike: .9;\n }\n\n 75% {\n --beam-spike: 1.4;\n }\n}\n\n@keyframes borderBeamSpike2 {\n 0%, 100% {\n --beam-spike2: 1.2;\n }\n\n 25% {\n --beam-spike2: .7;\n }\n\n 50% {\n --beam-spike2: 1.4;\n }\n\n 75% {\n --beam-spike2: .8;\n }\n}\n","<template>\n <div\n ref=\"wrapper\"\n :class=\"clsx(\n $style.borderBeam,\n VARIANT_CLASSES[variant],\n $style[colorVariant],\n isActive && !isFading && $style.isActive,\n isFading && $style.isFading,\n isPaused && $style.isPaused,\n isStatic && $style.isStatic\n )\"\n :style=\"style\"\n @animationend=\"onAnimationEnd\">\n <slot/>\n\n <div\n aria-hidden=\"true\"\n :class=\"$style.bloom\"/>\n </div>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { clamp } from '@basmilius/utils';\n import type { FluxVisualBorderBeamVariant } from '@flux-ui/types';\n import { clsx } from 'clsx';\n import { computed, ref, unref, useTemplateRef, watch } from 'vue';\n import { useBorderBeamPulse } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/BorderBeam.module.scss';\n\n const emit = defineEmits<{\n activate: [];\n deactivate: [];\n }>();\n\n const {\n active = true,\n brightness,\n colorVariant = 'colorful',\n duration,\n hueRange = 30,\n radius,\n saturation,\n staticColors = false,\n strength = 1,\n variant = 'md'\n } = defineProps<{\n readonly active?: boolean;\n readonly brightness?: number;\n readonly colorVariant?: 'colorful' | 'mono' | 'ocean' | 'sunset';\n readonly duration?: number;\n readonly hueRange?: number;\n readonly radius?: string | number;\n readonly saturation?: number;\n readonly staticColors?: boolean;\n readonly strength?: number;\n readonly variant?: FluxVisualBorderBeamVariant;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n\n const VARIANT_CLASSES: Record<FluxVisualBorderBeamVariant, string> = {\n 'sm': $style.sm,\n 'md': $style.md,\n 'line': $style.line,\n 'pulse-inner': $style.pulseInner,\n 'pulse-outside': $style.pulseOutside\n };\n\n const wrapperRef = useTemplateRef('wrapper');\n const inView = useInView(wrapperRef, {initial: true, rootMargin: '256px'});\n\n const isActive = ref(active);\n const isFading = ref(false);\n const glowScale = ref<{ x: number; y: number; } | null>(null);\n\n const isPulse = computed(() => variant === 'pulse-inner' || variant === 'pulse-outside');\n const isStatic = computed(() => staticColors || colorVariant === 'mono');\n const isPaused = computed(() => isActive.value && !isFading.value && !inView.value);\n const resolvedDuration = computed(() => duration ?? (variant === 'line' ? 3.1 : isPulse.value ? 2.3 : 1.96));\n\n const style = computed(() => ({\n '--beam-brightness': brightness,\n '--beam-duration': resolvedDuration.value,\n '--beam-glow-sx': glowScale.value?.x,\n '--beam-glow-sy': glowScale.value?.y,\n '--beam-hue-range': variant === 'line' ? Math.min(hueRange, 13) : hueRange,\n '--beam-radius': typeof radius === 'number' ? `${radius}px` : radius,\n '--beam-saturation': saturation,\n '--beam-strength': Math.max(0, Math.min(1, strength))\n }));\n\n useBorderBeamPulse({\n duration: resolvedDuration,\n elementRef: wrapperRef,\n enabled: computed(() => (isActive.value || isFading.value) && inView.value),\n staticColors: isStatic,\n variant: computed(() => variant)\n });\n\n watch(() => active, value => {\n if (value) {\n // Also covers re-activating while the fade-out is still running: cancel the\n // fade, otherwise its animationend would turn the beam off for good.\n isFading.value = false;\n isActive.value = true;\n } else if (isActive.value && !isFading.value) {\n isFading.value = true;\n }\n });\n\n // The pulse-outside glow geometry is authored in fixed pixels for a reference\n // element of ~350x140; measure the wrapped element and scale the glow per-axis\n // so the halo fits any component it's applied to.\n watch([wrapperRef, () => variant], (_, __, onCleanup) => {\n glowScale.value = null;\n\n const wrapper = unref(wrapperRef);\n\n if (!wrapper || variant !== 'pulse-outside' || typeof ResizeObserver === 'undefined') {\n return;\n }\n\n const child = wrapper.firstElementChild;\n\n if (!child || !(child instanceof HTMLElement)) {\n return;\n }\n\n const measure = (): void => {\n const rect = child.getBoundingClientRect();\n\n if (!rect.width || !rect.height) {\n return;\n }\n\n const x = +clamp(rect.width / 350, .35, 4).toFixed(3);\n const y = +clamp(rect.height / 140, .35, 4).toFixed(3);\n\n if (glowScale.value?.x !== x || glowScale.value?.y !== y) {\n glowScale.value = {x, y};\n }\n };\n\n measure();\n\n const observer = new ResizeObserver(measure);\n observer.observe(child);\n\n onCleanup(() => observer.disconnect());\n }, {immediate: true});\n\n function onAnimationEnd(event: AnimationEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n if (isFading.value) {\n isActive.value = false;\n isFading.value = false;\n emit('deactivate');\n } else if (isActive.value) {\n emit('activate');\n }\n }\n</script>\n","<template>\n <div\n ref=\"wrapper\"\n :class=\"clsx(\n $style.borderBeam,\n VARIANT_CLASSES[variant],\n $style[colorVariant],\n isActive && !isFading && $style.isActive,\n isFading && $style.isFading,\n isPaused && $style.isPaused,\n isStatic && $style.isStatic\n )\"\n :style=\"style\"\n @animationend=\"onAnimationEnd\">\n <slot/>\n\n <div\n aria-hidden=\"true\"\n :class=\"$style.bloom\"/>\n </div>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { clamp } from '@basmilius/utils';\n import type { FluxVisualBorderBeamVariant } from '@flux-ui/types';\n import { clsx } from 'clsx';\n import { computed, ref, unref, useTemplateRef, watch } from 'vue';\n import { useBorderBeamPulse } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/BorderBeam.module.scss';\n\n const emit = defineEmits<{\n activate: [];\n deactivate: [];\n }>();\n\n const {\n active = true,\n brightness,\n colorVariant = 'colorful',\n duration,\n hueRange = 30,\n radius,\n saturation,\n staticColors = false,\n strength = 1,\n variant = 'md'\n } = defineProps<{\n readonly active?: boolean;\n readonly brightness?: number;\n readonly colorVariant?: 'colorful' | 'mono' | 'ocean' | 'sunset';\n readonly duration?: number;\n readonly hueRange?: number;\n readonly radius?: string | number;\n readonly saturation?: number;\n readonly staticColors?: boolean;\n readonly strength?: number;\n readonly variant?: FluxVisualBorderBeamVariant;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n\n const VARIANT_CLASSES: Record<FluxVisualBorderBeamVariant, string> = {\n 'sm': $style.sm,\n 'md': $style.md,\n 'line': $style.line,\n 'pulse-inner': $style.pulseInner,\n 'pulse-outside': $style.pulseOutside\n };\n\n const wrapperRef = useTemplateRef('wrapper');\n const inView = useInView(wrapperRef, {initial: true, rootMargin: '256px'});\n\n const isActive = ref(active);\n const isFading = ref(false);\n const glowScale = ref<{ x: number; y: number; } | null>(null);\n\n const isPulse = computed(() => variant === 'pulse-inner' || variant === 'pulse-outside');\n const isStatic = computed(() => staticColors || colorVariant === 'mono');\n const isPaused = computed(() => isActive.value && !isFading.value && !inView.value);\n const resolvedDuration = computed(() => duration ?? (variant === 'line' ? 3.1 : isPulse.value ? 2.3 : 1.96));\n\n const style = computed(() => ({\n '--beam-brightness': brightness,\n '--beam-duration': resolvedDuration.value,\n '--beam-glow-sx': glowScale.value?.x,\n '--beam-glow-sy': glowScale.value?.y,\n '--beam-hue-range': variant === 'line' ? Math.min(hueRange, 13) : hueRange,\n '--beam-radius': typeof radius === 'number' ? `${radius}px` : radius,\n '--beam-saturation': saturation,\n '--beam-strength': Math.max(0, Math.min(1, strength))\n }));\n\n useBorderBeamPulse({\n duration: resolvedDuration,\n elementRef: wrapperRef,\n enabled: computed(() => (isActive.value || isFading.value) && inView.value),\n staticColors: isStatic,\n variant: computed(() => variant)\n });\n\n watch(() => active, value => {\n if (value) {\n // Also covers re-activating while the fade-out is still running: cancel the\n // fade, otherwise its animationend would turn the beam off for good.\n isFading.value = false;\n isActive.value = true;\n } else if (isActive.value && !isFading.value) {\n isFading.value = true;\n }\n });\n\n // The pulse-outside glow geometry is authored in fixed pixels for a reference\n // element of ~350x140; measure the wrapped element and scale the glow per-axis\n // so the halo fits any component it's applied to.\n watch([wrapperRef, () => variant], (_, __, onCleanup) => {\n glowScale.value = null;\n\n const wrapper = unref(wrapperRef);\n\n if (!wrapper || variant !== 'pulse-outside' || typeof ResizeObserver === 'undefined') {\n return;\n }\n\n const child = wrapper.firstElementChild;\n\n if (!child || !(child instanceof HTMLElement)) {\n return;\n }\n\n const measure = (): void => {\n const rect = child.getBoundingClientRect();\n\n if (!rect.width || !rect.height) {\n return;\n }\n\n const x = +clamp(rect.width / 350, .35, 4).toFixed(3);\n const y = +clamp(rect.height / 140, .35, 4).toFixed(3);\n\n if (glowScale.value?.x !== x || glowScale.value?.y !== y) {\n glowScale.value = {x, y};\n }\n };\n\n measure();\n\n const observer = new ResizeObserver(measure);\n observer.observe(child);\n\n onCleanup(() => observer.disconnect());\n }, {immediate: true});\n\n function onAnimationEnd(event: AnimationEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n if (isFading.value) {\n isActive.value = false;\n isFading.value = false;\n emit('deactivate');\n } else if (isActive.value) {\n emit('activate');\n }\n }\n</script>\n","<script lang=\"ts\">\n import { flattenVNodeTree, orange600, pink600, purple600 } from '@flux-ui/internals';\n import { clsx } from 'clsx';\n import { cloneVNode, defineComponent, Fragment, h, type PropType } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n export default defineComponent({\n inheritAttrs: false,\n props: {\n colors: {default: [purple600, 'transparent', orange600, 'transparent', pink600, 'transparent', purple600], type: Array as PropType<string[]>},\n duration: {default: 9, type: Number},\n offset: {default: 1, type: Number},\n radius: {default: undefined, type: [String, Number] as PropType<string | number>},\n width: {default: 2, type: Number}\n },\n setup(props, {attrs, slots}) {\n return () => h(\n Fragment,\n flattenVNodeTree(slots.default?.() ?? []).map(vnode => cloneVNode(vnode, {\n ...attrs,\n class: clsx(\n attrs.class as string,\n $style.borderShine\n ),\n style: {\n '--shine-colors': props.colors.join(', '),\n '--shine-duration': props.duration,\n '--shine-offset': props.offset,\n '--shine-radius': typeof props.radius === 'number' ? `${props.radius}px` : props.radius,\n '--shine-width': props.width\n }\n }))\n );\n }\n });\n</script>\n","<script lang=\"ts\">\n import { flattenVNodeTree, orange600, pink600, purple600 } from '@flux-ui/internals';\n import { clsx } from 'clsx';\n import { cloneVNode, defineComponent, Fragment, h, type PropType } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n export default defineComponent({\n inheritAttrs: false,\n props: {\n colors: {default: [purple600, 'transparent', orange600, 'transparent', pink600, 'transparent', purple600], type: Array as PropType<string[]>},\n duration: {default: 9, type: Number},\n offset: {default: 1, type: Number},\n radius: {default: undefined, type: [String, Number] as PropType<string | number>},\n width: {default: 2, type: Number}\n },\n setup(props, {attrs, slots}) {\n return () => h(\n Fragment,\n flattenVNodeTree(slots.default?.() ?? []).map(vnode => cloneVNode(vnode, {\n ...attrs,\n class: clsx(\n attrs.class as string,\n $style.borderShine\n ),\n style: {\n '--shine-colors': props.colors.join(', '),\n '--shine-duration': props.duration,\n '--shine-offset': props.offset,\n '--shine-radius': typeof props.radius === 'number' ? `${props.radius}px` : props.radius,\n '--shine-width': props.width\n }\n }))\n );\n }\n });\n</script>\n",".glowLayer {\n transition: opacity .25s ease;\n pointer-events: none;\n opacity: 0;\n -webkit-mask: radial-gradient(circle var(--pattern-glow-size, 120px) at var(--pattern-glow-x, 50%) var(--pattern-glow-y, 50%), #000 0%, transparent 100%);\n mask: radial-gradient(circle var(--pattern-glow-size, 120px) at var(--pattern-glow-x, 50%) var(--pattern-glow-y, 50%), #000 0%, transparent 100%);\n}\n\n.isActive {\n opacity: 1;\n}\n\n.glowDot {\n fill: var(--primary-solid);\n}\n\n.glowLine {\n stroke: var(--primary-solid);\n}\n","<template>\n <svg\n ref=\"root\"\n aria-hidden=\"true\"\n :class=\"$style.dotPattern\">\n <defs>\n <pattern\n :id=\"id\"\n :width=\"width\"\n :height=\"height\"\n patternContentUnits=\"userSpaceOnUse\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <circle\n :r=\"cr\"\n :cx=\"width / 2 - cx\"\n :cy=\"height / 2 - cy\"/>\n </pattern>\n\n <pattern\n v-if=\"glow\"\n :id=\"glowId\"\n :width=\"width\"\n :height=\"height\"\n patternContentUnits=\"userSpaceOnUse\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <circle\n :class=\"$glow.glowDot\"\n :r=\"cr\"\n :cx=\"width / 2 - cx\"\n :cy=\"height / 2 - cy\"/>\n </pattern>\n </defs>\n\n <rect\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${id})`\"/>\n\n <rect\n v-if=\"glow\"\n :class=\"[$glow.glowLayer, active && $glow.isActive]\"\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${glowId})`\"/>\n </svg>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $glow from '~flux/visuals/css/component/PatternGlow.module.scss';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n width = 16,\n height = 16,\n cr = 1,\n cx = 1,\n cy = 1,\n glow = false\n } = defineProps<{\n readonly width?: number;\n readonly height?: number;\n readonly cr?: number;\n readonly cx?: number;\n readonly cy?: number;\n readonly glow?: boolean;\n }>();\n\n const id = useId();\n const glowId = `${id}-glow`;\n const rootRef = useTemplateRef<SVGSVGElement>('root');\n const active = ref(false);\n\n // The pattern svg has pointer-events: none, so the cursor is tracked on the\n // parent scroll/overflow container instead. The position is written straight\n // to CSS custom properties to avoid a re-render on every pointermove.\n watch([rootRef, () => glow], (_, __, onCleanup) => {\n const root = unref(rootRef);\n\n if (!glow || !root || !root.parentElement) {\n return;\n }\n\n const parent = root.parentElement;\n\n const onPointerMove = (event: PointerEvent): void => {\n const rect = parent.getBoundingClientRect();\n root.style.setProperty('--pattern-glow-x', `${event.clientX - rect.left}px`);\n root.style.setProperty('--pattern-glow-y', `${event.clientY - rect.top}px`);\n };\n\n const onPointerEnter = (): void => {\n active.value = true;\n };\n\n const onPointerLeave = (): void => {\n active.value = false;\n };\n\n parent.addEventListener('pointermove', onPointerMove, {passive: true});\n parent.addEventListener('pointerenter', onPointerEnter);\n parent.addEventListener('pointerleave', onPointerLeave);\n\n onCleanup(() => {\n active.value = false;\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n });\n }, {immediate: true});\n</script>\n","<template>\n <svg\n ref=\"root\"\n aria-hidden=\"true\"\n :class=\"$style.dotPattern\">\n <defs>\n <pattern\n :id=\"id\"\n :width=\"width\"\n :height=\"height\"\n patternContentUnits=\"userSpaceOnUse\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <circle\n :r=\"cr\"\n :cx=\"width / 2 - cx\"\n :cy=\"height / 2 - cy\"/>\n </pattern>\n\n <pattern\n v-if=\"glow\"\n :id=\"glowId\"\n :width=\"width\"\n :height=\"height\"\n patternContentUnits=\"userSpaceOnUse\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <circle\n :class=\"$glow.glowDot\"\n :r=\"cr\"\n :cx=\"width / 2 - cx\"\n :cy=\"height / 2 - cy\"/>\n </pattern>\n </defs>\n\n <rect\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${id})`\"/>\n\n <rect\n v-if=\"glow\"\n :class=\"[$glow.glowLayer, active && $glow.isActive]\"\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${glowId})`\"/>\n </svg>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $glow from '~flux/visuals/css/component/PatternGlow.module.scss';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n width = 16,\n height = 16,\n cr = 1,\n cx = 1,\n cy = 1,\n glow = false\n } = defineProps<{\n readonly width?: number;\n readonly height?: number;\n readonly cr?: number;\n readonly cx?: number;\n readonly cy?: number;\n readonly glow?: boolean;\n }>();\n\n const id = useId();\n const glowId = `${id}-glow`;\n const rootRef = useTemplateRef<SVGSVGElement>('root');\n const active = ref(false);\n\n // The pattern svg has pointer-events: none, so the cursor is tracked on the\n // parent scroll/overflow container instead. The position is written straight\n // to CSS custom properties to avoid a re-render on every pointermove.\n watch([rootRef, () => glow], (_, __, onCleanup) => {\n const root = unref(rootRef);\n\n if (!glow || !root || !root.parentElement) {\n return;\n }\n\n const parent = root.parentElement;\n\n const onPointerMove = (event: PointerEvent): void => {\n const rect = parent.getBoundingClientRect();\n root.style.setProperty('--pattern-glow-x', `${event.clientX - rect.left}px`);\n root.style.setProperty('--pattern-glow-y', `${event.clientY - rect.top}px`);\n };\n\n const onPointerEnter = (): void => {\n active.value = true;\n };\n\n const onPointerLeave = (): void => {\n active.value = false;\n };\n\n parent.addEventListener('pointermove', onPointerMove, {passive: true});\n parent.addEventListener('pointerenter', onPointerEnter);\n parent.addEventListener('pointerleave', onPointerLeave);\n\n onCleanup(() => {\n active.value = false;\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n });\n }, {immediate: true});\n</script>\n","<template>\n <canvas\n ref=\"canvas\"\n aria-hidden=\"true\"\n :class=\"$style.flickeringGrid\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { mulberry32, prefersReducedMotion } from '@basmilius/utils';\n import { computed, unref, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n color = '#1d4ed8',\n flickerChance = 0.15,\n gap = 6,\n maxOpacity = 0.3,\n size = 3\n } = defineProps<{\n readonly color?: string;\n readonly flickerChance?: number;\n readonly gap?: number;\n readonly maxOpacity?: number;\n readonly size?: number;\n }>();\n\n const canvasRef = useTemplateRef('canvas');\n\n const inView = useInView(canvasRef);\n\n const mulberry = mulberry32(13);\n\n const rgb = computed(() => {\n const canvas = document.createElement('canvas');\n canvas.width = canvas.height = 1;\n\n const context = canvas.getContext('2d');\n\n if (!context) {\n return [0, 0, 0];\n }\n\n context.fillStyle = color;\n context.fillRect(0, 0, 1, 1);\n\n return context.getImageData(0, 0, 1, 1).data;\n });\n\n watch([canvasRef, inView], ([canvas, inView], _, onCleanup) => {\n if (!canvas || !inView) {\n return;\n }\n\n const context = canvas.getContext('2d');\n\n if (!context) {\n return;\n }\n\n let frame = 0;\n let lastTime = 0;\n let {width, height, columns, rows, squares, dpr} = setup(canvas);\n\n const reducedMotion = prefersReducedMotion();\n\n const onResize = () => {\n ({width, height, columns, rows, squares, dpr} = setup(canvas));\n\n if (reducedMotion) {\n draw(context, width, height, columns, rows, squares, dpr);\n }\n };\n\n window.addEventListener('resize', onResize, {passive: true});\n\n if (reducedMotion) {\n draw(context, width, height, columns, rows, squares, dpr);\n\n onCleanup(() => {\n window.removeEventListener('resize', onResize);\n });\n\n return;\n }\n\n const animate = (time: number): void => {\n const delta = lastTime > 0 ? (time - lastTime) / 1000 : 0;\n lastTime = time;\n\n tick(squares, delta);\n draw(context, width, height, columns, rows, squares, dpr);\n frame = requestAnimationFrame(animate);\n };\n\n frame = requestAnimationFrame(animate);\n\n onCleanup(() => {\n window.removeEventListener('resize', onResize);\n cancelAnimationFrame(frame);\n });\n }, {immediate: true});\n\n function draw(context: CanvasRenderingContext2D, width: number, height: number, columns: number, rows: number, squares: Float32Array, dpr: number): void {\n context.clearRect(0, 0, width * dpr, height * dpr);\n\n const [r, g, b] = unref(rgb);\n\n for (let i = 0; i < columns; ++i) {\n for (let j = 0; j < rows; ++j) {\n const opacity = squares[i * rows + j];\n context.fillStyle = `rgb(${r} ${g} ${b} / ${opacity})`;\n context.fillRect(\n (i * (size + gap) + width / 2 - (columns / 2 * (size + gap) - gap / 2)) * dpr,\n (j * (size + gap) + height / 2 - (rows / 2 * (size + gap) - gap / 2)) * dpr,\n size * dpr,\n size * dpr\n );\n }\n }\n }\n\n function setup(canvas: HTMLCanvasElement) {\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const dpr = window.devicePixelRatio || 1;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n const columns = Math.ceil(width / (size + gap));\n const rows = Math.ceil(height / (size + gap));\n const squares = new Float32Array(columns * rows);\n\n for (let i = 0; i < squares.length; ++i) {\n squares[i] = mulberry.next() * maxOpacity;\n }\n\n return {\n width,\n height,\n columns,\n rows,\n squares,\n dpr\n };\n }\n\n function tick(squares: Float32Array, delta: number): void {\n for (let i = 0; i < squares.length; ++i) {\n if (mulberry.next() < flickerChance * delta) {\n squares[i] = mulberry.next() * maxOpacity;\n }\n }\n }\n</script>\n","<template>\n <canvas\n ref=\"canvas\"\n aria-hidden=\"true\"\n :class=\"$style.flickeringGrid\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { mulberry32, prefersReducedMotion } from '@basmilius/utils';\n import { computed, unref, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n color = '#1d4ed8',\n flickerChance = 0.15,\n gap = 6,\n maxOpacity = 0.3,\n size = 3\n } = defineProps<{\n readonly color?: string;\n readonly flickerChance?: number;\n readonly gap?: number;\n readonly maxOpacity?: number;\n readonly size?: number;\n }>();\n\n const canvasRef = useTemplateRef('canvas');\n\n const inView = useInView(canvasRef);\n\n const mulberry = mulberry32(13);\n\n const rgb = computed(() => {\n const canvas = document.createElement('canvas');\n canvas.width = canvas.height = 1;\n\n const context = canvas.getContext('2d');\n\n if (!context) {\n return [0, 0, 0];\n }\n\n context.fillStyle = color;\n context.fillRect(0, 0, 1, 1);\n\n return context.getImageData(0, 0, 1, 1).data;\n });\n\n watch([canvasRef, inView], ([canvas, inView], _, onCleanup) => {\n if (!canvas || !inView) {\n return;\n }\n\n const context = canvas.getContext('2d');\n\n if (!context) {\n return;\n }\n\n let frame = 0;\n let lastTime = 0;\n let {width, height, columns, rows, squares, dpr} = setup(canvas);\n\n const reducedMotion = prefersReducedMotion();\n\n const onResize = () => {\n ({width, height, columns, rows, squares, dpr} = setup(canvas));\n\n if (reducedMotion) {\n draw(context, width, height, columns, rows, squares, dpr);\n }\n };\n\n window.addEventListener('resize', onResize, {passive: true});\n\n if (reducedMotion) {\n draw(context, width, height, columns, rows, squares, dpr);\n\n onCleanup(() => {\n window.removeEventListener('resize', onResize);\n });\n\n return;\n }\n\n const animate = (time: number): void => {\n const delta = lastTime > 0 ? (time - lastTime) / 1000 : 0;\n lastTime = time;\n\n tick(squares, delta);\n draw(context, width, height, columns, rows, squares, dpr);\n frame = requestAnimationFrame(animate);\n };\n\n frame = requestAnimationFrame(animate);\n\n onCleanup(() => {\n window.removeEventListener('resize', onResize);\n cancelAnimationFrame(frame);\n });\n }, {immediate: true});\n\n function draw(context: CanvasRenderingContext2D, width: number, height: number, columns: number, rows: number, squares: Float32Array, dpr: number): void {\n context.clearRect(0, 0, width * dpr, height * dpr);\n\n const [r, g, b] = unref(rgb);\n\n for (let i = 0; i < columns; ++i) {\n for (let j = 0; j < rows; ++j) {\n const opacity = squares[i * rows + j];\n context.fillStyle = `rgb(${r} ${g} ${b} / ${opacity})`;\n context.fillRect(\n (i * (size + gap) + width / 2 - (columns / 2 * (size + gap) - gap / 2)) * dpr,\n (j * (size + gap) + height / 2 - (rows / 2 * (size + gap) - gap / 2)) * dpr,\n size * dpr,\n size * dpr\n );\n }\n }\n }\n\n function setup(canvas: HTMLCanvasElement) {\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const dpr = window.devicePixelRatio || 1;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n const columns = Math.ceil(width / (size + gap));\n const rows = Math.ceil(height / (size + gap));\n const squares = new Float32Array(columns * rows);\n\n for (let i = 0; i < squares.length; ++i) {\n squares[i] = mulberry.next() * maxOpacity;\n }\n\n return {\n width,\n height,\n columns,\n rows,\n squares,\n dpr\n };\n }\n\n function tick(squares: Float32Array, delta: number): void {\n for (let i = 0; i < squares.length; ++i) {\n if (mulberry.next() < flickerChance * delta) {\n squares[i] = mulberry.next() * maxOpacity;\n }\n }\n }\n</script>\n","<template>\n <svg\n ref=\"root\"\n aria-hidden=\"true\"\n :class=\"$style.gridPattern\">\n <defs>\n <pattern\n :id=\"id\"\n :width=\"width\"\n :height=\"height\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <path\n :d=\"`M.5 ${height}V.5H${width}`\"\n fill=\"none\"\n :stroke-dasharray=\"strokeDasharray\"/>\n </pattern>\n\n <pattern\n v-if=\"glow\"\n :id=\"glowId\"\n :width=\"width\"\n :height=\"height\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <path\n :class=\"$glow.glowLine\"\n :d=\"`M.5 ${height}V.5H${width}`\"\n fill=\"none\"\n :stroke-dasharray=\"strokeDasharray\"/>\n </pattern>\n </defs>\n\n <rect\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${id})`\"/>\n\n <svg\n v-if=\"squares?.length\"\n style=\"overflow: visible;\">\n <rect\n v-for=\"[x, y] of squares\"\n :key=\"`${x}-${y}`\"\n :width=\"width - 1\"\n :height=\"height - 1\"\n :x=\"x * width\"\n :y=\"y * height\"\n stroke-width=\"0\"/>\n </svg>\n\n <rect\n v-if=\"glow\"\n :class=\"[$glow.glowLayer, active && $glow.isActive]\"\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${glowId})`\"/>\n </svg>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $glow from '~flux/visuals/css/component/PatternGlow.module.scss';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n width = 42,\n height = 42,\n strokeDasharray = 0,\n squares,\n glow = false\n } = defineProps<{\n readonly width?: number;\n readonly height?: number;\n readonly strokeDasharray?: number | string;\n readonly squares?: Array<[x: number, y: number]>;\n readonly glow?: boolean;\n }>();\n\n const id = useId();\n const glowId = `${id}-glow`;\n const rootRef = useTemplateRef<SVGSVGElement>('root');\n const active = ref(false);\n\n // The pattern svg has pointer-events: none, so the cursor is tracked on the\n // parent scroll/overflow container instead. The position is written straight\n // to CSS custom properties to avoid a re-render on every pointermove.\n watch([rootRef, () => glow], (_, __, onCleanup) => {\n const root = unref(rootRef);\n\n if (!glow || !root || !root.parentElement) {\n return;\n }\n\n const parent = root.parentElement;\n\n const onPointerMove = (event: PointerEvent): void => {\n const rect = parent.getBoundingClientRect();\n root.style.setProperty('--pattern-glow-x', `${event.clientX - rect.left}px`);\n root.style.setProperty('--pattern-glow-y', `${event.clientY - rect.top}px`);\n };\n\n const onPointerEnter = (): void => {\n active.value = true;\n };\n\n const onPointerLeave = (): void => {\n active.value = false;\n };\n\n parent.addEventListener('pointermove', onPointerMove, {passive: true});\n parent.addEventListener('pointerenter', onPointerEnter);\n parent.addEventListener('pointerleave', onPointerLeave);\n\n onCleanup(() => {\n active.value = false;\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n });\n }, {immediate: true});\n</script>\n","<template>\n <svg\n ref=\"root\"\n aria-hidden=\"true\"\n :class=\"$style.gridPattern\">\n <defs>\n <pattern\n :id=\"id\"\n :width=\"width\"\n :height=\"height\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <path\n :d=\"`M.5 ${height}V.5H${width}`\"\n fill=\"none\"\n :stroke-dasharray=\"strokeDasharray\"/>\n </pattern>\n\n <pattern\n v-if=\"glow\"\n :id=\"glowId\"\n :width=\"width\"\n :height=\"height\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <path\n :class=\"$glow.glowLine\"\n :d=\"`M.5 ${height}V.5H${width}`\"\n fill=\"none\"\n :stroke-dasharray=\"strokeDasharray\"/>\n </pattern>\n </defs>\n\n <rect\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${id})`\"/>\n\n <svg\n v-if=\"squares?.length\"\n style=\"overflow: visible;\">\n <rect\n v-for=\"[x, y] of squares\"\n :key=\"`${x}-${y}`\"\n :width=\"width - 1\"\n :height=\"height - 1\"\n :x=\"x * width\"\n :y=\"y * height\"\n stroke-width=\"0\"/>\n </svg>\n\n <rect\n v-if=\"glow\"\n :class=\"[$glow.glowLayer, active && $glow.isActive]\"\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${glowId})`\"/>\n </svg>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $glow from '~flux/visuals/css/component/PatternGlow.module.scss';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n width = 42,\n height = 42,\n strokeDasharray = 0,\n squares,\n glow = false\n } = defineProps<{\n readonly width?: number;\n readonly height?: number;\n readonly strokeDasharray?: number | string;\n readonly squares?: Array<[x: number, y: number]>;\n readonly glow?: boolean;\n }>();\n\n const id = useId();\n const glowId = `${id}-glow`;\n const rootRef = useTemplateRef<SVGSVGElement>('root');\n const active = ref(false);\n\n // The pattern svg has pointer-events: none, so the cursor is tracked on the\n // parent scroll/overflow container instead. The position is written straight\n // to CSS custom properties to avoid a re-render on every pointermove.\n watch([rootRef, () => glow], (_, __, onCleanup) => {\n const root = unref(rootRef);\n\n if (!glow || !root || !root.parentElement) {\n return;\n }\n\n const parent = root.parentElement;\n\n const onPointerMove = (event: PointerEvent): void => {\n const rect = parent.getBoundingClientRect();\n root.style.setProperty('--pattern-glow-x', `${event.clientX - rect.left}px`);\n root.style.setProperty('--pattern-glow-y', `${event.clientY - rect.top}px`);\n };\n\n const onPointerEnter = (): void => {\n active.value = true;\n };\n\n const onPointerLeave = (): void => {\n active.value = false;\n };\n\n parent.addEventListener('pointermove', onPointerMove, {passive: true});\n parent.addEventListener('pointerenter', onPointerEnter);\n parent.addEventListener('pointerleave', onPointerLeave);\n\n onCleanup(() => {\n active.value = false;\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n });\n }, {immediate: true});\n</script>\n",".highlighter {\n position: relative;\n}\n\n.highlighterGroup {\n display: contents;\n}\n","<template>\n <span\n ref=\"target\"\n :class=\"$style.highlighter\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { prefersReducedMotion } from '@basmilius/utils';\n import type { FluxVisualHighlighterVariant } from '@flux-ui/types';\n import { annotate } from 'rough-notation';\n import { computed, onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue';\n import { type HighlighterGroupEntry, useHighlighterGroupInjection } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/Highlighter.module.scss';\n\n const emit = defineEmits<{\n hidden: [];\n shown: [];\n }>();\n\n // The annotation props deliberately have no destructure defaults: an unset\n // prop must stay distinguishable from an explicit one, so an enclosing group\n // can supply the value instead. The effective* computeds resolve the chain.\n const {\n variant,\n color,\n strokeWidth,\n animationDuration,\n iterations,\n padding,\n multiline,\n whenInView = false\n } = defineProps<{\n readonly variant?: FluxVisualHighlighterVariant;\n readonly color?: string;\n readonly strokeWidth?: number;\n readonly animationDuration?: number;\n readonly iterations?: number;\n readonly padding?: number;\n readonly multiline?: boolean;\n readonly whenInView?: boolean;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n\n const targetRef = useTemplateRef('target');\n\n const group = useHighlighterGroupInjection();\n\n // Standalone in-view tracking. In a group the parent owns the reveal timing,\n // so no observer is set up at all.\n const inView = group ? shallowRef(true) : useInView(targetRef, {initial: !whenInView});\n\n const effectiveVariant = computed(() => variant ?? group?.defaults.variant ?? 'highlight');\n const effectiveColor = computed(() => color ?? group?.defaults.color ?? 'var(--warning-border)');\n const effectiveStrokeWidth = computed(() => strokeWidth ?? group?.defaults.strokeWidth ?? 1.5);\n const effectiveAnimationDuration = computed(() => animationDuration ?? group?.defaults.animationDuration ?? 500);\n const effectiveIterations = computed(() => iterations ?? group?.defaults.iterations ?? 2);\n const effectivePadding = computed(() => padding ?? group?.defaults.padding ?? 2);\n const effectiveMultiline = computed(() => multiline ?? group?.defaults.multiline ?? true);\n\n let annotation: ReturnType<typeof annotate> | null = null;\n let entry: HighlighterGroupEntry | null = null;\n let observer: ResizeObserver | null = null;\n let settleTimer: number | undefined;\n let shownTimer: number | undefined;\n let revealed = false;\n\n // The annotation type is immutable, so any prop change rebuilds it from scratch.\n watch([effectiveVariant, effectiveColor, effectiveStrokeWidth, effectiveAnimationDuration, effectiveIterations, effectivePadding, effectiveMultiline], () => build());\n\n // Standalone reveal once the element scrolls into view (whenInView).\n watch(inView, () => {\n if (!group) {\n reveal();\n }\n });\n\n onMounted(() => {\n const element = targetRef.value;\n\n if (group && element) {\n entry = {element, getAnnotation: () => annotation};\n group.add(entry);\n }\n\n build();\n });\n\n onBeforeUnmount(() => {\n if (group && entry) {\n group.remove(entry);\n entry = null;\n }\n\n window.clearTimeout(shownTimer);\n stopSettleWatch();\n annotation?.remove();\n annotation = null;\n });\n\n // rough-notation has no completion callback, so shown is emitted after the\n // draw animation's duration, or immediately when it draws without animation.\n function emitShown(): void {\n window.clearTimeout(shownTimer);\n\n if (prefersReducedMotion()) {\n emit('shown');\n return;\n }\n\n shownTimer = window.setTimeout(() => emit('shown'), effectiveAnimationDuration.value);\n }\n\n function show(): void {\n if (!annotation) {\n return;\n }\n\n revealed = true;\n stopSettleWatch();\n annotation.show();\n emitShown();\n }\n\n function hide(): void {\n if (!annotation) {\n return;\n }\n\n window.clearTimeout(shownTimer);\n annotation.hide();\n emit('hidden');\n }\n\n function replay(): void {\n if (!annotation) {\n return;\n }\n\n annotation.hide();\n annotation.show();\n emitShown();\n }\n\n // Standalone: draw once, with the intro animation, on the final geometry.\n // rough-notation keeps the drawn annotation aligned on later resizes itself.\n function reveal(): void {\n if (revealed || !annotation || !inView.value) {\n return;\n }\n\n show();\n }\n\n function stopSettleWatch(): void {\n window.clearTimeout(settleTimer);\n observer?.disconnect();\n observer = null;\n }\n\n function build(): void {\n annotation?.remove();\n annotation = null;\n stopSettleWatch();\n revealed = false;\n\n const element = targetRef.value;\n\n if (!element) {\n return;\n }\n\n annotation = annotate(element, {\n type: effectiveVariant.value,\n color: effectiveColor.value,\n strokeWidth: effectiveStrokeWidth.value,\n animationDuration: effectiveAnimationDuration.value,\n iterations: effectiveIterations.value,\n padding: effectivePadding.value,\n multiline: effectiveMultiline.value,\n animate: !prefersReducedMotion()\n });\n\n // In a group the parent collects the annotations and drives the cascade.\n if (group) {\n group.notify();\n return;\n }\n\n if (typeof ResizeObserver === 'undefined') {\n reveal();\n return;\n }\n\n // The surrounding chrome (preview panels, web-font swaps) can reflow right\n // after mount, which would otherwise play the intro animation where the\n // text sat *before* it settled. Debounce until the layout is quiet, then\n // draw on the final geometry.\n observer = new ResizeObserver(() => {\n window.clearTimeout(settleTimer);\n settleTimer = window.setTimeout(() => reveal(), 80);\n });\n observer.observe(element);\n observer.observe(document.body);\n }\n\n defineExpose({\n hide,\n replay,\n show\n });\n</script>\n","<template>\n <span\n ref=\"target\"\n :class=\"$style.highlighter\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { prefersReducedMotion } from '@basmilius/utils';\n import type { FluxVisualHighlighterVariant } from '@flux-ui/types';\n import { annotate } from 'rough-notation';\n import { computed, onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue';\n import { type HighlighterGroupEntry, useHighlighterGroupInjection } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/Highlighter.module.scss';\n\n const emit = defineEmits<{\n hidden: [];\n shown: [];\n }>();\n\n // The annotation props deliberately have no destructure defaults: an unset\n // prop must stay distinguishable from an explicit one, so an enclosing group\n // can supply the value instead. The effective* computeds resolve the chain.\n const {\n variant,\n color,\n strokeWidth,\n animationDuration,\n iterations,\n padding,\n multiline,\n whenInView = false\n } = defineProps<{\n readonly variant?: FluxVisualHighlighterVariant;\n readonly color?: string;\n readonly strokeWidth?: number;\n readonly animationDuration?: number;\n readonly iterations?: number;\n readonly padding?: number;\n readonly multiline?: boolean;\n readonly whenInView?: boolean;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n\n const targetRef = useTemplateRef('target');\n\n const group = useHighlighterGroupInjection();\n\n // Standalone in-view tracking. In a group the parent owns the reveal timing,\n // so no observer is set up at all.\n const inView = group ? shallowRef(true) : useInView(targetRef, {initial: !whenInView});\n\n const effectiveVariant = computed(() => variant ?? group?.defaults.variant ?? 'highlight');\n const effectiveColor = computed(() => color ?? group?.defaults.color ?? 'var(--warning-border)');\n const effectiveStrokeWidth = computed(() => strokeWidth ?? group?.defaults.strokeWidth ?? 1.5);\n const effectiveAnimationDuration = computed(() => animationDuration ?? group?.defaults.animationDuration ?? 500);\n const effectiveIterations = computed(() => iterations ?? group?.defaults.iterations ?? 2);\n const effectivePadding = computed(() => padding ?? group?.defaults.padding ?? 2);\n const effectiveMultiline = computed(() => multiline ?? group?.defaults.multiline ?? true);\n\n let annotation: ReturnType<typeof annotate> | null = null;\n let entry: HighlighterGroupEntry | null = null;\n let observer: ResizeObserver | null = null;\n let settleTimer: number | undefined;\n let shownTimer: number | undefined;\n let revealed = false;\n\n // The annotation type is immutable, so any prop change rebuilds it from scratch.\n watch([effectiveVariant, effectiveColor, effectiveStrokeWidth, effectiveAnimationDuration, effectiveIterations, effectivePadding, effectiveMultiline], () => build());\n\n // Standalone reveal once the element scrolls into view (whenInView).\n watch(inView, () => {\n if (!group) {\n reveal();\n }\n });\n\n onMounted(() => {\n const element = targetRef.value;\n\n if (group && element) {\n entry = {element, getAnnotation: () => annotation};\n group.add(entry);\n }\n\n build();\n });\n\n onBeforeUnmount(() => {\n if (group && entry) {\n group.remove(entry);\n entry = null;\n }\n\n window.clearTimeout(shownTimer);\n stopSettleWatch();\n annotation?.remove();\n annotation = null;\n });\n\n // rough-notation has no completion callback, so shown is emitted after the\n // draw animation's duration, or immediately when it draws without animation.\n function emitShown(): void {\n window.clearTimeout(shownTimer);\n\n if (prefersReducedMotion()) {\n emit('shown');\n return;\n }\n\n shownTimer = window.setTimeout(() => emit('shown'), effectiveAnimationDuration.value);\n }\n\n function show(): void {\n if (!annotation) {\n return;\n }\n\n revealed = true;\n stopSettleWatch();\n annotation.show();\n emitShown();\n }\n\n function hide(): void {\n if (!annotation) {\n return;\n }\n\n window.clearTimeout(shownTimer);\n annotation.hide();\n emit('hidden');\n }\n\n function replay(): void {\n if (!annotation) {\n return;\n }\n\n annotation.hide();\n annotation.show();\n emitShown();\n }\n\n // Standalone: draw once, with the intro animation, on the final geometry.\n // rough-notation keeps the drawn annotation aligned on later resizes itself.\n function reveal(): void {\n if (revealed || !annotation || !inView.value) {\n return;\n }\n\n show();\n }\n\n function stopSettleWatch(): void {\n window.clearTimeout(settleTimer);\n observer?.disconnect();\n observer = null;\n }\n\n function build(): void {\n annotation?.remove();\n annotation = null;\n stopSettleWatch();\n revealed = false;\n\n const element = targetRef.value;\n\n if (!element) {\n return;\n }\n\n annotation = annotate(element, {\n type: effectiveVariant.value,\n color: effectiveColor.value,\n strokeWidth: effectiveStrokeWidth.value,\n animationDuration: effectiveAnimationDuration.value,\n iterations: effectiveIterations.value,\n padding: effectivePadding.value,\n multiline: effectiveMultiline.value,\n animate: !prefersReducedMotion()\n });\n\n // In a group the parent collects the annotations and drives the cascade.\n if (group) {\n group.notify();\n return;\n }\n\n if (typeof ResizeObserver === 'undefined') {\n reveal();\n return;\n }\n\n // The surrounding chrome (preview panels, web-font swaps) can reflow right\n // after mount, which would otherwise play the intro animation where the\n // text sat *before* it settled. Debounce until the layout is quiet, then\n // draw on the final geometry.\n observer = new ResizeObserver(() => {\n window.clearTimeout(settleTimer);\n settleTimer = window.setTimeout(() => reveal(), 80);\n });\n observer.observe(element);\n observer.observe(document.body);\n }\n\n defineExpose({\n hide,\n replay,\n show\n });\n</script>\n","<template>\n <span :class=\"$style.highlighterGroup\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import type { FluxVisualHighlighterGroupProps } from '@flux-ui/types';\n import { useHighlighterGroup } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/Highlighter.module.scss';\n\n const props = defineProps<FluxVisualHighlighterGroupProps>();\n\n defineSlots<{\n default(): any;\n }>();\n\n useHighlighterGroup(props);\n</script>\n","<template>\n <span :class=\"$style.highlighterGroup\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import type { FluxVisualHighlighterGroupProps } from '@flux-ui/types';\n import { useHighlighterGroup } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/Highlighter.module.scss';\n\n const props = defineProps<FluxVisualHighlighterGroupProps>();\n\n defineSlots<{\n default(): any;\n }>();\n\n useHighlighterGroup(props);\n</script>\n",".noise {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n opacity: var(--noise-opacity, .05);\n // Raw feTurbulence emits four independent noise channels, so the alpha is\n // also noise and the grain is a faint, half-transparent colored static that\n // washes out to nothing under a low opacity. The filter runs in sRGB (so the\n // grain stays centered on mid-gray instead of being gamma-lifted toward\n // white), collapses to a single opaque gray channel and boosts its contrast,\n // giving a neutral film grain that reads clearly even at a few percent.\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='1.8' intercept='-0.4'/%3E%3CfeFuncG type='linear' slope='1.8' intercept='-0.4'/%3E%3CfeFuncB type='linear' slope='1.8' intercept='-0.4'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E\");\n background-repeat: repeat;\n mix-blend-mode: var(--noise-blend, overlay);\n\n // The overlay blend has little to bite into on a dark surface, so the grain\n // is lifted to stay perceptible.\n [dark] & {\n opacity: calc(var(--noise-opacity, .05) * 1.5);\n }\n}\n\n.animated {\n animation: fluxVisualNoiseShift .6s steps(1) infinite;\n}\n\n// Discrete position jumps read as film grain without a per-frame script.\n@keyframes fluxVisualNoiseShift {\n 0% {\n background-position: 0 0;\n }\n\n 10% {\n background-position: -12px 6px;\n }\n\n 20% {\n background-position: 10px -14px;\n }\n\n 30% {\n background-position: -6px 12px;\n }\n\n 40% {\n background-position: 14px 4px;\n }\n\n 50% {\n background-position: -14px -8px;\n }\n\n 60% {\n background-position: 8px 14px;\n }\n\n 70% {\n background-position: -10px -12px;\n }\n\n 80% {\n background-position: 12px 8px;\n }\n\n 90% {\n background-position: -4px -14px;\n }\n\n 100% {\n background-position: 0 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .animated {\n animation: none;\n }\n}\n","<template>\n <div\n aria-hidden=\"true\"\n :class=\"clsx($style.noise, animated && $style.animated)\"\n :style=\"style\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { clsx } from 'clsx';\n import { computed } from 'vue';\n import $style from '~flux/visuals/css/component/Noise.module.scss';\n\n type NoiseBlendMode = 'normal' | 'multiply' | 'screen' | 'overlay' | 'soft-light' | 'plus-lighter';\n\n const {\n animated = false,\n blend = 'overlay',\n opacity = 0.05\n } = defineProps<{\n readonly animated?: boolean;\n readonly blend?: NoiseBlendMode;\n readonly opacity?: number;\n }>();\n\n const style = computed(() => ({\n '--noise-blend': blend,\n '--noise-opacity': opacity\n }));\n</script>\n","<template>\n <div\n aria-hidden=\"true\"\n :class=\"clsx($style.noise, animated && $style.animated)\"\n :style=\"style\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { clsx } from 'clsx';\n import { computed } from 'vue';\n import $style from '~flux/visuals/css/component/Noise.module.scss';\n\n type NoiseBlendMode = 'normal' | 'multiply' | 'screen' | 'overlay' | 'soft-light' | 'plus-lighter';\n\n const {\n animated = false,\n blend = 'overlay',\n opacity = 0.05\n } = defineProps<{\n readonly animated?: boolean;\n readonly blend?: NoiseBlendMode;\n readonly opacity?: number;\n }>();\n\n const style = computed(() => ({\n '--noise-blend': blend,\n '--noise-opacity': opacity\n }));\n</script>\n",".numberFlow {\n font-variant-numeric: tabular-nums;\n display: inline-block;\n white-space: nowrap;\n}\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"accessibleValue\"\n :class=\"$style.numberFlow\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { computed, onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/NumberFlow.module.scss';\n\n type NumberFlowEasingKeyword = 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';\n\n type NumberFlowEasingFunction = (t: number) => number;\n\n type NumberFlowEasing = NumberFlowEasingKeyword | `cubic-bezier(${string})` | NumberFlowEasingFunction;\n\n const {\n value,\n animateOnMount = true,\n duration = 800,\n // The default matches the --swift-out custom property from @flux-ui/components\n // (packages/components/src/css/variables.scss). Keep these control points in\n // sync with that variable so the tween matches the rest of the design system.\n easing = 'cubic-bezier(0.55, 0, 0.1, 1)',\n format,\n locale\n } = defineProps<{\n readonly value: number;\n readonly animateOnMount?: boolean;\n readonly duration?: number;\n readonly easing?: NumberFlowEasing;\n readonly format?: Intl.NumberFormatOptions;\n readonly locale?: string;\n }>();\n\n // Named easing curves for the rAF tween. A CSS easing string cannot be\n // sampled per frame, so the value tween uses these functions instead.\n const EASINGS: Record<NumberFlowEasingKeyword, NumberFlowEasingFunction> = {\n 'linear': progress => progress,\n 'ease-in': progress => progress * progress,\n 'ease-out': progress => 1 - (1 - progress) * (1 - progress),\n 'ease-in-out': progress => progress < .5 ? 2 * progress * progress : 1 - ((-2 * progress + 2) ** 2) / 2\n };\n\n const CUBIC_BEZIER_PATTERN = /^cubic-bezier\\(\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*\\)$/;\n\n // Fallback for an unrecognized easing value, matching the --swift-out default.\n const swiftOutEasing = cubicBezier(0.55, 0, 0.1, 1);\n\n const labelRef = useTemplateRef('label');\n\n let frame = 0;\n let current = animateOnMount ? 0 : value;\n\n // Resolve the easing prop to a plain (t) => number function the tween can\n // sample: functions pass through, keywords map to the table above and a\n // cubic-bezier(...) string is parsed into a solver, falling back to swift-out.\n const easingFunction = computed<NumberFlowEasingFunction>(() => {\n if (typeof easing === 'function') {\n return easing;\n }\n\n const keyword = EASINGS[easing as NumberFlowEasingKeyword];\n\n if (keyword) {\n return keyword;\n }\n\n return parseCubicBezier(easing) ?? swiftOutEasing;\n });\n\n // Default to whole numbers so a mid-tween value never sprouts stray decimals.\n // Currency, percentage and fractional displays opt in through `format`.\n const formatter = computed(() => new Intl.NumberFormat(locale, format ?? {maximumFractionDigits: 0}));\n\n // Rendered once for SSR / first paint only. The engine owns the span's text\n // after mount, so this must NOT be reactive - a reactive {{ value }} would make\n // Vue re-patch the text node every frame and wipe the tween. The accessible\n // name stays current through the reactive :aria-label binding.\n const initialText = formatter.value.format(animateOnMount ? 0 : value);\n\n // Screen readers always read the final, settled value rather than the\n // intermediate frames streaming past on screen.\n const accessibleValue = computed(() => formatter.value.format(value));\n\n // Tween from wherever the display currently sits, so a value that changes\n // mid-tween keeps rolling smoothly instead of jumping.\n watch(() => value, next => tween(current, next));\n\n // Re-render in place when the locale or format changes.\n watch(formatter, () => render(current));\n\n onMounted(() => {\n if (animateOnMount) {\n tween(0, value);\n } else {\n render(value);\n }\n });\n\n onBeforeUnmount(cancel);\n\n // Evaluate a cubic-bezier(x1, y1, x2, y2) timing function in JS. The control\n // points describe x(t) and y(t); for a given progress we need y at the t where\n // x(t) === progress. x is solved with Newton-Raphson and a bisection fallback,\n // mirroring the standard UnitBezier approach browsers use for CSS easings.\n function cubicBezier(x1: number, y1: number, x2: number, y2: number): NumberFlowEasingFunction {\n const ax = 3 * x1 - 3 * x2 + 1;\n const bx = 3 * x2 - 6 * x1;\n const cx = 3 * x1;\n\n const ay = 3 * y1 - 3 * y2 + 1;\n const by = 3 * y2 - 6 * y1;\n const cy = 3 * y1;\n\n const sampleX = (t: number): number => ((ax * t + bx) * t + cx) * t;\n const sampleY = (t: number): number => ((ay * t + by) * t + cy) * t;\n const slopeX = (t: number): number => (3 * ax * t + 2 * bx) * t + cx;\n\n const solveX = (x: number): number => {\n let t = x;\n\n for (let i = 0; i < 8; ++i) {\n const error = sampleX(t) - x;\n\n if (Math.abs(error) < 1e-6) {\n return t;\n }\n\n const slope = slopeX(t);\n\n if (Math.abs(slope) < 1e-6) {\n break;\n }\n\n t -= error / slope;\n }\n\n let low = 0;\n let high = 1;\n t = x;\n\n for (let i = 0; i < 20; ++i) {\n const estimate = sampleX(t);\n\n if (Math.abs(estimate - x) < 1e-6) {\n return t;\n }\n\n if (estimate < x) {\n low = t;\n } else {\n high = t;\n }\n\n t = (low + high) / 2;\n }\n\n return t;\n };\n\n return progress => {\n if (progress <= 0) {\n return 0;\n }\n\n if (progress >= 1) {\n return 1;\n }\n\n return sampleY(solveX(progress));\n };\n }\n\n // Parse a cubic-bezier(...) string into a solver, or return null when the\n // string is malformed so the caller can fall back to the default easing.\n function parseCubicBezier(input: string): NumberFlowEasingFunction | null {\n const match = CUBIC_BEZIER_PATTERN.exec(input.trim());\n\n if (!match) {\n return null;\n }\n\n return cubicBezier(Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4]));\n }\n\n function render(next: number): void {\n current = next;\n\n const element = labelRef.value;\n\n if (element) {\n element.textContent = formatter.value.format(next);\n }\n }\n\n function cancel(): void {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n }\n\n function tween(from: number, to: number): void {\n cancel();\n\n // Reduced motion, no distance or no duration: snap straight to the value.\n if (from === to || duration <= 0 || prefersReducedMotion()) {\n render(to);\n return;\n }\n\n const ease = easingFunction.value;\n const delta = to - from;\n const startTime = performance.now();\n\n const step = (now: number): void => {\n const progress = Math.min(1, (now - startTime) / duration);\n render(from + delta * ease(progress));\n\n if (progress < 1) {\n frame = requestAnimationFrame(step);\n } else {\n frame = 0;\n render(to);\n }\n };\n\n frame = requestAnimationFrame(step);\n }\n</script>\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"accessibleValue\"\n :class=\"$style.numberFlow\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { computed, onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/NumberFlow.module.scss';\n\n type NumberFlowEasingKeyword = 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';\n\n type NumberFlowEasingFunction = (t: number) => number;\n\n type NumberFlowEasing = NumberFlowEasingKeyword | `cubic-bezier(${string})` | NumberFlowEasingFunction;\n\n const {\n value,\n animateOnMount = true,\n duration = 800,\n // The default matches the --swift-out custom property from @flux-ui/components\n // (packages/components/src/css/variables.scss). Keep these control points in\n // sync with that variable so the tween matches the rest of the design system.\n easing = 'cubic-bezier(0.55, 0, 0.1, 1)',\n format,\n locale\n } = defineProps<{\n readonly value: number;\n readonly animateOnMount?: boolean;\n readonly duration?: number;\n readonly easing?: NumberFlowEasing;\n readonly format?: Intl.NumberFormatOptions;\n readonly locale?: string;\n }>();\n\n // Named easing curves for the rAF tween. A CSS easing string cannot be\n // sampled per frame, so the value tween uses these functions instead.\n const EASINGS: Record<NumberFlowEasingKeyword, NumberFlowEasingFunction> = {\n 'linear': progress => progress,\n 'ease-in': progress => progress * progress,\n 'ease-out': progress => 1 - (1 - progress) * (1 - progress),\n 'ease-in-out': progress => progress < .5 ? 2 * progress * progress : 1 - ((-2 * progress + 2) ** 2) / 2\n };\n\n const CUBIC_BEZIER_PATTERN = /^cubic-bezier\\(\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*\\)$/;\n\n // Fallback for an unrecognized easing value, matching the --swift-out default.\n const swiftOutEasing = cubicBezier(0.55, 0, 0.1, 1);\n\n const labelRef = useTemplateRef('label');\n\n let frame = 0;\n let current = animateOnMount ? 0 : value;\n\n // Resolve the easing prop to a plain (t) => number function the tween can\n // sample: functions pass through, keywords map to the table above and a\n // cubic-bezier(...) string is parsed into a solver, falling back to swift-out.\n const easingFunction = computed<NumberFlowEasingFunction>(() => {\n if (typeof easing === 'function') {\n return easing;\n }\n\n const keyword = EASINGS[easing as NumberFlowEasingKeyword];\n\n if (keyword) {\n return keyword;\n }\n\n return parseCubicBezier(easing) ?? swiftOutEasing;\n });\n\n // Default to whole numbers so a mid-tween value never sprouts stray decimals.\n // Currency, percentage and fractional displays opt in through `format`.\n const formatter = computed(() => new Intl.NumberFormat(locale, format ?? {maximumFractionDigits: 0}));\n\n // Rendered once for SSR / first paint only. The engine owns the span's text\n // after mount, so this must NOT be reactive - a reactive {{ value }} would make\n // Vue re-patch the text node every frame and wipe the tween. The accessible\n // name stays current through the reactive :aria-label binding.\n const initialText = formatter.value.format(animateOnMount ? 0 : value);\n\n // Screen readers always read the final, settled value rather than the\n // intermediate frames streaming past on screen.\n const accessibleValue = computed(() => formatter.value.format(value));\n\n // Tween from wherever the display currently sits, so a value that changes\n // mid-tween keeps rolling smoothly instead of jumping.\n watch(() => value, next => tween(current, next));\n\n // Re-render in place when the locale or format changes.\n watch(formatter, () => render(current));\n\n onMounted(() => {\n if (animateOnMount) {\n tween(0, value);\n } else {\n render(value);\n }\n });\n\n onBeforeUnmount(cancel);\n\n // Evaluate a cubic-bezier(x1, y1, x2, y2) timing function in JS. The control\n // points describe x(t) and y(t); for a given progress we need y at the t where\n // x(t) === progress. x is solved with Newton-Raphson and a bisection fallback,\n // mirroring the standard UnitBezier approach browsers use for CSS easings.\n function cubicBezier(x1: number, y1: number, x2: number, y2: number): NumberFlowEasingFunction {\n const ax = 3 * x1 - 3 * x2 + 1;\n const bx = 3 * x2 - 6 * x1;\n const cx = 3 * x1;\n\n const ay = 3 * y1 - 3 * y2 + 1;\n const by = 3 * y2 - 6 * y1;\n const cy = 3 * y1;\n\n const sampleX = (t: number): number => ((ax * t + bx) * t + cx) * t;\n const sampleY = (t: number): number => ((ay * t + by) * t + cy) * t;\n const slopeX = (t: number): number => (3 * ax * t + 2 * bx) * t + cx;\n\n const solveX = (x: number): number => {\n let t = x;\n\n for (let i = 0; i < 8; ++i) {\n const error = sampleX(t) - x;\n\n if (Math.abs(error) < 1e-6) {\n return t;\n }\n\n const slope = slopeX(t);\n\n if (Math.abs(slope) < 1e-6) {\n break;\n }\n\n t -= error / slope;\n }\n\n let low = 0;\n let high = 1;\n t = x;\n\n for (let i = 0; i < 20; ++i) {\n const estimate = sampleX(t);\n\n if (Math.abs(estimate - x) < 1e-6) {\n return t;\n }\n\n if (estimate < x) {\n low = t;\n } else {\n high = t;\n }\n\n t = (low + high) / 2;\n }\n\n return t;\n };\n\n return progress => {\n if (progress <= 0) {\n return 0;\n }\n\n if (progress >= 1) {\n return 1;\n }\n\n return sampleY(solveX(progress));\n };\n }\n\n // Parse a cubic-bezier(...) string into a solver, or return null when the\n // string is malformed so the caller can fall back to the default easing.\n function parseCubicBezier(input: string): NumberFlowEasingFunction | null {\n const match = CUBIC_BEZIER_PATTERN.exec(input.trim());\n\n if (!match) {\n return null;\n }\n\n return cubicBezier(Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4]));\n }\n\n function render(next: number): void {\n current = next;\n\n const element = labelRef.value;\n\n if (element) {\n element.textContent = formatter.value.format(next);\n }\n }\n\n function cancel(): void {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n }\n\n function tween(from: number, to: number): void {\n cancel();\n\n // Reduced motion, no distance or no duration: snap straight to the value.\n if (from === to || duration <= 0 || prefersReducedMotion()) {\n render(to);\n return;\n }\n\n const ease = easingFunction.value;\n const delta = to - from;\n const startTime = performance.now();\n\n const step = (now: number): void => {\n const progress = Math.min(1, (now - startTime) / duration);\n render(from + delta * ease(progress));\n\n if (progress < 1) {\n frame = requestAnimationFrame(step);\n } else {\n frame = 0;\n render(to);\n }\n };\n\n frame = requestAnimationFrame(step);\n }\n</script>\n",".paneIllustration {\n --mask: linear-gradient(to bottom, black, transparent);\n --mask-content: linear-gradient(to bottom, black, rgb(0 0 0 / .75), transparent);\n\n position: relative;\n border-radius: calc(var(--radius) - 1px);\n\n &:not(:first-child) {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n }\n\n &:not(:last-child) {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n }\n}\n\n.paneIllustrationContent {\n position: relative;\n display: flex;\n height: 100%;\n align-items: center;\n justify-content: center;\n}\n\n.paneIllustrationContentControlled {\n composes: paneIllustrationContent;\n\n overflow: hidden;\n\n -webkit-mask-image: var(--mask-content);\n mask-image: var(--mask-content);\n}\n\n.paneIllustrationMagic {\n position: absolute;\n inset: -1px;\n border-radius: inherit;\n}\n\n.paneIllustrationMasked {\n composes: paneIllustration;\n\n .paneIllustrationMagic {\n -webkit-mask-image: var(--mask);\n mask-image: var(--mask);\n }\n}\n","<template>\n <div\n data-flux-pane-illustration\n :class=\"isMasked ? $style.paneIllustrationMasked : $style.paneIllustration\"\n :style=\"{\n aspectRatio\n }\">\n <div\n :class=\"$style.paneIllustrationMagic\"\n :style=\"{\n border: `1px solid ${borderColor}`\n }\">\n <FluxVisualGridPattern :stroke-dasharray=\"3\"/>\n\n <FluxVisualAnimatedColors\n :colors=\"animatedColors\"\n :opacity=\"animatedOpacity\"\n :seed=\"animatedSeed\"/>\n </div>\n\n <div\n v-if=\"slots.controlled\"\n :class=\"$style.paneIllustrationContentControlled\">\n <slot name=\"controlled\"/>\n </div>\n\n <div\n v-if=\"slots.default\"\n :class=\"$style.paneIllustrationContent\">\n <slot/>\n </div>\n </div>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { hexToRGB } from '@basmilius/utils';\n import { computed, type VNode } from 'vue';\n import FluxVisualAnimatedColors from './FluxVisualAnimatedColors.vue';\n import FluxVisualGridPattern from './FluxVisualGridPattern.vue';\n import $style from '~flux/visuals/css/component/PaneIllustration.module.scss';\n\n const {\n animatedColors,\n aspectRatio = 16 / 9\n } = defineProps<{\n readonly animatedColors: string[];\n readonly animatedOpacity?: number;\n readonly animatedSeed?: number;\n readonly aspectRatio?: number;\n readonly isMasked?: boolean;\n }>();\n\n const slots = defineSlots<{\n default?(): VNode[];\n controlled?(): VNode[];\n }>();\n\n const borderColor = computed(() => {\n if (!animatedColors || animatedColors.length === 0) {\n return 'transparent';\n }\n\n const [r, g, b] = hexToRGB(animatedColors[0]);\n\n return `rgb(${r} ${g} ${b} / .15)`;\n });\n</script>\n","<template>\n <div\n data-flux-pane-illustration\n :class=\"isMasked ? $style.paneIllustrationMasked : $style.paneIllustration\"\n :style=\"{\n aspectRatio\n }\">\n <div\n :class=\"$style.paneIllustrationMagic\"\n :style=\"{\n border: `1px solid ${borderColor}`\n }\">\n <FluxVisualGridPattern :stroke-dasharray=\"3\"/>\n\n <FluxVisualAnimatedColors\n :colors=\"animatedColors\"\n :opacity=\"animatedOpacity\"\n :seed=\"animatedSeed\"/>\n </div>\n\n <div\n v-if=\"slots.controlled\"\n :class=\"$style.paneIllustrationContentControlled\">\n <slot name=\"controlled\"/>\n </div>\n\n <div\n v-if=\"slots.default\"\n :class=\"$style.paneIllustrationContent\">\n <slot/>\n </div>\n </div>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { hexToRGB } from '@basmilius/utils';\n import { computed, type VNode } from 'vue';\n import FluxVisualAnimatedColors from './FluxVisualAnimatedColors.vue';\n import FluxVisualGridPattern from './FluxVisualGridPattern.vue';\n import $style from '~flux/visuals/css/component/PaneIllustration.module.scss';\n\n const {\n animatedColors,\n aspectRatio = 16 / 9\n } = defineProps<{\n readonly animatedColors: string[];\n readonly animatedOpacity?: number;\n readonly animatedSeed?: number;\n readonly aspectRatio?: number;\n readonly isMasked?: boolean;\n }>();\n\n const slots = defineSlots<{\n default?(): VNode[];\n controlled?(): VNode[];\n }>();\n\n const borderColor = computed(() => {\n if (!animatedColors || animatedColors.length === 0) {\n return 'transparent';\n }\n\n const [r, g, b] = hexToRGB(animatedColors[0]);\n\n return `rgb(${r} ${g} ${b} / .15)`;\n });\n</script>\n",".ping {\n position: relative;\n display: inline-block;\n width: var(--ping-size);\n height: var(--ping-size);\n pointer-events: none;\n color: var(--ping-color);\n border-radius: 50%;\n background: currentColor;\n}\n\n.ping::before,\n.ping::after {\n position: absolute;\n inset: 0;\n content: '';\n animation: visualPing calc(var(--ping-duration) * 1s) cubic-bezier(0, 0, .2, 1) infinite;\n border-radius: 50%;\n background: currentColor;\n}\n\n.ping::after {\n animation-delay: calc(var(--ping-duration) * -.5s);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ping::before,\n .ping::after {\n content: none;\n }\n}\n\n@keyframes visualPing {\n 0% {\n transform: scale(1);\n opacity: .5;\n }\n\n 75%,\n 100% {\n transform: scale(2.5);\n opacity: 0;\n }\n}\n","<template>\n <span\n aria-hidden=\"true\"\n :class=\"$style.ping\"\n :style=\"{\n '--ping-color': `var(--${color}-solid)`,\n '--ping-size': `${size}px`,\n '--ping-duration': duration\n }\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import $style from '~flux/visuals/css/component/Ping.module.scss';\n\n const {\n color = 'success',\n size = 9,\n duration = 1.4\n } = defineProps<{\n readonly color?: 'gray' | 'primary' | 'danger' | 'info' | 'success' | 'warning';\n readonly size?: number;\n readonly duration?: number;\n }>();\n</script>\n","<template>\n <span\n aria-hidden=\"true\"\n :class=\"$style.ping\"\n :style=\"{\n '--ping-color': `var(--${color}-solid)`,\n '--ping-size': `${size}px`,\n '--ping-duration': duration\n }\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import $style from '~flux/visuals/css/component/Ping.module.scss';\n\n const {\n color = 'success',\n size = 9,\n duration = 1.4\n } = defineProps<{\n readonly color?: 'gray' | 'primary' | 'danger' | 'info' | 'success' | 'warning';\n readonly size?: number;\n readonly duration?: number;\n }>();\n</script>\n",".slotText {\n display: inline-flex;\n white-space: pre;\n}\n\n.charSlot {\n line-height: 1.3;\n position: relative;\n display: inline-flex;\n // Clip only vertically: the roll needs a top/bottom mask, but glyph side\n // bearings, kerning overhang and the settle tilt must stay visible so\n // letters never look cropped.\n overflow: hidden;\n overflow-x: visible;\n overflow-y: clip;\n // Cells must never flex-shrink, otherwise a width-constrained line crushes\n // them into overlapping slivers instead of letting the row overflow.\n flex: none;\n justify-content: center;\n vertical-align: bottom;\n}\n\n// Cells appearing from or collapsing to empty change width drastically, so clip\n// them horizontally too while they resize — their glyph wipes in/out with the\n// cell instead of spilling over the neighbors.\n.charSlot.isResizing {\n overflow-x: clip;\n}\n\n.charSizer {\n visibility: hidden;\n white-space: pre;\n}\n\n.charFace {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n white-space: pre;\n will-change: transform;\n}\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"text\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/SlotText.module.scss';\n\n type SlotTextColor = string | ((index: number, total: number) => string);\n type SlotTextDirection = 'up' | 'down';\n\n type AnimateOptions = {\n direction?: SlotTextDirection;\n stagger?: number;\n duration?: number;\n exitOffset?: number;\n easing?: string;\n bounce?: number;\n color?: SlotTextColor;\n colorFade?: number;\n skipUnchanged?: boolean;\n interrupt?: boolean;\n };\n\n type FlashOptions = {\n revertAfter?: number;\n enter?: AnimateOptions;\n exit?: AnimateOptions;\n };\n\n type SlotState = {\n timers: number[];\n target: string;\n pending?: { text: string; options: AnimateOptions; };\n };\n\n const {\n text,\n bounce = .6,\n chromatic = false,\n color,\n colorFade = 280,\n direction = 'down',\n duration = 300,\n easing = 'cubic-bezier(0.34, 1.56, 0.64, 1)',\n exitOffset = 50,\n interrupt = true,\n skipUnchanged = true,\n stagger = 45\n } = defineProps<{\n readonly text: string;\n readonly bounce?: number;\n readonly chromatic?: boolean;\n readonly color?: string;\n readonly colorFade?: number;\n readonly direction?: SlotTextDirection;\n readonly duration?: number;\n readonly easing?: string;\n readonly exitOffset?: number;\n readonly interrupt?: boolean;\n readonly skipUnchanged?: boolean;\n readonly stagger?: number;\n }>();\n\n const NBSP = '\\u00A0';\n\n // Rendered once for SSR / first paint only. The engine owns the span's DOM\n // after mount, so this must NOT be reactive - a reactive {{ text }} would make\n // Vue re-run setElementText on every change and wipe the animated glyph cells.\n // The accessible name stays current through the reactive :aria-label binding.\n const initialText = text;\n\n const labelRef = useTemplateRef('label');\n\n // Per-instance record of the in-flight roll, so a new roll can interrupt it.\n let state: SlotState | null = null;\n let revertTimeout: number | undefined;\n let restingText: string | undefined;\n\n watch(() => text, value => set(value));\n\n onMounted(() => {\n const element = labelRef.value;\n\n if (element) {\n buildSlotText(element, text);\n }\n });\n\n onBeforeUnmount(() => {\n window.clearTimeout(revertTimeout);\n\n const element = labelRef.value;\n\n if (element) {\n clearSlotText(element, text);\n }\n });\n\n function glyph(char: string): string {\n return char === ' ' ? NBSP : char;\n }\n\n // Sweep the hue across the line so the roll lands as a chromatic spectrum.\n function chromaticColor(index: number, total: number): string {\n const t = total <= 1 ? 0 : index / (total - 1);\n return `hsl(${(t * 320) % 360} 92% 60%)`;\n }\n\n function baseOptions(): AnimateOptions {\n return {\n direction,\n stagger,\n duration,\n exitOffset,\n easing,\n bounce,\n color: chromatic ? chromaticColor : color,\n colorFade,\n skipUnchanged,\n interrupt\n };\n }\n\n function makeFace(char: string): HTMLSpanElement {\n const face = document.createElement('span');\n face.className = $style.charFace;\n face.textContent = glyph(char);\n return face;\n }\n\n function buildSlot(char: string): HTMLSpanElement {\n const slot = document.createElement('span');\n slot.className = $style.charSlot;\n slot.dataset.char = char;\n\n // Invisible sizer keeps the cell exactly the width/height of its glyph,\n // so the absolutely-positioned animating faces never reflow the line.\n const sizer = document.createElement('span');\n sizer.className = $style.charSizer;\n sizer.textContent = glyph(char);\n\n slot.append(sizer, makeFace(char));\n return slot;\n }\n\n function buildSlotText(container: HTMLElement, value: string): void {\n container.classList.add($style.slotText);\n container.replaceChildren(...Array.from(value, buildSlot));\n }\n\n // Cancel any running roll on the container and snap it to its target text.\n function settle(container: HTMLElement): void {\n if (!state) {\n return;\n }\n\n state.timers.forEach(timer => window.clearTimeout(timer));\n\n // Rebuild a pristine DOM at the text the interrupted roll was heading\n // toward, so the next animation starts from a clean baseline.\n const target = state.target;\n state = null;\n buildSlotText(container, target);\n }\n\n function animateSlotText(container: HTMLElement, toText: string, options: AnimateOptions = {}): void {\n const {\n direction = 'down',\n stagger = 45,\n duration = 300,\n exitOffset = 50,\n easing = 'cubic-bezier(0.34, 1.56, 0.64, 1)',\n bounce = .6,\n color,\n colorFade = 280,\n skipUnchanged = true,\n interrupt = true\n } = options;\n\n // Reduced motion: swap to the new text without rolling.\n if (prefersReducedMotion()) {\n buildSlotText(container, toText);\n return;\n }\n\n // Non-interrupting mode: if a roll is already in flight, let it finish\n // and remember this request instead. Only the latest request survives,\n // so spam taps coalesce into a single follow-up roll once it lands.\n if (state && !interrupt) {\n if (toText !== state.target) {\n state.pending = {text: toText, options};\n }\n return;\n }\n\n // Interrupt: fast-forward any previous roll to its target and tear down\n // its timers before we start fresh.\n settle(container);\n\n // First run / empty container → just build it.\n if (!container.querySelector(`.${$style.charSlot}`)) {\n buildSlotText(container, toText);\n return;\n }\n\n const slots = Array.from(container.querySelectorAll<HTMLElement>(`.${$style.charSlot}`));\n const fromText = slots.map(slot => slot.dataset.char ?? '').join('');\n\n // Non-interrupting mode drops rolls to the text already on screen, so\n // repeated triggers do not visibly re-roll an unchanged label.\n if (!interrupt && fromText === toText) {\n return;\n }\n\n const maxLen = Math.max(fromText.length, toText.length);\n\n // Whole-pixel slide distance = one cell height, so glyphs clip cleanly.\n // Ceil, not round: half a pixel short leaves a sliver of the outgoing\n // glyph visible at the clip edge.\n const sample = slots.find(slot => (slot.dataset.char ?? '') !== '') ?? slots[0];\n const cs = getComputedStyle(container);\n const H = Math.ceil(\n sample?.getBoundingClientRect().height\n || sample?.offsetHeight\n || container.getBoundingClientRect().height\n || parseFloat(cs.lineHeight)\n || 0\n ) || Math.ceil(parseFloat(cs.fontSize) * 1.3) || 18;\n\n // Resting color to settle the chromatic flash back to.\n const restColor = color ? cs.color : '';\n\n // Pre-create any extra cells up front so the row never reflows mid-roll.\n for (let i = slots.length; i < maxLen; i++) {\n const slot = buildSlot('');\n container.appendChild(slot);\n slots.push(slot);\n }\n\n const timers: number[] = [];\n state = {timers, target: toText};\n\n // down: new enters from above (-H to 0), old exits below (0 to +H)\n // up: new enters from below (+H to 0), old exits above (0 to -H)\n const outY = direction === 'down' ? H : -H;\n const inStart = direction === 'down' ? -H : H;\n\n // A tiny deterministic jitter in [-1, 1] per character. Scaled by\n // `bounce` it gives each glyph its own speed and a little tilt-wobble,\n // so the line does not land as one rigid block.\n const wobble = (index: number, salt: number): number => {\n const n = Math.sin((index + 1) * 12.9898 + salt * 78.233) * 43758.5453;\n return (n - Math.floor(n)) * 2 - 1;\n };\n\n // Track the slowest letter so the safety-net snap waits for everyone.\n let maxEnd = 0;\n\n for (let i = 0; i < maxLen; i++) {\n const fromChar = fromText[i] || '';\n const toChar = toText[i] || '';\n\n if (fromChar === toChar && (skipUnchanged || fromChar === '')) {\n continue;\n }\n\n const slot = slots[i];\n const sizer = slot.querySelector<HTMLElement>(`.${$style.charSizer}`)!;\n const oldFace = slot.querySelector<HTMLElement>(`.${$style.charFace}`);\n\n // Resize the cell to the new glyph — but ease the width instead of\n // snapping it, so a wide outgoing glyph is never cropped by a\n // suddenly-narrow cell and neighbors glide rather than jump.\n const oldW = slot.getBoundingClientRect().width;\n sizer.textContent = glyph(toChar);\n const newW = sizer.getBoundingClientRect().width;\n const widthChanges = Math.abs(newW - oldW) > .5;\n\n if (widthChanges) {\n slot.style.width = `${oldW}px`;\n }\n\n // A cell growing from or collapsing to empty changes width\n // drastically — clip it horizontally while it resizes so its glyph\n // wipes in/out with the cell instead of stacking onto the neighbors.\n if (fromChar === '' || toChar === '') {\n slot.classList.add($style.isResizing);\n }\n\n const tint = typeof color === 'function' ? color(i, maxLen) : color;\n\n // Per-letter personality: vary the speed, the stagger and a starting\n // tilt that springs back to upright as the glyph settles. Tail cells\n // (rolling out to nothing) join the same wave instead of queuing\n // behind it, so nothing trails.\n const isTail = toChar === '';\n const d = Math.round(duration * (isTail ? .75 : 1) * (1 + bounce * .45 * wobble(i, 1)));\n const staggerIndex = isTail ? toText.length * .5 + (i - toText.length) * .25 : i;\n const base = Math.round(staggerIndex * stagger * (1 + bounce * .25 * wobble(i, 2)));\n const tilt = (bounce * 5 * wobble(i, 3)).toFixed(2);\n\n const rollTrans = `transform ${d}ms ${easing}`;\n const trans = color ? `${rollTrans}, color ${colorFade}ms linear ${d}ms` : rollTrans;\n\n const newFace = makeFace(toChar);\n newFace.style.transformOrigin = '50% 50%';\n newFace.style.transform = `translateY(${inStart}px) rotate(${tilt}deg)`;\n\n if (tint) {\n newFace.style.color = tint;\n }\n\n slot.appendChild(newFace);\n\n void slot.offsetWidth; // commit start transforms\n\n // Glide the cell to its new width with a clean ease-out (no\n // overshoot) so it never pinches narrower than either glyph. Timing\n // depends on the kind of change:\n // - glyph → glyph: resize alongside the roll.\n // - glyph → empty: roll out at full width first, then snap closed.\n // - empty → glyph: open the cell quickly before the glyph rolls in.\n if (widthChanges) {\n let wDelay = base;\n let wDur = d;\n\n if (isTail) {\n wDelay = base + Math.round(d * .55);\n wDur = Math.max(140, Math.round(d * .6));\n } else if (fromChar === '') {\n wDur = Math.max(140, Math.round(d * .45));\n }\n\n timers.push(window.setTimeout(() => {\n slot.style.transition = `width ${wDur}ms cubic-bezier(0.2, 0, 0, 1)`;\n slot.style.width = `${newW}px`;\n }, wDelay));\n\n maxEnd = Math.max(maxEnd, wDelay + wDur);\n }\n\n maxEnd = Math.max(maxEnd, base + exitOffset + d + (color ? colorFade : 0));\n\n // Outgoing glyph slides away first (with its own little counter-tilt).\n if (oldFace) {\n timers.push(window.setTimeout(() => {\n oldFace.style.transition = rollTrans;\n oldFace.style.transform = `translateY(${outY}px) rotate(${-Number(tilt)}deg)`;\n }, base));\n }\n\n // Incoming glyph chases it in (and, if tinted, fades to rest after).\n timers.push(window.setTimeout(() => {\n newFace.style.transition = trans;\n newFace.style.transform = 'translateY(0) rotate(0deg)';\n\n if (color) {\n newFace.style.color = restColor;\n }\n\n const done = (event: TransitionEvent): void => {\n if (event.propertyName !== 'transform') {\n return; // ignore the color fade\n }\n\n newFace.removeEventListener('transitionend', done);\n slot.dataset.char = toChar;\n // Hand sizing back to the sizer (same px, nothing moves).\n slot.style.removeProperty('transition');\n slot.style.removeProperty('width');\n slot.classList.remove($style.isResizing);\n slot.querySelectorAll(`.${$style.charFace}`).forEach(face => {\n if (face !== newFace) {\n face.remove();\n }\n });\n };\n\n newFace.addEventListener('transitionend', done);\n }, base + exitOffset));\n }\n\n // Safety net: snap to a pristine DOM once the slowest letter settles. If\n // a non-interrupting call was deferred mid-roll, replay it now as a fresh\n // roll from this clean baseline.\n const total = maxEnd + 80;\n timers.push(window.setTimeout(() => {\n const pending = state?.pending;\n state = null;\n buildSlotText(container, toText);\n\n if (pending) {\n animateSlotText(container, pending.text, pending.options);\n }\n }, total));\n }\n\n function clearSlotText(container: HTMLElement, value = ''): void {\n settle(container);\n container.classList.remove($style.slotText);\n container.textContent = value;\n }\n\n // Roll to new text. Cancels any pending flash revert.\n function set(toText: string, options: AnimateOptions = {}): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n window.clearTimeout(revertTimeout);\n restingText = undefined;\n animateSlotText(element, toText, {...baseOptions(), ...options});\n }\n\n // Roll to temporary text, then roll back automatically — the classic\n // Copy → Copied → Copy in one call. Spam-safe: repeat flashes restart the\n // revert timer instead of queuing extra rolls.\n function flash(toText: string, {revertAfter = 1400, enter, exit}: FlashOptions = {}): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n // Capture the resting text only on the first flash of a burst, so a\n // flash-during-flash still reverts to the original label.\n if (restingText === undefined) {\n restingText = text;\n }\n\n animateSlotText(element, toText, {...baseOptions(), interrupt: false, ...enter});\n\n window.clearTimeout(revertTimeout);\n revertTimeout = window.setTimeout(() => {\n const back = restingText!;\n restingText = undefined;\n revertTimeout = undefined;\n\n const current = labelRef.value;\n\n if (current) {\n animateSlotText(current, back, {...baseOptions(), interrupt: false, ...exit});\n }\n }, revertAfter);\n }\n\n defineExpose({\n flash,\n set\n });\n</script>\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"text\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/SlotText.module.scss';\n\n type SlotTextColor = string | ((index: number, total: number) => string);\n type SlotTextDirection = 'up' | 'down';\n\n type AnimateOptions = {\n direction?: SlotTextDirection;\n stagger?: number;\n duration?: number;\n exitOffset?: number;\n easing?: string;\n bounce?: number;\n color?: SlotTextColor;\n colorFade?: number;\n skipUnchanged?: boolean;\n interrupt?: boolean;\n };\n\n type FlashOptions = {\n revertAfter?: number;\n enter?: AnimateOptions;\n exit?: AnimateOptions;\n };\n\n type SlotState = {\n timers: number[];\n target: string;\n pending?: { text: string; options: AnimateOptions; };\n };\n\n const {\n text,\n bounce = .6,\n chromatic = false,\n color,\n colorFade = 280,\n direction = 'down',\n duration = 300,\n easing = 'cubic-bezier(0.34, 1.56, 0.64, 1)',\n exitOffset = 50,\n interrupt = true,\n skipUnchanged = true,\n stagger = 45\n } = defineProps<{\n readonly text: string;\n readonly bounce?: number;\n readonly chromatic?: boolean;\n readonly color?: string;\n readonly colorFade?: number;\n readonly direction?: SlotTextDirection;\n readonly duration?: number;\n readonly easing?: string;\n readonly exitOffset?: number;\n readonly interrupt?: boolean;\n readonly skipUnchanged?: boolean;\n readonly stagger?: number;\n }>();\n\n const NBSP = '\\u00A0';\n\n // Rendered once for SSR / first paint only. The engine owns the span's DOM\n // after mount, so this must NOT be reactive - a reactive {{ text }} would make\n // Vue re-run setElementText on every change and wipe the animated glyph cells.\n // The accessible name stays current through the reactive :aria-label binding.\n const initialText = text;\n\n const labelRef = useTemplateRef('label');\n\n // Per-instance record of the in-flight roll, so a new roll can interrupt it.\n let state: SlotState | null = null;\n let revertTimeout: number | undefined;\n let restingText: string | undefined;\n\n watch(() => text, value => set(value));\n\n onMounted(() => {\n const element = labelRef.value;\n\n if (element) {\n buildSlotText(element, text);\n }\n });\n\n onBeforeUnmount(() => {\n window.clearTimeout(revertTimeout);\n\n const element = labelRef.value;\n\n if (element) {\n clearSlotText(element, text);\n }\n });\n\n function glyph(char: string): string {\n return char === ' ' ? NBSP : char;\n }\n\n // Sweep the hue across the line so the roll lands as a chromatic spectrum.\n function chromaticColor(index: number, total: number): string {\n const t = total <= 1 ? 0 : index / (total - 1);\n return `hsl(${(t * 320) % 360} 92% 60%)`;\n }\n\n function baseOptions(): AnimateOptions {\n return {\n direction,\n stagger,\n duration,\n exitOffset,\n easing,\n bounce,\n color: chromatic ? chromaticColor : color,\n colorFade,\n skipUnchanged,\n interrupt\n };\n }\n\n function makeFace(char: string): HTMLSpanElement {\n const face = document.createElement('span');\n face.className = $style.charFace;\n face.textContent = glyph(char);\n return face;\n }\n\n function buildSlot(char: string): HTMLSpanElement {\n const slot = document.createElement('span');\n slot.className = $style.charSlot;\n slot.dataset.char = char;\n\n // Invisible sizer keeps the cell exactly the width/height of its glyph,\n // so the absolutely-positioned animating faces never reflow the line.\n const sizer = document.createElement('span');\n sizer.className = $style.charSizer;\n sizer.textContent = glyph(char);\n\n slot.append(sizer, makeFace(char));\n return slot;\n }\n\n function buildSlotText(container: HTMLElement, value: string): void {\n container.classList.add($style.slotText);\n container.replaceChildren(...Array.from(value, buildSlot));\n }\n\n // Cancel any running roll on the container and snap it to its target text.\n function settle(container: HTMLElement): void {\n if (!state) {\n return;\n }\n\n state.timers.forEach(timer => window.clearTimeout(timer));\n\n // Rebuild a pristine DOM at the text the interrupted roll was heading\n // toward, so the next animation starts from a clean baseline.\n const target = state.target;\n state = null;\n buildSlotText(container, target);\n }\n\n function animateSlotText(container: HTMLElement, toText: string, options: AnimateOptions = {}): void {\n const {\n direction = 'down',\n stagger = 45,\n duration = 300,\n exitOffset = 50,\n easing = 'cubic-bezier(0.34, 1.56, 0.64, 1)',\n bounce = .6,\n color,\n colorFade = 280,\n skipUnchanged = true,\n interrupt = true\n } = options;\n\n // Reduced motion: swap to the new text without rolling.\n if (prefersReducedMotion()) {\n buildSlotText(container, toText);\n return;\n }\n\n // Non-interrupting mode: if a roll is already in flight, let it finish\n // and remember this request instead. Only the latest request survives,\n // so spam taps coalesce into a single follow-up roll once it lands.\n if (state && !interrupt) {\n if (toText !== state.target) {\n state.pending = {text: toText, options};\n }\n return;\n }\n\n // Interrupt: fast-forward any previous roll to its target and tear down\n // its timers before we start fresh.\n settle(container);\n\n // First run / empty container → just build it.\n if (!container.querySelector(`.${$style.charSlot}`)) {\n buildSlotText(container, toText);\n return;\n }\n\n const slots = Array.from(container.querySelectorAll<HTMLElement>(`.${$style.charSlot}`));\n const fromText = slots.map(slot => slot.dataset.char ?? '').join('');\n\n // Non-interrupting mode drops rolls to the text already on screen, so\n // repeated triggers do not visibly re-roll an unchanged label.\n if (!interrupt && fromText === toText) {\n return;\n }\n\n const maxLen = Math.max(fromText.length, toText.length);\n\n // Whole-pixel slide distance = one cell height, so glyphs clip cleanly.\n // Ceil, not round: half a pixel short leaves a sliver of the outgoing\n // glyph visible at the clip edge.\n const sample = slots.find(slot => (slot.dataset.char ?? '') !== '') ?? slots[0];\n const cs = getComputedStyle(container);\n const H = Math.ceil(\n sample?.getBoundingClientRect().height\n || sample?.offsetHeight\n || container.getBoundingClientRect().height\n || parseFloat(cs.lineHeight)\n || 0\n ) || Math.ceil(parseFloat(cs.fontSize) * 1.3) || 18;\n\n // Resting color to settle the chromatic flash back to.\n const restColor = color ? cs.color : '';\n\n // Pre-create any extra cells up front so the row never reflows mid-roll.\n for (let i = slots.length; i < maxLen; i++) {\n const slot = buildSlot('');\n container.appendChild(slot);\n slots.push(slot);\n }\n\n const timers: number[] = [];\n state = {timers, target: toText};\n\n // down: new enters from above (-H to 0), old exits below (0 to +H)\n // up: new enters from below (+H to 0), old exits above (0 to -H)\n const outY = direction === 'down' ? H : -H;\n const inStart = direction === 'down' ? -H : H;\n\n // A tiny deterministic jitter in [-1, 1] per character. Scaled by\n // `bounce` it gives each glyph its own speed and a little tilt-wobble,\n // so the line does not land as one rigid block.\n const wobble = (index: number, salt: number): number => {\n const n = Math.sin((index + 1) * 12.9898 + salt * 78.233) * 43758.5453;\n return (n - Math.floor(n)) * 2 - 1;\n };\n\n // Track the slowest letter so the safety-net snap waits for everyone.\n let maxEnd = 0;\n\n for (let i = 0; i < maxLen; i++) {\n const fromChar = fromText[i] || '';\n const toChar = toText[i] || '';\n\n if (fromChar === toChar && (skipUnchanged || fromChar === '')) {\n continue;\n }\n\n const slot = slots[i];\n const sizer = slot.querySelector<HTMLElement>(`.${$style.charSizer}`)!;\n const oldFace = slot.querySelector<HTMLElement>(`.${$style.charFace}`);\n\n // Resize the cell to the new glyph — but ease the width instead of\n // snapping it, so a wide outgoing glyph is never cropped by a\n // suddenly-narrow cell and neighbors glide rather than jump.\n const oldW = slot.getBoundingClientRect().width;\n sizer.textContent = glyph(toChar);\n const newW = sizer.getBoundingClientRect().width;\n const widthChanges = Math.abs(newW - oldW) > .5;\n\n if (widthChanges) {\n slot.style.width = `${oldW}px`;\n }\n\n // A cell growing from or collapsing to empty changes width\n // drastically — clip it horizontally while it resizes so its glyph\n // wipes in/out with the cell instead of stacking onto the neighbors.\n if (fromChar === '' || toChar === '') {\n slot.classList.add($style.isResizing);\n }\n\n const tint = typeof color === 'function' ? color(i, maxLen) : color;\n\n // Per-letter personality: vary the speed, the stagger and a starting\n // tilt that springs back to upright as the glyph settles. Tail cells\n // (rolling out to nothing) join the same wave instead of queuing\n // behind it, so nothing trails.\n const isTail = toChar === '';\n const d = Math.round(duration * (isTail ? .75 : 1) * (1 + bounce * .45 * wobble(i, 1)));\n const staggerIndex = isTail ? toText.length * .5 + (i - toText.length) * .25 : i;\n const base = Math.round(staggerIndex * stagger * (1 + bounce * .25 * wobble(i, 2)));\n const tilt = (bounce * 5 * wobble(i, 3)).toFixed(2);\n\n const rollTrans = `transform ${d}ms ${easing}`;\n const trans = color ? `${rollTrans}, color ${colorFade}ms linear ${d}ms` : rollTrans;\n\n const newFace = makeFace(toChar);\n newFace.style.transformOrigin = '50% 50%';\n newFace.style.transform = `translateY(${inStart}px) rotate(${tilt}deg)`;\n\n if (tint) {\n newFace.style.color = tint;\n }\n\n slot.appendChild(newFace);\n\n void slot.offsetWidth; // commit start transforms\n\n // Glide the cell to its new width with a clean ease-out (no\n // overshoot) so it never pinches narrower than either glyph. Timing\n // depends on the kind of change:\n // - glyph → glyph: resize alongside the roll.\n // - glyph → empty: roll out at full width first, then snap closed.\n // - empty → glyph: open the cell quickly before the glyph rolls in.\n if (widthChanges) {\n let wDelay = base;\n let wDur = d;\n\n if (isTail) {\n wDelay = base + Math.round(d * .55);\n wDur = Math.max(140, Math.round(d * .6));\n } else if (fromChar === '') {\n wDur = Math.max(140, Math.round(d * .45));\n }\n\n timers.push(window.setTimeout(() => {\n slot.style.transition = `width ${wDur}ms cubic-bezier(0.2, 0, 0, 1)`;\n slot.style.width = `${newW}px`;\n }, wDelay));\n\n maxEnd = Math.max(maxEnd, wDelay + wDur);\n }\n\n maxEnd = Math.max(maxEnd, base + exitOffset + d + (color ? colorFade : 0));\n\n // Outgoing glyph slides away first (with its own little counter-tilt).\n if (oldFace) {\n timers.push(window.setTimeout(() => {\n oldFace.style.transition = rollTrans;\n oldFace.style.transform = `translateY(${outY}px) rotate(${-Number(tilt)}deg)`;\n }, base));\n }\n\n // Incoming glyph chases it in (and, if tinted, fades to rest after).\n timers.push(window.setTimeout(() => {\n newFace.style.transition = trans;\n newFace.style.transform = 'translateY(0) rotate(0deg)';\n\n if (color) {\n newFace.style.color = restColor;\n }\n\n const done = (event: TransitionEvent): void => {\n if (event.propertyName !== 'transform') {\n return; // ignore the color fade\n }\n\n newFace.removeEventListener('transitionend', done);\n slot.dataset.char = toChar;\n // Hand sizing back to the sizer (same px, nothing moves).\n slot.style.removeProperty('transition');\n slot.style.removeProperty('width');\n slot.classList.remove($style.isResizing);\n slot.querySelectorAll(`.${$style.charFace}`).forEach(face => {\n if (face !== newFace) {\n face.remove();\n }\n });\n };\n\n newFace.addEventListener('transitionend', done);\n }, base + exitOffset));\n }\n\n // Safety net: snap to a pristine DOM once the slowest letter settles. If\n // a non-interrupting call was deferred mid-roll, replay it now as a fresh\n // roll from this clean baseline.\n const total = maxEnd + 80;\n timers.push(window.setTimeout(() => {\n const pending = state?.pending;\n state = null;\n buildSlotText(container, toText);\n\n if (pending) {\n animateSlotText(container, pending.text, pending.options);\n }\n }, total));\n }\n\n function clearSlotText(container: HTMLElement, value = ''): void {\n settle(container);\n container.classList.remove($style.slotText);\n container.textContent = value;\n }\n\n // Roll to new text. Cancels any pending flash revert.\n function set(toText: string, options: AnimateOptions = {}): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n window.clearTimeout(revertTimeout);\n restingText = undefined;\n animateSlotText(element, toText, {...baseOptions(), ...options});\n }\n\n // Roll to temporary text, then roll back automatically — the classic\n // Copy → Copied → Copy in one call. Spam-safe: repeat flashes restart the\n // revert timer instead of queuing extra rolls.\n function flash(toText: string, {revertAfter = 1400, enter, exit}: FlashOptions = {}): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n // Capture the resting text only on the first flash of a burst, so a\n // flash-during-flash still reverts to the original label.\n if (restingText === undefined) {\n restingText = text;\n }\n\n animateSlotText(element, toText, {...baseOptions(), interrupt: false, ...enter});\n\n window.clearTimeout(revertTimeout);\n revertTimeout = window.setTimeout(() => {\n const back = restingText!;\n restingText = undefined;\n revertTimeout = undefined;\n\n const current = labelRef.value;\n\n if (current) {\n animateSlotText(current, back, {...baseOptions(), interrupt: false, ...exit});\n }\n }, revertAfter);\n }\n\n defineExpose({\n flash,\n set\n });\n</script>\n",".textScramble {\n font-variant-numeric: tabular-nums;\n display: inline-block;\n white-space: pre;\n}\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"text\"\n :class=\"$style.textScramble\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { onBeforeUnmount, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/TextScramble.module.scss';\n\n type ScrambleCell = {\n from: string;\n to: string;\n start: number;\n end: number;\n char: string;\n lastSwap: number;\n fixed: boolean;\n };\n\n const emit = defineEmits<{\n finished: [];\n }>();\n\n const {\n text,\n characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',\n duration = 900,\n skipUnchanged = true,\n speed = 45,\n stagger = 0.5\n } = defineProps<{\n readonly text: string;\n readonly characters?: string;\n readonly duration?: number;\n readonly skipUnchanged?: boolean;\n readonly speed?: number;\n readonly stagger?: number;\n }>();\n\n // Rendered once for SSR / first paint only. The engine owns the span's text\n // after mount, so this must NOT be reactive - a reactive {{ text }} would make\n // Vue re-patch the text node every frame and wipe the scramble. The accessible\n // name stays current through the reactive :aria-label binding.\n const initialText = text;\n\n const labelRef = useTemplateRef('label');\n\n let frame = 0;\n let currentText = text;\n\n watch(() => text, value => set(value));\n\n onBeforeUnmount(cancel);\n\n function cancel(): void {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n }\n\n function randomChar(): string {\n return characters.charAt(Math.floor(Math.random() * characters.length));\n }\n\n // Decode `toText` character by character. Each cell holds its old glyph until\n // its staggered start, cycles through random glyphs, then settles on its\n // final glyph. `force` re-scrambles even unchanged cells, for replay().\n function scramble(element: HTMLElement, fromText: string, toText: string, force = false): void {\n cancel();\n\n // Reduced motion or no duration: swap straight to the final text.\n if (duration <= 0 || prefersReducedMotion()) {\n element.textContent = toText;\n emit('finished');\n return;\n }\n\n const maxLen = Math.max(fromText.length, toText.length);\n const spread = Math.min(Math.max(stagger, 0), 1);\n const revealWindow = duration * spread;\n const scrambleFor = duration - revealWindow;\n const cells: ScrambleCell[] = [];\n\n for (let i = 0; i < maxLen; ++i) {\n const from = fromText.charAt(i);\n const to = toText.charAt(i);\n\n if (!force && skipUnchanged && from !== '' && from === to) {\n cells.push({from, to, start: 0, end: 0, char: to, lastSwap: 0, fixed: true});\n continue;\n }\n\n const start = maxLen <= 1 ? 0 : (i / (maxLen - 1)) * revealWindow;\n cells.push({from, to, start, end: start + scrambleFor, char: '', lastSwap: -Infinity, fixed: false});\n }\n\n const startTime = performance.now();\n\n const step = (now: number): void => {\n const elapsed = now - startTime;\n let output = '';\n let done = 0;\n\n for (const cell of cells) {\n if (cell.fixed || elapsed >= cell.end) {\n output += cell.to;\n ++done;\n } else if (elapsed >= cell.start) {\n if (now - cell.lastSwap >= speed) {\n cell.char = randomChar();\n cell.lastSwap = now;\n }\n\n output += cell.char;\n } else {\n output += cell.from;\n }\n }\n\n element.textContent = output;\n\n if (done === cells.length) {\n frame = 0;\n emit('finished');\n return;\n }\n\n frame = requestAnimationFrame(step);\n };\n\n frame = requestAnimationFrame(step);\n }\n\n // Decode toward new text permanently, scrambling from the text on screen.\n function set(toText: string): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n const fromText = currentText;\n currentText = toText;\n scramble(element, fromText, toText);\n }\n\n // Re-run the decode on the current text without changing it.\n function replay(): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n scramble(element, currentText, currentText, true);\n }\n\n defineExpose({\n replay,\n set\n });\n</script>\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"text\"\n :class=\"$style.textScramble\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { onBeforeUnmount, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/TextScramble.module.scss';\n\n type ScrambleCell = {\n from: string;\n to: string;\n start: number;\n end: number;\n char: string;\n lastSwap: number;\n fixed: boolean;\n };\n\n const emit = defineEmits<{\n finished: [];\n }>();\n\n const {\n text,\n characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',\n duration = 900,\n skipUnchanged = true,\n speed = 45,\n stagger = 0.5\n } = defineProps<{\n readonly text: string;\n readonly characters?: string;\n readonly duration?: number;\n readonly skipUnchanged?: boolean;\n readonly speed?: number;\n readonly stagger?: number;\n }>();\n\n // Rendered once for SSR / first paint only. The engine owns the span's text\n // after mount, so this must NOT be reactive - a reactive {{ text }} would make\n // Vue re-patch the text node every frame and wipe the scramble. The accessible\n // name stays current through the reactive :aria-label binding.\n const initialText = text;\n\n const labelRef = useTemplateRef('label');\n\n let frame = 0;\n let currentText = text;\n\n watch(() => text, value => set(value));\n\n onBeforeUnmount(cancel);\n\n function cancel(): void {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n }\n\n function randomChar(): string {\n return characters.charAt(Math.floor(Math.random() * characters.length));\n }\n\n // Decode `toText` character by character. Each cell holds its old glyph until\n // its staggered start, cycles through random glyphs, then settles on its\n // final glyph. `force` re-scrambles even unchanged cells, for replay().\n function scramble(element: HTMLElement, fromText: string, toText: string, force = false): void {\n cancel();\n\n // Reduced motion or no duration: swap straight to the final text.\n if (duration <= 0 || prefersReducedMotion()) {\n element.textContent = toText;\n emit('finished');\n return;\n }\n\n const maxLen = Math.max(fromText.length, toText.length);\n const spread = Math.min(Math.max(stagger, 0), 1);\n const revealWindow = duration * spread;\n const scrambleFor = duration - revealWindow;\n const cells: ScrambleCell[] = [];\n\n for (let i = 0; i < maxLen; ++i) {\n const from = fromText.charAt(i);\n const to = toText.charAt(i);\n\n if (!force && skipUnchanged && from !== '' && from === to) {\n cells.push({from, to, start: 0, end: 0, char: to, lastSwap: 0, fixed: true});\n continue;\n }\n\n const start = maxLen <= 1 ? 0 : (i / (maxLen - 1)) * revealWindow;\n cells.push({from, to, start, end: start + scrambleFor, char: '', lastSwap: -Infinity, fixed: false});\n }\n\n const startTime = performance.now();\n\n const step = (now: number): void => {\n const elapsed = now - startTime;\n let output = '';\n let done = 0;\n\n for (const cell of cells) {\n if (cell.fixed || elapsed >= cell.end) {\n output += cell.to;\n ++done;\n } else if (elapsed >= cell.start) {\n if (now - cell.lastSwap >= speed) {\n cell.char = randomChar();\n cell.lastSwap = now;\n }\n\n output += cell.char;\n } else {\n output += cell.from;\n }\n }\n\n element.textContent = output;\n\n if (done === cells.length) {\n frame = 0;\n emit('finished');\n return;\n }\n\n frame = requestAnimationFrame(step);\n };\n\n frame = requestAnimationFrame(step);\n }\n\n // Decode toward new text permanently, scrambling from the text on screen.\n function set(toText: string): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n const fromText = currentText;\n currentText = toText;\n scramble(element, fromText, toText);\n }\n\n // Re-run the decode on the current text without changing it.\n function replay(): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n scramble(element, currentText, currentText, true);\n }\n\n defineExpose({\n replay,\n set\n });\n</script>\n",".textShimmer {\n display: inline-block;\n animation: textShimmerSweep var(--shimmer-duration, 2s) linear infinite;\n color: transparent;\n background-image: linear-gradient(\n 90deg,\n var(--shimmer-base, var(--foreground-subtle)) calc(50% - var(--shimmer-spread, 15) * 1%),\n var(--shimmer-color, var(--foreground-prominent)) 50%,\n var(--shimmer-base, var(--foreground-subtle)) calc(50% + var(--shimmer-spread, 15) * 1%)\n );\n // Both edges of the gradient are the base color, so tiling it seamlessly\n // keeps the text fully painted at every step: outside the moving highlight\n // band the text is always covered by the base color instead of falling off\n // the single tile into a transparent (clipped) gap. A no-repeat background\n // only covers the text for background-position 0%..100%; past that the tile\n // slides off and the text vanishes.\n background-repeat: repeat;\n background-position: 100% center;\n background-clip: text;\n background-size: 200% 100%;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n\n // No sweep when reduced motion is requested: fall back to a plain, solid\n // text color so the label stays legible without animating.\n @media (prefers-reduced-motion: reduce) {\n animation: none;\n color: var(--shimmer-base, var(--foreground-subtle));\n background: none;\n -webkit-text-fill-color: currentColor;\n }\n}\n\n// The 100% to -100% travel shifts the background by exactly one tile width\n// (background-size is 200%), so the repeated pattern lands back on itself and\n// the loop restarts without a jump.\n@keyframes textShimmerSweep {\n from {\n background-position: 100% center;\n }\n\n to {\n background-position: -100% center;\n }\n}\n","<template>\n <span\n :class=\"$style.textShimmer\"\n :style=\"{\n '--shimmer-duration': `${duration}s`,\n '--shimmer-spread': `${spread}`,\n '--shimmer-base': color,\n '--shimmer-color': shimmerColor\n }\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import $style from '~flux/visuals/css/component/TextShimmer.module.scss';\n\n const {\n color,\n duration = 2,\n shimmerColor,\n spread = 15\n } = defineProps<{\n readonly color?: string;\n readonly duration?: number;\n readonly shimmerColor?: string;\n readonly spread?: number;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n</script>\n","<template>\n <span\n :class=\"$style.textShimmer\"\n :style=\"{\n '--shimmer-duration': `${duration}s`,\n '--shimmer-spread': `${spread}`,\n '--shimmer-base': color,\n '--shimmer-color': shimmerColor\n }\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import $style from '~flux/visuals/css/component/TextShimmer.module.scss';\n\n const {\n color,\n duration = 2,\n shimmerColor,\n spread = 15\n } = defineProps<{\n readonly color?: string;\n readonly duration?: number;\n readonly shimmerColor?: string;\n readonly spread?: number;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n</script>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/css/component/Visual.module.scss","../src/component/FluxVisualAnimatedColors.vue","../src/component/FluxVisualAnimatedColors.vue","../src/css/component/Attention.module.scss","../src/component/FluxVisualAttention.vue","../src/component/FluxVisualAttention.vue","../src/composable/private/useBorderBeamPulse.ts","../src/composable/private/useHighlighterGroup.ts","../src/css/component/BorderBeam.module.scss","../src/component/FluxVisualBorderBeam.vue","../src/component/FluxVisualBorderBeam.vue","../src/component/FluxVisualBorderShine.vue","../src/component/FluxVisualBorderShine.vue","../src/css/component/PatternGlow.module.scss","../src/component/FluxVisualDotPattern.vue","../src/component/FluxVisualDotPattern.vue","../src/component/FluxVisualFlickeringGrid.vue","../src/component/FluxVisualFlickeringGrid.vue","../src/component/FluxVisualGridPattern.vue","../src/component/FluxVisualGridPattern.vue","../src/css/component/Highlighter.module.scss","../src/component/FluxVisualHighlighter.vue","../src/component/FluxVisualHighlighter.vue","../src/component/FluxVisualHighlighterGroup.vue","../src/component/FluxVisualHighlighterGroup.vue","../src/css/component/Noise.module.scss","../src/component/FluxVisualNoise.vue","../src/component/FluxVisualNoise.vue","../src/css/component/NumberFlow.module.scss","../src/component/FluxVisualNumberFlow.vue","../src/component/FluxVisualNumberFlow.vue","../src/css/component/PaneIllustration.module.scss","../src/component/FluxVisualPaneIllustration.vue","../src/component/FluxVisualPaneIllustration.vue","../src/css/component/Ping.module.scss","../src/component/FluxVisualPing.vue","../src/component/FluxVisualPing.vue","../src/css/component/SlotText.module.scss","../src/component/FluxVisualSlotText.vue","../src/component/FluxVisualSlotText.vue","../src/css/component/TextScramble.module.scss","../src/component/FluxVisualTextScramble.vue","../src/component/FluxVisualTextScramble.vue","../src/css/component/TextShimmer.module.scss","../src/component/FluxVisualTextShimmer.vue","../src/component/FluxVisualTextShimmer.vue"],"sourcesContent":["@property --shine-degrees {\n syntax: '<angle>';\n initial-value: 0deg;\n inherits: false;\n}\n\n.fillVisual {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n}\n\n.animatedColors {\n composes: fillVisual;\n\n filter: blur(60px) saturate(180%);\n}\n\n.dotPattern {\n composes: fillVisual;\n\n fill: var(--surface-stroke-hover);\n pointer-events: none;\n}\n\n.flickeringGrid {\n composes: fillVisual;\n\n pointer-events: none;\n}\n\n.gridPattern {\n composes: fillVisual;\n\n fill: var(--surface-stroke-muted);\n stroke: var(--surface-stroke-hover);\n pointer-events: none;\n}\n\n.borderShine {\n position: relative;\n\n --shine-radius: var(--radius);\n --shine-mask: linear-gradient(#fff #{0} #{0}) content-box, linear-gradient(#fff #{0} #{0});\n\n &::before {\n position: absolute;\n display: block;\n inset: calc(var(--shine-offset) * -1px);\n padding: calc(var(--shine-width) * 1px);\n content: '';\n background: conic-gradient(from var(--shine-degrees), #{var(--shine-colors)});\n border-radius: var(--shine-radius);\n pointer-events: none;\n animation: borderShinePosition calc(var(--shine-duration) * 1s) linear infinite;\n mask: var(--shine-mask);\n -webkit-mask-composite: xor;\n mask-composite: exclude;\n }\n}\n\n@keyframes borderShinePosition {\n from {\n --shine-degrees: 0deg;\n }\n\n to {\n --shine-degrees: 360deg;\n }\n}\n","<template>\n <canvas\n ref=\"canvas\"\n aria-hidden=\"true\"\n :class=\"$style.animatedColors\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { mulberry32, prefersReducedMotion } from '@basmilius/utils';\n import { computed, onBeforeUnmount, ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n type Polygon = [number, number, string, PolygonPoint[]];\n type PolygonPoint = [number, number, number];\n\n const {\n colors,\n incrementor = 1,\n opacity = .5,\n seed,\n static: isStatic\n } = defineProps<{\n readonly colors?: string[];\n readonly incrementor?: number;\n readonly opacity?: number;\n readonly seed?: number;\n readonly static?: boolean;\n }>();\n\n const canvasRef = useTemplateRef('canvas');\n const contextRef = ref<CanvasRenderingContext2D>();\n const animationFrame = ref(0);\n const tick = ref(0);\n const size = ref<{ width: number; height: number; } | null>(null);\n\n const instanceId = useId();\n const inView = useInView(canvasRef, {initial: true});\n const reducedMotion = prefersReducedMotion();\n\n const polygons = computed(() => {\n if (!colors || colors.length === 0) {\n return [];\n }\n\n const mulberry = mulberry32(seed ?? hashId(instanceId));\n const polygons: Polygon[] = [];\n\n for (const color of colors) {\n const localMulberry = mulberry.fork();\n\n const x = colors.length === 1 ? .5 : localMulberry.next();\n const y = colors.length === 1 ? .5 : localMulberry.next();\n const count = Math.round(localMulberry.nextBetween(6, 9));\n const points: PolygonPoint[] = [];\n\n for (let p = 0; p < count; ++p) {\n points.push([\n localMulberry.next(),\n localMulberry.next(),\n localMulberry.next()\n ]);\n }\n\n polygons.push([x, y, color, points]);\n }\n\n return polygons;\n });\n\n watch(canvasRef, (canvas, _, onCleanup) => {\n if (!canvas) {\n contextRef.value = undefined;\n size.value = null;\n return;\n }\n\n contextRef.value = canvas.getContext('2d', {\n alpha: true,\n colorSpace: 'display-p3'\n })!;\n\n if (typeof ResizeObserver === 'undefined') {\n size.value = {width: canvas.offsetWidth, height: canvas.offsetHeight};\n canvas.width = canvas.offsetWidth;\n canvas.height = canvas.offsetHeight;\n return;\n }\n\n const observer = new ResizeObserver(() => {\n const width = canvas.offsetWidth;\n const height = canvas.offsetHeight;\n\n if (!width || !height || (size.value?.width === width && size.value?.height === height)) {\n return;\n }\n\n canvas.width = width;\n canvas.height = height;\n size.value = {width, height};\n });\n\n observer.observe(canvas);\n\n onCleanup(() => observer.disconnect());\n }, {immediate: true});\n\n watch([polygons, () => opacity, size, inView], () => restart());\n\n onBeforeUnmount(() => cancel());\n\n function cancel(): void {\n cancelAnimationFrame(animationFrame.value);\n animationFrame.value = 0;\n }\n\n function schedule(): void {\n animationFrame.value = requestAnimationFrame(update);\n tick.value += incrementor;\n }\n\n function update(): void {\n render();\n\n if (!isStatic && !reducedMotion && unref(inView)) {\n schedule();\n } else {\n animationFrame.value = 0;\n }\n }\n\n function render(): void {\n const context = unref(contextRef);\n const shapes = unref(polygons);\n const dimensions = unref(size);\n\n if (!context || shapes.length === 0 || !dimensions) {\n return;\n }\n\n const {width, height} = dimensions;\n const widthBasedOpacity = Math.min(1, Math.max(.15, 360 / width));\n\n context.globalAlpha = opacity * widthBasedOpacity;\n context.globalCompositeOperation = 'screen';\n context.clearRect(0, 0, width, height);\n\n for (const [tx, ty, color, shape] of shapes) {\n context.save();\n context.translate(tx * width, ty * height);\n context.beginPath();\n context.fillStyle = color;\n\n for (let i = 0; i < shape.length; ++i) {\n let [x, y, m] = shape[i];\n\n x = Math.cos(x * Math.PI * 2 + tick.value / (m * 200 + 300)) * (width * .8);\n y = Math.sin(y * Math.PI * 2 + tick.value / (m * 100 + 300)) * (height * .8);\n\n if (i === 0) {\n context.moveTo(x, y);\n } else {\n context.lineTo(x, y);\n }\n }\n\n context.closePath();\n context.fill();\n context.restore();\n }\n }\n\n function restart(): void {\n cancel();\n\n if (isStatic || reducedMotion || !unref(inView)) {\n render();\n return;\n }\n\n schedule();\n }\n\n // mulberry32 seeds on a number, so the id becomes one.\n function hashId(value: string): number {\n let hash = 0;\n\n for (let index = 0; index < value.length; index++) {\n hash = (hash * 31 + value.charCodeAt(index)) | 0;\n }\n\n return hash;\n }\n</script>\n","<template>\n <canvas\n ref=\"canvas\"\n aria-hidden=\"true\"\n :class=\"$style.animatedColors\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { mulberry32, prefersReducedMotion } from '@basmilius/utils';\n import { computed, onBeforeUnmount, ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n type Polygon = [number, number, string, PolygonPoint[]];\n type PolygonPoint = [number, number, number];\n\n const {\n colors,\n incrementor = 1,\n opacity = .5,\n seed,\n static: isStatic\n } = defineProps<{\n readonly colors?: string[];\n readonly incrementor?: number;\n readonly opacity?: number;\n readonly seed?: number;\n readonly static?: boolean;\n }>();\n\n const canvasRef = useTemplateRef('canvas');\n const contextRef = ref<CanvasRenderingContext2D>();\n const animationFrame = ref(0);\n const tick = ref(0);\n const size = ref<{ width: number; height: number; } | null>(null);\n\n const instanceId = useId();\n const inView = useInView(canvasRef, {initial: true});\n const reducedMotion = prefersReducedMotion();\n\n const polygons = computed(() => {\n if (!colors || colors.length === 0) {\n return [];\n }\n\n const mulberry = mulberry32(seed ?? hashId(instanceId));\n const polygons: Polygon[] = [];\n\n for (const color of colors) {\n const localMulberry = mulberry.fork();\n\n const x = colors.length === 1 ? .5 : localMulberry.next();\n const y = colors.length === 1 ? .5 : localMulberry.next();\n const count = Math.round(localMulberry.nextBetween(6, 9));\n const points: PolygonPoint[] = [];\n\n for (let p = 0; p < count; ++p) {\n points.push([\n localMulberry.next(),\n localMulberry.next(),\n localMulberry.next()\n ]);\n }\n\n polygons.push([x, y, color, points]);\n }\n\n return polygons;\n });\n\n watch(canvasRef, (canvas, _, onCleanup) => {\n if (!canvas) {\n contextRef.value = undefined;\n size.value = null;\n return;\n }\n\n contextRef.value = canvas.getContext('2d', {\n alpha: true,\n colorSpace: 'display-p3'\n })!;\n\n if (typeof ResizeObserver === 'undefined') {\n size.value = {width: canvas.offsetWidth, height: canvas.offsetHeight};\n canvas.width = canvas.offsetWidth;\n canvas.height = canvas.offsetHeight;\n return;\n }\n\n const observer = new ResizeObserver(() => {\n const width = canvas.offsetWidth;\n const height = canvas.offsetHeight;\n\n if (!width || !height || (size.value?.width === width && size.value?.height === height)) {\n return;\n }\n\n canvas.width = width;\n canvas.height = height;\n size.value = {width, height};\n });\n\n observer.observe(canvas);\n\n onCleanup(() => observer.disconnect());\n }, {immediate: true});\n\n watch([polygons, () => opacity, size, inView], () => restart());\n\n onBeforeUnmount(() => cancel());\n\n function cancel(): void {\n cancelAnimationFrame(animationFrame.value);\n animationFrame.value = 0;\n }\n\n function schedule(): void {\n animationFrame.value = requestAnimationFrame(update);\n tick.value += incrementor;\n }\n\n function update(): void {\n render();\n\n if (!isStatic && !reducedMotion && unref(inView)) {\n schedule();\n } else {\n animationFrame.value = 0;\n }\n }\n\n function render(): void {\n const context = unref(contextRef);\n const shapes = unref(polygons);\n const dimensions = unref(size);\n\n if (!context || shapes.length === 0 || !dimensions) {\n return;\n }\n\n const {width, height} = dimensions;\n const widthBasedOpacity = Math.min(1, Math.max(.15, 360 / width));\n\n context.globalAlpha = opacity * widthBasedOpacity;\n context.globalCompositeOperation = 'screen';\n context.clearRect(0, 0, width, height);\n\n for (const [tx, ty, color, shape] of shapes) {\n context.save();\n context.translate(tx * width, ty * height);\n context.beginPath();\n context.fillStyle = color;\n\n for (let i = 0; i < shape.length; ++i) {\n let [x, y, m] = shape[i];\n\n x = Math.cos(x * Math.PI * 2 + tick.value / (m * 200 + 300)) * (width * .8);\n y = Math.sin(y * Math.PI * 2 + tick.value / (m * 100 + 300)) * (height * .8);\n\n if (i === 0) {\n context.moveTo(x, y);\n } else {\n context.lineTo(x, y);\n }\n }\n\n context.closePath();\n context.fill();\n context.restore();\n }\n }\n\n function restart(): void {\n cancel();\n\n if (isStatic || reducedMotion || !unref(inView)) {\n render();\n return;\n }\n\n schedule();\n }\n\n // mulberry32 seeds on a number, so the id becomes one.\n function hashId(value: string): number {\n let hash = 0;\n\n for (let index = 0; index < value.length; index++) {\n hash = (hash * 31 + value.charCodeAt(index)) | 0;\n }\n\n return hash;\n }\n</script>\n",".pulse {\n animation: visualAttentionPulse calc(var(--attention-duration) * 1ms) ease-in-out;\n}\n\n.shake {\n animation: visualAttentionShake calc(var(--attention-duration) * 1ms) ease-in-out;\n}\n\n.bounce {\n animation: visualAttentionBounce calc(var(--attention-duration) * 1ms) ease;\n}\n\n.tada {\n animation: visualAttentionTada calc(var(--attention-duration) * 1ms) ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .pulse,\n .shake,\n .bounce,\n .tada {\n animation: none;\n }\n}\n\n@keyframes visualAttentionPulse {\n 0% {\n transform: scale(1);\n }\n\n 50% {\n transform: scale(1.06);\n }\n\n 100% {\n transform: scale(1);\n }\n}\n\n@keyframes visualAttentionShake {\n 0%,\n 100% {\n transform: translateX(0);\n }\n\n 20% {\n transform: translateX(-6px);\n }\n\n 40% {\n transform: translateX(6px);\n }\n\n 60% {\n transform: translateX(-3px);\n }\n\n 80% {\n transform: translateX(3px);\n }\n}\n\n@keyframes visualAttentionBounce {\n 0%,\n 100% {\n transform: translateY(0);\n }\n\n 30% {\n transform: translateY(-9px);\n }\n\n 60% {\n transform: translateY(-3px);\n }\n}\n\n@keyframes visualAttentionTada {\n 0% {\n transform: scale(1) rotate(0);\n }\n\n 10%,\n 20% {\n transform: scale(.94) rotate(-3deg);\n }\n\n 30%,\n 50%,\n 70%,\n 90% {\n transform: scale(1.06) rotate(3deg);\n }\n\n 40%,\n 60%,\n 80% {\n transform: scale(1.06) rotate(-3deg);\n }\n\n 100% {\n transform: scale(1) rotate(0);\n }\n}\n","<script lang=\"ts\">\n import { prefersReducedMotion } from '@basmilius/utils';\n import { flattenVNodeTree } from '@flux-ui/internals';\n import { clsx } from 'clsx';\n import { cloneVNode, defineComponent, Fragment, h, onBeforeUnmount, type PropType, ref, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Attention.module.scss';\n\n type AttentionEffect = 'pulse' | 'shake' | 'bounce' | 'tada';\n\n export default defineComponent({\n inheritAttrs: false,\n props: {\n duration: {default: 700, type: Number},\n effect: {default: 'pulse', type: String as PropType<AttentionEffect>},\n trigger: {default: undefined, type: null as unknown as PropType<unknown>}\n },\n emits: {\n finished: () => true\n },\n setup(props, {attrs, emit, expose, slots}) {\n const EFFECT_CLASSES: Record<AttentionEffect, string> = {\n pulse: $style.pulse,\n shake: $style.shake,\n bounce: $style.bounce,\n tada: $style.tada\n };\n\n const isPlaying = ref(false);\n\n let restartFrame = 0;\n\n // Drop the effect class and re-add it two frames later so the animation\n // restarts cleanly, even when the same effect is played back to back.\n function play(): void {\n if (prefersReducedMotion()) {\n emit('finished');\n return;\n }\n\n cancelAnimationFrame(restartFrame);\n isPlaying.value = false;\n\n restartFrame = requestAnimationFrame(() => {\n restartFrame = requestAnimationFrame(() => {\n isPlaying.value = true;\n });\n });\n }\n\n function onAnimationEnd(event: AnimationEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n isPlaying.value = false;\n emit('finished');\n }\n\n watch(() => props.trigger, () => {\n play();\n });\n\n onBeforeUnmount(() => {\n cancelAnimationFrame(restartFrame);\n });\n\n expose({\n play\n });\n\n return () => h(\n Fragment,\n flattenVNodeTree(slots.default?.() ?? []).map(vnode => cloneVNode(vnode, {\n ...attrs,\n class: clsx(\n attrs.class as string,\n isPlaying.value && EFFECT_CLASSES[props.effect]\n ),\n style: {\n '--attention-duration': props.duration\n },\n onAnimationend: onAnimationEnd\n }))\n );\n }\n });\n</script>\n","<script lang=\"ts\">\n import { prefersReducedMotion } from '@basmilius/utils';\n import { flattenVNodeTree } from '@flux-ui/internals';\n import { clsx } from 'clsx';\n import { cloneVNode, defineComponent, Fragment, h, onBeforeUnmount, type PropType, ref, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Attention.module.scss';\n\n type AttentionEffect = 'pulse' | 'shake' | 'bounce' | 'tada';\n\n export default defineComponent({\n inheritAttrs: false,\n props: {\n duration: {default: 700, type: Number},\n effect: {default: 'pulse', type: String as PropType<AttentionEffect>},\n trigger: {default: undefined, type: null as unknown as PropType<unknown>}\n },\n emits: {\n finished: () => true\n },\n setup(props, {attrs, emit, expose, slots}) {\n const EFFECT_CLASSES: Record<AttentionEffect, string> = {\n pulse: $style.pulse,\n shake: $style.shake,\n bounce: $style.bounce,\n tada: $style.tada\n };\n\n const isPlaying = ref(false);\n\n let restartFrame = 0;\n\n // Drop the effect class and re-add it two frames later so the animation\n // restarts cleanly, even when the same effect is played back to back.\n function play(): void {\n if (prefersReducedMotion()) {\n emit('finished');\n return;\n }\n\n cancelAnimationFrame(restartFrame);\n isPlaying.value = false;\n\n restartFrame = requestAnimationFrame(() => {\n restartFrame = requestAnimationFrame(() => {\n isPlaying.value = true;\n });\n });\n }\n\n function onAnimationEnd(event: AnimationEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n isPlaying.value = false;\n emit('finished');\n }\n\n watch(() => props.trigger, () => {\n play();\n });\n\n onBeforeUnmount(() => {\n cancelAnimationFrame(restartFrame);\n });\n\n expose({\n play\n });\n\n return () => h(\n Fragment,\n flattenVNodeTree(slots.default?.() ?? []).map(vnode => cloneVNode(vnode, {\n ...attrs,\n class: clsx(\n attrs.class as string,\n isPlaying.value && EFFECT_CLASSES[props.effect]\n ),\n style: {\n '--attention-duration': props.duration\n },\n onAnimationend: onAnimationEnd\n }))\n );\n }\n });\n</script>\n","import { prefersReducedMotion } from '@basmilius/utils';\nimport type { FluxVisualBorderBeamVariant } from '@flux-ui/types';\nimport { type Ref, unref, watchEffect } from 'vue';\n\ntype PulseOscillator = {\n readonly prop: string;\n readonly a: number;\n readonly b: number;\n readonly delay: number;\n readonly period: number;\n readonly unit: '' | 'px';\n};\n\ntype PulseConfig = {\n readonly huePeriod: number | null;\n readonly oscillators: PulseOscillator[];\n};\n\ntype PulseInstance = {\n readonly config: PulseConfig;\n readonly element: HTMLElement;\n};\n\nconst FRAME_INTERVAL = 1000 / 30 - 2;\nconst TWO_PI = Math.PI * 2;\n\nconst instances = new Set<PulseInstance>();\nlet lastFrame = 0;\nlet rafId: number | null = null;\n\n/**\n * Cosine ease-in-out factor in [0, 1]: 0 at phase 0/1, 1 at phase 0.5.\n *\n * @param phase The current phase within the oscillation period.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nfunction pingPong(phase: number): number {\n return (1 - Math.cos(TWO_PI * phase)) / 2;\n}\n\n/**\n * Single shared requestAnimationFrame loop, throttled to ~30fps, that drives the\n * breathing motion of every registered pulse instance by writing CSS custom\n * properties. The breathing is very slow (1.6–6.4s periods), so a capped JS loop\n * repaints the gradient layers far less often than per-instance CSS keyframes\n * running at the display refresh rate would.\n *\n * @param ts The current timestamp provided by requestAnimationFrame.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nfunction frame(ts: number): void {\n rafId = requestAnimationFrame(frame);\n\n if (ts - lastFrame < FRAME_INTERVAL) {\n return;\n }\n\n lastFrame = ts;\n\n const tSec = ts / 1000;\n\n instances.forEach(({config, element}) => {\n for (const osc of config.oscillators) {\n const phase = (tSec - osc.delay) / osc.period;\n const value = osc.a + (osc.b - osc.a) * pingPong(phase);\n\n element.style.setProperty(osc.prop, osc.unit === 'px' ? `${value.toFixed(2)}px` : value.toFixed(4));\n }\n\n if (config.huePeriod !== null) {\n const value = ((tSec / config.huePeriod) % 1) * 360;\n\n element.style.setProperty('--beam-hue', `${value.toFixed(2)}deg`);\n }\n });\n}\n\n/**\n * Registers an element to be driven by the shared pulse loop and returns a\n * cleanup function that unregisters it again, stopping the loop once no\n * instances remain.\n *\n * @param element The border beam wrapper element.\n * @param config The oscillator configuration for the instance.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nfunction registerPulseInstance(element: HTMLElement, config: PulseConfig): () => void {\n const instance: PulseInstance = {config, element};\n instances.add(instance);\n\n if (rafId === null) {\n lastFrame = 0;\n rafId = requestAnimationFrame(frame);\n }\n\n return () => {\n instances.delete(instance);\n\n if (instances.size === 0 && rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n };\n}\n\n/**\n * Builds the theme/variant/duration-tuned oscillator table for a pulse instance.\n * Kept in sync with the gradient geometry in BorderBeam.module.scss.\n *\n * @param variant The pulse variant.\n * @param isDark Whether the instance is rendered within a dark themed tree.\n * @param duration The breathing duration in seconds.\n * @param staticColors Whether the hue drift is disabled.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nfunction createPulseConfig(variant: 'pulse-inner' | 'pulse-outside', isDark: boolean, duration: number, staticColors: boolean): PulseConfig {\n const durScale = duration / 2.3;\n const isInner = variant === 'pulse-inner';\n\n const sp = isInner ? .28 : (isDark ? .28 : .36);\n const dr = isInner ? (isDark ? 33 : 40) : (isDark ? 14 : 19);\n const op = isInner ? (isDark ? .48 : .45) : (isDark ? .46 : 0);\n const gh = isInner ? (isDark ? .34 : .22) : (isDark ? .16 : .58);\n const bs = (isInner ? (isDark ? 1.9 : 2.6) : (isDark ? 2.3 : 3.7)) * durScale;\n const ss = (isInner ? (isDark ? 2.6 : 4.6) : (isDark ? 6.4 : 4.6)) * durScale;\n const ghs = (isInner ? (isDark ? 2.4 : 5.5) : (isDark ? 2.4 : 3.8)) * durScale;\n const huePeriod = isInner ? 16 : 14;\n\n return {\n huePeriod: staticColors ? null : huePeriod,\n oscillators: [\n {prop: '--beam-bw1', a: 1 - sp, b: 1 + sp * 1.1, period: ss * .9, delay: 0, unit: ''},\n {prop: '--beam-bh1', a: 1 + sp * .9, b: 1 - sp * .85, period: ss * 1.26, delay: 0, unit: ''},\n {prop: '--beam-bx1', a: -dr, b: dr * .9, period: bs * 1.6, delay: 0, unit: 'px'},\n {prop: '--beam-by1', a: dr * .55, b: -dr * .7, period: bs * 1.6, delay: 0, unit: 'px'},\n {prop: '--beam-bw2', a: 1 + sp, b: 1 - sp * .85, period: ss * 1.1, delay: 0, unit: ''},\n {prop: '--beam-bh2', a: 1 - sp * .8, b: 1 + sp * 1.05, period: ss * .81, delay: 0, unit: ''},\n {prop: '--beam-bx2', a: dr * .8, b: -dr * .9, period: bs * 1.88, delay: 0, unit: 'px'},\n {prop: '--beam-by2', a: -dr, b: dr * .65, period: bs * 1.88, delay: 0, unit: 'px'},\n {prop: '--beam-bw3', a: 1 - sp * .6, b: 1 + sp * 1.15, period: ss * .98, delay: 0, unit: ''},\n {prop: '--beam-bh3', a: 1 + sp * .75, b: 1 - sp, period: ss * 1.4, delay: 0, unit: ''},\n {prop: '--beam-bx3', a: -dr * .6, b: dr, period: bs * 1.45, delay: 0, unit: 'px'},\n {prop: '--beam-by3', a: -dr * .85, b: dr * .45, period: bs * 1.45, delay: 0, unit: 'px'},\n {prop: '--beam-bgh', a: 1 - gh, b: 1 + gh, period: ghs, delay: 0, unit: ''},\n {prop: '--beam-bop-tl', a: 1 - op, b: 1, period: bs, delay: 0, unit: ''},\n {prop: '--beam-bop-tr', a: 1 - op, b: 1, period: bs * 1.32, delay: bs * .28, unit: ''},\n {prop: '--beam-bop-bl', a: 1 - op, b: 1, period: bs * .84, delay: bs * .55, unit: ''},\n {prop: '--beam-bop-br', a: 1 - op, b: 1, period: bs * 1.58, delay: bs * .83, unit: ''}\n ]\n };\n}\n\ntype UseBorderBeamPulseOptions = {\n readonly duration: Ref<number>;\n readonly elementRef: Ref<HTMLElement | null>;\n readonly enabled: Ref<boolean>;\n readonly staticColors: Ref<boolean>;\n readonly variant: Ref<FluxVisualBorderBeamVariant>;\n};\n\n/**\n * Drives the breathing of a pulse border beam from the shared, frame-rate-capped\n * animation loop while the instance is enabled. Respects prefers-reduced-motion\n * and resolves the theme from the nearest `[dark]` ancestor.\n *\n * @param options The reactive instance options.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nexport default function useBorderBeamPulse(options: UseBorderBeamPulseOptions): void {\n watchEffect(onCleanup => {\n const variant = unref(options.variant);\n\n if (variant !== 'pulse-inner' && variant !== 'pulse-outside') {\n return;\n }\n\n const element = unref(options.elementRef);\n\n if (!element || !unref(options.enabled)) {\n return;\n }\n\n if (prefersReducedMotion()) {\n return;\n }\n\n const isDark = element.closest('[dark]') !== null;\n const config = createPulseConfig(variant, isDark, unref(options.duration), unref(options.staticColors));\n\n onCleanup(registerPulseInstance(element, config));\n });\n}\n","import type { FluxVisualHighlighterGroupProps } from '@flux-ui/types';\nimport { annotationGroup } from 'rough-notation';\nimport { inject, type InjectionKey, onScopeDispose, provide } from 'vue';\n\ntype Annotation = Parameters<typeof annotationGroup>[0][number];\n\nexport type HighlighterGroupEntry = {\n readonly element: HTMLElement;\n getAnnotation(): Annotation | null;\n};\n\nexport type HighlighterGroupContext = {\n readonly defaults: FluxVisualHighlighterGroupProps;\n add(entry: HighlighterGroupEntry): void;\n remove(entry: HighlighterGroupEntry): void;\n notify(): void;\n};\n\nconst FluxVisualHighlighterGroupInjectionKey: InjectionKey<HighlighterGroupContext> = Symbol('flux-visual-highlighter-group');\n\n/**\n * Injects the enclosing highlighter group, if any. A `FluxVisualHighlighter`\n * that finds a group registers its annotation with it and lets the group drive\n * the cascade instead of drawing itself.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nexport function useHighlighterGroupInjection(): HighlighterGroupContext | null {\n return inject(FluxVisualHighlighterGroupInjectionKey, null);\n}\n\n/**\n * Collects the annotations of the descendant highlighters and reveals them as a\n * single rough-notation group, so they draw one after another in document order\n * rather than all at once. The draw is debounced so the initial burst of child\n * registrations (and any surrounding chrome settling its layout) coalesces into\n * one cascade, and — when `whenInView` is set — it waits until the first\n * highlighter scrolls into view. rough-notation keeps the drawn annotations\n * aligned on later resizes itself.\n *\n * The reactive props object is provided as `defaults` on the group context, so\n * descendant highlighters can inherit annotation props they don't set themselves.\n *\n * @param props The reactive props of the group component.\n *\n * @author Bas Milius <bas@mili.us>\n * @since 1.0.0\n */\nexport default function useHighlighterGroup(props: FluxVisualHighlighterGroupProps): void {\n const whenInView = props.whenInView ?? false;\n const entries = new Set<HighlighterGroupEntry>();\n\n let group: ReturnType<typeof annotationGroup> | null = null;\n let timer: number | undefined;\n let settleObserver: ResizeObserver | null = null;\n let inViewObserver: IntersectionObserver | null = null;\n let inView = !whenInView;\n\n function orderedAnnotations(): Annotation[] {\n return [...entries]\n .sort((a, b) => a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1)\n .map(entry => entry.getAnnotation())\n .filter((annotation): annotation is Annotation => annotation !== null);\n }\n\n function draw(): void {\n if (!inView) {\n return;\n }\n\n const annotations = orderedAnnotations();\n\n if (annotations.length === 0) {\n return;\n }\n\n // annotationGroup writes cumulative animation delays onto the annotations,\n // so a fresh group is rebuilt whenever the members change.\n group?.hide();\n group = annotationGroup(annotations);\n group.show();\n\n stopSettleWatch();\n }\n\n function schedule(): void {\n clearTimeout(timer);\n timer = setTimeout(() => draw(), 80);\n }\n\n function stopSettleWatch(): void {\n settleObserver?.disconnect();\n settleObserver = null;\n }\n\n // Draw the first cascade only once the surrounding layout has stopped moving,\n // so the animation plays over the text instead of where it sat pre-layout.\n function watchSettle(): void {\n if (settleObserver || typeof ResizeObserver === 'undefined') {\n return;\n }\n\n settleObserver = new ResizeObserver(() => schedule());\n settleObserver.observe(document.body);\n }\n\n function watchInView(element: HTMLElement): void {\n if (!whenInView || inViewObserver || typeof IntersectionObserver === 'undefined') {\n return;\n }\n\n inViewObserver = new IntersectionObserver(observed => {\n if (observed.some(entry => entry.isIntersecting)) {\n inView = true;\n inViewObserver?.disconnect();\n inViewObserver = null;\n schedule();\n }\n });\n\n inViewObserver.observe(element);\n }\n\n provide(FluxVisualHighlighterGroupInjectionKey, {\n defaults: props,\n add(entry) {\n entries.add(entry);\n watchInView(entry.element);\n watchSettle();\n schedule();\n },\n remove(entry) {\n entries.delete(entry);\n schedule();\n },\n notify() {\n schedule();\n }\n });\n\n onScopeDispose(() => {\n clearTimeout(timer);\n stopSettleWatch();\n inViewObserver?.disconnect();\n inViewObserver = null;\n group?.hide();\n group = null;\n });\n}\n","@use 'sass:list';\n@use 'sass:map';\n@use 'sass:math';\n@use 'sass:string';\n\n@property --beam-angle {\n syntax: '<angle>';\n initial-value: 0deg;\n inherits: true;\n}\n\n@property --beam-hue {\n syntax: '<angle>';\n initial-value: 0deg;\n inherits: true;\n}\n\n@property --beam-opacity {\n syntax: '<number>';\n initial-value: 0;\n inherits: true;\n}\n\n@property --beam-x {\n syntax: '<number>';\n initial-value: 0;\n inherits: true;\n}\n\n@property --beam-w {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n}\n\n@property --beam-h {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n}\n\n@property --beam-edge {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n}\n\n@property --beam-spike {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n}\n\n@property --beam-spike2 {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n}\n\n@each $name in (bw1, bh1, bw2, bh2, bw3, bh3, bgh, bop-tl, bop-tr, bop-bl, bop-br) {\n @property --beam-#{$name} {\n syntax: '<number>';\n initial-value: 1;\n inherits: true;\n }\n}\n\n@each $name in (bx1, by1, bx2, by2, bx3, by3) {\n @property --beam-#{$name} {\n syntax: '<length>';\n initial-value: 0px;\n inherits: true;\n }\n}\n\n/*\n * Palette data, ported from github.com/Jakubantalik/border-beam. Geometry is\n * shared between palettes; only the colors differ. The ring geometry doubles as\n * the pulse perimeter, where each blob is assigned to a breathing size region\n * (1-3) and an opacity quadrant (tl/tr/bl/br).\n */\n\n$ring-geometry: (\n (x: 33%, y: -7.4%, w: 70, h: 40, region: 1, quad: tl),\n (x: 12%, y: -5%, w: 60, h: 35, region: 2, quad: tl),\n (x: 2.1%, y: 68.3%, w: 40, h: 70, region: 3, quad: bl),\n (x: 2.1%, y: 68.3%, w: 20, h: 35, region: 1, quad: bl),\n (x: 74.4%, y: 100%, w: 180, h: 32, region: 2, quad: br),\n (x: 55%, y: 100%, w: 85, h: 26, region: 3, quad: br),\n (x: 93.9%, y: 0%, w: 74, h: 32, region: 1, quad: tr),\n (x: 100%, y: 27.1%, w: 26, h: 42, region: 2, quad: tr),\n (x: 100%, y: 27.1%, w: 52, h: 48, region: 3, quad: tr)\n);\n\n$ring-colors: (\n colorful: ((255, 50, 100), (40, 140, 255), (50, 200, 80), (30, 185, 170), (100, 70, 255), (40, 140, 255), (255, 120, 40), (240, 50, 180), (180, 40, 240)),\n mono: ((180, 180, 180), (140, 140, 140), (160, 160, 160), (130, 130, 130), (170, 170, 170), (150, 150, 150), (190, 190, 190), (145, 145, 145), (165, 165, 165)),\n ocean: ((100, 80, 220), (60, 120, 255), (80, 100, 200), (50, 140, 220), (120, 80, 255), (70, 130, 255), (140, 100, 240), (90, 110, 230), (130, 70, 255)),\n sunset: ((255, 80, 50), (255, 160, 40), (255, 120, 60), (255, 200, 50), (255, 100, 80), (255, 180, 60), (255, 60, 60), (255, 140, 50), (255, 90, 70))\n);\n\n$sm-geometry: (\n (x: 2%, y: 68%, w: 9, h: 18),\n (x: 2%, y: 68%, w: 4, h: 8),\n (x: 72%, y: -3%, w: 59, h: 9),\n (x: 74%, y: 100%, w: 42, h: 7),\n (x: 100%, y: 27%, w: 10, h: 17),\n (x: 100%, y: 27%, w: 10, h: 18),\n (x: 100%, y: 27%, w: 5, h: 10),\n (x: 100%, y: 27%, w: 11, h: 12)\n);\n\n$sm-colors: (\n colorful: ((50, 200, 80), (30, 185, 170), (255, 120, 40), (100, 70, 255), (240, 50, 180), (180, 40, 240), (40, 140, 255), (255, 50, 100)),\n mono: ((160, 160, 160), (140, 140, 140), (180, 180, 180), (150, 150, 150), (170, 170, 170), (155, 155, 155), (145, 145, 145), (165, 165, 165)),\n ocean: ((60, 140, 200), (50, 120, 180), (100, 80, 220), (80, 100, 255), (120, 70, 240), (90, 80, 220), (70, 110, 255), (110, 90, 230)),\n sunset: ((255, 180, 50), (255, 150, 40), (255, 80, 60), (255, 100, 80), (255, 60, 80), (255, 120, 60), (255, 200, 50), (255, 90, 70))\n);\n\n$sm-inner-alphas: (\n colorful: (.5, .45, .35, .35, .3, .4, .3, .3),\n mono: (.25, .22, .17, .17, .15, .2, .15, .15),\n ocean: (.5, .45, .35, .35, .3, .4, .3, .3),\n sunset: (.5, .45, .35, .35, .3, .4, .3, .3)\n);\n\n$line-geometry-dark: (\n (w: 36, h: 36, ox: 0, oy: 2),\n (w: 30, h: 32, ox: 39, oy: 0),\n (w: 33, h: 28, ox: -36, oy: 2),\n (w: 29, h: 34, ox: -54, oy: 0),\n (w: 27, h: 30, ox: 51, oy: -1),\n (w: 36, h: 24, ox: 21, oy: 1),\n (w: 30, h: 22, ox: -21, oy: 0),\n (w: 25, h: 28, ox: 66, oy: 1),\n (w: 23, h: 30, ox: -66, oy: -1)\n);\n\n$line-geometry-light: (\n (w: 45, h: 36, ox: 0, oy: 2),\n (w: 35, h: 32, ox: 65, oy: 0),\n (w: 40, h: 28, ox: -60, oy: 2),\n (w: 35, h: 34, ox: -90, oy: 0),\n (w: 38, h: 30, ox: 85, oy: -1),\n (w: 50, h: 24, ox: 35, oy: 1),\n (w: 40, h: 22, ox: -35, oy: 0),\n (w: 35, h: 28, ox: 110, oy: 1),\n (w: 30, h: 30, ox: -110, oy: -1)\n);\n\n$line-colors: (\n colorful: (\n dark: ((255, 50, 100), (40, 180, 220), (50, 200, 80), (180, 40, 240), (255, 160, 30), (100, 70, 255), (40, 140, 255), (240, 50, 180), (30, 185, 170)),\n light: ((255, 50, 100), (40, 140, 255), (50, 200, 80), (180, 40, 240), (30, 185, 170), (100, 70, 255), (40, 140, 255), (255, 120, 40), (240, 50, 180))\n ),\n mono: (\n dark: ((200, 200, 200), (170, 170, 170), (155, 155, 155), (185, 185, 185), (165, 165, 165), (180, 180, 180), (160, 160, 160), (175, 175, 175), (190, 190, 190)),\n light: ((100, 100, 100), (80, 80, 80), (90, 90, 90), (70, 70, 70), (85, 85, 85), (95, 95, 95), (75, 75, 75), (105, 105, 105), (65, 65, 65))\n ),\n ocean: (\n dark: ((100, 80, 220), (60, 120, 255), (80, 100, 200), (130, 70, 255), (70, 130, 255), (120, 80, 255), (90, 110, 230), (110, 90, 240), (140, 100, 255)),\n light: ((80, 60, 200), (50, 100, 220), (70, 90, 190), (110, 60, 220), (60, 110, 230), (100, 70, 240), (80, 100, 210), (90, 80, 225), (120, 90, 245))\n ),\n sunset: (\n dark: ((255, 100, 60), (255, 180, 50), (255, 140, 70), (255, 80, 80), (255, 200, 60), (255, 120, 50), (255, 160, 80), (255, 90, 60), (255, 70, 70)),\n light: ((220, 80, 40), (230, 150, 30), (210, 110, 50), (200, 60, 60), (220, 170, 40), (210, 100, 30), (230, 130, 60), (190, 70, 50), (180, 50, 50))\n )\n);\n\n$line-inner-geometry: (\n (w: 33, h: 30, ox: 0, oy: 0),\n (w: 24, h: 26, ox: 39, oy: -3),\n (w: 27, h: 24, ox: -36, oy: 0),\n (w: 23, h: 28, ox: -54, oy: -2),\n (w: 24, h: 24, ox: 51, oy: -1),\n (w: 30, h: 20, ox: 21, oy: 0),\n (w: 25, h: 18, ox: -21, oy: -2),\n (w: 21, h: 24, ox: 66, oy: 0),\n (w: 18, h: 26, ox: -66, oy: -1)\n);\n\n$line-inner-colors: (\n colorful: ((255, 50, 100), (40, 180, 220), (50, 200, 80), (180, 40, 240), (255, 160, 30), (100, 70, 255), (40, 140, 255), (240, 50, 180), (30, 185, 170)),\n mono: ((200, 200, 200), (170, 170, 170), (155, 155, 155), (185, 185, 185), (165, 165, 165), (180, 180, 180), (160, 160, 160), (175, 175, 175), (190, 190, 190)),\n ocean: ((100, 80, 220), (60, 120, 255), (80, 100, 200), (130, 70, 255), (70, 130, 255), (120, 80, 255), (90, 110, 230), (110, 90, 240), (140, 100, 255)),\n sunset: ((255, 100, 60), (255, 180, 50), (255, 140, 70), (255, 80, 80), (255, 200, 60), (255, 120, 50), (255, 160, 80), (255, 90, 60), (255, 70, 70))\n);\n\n$line-inner-alphas: (.48, .42, .48, .42, .5, .45, .4, .45, .52);\n\n$line-spikes: (\n colorful: (\n dark: (primary: ((255, 60, 80), 1), secondary: ((40, 190, 180), .98)),\n light: (primary: ((200, 30, 60), 1), secondary: ((20, 150, 140), 1))\n ),\n mono: (\n dark: (primary: ((200, 200, 200), 1), secondary: ((170, 170, 170), 1)),\n light: (primary: ((80, 80, 80), 1), secondary: ((120, 120, 120), 1))\n ),\n ocean: (\n dark: (primary: ((100, 120, 255), 1), secondary: ((130, 100, 220), .98)),\n light: (primary: ((60, 60, 180), 1), secondary: ((80, 100, 200), 1))\n ),\n sunset: (\n dark: (primary: ((255, 140, 80), 1), secondary: ((255, 100, 60), .98)),\n light: (primary: ((200, 80, 40), 1), secondary: ((220, 120, 30), 1))\n )\n);\n\n$line-bloom-spikes: (\n colorful: (\n dark: ((((100, 70, 255), 1), ((100, 70, 255), 1)), (((255, 170, 40), .59), ((255, 170, 40), .29)), (((50, 200, 100), 1), ((50, 200, 100), 1)), (((200, 50, 240), .91), ((200, 50, 240), .45)), (((40, 140, 255), 1), ((40, 140, 255), 1))),\n light: ((((80, 50, 200), 1), ((80, 50, 200), .8)), (((210, 130, 0), .7), ((210, 130, 0), .46)), (((30, 160, 70), 1), ((30, 160, 70), .82)), (((160, 30, 190), 1), ((160, 30, 190), .7)), (((30, 100, 200), 1), ((30, 100, 200), .78)))\n ),\n mono: (\n dark: ((((200, 200, 200), 1), ((200, 200, 200), 1)), (((180, 180, 180), .59), ((180, 180, 180), .29)), (((190, 190, 190), 1), ((190, 190, 190), 1)), (((170, 170, 170), .91), ((170, 170, 170), .45)), (((185, 185, 185), 1), ((185, 185, 185), 1))),\n light: ((((80, 80, 80), 1), ((80, 80, 80), .8)), (((100, 100, 100), .7), ((100, 100, 100), .46)), (((70, 70, 70), 1), ((70, 70, 70), .82)), (((90, 90, 90), 1), ((90, 90, 90), .7)), (((85, 85, 85), 1), ((85, 85, 85), .78)))\n ),\n ocean: (\n dark: ((((100, 80, 255), 1), ((100, 80, 255), 1)), (((80, 130, 220), .59), ((80, 130, 220), .29)), (((60, 100, 255), 1), ((60, 100, 255), 1)), (((90, 120, 200), .91), ((90, 120, 200), .45)), (((120, 90, 255), 1), ((120, 90, 255), 1))),\n light: ((((50, 40, 180), 1), ((50, 40, 180), .8)), (((40, 80, 200), .7), ((40, 80, 200), .46)), (((30, 50, 190), 1), ((30, 50, 190), .82)), (((60, 90, 180), 1), ((60, 90, 180), .7)), (((70, 60, 200), 1), ((70, 60, 200), .78)))\n ),\n sunset: (\n dark: ((((255, 100, 80), 1), ((255, 100, 80), 1)), (((255, 150, 80), .59), ((255, 150, 80), .29)), (((255, 80, 60), 1), ((255, 80, 60), 1)), (((255, 120, 50), .91), ((255, 120, 50), .45)), (((255, 140, 70), 1), ((255, 140, 70), 1))),\n light: ((((200, 60, 30), 1), ((200, 60, 30), .8)), (((220, 100, 20), .7), ((220, 100, 20), .46)), (((180, 40, 20), 1), ((180, 40, 20), .82)), (((210, 80, 10), 1), ((210, 80, 10), .7)), (((190, 70, 30), 1), ((190, 70, 30), .78)))\n )\n);\n\n$pulse-inner-sizes: ((65, 35), (55, 30), (35, 65), (15, 30), (173, 28), (80, 22), (69, 28), (22, 38), (47, 44));\n\n$pulse-inner-bloom: (\n (ci: 1, region: 1, quad: tl, w: 84, h: 48),\n (ci: 2, region: 2, quad: tl, w: 72, h: 42),\n (ci: 3, region: 3, quad: bl, w: 48, h: 84),\n (ci: 5, region: 2, quad: br, w: 216, h: 38),\n (ci: 6, region: 3, quad: br, w: 102, h: 31),\n (ci: 7, region: 1, quad: tr, w: 89, h: 38),\n (ci: 9, region: 3, quad: tr, w: 62, h: 58)\n);\n\n$pulse-outer-core: (\n (ci: 1, region: 1, quad: tl, w: 80, h: 19, x: 27%, y: 0%),\n (ci: 7, region: 2, quad: tr, w: 74, h: 11, x: 73%, y: -1%),\n (ci: 8, region: 3, quad: tr, w: 15, h: 44, x: 100%, y: 33%),\n (ci: 9, region: 1, quad: br, w: 19, h: 38, x: 101%, y: 72%),\n (ci: 5, region: 2, quad: br, w: 84, h: 13, x: 67%, y: 100%),\n (ci: 2, region: 3, quad: bl, w: 60, h: 21, x: 24%, y: 101%),\n (ci: 3, region: 1, quad: bl, w: 17, h: 40, x: 0%, y: 60%),\n (ci: 4, region: 2, quad: tl, w: 13, h: 32, x: -1%, y: 28%)\n);\n\n$pulse-outer-bloom: (\n (ci: 1, region: 1, quad: tl, w: 110, h: 30, x: 27%, y: 3%),\n (ci: 7, region: 2, quad: tr, w: 100, h: 20, x: 73%, y: 1%),\n (ci: 8, region: 3, quad: tr, w: 26, h: 62, x: 100%, y: 33%),\n (ci: 9, region: 1, quad: br, w: 30, h: 56, x: 101%, y: 72%),\n (ci: 5, region: 2, quad: br, w: 120, h: 22, x: 67%, y: 99%),\n (ci: 2, region: 3, quad: bl, w: 88, h: 32, x: 24%, y: 99%),\n (ci: 3, region: 1, quad: bl, w: 28, h: 58, x: 0%, y: 60%)\n);\n\n@function rgb-str($c, $alpha: null) {\n @if $alpha == null or $alpha == 1 {\n @return string.unquote('rgb(#{list.nth($c, 1)}, #{list.nth($c, 2)}, #{list.nth($c, 3)})');\n }\n @return string.unquote('rgba(#{list.nth($c, 1)}, #{list.nth($c, 2)}, #{list.nth($c, 3)}, #{$alpha})');\n}\n\n@function spike-color($pair, $factor: 1) {\n $alpha: math.div(math.round(list.nth($pair, 2) * $factor * 100), 100);\n @return rgb-str(list.nth($pair, 1), $alpha);\n}\n\n@function offset-str($value) {\n @if $value < 0 {\n @return ' - #{math.abs($value)}px';\n }\n @return ' + #{$value}px';\n}\n\n// Static blobs around the perimeter (md & sm stroke/inner layers).\n@function blobs($colors, $geometry, $alphas: null, $scale: 1) {\n $grads: ();\n @for $i from 1 through list.length($geometry) {\n $g: list.nth($geometry, $i);\n $alpha: null;\n @if $alphas != null {\n @if list.length($alphas) > 1 {\n $alpha: list.nth($alphas, $i);\n } @else {\n $alpha: $alphas;\n }\n }\n $w: math.round(map.get($g, w) * $scale);\n $h: math.round(map.get($g, h) * $scale);\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse #{$w}px #{$h}px at #{map.get($g, x)} #{map.get($g, y)}, #{rgb-str(list.nth($colors, $i), $alpha)}, transparent)'), comma);\n }\n @return $grads;\n}\n\n// Traveling blobs along the bottom edge (line stroke/inner layers).\n@function line-blobs($colors, $geometry, $alphas: null) {\n $grads: ();\n @for $i from 1 through list.length($geometry) {\n $g: list.nth($geometry, $i);\n $alpha: null;\n @if $alphas != null {\n $alpha: list.nth($alphas, $i);\n }\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse calc(#{map.get($g, w)}px * var(--beam-w)) calc(#{map.get($g, h)}px * var(--beam-h)) at calc(var(--beam-x) * 100%#{offset-str(map.get($g, ox))}) calc(100%#{offset-str(map.get($g, oy))}), #{rgb-str(list.nth($colors, $i), $alpha)}, transparent)'), comma);\n }\n @return $grads;\n}\n\n// Breathing blobs whose size, drift and opacity are driven from JS (pulse layers).\n@function pulse-blobs($entries) {\n $grads: ();\n @each $e in $entries {\n $r: map.get($e, region);\n $size: 'calc(#{map.get($e, w)}px * var(--beam-bw#{$r}) * var(--beam-glow-sx, 1)) calc(#{map.get($e, h)}px * var(--beam-bh#{$r}) * var(--beam-bgh) * var(--beam-glow-sy, 1))';\n $at: 'calc(#{map.get($e, x)} + var(--beam-bx#{$r})) calc(#{map.get($e, y)} + var(--beam-by#{$r}))';\n $color: 'rgba(#{list.nth(map.get($e, c), 1)}, #{list.nth(map.get($e, c), 2)}, #{list.nth(map.get($e, c), 3)}, var(--beam-bop-#{map.get($e, quad)}))';\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse #{$size} at #{$at}, #{$color}, transparent)'), comma);\n }\n @return $grads;\n}\n\n// Frozen variant of the pulse blobs: literal sizes/positions with the time-average\n// alpha, so the heavily blurred bloom bitmap is painted once and cached instead of\n// being re-rasterized every frame.\n@function pulse-blobs-frozen($entries) {\n $grads: ();\n @each $e in $entries {\n $size: 'calc(#{map.get($e, w)}px * var(--beam-glow-sx, 1)) calc(#{map.get($e, h)}px * var(--beam-glow-sy, 1))';\n $color: 'rgba(#{list.nth(map.get($e, c), 1)}, #{list.nth(map.get($e, c), 2)}, #{list.nth(map.get($e, c), 3)}, var(--beam-pulse-frozen))';\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse #{$size} at #{map.get($e, x)} #{map.get($e, y)}, #{$color}, transparent)'), comma);\n }\n @return $grads;\n}\n\n// The 9-blob pulse perimeter, with palette geometry and colors.\n@function pulse-ring($palette) {\n $entries: ();\n @for $i from 1 through list.length($ring-geometry) {\n $entries: list.append($entries, map.merge(list.nth($ring-geometry, $i), (c: list.nth(map.get($ring-colors, $palette), $i))), comma);\n }\n @return $entries;\n}\n\n// Same perimeter with the smaller inner sizes.\n@function pulse-ring-resized($palette) {\n $ring: pulse-ring($palette);\n $entries: ();\n @for $i from 1 through list.length($ring) {\n $size: list.nth($pulse-inner-sizes, $i);\n $entries: list.append($entries, map.merge(list.nth($ring, $i), (w: list.nth($size, 1), h: list.nth($size, 2))), comma);\n }\n @return $entries;\n}\n\n// Resolve a gradient table (ci references the palette ring) to emit-ready entries.\n@function pulse-table($palette, $table) {\n $ring: pulse-ring($palette);\n $entries: ();\n @each $t in $table {\n $src: list.nth($ring, map.get($t, ci));\n $x: map.get($src, x);\n $y: map.get($src, y);\n @if map.has-key($t, x) {\n $x: map.get($t, x);\n }\n @if map.has-key($t, y) {\n $y: map.get($t, y);\n }\n $entry: (\n c: map.get($src, c),\n x: $x,\n y: $y,\n w: map.get($t, w),\n h: map.get($t, h),\n region: map.get($t, region),\n quad: map.get($t, quad)\n );\n $entries: list.append($entries, $entry, comma);\n }\n @return $entries;\n}\n\n// The desynced spike/glow stack at the bottom edge (line bloom layer).\n@function line-bloom($palette, $theme) {\n $is-dark: $theme == dark;\n $is-mono: $palette == mono;\n $spikes-def: map.get($line-spikes, $palette, $theme);\n $primary: map.get($spikes-def, primary);\n $secondary: map.get($spikes-def, secondary);\n $pairs: map.get($line-bloom-spikes, $palette, $theme);\n\n // Mono uses uniform gray, so thin spikes at full opacity look like harsh\n // bars. Attenuate the opacity and widen the thin gradients so they appear\n // as soft glows instead.\n $sc1: spike-color($primary);\n $sc2: spike-color($secondary);\n $sc1-mid: $sc1;\n $sc2-mid: rgb-str(list.nth($secondary, 1), .49);\n $thin-w1: .8px;\n $thin-w2: 2px;\n $thin-w3: 1.2px;\n $thin-w4: .6px;\n $thin-h1: 92px;\n $thin-h2: 72px;\n $thin-h3: 85px;\n $thin-h4: 60px;\n $att: 1;\n\n @if not $is-dark {\n $sc1-mid: rgb-str(list.nth($primary, 1), .85);\n $sc2-mid: rgb-str(list.nth($secondary, 1), .7);\n $thin-w4: 1px;\n }\n\n @if $is-mono {\n $sc1: spike-color($primary, .14);\n $sc2: spike-color($secondary, .12);\n $thin-w1: 12px;\n $thin-w2: 14px;\n $thin-w3: 12px;\n $thin-w4: 10px;\n\n // The light theme widens the last thin spike further for mono.\n @if not $is-dark {\n $thin-w4: 12px;\n }\n $thin-h1: 42px;\n $thin-h2: 38px;\n $thin-h3: 40px;\n $thin-h4: 32px;\n $att: .14;\n\n @if $is-dark {\n $sc1-mid: spike-color($primary, .09);\n $sc2-mid: rgb-str(list.nth($secondary, 1), .06);\n } @else {\n $sc1-mid: spike-color($primary, .11);\n $sc2-mid: spike-color($secondary, .09);\n }\n }\n\n $att2: $att;\n @if $is-mono {\n $att2: $att * .7;\n }\n\n $colors1: ();\n $colors2: ();\n @each $pair in $pairs {\n $colors1: list.append($colors1, spike-color(list.nth($pair, 1), $att), comma);\n $colors2: list.append($colors2, spike-color(list.nth($pair, 2), $att2), comma);\n }\n\n $grads: (\n string.unquote('radial-gradient(ellipse calc(#{$thin-w1} * var(--beam-spike)) calc(#{$thin-h1} * var(--beam-h)) at 8% calc(100% - 2px), #{$sc1}, #{$sc1-mid} 30%, transparent 88%)'),\n string.unquote('radial-gradient(ellipse calc(10px * var(--beam-spike2)) calc(35px * var(--beam-h)) at 22% calc(100% - 4px), #{$sc2}, #{$sc2-mid} 50%, transparent 95%)'),\n string.unquote('radial-gradient(ellipse calc(#{$thin-w2} * (2 - var(--beam-spike))) calc(#{$thin-h2} * var(--beam-h)) at 36% calc(100% - 3px), #{list.nth($colors1, 1)}, #{list.nth($colors2, 1)} 40%, transparent 90%)'),\n string.unquote('radial-gradient(ellipse calc(14px * var(--beam-spike2)) calc(28px * var(--beam-h)) at 50% calc(100% - 2px), #{list.nth($colors1, 2)}, #{list.nth($colors2, 2)} 55%, transparent 96%)'),\n string.unquote('radial-gradient(ellipse calc(#{$thin-w3} * (2 - var(--beam-spike2))) calc(#{$thin-h3} * var(--beam-h)) at 64% calc(100% - 4px), #{list.nth($colors1, 3)}, #{list.nth($colors2, 3)} 35%, transparent 89%)'),\n string.unquote('radial-gradient(ellipse calc(7px * var(--beam-spike)) calc(45px * var(--beam-h)) at 78% calc(100% - 2px), #{list.nth($colors1, 4)}, #{list.nth($colors2, 4)} 48%, transparent 94%)'),\n string.unquote('radial-gradient(ellipse calc(#{$thin-w4} * (2 - var(--beam-spike))) calc(#{$thin-h4} * var(--beam-h)) at 92% calc(100% - 3px), #{list.nth($colors1, 5)}, #{list.nth($colors2, 5)} 42%, transparent 91%)')\n );\n\n @if $is-dark {\n $dot-c: 'rgba(255, 255, 255, 1)';\n $dot-20: 'rgba(255, 255, 255, 0.9)';\n $dot-50: 'rgba(255, 255, 255, 0.5)';\n $amb-c: 'rgba(255, 255, 255, 0.3)';\n $amb-25: 'rgba(255, 255, 255, 0.12)';\n $amb-55: 'rgba(255, 255, 255, 0.03)';\n\n @if $is-mono {\n $dot-c: 'rgba(255, 255, 255, 0.5)';\n $dot-20: 'rgba(255, 255, 255, 0.45)';\n $dot-50: 'rgba(255, 255, 255, 0.25)';\n $amb-c: 'rgba(255, 255, 255, 0.15)';\n $amb-25: 'rgba(255, 255, 255, 0.06)';\n $amb-55: 'rgba(255, 255, 255, 0.015)';\n }\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse calc(21px * var(--beam-spike)) calc(15px * var(--beam-spike2)) at calc(var(--beam-x) * 100%) calc(100% + 1px), #{$dot-c} 0%, #{$dot-20} 20%, #{$dot-50} 50%, transparent 100%)'), comma);\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse calc(42px * var(--beam-w)) calc(40px * var(--beam-h)) at calc(var(--beam-x) * 100%) 100%, #{$amb-c} 0%, #{$amb-25} 25%, #{$amb-55} 55%, transparent 80%)'), comma);\n } @else {\n $grads: list.append($grads, string.unquote('radial-gradient(ellipse calc(50px * var(--beam-w)) calc(32px * var(--beam-h)) at calc(var(--beam-x) * 100%) 100%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.18) 30%, rgba(0, 0, 0, 0.03) 60%, transparent 85%)'), comma);\n }\n\n @return $grads;\n}\n\n// Gradients that reference animated custom properties (--beam-angle, --beam-x,\n// --beam-w, ...) must be declared directly on the layer that paints them.\n// Routing them through an intermediate custom property breaks per-frame\n// updates of the dependent value in Chromium and WebKit, which makes the\n// animation stutter.\n$rotate-highlight-light: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 54%, rgba(0, 0, 0, 0.08) 57%, rgba(0, 0, 0, 0.2) 60%, rgba(0, 0, 0, 0.4) 63%, rgba(0, 0, 0, 0.55) 66%, rgba(0, 0, 0, 0.4) 69%, rgba(0, 0, 0, 0.2) 72%, rgba(0, 0, 0, 0.08) 75%, transparent 78%, transparent 100%)');\n$rotate-highlight-dark: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 54%, rgba(255, 255, 255, 0.1) 57%, rgba(255, 255, 255, 0.3) 60%, rgba(255, 255, 255, 0.6) 63%, rgba(255, 255, 255, 0.75) 66%, rgba(255, 255, 255, 0.6) 69%, rgba(255, 255, 255, 0.3) 72%, rgba(255, 255, 255, 0.1) 75%, transparent 78%, transparent 100%)');\n$rotate-bloom-light: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 58%, rgba(0, 0, 0, 0.02) 62%, rgba(0, 0, 0, 0.08) 65%, rgba(0, 0, 0, 0.2) 67%, rgba(0, 0, 0, 0.4) 69%, rgba(0, 0, 0, 0.6) 70%, rgba(0, 0, 0, 0.6) 70.5%, rgba(0, 0, 0, 0.4) 71.5%, rgba(0, 0, 0, 0.2) 73%, rgba(0, 0, 0, 0.08) 75%, rgba(0, 0, 0, 0.02) 78%, transparent 82%)');\n$rotate-bloom-dark: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 58%, rgba(255, 255, 255, 0.03) 62%, rgba(255, 255, 255, 0.08) 65%, rgba(255, 255, 255, 0.2) 67%, rgba(255, 255, 255, 0.45) 69%, rgba(255, 255, 255, 0.85) 70%, rgba(255, 255, 255, 0.85) 70.5%, rgba(255, 255, 255, 0.45) 71.5%, rgba(255, 255, 255, 0.2) 73%, rgba(255, 255, 255, 0.08) 75%, rgba(255, 255, 255, 0.03) 78%, transparent 82%)');\n$line-highlight-light: string.unquote('radial-gradient(ellipse calc(35px * var(--beam-w)) calc(28px * var(--beam-h)) at calc(var(--beam-x) * 100%) calc(100% + 2px), rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.25) 35%, transparent 70%)');\n$line-highlight-dark: string.unquote('radial-gradient(ellipse calc(24px * var(--beam-w)) calc(28px * var(--beam-h)) at calc(var(--beam-x) * 100%) calc(100% + 2px), rgba(255, 255, 255, 0.38) 0%, rgba(255, 255, 255, 0.12) 30%, transparent 65%)');\n$ring-mask: string.unquote('linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)');\n$edge-fade-y: string.unquote('linear-gradient(white, transparent 28px, transparent calc(100% - 28px), white)');\n$edge-fade-x: string.unquote('linear-gradient(to right, white, transparent 28px, transparent calc(100% - 28px), white)');\n$md-window: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 30%, rgba(255, 255, 255, 0.1) 36%, rgba(255, 255, 255, 0.35) 44%, white 52%, white 80%, rgba(255, 255, 255, 0.35) 86%, rgba(255, 255, 255, 0.1) 92%, transparent 95%, transparent 100%)');\n$sm-window: string.unquote('conic-gradient(from var(--beam-angle), transparent 0%, transparent 22%, rgba(255, 255, 255, 0.12) 28%, rgba(255, 255, 255, 0.4) 36%, white 46%, white 82%, rgba(255, 255, 255, 0.4) 88%, rgba(255, 255, 255, 0.12) 94%, transparent 97%, transparent 100%)');\n$line-window: string.unquote('radial-gradient(ellipse calc(78px * var(--beam-w)) calc(60px * var(--beam-h)) at calc(var(--beam-x) * 100%) 100%, white 0%, rgba(255, 255, 255, 0.5) 45%, transparent 100%)');\n$line-bloom-window: string.unquote('radial-gradient(ellipse calc(84px * var(--beam-w)) calc(110px * var(--beam-h)) at calc(var(--beam-x) * 100%) 100%, white 0%, rgba(255, 255, 255, 0.5) 35%, transparent 100%)');\n\n.borderBeam {\n position: relative;\n width: fit-content;\n border-radius: var(--beam-radius);\n\n --beam-hue-range: 30;\n --beam-mono: 1;\n --beam-radius: var(--radius);\n --beam-strength: 1;\n\n &::before,\n &::after {\n pointer-events: none;\n }\n}\n\n.bloom {\n position: absolute;\n display: none;\n pointer-events: none;\n}\n\n.mono {\n --beam-mono: .5;\n}\n\n@each $palette in (colorful, mono, ocean, sunset) {\n $inner-alpha: .45;\n @if $palette == mono {\n $inner-alpha: .225;\n }\n\n .#{$palette} {\n --beam-rotate-blobs: #{blobs(map.get($ring-colors, $palette), $ring-geometry)};\n --beam-rotate-inner: #{blobs(map.get($ring-colors, $palette), $ring-geometry, $inner-alpha, .9)};\n --beam-sm-blobs: #{blobs(map.get($sm-colors, $palette), $sm-geometry)};\n --beam-sm-inner: #{blobs(map.get($sm-colors, $palette), $sm-geometry, map.get($sm-inner-alphas, $palette))};\n --beam-pulse-ring: #{pulse-blobs(pulse-ring($palette))};\n --beam-pulse-inner-ring: #{pulse-blobs(pulse-ring-resized($palette))};\n --beam-pulse-inner-bloom: #{pulse-blobs-frozen(pulse-table($palette, $pulse-inner-bloom))};\n --beam-pulse-outer-core: #{pulse-blobs(pulse-table($palette, $pulse-outer-core))};\n --beam-pulse-outer-bloom: #{pulse-blobs-frozen(pulse-table($palette, $pulse-outer-bloom))};\n\n &.line::after {\n background: #{$line-highlight-light}, #{line-blobs(map.get($line-colors, $palette, light), $line-geometry-light)};\n }\n\n &.line::before {\n background: #{line-blobs(map.get($line-inner-colors, $palette), $line-inner-geometry, $line-inner-alphas)};\n }\n\n &.line > .bloom {\n background: #{line-bloom($palette, light)};\n }\n\n [dark] &.line::after {\n background: #{$line-highlight-dark}, #{line-blobs(map.get($line-colors, $palette, dark), $line-geometry-dark)};\n }\n\n [dark] &.line > .bloom {\n background: #{line-bloom($palette, dark)};\n }\n }\n}\n\n/*\n * Rotate family — a soft conic window travels around the perimeter and reveals\n * the colorful blobs, with a hot highlight inside the window.\n */\n\n.md,\n.sm {\n overflow: hidden;\n\n &.isActive {\n animation: borderBeamSpin calc(var(--beam-duration) * 1s) linear infinite, borderBeamFadeIn .6s ease forwards;\n }\n\n &.isFading {\n animation: borderBeamSpin calc(var(--beam-duration) * 1s) linear infinite, borderBeamFadeOut .5s ease forwards;\n }\n\n &.isActive::after,\n &.isFading::after {\n position: absolute;\n z-index: 2;\n inset: 0;\n padding: 1px;\n content: '';\n border-radius: calc(var(--beam-radius) - 1px);\n opacity: calc(var(--beam-opacity) * var(--beam-stroke-opacity) * var(--beam-mono) * var(--beam-strength));\n animation: borderBeamHueShift 12s ease-in-out infinite;\n clip-path: inset(0 round var(--beam-radius));\n }\n\n &.isActive::before,\n &.isFading::before {\n position: absolute;\n z-index: 1;\n inset: 0;\n content: '';\n opacity: calc(var(--beam-opacity) * var(--beam-inner-opacity) * var(--beam-mono) * var(--beam-strength));\n animation: borderBeamHueShift 12s ease-in-out infinite;\n clip-path: inset(0 round var(--beam-radius));\n }\n\n &.isStatic::before,\n &.isStatic::after {\n animation: none;\n }\n\n > .bloom {\n z-index: 3;\n inset: 0;\n padding: 1px;\n background: #{$rotate-bloom-light};\n border-radius: calc(var(--beam-radius) - 1px);\n opacity: 0;\n filter: blur(8px) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$ring-mask};\n mask-composite: exclude;\n }\n\n [dark] & > .bloom {\n background: #{$rotate-bloom-dark};\n }\n\n &.isActive > .bloom,\n &.isFading > .bloom {\n display: block;\n opacity: calc(var(--beam-opacity) * var(--beam-bloom-opacity) * var(--beam-mono) * var(--beam-strength));\n }\n}\n\n.md {\n --beam-bloom-opacity: .34;\n --beam-brightness: 1.3;\n --beam-inner-opacity: .26;\n --beam-inner-shadow: rgba(0, 0, 0, .14);\n --beam-saturation: 1.5;\n --beam-stroke-opacity: .12;\n\n [dark] & {\n --beam-bloom-opacity: .24;\n --beam-inner-opacity: .42;\n --beam-inner-shadow: rgba(255, 255, 255, .27);\n --beam-saturation: 1.2;\n --beam-stroke-opacity: .26;\n }\n\n &.isActive::after,\n &.isFading::after {\n background: #{$rotate-highlight-light}, var(--beam-rotate-blobs);\n mask: #{$md-window}, #{$ring-mask};\n mask-composite: intersect, exclude;\n }\n\n [dark] &.isActive::after,\n [dark] &.isFading::after {\n background: #{$rotate-highlight-dark}, var(--beam-rotate-blobs);\n }\n\n &.isActive::before,\n &.isFading::before {\n background: var(--beam-rotate-inner);\n border-radius: var(--beam-radius);\n box-shadow: inset 0 0 9px 1px var(--beam-inner-shadow);\n mask: #{$md-window}, #{$edge-fade-y}, #{$edge-fade-x};\n mask-composite: intersect, add;\n }\n}\n\n.sm {\n --beam-bloom-opacity: .16;\n --beam-brightness: 1.3;\n --beam-inner-opacity: .3;\n --beam-inner-shadow: rgba(0, 0, 0, .14);\n --beam-saturation: 1.8;\n --beam-stroke-opacity: .12;\n\n [dark] & {\n --beam-bloom-opacity: .38;\n --beam-inner-opacity: .24;\n --beam-inner-shadow: rgba(255, 255, 255, .3);\n --beam-saturation: 1.2;\n --beam-stroke-opacity: .46;\n }\n\n &.isActive::after,\n &.isFading::after {\n background: #{$rotate-highlight-light}, var(--beam-sm-blobs);\n mask: #{$sm-window}, #{$ring-mask};\n mask-composite: intersect, exclude;\n }\n\n [dark] &.isActive::after,\n [dark] &.isFading::after {\n background: #{$rotate-highlight-dark}, var(--beam-sm-blobs);\n }\n\n &.isActive::before,\n &.isFading::before {\n background: var(--beam-sm-inner);\n border-radius: var(--beam-radius);\n box-shadow: inset 0 0 5px 1px var(--beam-inner-shadow);\n mask: #{$sm-window};\n mask-composite: add;\n }\n}\n\n/*\n * Line — a glow that travels along the bottom edge, with breathing width/height\n * and desynced bloom spikes.\n */\n\n.line {\n overflow: hidden;\n\n --beam-bloom-opacity: .3;\n --beam-brightness: 1.3;\n --beam-inner-opacity: .32;\n --beam-inner-shadow: rgba(0, 0, 0, .14);\n --beam-saturation: 1.95;\n --beam-stroke-opacity: .16;\n\n [dark] & {\n --beam-bloom-opacity: .8;\n --beam-inner-opacity: .7;\n --beam-inner-shadow: rgba(255, 255, 255, .1);\n --beam-saturation: 1.2;\n --beam-stroke-opacity: 1.14;\n }\n\n &.isActive {\n animation: borderBeamTravel calc(var(--beam-duration) * 1s) linear infinite, borderBeamEdgeFade calc(var(--beam-duration) * 1s) linear infinite, borderBeamBreathe calc(var(--beam-duration) * 1.3s) ease-in-out infinite, borderBeamSpike calc(var(--beam-duration) * 1.33s) ease-in-out infinite, borderBeamSpike2 calc(var(--beam-duration) * 1.7s) ease-in-out infinite, borderBeamFadeIn .6s ease forwards;\n }\n\n &.isFading {\n animation: borderBeamTravel calc(var(--beam-duration) * 1s) linear infinite, borderBeamEdgeFade calc(var(--beam-duration) * 1s) linear infinite, borderBeamBreathe calc(var(--beam-duration) * 1.3s) ease-in-out infinite, borderBeamSpike calc(var(--beam-duration) * 1.33s) ease-in-out infinite, borderBeamSpike2 calc(var(--beam-duration) * 1.7s) ease-in-out infinite, borderBeamFadeOut .5s ease forwards;\n }\n\n &.isActive::after,\n &.isFading::after {\n position: absolute;\n z-index: 2;\n inset: 0;\n padding: 1px;\n content: '';\n border-radius: calc(var(--beam-radius) - 1px);\n opacity: calc(var(--beam-opacity) * var(--beam-edge) * var(--beam-stroke-opacity) * var(--beam-strength));\n animation: borderBeamHueShift 12s ease-in-out infinite;\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$line-window}, #{$ring-mask};\n mask-composite: intersect, exclude;\n }\n\n &.isActive::before,\n &.isFading::before {\n position: absolute;\n z-index: 1;\n inset: 0;\n content: '';\n border-radius: var(--beam-radius);\n box-shadow: inset 0 0 9px 1px var(--beam-inner-shadow);\n opacity: calc(var(--beam-opacity) * var(--beam-edge) * var(--beam-inner-opacity) * var(--beam-strength));\n animation: borderBeamHueShift 12s ease-in-out infinite;\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$line-window}, #{$edge-fade-y}, #{$edge-fade-x};\n mask-composite: intersect, add;\n }\n\n &.isStatic::before,\n &.isStatic::after {\n animation: none;\n }\n\n > .bloom {\n z-index: 3;\n inset: 0;\n border-radius: calc(var(--beam-radius) - 1px);\n opacity: 0;\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$line-bloom-window};\n mask-composite: add;\n }\n\n &.isActive > .bloom,\n &.isFading > .bloom {\n display: block;\n opacity: calc(var(--beam-opacity) * var(--beam-edge) * var(--beam-bloom-opacity) * var(--beam-strength));\n animation: borderBeamHueShiftBloom 8s ease-in-out infinite;\n }\n\n &.isStatic > .bloom {\n animation: none;\n }\n\n &.mono > .bloom {\n filter: blur(6px);\n }\n}\n\n/*\n * Pulse family — a breathing glow without rotation. The motion is driven from a\n * shared, frame-rate-capped JS loop that writes the --beam-b* custom properties.\n */\n\n.pulseInner,\n.pulseOutside {\n isolation: isolate;\n\n &.isActive {\n animation: borderBeamFadeIn .6s ease forwards;\n }\n\n &.isFading {\n animation: borderBeamFadeOut .5s ease forwards;\n }\n}\n\n.pulseInner {\n overflow: hidden;\n\n --beam-bloom-opacity: .8;\n --beam-brightness: 1.3;\n --beam-inner-opacity: .4;\n --beam-pulse-corner: 0, 0, 0;\n --beam-pulse-corner-alpha: .08;\n --beam-pulse-frozen: .775;\n --beam-saturation: .75;\n --beam-stroke-opacity: .32;\n\n [dark] & {\n --beam-bloom-opacity: .66;\n --beam-brightness: .75;\n --beam-inner-opacity: .44;\n --beam-pulse-corner: 255, 255, 255;\n --beam-pulse-corner-alpha: .18;\n --beam-pulse-frozen: .76;\n --beam-saturation: 1.2;\n --beam-stroke-opacity: 1.54;\n }\n\n &.isActive::after,\n &.isFading::after {\n position: absolute;\n z-index: 2;\n inset: 0;\n padding: 1px;\n content: '';\n background: var(--beam-pulse-ring);\n border-radius: var(--beam-radius);\n opacity: calc(var(--beam-opacity) * var(--beam-stroke-opacity) * var(--beam-mono) * var(--beam-strength));\n will-change: opacity, filter;\n filter: hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$ring-mask};\n mask-composite: exclude;\n }\n\n &.isActive::before,\n &.isFading::before {\n position: absolute;\n z-index: 1;\n inset: 0;\n content: '';\n background:\n var(--beam-pulse-inner-ring),\n radial-gradient(ellipse 60px 60px at 0% 0%, rgba(var(--beam-pulse-corner), calc(var(--beam-pulse-corner-alpha) * var(--beam-bop-tl))), transparent 70%),\n radial-gradient(ellipse 60px 60px at 100% 0%, rgba(var(--beam-pulse-corner), calc(var(--beam-pulse-corner-alpha) * var(--beam-bop-tr))), transparent 70%),\n radial-gradient(ellipse 60px 60px at 0% 100%, rgba(var(--beam-pulse-corner), calc(var(--beam-pulse-corner-alpha) * var(--beam-bop-bl))), transparent 70%),\n radial-gradient(ellipse 60px 60px at 100% 100%, rgba(var(--beam-pulse-corner), calc(var(--beam-pulse-corner-alpha) * var(--beam-bop-br))), transparent 70%);\n border-radius: var(--beam-radius);\n opacity: calc(var(--beam-opacity) * var(--beam-inner-opacity) * var(--beam-mono) * var(--beam-strength));\n will-change: opacity, filter;\n filter: hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$edge-fade-y}, #{$edge-fade-x};\n mask-composite: add;\n }\n\n > .bloom {\n z-index: 3;\n inset: 0;\n padding: 1px;\n background: var(--beam-pulse-inner-bloom);\n border-radius: var(--beam-radius);\n opacity: 0;\n will-change: opacity;\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$ring-mask};\n mask-composite: exclude;\n }\n\n &.isActive > .bloom,\n &.isFading > .bloom {\n display: block;\n opacity: calc(var(--beam-opacity) * var(--beam-bloom-opacity) * var(--beam-mono) * var(--beam-strength));\n filter: blur(8px) hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n}\n\n.pulseOutside {\n overflow: visible;\n\n --beam-bloom-opacity: .42;\n --beam-brightness: 1.7;\n --beam-inner-opacity: 1.04;\n --beam-pulse-bloom-blur: 15px;\n --beam-pulse-frozen: 1;\n --beam-pulse-glow-blur: 6px;\n --beam-saturation: .6;\n --beam-stroke-opacity: 1.96;\n\n [dark] & {\n --beam-bloom-opacity: .3;\n --beam-brightness: 1.9;\n --beam-inner-opacity: .34;\n --beam-pulse-bloom-blur: 22.5px;\n --beam-pulse-frozen: .77;\n --beam-pulse-glow-blur: 3px;\n --beam-saturation: 1.2;\n --beam-stroke-opacity: .94;\n }\n\n &.isActive::after,\n &.isFading::after {\n position: absolute;\n z-index: 2;\n inset: 0;\n padding: 1px;\n content: '';\n background: var(--beam-pulse-outer-core);\n border-radius: var(--beam-radius);\n opacity: calc(var(--beam-opacity) * var(--beam-stroke-opacity) * var(--beam-mono) * var(--beam-strength));\n will-change: opacity, filter;\n filter: hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n clip-path: inset(0 round var(--beam-radius));\n mask: #{$ring-mask};\n mask-composite: exclude;\n }\n\n &.isActive::before,\n &.isFading::before {\n position: absolute;\n z-index: -1;\n inset: -10px;\n content: '';\n background: var(--beam-pulse-outer-core);\n border-radius: calc(var(--beam-radius) + 10px);\n opacity: calc(var(--beam-opacity) * var(--beam-inner-opacity) * var(--beam-mono) * var(--beam-strength));\n will-change: opacity, filter;\n transform: scale(.95, .9);\n filter: blur(var(--beam-pulse-glow-blur)) hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n\n > .bloom {\n z-index: -1;\n inset: -30px;\n background: var(--beam-pulse-outer-bloom);\n border-radius: calc(var(--beam-radius) + 30px);\n opacity: 0;\n will-change: transform;\n transform: scale(.95, .9);\n }\n\n &.isActive > .bloom,\n &.isFading > .bloom {\n display: block;\n opacity: calc(var(--beam-opacity) * var(--beam-bloom-opacity) * var(--beam-mono) * var(--beam-strength));\n filter: blur(var(--beam-pulse-bloom-blur)) hue-rotate(var(--beam-hue)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n}\n\n.borderBeam.isPaused,\n.borderBeam.isPaused::before,\n.borderBeam.isPaused::after,\n.borderBeam.isPaused > .bloom {\n animation-play-state: paused !important;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .pulseInner,\n .pulseOutside,\n .pulseInner::before,\n .pulseInner::after,\n .pulseOutside::before,\n .pulseOutside::after,\n .pulseInner > .bloom,\n .pulseOutside > .bloom {\n animation: none !important;\n }\n}\n\n@keyframes borderBeamSpin {\n to {\n --beam-angle: 360deg;\n }\n}\n\n@keyframes borderBeamFadeIn {\n to {\n --beam-opacity: 1;\n }\n}\n\n@keyframes borderBeamFadeOut {\n from {\n --beam-opacity: 1;\n }\n\n to {\n --beam-opacity: 0;\n }\n}\n\n@keyframes borderBeamHueShift {\n 0%, 100% {\n filter: hue-rotate(calc(var(--beam-hue-range) * -1deg)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n\n 50% {\n filter: hue-rotate(calc(var(--beam-hue-range) * 1deg)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n}\n\n@keyframes borderBeamHueShiftBloom {\n 0%, 100% {\n filter: blur(8px) hue-rotate(calc((var(--beam-hue-range) + 10) * -1deg)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n\n 50% {\n filter: blur(8px) hue-rotate(calc((var(--beam-hue-range) + 10) * 1deg)) brightness(var(--beam-brightness)) saturate(var(--beam-saturation));\n }\n}\n\n@keyframes borderBeamTravel {\n 0% {\n --beam-x: .06;\n --beam-w: .5;\n }\n\n 10% {\n --beam-x: .15;\n --beam-w: .8;\n }\n\n 20% {\n --beam-x: .25;\n --beam-w: 1.1;\n }\n\n 30% {\n --beam-x: .35;\n --beam-w: 1.3;\n }\n\n 40% {\n --beam-x: .44;\n --beam-w: 1.45;\n }\n\n 50% {\n --beam-x: .5;\n --beam-w: 1.5;\n }\n\n 60% {\n --beam-x: .56;\n --beam-w: 1.45;\n }\n\n 70% {\n --beam-x: .65;\n --beam-w: 1.3;\n }\n\n 80% {\n --beam-x: .75;\n --beam-w: 1.1;\n }\n\n 90% {\n --beam-x: .85;\n --beam-w: .8;\n }\n\n 100% {\n --beam-x: .94;\n --beam-w: .5;\n }\n}\n\n@keyframes borderBeamEdgeFade {\n 0%, 100% {\n --beam-edge: 0;\n }\n\n 12.5% {\n --beam-edge: 0;\n }\n\n 32.5%, 67.5% {\n --beam-edge: 1;\n }\n\n 87.5% {\n --beam-edge: 0;\n }\n}\n\n@keyframes borderBeamBreathe {\n 0%, 100% {\n --beam-h: .8;\n }\n\n 25% {\n --beam-h: 1.25;\n }\n\n 55% {\n --beam-h: .85;\n }\n\n 80% {\n --beam-h: 1.3;\n }\n}\n\n@keyframes borderBeamSpike {\n 0%, 100% {\n --beam-spike: .8;\n }\n\n 25% {\n --beam-spike: 1.3;\n }\n\n 50% {\n --beam-spike: .9;\n }\n\n 75% {\n --beam-spike: 1.4;\n }\n}\n\n@keyframes borderBeamSpike2 {\n 0%, 100% {\n --beam-spike2: 1.2;\n }\n\n 25% {\n --beam-spike2: .7;\n }\n\n 50% {\n --beam-spike2: 1.4;\n }\n\n 75% {\n --beam-spike2: .8;\n }\n}\n","<template>\n <div\n ref=\"wrapper\"\n :class=\"clsx(\n $style.borderBeam,\n VARIANT_CLASSES[variant],\n $style[colorVariant],\n isActive && !isFading && $style.isActive,\n isFading && $style.isFading,\n isPaused && $style.isPaused,\n isStatic && $style.isStatic\n )\"\n :style=\"style\"\n @animationend=\"onAnimationEnd\">\n <slot/>\n\n <div\n aria-hidden=\"true\"\n :class=\"$style.bloom\"/>\n </div>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { clamp } from '@basmilius/utils';\n import type { FluxVisualBorderBeamVariant } from '@flux-ui/types';\n import { clsx } from 'clsx';\n import { computed, ref, unref, useTemplateRef, watch } from 'vue';\n import { useBorderBeamPulse } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/BorderBeam.module.scss';\n\n const emit = defineEmits<{\n activate: [];\n deactivate: [];\n }>();\n\n const {\n active = true,\n brightness,\n colorVariant = 'colorful',\n duration,\n hueRange = 30,\n radius,\n saturation,\n staticColors = false,\n strength = 1,\n variant = 'md'\n } = defineProps<{\n readonly active?: boolean;\n readonly brightness?: number;\n readonly colorVariant?: 'colorful' | 'mono' | 'ocean' | 'sunset';\n readonly duration?: number;\n readonly hueRange?: number;\n readonly radius?: string | number;\n readonly saturation?: number;\n readonly staticColors?: boolean;\n readonly strength?: number;\n readonly variant?: FluxVisualBorderBeamVariant;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n\n const VARIANT_CLASSES: Record<FluxVisualBorderBeamVariant, string> = {\n 'sm': $style.sm,\n 'md': $style.md,\n 'line': $style.line,\n 'pulse-inner': $style.pulseInner,\n 'pulse-outside': $style.pulseOutside\n };\n\n const wrapperRef = useTemplateRef('wrapper');\n const isActive = ref(active);\n const isFading = ref(false);\n const glowScale = ref<{ x: number; y: number; } | null>(null);\n\n const inView = useInView(wrapperRef, {initial: true, rootMargin: '256px'});\n\n const isPulse = computed(() => variant === 'pulse-inner' || variant === 'pulse-outside');\n const isStatic = computed(() => staticColors || colorVariant === 'mono');\n const isPaused = computed(() => isActive.value && !isFading.value && !inView.value);\n const resolvedDuration = computed(() => duration ?? (variant === 'line' ? 3.1 : isPulse.value ? 2.3 : 1.96));\n\n const style = computed(() => ({\n '--beam-brightness': brightness,\n '--beam-duration': resolvedDuration.value,\n '--beam-glow-sx': glowScale.value?.x,\n '--beam-glow-sy': glowScale.value?.y,\n '--beam-hue-range': variant === 'line' ? Math.min(hueRange, 13) : hueRange,\n '--beam-radius': typeof radius === 'number' ? `${radius}px` : radius,\n '--beam-saturation': saturation,\n '--beam-strength': Math.max(0, Math.min(1, strength))\n }));\n\n useBorderBeamPulse({\n duration: resolvedDuration,\n elementRef: wrapperRef,\n enabled: computed(() => (isActive.value || isFading.value) && inView.value),\n staticColors: isStatic,\n variant: computed(() => variant)\n });\n\n watch(() => active, value => {\n if (value) {\n // Also covers re-activating while the fade-out is still running: cancel the\n // fade, otherwise its animationend would turn the beam off for good.\n isFading.value = false;\n isActive.value = true;\n } else if (isActive.value && !isFading.value) {\n isFading.value = true;\n }\n });\n\n // The pulse-outside glow geometry is authored in fixed pixels for a reference\n // element of ~350x140; measure the wrapped element and scale the glow per-axis\n // so the halo fits any component it's applied to.\n watch([wrapperRef, () => variant], (_, __, onCleanup) => {\n glowScale.value = null;\n\n const wrapper = unref(wrapperRef);\n\n if (!wrapper || variant !== 'pulse-outside' || typeof ResizeObserver === 'undefined') {\n return;\n }\n\n const child = wrapper.firstElementChild;\n\n if (!child || !(child instanceof HTMLElement)) {\n return;\n }\n\n const measure = (): void => {\n const rect = child.getBoundingClientRect();\n\n if (!rect.width || !rect.height) {\n return;\n }\n\n const x = +clamp(rect.width / 350, .35, 4).toFixed(3);\n const y = +clamp(rect.height / 140, .35, 4).toFixed(3);\n\n if (glowScale.value?.x !== x || glowScale.value?.y !== y) {\n glowScale.value = {x, y};\n }\n };\n\n measure();\n\n const observer = new ResizeObserver(measure);\n observer.observe(child);\n\n onCleanup(() => observer.disconnect());\n }, {immediate: true});\n\n function onAnimationEnd(event: AnimationEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n if (isFading.value) {\n isActive.value = false;\n isFading.value = false;\n emit('deactivate');\n } else if (isActive.value) {\n emit('activate');\n }\n }\n</script>\n","<template>\n <div\n ref=\"wrapper\"\n :class=\"clsx(\n $style.borderBeam,\n VARIANT_CLASSES[variant],\n $style[colorVariant],\n isActive && !isFading && $style.isActive,\n isFading && $style.isFading,\n isPaused && $style.isPaused,\n isStatic && $style.isStatic\n )\"\n :style=\"style\"\n @animationend=\"onAnimationEnd\">\n <slot/>\n\n <div\n aria-hidden=\"true\"\n :class=\"$style.bloom\"/>\n </div>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { clamp } from '@basmilius/utils';\n import type { FluxVisualBorderBeamVariant } from '@flux-ui/types';\n import { clsx } from 'clsx';\n import { computed, ref, unref, useTemplateRef, watch } from 'vue';\n import { useBorderBeamPulse } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/BorderBeam.module.scss';\n\n const emit = defineEmits<{\n activate: [];\n deactivate: [];\n }>();\n\n const {\n active = true,\n brightness,\n colorVariant = 'colorful',\n duration,\n hueRange = 30,\n radius,\n saturation,\n staticColors = false,\n strength = 1,\n variant = 'md'\n } = defineProps<{\n readonly active?: boolean;\n readonly brightness?: number;\n readonly colorVariant?: 'colorful' | 'mono' | 'ocean' | 'sunset';\n readonly duration?: number;\n readonly hueRange?: number;\n readonly radius?: string | number;\n readonly saturation?: number;\n readonly staticColors?: boolean;\n readonly strength?: number;\n readonly variant?: FluxVisualBorderBeamVariant;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n\n const VARIANT_CLASSES: Record<FluxVisualBorderBeamVariant, string> = {\n 'sm': $style.sm,\n 'md': $style.md,\n 'line': $style.line,\n 'pulse-inner': $style.pulseInner,\n 'pulse-outside': $style.pulseOutside\n };\n\n const wrapperRef = useTemplateRef('wrapper');\n const isActive = ref(active);\n const isFading = ref(false);\n const glowScale = ref<{ x: number; y: number; } | null>(null);\n\n const inView = useInView(wrapperRef, {initial: true, rootMargin: '256px'});\n\n const isPulse = computed(() => variant === 'pulse-inner' || variant === 'pulse-outside');\n const isStatic = computed(() => staticColors || colorVariant === 'mono');\n const isPaused = computed(() => isActive.value && !isFading.value && !inView.value);\n const resolvedDuration = computed(() => duration ?? (variant === 'line' ? 3.1 : isPulse.value ? 2.3 : 1.96));\n\n const style = computed(() => ({\n '--beam-brightness': brightness,\n '--beam-duration': resolvedDuration.value,\n '--beam-glow-sx': glowScale.value?.x,\n '--beam-glow-sy': glowScale.value?.y,\n '--beam-hue-range': variant === 'line' ? Math.min(hueRange, 13) : hueRange,\n '--beam-radius': typeof radius === 'number' ? `${radius}px` : radius,\n '--beam-saturation': saturation,\n '--beam-strength': Math.max(0, Math.min(1, strength))\n }));\n\n useBorderBeamPulse({\n duration: resolvedDuration,\n elementRef: wrapperRef,\n enabled: computed(() => (isActive.value || isFading.value) && inView.value),\n staticColors: isStatic,\n variant: computed(() => variant)\n });\n\n watch(() => active, value => {\n if (value) {\n // Also covers re-activating while the fade-out is still running: cancel the\n // fade, otherwise its animationend would turn the beam off for good.\n isFading.value = false;\n isActive.value = true;\n } else if (isActive.value && !isFading.value) {\n isFading.value = true;\n }\n });\n\n // The pulse-outside glow geometry is authored in fixed pixels for a reference\n // element of ~350x140; measure the wrapped element and scale the glow per-axis\n // so the halo fits any component it's applied to.\n watch([wrapperRef, () => variant], (_, __, onCleanup) => {\n glowScale.value = null;\n\n const wrapper = unref(wrapperRef);\n\n if (!wrapper || variant !== 'pulse-outside' || typeof ResizeObserver === 'undefined') {\n return;\n }\n\n const child = wrapper.firstElementChild;\n\n if (!child || !(child instanceof HTMLElement)) {\n return;\n }\n\n const measure = (): void => {\n const rect = child.getBoundingClientRect();\n\n if (!rect.width || !rect.height) {\n return;\n }\n\n const x = +clamp(rect.width / 350, .35, 4).toFixed(3);\n const y = +clamp(rect.height / 140, .35, 4).toFixed(3);\n\n if (glowScale.value?.x !== x || glowScale.value?.y !== y) {\n glowScale.value = {x, y};\n }\n };\n\n measure();\n\n const observer = new ResizeObserver(measure);\n observer.observe(child);\n\n onCleanup(() => observer.disconnect());\n }, {immediate: true});\n\n function onAnimationEnd(event: AnimationEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n if (isFading.value) {\n isActive.value = false;\n isFading.value = false;\n emit('deactivate');\n } else if (isActive.value) {\n emit('activate');\n }\n }\n</script>\n","<script lang=\"ts\">\n import { flattenVNodeTree, orange600, pink600, purple600 } from '@flux-ui/internals';\n import { clsx } from 'clsx';\n import { cloneVNode, defineComponent, Fragment, h, type PropType } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n export default defineComponent({\n inheritAttrs: false,\n props: {\n colors: {default: [purple600, 'transparent', orange600, 'transparent', pink600, 'transparent', purple600], type: Array as PropType<string[]>},\n duration: {default: 9, type: Number},\n offset: {default: 1, type: Number},\n radius: {default: undefined, type: [String, Number] as PropType<string | number>},\n width: {default: 2, type: Number}\n },\n setup(props, {attrs, slots}) {\n return () => h(\n Fragment,\n flattenVNodeTree(slots.default?.() ?? []).map(vnode => cloneVNode(vnode, {\n ...attrs,\n class: clsx(\n attrs.class as string,\n $style.borderShine\n ),\n style: {\n '--shine-colors': props.colors.join(', '),\n '--shine-duration': props.duration,\n '--shine-offset': props.offset,\n '--shine-radius': typeof props.radius === 'number' ? `${props.radius}px` : props.radius,\n '--shine-width': props.width\n }\n }))\n );\n }\n });\n</script>\n","<script lang=\"ts\">\n import { flattenVNodeTree, orange600, pink600, purple600 } from '@flux-ui/internals';\n import { clsx } from 'clsx';\n import { cloneVNode, defineComponent, Fragment, h, type PropType } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n export default defineComponent({\n inheritAttrs: false,\n props: {\n colors: {default: [purple600, 'transparent', orange600, 'transparent', pink600, 'transparent', purple600], type: Array as PropType<string[]>},\n duration: {default: 9, type: Number},\n offset: {default: 1, type: Number},\n radius: {default: undefined, type: [String, Number] as PropType<string | number>},\n width: {default: 2, type: Number}\n },\n setup(props, {attrs, slots}) {\n return () => h(\n Fragment,\n flattenVNodeTree(slots.default?.() ?? []).map(vnode => cloneVNode(vnode, {\n ...attrs,\n class: clsx(\n attrs.class as string,\n $style.borderShine\n ),\n style: {\n '--shine-colors': props.colors.join(', '),\n '--shine-duration': props.duration,\n '--shine-offset': props.offset,\n '--shine-radius': typeof props.radius === 'number' ? `${props.radius}px` : props.radius,\n '--shine-width': props.width\n }\n }))\n );\n }\n });\n</script>\n",".glowLayer {\n transition: opacity .25s ease;\n pointer-events: none;\n opacity: 0;\n -webkit-mask: radial-gradient(circle var(--pattern-glow-size, 120px) at var(--pattern-glow-x, 50%) var(--pattern-glow-y, 50%), #000 0%, transparent 100%);\n mask: radial-gradient(circle var(--pattern-glow-size, 120px) at var(--pattern-glow-x, 50%) var(--pattern-glow-y, 50%), #000 0%, transparent 100%);\n}\n\n.isActive {\n opacity: 1;\n}\n\n.glowDot {\n fill: var(--primary-solid);\n}\n\n.glowLine {\n stroke: var(--primary-solid);\n}\n","<template>\n <svg\n ref=\"root\"\n aria-hidden=\"true\"\n :class=\"$style.dotPattern\">\n <defs>\n <pattern\n :id=\"id\"\n :width=\"width\"\n :height=\"height\"\n patternContentUnits=\"userSpaceOnUse\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <circle\n :r=\"cr\"\n :cx=\"width / 2 - cx\"\n :cy=\"height / 2 - cy\"/>\n </pattern>\n\n <pattern\n v-if=\"glow\"\n :id=\"glowId\"\n :width=\"width\"\n :height=\"height\"\n patternContentUnits=\"userSpaceOnUse\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <circle\n :class=\"$glow.glowDot\"\n :r=\"cr\"\n :cx=\"width / 2 - cx\"\n :cy=\"height / 2 - cy\"/>\n </pattern>\n </defs>\n\n <rect\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${id})`\"/>\n\n <rect\n v-if=\"glow\"\n :class=\"[$glow.glowLayer, active && $glow.isActive]\"\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${glowId})`\"/>\n </svg>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $glow from '~flux/visuals/css/component/PatternGlow.module.scss';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n width = 16,\n height = 16,\n cr = 1,\n cx = 1,\n cy = 1,\n glow = false\n } = defineProps<{\n readonly width?: number;\n readonly height?: number;\n readonly cr?: number;\n readonly cx?: number;\n readonly cy?: number;\n readonly glow?: boolean;\n }>();\n\n const rootRef = useTemplateRef<SVGSVGElement>('root');\n const active = ref(false);\n\n const id = useId();\n const glowId = `${id}-glow`;\n\n // The pattern svg has pointer-events: none, so the cursor is tracked on the\n // parent scroll/overflow container instead. The position is written straight\n // to CSS custom properties to avoid a re-render on every pointermove.\n watch([rootRef, () => glow], (_, __, onCleanup) => {\n const root = unref(rootRef);\n\n if (!glow || !root || !root.parentElement) {\n return;\n }\n\n const parent = root.parentElement;\n\n const onPointerMove = (event: PointerEvent): void => {\n const rect = parent.getBoundingClientRect();\n root.style.setProperty('--pattern-glow-x', `${event.clientX - rect.left}px`);\n root.style.setProperty('--pattern-glow-y', `${event.clientY - rect.top}px`);\n };\n\n const onPointerEnter = (): void => {\n active.value = true;\n };\n\n const onPointerLeave = (): void => {\n active.value = false;\n };\n\n parent.addEventListener('pointermove', onPointerMove, {passive: true});\n parent.addEventListener('pointerenter', onPointerEnter);\n parent.addEventListener('pointerleave', onPointerLeave);\n\n onCleanup(() => {\n active.value = false;\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n });\n }, {immediate: true});\n</script>\n","<template>\n <svg\n ref=\"root\"\n aria-hidden=\"true\"\n :class=\"$style.dotPattern\">\n <defs>\n <pattern\n :id=\"id\"\n :width=\"width\"\n :height=\"height\"\n patternContentUnits=\"userSpaceOnUse\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <circle\n :r=\"cr\"\n :cx=\"width / 2 - cx\"\n :cy=\"height / 2 - cy\"/>\n </pattern>\n\n <pattern\n v-if=\"glow\"\n :id=\"glowId\"\n :width=\"width\"\n :height=\"height\"\n patternContentUnits=\"userSpaceOnUse\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <circle\n :class=\"$glow.glowDot\"\n :r=\"cr\"\n :cx=\"width / 2 - cx\"\n :cy=\"height / 2 - cy\"/>\n </pattern>\n </defs>\n\n <rect\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${id})`\"/>\n\n <rect\n v-if=\"glow\"\n :class=\"[$glow.glowLayer, active && $glow.isActive]\"\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${glowId})`\"/>\n </svg>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $glow from '~flux/visuals/css/component/PatternGlow.module.scss';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n width = 16,\n height = 16,\n cr = 1,\n cx = 1,\n cy = 1,\n glow = false\n } = defineProps<{\n readonly width?: number;\n readonly height?: number;\n readonly cr?: number;\n readonly cx?: number;\n readonly cy?: number;\n readonly glow?: boolean;\n }>();\n\n const rootRef = useTemplateRef<SVGSVGElement>('root');\n const active = ref(false);\n\n const id = useId();\n const glowId = `${id}-glow`;\n\n // The pattern svg has pointer-events: none, so the cursor is tracked on the\n // parent scroll/overflow container instead. The position is written straight\n // to CSS custom properties to avoid a re-render on every pointermove.\n watch([rootRef, () => glow], (_, __, onCleanup) => {\n const root = unref(rootRef);\n\n if (!glow || !root || !root.parentElement) {\n return;\n }\n\n const parent = root.parentElement;\n\n const onPointerMove = (event: PointerEvent): void => {\n const rect = parent.getBoundingClientRect();\n root.style.setProperty('--pattern-glow-x', `${event.clientX - rect.left}px`);\n root.style.setProperty('--pattern-glow-y', `${event.clientY - rect.top}px`);\n };\n\n const onPointerEnter = (): void => {\n active.value = true;\n };\n\n const onPointerLeave = (): void => {\n active.value = false;\n };\n\n parent.addEventListener('pointermove', onPointerMove, {passive: true});\n parent.addEventListener('pointerenter', onPointerEnter);\n parent.addEventListener('pointerleave', onPointerLeave);\n\n onCleanup(() => {\n active.value = false;\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n });\n }, {immediate: true});\n</script>\n","<template>\n <canvas\n ref=\"canvas\"\n aria-hidden=\"true\"\n :class=\"$style.flickeringGrid\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { mulberry32, prefersReducedMotion } from '@basmilius/utils';\n import { computed, unref, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n color = '#1d4ed8',\n flickerChance = 0.15,\n gap = 6,\n maxOpacity = 0.3,\n size = 3\n } = defineProps<{\n readonly color?: string;\n readonly flickerChance?: number;\n readonly gap?: number;\n readonly maxOpacity?: number;\n readonly size?: number;\n }>();\n\n const canvasRef = useTemplateRef('canvas');\n\n const inView = useInView(canvasRef);\n\n const mulberry = mulberry32(13);\n\n const rgb = computed(() => {\n const canvas = document.createElement('canvas');\n canvas.width = canvas.height = 1;\n\n const context = canvas.getContext('2d');\n\n if (!context) {\n return [0, 0, 0];\n }\n\n context.fillStyle = color;\n context.fillRect(0, 0, 1, 1);\n\n return context.getImageData(0, 0, 1, 1).data;\n });\n\n watch([canvasRef, inView], ([canvas, inView], _, onCleanup) => {\n if (!canvas || !inView) {\n return;\n }\n\n const context = canvas.getContext('2d');\n\n if (!context) {\n return;\n }\n\n let frame = 0;\n let lastTime = 0;\n let {width, height, columns, rows, squares, dpr} = setup(canvas);\n\n const reducedMotion = prefersReducedMotion();\n\n const onResize = () => {\n ({width, height, columns, rows, squares, dpr} = setup(canvas));\n\n if (reducedMotion) {\n draw(context, width, height, columns, rows, squares, dpr);\n }\n };\n\n window.addEventListener('resize', onResize, {passive: true});\n\n if (reducedMotion) {\n draw(context, width, height, columns, rows, squares, dpr);\n\n onCleanup(() => {\n window.removeEventListener('resize', onResize);\n });\n\n return;\n }\n\n const animate = (time: number): void => {\n const delta = lastTime > 0 ? (time - lastTime) / 1000 : 0;\n lastTime = time;\n\n tick(squares, delta);\n draw(context, width, height, columns, rows, squares, dpr);\n frame = requestAnimationFrame(animate);\n };\n\n frame = requestAnimationFrame(animate);\n\n onCleanup(() => {\n window.removeEventListener('resize', onResize);\n cancelAnimationFrame(frame);\n });\n }, {immediate: true});\n\n function draw(context: CanvasRenderingContext2D, width: number, height: number, columns: number, rows: number, squares: Float32Array, dpr: number): void {\n context.clearRect(0, 0, width * dpr, height * dpr);\n\n const [r, g, b] = unref(rgb);\n\n for (let i = 0; i < columns; ++i) {\n for (let j = 0; j < rows; ++j) {\n const opacity = squares[i * rows + j];\n context.fillStyle = `rgb(${r} ${g} ${b} / ${opacity})`;\n context.fillRect(\n (i * (size + gap) + width / 2 - (columns / 2 * (size + gap) - gap / 2)) * dpr,\n (j * (size + gap) + height / 2 - (rows / 2 * (size + gap) - gap / 2)) * dpr,\n size * dpr,\n size * dpr\n );\n }\n }\n }\n\n function setup(canvas: HTMLCanvasElement) {\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const dpr = window.devicePixelRatio || 1;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n const columns = Math.ceil(width / (size + gap));\n const rows = Math.ceil(height / (size + gap));\n const squares = new Float32Array(columns * rows);\n\n for (let i = 0; i < squares.length; ++i) {\n squares[i] = mulberry.next() * maxOpacity;\n }\n\n return {\n width,\n height,\n columns,\n rows,\n squares,\n dpr\n };\n }\n\n function tick(squares: Float32Array, delta: number): void {\n for (let i = 0; i < squares.length; ++i) {\n if (mulberry.next() < flickerChance * delta) {\n squares[i] = mulberry.next() * maxOpacity;\n }\n }\n }\n</script>\n","<template>\n <canvas\n ref=\"canvas\"\n aria-hidden=\"true\"\n :class=\"$style.flickeringGrid\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { mulberry32, prefersReducedMotion } from '@basmilius/utils';\n import { computed, unref, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n color = '#1d4ed8',\n flickerChance = 0.15,\n gap = 6,\n maxOpacity = 0.3,\n size = 3\n } = defineProps<{\n readonly color?: string;\n readonly flickerChance?: number;\n readonly gap?: number;\n readonly maxOpacity?: number;\n readonly size?: number;\n }>();\n\n const canvasRef = useTemplateRef('canvas');\n\n const inView = useInView(canvasRef);\n\n const mulberry = mulberry32(13);\n\n const rgb = computed(() => {\n const canvas = document.createElement('canvas');\n canvas.width = canvas.height = 1;\n\n const context = canvas.getContext('2d');\n\n if (!context) {\n return [0, 0, 0];\n }\n\n context.fillStyle = color;\n context.fillRect(0, 0, 1, 1);\n\n return context.getImageData(0, 0, 1, 1).data;\n });\n\n watch([canvasRef, inView], ([canvas, inView], _, onCleanup) => {\n if (!canvas || !inView) {\n return;\n }\n\n const context = canvas.getContext('2d');\n\n if (!context) {\n return;\n }\n\n let frame = 0;\n let lastTime = 0;\n let {width, height, columns, rows, squares, dpr} = setup(canvas);\n\n const reducedMotion = prefersReducedMotion();\n\n const onResize = () => {\n ({width, height, columns, rows, squares, dpr} = setup(canvas));\n\n if (reducedMotion) {\n draw(context, width, height, columns, rows, squares, dpr);\n }\n };\n\n window.addEventListener('resize', onResize, {passive: true});\n\n if (reducedMotion) {\n draw(context, width, height, columns, rows, squares, dpr);\n\n onCleanup(() => {\n window.removeEventListener('resize', onResize);\n });\n\n return;\n }\n\n const animate = (time: number): void => {\n const delta = lastTime > 0 ? (time - lastTime) / 1000 : 0;\n lastTime = time;\n\n tick(squares, delta);\n draw(context, width, height, columns, rows, squares, dpr);\n frame = requestAnimationFrame(animate);\n };\n\n frame = requestAnimationFrame(animate);\n\n onCleanup(() => {\n window.removeEventListener('resize', onResize);\n cancelAnimationFrame(frame);\n });\n }, {immediate: true});\n\n function draw(context: CanvasRenderingContext2D, width: number, height: number, columns: number, rows: number, squares: Float32Array, dpr: number): void {\n context.clearRect(0, 0, width * dpr, height * dpr);\n\n const [r, g, b] = unref(rgb);\n\n for (let i = 0; i < columns; ++i) {\n for (let j = 0; j < rows; ++j) {\n const opacity = squares[i * rows + j];\n context.fillStyle = `rgb(${r} ${g} ${b} / ${opacity})`;\n context.fillRect(\n (i * (size + gap) + width / 2 - (columns / 2 * (size + gap) - gap / 2)) * dpr,\n (j * (size + gap) + height / 2 - (rows / 2 * (size + gap) - gap / 2)) * dpr,\n size * dpr,\n size * dpr\n );\n }\n }\n }\n\n function setup(canvas: HTMLCanvasElement) {\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const dpr = window.devicePixelRatio || 1;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n const columns = Math.ceil(width / (size + gap));\n const rows = Math.ceil(height / (size + gap));\n const squares = new Float32Array(columns * rows);\n\n for (let i = 0; i < squares.length; ++i) {\n squares[i] = mulberry.next() * maxOpacity;\n }\n\n return {\n width,\n height,\n columns,\n rows,\n squares,\n dpr\n };\n }\n\n function tick(squares: Float32Array, delta: number): void {\n for (let i = 0; i < squares.length; ++i) {\n if (mulberry.next() < flickerChance * delta) {\n squares[i] = mulberry.next() * maxOpacity;\n }\n }\n }\n</script>\n","<template>\n <svg\n ref=\"root\"\n aria-hidden=\"true\"\n :class=\"$style.gridPattern\">\n <defs>\n <pattern\n :id=\"id\"\n :width=\"width\"\n :height=\"height\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <path\n :d=\"`M.5 ${height}V.5H${width}`\"\n fill=\"none\"\n :stroke-dasharray=\"strokeDasharray\"/>\n </pattern>\n\n <pattern\n v-if=\"glow\"\n :id=\"glowId\"\n :width=\"width\"\n :height=\"height\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <path\n :class=\"$glow.glowLine\"\n :d=\"`M.5 ${height}V.5H${width}`\"\n fill=\"none\"\n :stroke-dasharray=\"strokeDasharray\"/>\n </pattern>\n </defs>\n\n <rect\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${id})`\"/>\n\n <svg\n v-if=\"squares?.length\"\n style=\"overflow: visible;\">\n <rect\n v-for=\"[x, y] of squares\"\n :key=\"`${x}-${y}`\"\n :width=\"width - 1\"\n :height=\"height - 1\"\n :x=\"x * width\"\n :y=\"y * height\"\n stroke-width=\"0\"/>\n </svg>\n\n <rect\n v-if=\"glow\"\n :class=\"[$glow.glowLayer, active && $glow.isActive]\"\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${glowId})`\"/>\n </svg>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $glow from '~flux/visuals/css/component/PatternGlow.module.scss';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n width = 42,\n height = 42,\n strokeDasharray = 0,\n squares,\n glow = false\n } = defineProps<{\n readonly width?: number;\n readonly height?: number;\n readonly strokeDasharray?: number | string;\n readonly squares?: Array<[x: number, y: number]>;\n readonly glow?: boolean;\n }>();\n\n const rootRef = useTemplateRef<SVGSVGElement>('root');\n const active = ref(false);\n\n const id = useId();\n const glowId = `${id}-glow`;\n\n // The pattern svg has pointer-events: none, so the cursor is tracked on the\n // parent scroll/overflow container instead. The position is written straight\n // to CSS custom properties to avoid a re-render on every pointermove.\n watch([rootRef, () => glow], (_, __, onCleanup) => {\n const root = unref(rootRef);\n\n if (!glow || !root || !root.parentElement) {\n return;\n }\n\n const parent = root.parentElement;\n\n const onPointerMove = (event: PointerEvent): void => {\n const rect = parent.getBoundingClientRect();\n root.style.setProperty('--pattern-glow-x', `${event.clientX - rect.left}px`);\n root.style.setProperty('--pattern-glow-y', `${event.clientY - rect.top}px`);\n };\n\n const onPointerEnter = (): void => {\n active.value = true;\n };\n\n const onPointerLeave = (): void => {\n active.value = false;\n };\n\n parent.addEventListener('pointermove', onPointerMove, {passive: true});\n parent.addEventListener('pointerenter', onPointerEnter);\n parent.addEventListener('pointerleave', onPointerLeave);\n\n onCleanup(() => {\n active.value = false;\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n });\n }, {immediate: true});\n</script>\n","<template>\n <svg\n ref=\"root\"\n aria-hidden=\"true\"\n :class=\"$style.gridPattern\">\n <defs>\n <pattern\n :id=\"id\"\n :width=\"width\"\n :height=\"height\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <path\n :d=\"`M.5 ${height}V.5H${width}`\"\n fill=\"none\"\n :stroke-dasharray=\"strokeDasharray\"/>\n </pattern>\n\n <pattern\n v-if=\"glow\"\n :id=\"glowId\"\n :width=\"width\"\n :height=\"height\"\n patternUnits=\"userSpaceOnUse\"\n :x=\"-1\"\n :y=\"-1\">\n <path\n :class=\"$glow.glowLine\"\n :d=\"`M.5 ${height}V.5H${width}`\"\n fill=\"none\"\n :stroke-dasharray=\"strokeDasharray\"/>\n </pattern>\n </defs>\n\n <rect\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${id})`\"/>\n\n <svg\n v-if=\"squares?.length\"\n style=\"overflow: visible;\">\n <rect\n v-for=\"[x, y] of squares\"\n :key=\"`${x}-${y}`\"\n :width=\"width - 1\"\n :height=\"height - 1\"\n :x=\"x * width\"\n :y=\"y * height\"\n stroke-width=\"0\"/>\n </svg>\n\n <rect\n v-if=\"glow\"\n :class=\"[$glow.glowLayer, active && $glow.isActive]\"\n width=\"100%\"\n height=\"100%\"\n stroke-width=\"0\"\n :fill=\"`url(#${glowId})`\"/>\n </svg>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { ref, unref, useId, useTemplateRef, watch } from 'vue';\n import $glow from '~flux/visuals/css/component/PatternGlow.module.scss';\n import $style from '~flux/visuals/css/component/Visual.module.scss';\n\n const {\n width = 42,\n height = 42,\n strokeDasharray = 0,\n squares,\n glow = false\n } = defineProps<{\n readonly width?: number;\n readonly height?: number;\n readonly strokeDasharray?: number | string;\n readonly squares?: Array<[x: number, y: number]>;\n readonly glow?: boolean;\n }>();\n\n const rootRef = useTemplateRef<SVGSVGElement>('root');\n const active = ref(false);\n\n const id = useId();\n const glowId = `${id}-glow`;\n\n // The pattern svg has pointer-events: none, so the cursor is tracked on the\n // parent scroll/overflow container instead. The position is written straight\n // to CSS custom properties to avoid a re-render on every pointermove.\n watch([rootRef, () => glow], (_, __, onCleanup) => {\n const root = unref(rootRef);\n\n if (!glow || !root || !root.parentElement) {\n return;\n }\n\n const parent = root.parentElement;\n\n const onPointerMove = (event: PointerEvent): void => {\n const rect = parent.getBoundingClientRect();\n root.style.setProperty('--pattern-glow-x', `${event.clientX - rect.left}px`);\n root.style.setProperty('--pattern-glow-y', `${event.clientY - rect.top}px`);\n };\n\n const onPointerEnter = (): void => {\n active.value = true;\n };\n\n const onPointerLeave = (): void => {\n active.value = false;\n };\n\n parent.addEventListener('pointermove', onPointerMove, {passive: true});\n parent.addEventListener('pointerenter', onPointerEnter);\n parent.addEventListener('pointerleave', onPointerLeave);\n\n onCleanup(() => {\n active.value = false;\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n });\n }, {immediate: true});\n</script>\n",".highlighter {\n position: relative;\n}\n\n.highlighterGroup {\n display: contents;\n}\n","<template>\n <span\n ref=\"target\"\n :class=\"$style.highlighter\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { prefersReducedMotion } from '@basmilius/utils';\n import type { FluxVisualHighlighterVariant } from '@flux-ui/types';\n import { annotate } from 'rough-notation';\n import { computed, onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue';\n import { type HighlighterGroupEntry, useHighlighterGroupInjection } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/Highlighter.module.scss';\n\n const emit = defineEmits<{\n hidden: [];\n shown: [];\n }>();\n\n // The annotation props deliberately have no destructure defaults: an unset\n // prop must stay distinguishable from an explicit one, so an enclosing group\n // can supply the value instead. The effective* computeds resolve the chain.\n const {\n variant,\n color,\n strokeWidth,\n animationDuration,\n iterations,\n padding,\n multiline,\n whenInView = false\n } = defineProps<{\n readonly variant?: FluxVisualHighlighterVariant;\n readonly color?: string;\n readonly strokeWidth?: number;\n readonly animationDuration?: number;\n readonly iterations?: number;\n readonly padding?: number;\n readonly multiline?: boolean;\n readonly whenInView?: boolean;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n\n const targetRef = useTemplateRef('target');\n\n const group = useHighlighterGroupInjection();\n\n // Standalone in-view tracking. In a group the parent owns the reveal timing,\n // so no observer is set up at all.\n const inView = group ? shallowRef(true) : useInView(targetRef, {initial: !whenInView});\n\n const effectiveVariant = computed(() => variant ?? group?.defaults.variant ?? 'highlight');\n const effectiveColor = computed(() => color ?? group?.defaults.color ?? 'var(--warning-border)');\n const effectiveStrokeWidth = computed(() => strokeWidth ?? group?.defaults.strokeWidth ?? 1.5);\n const effectiveAnimationDuration = computed(() => animationDuration ?? group?.defaults.animationDuration ?? 500);\n const effectiveIterations = computed(() => iterations ?? group?.defaults.iterations ?? 2);\n const effectivePadding = computed(() => padding ?? group?.defaults.padding ?? 2);\n const effectiveMultiline = computed(() => multiline ?? group?.defaults.multiline ?? true);\n\n let annotation: ReturnType<typeof annotate> | null = null;\n let entry: HighlighterGroupEntry | null = null;\n let observer: ResizeObserver | null = null;\n let settleTimer: number | undefined;\n let shownTimer: number | undefined;\n let revealed = false;\n\n // The annotation type is immutable, so any prop change rebuilds it from scratch.\n watch([effectiveVariant, effectiveColor, effectiveStrokeWidth, effectiveAnimationDuration, effectiveIterations, effectivePadding, effectiveMultiline], () => build());\n\n // Standalone reveal once the element scrolls into view (whenInView).\n watch(inView, () => {\n if (!group) {\n reveal();\n }\n });\n\n onMounted(() => {\n const element = targetRef.value;\n\n if (group && element) {\n entry = {element, getAnnotation: () => annotation};\n group.add(entry);\n }\n\n build();\n });\n\n onBeforeUnmount(() => {\n if (group && entry) {\n group.remove(entry);\n entry = null;\n }\n\n window.clearTimeout(shownTimer);\n stopSettleWatch();\n annotation?.remove();\n annotation = null;\n });\n\n // rough-notation has no completion callback, so shown is emitted after the\n // draw animation's duration, or immediately when it draws without animation.\n function emitShown(): void {\n window.clearTimeout(shownTimer);\n\n if (prefersReducedMotion()) {\n emit('shown');\n return;\n }\n\n shownTimer = window.setTimeout(() => emit('shown'), effectiveAnimationDuration.value);\n }\n\n function show(): void {\n if (!annotation) {\n return;\n }\n\n revealed = true;\n stopSettleWatch();\n annotation.show();\n emitShown();\n }\n\n function hide(): void {\n if (!annotation) {\n return;\n }\n\n window.clearTimeout(shownTimer);\n annotation.hide();\n emit('hidden');\n }\n\n function replay(): void {\n if (!annotation) {\n return;\n }\n\n annotation.hide();\n annotation.show();\n emitShown();\n }\n\n // Standalone: draw once, with the intro animation, on the final geometry.\n // rough-notation keeps the drawn annotation aligned on later resizes itself.\n function reveal(): void {\n if (revealed || !annotation || !inView.value) {\n return;\n }\n\n show();\n }\n\n function stopSettleWatch(): void {\n window.clearTimeout(settleTimer);\n observer?.disconnect();\n observer = null;\n }\n\n function build(): void {\n annotation?.remove();\n annotation = null;\n stopSettleWatch();\n revealed = false;\n\n const element = targetRef.value;\n\n if (!element) {\n return;\n }\n\n annotation = annotate(element, {\n type: effectiveVariant.value,\n color: effectiveColor.value,\n strokeWidth: effectiveStrokeWidth.value,\n animationDuration: effectiveAnimationDuration.value,\n iterations: effectiveIterations.value,\n padding: effectivePadding.value,\n multiline: effectiveMultiline.value,\n animate: !prefersReducedMotion()\n });\n\n // In a group the parent collects the annotations and drives the cascade.\n if (group) {\n group.notify();\n return;\n }\n\n if (typeof ResizeObserver === 'undefined') {\n reveal();\n return;\n }\n\n // The surrounding chrome (preview panels, web-font swaps) can reflow right\n // after mount, which would otherwise play the intro animation where the\n // text sat *before* it settled. Debounce until the layout is quiet, then\n // draw on the final geometry.\n observer = new ResizeObserver(() => {\n window.clearTimeout(settleTimer);\n settleTimer = window.setTimeout(() => reveal(), 80);\n });\n observer.observe(element);\n observer.observe(document.body);\n }\n\n defineExpose({\n hide,\n replay,\n show\n });\n</script>\n","<template>\n <span\n ref=\"target\"\n :class=\"$style.highlighter\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { useInView } from '@basmilius/common';\n import { prefersReducedMotion } from '@basmilius/utils';\n import type { FluxVisualHighlighterVariant } from '@flux-ui/types';\n import { annotate } from 'rough-notation';\n import { computed, onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue';\n import { type HighlighterGroupEntry, useHighlighterGroupInjection } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/Highlighter.module.scss';\n\n const emit = defineEmits<{\n hidden: [];\n shown: [];\n }>();\n\n // The annotation props deliberately have no destructure defaults: an unset\n // prop must stay distinguishable from an explicit one, so an enclosing group\n // can supply the value instead. The effective* computeds resolve the chain.\n const {\n variant,\n color,\n strokeWidth,\n animationDuration,\n iterations,\n padding,\n multiline,\n whenInView = false\n } = defineProps<{\n readonly variant?: FluxVisualHighlighterVariant;\n readonly color?: string;\n readonly strokeWidth?: number;\n readonly animationDuration?: number;\n readonly iterations?: number;\n readonly padding?: number;\n readonly multiline?: boolean;\n readonly whenInView?: boolean;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n\n const targetRef = useTemplateRef('target');\n\n const group = useHighlighterGroupInjection();\n\n // Standalone in-view tracking. In a group the parent owns the reveal timing,\n // so no observer is set up at all.\n const inView = group ? shallowRef(true) : useInView(targetRef, {initial: !whenInView});\n\n const effectiveVariant = computed(() => variant ?? group?.defaults.variant ?? 'highlight');\n const effectiveColor = computed(() => color ?? group?.defaults.color ?? 'var(--warning-border)');\n const effectiveStrokeWidth = computed(() => strokeWidth ?? group?.defaults.strokeWidth ?? 1.5);\n const effectiveAnimationDuration = computed(() => animationDuration ?? group?.defaults.animationDuration ?? 500);\n const effectiveIterations = computed(() => iterations ?? group?.defaults.iterations ?? 2);\n const effectivePadding = computed(() => padding ?? group?.defaults.padding ?? 2);\n const effectiveMultiline = computed(() => multiline ?? group?.defaults.multiline ?? true);\n\n let annotation: ReturnType<typeof annotate> | null = null;\n let entry: HighlighterGroupEntry | null = null;\n let observer: ResizeObserver | null = null;\n let settleTimer: number | undefined;\n let shownTimer: number | undefined;\n let revealed = false;\n\n // The annotation type is immutable, so any prop change rebuilds it from scratch.\n watch([effectiveVariant, effectiveColor, effectiveStrokeWidth, effectiveAnimationDuration, effectiveIterations, effectivePadding, effectiveMultiline], () => build());\n\n // Standalone reveal once the element scrolls into view (whenInView).\n watch(inView, () => {\n if (!group) {\n reveal();\n }\n });\n\n onMounted(() => {\n const element = targetRef.value;\n\n if (group && element) {\n entry = {element, getAnnotation: () => annotation};\n group.add(entry);\n }\n\n build();\n });\n\n onBeforeUnmount(() => {\n if (group && entry) {\n group.remove(entry);\n entry = null;\n }\n\n window.clearTimeout(shownTimer);\n stopSettleWatch();\n annotation?.remove();\n annotation = null;\n });\n\n // rough-notation has no completion callback, so shown is emitted after the\n // draw animation's duration, or immediately when it draws without animation.\n function emitShown(): void {\n window.clearTimeout(shownTimer);\n\n if (prefersReducedMotion()) {\n emit('shown');\n return;\n }\n\n shownTimer = window.setTimeout(() => emit('shown'), effectiveAnimationDuration.value);\n }\n\n function show(): void {\n if (!annotation) {\n return;\n }\n\n revealed = true;\n stopSettleWatch();\n annotation.show();\n emitShown();\n }\n\n function hide(): void {\n if (!annotation) {\n return;\n }\n\n window.clearTimeout(shownTimer);\n annotation.hide();\n emit('hidden');\n }\n\n function replay(): void {\n if (!annotation) {\n return;\n }\n\n annotation.hide();\n annotation.show();\n emitShown();\n }\n\n // Standalone: draw once, with the intro animation, on the final geometry.\n // rough-notation keeps the drawn annotation aligned on later resizes itself.\n function reveal(): void {\n if (revealed || !annotation || !inView.value) {\n return;\n }\n\n show();\n }\n\n function stopSettleWatch(): void {\n window.clearTimeout(settleTimer);\n observer?.disconnect();\n observer = null;\n }\n\n function build(): void {\n annotation?.remove();\n annotation = null;\n stopSettleWatch();\n revealed = false;\n\n const element = targetRef.value;\n\n if (!element) {\n return;\n }\n\n annotation = annotate(element, {\n type: effectiveVariant.value,\n color: effectiveColor.value,\n strokeWidth: effectiveStrokeWidth.value,\n animationDuration: effectiveAnimationDuration.value,\n iterations: effectiveIterations.value,\n padding: effectivePadding.value,\n multiline: effectiveMultiline.value,\n animate: !prefersReducedMotion()\n });\n\n // In a group the parent collects the annotations and drives the cascade.\n if (group) {\n group.notify();\n return;\n }\n\n if (typeof ResizeObserver === 'undefined') {\n reveal();\n return;\n }\n\n // The surrounding chrome (preview panels, web-font swaps) can reflow right\n // after mount, which would otherwise play the intro animation where the\n // text sat *before* it settled. Debounce until the layout is quiet, then\n // draw on the final geometry.\n observer = new ResizeObserver(() => {\n window.clearTimeout(settleTimer);\n settleTimer = window.setTimeout(() => reveal(), 80);\n });\n observer.observe(element);\n observer.observe(document.body);\n }\n\n defineExpose({\n hide,\n replay,\n show\n });\n</script>\n","<template>\n <span :class=\"$style.highlighterGroup\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import type { FluxVisualHighlighterGroupProps } from '@flux-ui/types';\n import { useHighlighterGroup } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/Highlighter.module.scss';\n\n const props = defineProps<FluxVisualHighlighterGroupProps>();\n\n defineSlots<{\n default(): any;\n }>();\n\n useHighlighterGroup(props);\n</script>\n","<template>\n <span :class=\"$style.highlighterGroup\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import type { FluxVisualHighlighterGroupProps } from '@flux-ui/types';\n import { useHighlighterGroup } from '~flux/visuals/composable/private';\n import $style from '~flux/visuals/css/component/Highlighter.module.scss';\n\n const props = defineProps<FluxVisualHighlighterGroupProps>();\n\n defineSlots<{\n default(): any;\n }>();\n\n useHighlighterGroup(props);\n</script>\n",".noise {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n opacity: var(--noise-opacity, .05);\n // Raw feTurbulence emits four independent noise channels, so the alpha is\n // also noise and the grain is a faint, half-transparent colored static that\n // washes out to nothing under a low opacity. The filter runs in sRGB (so the\n // grain stays centered on mid-gray instead of being gamma-lifted toward\n // white), collapses to a single opaque gray channel and boosts its contrast,\n // giving a neutral film grain that reads clearly even at a few percent.\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='1.8' intercept='-0.4'/%3E%3CfeFuncG type='linear' slope='1.8' intercept='-0.4'/%3E%3CfeFuncB type='linear' slope='1.8' intercept='-0.4'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E\");\n background-repeat: repeat;\n mix-blend-mode: var(--noise-blend, overlay);\n\n // The overlay blend has little to bite into on a dark surface, so the grain\n // is lifted to stay perceptible.\n [dark] & {\n opacity: calc(var(--noise-opacity, .05) * 1.5);\n }\n}\n\n.animated {\n animation: fluxVisualNoiseShift .6s steps(1) infinite;\n}\n\n// Discrete position jumps read as film grain without a per-frame script.\n@keyframes fluxVisualNoiseShift {\n 0% {\n background-position: 0 0;\n }\n\n 10% {\n background-position: -12px 6px;\n }\n\n 20% {\n background-position: 10px -14px;\n }\n\n 30% {\n background-position: -6px 12px;\n }\n\n 40% {\n background-position: 14px 4px;\n }\n\n 50% {\n background-position: -14px -8px;\n }\n\n 60% {\n background-position: 8px 14px;\n }\n\n 70% {\n background-position: -10px -12px;\n }\n\n 80% {\n background-position: 12px 8px;\n }\n\n 90% {\n background-position: -4px -14px;\n }\n\n 100% {\n background-position: 0 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .animated {\n animation: none;\n }\n}\n","<template>\n <div\n aria-hidden=\"true\"\n :class=\"clsx($style.noise, animated && $style.animated)\"\n :style=\"style\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { clsx } from 'clsx';\n import { computed } from 'vue';\n import $style from '~flux/visuals/css/component/Noise.module.scss';\n\n type NoiseBlendMode = 'normal' | 'multiply' | 'screen' | 'overlay' | 'soft-light' | 'plus-lighter';\n\n const {\n animated = false,\n blend = 'overlay',\n opacity = 0.05\n } = defineProps<{\n readonly animated?: boolean;\n readonly blend?: NoiseBlendMode;\n readonly opacity?: number;\n }>();\n\n const style = computed(() => ({\n '--noise-blend': blend,\n '--noise-opacity': opacity\n }));\n</script>\n","<template>\n <div\n aria-hidden=\"true\"\n :class=\"clsx($style.noise, animated && $style.animated)\"\n :style=\"style\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { clsx } from 'clsx';\n import { computed } from 'vue';\n import $style from '~flux/visuals/css/component/Noise.module.scss';\n\n type NoiseBlendMode = 'normal' | 'multiply' | 'screen' | 'overlay' | 'soft-light' | 'plus-lighter';\n\n const {\n animated = false,\n blend = 'overlay',\n opacity = 0.05\n } = defineProps<{\n readonly animated?: boolean;\n readonly blend?: NoiseBlendMode;\n readonly opacity?: number;\n }>();\n\n const style = computed(() => ({\n '--noise-blend': blend,\n '--noise-opacity': opacity\n }));\n</script>\n",".numberFlow {\n font-variant-numeric: tabular-nums;\n display: inline-block;\n white-space: nowrap;\n}\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"accessibleValue\"\n :class=\"$style.numberFlow\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { computed, onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/NumberFlow.module.scss';\n\n type NumberFlowEasingKeyword = 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';\n\n type NumberFlowEasingFunction = (t: number) => number;\n\n type NumberFlowEasing = NumberFlowEasingKeyword | `cubic-bezier(${string})` | NumberFlowEasingFunction;\n\n const {\n value,\n animateOnMount = true,\n duration = 800,\n // The default matches the --swift-out custom property from @flux-ui/components\n // (packages/components/src/css/variables.scss). Keep these control points in\n // sync with that variable so the tween matches the rest of the design system.\n easing = 'cubic-bezier(0.55, 0, 0.1, 1)',\n format,\n locale\n } = defineProps<{\n readonly value: number;\n readonly animateOnMount?: boolean;\n readonly duration?: number;\n readonly easing?: NumberFlowEasing;\n readonly format?: Intl.NumberFormatOptions;\n readonly locale?: string;\n }>();\n\n // Named easing curves for the rAF tween. A CSS easing string cannot be\n // sampled per frame, so the value tween uses these functions instead.\n const EASINGS: Record<NumberFlowEasingKeyword, NumberFlowEasingFunction> = {\n 'linear': progress => progress,\n 'ease-in': progress => progress * progress,\n 'ease-out': progress => 1 - (1 - progress) * (1 - progress),\n 'ease-in-out': progress => progress < .5 ? 2 * progress * progress : 1 - ((-2 * progress + 2) ** 2) / 2\n };\n\n const CUBIC_BEZIER_PATTERN = /^cubic-bezier\\(\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*\\)$/;\n\n // Fallback for an unrecognized easing value, matching the --swift-out default.\n const swiftOutEasing = cubicBezier(0.55, 0, 0.1, 1);\n\n const labelRef = useTemplateRef('label');\n\n let frame = 0;\n let current = animateOnMount ? 0 : value;\n\n // Resolve the easing prop to a plain (t) => number function the tween can\n // sample: functions pass through, keywords map to the table above and a\n // cubic-bezier(...) string is parsed into a solver, falling back to swift-out.\n const easingFunction = computed<NumberFlowEasingFunction>(() => {\n if (typeof easing === 'function') {\n return easing;\n }\n\n const keyword = EASINGS[easing as NumberFlowEasingKeyword];\n\n if (keyword) {\n return keyword;\n }\n\n return parseCubicBezier(easing) ?? swiftOutEasing;\n });\n\n // Default to whole numbers so a mid-tween value never sprouts stray decimals.\n // Currency, percentage and fractional displays opt in through `format`.\n const formatter = computed(() => new Intl.NumberFormat(locale, format ?? {maximumFractionDigits: 0}));\n\n // Rendered once for SSR / first paint only. The engine owns the span's text\n // after mount, so this must NOT be reactive - a reactive {{ value }} would make\n // Vue re-patch the text node every frame and wipe the tween. The accessible\n // name stays current through the reactive :aria-label binding.\n const initialText = formatter.value.format(animateOnMount ? 0 : value);\n\n // Screen readers always read the final, settled value rather than the\n // intermediate frames streaming past on screen.\n const accessibleValue = computed(() => formatter.value.format(value));\n\n // Tween from wherever the display currently sits, so a value that changes\n // mid-tween keeps rolling smoothly instead of jumping.\n watch(() => value, next => tween(current, next));\n\n // Re-render in place when the locale or format changes.\n watch(formatter, () => render(current));\n\n onMounted(() => {\n if (animateOnMount) {\n tween(0, value);\n } else {\n render(value);\n }\n });\n\n onBeforeUnmount(cancel);\n\n // Evaluate a cubic-bezier(x1, y1, x2, y2) timing function in JS. The control\n // points describe x(t) and y(t); for a given progress we need y at the t where\n // x(t) === progress. x is solved with Newton-Raphson and a bisection fallback,\n // mirroring the standard UnitBezier approach browsers use for CSS easings.\n function cubicBezier(x1: number, y1: number, x2: number, y2: number): NumberFlowEasingFunction {\n const ax = 3 * x1 - 3 * x2 + 1;\n const bx = 3 * x2 - 6 * x1;\n const cx = 3 * x1;\n\n const ay = 3 * y1 - 3 * y2 + 1;\n const by = 3 * y2 - 6 * y1;\n const cy = 3 * y1;\n\n const sampleX = (t: number): number => ((ax * t + bx) * t + cx) * t;\n const sampleY = (t: number): number => ((ay * t + by) * t + cy) * t;\n const slopeX = (t: number): number => (3 * ax * t + 2 * bx) * t + cx;\n\n const solveX = (x: number): number => {\n let t = x;\n\n for (let i = 0; i < 8; ++i) {\n const error = sampleX(t) - x;\n\n if (Math.abs(error) < 1e-6) {\n return t;\n }\n\n const slope = slopeX(t);\n\n if (Math.abs(slope) < 1e-6) {\n break;\n }\n\n t -= error / slope;\n }\n\n let low = 0;\n let high = 1;\n t = x;\n\n for (let i = 0; i < 20; ++i) {\n const estimate = sampleX(t);\n\n if (Math.abs(estimate - x) < 1e-6) {\n return t;\n }\n\n if (estimate < x) {\n low = t;\n } else {\n high = t;\n }\n\n t = (low + high) / 2;\n }\n\n return t;\n };\n\n return progress => {\n if (progress <= 0) {\n return 0;\n }\n\n if (progress >= 1) {\n return 1;\n }\n\n return sampleY(solveX(progress));\n };\n }\n\n // Parse a cubic-bezier(...) string into a solver, or return null when the\n // string is malformed so the caller can fall back to the default easing.\n function parseCubicBezier(input: string): NumberFlowEasingFunction | null {\n const match = CUBIC_BEZIER_PATTERN.exec(input.trim());\n\n if (!match) {\n return null;\n }\n\n return cubicBezier(Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4]));\n }\n\n function render(next: number): void {\n current = next;\n\n const element = labelRef.value;\n\n if (element) {\n element.textContent = formatter.value.format(next);\n }\n }\n\n function cancel(): void {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n }\n\n function tween(from: number, to: number): void {\n cancel();\n\n // Reduced motion, no distance or no duration: snap straight to the value.\n if (from === to || duration <= 0 || prefersReducedMotion()) {\n render(to);\n return;\n }\n\n const ease = easingFunction.value;\n const delta = to - from;\n const startTime = performance.now();\n\n const step = (now: number): void => {\n const progress = Math.min(1, (now - startTime) / duration);\n render(from + delta * ease(progress));\n\n if (progress < 1) {\n frame = requestAnimationFrame(step);\n } else {\n frame = 0;\n render(to);\n }\n };\n\n frame = requestAnimationFrame(step);\n }\n</script>\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"accessibleValue\"\n :class=\"$style.numberFlow\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { computed, onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/NumberFlow.module.scss';\n\n type NumberFlowEasingKeyword = 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';\n\n type NumberFlowEasingFunction = (t: number) => number;\n\n type NumberFlowEasing = NumberFlowEasingKeyword | `cubic-bezier(${string})` | NumberFlowEasingFunction;\n\n const {\n value,\n animateOnMount = true,\n duration = 800,\n // The default matches the --swift-out custom property from @flux-ui/components\n // (packages/components/src/css/variables.scss). Keep these control points in\n // sync with that variable so the tween matches the rest of the design system.\n easing = 'cubic-bezier(0.55, 0, 0.1, 1)',\n format,\n locale\n } = defineProps<{\n readonly value: number;\n readonly animateOnMount?: boolean;\n readonly duration?: number;\n readonly easing?: NumberFlowEasing;\n readonly format?: Intl.NumberFormatOptions;\n readonly locale?: string;\n }>();\n\n // Named easing curves for the rAF tween. A CSS easing string cannot be\n // sampled per frame, so the value tween uses these functions instead.\n const EASINGS: Record<NumberFlowEasingKeyword, NumberFlowEasingFunction> = {\n 'linear': progress => progress,\n 'ease-in': progress => progress * progress,\n 'ease-out': progress => 1 - (1 - progress) * (1 - progress),\n 'ease-in-out': progress => progress < .5 ? 2 * progress * progress : 1 - ((-2 * progress + 2) ** 2) / 2\n };\n\n const CUBIC_BEZIER_PATTERN = /^cubic-bezier\\(\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*,\\s*([-\\d.]+)\\s*\\)$/;\n\n // Fallback for an unrecognized easing value, matching the --swift-out default.\n const swiftOutEasing = cubicBezier(0.55, 0, 0.1, 1);\n\n const labelRef = useTemplateRef('label');\n\n let frame = 0;\n let current = animateOnMount ? 0 : value;\n\n // Resolve the easing prop to a plain (t) => number function the tween can\n // sample: functions pass through, keywords map to the table above and a\n // cubic-bezier(...) string is parsed into a solver, falling back to swift-out.\n const easingFunction = computed<NumberFlowEasingFunction>(() => {\n if (typeof easing === 'function') {\n return easing;\n }\n\n const keyword = EASINGS[easing as NumberFlowEasingKeyword];\n\n if (keyword) {\n return keyword;\n }\n\n return parseCubicBezier(easing) ?? swiftOutEasing;\n });\n\n // Default to whole numbers so a mid-tween value never sprouts stray decimals.\n // Currency, percentage and fractional displays opt in through `format`.\n const formatter = computed(() => new Intl.NumberFormat(locale, format ?? {maximumFractionDigits: 0}));\n\n // Rendered once for SSR / first paint only. The engine owns the span's text\n // after mount, so this must NOT be reactive - a reactive {{ value }} would make\n // Vue re-patch the text node every frame and wipe the tween. The accessible\n // name stays current through the reactive :aria-label binding.\n const initialText = formatter.value.format(animateOnMount ? 0 : value);\n\n // Screen readers always read the final, settled value rather than the\n // intermediate frames streaming past on screen.\n const accessibleValue = computed(() => formatter.value.format(value));\n\n // Tween from wherever the display currently sits, so a value that changes\n // mid-tween keeps rolling smoothly instead of jumping.\n watch(() => value, next => tween(current, next));\n\n // Re-render in place when the locale or format changes.\n watch(formatter, () => render(current));\n\n onMounted(() => {\n if (animateOnMount) {\n tween(0, value);\n } else {\n render(value);\n }\n });\n\n onBeforeUnmount(cancel);\n\n // Evaluate a cubic-bezier(x1, y1, x2, y2) timing function in JS. The control\n // points describe x(t) and y(t); for a given progress we need y at the t where\n // x(t) === progress. x is solved with Newton-Raphson and a bisection fallback,\n // mirroring the standard UnitBezier approach browsers use for CSS easings.\n function cubicBezier(x1: number, y1: number, x2: number, y2: number): NumberFlowEasingFunction {\n const ax = 3 * x1 - 3 * x2 + 1;\n const bx = 3 * x2 - 6 * x1;\n const cx = 3 * x1;\n\n const ay = 3 * y1 - 3 * y2 + 1;\n const by = 3 * y2 - 6 * y1;\n const cy = 3 * y1;\n\n const sampleX = (t: number): number => ((ax * t + bx) * t + cx) * t;\n const sampleY = (t: number): number => ((ay * t + by) * t + cy) * t;\n const slopeX = (t: number): number => (3 * ax * t + 2 * bx) * t + cx;\n\n const solveX = (x: number): number => {\n let t = x;\n\n for (let i = 0; i < 8; ++i) {\n const error = sampleX(t) - x;\n\n if (Math.abs(error) < 1e-6) {\n return t;\n }\n\n const slope = slopeX(t);\n\n if (Math.abs(slope) < 1e-6) {\n break;\n }\n\n t -= error / slope;\n }\n\n let low = 0;\n let high = 1;\n t = x;\n\n for (let i = 0; i < 20; ++i) {\n const estimate = sampleX(t);\n\n if (Math.abs(estimate - x) < 1e-6) {\n return t;\n }\n\n if (estimate < x) {\n low = t;\n } else {\n high = t;\n }\n\n t = (low + high) / 2;\n }\n\n return t;\n };\n\n return progress => {\n if (progress <= 0) {\n return 0;\n }\n\n if (progress >= 1) {\n return 1;\n }\n\n return sampleY(solveX(progress));\n };\n }\n\n // Parse a cubic-bezier(...) string into a solver, or return null when the\n // string is malformed so the caller can fall back to the default easing.\n function parseCubicBezier(input: string): NumberFlowEasingFunction | null {\n const match = CUBIC_BEZIER_PATTERN.exec(input.trim());\n\n if (!match) {\n return null;\n }\n\n return cubicBezier(Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4]));\n }\n\n function render(next: number): void {\n current = next;\n\n const element = labelRef.value;\n\n if (element) {\n element.textContent = formatter.value.format(next);\n }\n }\n\n function cancel(): void {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n }\n\n function tween(from: number, to: number): void {\n cancel();\n\n // Reduced motion, no distance or no duration: snap straight to the value.\n if (from === to || duration <= 0 || prefersReducedMotion()) {\n render(to);\n return;\n }\n\n const ease = easingFunction.value;\n const delta = to - from;\n const startTime = performance.now();\n\n const step = (now: number): void => {\n const progress = Math.min(1, (now - startTime) / duration);\n render(from + delta * ease(progress));\n\n if (progress < 1) {\n frame = requestAnimationFrame(step);\n } else {\n frame = 0;\n render(to);\n }\n };\n\n frame = requestAnimationFrame(step);\n }\n</script>\n",".paneIllustration {\n --mask: linear-gradient(to bottom, black, transparent);\n --mask-content: linear-gradient(to bottom, black, rgb(0 0 0 / .75), transparent);\n\n position: relative;\n border-radius: calc(var(--radius) - 1px);\n\n &:not(:first-child) {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n }\n\n &:not(:last-child) {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n }\n}\n\n.paneIllustrationContent {\n position: relative;\n display: flex;\n height: 100%;\n align-items: center;\n justify-content: center;\n}\n\n.paneIllustrationContentControlled {\n composes: paneIllustrationContent;\n\n overflow: hidden;\n\n -webkit-mask-image: var(--mask-content);\n mask-image: var(--mask-content);\n}\n\n.paneIllustrationMagic {\n position: absolute;\n inset: -1px;\n border-radius: inherit;\n}\n\n.paneIllustrationMasked {\n composes: paneIllustration;\n\n .paneIllustrationMagic {\n -webkit-mask-image: var(--mask);\n mask-image: var(--mask);\n }\n}\n","<template>\n <div\n data-flux-pane-illustration\n :class=\"isMasked ? $style.paneIllustrationMasked : $style.paneIllustration\"\n :style=\"{\n aspectRatio\n }\">\n <div\n :class=\"$style.paneIllustrationMagic\"\n :style=\"{\n border: `1px solid ${borderColor}`\n }\">\n <FluxVisualGridPattern :stroke-dasharray=\"3\"/>\n\n <FluxVisualAnimatedColors\n :colors=\"animatedColors\"\n :opacity=\"animatedOpacity\"\n :seed=\"animatedSeed\"/>\n </div>\n\n <div\n v-if=\"slots.controlled\"\n :class=\"$style.paneIllustrationContentControlled\">\n <slot name=\"controlled\"/>\n </div>\n\n <div\n v-if=\"slots.default\"\n :class=\"$style.paneIllustrationContent\">\n <slot/>\n </div>\n </div>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { hexToRGB } from '@basmilius/utils';\n import { computed, type VNode } from 'vue';\n import FluxVisualAnimatedColors from './FluxVisualAnimatedColors.vue';\n import FluxVisualGridPattern from './FluxVisualGridPattern.vue';\n import $style from '~flux/visuals/css/component/PaneIllustration.module.scss';\n\n const {\n animatedColors,\n aspectRatio = 16 / 9\n } = defineProps<{\n readonly animatedColors: string[];\n readonly animatedOpacity?: number;\n readonly animatedSeed?: number;\n readonly aspectRatio?: number;\n readonly isMasked?: boolean;\n }>();\n\n const slots = defineSlots<{\n default?(): VNode[];\n controlled?(): VNode[];\n }>();\n\n const borderColor = computed(() => {\n if (!animatedColors || animatedColors.length === 0) {\n return 'transparent';\n }\n\n const [r, g, b] = hexToRGB(animatedColors[0]);\n\n return `rgb(${r} ${g} ${b} / .15)`;\n });\n</script>\n","<template>\n <div\n data-flux-pane-illustration\n :class=\"isMasked ? $style.paneIllustrationMasked : $style.paneIllustration\"\n :style=\"{\n aspectRatio\n }\">\n <div\n :class=\"$style.paneIllustrationMagic\"\n :style=\"{\n border: `1px solid ${borderColor}`\n }\">\n <FluxVisualGridPattern :stroke-dasharray=\"3\"/>\n\n <FluxVisualAnimatedColors\n :colors=\"animatedColors\"\n :opacity=\"animatedOpacity\"\n :seed=\"animatedSeed\"/>\n </div>\n\n <div\n v-if=\"slots.controlled\"\n :class=\"$style.paneIllustrationContentControlled\">\n <slot name=\"controlled\"/>\n </div>\n\n <div\n v-if=\"slots.default\"\n :class=\"$style.paneIllustrationContent\">\n <slot/>\n </div>\n </div>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { hexToRGB } from '@basmilius/utils';\n import { computed, type VNode } from 'vue';\n import FluxVisualAnimatedColors from './FluxVisualAnimatedColors.vue';\n import FluxVisualGridPattern from './FluxVisualGridPattern.vue';\n import $style from '~flux/visuals/css/component/PaneIllustration.module.scss';\n\n const {\n animatedColors,\n aspectRatio = 16 / 9\n } = defineProps<{\n readonly animatedColors: string[];\n readonly animatedOpacity?: number;\n readonly animatedSeed?: number;\n readonly aspectRatio?: number;\n readonly isMasked?: boolean;\n }>();\n\n const slots = defineSlots<{\n default?(): VNode[];\n controlled?(): VNode[];\n }>();\n\n const borderColor = computed(() => {\n if (!animatedColors || animatedColors.length === 0) {\n return 'transparent';\n }\n\n const [r, g, b] = hexToRGB(animatedColors[0]);\n\n return `rgb(${r} ${g} ${b} / .15)`;\n });\n</script>\n",".ping {\n position: relative;\n display: inline-block;\n width: var(--ping-size);\n height: var(--ping-size);\n pointer-events: none;\n color: var(--ping-color);\n border-radius: 50%;\n background: currentColor;\n}\n\n.ping::before,\n.ping::after {\n position: absolute;\n inset: 0;\n content: '';\n animation: visualPing calc(var(--ping-duration) * 1s) cubic-bezier(0, 0, .2, 1) infinite;\n border-radius: 50%;\n background: currentColor;\n}\n\n.ping::after {\n animation-delay: calc(var(--ping-duration) * -.5s);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ping::before,\n .ping::after {\n content: none;\n }\n}\n\n@keyframes visualPing {\n 0% {\n transform: scale(1);\n opacity: .5;\n }\n\n 75%,\n 100% {\n transform: scale(2.5);\n opacity: 0;\n }\n}\n","<template>\n <span\n aria-hidden=\"true\"\n :class=\"$style.ping\"\n :style=\"{\n '--ping-color': `var(--${color}-solid)`,\n '--ping-size': `${size}px`,\n '--ping-duration': duration\n }\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import $style from '~flux/visuals/css/component/Ping.module.scss';\n\n const {\n color = 'success',\n size = 9,\n duration = 1.4\n } = defineProps<{\n readonly color?: 'gray' | 'primary' | 'danger' | 'info' | 'success' | 'warning';\n readonly size?: number;\n readonly duration?: number;\n }>();\n</script>\n","<template>\n <span\n aria-hidden=\"true\"\n :class=\"$style.ping\"\n :style=\"{\n '--ping-color': `var(--${color}-solid)`,\n '--ping-size': `${size}px`,\n '--ping-duration': duration\n }\"/>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import $style from '~flux/visuals/css/component/Ping.module.scss';\n\n const {\n color = 'success',\n size = 9,\n duration = 1.4\n } = defineProps<{\n readonly color?: 'gray' | 'primary' | 'danger' | 'info' | 'success' | 'warning';\n readonly size?: number;\n readonly duration?: number;\n }>();\n</script>\n",".slotText {\n display: inline-flex;\n white-space: pre;\n}\n\n.charSlot {\n line-height: 1.3;\n position: relative;\n display: inline-flex;\n // Clip only vertically: the roll needs a top/bottom mask, but glyph side\n // bearings, kerning overhang and the settle tilt must stay visible so\n // letters never look cropped.\n overflow: hidden;\n overflow-x: visible;\n overflow-y: clip;\n // Cells must never flex-shrink, otherwise a width-constrained line crushes\n // them into overlapping slivers instead of letting the row overflow.\n flex: none;\n justify-content: center;\n vertical-align: bottom;\n}\n\n// Cells appearing from or collapsing to empty change width drastically, so clip\n// them horizontally too while they resize — their glyph wipes in/out with the\n// cell instead of spilling over the neighbors.\n.charSlot.isResizing {\n overflow-x: clip;\n}\n\n.charSizer {\n visibility: hidden;\n white-space: pre;\n}\n\n.charFace {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n white-space: pre;\n will-change: transform;\n}\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"text\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/SlotText.module.scss';\n\n type SlotTextColor = string | ((index: number, total: number) => string);\n type SlotTextDirection = 'up' | 'down';\n\n type AnimateOptions = {\n direction?: SlotTextDirection;\n stagger?: number;\n duration?: number;\n exitOffset?: number;\n easing?: string;\n bounce?: number;\n color?: SlotTextColor;\n colorFade?: number;\n skipUnchanged?: boolean;\n interrupt?: boolean;\n };\n\n type FlashOptions = {\n revertAfter?: number;\n enter?: AnimateOptions;\n exit?: AnimateOptions;\n };\n\n type SlotState = {\n timers: number[];\n target: string;\n pending?: { text: string; options: AnimateOptions; };\n };\n\n const {\n text,\n bounce = .6,\n chromatic = false,\n color,\n colorFade = 280,\n direction = 'down',\n duration = 300,\n easing = 'cubic-bezier(0.34, 1.56, 0.64, 1)',\n exitOffset = 50,\n interrupt = true,\n skipUnchanged = true,\n stagger = 45\n } = defineProps<{\n readonly text: string;\n readonly bounce?: number;\n readonly chromatic?: boolean;\n readonly color?: string;\n readonly colorFade?: number;\n readonly direction?: SlotTextDirection;\n readonly duration?: number;\n readonly easing?: string;\n readonly exitOffset?: number;\n readonly interrupt?: boolean;\n readonly skipUnchanged?: boolean;\n readonly stagger?: number;\n }>();\n\n const NBSP = '\\u00A0';\n\n // Rendered once for SSR / first paint only. The engine owns the span's DOM\n // after mount, so this must NOT be reactive - a reactive {{ text }} would make\n // Vue re-run setElementText on every change and wipe the animated glyph cells.\n // The accessible name stays current through the reactive :aria-label binding.\n const initialText = text;\n\n const labelRef = useTemplateRef('label');\n\n // Per-instance record of the in-flight roll, so a new roll can interrupt it.\n let state: SlotState | null = null;\n let revertTimeout: number | undefined;\n let restingText: string | undefined;\n\n watch(() => text, value => set(value));\n\n onMounted(() => {\n const element = labelRef.value;\n\n if (element) {\n buildSlotText(element, text);\n }\n });\n\n onBeforeUnmount(() => {\n window.clearTimeout(revertTimeout);\n\n const element = labelRef.value;\n\n if (element) {\n clearSlotText(element, text);\n }\n });\n\n function glyph(char: string): string {\n return char === ' ' ? NBSP : char;\n }\n\n // Sweep the hue across the line so the roll lands as a chromatic spectrum.\n function chromaticColor(index: number, total: number): string {\n const t = total <= 1 ? 0 : index / (total - 1);\n return `hsl(${(t * 320) % 360} 92% 60%)`;\n }\n\n function baseOptions(): AnimateOptions {\n return {\n direction,\n stagger,\n duration,\n exitOffset,\n easing,\n bounce,\n color: chromatic ? chromaticColor : color,\n colorFade,\n skipUnchanged,\n interrupt\n };\n }\n\n function makeFace(char: string): HTMLSpanElement {\n const face = document.createElement('span');\n face.className = $style.charFace;\n face.textContent = glyph(char);\n return face;\n }\n\n function buildSlot(char: string): HTMLSpanElement {\n const slot = document.createElement('span');\n slot.className = $style.charSlot;\n slot.dataset.char = char;\n\n // Invisible sizer keeps the cell exactly the width/height of its glyph,\n // so the absolutely-positioned animating faces never reflow the line.\n const sizer = document.createElement('span');\n sizer.className = $style.charSizer;\n sizer.textContent = glyph(char);\n\n slot.append(sizer, makeFace(char));\n return slot;\n }\n\n function buildSlotText(container: HTMLElement, value: string): void {\n container.classList.add($style.slotText);\n container.replaceChildren(...Array.from(value, buildSlot));\n }\n\n // Cancel any running roll on the container and snap it to its target text.\n function settle(container: HTMLElement): void {\n if (!state) {\n return;\n }\n\n state.timers.forEach(timer => window.clearTimeout(timer));\n\n // Rebuild a pristine DOM at the text the interrupted roll was heading\n // toward, so the next animation starts from a clean baseline.\n const target = state.target;\n state = null;\n buildSlotText(container, target);\n }\n\n function animateSlotText(container: HTMLElement, toText: string, options: AnimateOptions = {}): void {\n const {\n direction = 'down',\n stagger = 45,\n duration = 300,\n exitOffset = 50,\n easing = 'cubic-bezier(0.34, 1.56, 0.64, 1)',\n bounce = .6,\n color,\n colorFade = 280,\n skipUnchanged = true,\n interrupt = true\n } = options;\n\n // Reduced motion: swap to the new text without rolling.\n if (prefersReducedMotion()) {\n buildSlotText(container, toText);\n return;\n }\n\n // Non-interrupting mode: if a roll is already in flight, let it finish\n // and remember this request instead. Only the latest request survives,\n // so spam taps coalesce into a single follow-up roll once it lands.\n if (state && !interrupt) {\n if (toText !== state.target) {\n state.pending = {text: toText, options};\n }\n return;\n }\n\n // Interrupt: fast-forward any previous roll to its target and tear down\n // its timers before we start fresh.\n settle(container);\n\n // First run / empty container → just build it.\n if (!container.querySelector(`.${$style.charSlot}`)) {\n buildSlotText(container, toText);\n return;\n }\n\n const slots = Array.from(container.querySelectorAll<HTMLElement>(`.${$style.charSlot}`));\n const fromText = slots.map(slot => slot.dataset.char ?? '').join('');\n\n // Non-interrupting mode drops rolls to the text already on screen, so\n // repeated triggers do not visibly re-roll an unchanged label.\n if (!interrupt && fromText === toText) {\n return;\n }\n\n const maxLen = Math.max(fromText.length, toText.length);\n\n // Whole-pixel slide distance = one cell height, so glyphs clip cleanly.\n // Ceil, not round: half a pixel short leaves a sliver of the outgoing\n // glyph visible at the clip edge.\n const sample = slots.find(slot => (slot.dataset.char ?? '') !== '') ?? slots[0];\n const cs = getComputedStyle(container);\n const H = Math.ceil(\n sample?.getBoundingClientRect().height\n || sample?.offsetHeight\n || container.getBoundingClientRect().height\n || parseFloat(cs.lineHeight)\n || 0\n ) || Math.ceil(parseFloat(cs.fontSize) * 1.3) || 18;\n\n // Resting color to settle the chromatic flash back to.\n const restColor = color ? cs.color : '';\n\n // Pre-create any extra cells up front so the row never reflows mid-roll.\n for (let i = slots.length; i < maxLen; i++) {\n const slot = buildSlot('');\n container.appendChild(slot);\n slots.push(slot);\n }\n\n const timers: number[] = [];\n state = {timers, target: toText};\n\n // down: new enters from above (-H to 0), old exits below (0 to +H)\n // up: new enters from below (+H to 0), old exits above (0 to -H)\n const outY = direction === 'down' ? H : -H;\n const inStart = direction === 'down' ? -H : H;\n\n // A tiny deterministic jitter in [-1, 1] per character. Scaled by\n // `bounce` it gives each glyph its own speed and a little tilt-wobble,\n // so the line does not land as one rigid block.\n const wobble = (index: number, salt: number): number => {\n const n = Math.sin((index + 1) * 12.9898 + salt * 78.233) * 43758.5453;\n return (n - Math.floor(n)) * 2 - 1;\n };\n\n // Track the slowest letter so the safety-net snap waits for everyone.\n let maxEnd = 0;\n\n for (let i = 0; i < maxLen; i++) {\n const fromChar = fromText[i] || '';\n const toChar = toText[i] || '';\n\n if (fromChar === toChar && (skipUnchanged || fromChar === '')) {\n continue;\n }\n\n const slot = slots[i];\n const sizer = slot.querySelector<HTMLElement>(`.${$style.charSizer}`)!;\n const oldFace = slot.querySelector<HTMLElement>(`.${$style.charFace}`);\n\n // Resize the cell to the new glyph — but ease the width instead of\n // snapping it, so a wide outgoing glyph is never cropped by a\n // suddenly-narrow cell and neighbors glide rather than jump.\n const oldW = slot.getBoundingClientRect().width;\n sizer.textContent = glyph(toChar);\n const newW = sizer.getBoundingClientRect().width;\n const widthChanges = Math.abs(newW - oldW) > .5;\n\n if (widthChanges) {\n slot.style.width = `${oldW}px`;\n }\n\n // A cell growing from or collapsing to empty changes width\n // drastically — clip it horizontally while it resizes so its glyph\n // wipes in/out with the cell instead of stacking onto the neighbors.\n if (fromChar === '' || toChar === '') {\n slot.classList.add($style.isResizing);\n }\n\n const tint = typeof color === 'function' ? color(i, maxLen) : color;\n\n // Per-letter personality: vary the speed, the stagger and a starting\n // tilt that springs back to upright as the glyph settles. Tail cells\n // (rolling out to nothing) join the same wave instead of queuing\n // behind it, so nothing trails.\n const isTail = toChar === '';\n const d = Math.round(duration * (isTail ? .75 : 1) * (1 + bounce * .45 * wobble(i, 1)));\n const staggerIndex = isTail ? toText.length * .5 + (i - toText.length) * .25 : i;\n const base = Math.round(staggerIndex * stagger * (1 + bounce * .25 * wobble(i, 2)));\n const tilt = (bounce * 5 * wobble(i, 3)).toFixed(2);\n\n const rollTrans = `transform ${d}ms ${easing}`;\n const trans = color ? `${rollTrans}, color ${colorFade}ms linear ${d}ms` : rollTrans;\n\n const newFace = makeFace(toChar);\n newFace.style.transformOrigin = '50% 50%';\n newFace.style.transform = `translateY(${inStart}px) rotate(${tilt}deg)`;\n\n if (tint) {\n newFace.style.color = tint;\n }\n\n slot.appendChild(newFace);\n\n void slot.offsetWidth; // commit start transforms\n\n // Glide the cell to its new width with a clean ease-out (no\n // overshoot) so it never pinches narrower than either glyph. Timing\n // depends on the kind of change:\n // - glyph → glyph: resize alongside the roll.\n // - glyph → empty: roll out at full width first, then snap closed.\n // - empty → glyph: open the cell quickly before the glyph rolls in.\n if (widthChanges) {\n let wDelay = base;\n let wDur = d;\n\n if (isTail) {\n wDelay = base + Math.round(d * .55);\n wDur = Math.max(140, Math.round(d * .6));\n } else if (fromChar === '') {\n wDur = Math.max(140, Math.round(d * .45));\n }\n\n timers.push(window.setTimeout(() => {\n slot.style.transition = `width ${wDur}ms cubic-bezier(0.2, 0, 0, 1)`;\n slot.style.width = `${newW}px`;\n }, wDelay));\n\n maxEnd = Math.max(maxEnd, wDelay + wDur);\n }\n\n maxEnd = Math.max(maxEnd, base + exitOffset + d + (color ? colorFade : 0));\n\n // Outgoing glyph slides away first (with its own little counter-tilt).\n if (oldFace) {\n timers.push(window.setTimeout(() => {\n oldFace.style.transition = rollTrans;\n oldFace.style.transform = `translateY(${outY}px) rotate(${-Number(tilt)}deg)`;\n }, base));\n }\n\n // Incoming glyph chases it in (and, if tinted, fades to rest after).\n timers.push(window.setTimeout(() => {\n newFace.style.transition = trans;\n newFace.style.transform = 'translateY(0) rotate(0deg)';\n\n if (color) {\n newFace.style.color = restColor;\n }\n\n const done = (event: TransitionEvent): void => {\n if (event.propertyName !== 'transform') {\n return; // ignore the color fade\n }\n\n newFace.removeEventListener('transitionend', done);\n slot.dataset.char = toChar;\n // Hand sizing back to the sizer (same px, nothing moves).\n slot.style.removeProperty('transition');\n slot.style.removeProperty('width');\n slot.classList.remove($style.isResizing);\n slot.querySelectorAll(`.${$style.charFace}`).forEach(face => {\n if (face !== newFace) {\n face.remove();\n }\n });\n };\n\n newFace.addEventListener('transitionend', done);\n }, base + exitOffset));\n }\n\n // Safety net: snap to a pristine DOM once the slowest letter settles. If\n // a non-interrupting call was deferred mid-roll, replay it now as a fresh\n // roll from this clean baseline.\n const total = maxEnd + 80;\n timers.push(window.setTimeout(() => {\n const pending = state?.pending;\n state = null;\n buildSlotText(container, toText);\n\n if (pending) {\n animateSlotText(container, pending.text, pending.options);\n }\n }, total));\n }\n\n function clearSlotText(container: HTMLElement, value = ''): void {\n settle(container);\n container.classList.remove($style.slotText);\n container.textContent = value;\n }\n\n // Roll to new text. Cancels any pending flash revert.\n function set(toText: string, options: AnimateOptions = {}): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n window.clearTimeout(revertTimeout);\n restingText = undefined;\n animateSlotText(element, toText, {...baseOptions(), ...options});\n }\n\n // Roll to temporary text, then roll back automatically — the classic\n // Copy → Copied → Copy in one call. Spam-safe: repeat flashes restart the\n // revert timer instead of queuing extra rolls.\n function flash(toText: string, {revertAfter = 1400, enter, exit}: FlashOptions = {}): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n // Capture the resting text only on the first flash of a burst, so a\n // flash-during-flash still reverts to the original label.\n if (restingText === undefined) {\n restingText = text;\n }\n\n animateSlotText(element, toText, {...baseOptions(), interrupt: false, ...enter});\n\n window.clearTimeout(revertTimeout);\n revertTimeout = window.setTimeout(() => {\n const back = restingText!;\n restingText = undefined;\n revertTimeout = undefined;\n\n const current = labelRef.value;\n\n if (current) {\n animateSlotText(current, back, {...baseOptions(), interrupt: false, ...exit});\n }\n }, revertAfter);\n }\n\n defineExpose({\n flash,\n set\n });\n</script>\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"text\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/SlotText.module.scss';\n\n type SlotTextColor = string | ((index: number, total: number) => string);\n type SlotTextDirection = 'up' | 'down';\n\n type AnimateOptions = {\n direction?: SlotTextDirection;\n stagger?: number;\n duration?: number;\n exitOffset?: number;\n easing?: string;\n bounce?: number;\n color?: SlotTextColor;\n colorFade?: number;\n skipUnchanged?: boolean;\n interrupt?: boolean;\n };\n\n type FlashOptions = {\n revertAfter?: number;\n enter?: AnimateOptions;\n exit?: AnimateOptions;\n };\n\n type SlotState = {\n timers: number[];\n target: string;\n pending?: { text: string; options: AnimateOptions; };\n };\n\n const {\n text,\n bounce = .6,\n chromatic = false,\n color,\n colorFade = 280,\n direction = 'down',\n duration = 300,\n easing = 'cubic-bezier(0.34, 1.56, 0.64, 1)',\n exitOffset = 50,\n interrupt = true,\n skipUnchanged = true,\n stagger = 45\n } = defineProps<{\n readonly text: string;\n readonly bounce?: number;\n readonly chromatic?: boolean;\n readonly color?: string;\n readonly colorFade?: number;\n readonly direction?: SlotTextDirection;\n readonly duration?: number;\n readonly easing?: string;\n readonly exitOffset?: number;\n readonly interrupt?: boolean;\n readonly skipUnchanged?: boolean;\n readonly stagger?: number;\n }>();\n\n const NBSP = '\\u00A0';\n\n // Rendered once for SSR / first paint only. The engine owns the span's DOM\n // after mount, so this must NOT be reactive - a reactive {{ text }} would make\n // Vue re-run setElementText on every change and wipe the animated glyph cells.\n // The accessible name stays current through the reactive :aria-label binding.\n const initialText = text;\n\n const labelRef = useTemplateRef('label');\n\n // Per-instance record of the in-flight roll, so a new roll can interrupt it.\n let state: SlotState | null = null;\n let revertTimeout: number | undefined;\n let restingText: string | undefined;\n\n watch(() => text, value => set(value));\n\n onMounted(() => {\n const element = labelRef.value;\n\n if (element) {\n buildSlotText(element, text);\n }\n });\n\n onBeforeUnmount(() => {\n window.clearTimeout(revertTimeout);\n\n const element = labelRef.value;\n\n if (element) {\n clearSlotText(element, text);\n }\n });\n\n function glyph(char: string): string {\n return char === ' ' ? NBSP : char;\n }\n\n // Sweep the hue across the line so the roll lands as a chromatic spectrum.\n function chromaticColor(index: number, total: number): string {\n const t = total <= 1 ? 0 : index / (total - 1);\n return `hsl(${(t * 320) % 360} 92% 60%)`;\n }\n\n function baseOptions(): AnimateOptions {\n return {\n direction,\n stagger,\n duration,\n exitOffset,\n easing,\n bounce,\n color: chromatic ? chromaticColor : color,\n colorFade,\n skipUnchanged,\n interrupt\n };\n }\n\n function makeFace(char: string): HTMLSpanElement {\n const face = document.createElement('span');\n face.className = $style.charFace;\n face.textContent = glyph(char);\n return face;\n }\n\n function buildSlot(char: string): HTMLSpanElement {\n const slot = document.createElement('span');\n slot.className = $style.charSlot;\n slot.dataset.char = char;\n\n // Invisible sizer keeps the cell exactly the width/height of its glyph,\n // so the absolutely-positioned animating faces never reflow the line.\n const sizer = document.createElement('span');\n sizer.className = $style.charSizer;\n sizer.textContent = glyph(char);\n\n slot.append(sizer, makeFace(char));\n return slot;\n }\n\n function buildSlotText(container: HTMLElement, value: string): void {\n container.classList.add($style.slotText);\n container.replaceChildren(...Array.from(value, buildSlot));\n }\n\n // Cancel any running roll on the container and snap it to its target text.\n function settle(container: HTMLElement): void {\n if (!state) {\n return;\n }\n\n state.timers.forEach(timer => window.clearTimeout(timer));\n\n // Rebuild a pristine DOM at the text the interrupted roll was heading\n // toward, so the next animation starts from a clean baseline.\n const target = state.target;\n state = null;\n buildSlotText(container, target);\n }\n\n function animateSlotText(container: HTMLElement, toText: string, options: AnimateOptions = {}): void {\n const {\n direction = 'down',\n stagger = 45,\n duration = 300,\n exitOffset = 50,\n easing = 'cubic-bezier(0.34, 1.56, 0.64, 1)',\n bounce = .6,\n color,\n colorFade = 280,\n skipUnchanged = true,\n interrupt = true\n } = options;\n\n // Reduced motion: swap to the new text without rolling.\n if (prefersReducedMotion()) {\n buildSlotText(container, toText);\n return;\n }\n\n // Non-interrupting mode: if a roll is already in flight, let it finish\n // and remember this request instead. Only the latest request survives,\n // so spam taps coalesce into a single follow-up roll once it lands.\n if (state && !interrupt) {\n if (toText !== state.target) {\n state.pending = {text: toText, options};\n }\n return;\n }\n\n // Interrupt: fast-forward any previous roll to its target and tear down\n // its timers before we start fresh.\n settle(container);\n\n // First run / empty container → just build it.\n if (!container.querySelector(`.${$style.charSlot}`)) {\n buildSlotText(container, toText);\n return;\n }\n\n const slots = Array.from(container.querySelectorAll<HTMLElement>(`.${$style.charSlot}`));\n const fromText = slots.map(slot => slot.dataset.char ?? '').join('');\n\n // Non-interrupting mode drops rolls to the text already on screen, so\n // repeated triggers do not visibly re-roll an unchanged label.\n if (!interrupt && fromText === toText) {\n return;\n }\n\n const maxLen = Math.max(fromText.length, toText.length);\n\n // Whole-pixel slide distance = one cell height, so glyphs clip cleanly.\n // Ceil, not round: half a pixel short leaves a sliver of the outgoing\n // glyph visible at the clip edge.\n const sample = slots.find(slot => (slot.dataset.char ?? '') !== '') ?? slots[0];\n const cs = getComputedStyle(container);\n const H = Math.ceil(\n sample?.getBoundingClientRect().height\n || sample?.offsetHeight\n || container.getBoundingClientRect().height\n || parseFloat(cs.lineHeight)\n || 0\n ) || Math.ceil(parseFloat(cs.fontSize) * 1.3) || 18;\n\n // Resting color to settle the chromatic flash back to.\n const restColor = color ? cs.color : '';\n\n // Pre-create any extra cells up front so the row never reflows mid-roll.\n for (let i = slots.length; i < maxLen; i++) {\n const slot = buildSlot('');\n container.appendChild(slot);\n slots.push(slot);\n }\n\n const timers: number[] = [];\n state = {timers, target: toText};\n\n // down: new enters from above (-H to 0), old exits below (0 to +H)\n // up: new enters from below (+H to 0), old exits above (0 to -H)\n const outY = direction === 'down' ? H : -H;\n const inStart = direction === 'down' ? -H : H;\n\n // A tiny deterministic jitter in [-1, 1] per character. Scaled by\n // `bounce` it gives each glyph its own speed and a little tilt-wobble,\n // so the line does not land as one rigid block.\n const wobble = (index: number, salt: number): number => {\n const n = Math.sin((index + 1) * 12.9898 + salt * 78.233) * 43758.5453;\n return (n - Math.floor(n)) * 2 - 1;\n };\n\n // Track the slowest letter so the safety-net snap waits for everyone.\n let maxEnd = 0;\n\n for (let i = 0; i < maxLen; i++) {\n const fromChar = fromText[i] || '';\n const toChar = toText[i] || '';\n\n if (fromChar === toChar && (skipUnchanged || fromChar === '')) {\n continue;\n }\n\n const slot = slots[i];\n const sizer = slot.querySelector<HTMLElement>(`.${$style.charSizer}`)!;\n const oldFace = slot.querySelector<HTMLElement>(`.${$style.charFace}`);\n\n // Resize the cell to the new glyph — but ease the width instead of\n // snapping it, so a wide outgoing glyph is never cropped by a\n // suddenly-narrow cell and neighbors glide rather than jump.\n const oldW = slot.getBoundingClientRect().width;\n sizer.textContent = glyph(toChar);\n const newW = sizer.getBoundingClientRect().width;\n const widthChanges = Math.abs(newW - oldW) > .5;\n\n if (widthChanges) {\n slot.style.width = `${oldW}px`;\n }\n\n // A cell growing from or collapsing to empty changes width\n // drastically — clip it horizontally while it resizes so its glyph\n // wipes in/out with the cell instead of stacking onto the neighbors.\n if (fromChar === '' || toChar === '') {\n slot.classList.add($style.isResizing);\n }\n\n const tint = typeof color === 'function' ? color(i, maxLen) : color;\n\n // Per-letter personality: vary the speed, the stagger and a starting\n // tilt that springs back to upright as the glyph settles. Tail cells\n // (rolling out to nothing) join the same wave instead of queuing\n // behind it, so nothing trails.\n const isTail = toChar === '';\n const d = Math.round(duration * (isTail ? .75 : 1) * (1 + bounce * .45 * wobble(i, 1)));\n const staggerIndex = isTail ? toText.length * .5 + (i - toText.length) * .25 : i;\n const base = Math.round(staggerIndex * stagger * (1 + bounce * .25 * wobble(i, 2)));\n const tilt = (bounce * 5 * wobble(i, 3)).toFixed(2);\n\n const rollTrans = `transform ${d}ms ${easing}`;\n const trans = color ? `${rollTrans}, color ${colorFade}ms linear ${d}ms` : rollTrans;\n\n const newFace = makeFace(toChar);\n newFace.style.transformOrigin = '50% 50%';\n newFace.style.transform = `translateY(${inStart}px) rotate(${tilt}deg)`;\n\n if (tint) {\n newFace.style.color = tint;\n }\n\n slot.appendChild(newFace);\n\n void slot.offsetWidth; // commit start transforms\n\n // Glide the cell to its new width with a clean ease-out (no\n // overshoot) so it never pinches narrower than either glyph. Timing\n // depends on the kind of change:\n // - glyph → glyph: resize alongside the roll.\n // - glyph → empty: roll out at full width first, then snap closed.\n // - empty → glyph: open the cell quickly before the glyph rolls in.\n if (widthChanges) {\n let wDelay = base;\n let wDur = d;\n\n if (isTail) {\n wDelay = base + Math.round(d * .55);\n wDur = Math.max(140, Math.round(d * .6));\n } else if (fromChar === '') {\n wDur = Math.max(140, Math.round(d * .45));\n }\n\n timers.push(window.setTimeout(() => {\n slot.style.transition = `width ${wDur}ms cubic-bezier(0.2, 0, 0, 1)`;\n slot.style.width = `${newW}px`;\n }, wDelay));\n\n maxEnd = Math.max(maxEnd, wDelay + wDur);\n }\n\n maxEnd = Math.max(maxEnd, base + exitOffset + d + (color ? colorFade : 0));\n\n // Outgoing glyph slides away first (with its own little counter-tilt).\n if (oldFace) {\n timers.push(window.setTimeout(() => {\n oldFace.style.transition = rollTrans;\n oldFace.style.transform = `translateY(${outY}px) rotate(${-Number(tilt)}deg)`;\n }, base));\n }\n\n // Incoming glyph chases it in (and, if tinted, fades to rest after).\n timers.push(window.setTimeout(() => {\n newFace.style.transition = trans;\n newFace.style.transform = 'translateY(0) rotate(0deg)';\n\n if (color) {\n newFace.style.color = restColor;\n }\n\n const done = (event: TransitionEvent): void => {\n if (event.propertyName !== 'transform') {\n return; // ignore the color fade\n }\n\n newFace.removeEventListener('transitionend', done);\n slot.dataset.char = toChar;\n // Hand sizing back to the sizer (same px, nothing moves).\n slot.style.removeProperty('transition');\n slot.style.removeProperty('width');\n slot.classList.remove($style.isResizing);\n slot.querySelectorAll(`.${$style.charFace}`).forEach(face => {\n if (face !== newFace) {\n face.remove();\n }\n });\n };\n\n newFace.addEventListener('transitionend', done);\n }, base + exitOffset));\n }\n\n // Safety net: snap to a pristine DOM once the slowest letter settles. If\n // a non-interrupting call was deferred mid-roll, replay it now as a fresh\n // roll from this clean baseline.\n const total = maxEnd + 80;\n timers.push(window.setTimeout(() => {\n const pending = state?.pending;\n state = null;\n buildSlotText(container, toText);\n\n if (pending) {\n animateSlotText(container, pending.text, pending.options);\n }\n }, total));\n }\n\n function clearSlotText(container: HTMLElement, value = ''): void {\n settle(container);\n container.classList.remove($style.slotText);\n container.textContent = value;\n }\n\n // Roll to new text. Cancels any pending flash revert.\n function set(toText: string, options: AnimateOptions = {}): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n window.clearTimeout(revertTimeout);\n restingText = undefined;\n animateSlotText(element, toText, {...baseOptions(), ...options});\n }\n\n // Roll to temporary text, then roll back automatically — the classic\n // Copy → Copied → Copy in one call. Spam-safe: repeat flashes restart the\n // revert timer instead of queuing extra rolls.\n function flash(toText: string, {revertAfter = 1400, enter, exit}: FlashOptions = {}): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n // Capture the resting text only on the first flash of a burst, so a\n // flash-during-flash still reverts to the original label.\n if (restingText === undefined) {\n restingText = text;\n }\n\n animateSlotText(element, toText, {...baseOptions(), interrupt: false, ...enter});\n\n window.clearTimeout(revertTimeout);\n revertTimeout = window.setTimeout(() => {\n const back = restingText!;\n restingText = undefined;\n revertTimeout = undefined;\n\n const current = labelRef.value;\n\n if (current) {\n animateSlotText(current, back, {...baseOptions(), interrupt: false, ...exit});\n }\n }, revertAfter);\n }\n\n defineExpose({\n flash,\n set\n });\n</script>\n",".textScramble {\n font-variant-numeric: tabular-nums;\n display: inline-block;\n white-space: pre;\n}\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"text\"\n :class=\"$style.textScramble\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { onBeforeUnmount, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/TextScramble.module.scss';\n\n type ScrambleCell = {\n from: string;\n to: string;\n start: number;\n end: number;\n char: string;\n lastSwap: number;\n fixed: boolean;\n };\n\n const emit = defineEmits<{\n finished: [];\n }>();\n\n const {\n text,\n characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',\n duration = 900,\n skipUnchanged = true,\n speed = 45,\n stagger = 0.5\n } = defineProps<{\n readonly text: string;\n readonly characters?: string;\n readonly duration?: number;\n readonly skipUnchanged?: boolean;\n readonly speed?: number;\n readonly stagger?: number;\n }>();\n\n // Rendered once for SSR / first paint only. The engine owns the span's text\n // after mount, so this must NOT be reactive - a reactive {{ text }} would make\n // Vue re-patch the text node every frame and wipe the scramble. The accessible\n // name stays current through the reactive :aria-label binding.\n const initialText = text;\n\n const labelRef = useTemplateRef('label');\n\n let frame = 0;\n let currentText = text;\n\n watch(() => text, value => set(value));\n\n onBeforeUnmount(cancel);\n\n function cancel(): void {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n }\n\n function randomChar(): string {\n return characters.charAt(Math.floor(Math.random() * characters.length));\n }\n\n // Decode `toText` character by character. Each cell holds its old glyph until\n // its staggered start, cycles through random glyphs, then settles on its\n // final glyph. `force` re-scrambles even unchanged cells, for replay().\n function scramble(element: HTMLElement, fromText: string, toText: string, force = false): void {\n cancel();\n\n // Reduced motion or no duration: swap straight to the final text.\n if (duration <= 0 || prefersReducedMotion()) {\n element.textContent = toText;\n emit('finished');\n return;\n }\n\n const maxLen = Math.max(fromText.length, toText.length);\n const spread = Math.min(Math.max(stagger, 0), 1);\n const revealWindow = duration * spread;\n const scrambleFor = duration - revealWindow;\n const cells: ScrambleCell[] = [];\n\n for (let i = 0; i < maxLen; ++i) {\n const from = fromText.charAt(i);\n const to = toText.charAt(i);\n\n if (!force && skipUnchanged && from !== '' && from === to) {\n cells.push({from, to, start: 0, end: 0, char: to, lastSwap: 0, fixed: true});\n continue;\n }\n\n const start = maxLen <= 1 ? 0 : (i / (maxLen - 1)) * revealWindow;\n cells.push({from, to, start, end: start + scrambleFor, char: '', lastSwap: -Infinity, fixed: false});\n }\n\n const startTime = performance.now();\n\n const step = (now: number): void => {\n const elapsed = now - startTime;\n let output = '';\n let done = 0;\n\n for (const cell of cells) {\n if (cell.fixed || elapsed >= cell.end) {\n output += cell.to;\n ++done;\n } else if (elapsed >= cell.start) {\n if (now - cell.lastSwap >= speed) {\n cell.char = randomChar();\n cell.lastSwap = now;\n }\n\n output += cell.char;\n } else {\n output += cell.from;\n }\n }\n\n element.textContent = output;\n\n if (done === cells.length) {\n frame = 0;\n emit('finished');\n return;\n }\n\n frame = requestAnimationFrame(step);\n };\n\n frame = requestAnimationFrame(step);\n }\n\n // Decode toward new text permanently, scrambling from the text on screen.\n function set(toText: string): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n const fromText = currentText;\n currentText = toText;\n scramble(element, fromText, toText);\n }\n\n // Re-run the decode on the current text without changing it.\n function replay(): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n scramble(element, currentText, currentText, true);\n }\n\n defineExpose({\n replay,\n set\n });\n</script>\n","<template>\n <span\n ref=\"label\"\n :aria-label=\"text\"\n :class=\"$style.textScramble\">{{ initialText }}</span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import { prefersReducedMotion } from '@basmilius/utils';\n import { onBeforeUnmount, useTemplateRef, watch } from 'vue';\n import $style from '~flux/visuals/css/component/TextScramble.module.scss';\n\n type ScrambleCell = {\n from: string;\n to: string;\n start: number;\n end: number;\n char: string;\n lastSwap: number;\n fixed: boolean;\n };\n\n const emit = defineEmits<{\n finished: [];\n }>();\n\n const {\n text,\n characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',\n duration = 900,\n skipUnchanged = true,\n speed = 45,\n stagger = 0.5\n } = defineProps<{\n readonly text: string;\n readonly characters?: string;\n readonly duration?: number;\n readonly skipUnchanged?: boolean;\n readonly speed?: number;\n readonly stagger?: number;\n }>();\n\n // Rendered once for SSR / first paint only. The engine owns the span's text\n // after mount, so this must NOT be reactive - a reactive {{ text }} would make\n // Vue re-patch the text node every frame and wipe the scramble. The accessible\n // name stays current through the reactive :aria-label binding.\n const initialText = text;\n\n const labelRef = useTemplateRef('label');\n\n let frame = 0;\n let currentText = text;\n\n watch(() => text, value => set(value));\n\n onBeforeUnmount(cancel);\n\n function cancel(): void {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n }\n\n function randomChar(): string {\n return characters.charAt(Math.floor(Math.random() * characters.length));\n }\n\n // Decode `toText` character by character. Each cell holds its old glyph until\n // its staggered start, cycles through random glyphs, then settles on its\n // final glyph. `force` re-scrambles even unchanged cells, for replay().\n function scramble(element: HTMLElement, fromText: string, toText: string, force = false): void {\n cancel();\n\n // Reduced motion or no duration: swap straight to the final text.\n if (duration <= 0 || prefersReducedMotion()) {\n element.textContent = toText;\n emit('finished');\n return;\n }\n\n const maxLen = Math.max(fromText.length, toText.length);\n const spread = Math.min(Math.max(stagger, 0), 1);\n const revealWindow = duration * spread;\n const scrambleFor = duration - revealWindow;\n const cells: ScrambleCell[] = [];\n\n for (let i = 0; i < maxLen; ++i) {\n const from = fromText.charAt(i);\n const to = toText.charAt(i);\n\n if (!force && skipUnchanged && from !== '' && from === to) {\n cells.push({from, to, start: 0, end: 0, char: to, lastSwap: 0, fixed: true});\n continue;\n }\n\n const start = maxLen <= 1 ? 0 : (i / (maxLen - 1)) * revealWindow;\n cells.push({from, to, start, end: start + scrambleFor, char: '', lastSwap: -Infinity, fixed: false});\n }\n\n const startTime = performance.now();\n\n const step = (now: number): void => {\n const elapsed = now - startTime;\n let output = '';\n let done = 0;\n\n for (const cell of cells) {\n if (cell.fixed || elapsed >= cell.end) {\n output += cell.to;\n ++done;\n } else if (elapsed >= cell.start) {\n if (now - cell.lastSwap >= speed) {\n cell.char = randomChar();\n cell.lastSwap = now;\n }\n\n output += cell.char;\n } else {\n output += cell.from;\n }\n }\n\n element.textContent = output;\n\n if (done === cells.length) {\n frame = 0;\n emit('finished');\n return;\n }\n\n frame = requestAnimationFrame(step);\n };\n\n frame = requestAnimationFrame(step);\n }\n\n // Decode toward new text permanently, scrambling from the text on screen.\n function set(toText: string): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n const fromText = currentText;\n currentText = toText;\n scramble(element, fromText, toText);\n }\n\n // Re-run the decode on the current text without changing it.\n function replay(): void {\n const element = labelRef.value;\n\n if (!element) {\n return;\n }\n\n scramble(element, currentText, currentText, true);\n }\n\n defineExpose({\n replay,\n set\n });\n</script>\n",".textShimmer {\n display: inline-block;\n animation: textShimmerSweep var(--shimmer-duration, 2s) linear infinite;\n color: transparent;\n background-image: linear-gradient(\n 90deg,\n var(--shimmer-base, var(--foreground-subtle)) calc(50% - var(--shimmer-spread, 15) * 1%),\n var(--shimmer-color, var(--foreground-prominent)) 50%,\n var(--shimmer-base, var(--foreground-subtle)) calc(50% + var(--shimmer-spread, 15) * 1%)\n );\n // Both edges of the gradient are the base color, so tiling it seamlessly\n // keeps the text fully painted at every step: outside the moving highlight\n // band the text is always covered by the base color instead of falling off\n // the single tile into a transparent (clipped) gap. A no-repeat background\n // only covers the text for background-position 0%..100%; past that the tile\n // slides off and the text vanishes.\n background-repeat: repeat;\n background-position: 100% center;\n background-clip: text;\n background-size: 200% 100%;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n\n // No sweep when reduced motion is requested: fall back to a plain, solid\n // text color so the label stays legible without animating.\n @media (prefers-reduced-motion: reduce) {\n animation: none;\n color: var(--shimmer-base, var(--foreground-subtle));\n background: none;\n -webkit-text-fill-color: currentColor;\n }\n}\n\n// The 100% to -100% travel shifts the background by exactly one tile width\n// (background-size is 200%), so the repeated pattern lands back on itself and\n// the loop restarts without a jump.\n@keyframes textShimmerSweep {\n from {\n background-position: 100% center;\n }\n\n to {\n background-position: -100% center;\n }\n}\n","<template>\n <span\n :class=\"$style.textShimmer\"\n :style=\"{\n '--shimmer-duration': `${duration}s`,\n '--shimmer-spread': `${spread}`,\n '--shimmer-base': color,\n '--shimmer-color': shimmerColor\n }\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import $style from '~flux/visuals/css/component/TextShimmer.module.scss';\n\n const {\n color,\n duration = 2,\n shimmerColor,\n spread = 15\n } = defineProps<{\n readonly color?: string;\n readonly duration?: number;\n readonly shimmerColor?: string;\n readonly spread?: number;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n</script>\n","<template>\n <span\n :class=\"$style.textShimmer\"\n :style=\"{\n '--shimmer-duration': `${duration}s`,\n '--shimmer-spread': `${spread}`,\n '--shimmer-base': color,\n '--shimmer-color': shimmerColor\n }\">\n <slot/>\n </span>\n</template>\n\n<script\n lang=\"ts\"\n setup>\n import $style from '~flux/visuals/css/component/TextShimmer.module.scss';\n\n const {\n color,\n duration = 2,\n shimmerColor,\n spread = 15\n } = defineProps<{\n readonly color?: string;\n readonly duration?: number;\n readonly shimmerColor?: string;\n readonly spread?: number;\n }>();\n\n defineSlots<{\n default(): any;\n }>();\n</script>\n"],"mappings":"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"}
|