ngwr 15.2.0 → 15.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/alert/styles/_index.scss +13 -5
  2. package/bits/border-glow/styles/_rules.scss +60 -49
  3. package/bits/click-spark/click-spark.scss +5 -1
  4. package/bits/click-spark/styles/_index.scss +10 -15
  5. package/bits/click-spark/styles/_rules.scss +20 -0
  6. package/fesm2022/ngwr-alert.mjs +2 -2
  7. package/fesm2022/ngwr-alert.mjs.map +1 -1
  8. package/fesm2022/ngwr-bits-aurora.mjs +108 -52
  9. package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
  10. package/fesm2022/ngwr-bits-border-glow-testing.mjs +8 -8
  11. package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
  12. package/fesm2022/ngwr-bits-border-glow.mjs +29 -26
  13. package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
  14. package/fesm2022/ngwr-bits-dot-grid.mjs +11 -14
  15. package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
  16. package/fesm2022/ngwr-bits-fuzzy-text.mjs +18 -4
  17. package/fesm2022/ngwr-bits-fuzzy-text.mjs.map +1 -1
  18. package/fesm2022/ngwr-bits-ripple-grid.mjs +14 -13
  19. package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
  20. package/fesm2022/ngwr-bits-splash-cursor.mjs +21 -10
  21. package/fesm2022/ngwr-bits-splash-cursor.mjs.map +1 -1
  22. package/fesm2022/ngwr-bits-waves.mjs +10 -15
  23. package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
  24. package/fesm2022/ngwr-breadcrumbs.mjs +18 -2
  25. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  26. package/fesm2022/ngwr-hotkey.mjs +23 -1
  27. package/fesm2022/ngwr-hotkey.mjs.map +1 -1
  28. package/fesm2022/ngwr-i18n-ar.mjs +2 -0
  29. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
  30. package/fesm2022/ngwr-i18n-cs.mjs +2 -0
  31. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
  32. package/fesm2022/ngwr-i18n-de.mjs +2 -0
  33. package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
  34. package/fesm2022/ngwr-i18n-en.mjs +2 -0
  35. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  36. package/fesm2022/ngwr-i18n-es.mjs +2 -0
  37. package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
  38. package/fesm2022/ngwr-i18n-fr.mjs +2 -0
  39. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
  40. package/fesm2022/ngwr-i18n-he.mjs +2 -0
  41. package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
  42. package/fesm2022/ngwr-i18n-hi.mjs +2 -0
  43. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
  44. package/fesm2022/ngwr-i18n-id.mjs +2 -0
  45. package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
  46. package/fesm2022/ngwr-i18n-it.mjs +2 -0
  47. package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
  48. package/fesm2022/ngwr-i18n-ja.mjs +2 -0
  49. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
  50. package/fesm2022/ngwr-i18n-ko.mjs +2 -0
  51. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
  52. package/fesm2022/ngwr-i18n-nl.mjs +2 -0
  53. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
  54. package/fesm2022/ngwr-i18n-pl.mjs +2 -0
  55. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
  56. package/fesm2022/ngwr-i18n-pt.mjs +2 -0
  57. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
  58. package/fesm2022/ngwr-i18n-ru.mjs +2 -0
  59. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  60. package/fesm2022/ngwr-i18n-sv.mjs +2 -0
  61. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
  62. package/fesm2022/ngwr-i18n-tr.mjs +2 -0
  63. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
  64. package/fesm2022/ngwr-i18n-uk.mjs +2 -0
  65. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
  66. package/fesm2022/ngwr-i18n-vi.mjs +2 -0
  67. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
  68. package/fesm2022/ngwr-i18n-zh-tw.mjs +2 -0
  69. package/fesm2022/ngwr-i18n-zh-tw.mjs.map +1 -1
  70. package/fesm2022/ngwr-i18n-zh.mjs +2 -0
  71. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
  72. package/fesm2022/ngwr-pagination.mjs +3 -0
  73. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  74. package/fesm2022/ngwr-pipes.mjs +163 -49
  75. package/fesm2022/ngwr-pipes.mjs.map +1 -1
  76. package/fesm2022/ngwr-schema-form.mjs +3 -0
  77. package/fesm2022/ngwr-schema-form.mjs.map +1 -1
  78. package/fesm2022/ngwr-select-testing.mjs +16 -0
  79. package/fesm2022/ngwr-select-testing.mjs.map +1 -1
  80. package/fesm2022/ngwr-select.mjs +218 -38
  81. package/fesm2022/ngwr-select.mjs.map +1 -1
  82. package/fesm2022/ngwr-statistic.mjs +20 -21
  83. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  84. package/fesm2022/ngwr-version.mjs +1 -1
  85. package/fesm2022/ngwr-version.mjs.map +1 -1
  86. package/i18n/ar.json +2 -0
  87. package/i18n/cs.json +2 -0
  88. package/i18n/de.json +2 -0
  89. package/i18n/en.json +2 -0
  90. package/i18n/es.json +2 -0
  91. package/i18n/fr.json +2 -0
  92. package/i18n/he.json +2 -0
  93. package/i18n/hi.json +2 -0
  94. package/i18n/id.json +2 -0
  95. package/i18n/it.json +2 -0
  96. package/i18n/ja.json +2 -0
  97. package/i18n/ko.json +2 -0
  98. package/i18n/nl.json +2 -0
  99. package/i18n/pl.json +2 -0
  100. package/i18n/pt.json +2 -0
  101. package/i18n/ru.json +2 -0
  102. package/i18n/sv.json +2 -0
  103. package/i18n/tr.json +2 -0
  104. package/i18n/uk.json +2 -0
  105. package/i18n/vi.json +2 -0
  106. package/i18n/zh-TW.json +2 -0
  107. package/i18n/zh.json +2 -0
  108. package/llms-full.txt +5 -5
  109. package/package.json +1 -1
  110. package/schematics/use/symbol-map.json +2 -0
  111. package/select/styles/_index.scss +23 -1
  112. package/skills/ngwr/SKILL.md +3 -1
  113. package/skills/ngwr/references/catalog.md +1 -1
  114. package/types/ngwr-alert.d.ts +3 -1
  115. package/types/ngwr-bits-aurora.d.ts +24 -2
  116. package/types/ngwr-bits-border-glow-testing.d.ts +9 -5
  117. package/types/ngwr-bits-border-glow.d.ts +10 -4
  118. package/types/ngwr-bits-fuzzy-text.d.ts +2 -1
  119. package/types/ngwr-bits-ripple-grid.d.ts +7 -2
  120. package/types/ngwr-bits-splash-cursor.d.ts +2 -1
  121. package/types/ngwr-breadcrumbs.d.ts +15 -0
  122. package/types/ngwr-hotkey.d.ts +14 -2
  123. package/types/ngwr-pipes.d.ts +142 -32
  124. package/types/ngwr-select-testing.d.ts +9 -0
  125. package/types/ngwr-select.d.ts +133 -2
  126. package/types/ngwr-sidebar.d.ts +1 -1
  127. package/types/ngwr-version.d.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-bits-border-glow.mjs","names":[],"sources":["../../../projects/lib/bits/border-glow/border-glow.ts","../../../projects/lib/bits/border-glow/border-glow.html"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Port of the reactbits.dev BorderGlow effect — a cursor-tracked cone of\n * mesh-gradient light that follows the pointer along the card's perimeter,\n * with an outer halo, an optional auto-sweep on mount and an optional endless\n * loop.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport { Component, ElementRef, PLATFORM_ID, ViewEncapsulation, computed, effect, inject, input } from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { isSafeCssValue, numAttr } from 'ngwr/utils';\n\nconst RADIANS_TO_DEG = 180 / Math.PI;\n\n/**\n * Where a looping cone rests under reduced motion, and where it starts when\n * nothing has written an angle yet — the stylesheet's own default.\n */\nconst REST_ANGLE = 45;\n\n/**\n * The most frame time one loop step may count, in ms. A hidden tab, a long task\n * or a debugger stops frames without telling the loop, and the first frame back\n * would otherwise jump the cone by the whole time away.\n */\nconst MAX_LOOP_STEP_MS = 100;\n\n/** `'40 80 80'`, `'268 100% 76%'`, `'40deg 80% 80%'` — the bare HSL parts `glowColor` has always taken. */\nconst HSL_PARTS = /^\\s*([\\d.]+)(?:deg)?\\s+([\\d.]+)%?\\s+([\\d.]+)%?\\s*$/;\n\nconst GLOW_OPACITY_STEPS = [100, 60, 50, 40, 30, 20, 10] as const;\nconst GLOW_VAR_SUFFIXES = ['', '-60', '-50', '-40', '-30', '-20', '-10'] as const;\n\nconst GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%'] as const;\nconst GRADIENT_VAR_NAMES = [\n '--wr-border-glow-gradient-one',\n '--wr-border-glow-gradient-two',\n '--wr-border-glow-gradient-three',\n '--wr-border-glow-gradient-four',\n '--wr-border-glow-gradient-five',\n '--wr-border-glow-gradient-six',\n '--wr-border-glow-gradient-seven',\n] as const;\nconst GRADIENT_COLOR_INDEX = [0, 1, 2, 0, 1, 2, 1] as const;\n\nconst DEFAULT_COLORS: readonly string[] = ['#c084fc', '#f472b6', '#38bdf8'];\n\nfunction buildGlowVars(glowColor: string, intensity: number): Record<string, string> | null {\n const parts = HSL_PARTS.exec(glowColor);\n // Anything else is a CSS colour, interpolated into a `color-mix()` — so it is\n // held to the containment check `colors` and `backgroundColor` pass, and an\n // unsafe value leaves the halo to the theme.\n if (!parts && !isSafeCssValue(glowColor)) return null;\n const base = parts\n ? `${Number.parseFloat(parts[1])}deg ${Number.parseFloat(parts[2])}% ${Number.parseFloat(parts[3])}%`\n : glowColor.trim();\n const out: Record<string, string> = {};\n for (let i = 0; i < GLOW_OPACITY_STEPS.length; i++) {\n const alpha = Math.min(GLOW_OPACITY_STEPS[i] * intensity, 100);\n out[`--wr-border-glow-color${GLOW_VAR_SUFFIXES[i]}`] = parts\n ? `hsl(${base} / ${alpha}%)`\n : `color-mix(in srgb, ${base} ${alpha}%, transparent)`;\n }\n return out;\n}\n\nfunction buildGradientVars(colors: readonly string[]): Record<string, string> {\n const out: Record<string, string> = {};\n // Each entry is INTERPOLATED into the `radial-gradient()` below, so a value\n // that balances the parens around it escapes into a sibling layer and can add\n // a `url()` the browser fetches. One unsafe entry discards the palette — the\n // same all-or-nothing rule `wr-gradient-text` follows, for the same reason.\n const palette = colors.length > 0 && colors.every(color => isSafeCssValue(color)) ? colors : DEFAULT_COLORS;\n for (let i = 0; i < GRADIENT_VAR_NAMES.length; i++) {\n const colour = palette[Math.min(GRADIENT_COLOR_INDEX[i], palette.length - 1)];\n out[GRADIENT_VAR_NAMES[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${colour} 0px, transparent 50%)`;\n }\n out['--wr-border-glow-gradient-base'] = `linear-gradient(${palette[0]} 0 100%)`;\n return out;\n}\n\ninterface AnimateOptions {\n readonly start?: number;\n readonly end?: number;\n readonly duration?: number;\n readonly delay?: number;\n readonly ease?: (t: number) => number;\n readonly onUpdate: (v: number) => void;\n readonly onEnd?: () => void;\n}\n\n/** Tiny rAF-driven tweener. Returns a cancel function so callers can clean up on destroy. */\nfunction animateValue(opts: AnimateOptions): () => void {\n const { start = 0, end = 100, duration = 1000, delay = 0, ease = (t: number): number => t, onUpdate, onEnd } = opts;\n let raf = 0;\n let cancelled = false;\n\n const startAt = performance.now() + delay;\n const tick = (): void => {\n if (cancelled) return;\n const elapsed = performance.now() - startAt;\n const t = Math.min(Math.max(elapsed / duration, 0), 1);\n onUpdate(start + (end - start) * ease(t));\n if (t < 1) raf = requestAnimationFrame(tick);\n else if (onEnd) onEnd();\n };\n\n const timer = setTimeout(() => {\n raf = requestAnimationFrame(tick);\n }, delay);\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n cancelAnimationFrame(raf);\n };\n}\n\n/**\n * Cursor-tracked glowing border card. A cone of light follows the pointer\n * along the card's perimeter, brightening as the cursor approaches an edge.\n *\n * - Outer halo via stacked `box-shadow` on an inner `.edge-light` element.\n * - Inner bloom near the edges, painted with a normal blend by default and\n * overridable through `--wr-border-glow-fill-blend` (subtle; control the\n * strength via `[fillOpacity]`).\n * - Optional `[animated]` mode performs a one-shot sweep on mount, useful\n * for drawing attention to a freshly-rendered card.\n * - Optional `[loop]` mode circles the perimeter for ever, edge fully lit —\n * a frame for a call to action rather than a reaction to the cursor.\n *\n * @example\n * ```html\n * <wr-border-glow [animated]=\"true\" [colors]=\"['#c084fc', '#f472b6', '#38bdf8']\">\n * <h3>Hover me</h3>\n * <p>The border lights up where the cursor is.</p>\n * </wr-border-glow>\n * ```\n *\n * @see https://www.reactbits.dev/components/border-glow (visual reference)\n */\n@Component({\n selector: 'wr-border-glow',\n templateUrl: './border-glow.html',\n styleUrl: './border-glow.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-border-glow',\n '(pointerenter)': 'onPointerEnter()',\n '(pointermove)': 'onPointerMove($event)',\n '(pointerleave)': 'onPointerLeave()',\n },\n})\nexport class WrBorderGlow {\n /**\n * Card background fill. When unset, uses the theme surface token. A hook set inline on\n * the same element is replaced by this input and does not come back when it is unset,\n * so set it from a stylesheet rule. The same goes for `glowColor` and `colors`.\n */\n readonly backgroundColor = input<string | null>(null);\n\n /** Corner radius in pixels. @default 28 */\n readonly borderRadius = input(28, { transform: numAttr(28) });\n\n /** Halo extent in pixels — how far the outer glow reaches past the card edge. @default 40 */\n readonly glowRadius = input(40, { transform: numAttr(40) });\n\n /**\n * Halo opacity multiplier (1 = full strength).\n *\n * Applies only alongside an explicit `glowColor`: the halo variables are\n * built from the two together, so on the default palette this input is read\n * and never reaches a declaration.\n *\n * @default 1\n */\n readonly glowIntensity = input(1, { transform: numAttr(1) });\n\n /** Width of the lit cone as a percentage of the perimeter. @default 25 */\n readonly coneSpread = input(25, { transform: numAttr(25) });\n\n /** How sharply the halo fades as the cursor leaves the edge. Lower = wider falloff. @default 30 */\n readonly edgeSensitivity = input(30, { transform: numAttr(30) });\n\n /** Strength of the interior bloom near edges (blend: `--wr-border-glow-fill-blend`). @default 0.5 */\n readonly fillOpacity = input(0.5, { transform: numAttr(0.5) });\n\n /**\n * Halo colour: any CSS colour (`'#3567ff'`, `'var(--wr-color-primary)'`), or\n * the bare `'H S L'` parts (no commas) it has always taken. A value that\n * could escape the `color-mix()` it is composed into is ignored. When unset,\n * the theme decides: a deep amber on light surfaces, pale amber on dark.\n * Note `glowIntensity` only applies alongside an explicit `glowColor`.\n */\n readonly glowColor = input<string | null>(null);\n\n /**\n * Mesh-gradient palette for the colored border slice. When unset, the\n * theme decides: deep tones on light, pastels on dark.\n */\n readonly colors = input<readonly string[] | null>(null);\n\n /** Play a one-shot perimeter sweep on mount, then fade out. @default false */\n readonly animated = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Circle the perimeter for ever at a constant pace, edge fully lit. The\n * pointer still drives while it is over the card, and the loop picks up from\n * where it left. Replaces the `animated` sweep; under reduced motion the edge\n * stays lit and still.\n *\n * @default false\n */\n readonly loop = input(false, { transform: coerceBooleanProperty });\n\n /** Seconds per lap while `loop` is on. @default 6 */\n readonly speed = input(6, { transform: numAttr(6) });\n\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n /** Whether a pointer is over the card — the loop holds still while it drives. */\n private pointerInside = false;\n\n /** Inline CSS variable bag — recomputed when colour / radius / sensitivity inputs change. */\n protected readonly cssVars = computed<Record<string, string>>(() => {\n const vars: Record<string, string> = {\n '--wr-border-glow-radius': `${this.borderRadius()}px`,\n '--wr-border-glow-padding': `${this.glowRadius()}px`,\n '--wr-border-glow-cone-spread': String(this.coneSpread()),\n '--wr-border-glow-edge-sensitivity': String(this.edgeSensitivity()),\n '--wr-border-glow-fill-opacity': String(this.fillOpacity()),\n };\n const colors = this.colors();\n if (colors !== null) Object.assign(vars, buildGradientVars(colors));\n // Colour vars are written only when set — otherwise the stylesheet's\n // per-theme defaults (light vs dark) take over.\n // Composed, not passed through: `_rules.scss` reads this var inside a\n // `linear-gradient()` and again in a `background` shorthand, where a `url()`\n // is valid and fetches. An audit bound `url(\"…\")` here and recorded the\n // request. Validated like its `colors` sibling on this same component —\n // leaving one of the two unchecked is two rules inside one component.\n const bg = this.backgroundColor();\n if (bg !== null && isSafeCssValue(bg)) vars['--wr-border-glow-bg'] = bg;\n const glow = this.glowColor();\n const glowVars = glow === null ? null : buildGlowVars(glow, this.glowIntensity());\n if (glowVars !== null) Object.assign(vars, glowVars);\n return vars;\n });\n\n constructor() {\n // Apply the variable bag to the host on every input change. Bind in an\n // effect rather than `host: { '[style.--foo]': '...' }` so a single\n // `setProperty` loop handles the lot. What left the bag comes off the host, or an\n // input set and then unset stays over the stylesheet's default for good, but only\n // while the host still holds what this wrote: anything else there is someone else's.\n // Read back rather than kept as written, because the browser trims a custom property.\n let written: Record<string, string> = {};\n effect(() => {\n const { style } = this.el.nativeElement;\n const vars = this.cssVars();\n for (const [k, held] of Object.entries(written)) {\n if (!Object.hasOwn(vars, k) && style.getPropertyValue(k) === held) style.removeProperty(k);\n }\n written = {};\n for (const [k, v] of Object.entries(vars)) {\n style.setProperty(k, v);\n written[k] = style.getPropertyValue(k);\n }\n });\n\n if (!this.isBrowser) return;\n\n // Auto-sweep on mount — fires once when `animated` is true, unless `loop`\n // replaces it.\n effect(onCleanup => {\n if (!this.animated() || this.loop()) return;\n onCleanup(this.runAutoSweep());\n });\n\n // Endless loop. Reduced motion is read here, so a change to the preference\n // swaps the rotation for a still, lit edge and back.\n effect(onCleanup => {\n if (!this.loop()) return;\n onCleanup(this.runLoop(this.platform.prefersReducedMotion()));\n });\n }\n\n protected onPointerEnter(): void {\n this.pointerInside = true;\n }\n\n /** Hands the cone back to the loop, re-lit, from wherever the pointer left it. */\n protected onPointerLeave(): void {\n this.pointerInside = false;\n if (this.loop()) this.el.nativeElement.style.setProperty('--wr-border-glow-edge-proximity', '100');\n }\n\n /** Pointer-driven update of `--wr-border-glow-angle` + `--wr-border-glow-edge-proximity`. */\n protected onPointerMove(event: PointerEvent): void {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const cx = rect.width / 2;\n const cy = rect.height / 2;\n\n const dx = x - cx;\n const dy = y - cy;\n\n // `edge-proximity`: 0 at centre, 1 at the perimeter (whichever axis hits first).\n let edge = 0;\n if (dx !== 0 || dy !== 0) {\n const kx = dx === 0 ? Infinity : cx / Math.abs(dx);\n const ky = dy === 0 ? Infinity : cy / Math.abs(dy);\n edge = Math.min(Math.max(1 / Math.min(kx, ky), 0), 1);\n }\n\n // `cursor-angle`: 0deg = top, increases clockwise.\n let angle = 0;\n if (dx !== 0 || dy !== 0) {\n angle = Math.atan2(dy, dx) * RADIANS_TO_DEG + 90;\n if (angle < 0) angle += 360;\n }\n\n host.style.setProperty('--wr-border-glow-edge-proximity', (edge * 100).toFixed(3));\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n }\n\n /** One-shot perimeter sweep: in → around → out, mirroring the reactbits behaviour. */\n private runAutoSweep(): () => void {\n // Reduced motion: skip the decorative mount sweep. The pointer-driven\n // glow stays — it maps 1:1 to the cursor with no autonomous motion.\n if (this.platform.prefersReducedMotion()) return () => undefined;\n const host = this.el.nativeElement;\n const ANGLE_START = 110;\n const ANGLE_END = 465;\n host.classList.add('wr-border-glow--sweeping');\n host.style.setProperty('--wr-border-glow-angle', `${ANGLE_START}deg`);\n\n const easeOutCubic = (t: number): number => 1 - (1 - t) ** 3;\n const easeInCubic = (t: number): number => t * t * t;\n const lerpAngle = (v: number): string => `${(ANGLE_END - ANGLE_START) * (v / 100) + ANGLE_START}deg`;\n\n // 1. Ramp edge-proximity up to 100 quickly.\n const cancelA = animateValue({\n duration: 500,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n });\n // 2. Sweep angle first half (ease-in).\n const cancelB = animateValue({\n duration: 1500,\n end: 50,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 3. Sweep angle second half (ease-out).\n const cancelC = animateValue({\n delay: 1500,\n duration: 2250,\n start: 50,\n end: 100,\n ease: easeOutCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 4. Fade proximity back to 0.\n const cancelD = animateValue({\n delay: 2500,\n duration: 1500,\n start: 100,\n end: 0,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n onEnd: () => host.classList.remove('wr-border-glow--sweeping'),\n });\n\n // Returned rather than registered on `destroyRef`: this method runs from an\n // effect, so registering there stacked one more teardown per re-run and none\n // of them fired until the component died. The caller ties it to the effect's\n // own cleanup instead.\n return () => {\n cancelA();\n cancelB();\n cancelC();\n cancelD();\n host.classList.remove('wr-border-glow--sweeping');\n };\n }\n\n /** Endless perimeter loop at a constant pace. Returns its canceller, like `runAutoSweep`. */\n private runLoop(reducedMotion: boolean): () => void {\n const host = this.el.nativeElement;\n host.classList.add('wr-border-glow--looping');\n host.style.setProperty('--wr-border-glow-edge-proximity', '100');\n\n // Reduced motion: no rotation at all. The edge stays lit and still, so a\n // card that uses it as a frame keeps one.\n if (reducedMotion) {\n host.style.setProperty('--wr-border-glow-angle', `${REST_ANGLE}deg`);\n return () => host.classList.remove('wr-border-glow--looping');\n }\n\n let raf = 0;\n let angle = REST_ANGLE;\n // `null` until there is a frame to measure from: the first frame, and the\n // first after the pointer leaves or the card scrolls back into view, pick up\n // the painted angle instead of jumping by the time that passed.\n let last: number | null = null;\n\n const tick = (now: number): void => {\n raf = requestAnimationFrame(tick);\n if (this.pointerInside) {\n last = null;\n return;\n }\n if (last === null) {\n angle = this.paintedAngle();\n } else {\n // By elapsed time rather than per frame, so the pace is the same at\n // any refresh rate. `speed` is in seconds, the timestamps in ms.\n const lap = this.speed() * 1000;\n const step = Math.min(now - last, MAX_LOOP_STEP_MS);\n if (lap > 0) angle = (angle + (360 * step) / lap) % 360;\n }\n last = now;\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n };\n const start = (): void => {\n if (raf === 0) raf = requestAnimationFrame(tick);\n };\n const pause = (): void => {\n cancelAnimationFrame(raf);\n raf = 0;\n last = null;\n };\n\n // Off screen the loop stops asking for frames. Without an observer (jsdom)\n // it simply runs.\n let observer: IntersectionObserver | null = null;\n if (typeof IntersectionObserver === 'undefined') {\n start();\n } else {\n observer = new IntersectionObserver(entries => (entries[entries.length - 1].isIntersecting ? start() : pause()));\n observer.observe(host);\n }\n\n return () => {\n observer?.disconnect();\n pause();\n host.classList.remove('wr-border-glow--looping');\n };\n }\n\n /** The angle the host paints now — the pointer's, a sweep's or the loop's own. */\n private paintedAngle(): number {\n const angle = Number.parseFloat(this.el.nativeElement.style.getPropertyValue('--wr-border-glow-angle'));\n return Number.isNaN(angle) ? REST_ANGLE : angle;\n }\n}\n","<span class=\"wr-border-glow__edge\" aria-hidden=\"true\"></span>\n<div class=\"wr-border-glow__inner\">\n <ng-content />\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-bits-border-glow.mjs","names":[],"sources":["../../../projects/lib/bits/border-glow/border-glow.ts","../../../projects/lib/bits/border-glow/border-glow.html"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Port of the reactbits.dev BorderGlow effect — a cursor-tracked cone of\n * mesh-gradient light that follows the pointer along the card's perimeter,\n * with an outer halo, an optional auto-sweep on mount and an optional endless\n * loop.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport { Component, ElementRef, PLATFORM_ID, ViewEncapsulation, computed, effect, inject, input } from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { isSafeCssValue, numAttr } from 'ngwr/utils';\n\nconst RADIANS_TO_DEG = 180 / Math.PI;\n\n/**\n * Where a looping cone rests under reduced motion, and where it starts when\n * nothing has written an angle yet — the stylesheet's own default.\n */\nconst REST_ANGLE = 45;\n\n/**\n * The most frame time one loop step may count, in ms. A hidden tab, a long task\n * or a debugger stops frames without telling the loop, and the first frame back\n * would otherwise jump the cone by the whole time away.\n */\nconst MAX_LOOP_STEP_MS = 100;\n\n/** `'40 80 80'`, `'268 100% 76%'`, `'40deg 80% 80%'` — the bare HSL parts `glowColor` has always taken. */\nconst HSL_PARTS = /^\\s*([\\d.]+)(?:deg)?\\s+([\\d.]+)%?\\s+([\\d.]+)%?\\s*$/;\n\nconst GLOW_OPACITY_STEPS = [100, 60, 50, 40, 30, 20, 10] as const;\nconst GLOW_VAR_SUFFIXES = ['', '-60', '-50', '-40', '-30', '-20', '-10'] as const;\n\nconst GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%'] as const;\n// Every name a nullable colour input writes is the `--wr-border-glow-input-*`\n// twin of a hook, never the hook itself: `_rules.scss` reads\n// `var(--wr-border-glow-input-x, var(--wr-border-glow-x))`, so an author's own\n// value on the hook survives the input and is read again once it is unset.\nconst GRADIENT_VAR_NAMES = [\n '--wr-border-glow-input-gradient-one',\n '--wr-border-glow-input-gradient-two',\n '--wr-border-glow-input-gradient-three',\n '--wr-border-glow-input-gradient-four',\n '--wr-border-glow-input-gradient-five',\n '--wr-border-glow-input-gradient-six',\n '--wr-border-glow-input-gradient-seven',\n] as const;\nconst GRADIENT_COLOR_INDEX = [0, 1, 2, 0, 1, 2, 1] as const;\n\nconst DEFAULT_COLORS: readonly string[] = ['#c084fc', '#f472b6', '#38bdf8'];\n\nfunction buildGlowVars(glowColor: string, intensity: number): Record<string, string> | null {\n const parts = HSL_PARTS.exec(glowColor);\n // Anything else is a CSS colour, interpolated into a `color-mix()` — so it is\n // held to the containment check `colors` and `backgroundColor` pass, and an\n // unsafe value leaves the halo to the theme.\n if (!parts && !isSafeCssValue(glowColor)) return null;\n const base = parts\n ? `${Number.parseFloat(parts[1])}deg ${Number.parseFloat(parts[2])}% ${Number.parseFloat(parts[3])}%`\n : glowColor.trim();\n const out: Record<string, string> = {};\n for (let i = 0; i < GLOW_OPACITY_STEPS.length; i++) {\n const alpha = Math.min(GLOW_OPACITY_STEPS[i] * intensity, 100);\n out[`--wr-border-glow-input-color${GLOW_VAR_SUFFIXES[i]}`] = parts\n ? `hsl(${base} / ${alpha}%)`\n : `color-mix(in srgb, ${base} ${alpha}%, transparent)`;\n }\n return out;\n}\n\nfunction buildGradientVars(colors: readonly string[]): Record<string, string> {\n const out: Record<string, string> = {};\n // Each entry is INTERPOLATED into the `radial-gradient()` below, so a value\n // that balances the parens around it escapes into a sibling layer and can add\n // a `url()` the browser fetches. One unsafe entry discards the palette — the\n // same all-or-nothing rule `wr-gradient-text` follows, for the same reason.\n const palette = colors.length > 0 && colors.every(color => isSafeCssValue(color)) ? colors : DEFAULT_COLORS;\n for (let i = 0; i < GRADIENT_VAR_NAMES.length; i++) {\n const colour = palette[Math.min(GRADIENT_COLOR_INDEX[i], palette.length - 1)];\n out[GRADIENT_VAR_NAMES[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${colour} 0px, transparent 50%)`;\n }\n out['--wr-border-glow-input-gradient-base'] = `linear-gradient(${palette[0]} 0 100%)`;\n return out;\n}\n\ninterface AnimateOptions {\n readonly start?: number;\n readonly end?: number;\n readonly duration?: number;\n readonly delay?: number;\n readonly ease?: (t: number) => number;\n readonly onUpdate: (v: number) => void;\n readonly onEnd?: () => void;\n}\n\n/** Tiny rAF-driven tweener. Returns a cancel function so callers can clean up on destroy. */\nfunction animateValue(opts: AnimateOptions): () => void {\n const { start = 0, end = 100, duration = 1000, delay = 0, ease = (t: number): number => t, onUpdate, onEnd } = opts;\n let raf = 0;\n let cancelled = false;\n\n const startAt = performance.now() + delay;\n const tick = (): void => {\n if (cancelled) return;\n const elapsed = performance.now() - startAt;\n const t = Math.min(Math.max(elapsed / duration, 0), 1);\n onUpdate(start + (end - start) * ease(t));\n if (t < 1) raf = requestAnimationFrame(tick);\n else if (onEnd) onEnd();\n };\n\n const timer = setTimeout(() => {\n raf = requestAnimationFrame(tick);\n }, delay);\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n cancelAnimationFrame(raf);\n };\n}\n\n/**\n * Cursor-tracked glowing border card. A cone of light follows the pointer\n * along the card's perimeter, brightening as the cursor approaches an edge.\n *\n * - Outer halo via stacked `box-shadow` on an inner `.edge-light` element.\n * - Inner bloom near the edges, painted with a normal blend by default and\n * overridable through `--wr-border-glow-fill-blend` (subtle; control the\n * strength via `[fillOpacity]`).\n * - Optional `[animated]` mode performs a one-shot sweep on mount, useful\n * for drawing attention to a freshly-rendered card.\n * - Optional `[loop]` mode circles the perimeter for ever, edge fully lit —\n * a frame for a call to action rather than a reaction to the cursor.\n *\n * @example\n * ```html\n * <wr-border-glow [animated]=\"true\" [colors]=\"['#c084fc', '#f472b6', '#38bdf8']\">\n * <h3>Hover me</h3>\n * <p>The border lights up where the cursor is.</p>\n * </wr-border-glow>\n * ```\n *\n * @see https://www.reactbits.dev/components/border-glow (visual reference)\n */\n@Component({\n selector: 'wr-border-glow',\n templateUrl: './border-glow.html',\n styleUrl: './border-glow.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-border-glow',\n // The geometry is written whatever the inputs say — none of these can be\n // unset — so each goes on the host as a binding to the hook's own name, the\n // way `wr-threads` binds `--wr-threads-count`. Bound here rather than through\n // the colour bag below, because `gen:css-vars` reads host bindings and literal\n // `setProperty` names and nothing else: written through the bag, the four\n // hooks the stylesheet declares were documented as a rule's to set while every\n // instance overwrote them inline.\n '[style.--wr-border-glow-radius.px]': 'borderRadius()',\n '[style.--wr-border-glow-padding.px]': 'glowRadius()',\n '[style.--wr-border-glow-cone-spread]': 'coneSpread()',\n '[style.--wr-border-glow-edge-sensitivity]': 'edgeSensitivity()',\n '[style.--wr-border-glow-fill-opacity]': 'fillOpacity()',\n '(pointerenter)': 'onPointerEnter()',\n '(pointermove)': 'onPointerMove($event)',\n '(pointerleave)': 'onPointerLeave()',\n },\n})\nexport class WrBorderGlow {\n /**\n * Card background fill. When unset, `--wr-border-glow-bg` decides, which defaults to\n * the theme surface. Written to `--wr-border-glow-input-bg` rather than to the hook,\n * so a `--wr-border-glow-bg` an author set on the element — inline or from a rule —\n * stays there under this input and is read again when it is unset. The same goes\n * for `glowColor` and `colors`.\n */\n readonly backgroundColor = input<string | null>(null);\n\n /** Corner radius in pixels. @default 28 */\n readonly borderRadius = input(28, { transform: numAttr(28) });\n\n /** Halo extent in pixels — how far the outer glow reaches past the card edge. @default 40 */\n readonly glowRadius = input(40, { transform: numAttr(40) });\n\n /**\n * Halo opacity multiplier (1 = full strength).\n *\n * Applies only alongside an explicit `glowColor`: the halo variables are\n * built from the two together, so on the default palette this input is read\n * and never reaches a declaration.\n *\n * @default 1\n */\n readonly glowIntensity = input(1, { transform: numAttr(1) });\n\n /** Width of the lit cone as a percentage of the perimeter. @default 25 */\n readonly coneSpread = input(25, { transform: numAttr(25) });\n\n /** How sharply the halo fades as the cursor leaves the edge. Lower = wider falloff. @default 30 */\n readonly edgeSensitivity = input(30, { transform: numAttr(30) });\n\n /** Strength of the interior bloom near edges (blend: `--wr-border-glow-fill-blend`). @default 0.5 */\n readonly fillOpacity = input(0.5, { transform: numAttr(0.5) });\n\n /**\n * Halo colour: any CSS colour (`'#3567ff'`, `'var(--wr-color-primary)'`), or\n * the bare `'H S L'` parts (no commas) it has always taken. A value that\n * could escape the `color-mix()` it is composed into is ignored. When unset,\n * the theme decides: a deep amber on light surfaces, pale amber on dark.\n * Note `glowIntensity` only applies alongside an explicit `glowColor`.\n */\n readonly glowColor = input<string | null>(null);\n\n /**\n * Mesh-gradient palette for the colored border slice. When unset, the\n * theme decides: deep tones on light, pastels on dark.\n */\n readonly colors = input<readonly string[] | null>(null);\n\n /** Play a one-shot perimeter sweep on mount, then fade out. @default false */\n readonly animated = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Circle the perimeter for ever at a constant pace, edge fully lit. The\n * pointer still drives while it is over the card, and the loop picks up from\n * where it left. Replaces the `animated` sweep; under reduced motion the edge\n * stays lit and still.\n *\n * @default false\n */\n readonly loop = input(false, { transform: coerceBooleanProperty });\n\n /** Seconds per lap while `loop` is on. @default 6 */\n readonly speed = input(6, { transform: numAttr(6) });\n\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n /** Whether a pointer is over the card — the loop holds still while it drives. */\n private pointerInside = false;\n\n /**\n * Inline CSS variable bag — the colour inputs, recomputed when one changes. Every\n * name in it is an `--wr-border-glow-input-*` twin that is there only while its\n * input is set; the geometry, which is always set, is bound in `host`.\n */\n protected readonly cssVars = computed<Record<string, string>>(() => {\n const vars: Record<string, string> = {};\n const colors = this.colors();\n if (colors !== null) Object.assign(vars, buildGradientVars(colors));\n // Colour vars are written only when set — otherwise the stylesheet's\n // per-theme defaults (light vs dark) take over.\n // Composed, not passed through: `_rules.scss` reads this var inside a\n // `linear-gradient()` and again in a `background` shorthand, where a `url()`\n // is valid and fetches. An audit bound `url(\"…\")` here and recorded the\n // request. Validated like its `colors` sibling on this same component —\n // leaving one of the two unchecked is two rules inside one component.\n const bg = this.backgroundColor();\n if (bg !== null && isSafeCssValue(bg)) vars['--wr-border-glow-input-bg'] = bg;\n const glow = this.glowColor();\n const glowVars = glow === null ? null : buildGlowVars(glow, this.glowIntensity());\n if (glowVars !== null) Object.assign(vars, glowVars);\n return vars;\n });\n\n constructor() {\n // Apply the variable bag to the host on every colour input change. An effect\n // rather than one `host: { '[style.--foo]': '...' }` per name, because a\n // palette is seven names and a glow colour another seven, and a single\n // `setProperty` loop handles the lot. What left the bag comes off the host, or\n // an input set and then unset stays over the stylesheet's default for good.\n // Every name here is an `--wr-border-glow-input-*` twin nothing else declares\n // inline, so removing one takes nothing of the author's: their value sits on\n // the hook itself, which this never writes.\n let written: string[] = [];\n effect(() => {\n const { style } = this.el.nativeElement;\n const vars = this.cssVars();\n for (const k of written) {\n if (!Object.hasOwn(vars, k)) style.removeProperty(k);\n }\n written = Object.keys(vars);\n for (const k of written) style.setProperty(k, vars[k]);\n });\n\n if (!this.isBrowser) return;\n\n // Auto-sweep on mount — fires once when `animated` is true, unless `loop`\n // replaces it.\n effect(onCleanup => {\n if (!this.animated() || this.loop()) return;\n onCleanup(this.runAutoSweep());\n });\n\n // Endless loop. Reduced motion is read here, so a change to the preference\n // swaps the rotation for a still, lit edge and back.\n effect(onCleanup => {\n if (!this.loop()) return;\n onCleanup(this.runLoop(this.platform.prefersReducedMotion()));\n });\n }\n\n protected onPointerEnter(): void {\n this.pointerInside = true;\n }\n\n /** Hands the cone back to the loop, re-lit, from wherever the pointer left it. */\n protected onPointerLeave(): void {\n this.pointerInside = false;\n if (this.loop()) this.el.nativeElement.style.setProperty('--wr-border-glow-edge-proximity', '100');\n }\n\n /** Pointer-driven update of `--wr-border-glow-angle` + `--wr-border-glow-edge-proximity`. */\n protected onPointerMove(event: PointerEvent): void {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const cx = rect.width / 2;\n const cy = rect.height / 2;\n\n const dx = x - cx;\n const dy = y - cy;\n\n // `edge-proximity`: 0 at centre, 1 at the perimeter (whichever axis hits first).\n let edge = 0;\n if (dx !== 0 || dy !== 0) {\n const kx = dx === 0 ? Infinity : cx / Math.abs(dx);\n const ky = dy === 0 ? Infinity : cy / Math.abs(dy);\n edge = Math.min(Math.max(1 / Math.min(kx, ky), 0), 1);\n }\n\n // `cursor-angle`: 0deg = top, increases clockwise.\n let angle = 0;\n if (dx !== 0 || dy !== 0) {\n angle = Math.atan2(dy, dx) * RADIANS_TO_DEG + 90;\n if (angle < 0) angle += 360;\n }\n\n host.style.setProperty('--wr-border-glow-edge-proximity', (edge * 100).toFixed(3));\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n }\n\n /** One-shot perimeter sweep: in → around → out, mirroring the reactbits behaviour. */\n private runAutoSweep(): () => void {\n // Reduced motion: skip the decorative mount sweep. The pointer-driven\n // glow stays — it maps 1:1 to the cursor with no autonomous motion.\n if (this.platform.prefersReducedMotion()) return () => undefined;\n const host = this.el.nativeElement;\n const ANGLE_START = 110;\n const ANGLE_END = 465;\n host.classList.add('wr-border-glow--sweeping');\n host.style.setProperty('--wr-border-glow-angle', `${ANGLE_START}deg`);\n\n const easeOutCubic = (t: number): number => 1 - (1 - t) ** 3;\n const easeInCubic = (t: number): number => t * t * t;\n const lerpAngle = (v: number): string => `${(ANGLE_END - ANGLE_START) * (v / 100) + ANGLE_START}deg`;\n\n // 1. Ramp edge-proximity up to 100 quickly.\n const cancelA = animateValue({\n duration: 500,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n });\n // 2. Sweep angle first half (ease-in).\n const cancelB = animateValue({\n duration: 1500,\n end: 50,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 3. Sweep angle second half (ease-out).\n const cancelC = animateValue({\n delay: 1500,\n duration: 2250,\n start: 50,\n end: 100,\n ease: easeOutCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 4. Fade proximity back to 0.\n const cancelD = animateValue({\n delay: 2500,\n duration: 1500,\n start: 100,\n end: 0,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n onEnd: () => host.classList.remove('wr-border-glow--sweeping'),\n });\n\n // Returned rather than registered on `destroyRef`: this method runs from an\n // effect, so registering there stacked one more teardown per re-run and none\n // of them fired until the component died. The caller ties it to the effect's\n // own cleanup instead.\n return () => {\n cancelA();\n cancelB();\n cancelC();\n cancelD();\n host.classList.remove('wr-border-glow--sweeping');\n };\n }\n\n /** Endless perimeter loop at a constant pace. Returns its canceller, like `runAutoSweep`. */\n private runLoop(reducedMotion: boolean): () => void {\n const host = this.el.nativeElement;\n host.classList.add('wr-border-glow--looping');\n host.style.setProperty('--wr-border-glow-edge-proximity', '100');\n\n // Reduced motion: no rotation at all. The edge stays lit and still, so a\n // card that uses it as a frame keeps one.\n if (reducedMotion) {\n host.style.setProperty('--wr-border-glow-angle', `${REST_ANGLE}deg`);\n return () => host.classList.remove('wr-border-glow--looping');\n }\n\n let raf = 0;\n let angle = REST_ANGLE;\n // `null` until there is a frame to measure from: the first frame, and the\n // first after the pointer leaves or the card scrolls back into view, pick up\n // the painted angle instead of jumping by the time that passed.\n let last: number | null = null;\n\n const tick = (now: number): void => {\n raf = requestAnimationFrame(tick);\n if (this.pointerInside) {\n last = null;\n return;\n }\n if (last === null) {\n angle = this.paintedAngle();\n } else {\n // By elapsed time rather than per frame, so the pace is the same at\n // any refresh rate. `speed` is in seconds, the timestamps in ms.\n const lap = this.speed() * 1000;\n const step = Math.min(now - last, MAX_LOOP_STEP_MS);\n if (lap > 0) angle = (angle + (360 * step) / lap) % 360;\n }\n last = now;\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n };\n const start = (): void => {\n if (raf === 0) raf = requestAnimationFrame(tick);\n };\n const pause = (): void => {\n cancelAnimationFrame(raf);\n raf = 0;\n last = null;\n };\n\n // Off screen the loop stops asking for frames. Without an observer (jsdom)\n // it simply runs.\n let observer: IntersectionObserver | null = null;\n if (typeof IntersectionObserver === 'undefined') {\n start();\n } else {\n observer = new IntersectionObserver(entries => (entries[entries.length - 1].isIntersecting ? start() : pause()));\n observer.observe(host);\n }\n\n return () => {\n observer?.disconnect();\n pause();\n host.classList.remove('wr-border-glow--looping');\n };\n }\n\n /** The angle the host paints now — the pointer's, a sweep's or the loop's own. */\n private paintedAngle(): number {\n const angle = Number.parseFloat(this.el.nativeElement.style.getPropertyValue('--wr-border-glow-angle'));\n return Number.isNaN(angle) ? REST_ANGLE : angle;\n }\n}\n","<span class=\"wr-border-glow__edge\" aria-hidden=\"true\"></span>\n<div class=\"wr-border-glow__inner\">\n <ng-content />\n</div>\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import * as i0 from "@angular/core";
2
- import { Component, DestroyRef, ElementRef, NgZone, PLATFORM_ID, ViewEncapsulation, afterNextRender, computed, effect, inject, input, signal, untracked, viewChild } from "@angular/core";
2
+ import { Component, DestroyRef, ElementRef, NgZone, PLATFORM_ID, ViewEncapsulation, afterNextRender, afterRenderEffect, computed, effect, inject, input, signal, untracked, viewChild } from "@angular/core";
3
3
  import { isPlatformBrowser } from "@angular/common";
4
4
  import { numAttr, resolveCssColor, watchColorScheme } from "ngwr/utils";
5
5
  import { WrPlatform } from "ngwr/platform";
@@ -80,29 +80,26 @@ var WrDotGrid = class WrDotGrid {
80
80
  effect(() => {
81
81
  this.pitch();
82
82
  untracked(() => {
83
- if (!this.ctx) return;
84
- this.placeDots();
85
- this.requestDraw();
83
+ if (this.ctx) this.placeDots();
86
84
  });
87
85
  });
88
- effect(() => {
86
+ afterRenderEffect(() => {
87
+ if (!this.booted()) return;
88
+ const still = this.platform.prefersReducedMotion();
89
+ this.pitch();
89
90
  this.size();
90
91
  this.dotColor();
91
92
  this.activeColor();
92
93
  this.radius();
93
94
  this.strength();
94
- untracked(() => this.requestDraw());
95
+ untracked(() => {
96
+ if (still) this.rest();
97
+ this.requestDraw();
98
+ });
95
99
  });
96
100
  watchColorScheme(() => this.requestDraw());
97
101
  effect((onCleanup) => {
98
- if (!this.booted()) return;
99
- if (this.platform.prefersReducedMotion()) {
100
- untracked(() => {
101
- this.rest();
102
- this.draw();
103
- });
104
- return;
105
- }
102
+ if (!this.booted() || this.platform.prefersReducedMotion()) return;
106
103
  onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact())));
107
104
  });
108
105
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-bits-dot-grid.mjs","names":[],"sources":["../../../projects/lib/bits/dot-grid/dot-grid.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * After the Dot Grid background on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/dot-grid). The effect is the same\n * idea; the code is ngwr's own: a 2D canvas, one spring per dot, and a frame\n * loop that sleeps whenever nothing moves.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** One dot: where the grid puts it, how far it has been pushed, and how lit it is. */\ninterface Dot {\n readonly x: number;\n readonly y: number;\n /** Offset from rest, in CSS pixels. */\n ox: number;\n oy: number;\n /** Velocity of that offset, in CSS pixels per 60 Hz frame. */\n vx: number;\n vy: number;\n /** How far toward the active colour, 0..1. */\n glow: number;\n}\n\n/**\n * The most bitmap pixels the canvas is backed with, the bound `wr-waves`\n * uses: a hero keeps the full 2x, and a very wide one steps down toward 1x\n * rather than growing a bitmap every frame has to clear and fill.\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The smallest pitch the grid is laid out at. A 1px gap on a full-page host\n * is two million dots, which no frame can draw; 4px is already a fine mesh.\n */\nconst MIN_GAP = 4;\n\n/** A 60 Hz frame, in ms — the unit the spring constants below are tuned in. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, so a tab that wakes after a pause does not fling the dots. */\nconst MAX_STEP = 3;\n\n/** Spring pull back toward the target offset, per frame. */\nconst SPRING = 0.1;\n\n/** Velocity a dot keeps from one frame to the next; under 1 is what lets it settle. */\nconst DAMPING = 0.8;\n\n/** Share of the gap to its target a dot's glow closes per frame while it brightens. */\nconst GLOW_RISE = 0.3;\n\n/** The same while it fades — slower, so the pointer leaves a short trail behind it. */\nconst GLOW_FALL = 0.06;\n\n/** Below this, in pixels or pixels per frame, a dot counts as at rest. */\nconst REST = 0.01;\n\n/** Below this a glow counts as settled: under one step of an 8-bit alpha. */\nconst GLOW_REST = 1 / 256;\n\n/** Alpha steps the lit dots are batched into, one path and one fill each. */\nconst GLOW_LEVELS = 16;\n\n/** What the dots fall back to when the colour cannot be read: `--wr-border-strong` in light. */\nconst DOT_FALLBACK = 'rgb(156, 163, 175)';\n\n/** What the lit dots fall back to: the shipped primary. */\nconst ACTIVE_FALLBACK = 'rgb(57, 105, 226)';\n\nconst TAU = Math.PI * 2;\n\n/**\n * Dot-grid background — evenly spaced dots on a 2D canvas that light up in the\n * active colour near the pointer and are pushed away from it, both by the same\n * falloff, then spring back. Fills its nearest positioned ancestor; put it\n * behind hero copy or section content.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * dots answer a pointer that is over the content in front of them rather than\n * over the canvas — the host itself takes no pointer events and never steals a\n * click. Placed directly in a shadow root, where it has no parent element, it\n * follows the pointer on the shadow host; with neither it listens nowhere and\n * the grid stays unlit. Content placed after it needs `position: relative` (or\n * any z-index) to paint above it, as with any absolutely positioned layer.\n *\n * The grid is laid out in the host's own box, before transforms, so one that\n * boots inside a scaling-in panel (a dialog opening) is not stretched when the\n * animation ends, and the pointer is mapped back through the transform.\n *\n * Decorative: the canvas is `aria-hidden`, the host has no role and nothing in\n * it takes focus. The frame loop runs only while something moves — a still\n * grid asks for no frames at all — and never while the host is off screen.\n *\n * Under `prefers-reduced-motion` one still grid is drawn, in the resting\n * colour, and nothing follows the pointer; it is drawn again when the colours\n * or the page's colour scheme change. The preference is read live.\n *\n * Colours are theme tokens by default — the dots `--wr-border-strong`, the lit\n * ones `--wr-color-primary` — through the `--wr-dot-grid-dot-color` and\n * `--wr-dot-grid-active-color` hooks, and resolved before they reach the\n * canvas, which reads neither a `var()` nor a `light-dark()`.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-dot-grid />\n * <h1 style=\"position: relative\">Hero copy above the dots</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/dot-grid\n */\n@Component({\n selector: 'wr-dot-grid',\n template: '<canvas #canvas class=\"wr-dot-grid__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './dot-grid.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-dot-grid',\n // Feed the CSS stand-in grid (see `styles/_rules.scss`) so it draws at the\n // real pitch, size and colour, and switch it off once the canvas has painted.\n '[style.--wr-dot-grid-gap.px]': 'pitch()',\n '[style.--wr-dot-grid-dot-size.px]': 'size()',\n '[style.--wr-dot-grid-input-dot-color]': 'standInColor()',\n '[class.wr-dot-grid--painted]': 'painted()',\n },\n})\nexport class WrDotGrid {\n /** Dot diameter in px. @default 3 */\n readonly dotSize = input(3, { transform: numAttr(3) });\n\n /** Distance between dot centres in px, never under 4. @default 24 */\n readonly gap = input(24, { transform: numAttr(24) });\n\n /** Resting dot colour, any CSS colour. When unset, empty or blank, `--wr-dot-grid-dot-color` decides. */\n readonly dotColor = input<string | null>(null);\n\n /** Colour a dot takes near the pointer, any CSS colour. When unset, empty or blank, `--wr-dot-grid-active-color` decides. */\n readonly activeColor = input<string | null>(null);\n\n /** How far from the pointer a dot still reacts, in px. @default 120 */\n readonly radius = input(120, { transform: numAttr(120) });\n\n /**\n * How far a dot right by the pointer is pushed, in px; the push fades out\n * toward `radius`. `0` keeps the dots in place and only lights them, and a\n * negative value pulls them in. @default 12\n */\n readonly strength = input(12, { transform: numAttr(12) });\n\n /** The pitch the grid is laid out at: `gap`, floored. */\n protected readonly pitch = computed(() => Math.max(MIN_GAP, this.gap()));\n\n /** The dot diameter drawn: `dotSize`, never negative. */\n protected readonly size = computed(() => Math.max(0, this.dotSize()));\n\n /** A colour the author chose, published so the stand-in draws in it too; `null` while the hook decides. */\n protected readonly standInColor = computed(() => (this.dotColor() ?? '').trim() || null);\n\n /** The colours the canvas resolves: an input that is unset, empty or blank leaves the hook in charge. */\n private readonly dotExpression = computed(() => (this.dotColor() ?? '').trim() || 'var(--wr-dot-grid-dot-color)');\n private readonly activeExpression = computed(\n () => (this.activeColor() ?? '').trim() || 'var(--wr-dot-grid-active-color)'\n );\n\n /**\n * Flips once the canvas has drawn, which retires the CSS stand-in grid.\n *\n * A canvas cannot be prerendered, so on an SSR'd page it is blank until the\n * bundle has loaded and hydrated. The stylesheet paints the same dots at the\n * same pitch from the prerendered HTML in the meantime, and this hands over.\n */\n protected readonly painted = signal(false);\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private ctx: CanvasRenderingContext2D | null = null;\n /** The host's layout box, in CSS pixels before any transform: what the grid is laid out in. */\n private box = { width: 0, height: 0 };\n /** Bitmap pixels per CSS pixel the canvas is backed at; 0 until the first fit. */\n private ratio = 0;\n private dots: Dot[] = [];\n /** Lit dots by alpha step, reused frame to frame. */\n private readonly lit: Dot[][] = Array.from({ length: GLOW_LEVELS }, () => []);\n /** The pointer, in viewport coordinates, and whether it is over the parent at all. */\n private readonly pointer = { clientX: 0, clientY: 0, inside: false };\n private rafId = 0;\n private lastTime = -1;\n /** Schedules a frame while motion is allowed; `null` under reduced motion. */\n private wake: (() => void) | null = null;\n\n /** Flips once `boot()` has a context and a grid — everything after waits for it. */\n private readonly booted = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // A new pitch is a new grid. It leaves the bitmap alone, so the frame it\n // asks for repaints over the old grid rather than over a blank canvas.\n effect(() => {\n this.pitch();\n untracked(() => {\n if (!this.ctx) return;\n this.placeDots();\n this.requestDraw();\n });\n });\n\n // Everything else only changes what the next frame draws. A sleeping loop\n // or a still grid has no next frame, so ask for one.\n effect(() => {\n this.size();\n this.dotColor();\n this.activeColor();\n this.radius();\n this.strength();\n untracked(() => this.requestDraw());\n });\n\n // A drawn grid keeps the colours it was drawn in, and the loop sleeps when\n // nothing moves, so a scheme switch asks for a frame to re-resolve them.\n watchColorScheme(() => this.requestDraw());\n\n // Motion or a still grid, decided live.\n effect(onCleanup => {\n if (!this.booted()) return;\n if (this.platform.prefersReducedMotion()) {\n untracked(() => {\n this.rest();\n this.draw();\n });\n return;\n }\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact())));\n });\n }\n\n // Setup\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n this.ctx = canvas.getContext('2d');\n if (!this.ctx) return;\n\n // `clientWidth` and not `getBoundingClientRect()`: the rect is the box after\n // transforms, so a grid booted inside a dialog's scale-in would be laid out\n // for 96% of its box and stretched by CSS once the animation ended. The\n // observer's first entry follows with the exact, sub-pixel size.\n const hostEl = this.host.nativeElement;\n this.fit(hostEl.clientWidth, hostEl.clientHeight);\n this.placeDots();\n\n // The box is the observer's to follow, transforms excluded. A window resize\n // is for the pixel ratio, which browser zoom and a move to another screen\n // change with no box change at all.\n const onResize = (): void => this.resize(this.box.width, this.box.height);\n window.addEventListener('resize', onResize);\n const ro = new ResizeObserver(entries => {\n const rect = entries[entries.length - 1]?.contentRect;\n if (rect) this.resize(rect.width, rect.height);\n });\n ro.observe(hostEl);\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', onResize);\n ro.disconnect();\n });\n this.booted.set(true);\n }\n\n /** Follow the pointer and draw on demand; returns what stops both. */\n private interact(): () => void {\n const hostEl = this.host.nativeElement;\n // The parent, not the host: content in front of the canvas receives the\n // pointer, and its events bubble to the parent, never to a sibling. Placed\n // directly in a shadow root it has none, and the shadow host is where those\n // composed events land. Never the host itself, which takes no pointer\n // events and so could never hear one.\n const root = hostEl.getRootNode();\n const target =\n hostEl.parentElement ?? (root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null);\n\n // Off screen the grid asks for no frames. Without an observer (jsdom) it\n // counts as visible.\n let visible = typeof IntersectionObserver === 'undefined';\n const wake = (): void => {\n if (visible && this.rafId === 0) this.rafId = requestAnimationFrame(time => this.tick(time));\n };\n const halt = (): void => {\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.lastTime = -1;\n };\n\n const onMove = (event: PointerEvent): void => {\n this.pointer.clientX = event.clientX;\n this.pointer.clientY = event.clientY;\n this.pointer.inside = true;\n wake();\n };\n const onLeave = (): void => {\n this.pointer.inside = false;\n wake();\n };\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerdown', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => {\n visible = entries[entries.length - 1].isIntersecting;\n if (visible) wake();\n else halt();\n });\n io?.observe(hostEl);\n\n this.wake = wake;\n wake();\n\n return () => {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerdown', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n io?.disconnect();\n halt();\n this.wake = null;\n this.pointer.inside = false;\n };\n }\n\n /** Draw the grid as it should look now: a frame while motion is allowed, at once under reduced motion. */\n private requestDraw(): void {\n if (!this.booted()) return;\n if (this.wake) this.wake();\n else if (this.platform.prefersReducedMotion()) this.draw();\n }\n\n /** Fit the canvas to a new box or pixel ratio and, when that cleared it, repaint at once. */\n private resize(width: number, height: number): void {\n const moved = width !== this.box.width || height !== this.box.height;\n if (!this.fit(width, height)) return;\n if (moved) this.placeDots();\n // Observer entries arrive after the frame's own callbacks and before its\n // paint, so a frame asked for here would leave this one blank — with the\n // stand-in already retired, a resize drag would flicker every frame.\n this.draw();\n // Re-placed dots are at rest and unlit, which is right unless the pointer\n // is over them; a running loop simply carries on.\n if (this.pointer.inside) this.wake?.();\n }\n\n /**\n * Back the canvas for a `width` × `height` box, in CSS pixels, at the screen's\n * pixel ratio. Returns whether anything changed: writing `canvas.width` clears\n * the bitmap even to the value it already has, so an unchanged box is left alone.\n */\n private fit(width: number, height: number): boolean {\n // Capped at 2 and by the pixel budget, never below 1 — a bitmap the size of\n // the CSS box blurs every dot on a 2x screen.\n const area = width * height;\n const budget = area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2;\n const ratio = Math.max(1, Math.min(window.devicePixelRatio || 1, 2, budget));\n if (width === this.box.width && height === this.box.height && ratio === this.ratio) return false;\n\n this.box = { width, height };\n this.ratio = ratio;\n const canvas = this.canvasRef().nativeElement;\n canvas.width = Math.round(width * ratio);\n canvas.height = Math.round(height * ratio);\n // Resizing the bitmap resets the transform, so the scale goes on after it.\n // Every coordinate the draw uses is in CSS pixels.\n this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);\n return true;\n }\n\n /** Lay the dots out over the box, every one at rest. */\n private placeDots(): void {\n // One dot sits on the centre of the box and the rest step out from it by\n // the pitch — exactly where the stand-in's centred `background-position`\n // puts them, so the handover does not shift a single dot. One ring past\n // each edge, for the dots a push brings into view.\n const gap = this.pitch();\n const xs = this.axis(this.box.width, gap);\n const ys = this.axis(this.box.height, gap);\n const dots: Dot[] = [];\n for (const y of ys) {\n for (const x of xs) dots.push({ x, y, ox: 0, oy: 0, vx: 0, vy: 0, glow: 0 });\n }\n this.dots = dots;\n }\n\n /** Positions along one side of `length`: the centre, then every `gap` out to one step past each edge. */\n private axis(length: number, gap: number): number[] {\n const centre = length / 2;\n const steps = Math.ceil(centre / gap) + 1;\n const positions: number[] = [];\n for (let i = -steps; i <= steps; i++) positions.push(centre + i * gap);\n return positions;\n }\n\n /** Every dot back at rest and unlit, and the pointer gone. */\n private rest(): void {\n this.pointer.inside = false;\n for (const dot of this.dots) {\n dot.ox = dot.oy = dot.vx = dot.vy = dot.glow = 0;\n }\n }\n\n // Frame loop\n\n private tick(time: number): void {\n this.rafId = 0;\n // The springs are tuned per 60 Hz frame and stepped by the time that really\n // passed, so a 120 Hz screen does not run them twice as fast. A timestamp\n // that did not advance counts as one frame.\n const elapsed = time - this.lastTime;\n const step = this.lastTime < 0 || elapsed <= 0 ? 1 : Math.min(MAX_STEP, elapsed / FRAME_MS);\n this.lastTime = time;\n\n const moving = this.move(step);\n this.draw();\n\n // Nothing left to move: sleep until the pointer, a resize or a new input\n // asks for another frame.\n if (moving && this.wake) this.rafId = requestAnimationFrame(next => this.tick(next));\n else this.lastTime = -1;\n }\n\n /** Advance every dot by `step` frames; returns whether any is still moving. */\n private move(step: number): boolean {\n const reach = Math.max(0, this.radius());\n const push = this.strength();\n const active = this.pointer.inside && reach > 0;\n\n let px = 0;\n let py = 0;\n if (active) {\n // Read once a frame rather than once a move: a page that scrolls under a\n // still pointer moves the grid under it too. The rect is the box as\n // drawn, after any transform on it or an ancestor, and the grid is laid\n // out in the box before them, so the pointer is scaled back into it.\n const rect = this.host.nativeElement.getBoundingClientRect();\n px = (this.pointer.clientX - rect.left) * (rect.width > 0 ? this.box.width / rect.width : 1);\n py = (this.pointer.clientY - rect.top) * (rect.height > 0 ? this.box.height / rect.height : 1);\n }\n\n const reach2 = reach * reach;\n const pull = SPRING * step;\n const keep = Math.pow(DAMPING, step);\n const rise = 1 - Math.pow(1 - GLOW_RISE, step);\n const fall = 1 - Math.pow(1 - GLOW_FALL, step);\n\n let moving = false;\n for (const dot of this.dots) {\n let tx = 0;\n let ty = 0;\n let glow = 0;\n if (active) {\n const dx = dot.x - px;\n const dy = dot.y - py;\n const d2 = dx * dx + dy * dy;\n if (d2 < reach2) {\n const d = Math.sqrt(d2);\n const u = 1 - d / reach;\n // Smoothstep: no edge where the radius ends, and a flat top under the pointer.\n glow = u * u * (3 - 2 * u);\n if (d > 0) {\n tx = (dx / d) * push * glow;\n ty = (dy / d) * push * glow;\n }\n }\n }\n\n dot.vx = (dot.vx + (tx - dot.ox) * pull) * keep;\n dot.vy = (dot.vy + (ty - dot.oy) * pull) * keep;\n dot.ox += dot.vx * step;\n dot.oy += dot.vy * step;\n dot.glow += (glow - dot.glow) * (glow > dot.glow ? rise : fall);\n\n if (\n !moving &&\n (Math.abs(tx - dot.ox) > REST ||\n Math.abs(ty - dot.oy) > REST ||\n Math.abs(dot.vx) > REST ||\n Math.abs(dot.vy) > REST ||\n Math.abs(glow - dot.glow) > GLOW_REST)\n ) {\n moving = true;\n }\n }\n return moving;\n }\n\n private draw(): void {\n const ctx = this.ctx;\n if (!ctx) return;\n // Retire the stand-in only once something is really painted. Runs outside\n // Angular, so the `set` schedules the one host-binding update it needs.\n if (!this.painted()) this.painted.set(true);\n\n const canvas = this.canvasRef().nativeElement;\n const r = this.size() / 2;\n ctx.clearRect(0, 0, this.box.width, this.box.height);\n ctx.globalAlpha = 1;\n ctx.fillStyle = resolveCssColor(canvas, this.dotExpression(), DOT_FALLBACK);\n this.fillDots(ctx, this.dots, r);\n\n // Lit dots go over their resting selves in the active colour, at their\n // glow as alpha: the blend of the two colours without parsing either, so\n // an `oklch()` or a `display-p3` colour works as well as an `rgb()`.\n for (const level of this.lit) level.length = 0;\n let anyLit = false;\n for (const dot of this.dots) {\n const level = Math.round(dot.glow * GLOW_LEVELS);\n if (level > 0) {\n this.lit[Math.min(level, GLOW_LEVELS) - 1].push(dot);\n anyLit = true;\n }\n }\n if (!anyLit) return;\n\n ctx.fillStyle = resolveCssColor(canvas, this.activeExpression(), ACTIVE_FALLBACK);\n this.lit.forEach((dots, i) => {\n if (dots.length === 0) return;\n ctx.globalAlpha = (i + 1) / GLOW_LEVELS;\n this.fillDots(ctx, dots, r);\n });\n ctx.globalAlpha = 1;\n }\n\n /** One path for every dot in `dots`, filled once. */\n private fillDots(ctx: CanvasRenderingContext2D, dots: readonly Dot[], r: number): void {\n ctx.beginPath();\n for (const dot of dots) {\n const x = dot.x + dot.ox;\n const y = dot.y + dot.oy;\n ctx.moveTo(x + r, y);\n ctx.arc(x, y, r, 0, TAU);\n }\n ctx.fill();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-bits-dot-grid.mjs","names":[],"sources":["../../../projects/lib/bits/dot-grid/dot-grid.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * After the Dot Grid background on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/dot-grid). The effect is the same\n * idea; the code is ngwr's own: a 2D canvas, one spring per dot, and a frame\n * loop that sleeps whenever nothing moves.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n afterRenderEffect,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** One dot: where the grid puts it, how far it has been pushed, and how lit it is. */\ninterface Dot {\n readonly x: number;\n readonly y: number;\n /** Offset from rest, in CSS pixels. */\n ox: number;\n oy: number;\n /** Velocity of that offset, in CSS pixels per 60 Hz frame. */\n vx: number;\n vy: number;\n /** How far toward the active colour, 0..1. */\n glow: number;\n}\n\n/**\n * The most bitmap pixels the canvas is backed with, the bound `wr-waves`\n * uses: a hero keeps the full 2x, and a very wide one steps down toward 1x\n * rather than growing a bitmap every frame has to clear and fill.\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The smallest pitch the grid is laid out at. A 1px gap on a full-page host\n * is two million dots, which no frame can draw; 4px is already a fine mesh.\n */\nconst MIN_GAP = 4;\n\n/** A 60 Hz frame, in ms — the unit the spring constants below are tuned in. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, so a tab that wakes after a pause does not fling the dots. */\nconst MAX_STEP = 3;\n\n/** Spring pull back toward the target offset, per frame. */\nconst SPRING = 0.1;\n\n/** Velocity a dot keeps from one frame to the next; under 1 is what lets it settle. */\nconst DAMPING = 0.8;\n\n/** Share of the gap to its target a dot's glow closes per frame while it brightens. */\nconst GLOW_RISE = 0.3;\n\n/** The same while it fades — slower, so the pointer leaves a short trail behind it. */\nconst GLOW_FALL = 0.06;\n\n/** Below this, in pixels or pixels per frame, a dot counts as at rest. */\nconst REST = 0.01;\n\n/** Below this a glow counts as settled: under one step of an 8-bit alpha. */\nconst GLOW_REST = 1 / 256;\n\n/** Alpha steps the lit dots are batched into, one path and one fill each. */\nconst GLOW_LEVELS = 16;\n\n/** What the dots fall back to when the colour cannot be read: `--wr-border-strong` in light. */\nconst DOT_FALLBACK = 'rgb(156, 163, 175)';\n\n/** What the lit dots fall back to: the shipped primary. */\nconst ACTIVE_FALLBACK = 'rgb(57, 105, 226)';\n\nconst TAU = Math.PI * 2;\n\n/**\n * Dot-grid background — evenly spaced dots on a 2D canvas that light up in the\n * active colour near the pointer and are pushed away from it, both by the same\n * falloff, then spring back. Fills its nearest positioned ancestor; put it\n * behind hero copy or section content.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * dots answer a pointer that is over the content in front of them rather than\n * over the canvas — the host itself takes no pointer events and never steals a\n * click. Placed directly in a shadow root, where it has no parent element, it\n * follows the pointer on the shadow host; with neither it listens nowhere and\n * the grid stays unlit. Content placed after it needs `position: relative` (or\n * any z-index) to paint above it, as with any absolutely positioned layer.\n *\n * The grid is laid out in the host's own box, before transforms, so one that\n * boots inside a scaling-in panel (a dialog opening) is not stretched when the\n * animation ends, and the pointer is mapped back through the transform.\n *\n * Decorative: the canvas is `aria-hidden`, the host has no role and nothing in\n * it takes focus. The frame loop runs only while something moves — a still\n * grid asks for no frames at all — and never while the host is off screen.\n *\n * Under `prefers-reduced-motion` one still grid is drawn, in the resting\n * colour, and nothing follows the pointer; it is drawn again when the colours\n * or the page's colour scheme change. The preference is read live.\n *\n * Colours are theme tokens by default — the dots `--wr-border-strong`, the lit\n * ones `--wr-color-primary` — through the `--wr-dot-grid-dot-color` and\n * `--wr-dot-grid-active-color` hooks, and resolved before they reach the\n * canvas, which reads neither a `var()` nor a `light-dark()`.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-dot-grid />\n * <h1 style=\"position: relative\">Hero copy above the dots</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/dot-grid\n */\n@Component({\n selector: 'wr-dot-grid',\n template: '<canvas #canvas class=\"wr-dot-grid__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './dot-grid.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-dot-grid',\n // Feed the CSS stand-in grid (see `styles/_rules.scss`) so it draws at the\n // real pitch, size and colour, and switch it off once the canvas has painted.\n '[style.--wr-dot-grid-gap.px]': 'pitch()',\n '[style.--wr-dot-grid-dot-size.px]': 'size()',\n '[style.--wr-dot-grid-input-dot-color]': 'standInColor()',\n '[class.wr-dot-grid--painted]': 'painted()',\n },\n})\nexport class WrDotGrid {\n /** Dot diameter in px. @default 3 */\n readonly dotSize = input(3, { transform: numAttr(3) });\n\n /** Distance between dot centres in px, never under 4. @default 24 */\n readonly gap = input(24, { transform: numAttr(24) });\n\n /** Resting dot colour, any CSS colour. When unset, empty or blank, `--wr-dot-grid-dot-color` decides. */\n readonly dotColor = input<string | null>(null);\n\n /** Colour a dot takes near the pointer, any CSS colour. When unset, empty or blank, `--wr-dot-grid-active-color` decides. */\n readonly activeColor = input<string | null>(null);\n\n /** How far from the pointer a dot still reacts, in px. @default 120 */\n readonly radius = input(120, { transform: numAttr(120) });\n\n /**\n * How far a dot right by the pointer is pushed, in px; the push fades out\n * toward `radius`. `0` keeps the dots in place and only lights them, and a\n * negative value pulls them in. @default 12\n */\n readonly strength = input(12, { transform: numAttr(12) });\n\n /** The pitch the grid is laid out at: `gap`, floored. */\n protected readonly pitch = computed(() => Math.max(MIN_GAP, this.gap()));\n\n /** The dot diameter drawn: `dotSize`, never negative. */\n protected readonly size = computed(() => Math.max(0, this.dotSize()));\n\n /** A colour the author chose, published so the stand-in draws in it too; `null` while the hook decides. */\n protected readonly standInColor = computed(() => (this.dotColor() ?? '').trim() || null);\n\n /** The colours the canvas resolves: an input that is unset, empty or blank leaves the hook in charge. */\n private readonly dotExpression = computed(() => (this.dotColor() ?? '').trim() || 'var(--wr-dot-grid-dot-color)');\n private readonly activeExpression = computed(\n () => (this.activeColor() ?? '').trim() || 'var(--wr-dot-grid-active-color)'\n );\n\n /**\n * Flips once the canvas has drawn, which retires the CSS stand-in grid.\n *\n * A canvas cannot be prerendered, so on an SSR'd page it is blank until the\n * bundle has loaded and hydrated. The stylesheet paints the same dots at the\n * same pitch from the prerendered HTML in the meantime, and this hands over.\n */\n protected readonly painted = signal(false);\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private ctx: CanvasRenderingContext2D | null = null;\n /** The host's layout box, in CSS pixels before any transform: what the grid is laid out in. */\n private box = { width: 0, height: 0 };\n /** Bitmap pixels per CSS pixel the canvas is backed at; 0 until the first fit. */\n private ratio = 0;\n private dots: Dot[] = [];\n /** Lit dots by alpha step, reused frame to frame. */\n private readonly lit: Dot[][] = Array.from({ length: GLOW_LEVELS }, () => []);\n /** The pointer, in viewport coordinates, and whether it is over the parent at all. */\n private readonly pointer = { clientX: 0, clientY: 0, inside: false };\n private rafId = 0;\n private lastTime = -1;\n /** Schedules a frame while motion is allowed; `null` under reduced motion. */\n private wake: (() => void) | null = null;\n\n /** Flips once `boot()` has a context and a grid — everything after waits for it. */\n private readonly booted = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // A new pitch is a new grid. It leaves the bitmap alone, so the frame asked\n // for below repaints over the old grid rather than over a blank canvas.\n effect(() => {\n this.pitch();\n untracked(() => {\n if (this.ctx) this.placeDots();\n });\n });\n\n // Everything that changes the picture only changes what the next frame\n // draws. A sleeping loop has no next frame, so ask for one; a still grid has\n // none either, so it is drawn at once, every dot at rest and unlit — and so\n // it is the moment motion stops. After render rather than an `effect`, which\n // runs before a directive's host binding: a still grid drawn then would miss\n // a hook it changes in the same pass.\n afterRenderEffect(() => {\n if (!this.booted()) return;\n const still = this.platform.prefersReducedMotion();\n this.pitch();\n this.size();\n this.dotColor();\n this.activeColor();\n this.radius();\n this.strength();\n untracked(() => {\n if (still) this.rest();\n this.requestDraw();\n });\n });\n\n // A drawn grid keeps the colours it was drawn in, and the loop sleeps when\n // nothing moves, so a scheme switch asks for a frame to re-resolve them.\n watchColorScheme(() => this.requestDraw());\n\n // Motion, decided live: never under `prefers-reduced-motion`, which the\n // still grid above answers.\n effect(onCleanup => {\n if (!this.booted() || this.platform.prefersReducedMotion()) return;\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact())));\n });\n }\n\n // Setup\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n this.ctx = canvas.getContext('2d');\n if (!this.ctx) return;\n\n // `clientWidth` and not `getBoundingClientRect()`: the rect is the box after\n // transforms, so a grid booted inside a dialog's scale-in would be laid out\n // for 96% of its box and stretched by CSS once the animation ended. The\n // observer's first entry follows with the exact, sub-pixel size.\n const hostEl = this.host.nativeElement;\n this.fit(hostEl.clientWidth, hostEl.clientHeight);\n this.placeDots();\n\n // The box is the observer's to follow, transforms excluded. A window resize\n // is for the pixel ratio, which browser zoom and a move to another screen\n // change with no box change at all.\n const onResize = (): void => this.resize(this.box.width, this.box.height);\n window.addEventListener('resize', onResize);\n const ro = new ResizeObserver(entries => {\n const rect = entries[entries.length - 1]?.contentRect;\n if (rect) this.resize(rect.width, rect.height);\n });\n ro.observe(hostEl);\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', onResize);\n ro.disconnect();\n });\n this.booted.set(true);\n }\n\n /** Follow the pointer and draw on demand; returns what stops both. */\n private interact(): () => void {\n const hostEl = this.host.nativeElement;\n // The parent, not the host: content in front of the canvas receives the\n // pointer, and its events bubble to the parent, never to a sibling. Placed\n // directly in a shadow root it has none, and the shadow host is where those\n // composed events land. Never the host itself, which takes no pointer\n // events and so could never hear one.\n const root = hostEl.getRootNode();\n const target =\n hostEl.parentElement ?? (root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null);\n\n // Off screen the grid asks for no frames. Without an observer (jsdom) it\n // counts as visible.\n let visible = typeof IntersectionObserver === 'undefined';\n const wake = (): void => {\n if (visible && this.rafId === 0) this.rafId = requestAnimationFrame(time => this.tick(time));\n };\n const halt = (): void => {\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.lastTime = -1;\n };\n\n const onMove = (event: PointerEvent): void => {\n this.pointer.clientX = event.clientX;\n this.pointer.clientY = event.clientY;\n this.pointer.inside = true;\n wake();\n };\n const onLeave = (): void => {\n this.pointer.inside = false;\n wake();\n };\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerdown', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => {\n visible = entries[entries.length - 1].isIntersecting;\n if (visible) wake();\n else halt();\n });\n io?.observe(hostEl);\n\n this.wake = wake;\n wake();\n\n return () => {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerdown', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n io?.disconnect();\n halt();\n this.wake = null;\n this.pointer.inside = false;\n };\n }\n\n /** Draw the grid as it should look now: a frame while motion is allowed, at once under reduced motion. */\n private requestDraw(): void {\n if (!this.booted()) return;\n if (this.wake) this.wake();\n else if (this.platform.prefersReducedMotion()) this.draw();\n }\n\n /** Fit the canvas to a new box or pixel ratio and, when that cleared it, repaint at once. */\n private resize(width: number, height: number): void {\n const moved = width !== this.box.width || height !== this.box.height;\n if (!this.fit(width, height)) return;\n if (moved) this.placeDots();\n // Observer entries arrive after the frame's own callbacks and before its\n // paint, so a frame asked for here would leave this one blank — with the\n // stand-in already retired, a resize drag would flicker every frame.\n this.draw();\n // Re-placed dots are at rest and unlit, which is right unless the pointer\n // is over them; a running loop simply carries on.\n if (this.pointer.inside) this.wake?.();\n }\n\n /**\n * Back the canvas for a `width` × `height` box, in CSS pixels, at the screen's\n * pixel ratio. Returns whether anything changed: writing `canvas.width` clears\n * the bitmap even to the value it already has, so an unchanged box is left alone.\n */\n private fit(width: number, height: number): boolean {\n // Capped at 2 and by the pixel budget, never below 1 — a bitmap the size of\n // the CSS box blurs every dot on a 2x screen.\n const area = width * height;\n const budget = area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2;\n const ratio = Math.max(1, Math.min(window.devicePixelRatio || 1, 2, budget));\n if (width === this.box.width && height === this.box.height && ratio === this.ratio) return false;\n\n this.box = { width, height };\n this.ratio = ratio;\n const canvas = this.canvasRef().nativeElement;\n canvas.width = Math.round(width * ratio);\n canvas.height = Math.round(height * ratio);\n // Resizing the bitmap resets the transform, so the scale goes on after it.\n // Every coordinate the draw uses is in CSS pixels.\n this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);\n return true;\n }\n\n /** Lay the dots out over the box, every one at rest. */\n private placeDots(): void {\n // One dot sits on the centre of the box and the rest step out from it by\n // the pitch — exactly where the stand-in's centred `background-position`\n // puts them, so the handover does not shift a single dot. One ring past\n // each edge, for the dots a push brings into view.\n const gap = this.pitch();\n const xs = this.axis(this.box.width, gap);\n const ys = this.axis(this.box.height, gap);\n const dots: Dot[] = [];\n for (const y of ys) {\n for (const x of xs) dots.push({ x, y, ox: 0, oy: 0, vx: 0, vy: 0, glow: 0 });\n }\n this.dots = dots;\n }\n\n /** Positions along one side of `length`: the centre, then every `gap` out to one step past each edge. */\n private axis(length: number, gap: number): number[] {\n const centre = length / 2;\n const steps = Math.ceil(centre / gap) + 1;\n const positions: number[] = [];\n for (let i = -steps; i <= steps; i++) positions.push(centre + i * gap);\n return positions;\n }\n\n /** Every dot back at rest and unlit, and the pointer gone. */\n private rest(): void {\n this.pointer.inside = false;\n for (const dot of this.dots) {\n dot.ox = dot.oy = dot.vx = dot.vy = dot.glow = 0;\n }\n }\n\n // Frame loop\n\n private tick(time: number): void {\n this.rafId = 0;\n // The springs are tuned per 60 Hz frame and stepped by the time that really\n // passed, so a 120 Hz screen does not run them twice as fast. A timestamp\n // that did not advance counts as one frame.\n const elapsed = time - this.lastTime;\n const step = this.lastTime < 0 || elapsed <= 0 ? 1 : Math.min(MAX_STEP, elapsed / FRAME_MS);\n this.lastTime = time;\n\n const moving = this.move(step);\n this.draw();\n\n // Nothing left to move: sleep until the pointer, a resize or a new input\n // asks for another frame.\n if (moving && this.wake) this.rafId = requestAnimationFrame(next => this.tick(next));\n else this.lastTime = -1;\n }\n\n /** Advance every dot by `step` frames; returns whether any is still moving. */\n private move(step: number): boolean {\n const reach = Math.max(0, this.radius());\n const push = this.strength();\n const active = this.pointer.inside && reach > 0;\n\n let px = 0;\n let py = 0;\n if (active) {\n // Read once a frame rather than once a move: a page that scrolls under a\n // still pointer moves the grid under it too. The rect is the box as\n // drawn, after any transform on it or an ancestor, and the grid is laid\n // out in the box before them, so the pointer is scaled back into it.\n const rect = this.host.nativeElement.getBoundingClientRect();\n px = (this.pointer.clientX - rect.left) * (rect.width > 0 ? this.box.width / rect.width : 1);\n py = (this.pointer.clientY - rect.top) * (rect.height > 0 ? this.box.height / rect.height : 1);\n }\n\n const reach2 = reach * reach;\n const pull = SPRING * step;\n const keep = Math.pow(DAMPING, step);\n const rise = 1 - Math.pow(1 - GLOW_RISE, step);\n const fall = 1 - Math.pow(1 - GLOW_FALL, step);\n\n let moving = false;\n for (const dot of this.dots) {\n let tx = 0;\n let ty = 0;\n let glow = 0;\n if (active) {\n const dx = dot.x - px;\n const dy = dot.y - py;\n const d2 = dx * dx + dy * dy;\n if (d2 < reach2) {\n const d = Math.sqrt(d2);\n const u = 1 - d / reach;\n // Smoothstep: no edge where the radius ends, and a flat top under the pointer.\n glow = u * u * (3 - 2 * u);\n if (d > 0) {\n tx = (dx / d) * push * glow;\n ty = (dy / d) * push * glow;\n }\n }\n }\n\n dot.vx = (dot.vx + (tx - dot.ox) * pull) * keep;\n dot.vy = (dot.vy + (ty - dot.oy) * pull) * keep;\n dot.ox += dot.vx * step;\n dot.oy += dot.vy * step;\n dot.glow += (glow - dot.glow) * (glow > dot.glow ? rise : fall);\n\n if (\n !moving &&\n (Math.abs(tx - dot.ox) > REST ||\n Math.abs(ty - dot.oy) > REST ||\n Math.abs(dot.vx) > REST ||\n Math.abs(dot.vy) > REST ||\n Math.abs(glow - dot.glow) > GLOW_REST)\n ) {\n moving = true;\n }\n }\n return moving;\n }\n\n private draw(): void {\n const ctx = this.ctx;\n if (!ctx) return;\n // Retire the stand-in only once something is really painted. Runs outside\n // Angular, so the `set` schedules the one host-binding update it needs.\n if (!this.painted()) this.painted.set(true);\n\n const canvas = this.canvasRef().nativeElement;\n const r = this.size() / 2;\n ctx.clearRect(0, 0, this.box.width, this.box.height);\n ctx.globalAlpha = 1;\n ctx.fillStyle = resolveCssColor(canvas, this.dotExpression(), DOT_FALLBACK);\n this.fillDots(ctx, this.dots, r);\n\n // Lit dots go over their resting selves in the active colour, at their\n // glow as alpha: the blend of the two colours without parsing either, so\n // an `oklch()` or a `display-p3` colour works as well as an `rgb()`.\n for (const level of this.lit) level.length = 0;\n let anyLit = false;\n for (const dot of this.dots) {\n const level = Math.round(dot.glow * GLOW_LEVELS);\n if (level > 0) {\n this.lit[Math.min(level, GLOW_LEVELS) - 1].push(dot);\n anyLit = true;\n }\n }\n if (!anyLit) return;\n\n ctx.fillStyle = resolveCssColor(canvas, this.activeExpression(), ACTIVE_FALLBACK);\n this.lit.forEach((dots, i) => {\n if (dots.length === 0) return;\n ctx.globalAlpha = (i + 1) / GLOW_LEVELS;\n this.fillDots(ctx, dots, r);\n });\n ctx.globalAlpha = 1;\n }\n\n /** One path for every dot in `dots`, filled once. */\n private fillDots(ctx: CanvasRenderingContext2D, dots: readonly Dot[], r: number): void {\n ctx.beginPath();\n for (const dot of dots) {\n const x = dot.x + dot.ox;\n const y = dot.y + dot.oy;\n ctx.moveTo(x + r, y);\n ctx.arc(x, y, r, 0, TAU);\n }\n ctx.fill();\n }\n}\n"],"mappings":"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"}
@@ -310,16 +310,29 @@ var WrFuzzyText = class WrFuzzyText {
310
310
  }
311
311
  rafId = requestAnimationFrame(run);
312
312
  };
313
+ let visible = typeof IntersectionObserver === "undefined";
314
+ let hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;
313
315
  const resume = () => {
314
- if (rafId === 0) rafId = requestAnimationFrame(run);
316
+ if (visible && hasBox && rafId === 0) rafId = requestAnimationFrame(run);
315
317
  };
316
318
  const park = () => {
317
319
  cancelAnimationFrame(rafId);
318
320
  rafId = 0;
319
321
  };
320
- const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => entries[entries.length - 1].isIntersecting ? resume() : park());
321
- if (io) io.observe(canvas);
322
- else resume();
322
+ const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => {
323
+ const { isIntersecting, intersectionRatio } = entries[entries.length - 1];
324
+ visible = isIntersecting && intersectionRatio > 0;
325
+ if (visible) resume();
326
+ else park();
327
+ }, { threshold: [0, 1e-9] });
328
+ io?.observe(canvas);
329
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => {
330
+ hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;
331
+ if (hasBox) resume();
332
+ else park();
333
+ });
334
+ ro?.observe(canvas);
335
+ resume();
323
336
  const isInsideText = (x, y) => x >= interactiveLeft && x <= interactiveRight && y >= interactiveTop && y <= interactiveBottom;
324
337
  const isOverText = (clientX, clientY) => {
325
338
  const rect = canvas.getBoundingClientRect();
@@ -357,6 +370,7 @@ var WrFuzzyText = class WrFuzzyText {
357
370
  this.teardown = () => {
358
371
  park();
359
372
  io?.disconnect();
373
+ ro?.disconnect();
360
374
  window.removeEventListener("resize", onResize);
361
375
  clearTimeout(glitchOnTimer);
362
376
  clearTimeout(glitchOffTimer);
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-bits-fuzzy-text.mjs","names":[],"sources":["../../../projects/lib/bits/fuzzy-text/fuzzy-text.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Angular adaptation of the FuzzyText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/fuzzy-text\n *\n * Renders text to an offscreen 2D canvas, then redraws it onto the\n * visible canvas one row (or column) at a time with random horizontal /\n * vertical offsets. Intensity reacts to hover / click / glitch state.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport type { ElementRef } from '@angular/core';\nimport {\n Component,\n DestroyRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n effect,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\nimport type { WrFuzzyTextDirection } from './types';\n\n/**\n * Canvas-rendered \"fuzzy\" text — each row/column of glyphs is offset by\n * a random amount per frame, giving the text a TV-static or motion-blur\n * appearance. Intensity ramps up on hover, click, and during periodic\n * glitch bursts (configurable).\n *\n * @example\n * ```html\n * <wr-fuzzy-text text=\"404\" />\n * <wr-fuzzy-text\n * text=\"HELLO\"\n * color=\"#0ea5e9\"\n * [fuzzRange]=\"50\"\n * [hoverIntensity]=\"0.8\"\n * [glitchMode]=\"true\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/fuzzy-text\n */\n@Component({\n selector: 'wr-fuzzy-text',\n // The text is drawn into the canvas as PIXELS, so without the first span there is\n // nothing for a screen reader to read at all — the same layer `wr-rotating-text`\n // and `wr-decrypt-text` carry. Hidden inline rather than through a class: this\n // entry point ships no stylesheet, and a consumer who imported none would\n // otherwise see the text twice.\n //\n // BOUND, not a static `style=\"…\"` attribute. A real attribute is refused under\n // `style-src 'self'`, and what is lost there is not a colour: the span drops\n // every visual-hiding declaration and the word renders on screen beside the\n // canvas that already draws it. A binding goes through `style.setProperty`,\n // which is CSSOM and outside CSP's reach.\n template:\n '<span class=\"wr-fuzzy-text__sr-only\" [style]=\"srOnly\">{{ text() }}</span>' +\n '<canvas #canvas aria-hidden=\"true\" [style]=\"fit\"></canvas>',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-fuzzy-text', '[style.color]': \"'var(--wr-color-on-surface)'\" },\n})\nexport class WrFuzzyText {\n /**\n * Visual-hiding declarations for the screen-reader copy of the text. A stable\n * object literal, so the binding is written once rather than on every pass.\n */\n protected readonly srOnly: Readonly<Record<string, string>> = {\n position: 'absolute',\n width: '1px',\n height: '1px',\n overflow: 'hidden',\n 'clip-path': 'inset(50%)',\n 'white-space': 'nowrap',\n };\n\n /**\n * The canvas is as wide as the text it draws, and text cannot wrap, so a long\n * string or a narrow screen pushed it past its container and clipped it. Scaled\n * down to fit instead, keeping its aspect ratio; the pointer maths below reads\n * the drawn size back. Bound for the reason `srOnly` is.\n */\n protected readonly fit: Readonly<Record<string, string>> = { 'max-width': '100%', height: 'auto' };\n\n /** Text to render. */\n readonly text = input.required<string>();\n\n /** Font size as CSS length (`'4rem'`, `'72px'`, …) or px number. @default 'clamp(2rem, 10vw, 10rem)' */\n readonly fontSize = input<string | number>('clamp(2rem, 10vw, 10rem)');\n\n /** Font weight. @default 900 */\n readonly fontWeight = input<number | string>(900);\n\n /** Font family. `'inherit'` reads the canvas's computed style. @default 'inherit' */\n readonly fontFamily = input('inherit');\n\n /**\n * Text colour, any CSS colour — `var()` and `light-dark()` included, resolved\n * against the canvas. `'inherit'` follows the canvas's computed colour. Both are\n * re-read while the text animates, so a colour-scheme change lands on the next\n * frame, and the still copy drawn under reduced motion is redrawn when the\n * page's scheme switches. Ignored if `[gradient]` is set. @default 'inherit'\n */\n readonly color = input('inherit');\n\n /** Optional horizontal gradient stops, each resolved the way `color` is. */\n readonly gradient = input<readonly string[] | null>(null);\n\n /** Enable hover / touch reactivity. @default true */\n readonly enableHover = input(true, { transform: coerceBooleanProperty });\n\n /** Base intensity (0..1) when idle. @default 0.18 */\n readonly baseIntensity = input(0.18, { transform: numAttr(0.18) });\n\n /** Intensity (0..1) while the pointer hovers the text. @default 0.5 */\n readonly hoverIntensity = input(0.5, { transform: numAttr(0.5) });\n\n /** Max pixel displacement per row/column at intensity 1. @default 30 */\n readonly fuzzRange = input(30, { transform: numAttr(30) });\n\n /** Frame rate cap. @default 60 */\n readonly fps = input(60, { transform: numAttr(60) });\n\n /** Fuzz direction. @default 'horizontal' */\n readonly direction = input<WrFuzzyTextDirection>('horizontal');\n\n /** Ms to ease intensity toward target. `0` snaps. @default 0 */\n readonly transitionDuration = input(0, { transform: numAttr(0) });\n\n /** Spike to full intensity briefly on click. @default false */\n readonly clickEffect = input(false, { transform: coerceBooleanProperty });\n\n /** Periodic \"glitch\" bursts at full intensity. @default false */\n readonly glitchMode = input(false, { transform: coerceBooleanProperty });\n\n /** Ms between glitch bursts. @default 2000 */\n readonly glitchInterval = input(2000, { transform: numAttr(2000) });\n\n /** Ms a glitch burst lasts. @default 200 */\n readonly glitchDuration = input(200, { transform: numAttr(200) });\n\n /** Extra pixels between glyphs. @default 0 */\n readonly letterSpacing = input(0, { transform: numAttr(0) });\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n\n private teardown: (() => void) | undefined;\n\n /**\n * Re-bakes and redraws the still copy, set only while one is on the canvas.\n * The fuzz loop needs none: every frame it draws re-resolves the fill.\n */\n private repaint: (() => void) | undefined;\n\n /**\n * `init()` suspends on font loading, so the component can be destroyed while\n * it is awaiting. At that moment `teardown` is still undefined, so the\n * destroy hook has nothing to call — and everything the rest of `init()`\n * starts (the rAF loop, the glitch timers, canvas listeners) would run for\n * the life of the page. This flag lets the continuation bail out.\n */\n private destroyed = false;\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n void this.init();\n });\n\n // Re-init whenever any input that affects the offscreen rendering changes.\n effect(() => {\n this.text();\n this.fontSize();\n this.fontWeight();\n this.fontFamily();\n this.color();\n this.gradient();\n this.letterSpacing();\n this.direction();\n this.fuzzRange();\n this.fps();\n this.glitchMode();\n this.glitchInterval();\n this.glitchDuration();\n // Read live: `init()` chooses between the still copy and the loop, so a\n // preference switched while the page is open needs a fresh one.\n this.platform.prefersReducedMotion();\n this.restart();\n });\n\n // The still copy keeps the colours it was baked in, so a colour-scheme\n // switch under reduced motion has to ask for it again.\n watchColorScheme(() => this.repaint?.());\n\n this.destroyRef.onDestroy(() => {\n this.destroyed = true;\n this.teardown?.();\n });\n }\n\n // Init / loop\n\n /** Tear the running instance down and boot a fresh one. A no-op before the first boot. */\n private restart(): void {\n if (!this.teardown) return;\n this.teardown();\n this.teardown = undefined;\n queueMicrotask(() => {\n void this.init();\n });\n }\n\n /**\n * The fill, as computed colours: one entry for `color`, or one per gradient stop.\n * A canvas reads neither a custom property nor `light-dark()` — `fillStyle` keeps\n * its previous value and `addColorStop` throws — so each goes through the cascade\n * first, falling back to the canvas's own computed colour.\n */\n private resolveFill(canvas: HTMLCanvasElement): string[] {\n const inherited = window.getComputedStyle(canvas).color || '#fff';\n const gradient = this.gradient();\n if (gradient && gradient.length >= 2) return gradient.map(stop => resolveCssColor(canvas, stop, inherited));\n const color = this.color();\n return [color === 'inherit' ? inherited : resolveCssColor(canvas, color, inherited)];\n }\n\n /**\n * CSS font size in pixels, resolved where the canvas would resolve it: inside the\n * canvas, whose fallback content is never painted, so `em` and `%` follow the\n * host's own font size.\n */\n private measureFontSize(fontSize: string | number, canvas: HTMLCanvasElement): number {\n if (typeof fontSize === 'number') return fontSize;\n const tmp = document.createElement('span');\n tmp.style.fontSize = fontSize;\n canvas.appendChild(tmp);\n const px = Number.parseFloat(window.getComputedStyle(tmp).fontSize);\n tmp.remove();\n return px;\n }\n\n private async init(): Promise<void> {\n const canvas = this.canvasRef().nativeElement;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const fontFamily =\n this.fontFamily() === 'inherit' ? window.getComputedStyle(canvas).fontFamily || 'sans-serif' : this.fontFamily();\n const fontSize = this.fontSize();\n // Numeric font size for buffer maths — and for the font string itself. The canvas\n // was handed the raw CSS, and Chromium caches a parsed font by its string, so a\n // `clamp(2rem, 10vw, 10rem)` kept the pixel size of the viewport it was first\n // parsed at however often the canvas was rebuilt after a resize.\n const numericFontSize = this.measureFontSize(fontSize, canvas);\n const fontSizeStr =\n typeof fontSize === 'number'\n ? `${fontSize}px`\n : Number.isFinite(numericFontSize)\n ? `${numericFontSize}px`\n : fontSize;\n const fontWeight = this.fontWeight();\n const fontString = `${fontWeight} ${fontSizeStr} ${fontFamily}`;\n\n try {\n await document.fonts.load(fontString);\n } catch {\n await document.fonts.ready;\n }\n\n // Destroyed while the fonts were loading — nothing below may start.\n if (this.destroyed) return;\n\n const text = this.text();\n const letterSpacing = this.letterSpacing();\n const fuzzRange = this.fuzzRange();\n const direction = this.direction();\n\n // Offscreen canvas with the laid-out text.\n const offscreen = document.createElement('canvas');\n const offCtx = offscreen.getContext('2d');\n if (!offCtx) return;\n offCtx.font = fontString;\n offCtx.textBaseline = 'alphabetic';\n\n let totalWidth = 0;\n if (letterSpacing !== 0) {\n for (const ch of text) totalWidth += offCtx.measureText(ch).width + letterSpacing;\n totalWidth -= letterSpacing;\n } else {\n totalWidth = offCtx.measureText(text).width;\n }\n const m = offCtx.measureText(text);\n const actualLeft = m.actualBoundingBoxLeft ?? 0;\n const actualRight = letterSpacing !== 0 ? totalWidth : (m.actualBoundingBoxRight ?? m.width);\n const actualAscent = m.actualBoundingBoxAscent ?? numericFontSize;\n const actualDescent = m.actualBoundingBoxDescent ?? numericFontSize * 0.2;\n const textBoundingWidth = Math.ceil(letterSpacing !== 0 ? totalWidth : actualLeft + actualRight);\n const tightHeight = Math.ceil(actualAscent + actualDescent);\n\n const extraWidthBuffer = 10;\n const offscreenWidth = textBoundingWidth + extraWidthBuffer;\n offscreen.width = offscreenWidth;\n offscreen.height = tightHeight;\n const xOffset = extraWidthBuffer / 2;\n\n // The text is baked into the offscreen canvas in the colours resolved now, and\n // re-baked whenever they resolve differently (see `refill` in the loop).\n const bake = (fills: readonly string[]): void => {\n offCtx.font = fontString;\n offCtx.textBaseline = 'alphabetic';\n if (fills.length >= 2) {\n const g = offCtx.createLinearGradient(0, 0, offscreenWidth, 0);\n fills.forEach((c, i) => g.addColorStop(i / (fills.length - 1), c));\n offCtx.fillStyle = g;\n } else {\n offCtx.fillStyle = fills[0];\n }\n\n if (letterSpacing !== 0) {\n let xPos = xOffset;\n for (const ch of text) {\n offCtx.fillText(ch, xPos, actualAscent);\n xPos += offCtx.measureText(ch).width + letterSpacing;\n }\n } else {\n offCtx.fillText(text, xOffset - actualLeft, actualAscent);\n }\n };\n let fillKey = '';\n /** Re-resolve the fill; re-bake and report `true` when it changed. */\n const refill = (): boolean => {\n const fills = this.resolveFill(canvas);\n const key = fills.join('|');\n if (key === fillKey) return false;\n if (fillKey) offCtx.clearRect(0, 0, offscreenWidth, tightHeight);\n fillKey = key;\n bake(fills);\n return true;\n };\n refill();\n\n // Size the visible canvas, leaving room for the max horizontal fuzz.\n const horizontalMargin = fuzzRange + 20;\n const verticalMargin = 0;\n canvas.width = offscreenWidth + horizontalMargin * 2;\n canvas.height = tightHeight + verticalMargin * 2;\n ctx.translate(horizontalMargin, verticalMargin);\n\n const interactiveLeft = horizontalMargin + xOffset;\n const interactiveTop = verticalMargin;\n const interactiveRight = interactiveLeft + textBoundingWidth;\n const interactiveBottom = interactiveTop + tightHeight;\n\n // A viewport-relative size (the default is `clamp(2rem, 10vw, 10rem)`) is\n // measured into the bitmap once, so a rotated phone or a resized window kept\n // the old size until a reload. Re-measured on resize, and rebuilt only when\n // the pixel size actually moved.\n const onResize = (): void => {\n if (!Object.is(this.measureFontSize(this.fontSize(), canvas), numericFontSize)) this.restart();\n };\n window.addEventListener('resize', onResize);\n\n // Reduced motion: paint the text once, clean — no fuzz loop, no\n // glitch bursts, no pointer reactivity. Teardown must still be set\n // so the re-init effect keeps redrawing on input changes.\n if (this.platform.prefersReducedMotion()) {\n ctx.drawImage(offscreen, 0, 0);\n // A scheme switch re-bakes the text in whatever its colours now resolve\n // to and draws it again; a switch that leaves them alone draws nothing.\n // Cleared in device space, the way the loop's second pass clears.\n this.repaint = (): void => {\n if (!refill()) return;\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.restore();\n ctx.drawImage(offscreen, 0, 0);\n };\n this.teardown = (): void => {\n this.repaint = undefined;\n window.removeEventListener('resize', onResize);\n };\n return;\n }\n\n let isHovering = false;\n let isClicking = false;\n let isGlitching = false;\n let currentIntensity = this.baseIntensity();\n let targetIntensity = currentIntensity;\n let lastFrame = 0;\n const frameDuration = 1000 / this.fps();\n\n let rafId = 0;\n let glitchOnTimer: ReturnType<typeof setTimeout> | undefined;\n let glitchOffTimer: ReturnType<typeof setTimeout> | undefined;\n let clickTimer: ReturnType<typeof setTimeout> | undefined;\n\n const startGlitchLoop = (): void => {\n if (!this.glitchMode()) return;\n glitchOnTimer = setTimeout(() => {\n isGlitching = true;\n glitchOffTimer = setTimeout(() => {\n isGlitching = false;\n startGlitchLoop();\n }, this.glitchDuration());\n }, this.glitchInterval());\n };\n if (this.glitchMode()) startGlitchLoop();\n\n const run = (ts: number): void => {\n if (ts - lastFrame < frameDuration) {\n rafId = requestAnimationFrame(run);\n return;\n }\n lastFrame = ts;\n\n // A colour scheme switched under a running loop lands here: the text is\n // re-baked in whatever its colours now resolve to.\n refill();\n\n ctx.clearRect(\n -fuzzRange - 20,\n -fuzzRange - 10,\n offscreenWidth + 2 * (fuzzRange + 20),\n tightHeight + 2 * (fuzzRange + 10)\n );\n\n if (isClicking) targetIntensity = 1;\n else if (isGlitching) targetIntensity = 1;\n else if (isHovering) targetIntensity = this.hoverIntensity();\n else targetIntensity = this.baseIntensity();\n\n const td = this.transitionDuration();\n if (td > 0) {\n const step = 1 / (td / frameDuration);\n if (currentIntensity < targetIntensity) currentIntensity = Math.min(currentIntensity + step, targetIntensity);\n else if (currentIntensity > targetIntensity)\n currentIntensity = Math.max(currentIntensity - step, targetIntensity);\n } else {\n currentIntensity = targetIntensity;\n }\n\n if (direction === 'horizontal') {\n for (let j = 0; j < tightHeight; j++) {\n const dx = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, 0, j, offscreenWidth, 1, dx, j, offscreenWidth, 1);\n }\n } else if (direction === 'vertical') {\n for (let i = 0; i < offscreenWidth; i++) {\n const dy = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, i, 0, 1, tightHeight, i, dy, 1, tightHeight);\n }\n } else {\n // both: horizontal pass, then a vertical pass over what it drew.\n for (let j = 0; j < tightHeight; j++) {\n const dx = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, 0, j, offscreenWidth, 1, dx, j, offscreenWidth, 1);\n }\n\n // The second pass runs in DEVICE space, with the transform off.\n //\n // `getImageData` and `putImageData` are specified to ignore the\n // current transformation matrix; `drawImage` and `clearRect` honour\n // it. The context carries `translate(horizontalMargin, …)`, so the\n // two halves of this branch were addressing pixels two different\n // ways: the read started at device x=0, a whole margin to the LEFT of\n // what the horizontal pass had drawn, and the write put it back\n // there — shifting the text off its own box every frame. Resetting\n // the matrix for the duration is what makes the four calls agree.\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n\n // Wide enough for the fuzz the first pass may have thrown either way,\n // and clamped to the canvas so no read runs off the edge.\n const readX = Math.max(0, horizontalMargin - fuzzRange);\n const readW = Math.min(canvas.width - readX, offscreenWidth + fuzzRange * 2);\n const readY = verticalMargin;\n const readH = Math.min(canvas.height - readY, tightHeight);\n\n const data = ctx.getImageData(readX, readY, readW, readH);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.putImageData(data, readX, readY);\n\n for (let i = 0; i < readW; i++) {\n const dy = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange * 0.5);\n if (dy === 0) continue;\n const col = ctx.getImageData(readX + i, readY, 1, readH);\n // `putImageData` REPLACES rather than composites, so the column has\n // to be cleared before it is written back at its offset.\n ctx.clearRect(readX + i, 0, 1, canvas.height);\n ctx.putImageData(col, readX + i, readY + dy);\n }\n\n ctx.restore();\n }\n\n rafId = requestAnimationFrame(run);\n };\n\n // Off screen the loop stops asking for frames, the way `wr-waves` does: it is\n // decoration, and every frame out of sight still redrew one slice per row.\n // Without an observer (jsdom) it simply runs.\n const resume = (): void => {\n if (rafId === 0) rafId = requestAnimationFrame(run);\n };\n const park = (): void => {\n cancelAnimationFrame(rafId);\n rafId = 0;\n };\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => (entries[entries.length - 1].isIntersecting ? resume() : park()));\n if (io) io.observe(canvas);\n else resume();\n\n const isInsideText = (x: number, y: number): boolean =>\n x >= interactiveLeft && x <= interactiveRight && y >= interactiveTop && y <= interactiveBottom;\n\n /**\n * A client point in canvas pixels. The canvas may be drawn smaller than its\n * bitmap (`fit`), so the offset is scaled back up; a box with no size (jsdom)\n * is taken as unscaled.\n */\n const isOverText = (clientX: number, clientY: number): boolean => {\n const rect = canvas.getBoundingClientRect();\n const scale = rect.width > 0 ? canvas.width / rect.width : 1;\n return isInsideText((clientX - rect.left) * scale, (clientY - rect.top) * scale);\n };\n\n const onMouseMove = (e: MouseEvent): void => {\n if (!this.enableHover()) return;\n isHovering = isOverText(e.clientX, e.clientY);\n };\n const onMouseLeave = (): void => {\n isHovering = false;\n };\n // Passive, and it never cancels: the text is decoration, and a swipe that\n // starts on it has to scroll the page like a swipe anywhere else. It used to\n // `preventDefault()`, which turned the whole canvas into a scroll dead zone on\n // every touch screen.\n const onTouchMove = (e: TouchEvent): void => {\n if (!this.enableHover()) return;\n const t = e.touches[0];\n isHovering = isOverText(t.clientX, t.clientY);\n };\n const onTouchEnd = (): void => {\n isHovering = false;\n };\n const onClick = (): void => {\n if (!this.clickEffect()) return;\n isClicking = true;\n clearTimeout(clickTimer);\n clickTimer = setTimeout(() => {\n isClicking = false;\n }, 150);\n };\n\n // Attached unconditionally, and gated inside the three handlers that can SET a\n // reactive state instead. `enableHover` and `clickEffect` are not in the re-init\n // effect's dependency list, so an attach conditional on them read the value at\n // boot and never again: a `[clickEffect]` bound to a signal that starts `false` —\n // the default, so the ordinary case — stayed dead after it flipped true. Adding\n // them to that list would restart the rAF loop and the glitch timers just to\n // toggle a listener, which is visible; the guards are not.\n //\n // `onMouseLeave` / `onTouchEnd` stay ungated deliberately: they only ever CLEAR\n // toward the idle state, and gating them would strand `isHovering` on for good\n // when hover is switched off mid-hover.\n canvas.addEventListener('mousemove', onMouseMove);\n canvas.addEventListener('mouseleave', onMouseLeave);\n canvas.addEventListener('touchmove', onTouchMove, { passive: true });\n canvas.addEventListener('touchend', onTouchEnd);\n canvas.addEventListener('click', onClick);\n\n this.teardown = (): void => {\n park();\n io?.disconnect();\n window.removeEventListener('resize', onResize);\n clearTimeout(glitchOnTimer);\n clearTimeout(glitchOffTimer);\n clearTimeout(clickTimer);\n canvas.removeEventListener('mousemove', onMouseMove);\n canvas.removeEventListener('mouseleave', onMouseLeave);\n canvas.removeEventListener('touchmove', onTouchMove);\n canvas.removeEventListener('touchend', onTouchEnd);\n canvas.removeEventListener('click', onClick);\n };\n }\n}\n\nexport type { WrFuzzyTextDirection } from './types';\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAyEA,IAAa,cAAb,MAAa,YAAW;CAKH,SAA2C;EAC5D,UAAU;EACV,OAAO;EACP,QAAQ;EACR,UAAU;EACV,aAAa;EACb,eAAe;CAChB;CAQkB,MAAwC;EAAE,aAAa;EAAQ,QAAQ;CAAM;CAGvF,OAAO,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,WAAW,MAAuB,4BAA2B,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAG7D,aAAa,MAAuB,KAAI,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGxC,aAAa,MAAM,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAS7B,QAAQ,MAAM,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAGxB,WAAW,MAAgC,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAGhD,cAAc,MAAM,MAAI;EAAA,GAAA,YAAA,EAAA,WAAA,cAAA,+BAAA,CAAA;EAAI,WAAW;CAAqB,CAAA;CAG5D,gBAAgB,MAAM,KAAI;EAAA,GAAA,YAAA,EAAA,WAAA,gBAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,GAAI;CAAC,CAAA;CAGtD,iBAAiB,MAAM,IAAG;EAAA,GAAA,YAAA,EAAA,WAAA,iBAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,EAAG;CAAC,CAAA;CAGrD,YAAY,MAAM,IAAE;EAAA,GAAA,YAAA,EAAA,WAAA,YAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,EAAE;CAAC,CAAA;CAG9C,MAAM,MAAM,IAAE;EAAA,GAAA,YAAA,EAAA,WAAA,MAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,EAAE;CAAC,CAAA;CAGxC,YAAY,MAA4B,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGrD,qBAAqB,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,qBAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,CAAC;CAAC,CAAA;CAGrD,cAAc,MAAM,OAAK;EAAA,GAAA,YAAA,EAAA,WAAA,cAAA,+BAAA,CAAA;EAAI,WAAW;CAAqB,CAAA;CAG7D,aAAa,MAAM,OAAK;EAAA,GAAA,YAAA,EAAA,WAAA,aAAA,+BAAA,CAAA;EAAI,WAAW;CAAqB,CAAA;CAG5D,iBAAiB,MAAM,KAAI;EAAA,GAAA,YAAA,EAAA,WAAA,iBAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,GAAI;CAAC,CAAA;CAGvD,iBAAiB,MAAM,KAAG;EAAA,GAAA,YAAA,EAAA,WAAA,iBAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,GAAG;CAAC,CAAA;CAGrD,gBAAgB,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,gBAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,CAAC;CAAC,CAAA;CAExC,YAAY,UAAU,SAAwC,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEvE,aAAa,OAAO,UAAU;CAC9B,YAAY,kBAAkB,OAAO,WAAW,CAAC;CACjD,WAAW,OAAO,UAAU;CAErC;CAMA;CASA,YAAY;CAEpB,cAAA;EACE,IAAI,CAAC,KAAK,WAAW;EAErB,sBAAqB;GACnB,AAAK,KAAK,KAAI;EAChB,CAAC;EAGD,aAAY;GACV,KAAK,KAAI;GACT,KAAK,SAAQ;GACb,KAAK,WAAU;GACf,KAAK,WAAU;GACf,KAAK,MAAK;GACV,KAAK,SAAQ;GACb,KAAK,cAAa;GAClB,KAAK,UAAS;GACd,KAAK,UAAS;GACd,KAAK,IAAG;GACR,KAAK,WAAU;GACf,KAAK,eAAc;GACnB,KAAK,eAAc;GAGnB,KAAK,SAAS,qBAAoB;GAClC,KAAK,QAAO;EACd,CAAC;EAID,uBAAuB,KAAK,UAAS,CAAE;EAEvC,KAAK,WAAW,gBAAe;GAC7B,KAAK,YAAY;GACjB,KAAK,WAAU;EACjB,CAAC;CACH;CAKQ,UAAO;EACb,IAAI,CAAC,KAAK,UAAU;EACpB,KAAK,SAAQ;EACb,KAAK,WAAW;EAChB,qBAAoB;GAClB,AAAK,KAAK,KAAI;EAChB,CAAC;CACH;CAQQ,YAAY,QAAyB;EAC3C,MAAM,YAAY,OAAO,iBAAiB,MAAM,CAAC,CAAC,SAAS;EAC3D,MAAM,WAAW,KAAK,SAAQ;EAC9B,IAAI,YAAY,SAAS,UAAU,GAAG,OAAO,SAAS,KAAI,SAAQ,gBAAgB,QAAQ,MAAM,SAAS,CAAC;EAC1G,MAAM,QAAQ,KAAK,MAAK;EACxB,OAAO,CAAC,UAAU,YAAY,YAAY,gBAAgB,QAAQ,OAAO,SAAS,CAAC;CACrF;CAOQ,gBAAgB,UAA2B,QAAyB;EAC1E,IAAI,OAAO,aAAa,UAAU,OAAO;EACzC,MAAM,MAAM,SAAS,cAAc,MAAM;EACzC,IAAI,MAAM,WAAW;EACrB,OAAO,YAAY,GAAG;EACtB,MAAM,KAAK,OAAO,WAAW,OAAO,iBAAiB,GAAG,CAAC,CAAC,QAAQ;EAClE,IAAI,OAAM;EACV,OAAO;CACT;CAEQ,MAAM,OAAI;EAChB,MAAM,SAAS,KAAK,UAAS,CAAE,CAAC;EAChC,MAAM,MAAM,OAAO,WAAW,IAAI;EAClC,IAAI,CAAC,KAAK;EAEV,MAAM,aACJ,KAAK,WAAU,MAAO,YAAY,OAAO,iBAAiB,MAAM,CAAC,CAAC,cAAc,eAAe,KAAK,WAAU;EAChH,MAAM,WAAW,KAAK,SAAQ;EAK9B,MAAM,kBAAkB,KAAK,gBAAgB,UAAU,MAAM;EAC7D,MAAM,cACJ,OAAO,aAAa,WAChB,GAAG,SAAQ,MACX,OAAO,SAAS,eAAe,IAC7B,GAAG,gBAAe,MAClB;EAER,MAAM,aAAa,GADA,KAAK,WACF,EAAU,GAAI,YAAW,GAAI;EAEnD,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,UAAU;EACtC,QAAQ;GACN,MAAM,SAAS,MAAM;EACvB;EAGA,IAAI,KAAK,WAAW;EAEpB,MAAM,OAAO,KAAK,KAAI;EACtB,MAAM,gBAAgB,KAAK,cAAa;EACxC,MAAM,YAAY,KAAK,UAAS;EAChC,MAAM,YAAY,KAAK,UAAS;EAGhC,MAAM,YAAY,SAAS,cAAc,QAAQ;EACjD,MAAM,SAAS,UAAU,WAAW,IAAI;EACxC,IAAI,CAAC,QAAQ;EACb,OAAO,OAAO;EACd,OAAO,eAAe;EAEtB,IAAI,aAAa;EACjB,IAAI,kBAAkB,GAAG;GACvB,KAAK,MAAM,MAAM,MAAM,cAAc,OAAO,YAAY,EAAE,CAAC,CAAC,QAAQ;GACpE,cAAc;EAChB,OACE,aAAa,OAAO,YAAY,IAAI,CAAC,CAAC;EAExC,MAAM,IAAI,OAAO,YAAY,IAAI;EACjC,MAAM,aAAa,EAAE,yBAAyB;EAC9C,MAAM,cAAc,kBAAkB,IAAI,aAAc,EAAE,0BAA0B,EAAE;EACtF,MAAM,eAAe,EAAE,2BAA2B;EAClD,MAAM,gBAAgB,EAAE,4BAA4B,kBAAkB;EACtE,MAAM,oBAAoB,KAAK,KAAK,kBAAkB,IAAI,aAAa,aAAa,WAAW;EAC/F,MAAM,cAAc,KAAK,KAAK,eAAe,aAAa;EAE1D,MAAM,mBAAmB;EACzB,MAAM,iBAAiB,oBAAoB;EAC3C,UAAU,QAAQ;EAClB,UAAU,SAAS;EACnB,MAAM,UAAU,mBAAmB;EAInC,MAAM,QAAQ,UAAkC;GAC9C,OAAO,OAAO;GACd,OAAO,eAAe;GACtB,IAAI,MAAM,UAAU,GAAG;IACrB,MAAM,IAAI,OAAO,qBAAqB,GAAG,GAAG,gBAAgB,CAAC;IAC7D,MAAM,SAAS,GAAG,MAAM,EAAE,aAAa,KAAK,MAAM,SAAS,IAAI,CAAC,CAAC;IACjE,OAAO,YAAY;GACrB,OACE,OAAO,YAAY,MAAM;GAG3B,IAAI,kBAAkB,GAAG;IACvB,IAAI,OAAO;IACX,KAAK,MAAM,MAAM,MAAM;KACrB,OAAO,SAAS,IAAI,MAAM,YAAY;KACtC,QAAQ,OAAO,YAAY,EAAE,CAAC,CAAC,QAAQ;IACzC;GACF,OACE,OAAO,SAAS,MAAM,UAAU,YAAY,YAAY;EAE5D;EACA,IAAI,UAAU;EAEd,MAAM,eAAuB;GAC3B,MAAM,QAAQ,KAAK,YAAY,MAAM;GACrC,MAAM,MAAM,MAAM,KAAK,GAAG;GAC1B,IAAI,QAAQ,SAAS,OAAO;GAC5B,IAAI,SAAS,OAAO,UAAU,GAAG,GAAG,gBAAgB,WAAW;GAC/D,UAAU;GACV,KAAK,KAAK;GACV,OAAO;EACT;EACA,OAAM;EAGN,MAAM,mBAAmB,YAAY;EACrC,MAAM,iBAAiB;EACvB,OAAO,QAAQ,iBAAiB,mBAAmB;EACnD,OAAO,SAAS,cAAc;EAC9B,IAAI,UAAU,kBAAkB,cAAc;EAE9C,MAAM,kBAAkB,mBAAmB;EAC3C,MAAM,iBAAiB;EACvB,MAAM,mBAAmB,kBAAkB;EAC3C,MAAM,oBAAoB,iBAAiB;EAM3C,MAAM,iBAAsB;GAC1B,IAAI,CAAC,OAAO,GAAG,KAAK,gBAAgB,KAAK,SAAQ,GAAI,MAAM,GAAG,eAAe,GAAG,KAAK,QAAO;EAC9F;EACA,OAAO,iBAAiB,UAAU,QAAQ;EAK1C,IAAI,KAAK,SAAS,qBAAoB,GAAI;GACxC,IAAI,UAAU,WAAW,GAAG,CAAC;GAI7B,KAAK,gBAAqB;IACxB,IAAI,CAAC,OAAM,GAAI;IACf,IAAI,KAAI;IACR,IAAI,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACjC,IAAI,UAAU,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;IAC/C,IAAI,QAAO;IACX,IAAI,UAAU,WAAW,GAAG,CAAC;GAC/B;GACA,KAAK,iBAAsB;IACzB,KAAK,UAAU;IACf,OAAO,oBAAoB,UAAU,QAAQ;GAC/C;GACA;EACF;EAEA,IAAI,aAAa;EACjB,IAAI,aAAa;EACjB,IAAI,cAAc;EAClB,IAAI,mBAAmB,KAAK,cAAa;EACzC,IAAI,kBAAkB;EACtB,IAAI,YAAY;EAChB,MAAM,gBAAgB,MAAO,KAAK,IAAG;EAErC,IAAI,QAAQ;EACZ,IAAI;EACJ,IAAI;EACJ,IAAI;EAEJ,MAAM,wBAA6B;GACjC,IAAI,CAAC,KAAK,WAAU,GAAI;GACxB,gBAAgB,iBAAgB;IAC9B,cAAc;IACd,iBAAiB,iBAAgB;KAC/B,cAAc;KACd,gBAAe;IACjB,GAAG,KAAK,eAAc,CAAE;GAC1B,GAAG,KAAK,eAAc,CAAE;EAC1B;EACA,IAAI,KAAK,WAAU,GAAI,gBAAe;EAEtC,MAAM,OAAO,OAAoB;GAC/B,IAAI,KAAK,YAAY,eAAe;IAClC,QAAQ,sBAAsB,GAAG;IACjC;GACF;GACA,YAAY;GAIZ,OAAM;GAEN,IAAI,UACF,CAAC,YAAY,IACb,CAAC,YAAY,IACb,iBAAiB,KAAK,YAAY,KAClC,cAAc,KAAK,YAAY,GAAG;GAGpC,IAAI,YAAY,kBAAkB;QAC7B,IAAI,aAAa,kBAAkB;QACnC,IAAI,YAAY,kBAAkB,KAAK,eAAc;QACrD,kBAAkB,KAAK,cAAa;GAEzC,MAAM,KAAK,KAAK,mBAAkB;GAClC,IAAI,KAAK,GAAG;IACV,MAAM,OAAO,KAAK,KAAK;IACvB,IAAI,mBAAmB,iBAAiB,mBAAmB,KAAK,IAAI,mBAAmB,MAAM,eAAe;SACvG,IAAI,mBAAmB,iBAC1B,mBAAmB,KAAK,IAAI,mBAAmB,MAAM,eAAe;GACxE,OACE,mBAAmB;GAGrB,IAAI,cAAc,cAChB,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAAK;IACpC,MAAM,KAAK,KAAK,MAAM,oBAAoB,KAAK,OAAM,IAAK,MAAO,SAAS;IAC1E,IAAI,UAAU,WAAW,GAAG,GAAG,gBAAgB,GAAG,IAAI,GAAG,gBAAgB,CAAC;GAC5E;QACK,IAAI,cAAc,YACvB,KAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,KAAK;IACvC,MAAM,KAAK,KAAK,MAAM,oBAAoB,KAAK,OAAM,IAAK,MAAO,SAAS;IAC1E,IAAI,UAAU,WAAW,GAAG,GAAG,GAAG,aAAa,GAAG,IAAI,GAAG,WAAW;GACtE;QACK;IAEL,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAAK;KACpC,MAAM,KAAK,KAAK,MAAM,oBAAoB,KAAK,OAAM,IAAK,MAAO,SAAS;KAC1E,IAAI,UAAU,WAAW,GAAG,GAAG,gBAAgB,GAAG,IAAI,GAAG,gBAAgB,CAAC;IAC5E;IAYA,IAAI,KAAI;IACR,IAAI,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAIjC,MAAM,QAAQ,KAAK,IAAI,GAAG,mBAAmB,SAAS;IACtD,MAAM,QAAQ,KAAK,IAAI,OAAO,QAAQ,OAAO,iBAAiB,YAAY,CAAC;IAC3E,MAAM,QAAQ;IACd,MAAM,QAAQ,KAAK,IAAI,OAAO,SAAS,OAAO,WAAW;IAEzD,MAAM,OAAO,IAAI,aAAa,OAAO,OAAO,OAAO,KAAK;IACxD,IAAI,UAAU,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;IAC/C,IAAI,aAAa,MAAM,OAAO,KAAK;IAEnC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;KAC9B,MAAM,KAAK,KAAK,MAAM,oBAAoB,KAAK,OAAM,IAAK,MAAO,YAAY,EAAG;KAChF,IAAI,OAAO,GAAG;KACd,MAAM,MAAM,IAAI,aAAa,QAAQ,GAAG,OAAO,GAAG,KAAK;KAGvD,IAAI,UAAU,QAAQ,GAAG,GAAG,GAAG,OAAO,MAAM;KAC5C,IAAI,aAAa,KAAK,QAAQ,GAAG,QAAQ,EAAE;IAC7C;IAEA,IAAI,QAAO;GACb;GAEA,QAAQ,sBAAsB,GAAG;EACnC;EAKA,MAAM,eAAoB;GACxB,IAAI,UAAU,GAAG,QAAQ,sBAAsB,GAAG;EACpD;EACA,MAAM,aAAkB;GACtB,qBAAqB,KAAK;GAC1B,QAAQ;EACV;EACA,MAAM,KACJ,OAAO,yBAAyB,cAC5B,OACA,IAAI,sBAAqB,YAAY,QAAQ,QAAQ,SAAS,EAAE,CAAC,iBAAiB,OAAM,IAAK,KAAI,CAAG;EAC1G,IAAI,IAAI,GAAG,QAAQ,MAAM;OACpB,OAAM;EAEX,MAAM,gBAAgB,GAAW,MAC/B,KAAK,mBAAmB,KAAK,oBAAoB,KAAK,kBAAkB,KAAK;EAO/E,MAAM,cAAc,SAAiB,YAA4B;GAC/D,MAAM,OAAO,OAAO,sBAAqB;GACzC,MAAM,QAAQ,KAAK,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ;GAC3D,OAAO,cAAc,UAAU,KAAK,QAAQ,QAAQ,UAAU,KAAK,OAAO,KAAK;EACjF;EAEA,MAAM,eAAe,MAAuB;GAC1C,IAAI,CAAC,KAAK,YAAW,GAAI;GACzB,aAAa,WAAW,EAAE,SAAS,EAAE,OAAO;EAC9C;EACA,MAAM,qBAA0B;GAC9B,aAAa;EACf;EAKA,MAAM,eAAe,MAAuB;GAC1C,IAAI,CAAC,KAAK,YAAW,GAAI;GACzB,MAAM,IAAI,EAAE,QAAQ;GACpB,aAAa,WAAW,EAAE,SAAS,EAAE,OAAO;EAC9C;EACA,MAAM,mBAAwB;GAC5B,aAAa;EACf;EACA,MAAM,gBAAqB;GACzB,IAAI,CAAC,KAAK,YAAW,GAAI;GACzB,aAAa;GACb,aAAa,UAAU;GACvB,aAAa,iBAAgB;IAC3B,aAAa;GACf,GAAG,GAAG;EACR;EAaA,OAAO,iBAAiB,aAAa,WAAW;EAChD,OAAO,iBAAiB,cAAc,YAAY;EAClD,OAAO,iBAAiB,aAAa,aAAa,EAAE,SAAS,KAAI,CAAE;EACnE,OAAO,iBAAiB,YAAY,UAAU;EAC9C,OAAO,iBAAiB,SAAS,OAAO;EAExC,KAAK,iBAAsB;GACzB,KAAI;GACJ,IAAI,WAAU;GACd,OAAO,oBAAoB,UAAU,QAAQ;GAC7C,aAAa,aAAa;GAC1B,aAAa,cAAc;GAC3B,aAAa,UAAU;GACvB,OAAO,oBAAoB,aAAa,WAAW;GACnD,OAAO,oBAAoB,cAAc,YAAY;GACrD,OAAO,oBAAoB,aAAa,WAAW;GACnD,OAAO,oBAAoB,YAAY,UAAU;GACjD,OAAO,oBAAoB,SAAS,OAAO;EAC7C;CACF;CAphBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAW,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAX,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAW,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,eAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,KAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,oBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,eAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA;GAAA,YAAA,EAAA,eAAA,+BAAA;GAAA,gBAAA;EAAA;EAAA,aAAA,CAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,QAAA;GAAA,aAAA;GAAA,UAAA;EAAA,CAAA;EAAA,UAAA;EAAA,UAAA;EAAA,UAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAAX,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAW,YAAA,CAAA;EAnBvB,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GAYV,UACE;GAEF,eAAe,kBAAkB;GACjC,MAAM;IAAE,OAAO;IAAiB,iBAAiB;GAA8B;EAChF,CAAA;CAmFgF,CAAA;CAAA,sBAAA,CAAA;CAAA,gBAAA;EAAA,MAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,OAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,UAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,eAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,KAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,oBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,aAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,YAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,gBAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,eAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,WAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,UAAQ,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;CAAA;AAAA,CAAA"}
1
+ {"version":3,"file":"ngwr-bits-fuzzy-text.mjs","names":[],"sources":["../../../projects/lib/bits/fuzzy-text/fuzzy-text.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Angular adaptation of the FuzzyText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/fuzzy-text\n *\n * Renders text to an offscreen 2D canvas, then redraws it onto the\n * visible canvas one row (or column) at a time with random horizontal /\n * vertical offsets. Intensity reacts to hover / click / glitch state.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport type { ElementRef } from '@angular/core';\nimport {\n Component,\n DestroyRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n effect,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\nimport type { WrFuzzyTextDirection } from './types';\n\n/**\n * Canvas-rendered \"fuzzy\" text — each row/column of glyphs is offset by\n * a random amount per frame, giving the text a TV-static or motion-blur\n * appearance. Intensity ramps up on hover, click, and during periodic\n * glitch bursts (configurable). Off screen, or drawn at no size, the loop\n * stops asking for frames and resumes where it left off.\n *\n * @example\n * ```html\n * <wr-fuzzy-text text=\"404\" />\n * <wr-fuzzy-text\n * text=\"HELLO\"\n * color=\"#0ea5e9\"\n * [fuzzRange]=\"50\"\n * [hoverIntensity]=\"0.8\"\n * [glitchMode]=\"true\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/fuzzy-text\n */\n@Component({\n selector: 'wr-fuzzy-text',\n // The text is drawn into the canvas as PIXELS, so without the first span there is\n // nothing for a screen reader to read at all — the same layer `wr-rotating-text`\n // and `wr-decrypt-text` carry. Hidden inline rather than through a class: this\n // entry point ships no stylesheet, and a consumer who imported none would\n // otherwise see the text twice.\n //\n // BOUND, not a static `style=\"…\"` attribute. A real attribute is refused under\n // `style-src 'self'`, and what is lost there is not a colour: the span drops\n // every visual-hiding declaration and the word renders on screen beside the\n // canvas that already draws it. A binding goes through `style.setProperty`,\n // which is CSSOM and outside CSP's reach.\n template:\n '<span class=\"wr-fuzzy-text__sr-only\" [style]=\"srOnly\">{{ text() }}</span>' +\n '<canvas #canvas aria-hidden=\"true\" [style]=\"fit\"></canvas>',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-fuzzy-text', '[style.color]': \"'var(--wr-color-on-surface)'\" },\n})\nexport class WrFuzzyText {\n /**\n * Visual-hiding declarations for the screen-reader copy of the text. A stable\n * object literal, so the binding is written once rather than on every pass.\n */\n protected readonly srOnly: Readonly<Record<string, string>> = {\n position: 'absolute',\n width: '1px',\n height: '1px',\n overflow: 'hidden',\n 'clip-path': 'inset(50%)',\n 'white-space': 'nowrap',\n };\n\n /**\n * The canvas is as wide as the text it draws, and text cannot wrap, so a long\n * string or a narrow screen pushed it past its container and clipped it. Scaled\n * down to fit instead, keeping its aspect ratio; the pointer maths below reads\n * the drawn size back. Bound for the reason `srOnly` is.\n */\n protected readonly fit: Readonly<Record<string, string>> = { 'max-width': '100%', height: 'auto' };\n\n /** Text to render. */\n readonly text = input.required<string>();\n\n /** Font size as CSS length (`'4rem'`, `'72px'`, …) or px number. @default 'clamp(2rem, 10vw, 10rem)' */\n readonly fontSize = input<string | number>('clamp(2rem, 10vw, 10rem)');\n\n /** Font weight. @default 900 */\n readonly fontWeight = input<number | string>(900);\n\n /** Font family. `'inherit'` reads the canvas's computed style. @default 'inherit' */\n readonly fontFamily = input('inherit');\n\n /**\n * Text colour, any CSS colour — `var()` and `light-dark()` included, resolved\n * against the canvas. `'inherit'` follows the canvas's computed colour. Both are\n * re-read while the text animates, so a colour-scheme change lands on the next\n * frame, and the still copy drawn under reduced motion is redrawn when the\n * page's scheme switches. Ignored if `[gradient]` is set. @default 'inherit'\n */\n readonly color = input('inherit');\n\n /** Optional horizontal gradient stops, each resolved the way `color` is. */\n readonly gradient = input<readonly string[] | null>(null);\n\n /** Enable hover / touch reactivity. @default true */\n readonly enableHover = input(true, { transform: coerceBooleanProperty });\n\n /** Base intensity (0..1) when idle. @default 0.18 */\n readonly baseIntensity = input(0.18, { transform: numAttr(0.18) });\n\n /** Intensity (0..1) while the pointer hovers the text. @default 0.5 */\n readonly hoverIntensity = input(0.5, { transform: numAttr(0.5) });\n\n /** Max pixel displacement per row/column at intensity 1. @default 30 */\n readonly fuzzRange = input(30, { transform: numAttr(30) });\n\n /** Frame rate cap. @default 60 */\n readonly fps = input(60, { transform: numAttr(60) });\n\n /** Fuzz direction. @default 'horizontal' */\n readonly direction = input<WrFuzzyTextDirection>('horizontal');\n\n /** Ms to ease intensity toward target. `0` snaps. @default 0 */\n readonly transitionDuration = input(0, { transform: numAttr(0) });\n\n /** Spike to full intensity briefly on click. @default false */\n readonly clickEffect = input(false, { transform: coerceBooleanProperty });\n\n /** Periodic \"glitch\" bursts at full intensity. @default false */\n readonly glitchMode = input(false, { transform: coerceBooleanProperty });\n\n /** Ms between glitch bursts. @default 2000 */\n readonly glitchInterval = input(2000, { transform: numAttr(2000) });\n\n /** Ms a glitch burst lasts. @default 200 */\n readonly glitchDuration = input(200, { transform: numAttr(200) });\n\n /** Extra pixels between glyphs. @default 0 */\n readonly letterSpacing = input(0, { transform: numAttr(0) });\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n\n private teardown: (() => void) | undefined;\n\n /**\n * Re-bakes and redraws the still copy, set only while one is on the canvas.\n * The fuzz loop needs none: every frame it draws re-resolves the fill.\n */\n private repaint: (() => void) | undefined;\n\n /**\n * `init()` suspends on font loading, so the component can be destroyed while\n * it is awaiting. At that moment `teardown` is still undefined, so the\n * destroy hook has nothing to call — and everything the rest of `init()`\n * starts (the rAF loop, the glitch timers, canvas listeners) would run for\n * the life of the page. This flag lets the continuation bail out.\n */\n private destroyed = false;\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n void this.init();\n });\n\n // Re-init whenever any input that affects the offscreen rendering changes.\n effect(() => {\n this.text();\n this.fontSize();\n this.fontWeight();\n this.fontFamily();\n this.color();\n this.gradient();\n this.letterSpacing();\n this.direction();\n this.fuzzRange();\n this.fps();\n this.glitchMode();\n this.glitchInterval();\n this.glitchDuration();\n // Read live: `init()` chooses between the still copy and the loop, so a\n // preference switched while the page is open needs a fresh one.\n this.platform.prefersReducedMotion();\n this.restart();\n });\n\n // The still copy keeps the colours it was baked in, so a colour-scheme\n // switch under reduced motion has to ask for it again.\n watchColorScheme(() => this.repaint?.());\n\n this.destroyRef.onDestroy(() => {\n this.destroyed = true;\n this.teardown?.();\n });\n }\n\n // Init / loop\n\n /** Tear the running instance down and boot a fresh one. A no-op before the first boot. */\n private restart(): void {\n if (!this.teardown) return;\n this.teardown();\n this.teardown = undefined;\n queueMicrotask(() => {\n void this.init();\n });\n }\n\n /**\n * The fill, as computed colours: one entry for `color`, or one per gradient stop.\n * A canvas reads neither a custom property nor `light-dark()` — `fillStyle` keeps\n * its previous value and `addColorStop` throws — so each goes through the cascade\n * first, falling back to the canvas's own computed colour.\n */\n private resolveFill(canvas: HTMLCanvasElement): string[] {\n const inherited = window.getComputedStyle(canvas).color || '#fff';\n const gradient = this.gradient();\n if (gradient && gradient.length >= 2) return gradient.map(stop => resolveCssColor(canvas, stop, inherited));\n const color = this.color();\n return [color === 'inherit' ? inherited : resolveCssColor(canvas, color, inherited)];\n }\n\n /**\n * CSS font size in pixels, resolved where the canvas would resolve it: inside the\n * canvas, whose fallback content is never painted, so `em` and `%` follow the\n * host's own font size.\n */\n private measureFontSize(fontSize: string | number, canvas: HTMLCanvasElement): number {\n if (typeof fontSize === 'number') return fontSize;\n const tmp = document.createElement('span');\n tmp.style.fontSize = fontSize;\n canvas.appendChild(tmp);\n const px = Number.parseFloat(window.getComputedStyle(tmp).fontSize);\n tmp.remove();\n return px;\n }\n\n private async init(): Promise<void> {\n const canvas = this.canvasRef().nativeElement;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const fontFamily =\n this.fontFamily() === 'inherit' ? window.getComputedStyle(canvas).fontFamily || 'sans-serif' : this.fontFamily();\n const fontSize = this.fontSize();\n // Numeric font size for buffer maths — and for the font string itself. The canvas\n // was handed the raw CSS, and Chromium caches a parsed font by its string, so a\n // `clamp(2rem, 10vw, 10rem)` kept the pixel size of the viewport it was first\n // parsed at however often the canvas was rebuilt after a resize.\n const numericFontSize = this.measureFontSize(fontSize, canvas);\n const fontSizeStr =\n typeof fontSize === 'number'\n ? `${fontSize}px`\n : Number.isFinite(numericFontSize)\n ? `${numericFontSize}px`\n : fontSize;\n const fontWeight = this.fontWeight();\n const fontString = `${fontWeight} ${fontSizeStr} ${fontFamily}`;\n\n try {\n await document.fonts.load(fontString);\n } catch {\n await document.fonts.ready;\n }\n\n // Destroyed while the fonts were loading — nothing below may start.\n if (this.destroyed) return;\n\n const text = this.text();\n const letterSpacing = this.letterSpacing();\n const fuzzRange = this.fuzzRange();\n const direction = this.direction();\n\n // Offscreen canvas with the laid-out text.\n const offscreen = document.createElement('canvas');\n const offCtx = offscreen.getContext('2d');\n if (!offCtx) return;\n offCtx.font = fontString;\n offCtx.textBaseline = 'alphabetic';\n\n let totalWidth = 0;\n if (letterSpacing !== 0) {\n for (const ch of text) totalWidth += offCtx.measureText(ch).width + letterSpacing;\n totalWidth -= letterSpacing;\n } else {\n totalWidth = offCtx.measureText(text).width;\n }\n const m = offCtx.measureText(text);\n const actualLeft = m.actualBoundingBoxLeft ?? 0;\n const actualRight = letterSpacing !== 0 ? totalWidth : (m.actualBoundingBoxRight ?? m.width);\n const actualAscent = m.actualBoundingBoxAscent ?? numericFontSize;\n const actualDescent = m.actualBoundingBoxDescent ?? numericFontSize * 0.2;\n const textBoundingWidth = Math.ceil(letterSpacing !== 0 ? totalWidth : actualLeft + actualRight);\n const tightHeight = Math.ceil(actualAscent + actualDescent);\n\n const extraWidthBuffer = 10;\n const offscreenWidth = textBoundingWidth + extraWidthBuffer;\n offscreen.width = offscreenWidth;\n offscreen.height = tightHeight;\n const xOffset = extraWidthBuffer / 2;\n\n // The text is baked into the offscreen canvas in the colours resolved now, and\n // re-baked whenever they resolve differently (see `refill` in the loop).\n const bake = (fills: readonly string[]): void => {\n offCtx.font = fontString;\n offCtx.textBaseline = 'alphabetic';\n if (fills.length >= 2) {\n const g = offCtx.createLinearGradient(0, 0, offscreenWidth, 0);\n fills.forEach((c, i) => g.addColorStop(i / (fills.length - 1), c));\n offCtx.fillStyle = g;\n } else {\n offCtx.fillStyle = fills[0];\n }\n\n if (letterSpacing !== 0) {\n let xPos = xOffset;\n for (const ch of text) {\n offCtx.fillText(ch, xPos, actualAscent);\n xPos += offCtx.measureText(ch).width + letterSpacing;\n }\n } else {\n offCtx.fillText(text, xOffset - actualLeft, actualAscent);\n }\n };\n let fillKey = '';\n /** Re-resolve the fill; re-bake and report `true` when it changed. */\n const refill = (): boolean => {\n const fills = this.resolveFill(canvas);\n const key = fills.join('|');\n if (key === fillKey) return false;\n if (fillKey) offCtx.clearRect(0, 0, offscreenWidth, tightHeight);\n fillKey = key;\n bake(fills);\n return true;\n };\n refill();\n\n // Size the visible canvas, leaving room for the max horizontal fuzz.\n const horizontalMargin = fuzzRange + 20;\n const verticalMargin = 0;\n canvas.width = offscreenWidth + horizontalMargin * 2;\n canvas.height = tightHeight + verticalMargin * 2;\n ctx.translate(horizontalMargin, verticalMargin);\n\n const interactiveLeft = horizontalMargin + xOffset;\n const interactiveTop = verticalMargin;\n const interactiveRight = interactiveLeft + textBoundingWidth;\n const interactiveBottom = interactiveTop + tightHeight;\n\n // A viewport-relative size (the default is `clamp(2rem, 10vw, 10rem)`) is\n // measured into the bitmap once, so a rotated phone or a resized window kept\n // the old size until a reload. Re-measured on resize, and rebuilt only when\n // the pixel size actually moved.\n const onResize = (): void => {\n if (!Object.is(this.measureFontSize(this.fontSize(), canvas), numericFontSize)) this.restart();\n };\n window.addEventListener('resize', onResize);\n\n // Reduced motion: paint the text once, clean — no fuzz loop, no\n // glitch bursts, no pointer reactivity. Teardown must still be set\n // so the re-init effect keeps redrawing on input changes.\n if (this.platform.prefersReducedMotion()) {\n ctx.drawImage(offscreen, 0, 0);\n // A scheme switch re-bakes the text in whatever its colours now resolve\n // to and draws it again; a switch that leaves them alone draws nothing.\n // Cleared in device space, the way the loop's second pass clears.\n this.repaint = (): void => {\n if (!refill()) return;\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.restore();\n ctx.drawImage(offscreen, 0, 0);\n };\n this.teardown = (): void => {\n this.repaint = undefined;\n window.removeEventListener('resize', onResize);\n };\n return;\n }\n\n let isHovering = false;\n let isClicking = false;\n let isGlitching = false;\n let currentIntensity = this.baseIntensity();\n let targetIntensity = currentIntensity;\n let lastFrame = 0;\n const frameDuration = 1000 / this.fps();\n\n let rafId = 0;\n let glitchOnTimer: ReturnType<typeof setTimeout> | undefined;\n let glitchOffTimer: ReturnType<typeof setTimeout> | undefined;\n let clickTimer: ReturnType<typeof setTimeout> | undefined;\n\n const startGlitchLoop = (): void => {\n if (!this.glitchMode()) return;\n glitchOnTimer = setTimeout(() => {\n isGlitching = true;\n glitchOffTimer = setTimeout(() => {\n isGlitching = false;\n startGlitchLoop();\n }, this.glitchDuration());\n }, this.glitchInterval());\n };\n if (this.glitchMode()) startGlitchLoop();\n\n const run = (ts: number): void => {\n if (ts - lastFrame < frameDuration) {\n rafId = requestAnimationFrame(run);\n return;\n }\n lastFrame = ts;\n\n // A colour scheme switched under a running loop lands here: the text is\n // re-baked in whatever its colours now resolve to.\n refill();\n\n ctx.clearRect(\n -fuzzRange - 20,\n -fuzzRange - 10,\n offscreenWidth + 2 * (fuzzRange + 20),\n tightHeight + 2 * (fuzzRange + 10)\n );\n\n if (isClicking) targetIntensity = 1;\n else if (isGlitching) targetIntensity = 1;\n else if (isHovering) targetIntensity = this.hoverIntensity();\n else targetIntensity = this.baseIntensity();\n\n const td = this.transitionDuration();\n if (td > 0) {\n const step = 1 / (td / frameDuration);\n if (currentIntensity < targetIntensity) currentIntensity = Math.min(currentIntensity + step, targetIntensity);\n else if (currentIntensity > targetIntensity)\n currentIntensity = Math.max(currentIntensity - step, targetIntensity);\n } else {\n currentIntensity = targetIntensity;\n }\n\n if (direction === 'horizontal') {\n for (let j = 0; j < tightHeight; j++) {\n const dx = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, 0, j, offscreenWidth, 1, dx, j, offscreenWidth, 1);\n }\n } else if (direction === 'vertical') {\n for (let i = 0; i < offscreenWidth; i++) {\n const dy = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, i, 0, 1, tightHeight, i, dy, 1, tightHeight);\n }\n } else {\n // both: horizontal pass, then a vertical pass over what it drew.\n for (let j = 0; j < tightHeight; j++) {\n const dx = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, 0, j, offscreenWidth, 1, dx, j, offscreenWidth, 1);\n }\n\n // The second pass runs in DEVICE space, with the transform off.\n //\n // `getImageData` and `putImageData` are specified to ignore the\n // current transformation matrix; `drawImage` and `clearRect` honour\n // it. The context carries `translate(horizontalMargin, …)`, so the\n // two halves of this branch were addressing pixels two different\n // ways: the read started at device x=0, a whole margin to the LEFT of\n // what the horizontal pass had drawn, and the write put it back\n // there — shifting the text off its own box every frame. Resetting\n // the matrix for the duration is what makes the four calls agree.\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n\n // Wide enough for the fuzz the first pass may have thrown either way,\n // and clamped to the canvas so no read runs off the edge.\n const readX = Math.max(0, horizontalMargin - fuzzRange);\n const readW = Math.min(canvas.width - readX, offscreenWidth + fuzzRange * 2);\n const readY = verticalMargin;\n const readH = Math.min(canvas.height - readY, tightHeight);\n\n const data = ctx.getImageData(readX, readY, readW, readH);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.putImageData(data, readX, readY);\n\n for (let i = 0; i < readW; i++) {\n const dy = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange * 0.5);\n if (dy === 0) continue;\n const col = ctx.getImageData(readX + i, readY, 1, readH);\n // `putImageData` REPLACES rather than composites, so the column has\n // to be cleared before it is written back at its offset.\n ctx.clearRect(readX + i, 0, 1, canvas.height);\n ctx.putImageData(col, readX + i, readY + dy);\n }\n\n ctx.restore();\n }\n\n rafId = requestAnimationFrame(run);\n };\n\n // Off screen, or drawn at no size, the loop stops asking for frames, the way\n // `wr-waves` does: it is decoration, and every frame out of sight still redrew\n // one slice per row. The box is the canvas as DRAWN — `max-width: 100%` shrinks\n // it to nothing in a parent with no width — not its bitmap, which is the text's\n // size. Without the observers (jsdom) nothing reports a change, so what is\n // measured here stands: a box runs the loop, no box parks it. A box flush\n // against a zero-height clip intersects with nothing showing, and only a second\n // threshold reports the clip moving. It sits below any real ratio, or a clip\n // closing on a transition steps from under it to 0 without crossing anything.\n let visible = typeof IntersectionObserver === 'undefined';\n let hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;\n const resume = (): void => {\n if (visible && hasBox && rafId === 0) rafId = requestAnimationFrame(run);\n };\n const park = (): void => {\n cancelAnimationFrame(rafId);\n rafId = 0;\n };\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(\n entries => {\n const { isIntersecting, intersectionRatio } = entries[entries.length - 1];\n visible = isIntersecting && intersectionRatio > 0;\n if (visible) resume();\n else park();\n },\n { threshold: [0, 1e-9] }\n );\n io?.observe(canvas);\n const ro =\n typeof ResizeObserver === 'undefined'\n ? null\n : new ResizeObserver(() => {\n hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;\n if (hasBox) resume();\n else park();\n });\n ro?.observe(canvas);\n resume();\n\n const isInsideText = (x: number, y: number): boolean =>\n x >= interactiveLeft && x <= interactiveRight && y >= interactiveTop && y <= interactiveBottom;\n\n /**\n * A client point in canvas pixels. The canvas may be drawn smaller than its\n * bitmap (`fit`), so the offset is scaled back up; a box with no size (jsdom)\n * is taken as unscaled.\n */\n const isOverText = (clientX: number, clientY: number): boolean => {\n const rect = canvas.getBoundingClientRect();\n const scale = rect.width > 0 ? canvas.width / rect.width : 1;\n return isInsideText((clientX - rect.left) * scale, (clientY - rect.top) * scale);\n };\n\n const onMouseMove = (e: MouseEvent): void => {\n if (!this.enableHover()) return;\n isHovering = isOverText(e.clientX, e.clientY);\n };\n const onMouseLeave = (): void => {\n isHovering = false;\n };\n // Passive, and it never cancels: the text is decoration, and a swipe that\n // starts on it has to scroll the page like a swipe anywhere else. It used to\n // `preventDefault()`, which turned the whole canvas into a scroll dead zone on\n // every touch screen.\n const onTouchMove = (e: TouchEvent): void => {\n if (!this.enableHover()) return;\n const t = e.touches[0];\n isHovering = isOverText(t.clientX, t.clientY);\n };\n const onTouchEnd = (): void => {\n isHovering = false;\n };\n const onClick = (): void => {\n if (!this.clickEffect()) return;\n isClicking = true;\n clearTimeout(clickTimer);\n clickTimer = setTimeout(() => {\n isClicking = false;\n }, 150);\n };\n\n // Attached unconditionally, and gated inside the three handlers that can SET a\n // reactive state instead. `enableHover` and `clickEffect` are not in the re-init\n // effect's dependency list, so an attach conditional on them read the value at\n // boot and never again: a `[clickEffect]` bound to a signal that starts `false` —\n // the default, so the ordinary case — stayed dead after it flipped true. Adding\n // them to that list would restart the rAF loop and the glitch timers just to\n // toggle a listener, which is visible; the guards are not.\n //\n // `onMouseLeave` / `onTouchEnd` stay ungated deliberately: they only ever CLEAR\n // toward the idle state, and gating them would strand `isHovering` on for good\n // when hover is switched off mid-hover.\n canvas.addEventListener('mousemove', onMouseMove);\n canvas.addEventListener('mouseleave', onMouseLeave);\n canvas.addEventListener('touchmove', onTouchMove, { passive: true });\n canvas.addEventListener('touchend', onTouchEnd);\n canvas.addEventListener('click', onClick);\n\n this.teardown = (): void => {\n park();\n io?.disconnect();\n ro?.disconnect();\n window.removeEventListener('resize', onResize);\n clearTimeout(glitchOnTimer);\n clearTimeout(glitchOffTimer);\n clearTimeout(clickTimer);\n canvas.removeEventListener('mousemove', onMouseMove);\n canvas.removeEventListener('mouseleave', onMouseLeave);\n canvas.removeEventListener('touchmove', onTouchMove);\n canvas.removeEventListener('touchend', onTouchEnd);\n canvas.removeEventListener('click', onClick);\n };\n }\n}\n\nexport type { WrFuzzyTextDirection } from './types';\n"],"mappings":"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"}