ngwr 15.3.0 → 15.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bits/border-glow/styles/_rules.scss +60 -49
- package/bits/click-spark/click-spark.scss +5 -1
- package/bits/click-spark/styles/_index.scss +10 -15
- package/bits/click-spark/styles/_rules.scss +20 -0
- package/comment/styles/_index.scss +270 -0
- package/dialog/styles/_index.scss +6 -0
- package/fesm2022/ngwr-bits-aurora.mjs +108 -52
- package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
- package/fesm2022/ngwr-bits-border-glow-testing.mjs +8 -8
- package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
- package/fesm2022/ngwr-bits-border-glow.mjs +29 -26
- package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-bits-dot-grid.mjs +11 -14
- package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
- package/fesm2022/ngwr-bits-fuzzy-text.mjs +18 -4
- package/fesm2022/ngwr-bits-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-bits-ripple-grid.mjs +14 -13
- package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
- package/fesm2022/ngwr-bits-splash-cursor.mjs +21 -10
- package/fesm2022/ngwr-bits-splash-cursor.mjs.map +1 -1
- package/fesm2022/ngwr-bits-waves.mjs +10 -15
- package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
- package/fesm2022/ngwr-comment-testing.mjs +60 -0
- package/fesm2022/ngwr-comment-testing.mjs.map +1 -0
- package/fesm2022/ngwr-comment.mjs +516 -0
- package/fesm2022/ngwr-comment.mjs.map +1 -0
- package/fesm2022/ngwr-dialog-testing.mjs +14 -0
- package/fesm2022/ngwr-dialog-testing.mjs.map +1 -1
- package/fesm2022/ngwr-dialog.mjs +293 -134
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +2 -0
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-editor.mjs +1 -1
- package/fesm2022/ngwr-hotkey.mjs +86 -6
- package/fesm2022/ngwr-hotkey.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ar.mjs +19 -0
- package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-cs.mjs +19 -0
- package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-de.mjs +19 -0
- package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +19 -0
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-es.mjs +19 -0
- package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-fr.mjs +19 -0
- package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-he.mjs +19 -0
- package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-hi.mjs +19 -0
- package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-id.mjs +19 -0
- package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-it.mjs +19 -0
- package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ja.mjs +19 -0
- package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ko.mjs +19 -0
- package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-nl.mjs +19 -0
- package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-pl.mjs +19 -0
- package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-pt.mjs +19 -0
- package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +19 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-sv.mjs +19 -0
- package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-tr.mjs +19 -0
- package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-uk.mjs +19 -0
- package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-vi.mjs +19 -0
- package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-zh-tw.mjs +19 -0
- package/fesm2022/ngwr-i18n-zh-tw.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-zh.mjs +19 -0
- package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
- package/fesm2022/ngwr-input-number-testing.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +109 -12
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-keyboard.mjs +78 -18
- package/fesm2022/ngwr-keyboard.mjs.map +1 -1
- package/fesm2022/ngwr-pipes.mjs +162 -51
- package/fesm2022/ngwr-pipes.mjs.map +1 -1
- package/fesm2022/ngwr-platform.mjs +134 -2
- package/fesm2022/ngwr-platform.mjs.map +1 -1
- package/fesm2022/ngwr-schema-form.mjs +2 -1
- package/fesm2022/ngwr-schema-form.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +11 -16
- package/fesm2022/ngwr-statistic.mjs.map +1 -1
- package/fesm2022/ngwr-storage.mjs +54 -33
- package/fesm2022/ngwr-storage.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +90 -34
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-transfer.mjs +1 -1
- package/fesm2022/ngwr-utils-router.mjs +165 -0
- package/fesm2022/ngwr-utils-router.mjs.map +1 -0
- package/fesm2022/ngwr-utils.mjs +311 -3
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/i18n/ar.json +19 -0
- package/i18n/cs.json +19 -0
- package/i18n/de.json +19 -0
- package/i18n/en.json +19 -0
- package/i18n/es.json +19 -0
- package/i18n/fr.json +19 -0
- package/i18n/he.json +19 -0
- package/i18n/hi.json +19 -0
- package/i18n/id.json +19 -0
- package/i18n/it.json +19 -0
- package/i18n/ja.json +19 -0
- package/i18n/ko.json +19 -0
- package/i18n/nl.json +19 -0
- package/i18n/pl.json +19 -0
- package/i18n/pt.json +19 -0
- package/i18n/ru.json +19 -0
- package/i18n/sv.json +19 -0
- package/i18n/tr.json +19 -0
- package/i18n/uk.json +19 -0
- package/i18n/vi.json +19 -0
- package/i18n/zh-TW.json +19 -0
- package/i18n/zh.json +19 -0
- package/llms-full.txt +32 -16
- package/package.json +14 -1
- package/schematics/use/symbol-map.json +3 -0
- package/skills/ngwr/SKILL.md +4 -2
- package/skills/ngwr/references/catalog.md +3 -0
- package/table/styles/_index.scss +17 -0
- package/types/ngwr-badge.d.ts +2 -2
- package/types/ngwr-bits-aurora.d.ts +24 -2
- package/types/ngwr-bits-border-glow-testing.d.ts +9 -5
- package/types/ngwr-bits-border-glow.d.ts +10 -4
- package/types/ngwr-bits-fuzzy-text.d.ts +2 -1
- package/types/ngwr-bits-ripple-grid.d.ts +7 -2
- package/types/ngwr-bits-splash-cursor.d.ts +2 -1
- package/types/ngwr-button.d.ts +1 -1
- package/types/ngwr-comment-testing.d.ts +90 -0
- package/types/ngwr-comment.d.ts +304 -0
- package/types/ngwr-dialog-testing.d.ts +7 -0
- package/types/ngwr-dialog.d.ts +109 -6
- package/types/ngwr-divider.d.ts +1 -1
- package/types/ngwr-drawer.d.ts +1 -1
- package/types/ngwr-hotkey.d.ts +60 -3
- package/types/ngwr-image-cropper.d.ts +1 -1
- package/types/ngwr-input-number-testing.d.ts +5 -0
- package/types/ngwr-input-number.d.ts +91 -12
- package/types/ngwr-keyboard.d.ts +28 -1
- package/types/ngwr-pipes.d.ts +142 -32
- package/types/ngwr-platform.d.ts +73 -7
- package/types/ngwr-popconfirm.d.ts +2 -2
- package/types/ngwr-progress.d.ts +1 -1
- package/types/ngwr-select.d.ts +19 -13
- package/types/ngwr-skeleton.d.ts +1 -1
- package/types/ngwr-storage.d.ts +33 -3
- package/types/ngwr-table.d.ts +145 -44
- package/types/ngwr-utils-router.d.ts +95 -0
- package/types/ngwr-utils.d.ts +262 -2
- package/types/ngwr-version.d.ts +1 -1
|
@@ -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":"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"}
|
|
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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"}
|
|
@@ -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 { coerceBooleanProperty } from "@angular/cdk/coercion";
|
|
4
4
|
import { isPlatformBrowser } from "@angular/common";
|
|
5
5
|
import { clamp, numAttr, resolveCssColor, watchColorScheme } from "ngwr/utils";
|
|
@@ -156,27 +156,26 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
156
156
|
wake = null;
|
|
157
157
|
armIdle = null;
|
|
158
158
|
booted = signal(false, ...ngDevMode ? [{ debugName: "booted" }] : /* istanbul ignore next */ []);
|
|
159
|
+
hasBox = signal(false, ...ngDevMode ? [{ debugName: "hasBox" }] : /* istanbul ignore next */ []);
|
|
159
160
|
constructor() {
|
|
160
161
|
if (!this.isBrowser) return;
|
|
161
162
|
afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));
|
|
162
|
-
|
|
163
|
+
afterRenderEffect(() => {
|
|
164
|
+
if (!this.booted()) return;
|
|
165
|
+
const still = this.platform.prefersReducedMotion();
|
|
163
166
|
this.cell();
|
|
164
167
|
this.stroke();
|
|
165
168
|
this.lineColor();
|
|
166
169
|
this.rippleColor();
|
|
167
170
|
this.strength();
|
|
168
|
-
untracked(() =>
|
|
171
|
+
untracked(() => {
|
|
172
|
+
if (still) this.rest();
|
|
173
|
+
this.requestDraw();
|
|
174
|
+
});
|
|
169
175
|
});
|
|
170
176
|
watchColorScheme(() => this.requestDraw());
|
|
171
177
|
effect((onCleanup) => {
|
|
172
|
-
if (!this.booted()) return;
|
|
173
|
-
if (this.platform.prefersReducedMotion()) {
|
|
174
|
-
untracked(() => {
|
|
175
|
-
this.rest();
|
|
176
|
-
this.draw();
|
|
177
|
-
});
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
178
|
+
if (!this.booted() || !this.hasBox() || this.platform.prefersReducedMotion()) return;
|
|
180
179
|
const trail = this.rippleOnMove();
|
|
181
180
|
const idle = this.idle();
|
|
182
181
|
onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact(trail, idle))));
|
|
@@ -254,12 +253,13 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
254
253
|
target?.addEventListener("pointercancel", onLeave, { passive: true });
|
|
255
254
|
}
|
|
256
255
|
const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => {
|
|
257
|
-
|
|
256
|
+
const { isIntersecting, intersectionRatio } = entries[entries.length - 1];
|
|
257
|
+
visible = isIntersecting && intersectionRatio > 0;
|
|
258
258
|
if (visible) {
|
|
259
259
|
wake();
|
|
260
260
|
armIdle();
|
|
261
261
|
} else halt();
|
|
262
|
-
});
|
|
262
|
+
}, { threshold: [0, 1e-9] });
|
|
263
263
|
io?.observe(hostEl);
|
|
264
264
|
this.wake = wake;
|
|
265
265
|
this.armIdle = armIdle;
|
|
@@ -308,6 +308,7 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
308
308
|
height
|
|
309
309
|
};
|
|
310
310
|
this.ratio = ratio;
|
|
311
|
+
this.hasBox.set(width > 0 && height > 0);
|
|
311
312
|
const canvas = this.canvasRef().nativeElement;
|
|
312
313
|
canvas.width = Math.round(width * ratio);
|
|
313
314
|
canvas.height = Math.round(height * ratio);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-ripple-grid.mjs","names":[],"sources":["../../../projects/lib/bits/ripple-grid/ripple-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 Ripple Grid background on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/ripple-grid). The idea is the same;\n * the code is ngwr's own: the original is a WebGL shader, this is a 2D canvas\n * of grid lines bent by rings that travel out from the pointer, and a frame\n * loop that sleeps whenever no ring is alive.\n *\n * A 2D canvas rather than WebGL, deliberately. The picture is a few dozen\n * hairlines, which a 2D context antialiases on its own; the bend is evaluated\n * only along the stretch of each line a ring can reach, so a still grid is\n * two points per line; it boots with no shader to compile, and it still draws\n * on the machines that blocklist WebGL2.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\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 { clamp, numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** One ring, in the host's layout box. */\ninterface Ripple {\n readonly x: number;\n readonly y: number;\n /** Milliseconds since it left its origin. */\n age: number;\n /** Its strength as a share of `strength`: a press is 1, a move or an idle ripple less. */\n readonly share: number;\n}\n\n/** A ring asked for since the last frame; the next frame places it. */\ninterface Pending {\n readonly x: number;\n readonly y: number;\n readonly share: number;\n /** Whether `x` and `y` are viewport coordinates (a pointer) rather than the box's own (the idle ripple). */\n readonly client: boolean;\n}\n\n/**\n * One alpha step's lit stretches, as polylines packed end to end into buffers that\n * keep their capacity frame to frame: `points` holds `[x, y, x, y, …]` with `length`\n * entries in use, `starts` the index in `points` where each run begins, `runs` in use.\n */\ninterface GlowRuns {\n points: Float64Array;\n length: number;\n starts: Uint32Array;\n runs: number;\n}\n\n/** What a frame needs to know about a live ring, worked out once per frame rather than once per sample. */\ninterface Ring {\n x: number;\n y: number;\n /** Distance the crest has travelled, in px. */\n radius: number;\n /** The band the ring reaches, from `inner` to `outer` px from its origin, and both squared. */\n inner: number;\n outer: number;\n inner2: number;\n outer2: number;\n /** Displacement at the crest before the overlap budget, in px; negative pulls inward. */\n raw: number;\n /** Displacement at the crest as drawn, within the overlap budget, in px. */\n push: number;\n /** Glow at the crest, 0..1. */\n glow: number;\n}\n\n/**\n * The most bitmap pixels the canvas is backed with, the bound `wr-waves` and\n * `wr-dot-grid` keep. On a 2x screen a box up to 625,000 CSS px² (1000 × 625)\n * keeps the full 2x; past that the canvas drops to 1x outright rather than to a\n * fraction between, because a fractional bitmap is resampled onto the screen and\n * draws neighbouring hairlines at visibly different weights (see `pixelRatio()`).\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The smallest cell the grid is laid out at. Under 12px the lines read as a\n * fill rather than a grid, and a full-page host would be hundreds of lines for\n * every ring to bend.\n */\nconst MIN_CELL = 12;\n\n/** The thinnest line drawn, in px. A canvas ignores a zero or negative `lineWidth` and keeps the last one. */\nconst MIN_LINE_WIDTH = 0.25;\n\n/** A 60 Hz frame, in ms: what the first frame of a run advances by, with no frame before it to measure from. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, in ms, so a tab that wakes after a pause does not jump the rings. */\nconst MAX_FRAME_MS = 50;\n\n/** A ring's half-width as a share of the cell, so a fine grid and a coarse one show the same ripple. */\nconst RING_SHARE = 0.75;\n\n/** Bounds on that half-width, in px. */\nconst MIN_RING = 16;\nconst MAX_RING = 60;\n\n/** How far either side of its crest a ring still bends anything, in half-widths: past 3 the bell is under 0.02%. */\nconst RING_REACH = 3;\n\n/** The shortest distance between the points a bent line is drawn through, in px. */\nconst MIN_SAMPLE = 6;\n\n/** Distance the pointer travels between two ripples it leaves behind, in cells, and never under 32px. */\nconst MOVE_SPACING = 2;\nconst MIN_MOVE_SPACING = 32;\n\n/** Strength of a ripple a moving pointer leaves, and of an idle ripple, as a share of a press. */\nconst MOVE_SHARE = 0.4;\nconst IDLE_SHARE = 0.6;\n\n/** The shortest idle interval, in ms: shorter would stack rings at the centre faster than they spread. */\nconst MIN_IDLE_MS = 500;\n\n/**\n * The most rings alive at once. A new one past it replaces the faintest live\n * ring, the oldest of the faintest on a tie, and is dropped itself when every\n * live ring is still stronger than it starts out — a trail ring among presses.\n */\nconst MAX_RIPPLES = 16;\n\n/**\n * The longest a ring lives, in ms, fading out over the last `FADE_MS` of it.\n * Only `speed` 0 with `decay` 0 ever reaches it — a ring that neither travels\n * nor fades would otherwise keep the loop running for good.\n */\nconst MAX_AGE_MS = 6000;\nconst FADE_MS = 1000;\n\n/**\n * The bend at a ring's crest, as a share of the ring's half-width, that the\n * rings reaching any one point may add up to: what keeps the grid from folding.\n *\n * A line folds back over itself where the field's slope reaches 1. One ring's\n * slope is at most 2.3 × its crest bend ÷ its half-width — measured over every\n * radius, the steepest point being where the ease into the origin meets the\n * crest's flank — and the slopes of overlapping rings add. Held to 0.39 of a\n * half-width, the summed slope stays under 0.9 wherever the rings overlap, so a\n * line keeps its course and its neighbours' order however hard it is pressed:\n * 11.7px at the default cell. Bounding the summed BEND instead, point by point,\n * folds anyway, and kinks the lines where the sum crosses the bound.\n */\nconst PUSH_PER_WIDTH = 0.39;\n\n/**\n * How long a ring takes to reach its full bend, in ms. Born at full strength it\n * would take its share of the overlap budget in one frame and shrink every ring\n * it overlaps by a step; ramped in, they give way smoothly. At the default speed\n * the crest is still inside its first half-width by then, where the ease keeps\n * the bend small anyway.\n */\nconst BIRTH_MS = 100;\n\n/** Alpha steps the lit stretches are batched into, one path and one stroke each. */\nconst GLOW_LEVELS = 16;\n\n/** Below this a ring's glow rounds to no alpha step at all. */\nconst GLOW_REST = 1 / (2 * GLOW_LEVELS);\n\n/** Below this, in px, a ring's displacement counts as gone. */\nconst PUSH_REST = 0.1;\n\n/** What the lines fall back to when the colour cannot be read: `--wr-border-base` in light. */\nconst LINE_FALLBACK = 'rgb(209, 213, 219)';\n\n/** What the lit stretches fall back to: the shipped primary. */\nconst RIPPLE_FALLBACK = 'rgb(57, 105, 226)';\n\n/** The colours an unset, empty or blank input leaves to the hooks. */\nconst LINE_TOKEN = 'var(--wr-ripple-grid-line-color)';\nconst RIPPLE_TOKEN = 'var(--wr-ripple-grid-ripple-color)';\n\n/** An empty run list for one alpha step, with room for a few lit stretches before it first grows. */\nconst glowRuns = (): GlowRuns => ({ points: new Float64Array(256), length: 0, starts: new Uint32Array(32), runs: 0 });\n\n/** Open a new lit run at `(x0, y0)` – `(x1, y1)`, growing the buffers only when they are full. */\nfunction startRun(level: GlowRuns, x0: number, y0: number, x1: number, y1: number): void {\n if (level.runs === level.starts.length) {\n const starts = new Uint32Array(level.starts.length * 2);\n starts.set(level.starts);\n level.starts = starts;\n }\n level.starts[level.runs++] = level.length;\n addPoint(level, x0, y0);\n addPoint(level, x1, y1);\n}\n\n/** Carry the open run on to `(x, y)`. */\nfunction addPoint(level: GlowRuns, x: number, y: number): void {\n if (level.length + 2 > level.points.length) {\n const points = new Float64Array(level.points.length * 2);\n points.set(level.points);\n level.points = points;\n }\n level.points[level.length++] = x;\n level.points[level.length++] = y;\n}\n\n/**\n * A line position moved so its stroke's edges fall on bitmap pixel boundaries:\n * `stroke` bitmap pixels wide at `ratio` bitmap pixels per CSS pixel. A 1px line\n * left on a pixel boundary covers two pixels at half alpha — at 1x every line of\n * the grid did — and at a fractional ratio neighbouring lines land on different\n * fractions and draw at visibly different weights. A stroke under one bitmap\n * pixel goes on a pixel's centre: Skia draws it as a one-pixel hairline at\n * reduced alpha, which only lands inside a single pixel from there.\n */\nfunction snap(position: number, ratio: number, stroke: number): number {\n const width = Math.max(1, stroke);\n return (Math.round(position * ratio - width / 2) + width / 2) / ratio;\n}\n\n/**\n * Ripple-grid background — thin grid lines on a 2D canvas that the pointer\n * sends ripples through. A press sends out a ring that travels outward, bends\n * the lines it passes away from where it started and lights them in the ripple\n * colour, and fades as it goes; with `rippleOnMove` a moving pointer leaves a\n * trail of softer rings behind it. Fills its nearest positioned ancestor; put\n * it behind hero copy or section content.\n *\n * With nothing pressed or moved the grid is still, and asks for no frames at\n * all. `idleInterval` sends a softer ring out from the centre every so many\n * milliseconds on its own; it is off by default, so a page that wants a grid\n * which moves before anyone touches it asks for one. Each ring lives until it\n * has faded or left the box, and never longer than six seconds.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * grid answers a pointer that is over the content in front of it 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 * only an idle ring ever moves it. Content placed after it needs\n * `position: relative` (or any z-index) to paint above it, as with any\n * 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 a ring is alive, and never\n * while the host is off screen; an idle ring comes due only while frames run,\n * so a hidden tab does not queue them up.\n *\n * Under `prefers-reduced-motion` one still grid is drawn, in the line colour,\n * with no ripples, no idle rings and no pointer listeners; it is drawn again\n * when an input, the box or the page's colour scheme changes. The preference\n * is read live, and switching it on drops the rings already travelling.\n *\n * Colours are theme tokens by default — the lines `--wr-border-base`, the lit\n * stretches `--wr-color-primary` — through the `--wr-ripple-grid-line-color`\n * and `--wr-ripple-grid-ripple-color` hooks, and resolved before they reach\n * the canvas, which reads neither a `var()` nor a `light-dark()`.\n *\n * Until the canvas has drawn — the seconds before hydration on a prerendered\n * page — the stylesheet paints the same grid, at the same cell, width and\n * colour, and `.wr-ripple-grid--painted` retires it.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-ripple-grid />\n * <h1 style=\"position: relative\">Hero copy above the grid</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/ripple-grid\n */\n@Component({\n selector: 'wr-ripple-grid',\n template: '<canvas #canvas class=\"wr-ripple-grid__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './ripple-grid.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-ripple-grid',\n // Feed the CSS stand-in grid (see `styles/_rules.scss`) so it draws at the\n // real cell, width and colour, and switch it off once the canvas has painted.\n '[style.--wr-ripple-grid-cell-size.px]': 'cell()',\n '[style.--wr-ripple-grid-line-width.px]': 'stroke()',\n '[style.--wr-ripple-grid-input-line-color]': 'standInColor()',\n '[class.wr-ripple-grid--painted]': 'painted()',\n },\n})\nexport class WrRippleGrid {\n /** Distance between grid lines in px, never under 12. @default 40 */\n readonly cellSize = input(40, { transform: numAttr(40) });\n\n /** Line colour, any CSS colour. When unset, `--wr-ripple-grid-line-color` decides. */\n readonly lineColor = input<string | null>(null);\n\n /** Colour a line takes where a ripple passes, any CSS colour. When unset, `--wr-ripple-grid-ripple-color` decides. */\n readonly rippleColor = input<string | null>(null);\n\n /** Line width in px, never under 0.25. @default 1 */\n readonly lineWidth = input(1, { transform: numAttr(1) });\n\n /**\n * How far a fresh ripple from a press bends the lines at its crest, in px;\n * a ripple the moving pointer leaves bends them 40% as far. `0` keeps the\n * lines straight and only lights them, and a negative value bends them\n * inward. A bend steep enough to fold a line back over itself is never\n * drawn: one ring, or several where they overlap, bends a line at most about\n * 0.3 of a cell for cells from 22 to 80px — 11.7px at the default 40 — and\n * rings that overlap share that bound rather than adding past it. @default 10\n */\n readonly strength = input(10, { transform: numAttr(10) });\n\n /** How fast a ripple travels outward, in px per second; `0` keeps it where it started. @default 320 */\n readonly speed = input(320, { transform: numAttr(320) });\n\n /**\n * How fast a ripple fades, per second: its strength is multiplied by\n * `e^-decay` every second, so `1.4` halves it in half a second. `0` keeps it\n * at full strength until it leaves the box. @default 1.4\n */\n readonly decay = input(1.4, { transform: numAttr(1.4) });\n\n /** Whether a moving pointer leaves ripples behind it, and not only a press. @default true */\n readonly rippleOnMove = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Milliseconds between the softer ripples sent out from the centre on their\n * own, never under 500; `0` sends none, and the grid then stays still until\n * the pointer moves it. @default 0\n */\n readonly idleInterval = input(0, { transform: numAttr(0) });\n\n /** The cell the grid is laid out at: `cellSize`, floored. */\n protected readonly cell = computed(() => Math.max(MIN_CELL, this.cellSize()));\n\n /** The line width drawn: `lineWidth`, floored. */\n protected readonly stroke = computed(() => Math.max(MIN_LINE_WIDTH, this.lineWidth()));\n\n /**\n * A line colour the author chose, published so the stand-in draws in it too;\n * `null` while the input is unset. On a variable of its own rather than on the\n * hook, which an author sets from outside: a `null` binding wipes a value of its\n * own name from the host's `style` attribute.\n */\n protected readonly standInColor = computed(() => (this.lineColor() ?? '').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 lineExpression = computed(() => (this.lineColor() ?? '').trim() || LINE_TOKEN);\n private readonly rippleExpression = computed(() => (this.rippleColor() ?? '').trim() || RIPPLE_TOKEN);\n\n /** The idle interval in force, in ms; 0 when there is none. */\n private readonly idle = computed(() => {\n const interval = this.idleInterval();\n return interval > 0 ? Math.max(MIN_IDLE_MS, interval) : 0;\n });\n\n /** A ring's half-width, in px: a share of the cell, within bounds. */\n private readonly ringWidth = computed(() => clamp(this.cell() * RING_SHARE, MIN_RING, MAX_RING));\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 lines at the\n * same cell 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 readonly ripples: Ripple[] = [];\n private readonly pending: Pending[] = [];\n /** The live rings as this frame draws them, reused frame to frame. */\n private readonly rings: Ring[] = [];\n private ringCount = 0;\n /** The rings that reach the line being traced, reused line to line. */\n private readonly near: Ring[] = [];\n /** Lit stretches by alpha step, reused frame to frame. */\n private readonly lit: GlowRuns[] = Array.from({ length: GLOW_LEVELS }, glowRuns);\n private anyLit = false;\n /** Where the last trail ripple was left, in viewport coordinates, while the pointer is over the parent. */\n private readonly anchor = { x: 0, y: 0, set: false };\n /** The ring half-width and sample step this frame draws with, read once rather than per sample. */\n private frameRing = MIN_RING;\n private frameStep = MIN_SAMPLE;\n /** One sample, displaced: written by `displace()`, read straight after. */\n private sx = 0;\n private sy = 0;\n private sg = 0;\n private rafId = 0;\n private lastTime = -1;\n /** The frame callback, made once rather than a closure per frame. */\n private readonly frame = (time: number): void => this.tick(time);\n /** Whether the idle timer has fired and the next frame owes the centre a ring. */\n private idleDue = false;\n /** Schedules a frame while motion is allowed; `null` under reduced motion. */\n private wake: (() => void) | null = null;\n /** Arms the next idle ring while motion is allowed; `null` under reduced motion. */\n private armIdle: (() => void) | null = null;\n\n /** Flips once `boot()` has a context and a box — 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 // Everything that changes the picture only changes what the next frame\n // draws. A sleeping loop or a still grid has no next frame, so ask for one.\n // `speed` and `decay` are left out: with no ring alive they change nothing\n // on screen, and with one alive the loop is already running.\n effect(() => {\n this.cell();\n this.stroke();\n this.lineColor();\n this.rippleColor();\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. Toggling `rippleOnMove` or the idle\n // interval restarts the listeners and the timer; rings already travelling\n // carry on.\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 const trail = this.rippleOnMove();\n const idle = this.idle();\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact(trail, idle))));\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\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, keep the idle timer and draw on demand; returns what stops all three. */\n private interact(trail: boolean, interval: number): () => 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, keeps no timer and takes no\n // pointer. Without an observer (jsdom) it counts as visible.\n let visible = typeof IntersectionObserver === 'undefined';\n let idleTimer = 0;\n const wake = (): void => {\n if (visible && this.rafId === 0) this.rafId = requestAnimationFrame(this.frame);\n };\n // Re-armed from the frame that places the ring rather than from the timer\n // itself, so a background tab — no frames, timers still firing — owes the\n // centre one ring at most, not one per interval it was away.\n const armIdle = (): void => {\n if (interval <= 0 || !visible || idleTimer !== 0 || this.idleDue) return;\n idleTimer = window.setTimeout(() => {\n idleTimer = 0;\n this.idleDue = true;\n wake();\n }, interval);\n };\n const halt = (): void => {\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.lastTime = -1;\n window.clearTimeout(idleTimer);\n idleTimer = 0;\n };\n\n const onDown = (event: PointerEvent): void => {\n if (!visible) return;\n this.emit(event.clientX, event.clientY, 1);\n wake();\n };\n // The first move only marks where the pointer came in: a ring on arrival\n // would ripple the grid every time the pointer crossed into the section.\n const onMove = (event: PointerEvent): void => {\n if (!visible) return;\n const anchor = this.anchor;\n const spacing = Math.max(MIN_MOVE_SPACING, this.cell() * MOVE_SPACING);\n if (anchor.set && Math.hypot(event.clientX - anchor.x, event.clientY - anchor.y) < spacing) return;\n if (anchor.set) {\n this.emit(event.clientX, event.clientY, MOVE_SHARE);\n wake();\n } else {\n anchor.x = event.clientX;\n anchor.y = event.clientY;\n anchor.set = true;\n }\n };\n const onLeave = (): void => {\n this.anchor.set = false;\n };\n target?.addEventListener('pointerdown', onDown, { passive: true });\n if (trail) {\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n }\n\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => {\n visible = entries[entries.length - 1].isIntersecting;\n if (visible) {\n wake();\n armIdle();\n } else {\n halt();\n }\n });\n io?.observe(hostEl);\n\n this.wake = wake;\n this.armIdle = armIdle;\n wake();\n armIdle();\n\n return () => {\n target?.removeEventListener('pointerdown', onDown);\n if (trail) {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n }\n io?.disconnect();\n halt();\n this.wake = null;\n this.armIdle = null;\n this.idleDue = false;\n this.anchor.set = false;\n };\n }\n\n /** Queue a ring at a viewport point for the next frame, and mark the point for the trail. */\n private emit(clientX: number, clientY: number, share: number): void {\n this.pending.push({ x: clientX, y: clientY, share, client: true });\n this.anchor.x = clientX;\n this.anchor.y = clientY;\n this.anchor.set = true;\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 if (!this.fit(width, height)) return;\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. The\n // rings keep their place in the box; a running loop simply carries on.\n this.draw();\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 const ratio = this.pixelRatio(width * height);\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 /**\n * Bitmap pixels per CSS pixel for a box of `area` CSS px². The screen's own\n * ratio when it fits under 2 and the pixel budget, so a bitmap pixel is a\n * device pixel and a snapped line lands on whole device pixels; otherwise the\n * largest whole fraction of it that fits — a half, a third — so each bitmap\n * pixel still covers a whole number of device pixels and every line keeps one\n * weight. A ratio between (1.31 on a 2x screen) is resampled onto the screen\n * by a fraction, which no snapping survives. Never below 1 on a 1x screen or\n * better: a 1.5x screen past the budget takes 1 and accepts the resampling,\n * rather than a bitmap coarser than the CSS box.\n */\n private pixelRatio(area: number): number {\n const device = window.devicePixelRatio || 1;\n const limit = Math.min(2, area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2);\n let ratio = device;\n for (let k = 2; ratio > limit; k++) ratio = device / k;\n return ratio < 1 ? Math.min(1, device) : ratio;\n }\n\n /** No ring alive, none queued, none owed. */\n private rest(): void {\n this.ripples.length = 0;\n this.pending.length = 0;\n this.idleDue = false;\n this.anchor.set = false;\n }\n\n // Frame loop\n\n private tick(time: number): void {\n this.rafId = 0;\n // A ring is a function of its age, so stepping it by the time that really\n // passed is what keeps a 120 Hz screen from running it twice as fast. A\n // timestamp that did not advance counts as one 60 Hz frame, and so does\n // the first frame of a run, which has nothing to measure from.\n const elapsed = time - this.lastTime;\n const step = this.lastTime < 0 || elapsed <= 0 ? FRAME_MS : Math.min(MAX_FRAME_MS, elapsed);\n this.lastTime = time;\n\n this.advance(step);\n this.draw();\n\n // No ring left: sleep until the pointer, the idle timer, a resize or a new\n // input asks for another frame. The frame that retired the last ring has\n // just drawn the grid straight.\n if (this.ripples.length > 0 && this.wake) this.rafId = requestAnimationFrame(this.frame);\n else this.lastTime = -1;\n }\n\n /** Age every ring by `step` ms, place the queued ones, and retire the ones with nothing left to show. */\n private advance(step: number): void {\n for (const ripple of this.ripples) ripple.age += step;\n\n if (this.idleDue) {\n this.idleDue = false;\n this.pending.push({ x: this.box.width / 2, y: this.box.height / 2, share: IDLE_SHARE, client: false });\n this.armIdle?.();\n }\n if (this.pending.length > 0) {\n // The rect is the box as drawn, after any transform on it or an ancestor,\n // and the grid is laid out in the box before them, so a pointer is scaled\n // back into it. Read once a frame, not once an event.\n let needsRect = false;\n for (let i = 0; i < this.pending.length && !needsRect; i++) needsRect = this.pending[i].client;\n const rect = needsRect ? this.host.nativeElement.getBoundingClientRect() : null;\n const sx = rect && rect.width > 0 ? this.box.width / rect.width : 1;\n const sy = rect && rect.height > 0 ? this.box.height / rect.height : 1;\n for (const entry of this.pending) {\n const x = entry.client && rect ? (entry.x - rect.left) * sx : entry.x;\n const y = entry.client && rect ? (entry.y - rect.top) * sy : entry.y;\n this.place(x, y, entry.share);\n }\n this.pending.length = 0;\n }\n\n const speed = Math.max(0, this.speed());\n const strength = Math.abs(this.strength());\n const reach = this.ringWidth() * RING_REACH;\n const { width, height } = this.box;\n for (let i = this.ripples.length - 1; i >= 0; i--) {\n const ripple = this.ripples[i];\n const energy = this.energy(ripple);\n // Past its farthest corner (plus the margin a bend can reach) the ring\n // has nothing left in the box to bend.\n const far = Math.hypot(Math.max(ripple.x, width - ripple.x), Math.max(ripple.y, height - ripple.y));\n const gone = (speed * ripple.age) / 1000 - reach > far + this.margin();\n // A ring at the end of its life has faded to nothing, so this retires it too.\n if (gone || (energy < GLOW_REST && energy * strength < PUSH_REST)) {\n this.ripples.splice(i, 1);\n }\n }\n }\n\n /**\n * Start a ring at a box point. Past `MAX_RIPPLES` it replaces the faintest live\n * ring — the oldest of the faintest on a tie — so a press in the middle of a\n * fast drag is not the one that vanishes; when every live ring is still\n * stronger than this one starts out, this one is the faintest, and it is dropped.\n */\n private place(x: number, y: number, share: number): void {\n const ripples = this.ripples;\n if (ripples.length < MAX_RIPPLES) {\n ripples.push({ x, y, age: 0, share });\n return;\n }\n let faintest = 0;\n let least = Infinity;\n for (let i = 0; i < ripples.length; i++) {\n const energy = this.energy(ripples[i]);\n if (energy < least || (energy === least && ripples[i].age > ripples[faintest].age)) {\n faintest = i;\n least = energy;\n }\n }\n if (share < least) return;\n ripples[faintest] = { x, y, age: 0, share };\n }\n\n /** How strong a ring still is, 0..1: its share, faded by `decay` and by the end of its life. */\n private energy(ripple: Ripple): number {\n const decay = Math.max(0, this.decay());\n const end = clamp((MAX_AGE_MS - ripple.age) / FADE_MS, 0, 1);\n return ripple.share * Math.exp((-decay * ripple.age) / 1000) * end;\n }\n\n /**\n * How far past each edge the lines run, so a bend never pulls a line's end into\n * view: a cell, plus the most the rings at one point can bend it — the overlap\n * budget, or every ring at full strength when that is less.\n */\n private margin(): number {\n return this.cell() + Math.min(Math.abs(this.strength()) * MAX_RIPPLES, PUSH_PER_WIDTH * this.ringWidth());\n }\n\n // Drawing\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 { width, height } = this.box;\n ctx.clearRect(0, 0, width, height);\n this.prepareRings();\n for (let i = 0; i < GLOW_LEVELS; i++) {\n this.lit[i].length = 0;\n this.lit[i].runs = 0;\n }\n this.anyLit = false;\n\n // The stroke in whole bitmap pixels, and every line moved so its edges fall\n // on them (see `snap()`). Under one bitmap pixel the width is kept, so a\n // thin line stays faint, and lands centred in a single pixel.\n const ratio = this.ratio || 1;\n const scaled = this.stroke() * ratio;\n const stroke = scaled >= 1 ? Math.round(scaled) : scaled;\n const cell = this.cell();\n const margin = this.margin();\n ctx.globalAlpha = 1;\n ctx.lineWidth = stroke / ratio;\n ctx.lineCap = 'butt';\n ctx.lineJoin = 'round';\n ctx.strokeStyle = resolveCssColor(canvas, this.lineExpression(), LINE_FALLBACK);\n ctx.beginPath();\n // A cell centred on the box, then every `cell` out, far enough past each\n // edge that a bend never brings an undrawn line into view — where the\n // stand-in's centred tiles put them, give or take the half device pixel\n // the snap moves a line by.\n const cx = width / 2;\n const cy = height / 2;\n const columns = Math.ceil((cx + margin) / cell);\n const rows = Math.ceil((cy + margin) / cell);\n for (let i = -columns; i < columns; i++) {\n this.trace(ctx, snap(cx + (i + 0.5) * cell, ratio, stroke), -margin, height + margin, true);\n }\n for (let i = -rows; i < rows; i++) {\n this.trace(ctx, snap(cy + (i + 0.5) * cell, ratio, stroke), -margin, width + margin, false);\n }\n ctx.stroke();\n if (!this.anyLit) return;\n\n // Lit stretches go over their own lines in the ripple colour, at their glow\n // as alpha: the blend of the two colours without parsing either, so an\n // `oklch()` or a `display-p3` colour works as well as an `rgb()`.\n ctx.strokeStyle = resolveCssColor(canvas, this.rippleExpression(), RIPPLE_FALLBACK);\n for (let i = 0; i < GLOW_LEVELS; i++) {\n const level = this.lit[i];\n if (level.runs === 0) continue;\n const points = level.points;\n ctx.globalAlpha = (i + 1) / GLOW_LEVELS;\n ctx.beginPath();\n for (let run = 0; run < level.runs; run++) {\n const start = level.starts[run];\n const end = run + 1 < level.runs ? level.starts[run + 1] : level.length;\n ctx.moveTo(points[start], points[start + 1]);\n for (let p = start + 2; p < end; p += 2) ctx.lineTo(points[p], points[p + 1]);\n }\n ctx.stroke();\n }\n ctx.globalAlpha = 1;\n }\n\n /**\n * Work out where every live ring stands at its current age, for this frame's\n * samples, and hold the rings that overlap to the bend budget together.\n *\n * Each ring's bend is scaled by its share of the budget against every ring\n * whose band meets its own: wherever two rings both reach a point their bands\n * meet, so the rings acting on any point add up to the budget at most (see\n * `PUSH_PER_WIDTH`), while a ring that meets no other keeps its full bend. A\n * pair counts in full once its bands meet and fades out over one half-width of\n * gap before that, so two rings drifting into each other give way smoothly\n * rather than both shrinking in one frame.\n */\n private prepareRings(): void {\n const speed = Math.max(0, this.speed());\n const strength = this.strength();\n const width = this.ringWidth();\n this.frameRing = width;\n this.frameStep = Math.max(MIN_SAMPLE, width / 4);\n const reach = width * RING_REACH;\n const rings = this.rings;\n this.ringCount = 0;\n for (const ripple of this.ripples) {\n const energy = this.energy(ripple);\n const radius = (speed * ripple.age) / 1000;\n const inner = Math.max(0, radius - reach);\n const outer = radius + reach;\n const ring = (rings[this.ringCount] ??= {\n x: 0,\n y: 0,\n radius: 0,\n inner: 0,\n outer: 0,\n inner2: 0,\n outer2: 0,\n raw: 0,\n push: 0,\n glow: 0,\n });\n ring.x = ripple.x;\n ring.y = ripple.y;\n ring.radius = radius;\n ring.inner = inner;\n ring.outer = outer;\n ring.inner2 = inner * inner;\n ring.outer2 = outer * outer;\n ring.raw = strength * energy * Math.min(1, ripple.age / BIRTH_MS);\n ring.glow = energy;\n this.ringCount++;\n }\n\n const budget = PUSH_PER_WIDTH * width;\n for (let i = 0; i < this.ringCount; i++) {\n const ring = rings[i];\n let shared = 0;\n for (let j = 0; j < this.ringCount; j++) {\n const other = rings[j];\n const between = Math.hypot(ring.x - other.x, ring.y - other.y);\n const gap = Math.max(\n between - ring.outer - other.outer,\n ring.inner - other.outer - between,\n other.inner - ring.outer - between\n );\n if (gap < width) shared += Math.abs(other.raw) * (gap <= 0 ? 1 : 1 - gap / width);\n }\n ring.push = shared > budget ? (ring.raw * budget) / shared : ring.raw;\n }\n }\n\n /**\n * Add one grid line to the path — vertical at `x = fixed` or horizontal at\n * `y = fixed`, from `from` to `to` along it — and file its lit stretches by\n * alpha step. A line no ring reaches is two points; one a ring reaches is\n * sampled only across the stretch the rings can bend, and straight either\n * side of it. Where that stretch runs to an end of the line, the end is the\n * displaced sample itself: drawing the undisplaced end before it would step\n * the line back along itself.\n */\n private trace(ctx: CanvasRenderingContext2D, fixed: number, from: number, to: number, vertical: boolean): void {\n const near = this.near;\n let count = 0;\n // The stretch of the line any ring's band can reach.\n let start = to;\n let end = from;\n for (let i = 0; i < this.ringCount; i++) {\n const ring = this.rings[i];\n const across = Math.abs(fixed - (vertical ? ring.x : ring.y));\n if (across >= ring.outer) continue;\n const along = vertical ? ring.y : ring.x;\n // Every point of the line already inside the band's inner edge: the ring\n // has passed it.\n if (ring.inner >= Math.hypot(across, Math.max(along - from, to - along))) continue;\n const half = Math.sqrt(ring.outer2 - across * across);\n start = Math.min(start, along - half);\n end = Math.max(end, along + half);\n near[count++] = ring;\n }\n\n if (count === 0 || start >= end) {\n if (vertical) {\n ctx.moveTo(fixed, from);\n ctx.lineTo(fixed, to);\n } else {\n ctx.moveTo(from, fixed);\n ctx.lineTo(to, fixed);\n }\n return;\n }\n\n // Samples on a lattice fixed to the line rather than to the band, so a\n // travelling ring does not slide the points it bends through.\n const step = this.frameStep;\n const first = Math.max(from, from + Math.floor((start - from) / step) * step);\n const last = Math.min(to, from + Math.ceil((end - from) / step) * step);\n if (first > from) {\n if (vertical) ctx.moveTo(fixed, from);\n else ctx.moveTo(from, fixed);\n }\n\n // The alpha step of the lit run being extended, 0 while none is open.\n let runLevel = 0;\n let px = 0;\n let py = 0;\n let pg = 0;\n for (let t = first, s = 0; ; s++, t = Math.min(last, first + s * step)) {\n if (vertical) this.displace(fixed, t, count);\n else this.displace(t, fixed, count);\n const x = this.sx;\n const y = this.sy;\n const g = this.sg;\n if (s === 0 && first <= from) ctx.moveTo(x, y);\n else ctx.lineTo(x, y);\n if (s > 0) {\n const level = Math.min(GLOW_LEVELS, Math.round(((pg + g) / 2) * GLOW_LEVELS));\n if (level === 0) {\n runLevel = 0;\n } else if (runLevel === level) {\n addPoint(this.lit[level - 1], x, y);\n } else {\n startRun(this.lit[level - 1], px, py, x, y);\n runLevel = level;\n this.anyLit = true;\n }\n }\n px = x;\n py = y;\n pg = g;\n if (t >= last) break;\n }\n if (last < to) {\n if (vertical) ctx.lineTo(fixed, to);\n else ctx.lineTo(to, fixed);\n }\n }\n\n /**\n * Where the grid point `(x, y)` is drawn and how lit it is, with the first\n * `count` rings in `near` acting on it; written to `sx`, `sy` and `sg`.\n *\n * Each ring pushes the point along the line from its origin. The push is a\n * bell around the crest, shaped by a cosine so the crest bends outward and a\n * shallow trough either side bends back in — what reads as a ripple rather\n * than a bulge — and eased in over the first half-width, so the lines through\n * the origin itself do not tear. The glow is the bell alone. Overlapping rings\n * add up; `prepareRings()` has already held them to a budget that cannot fold\n * the grid, so nothing is clamped here.\n */\n private displace(x: number, y: number, count: number): void {\n const width = this.frameRing;\n let dx = 0;\n let dy = 0;\n let glow = 0;\n for (let i = 0; i < count; i++) {\n const ring = this.near[i];\n const ox = x - ring.x;\n const oy = y - ring.y;\n const d2 = ox * ox + oy * oy;\n if (d2 <= ring.inner2 || d2 >= ring.outer2) continue;\n const d = Math.sqrt(d2);\n const u = (d - ring.radius) / width;\n const bell = Math.exp(-u * u);\n const ease = d < width ? d / width : 1;\n const amount = (ring.push * bell * Math.cos(2 * u) * ease) / d;\n dx += ox * amount;\n dy += oy * amount;\n glow += ring.glow * bell * ease;\n }\n this.sx = x + dx;\n this.sy = y + dy;\n this.sg = Math.min(1, glow);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-bits-ripple-grid.mjs","names":[],"sources":["../../../projects/lib/bits/ripple-grid/ripple-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 Ripple Grid background on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/ripple-grid). The idea is the same;\n * the code is ngwr's own: the original is a WebGL shader, this is a 2D canvas\n * of grid lines bent by rings that travel out from the pointer, and a frame\n * loop that sleeps whenever no ring is alive.\n *\n * A 2D canvas rather than WebGL, deliberately. The picture is a few dozen\n * hairlines, which a 2D context antialiases on its own; the bend is evaluated\n * only along the stretch of each line a ring can reach, so a still grid is\n * two points per line; it boots with no shader to compile, and it still draws\n * on the machines that blocklist WebGL2.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\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 { clamp, numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** One ring, in the host's layout box. */\ninterface Ripple {\n readonly x: number;\n readonly y: number;\n /** Milliseconds since it left its origin. */\n age: number;\n /** Its strength as a share of `strength`: a press is 1, a move or an idle ripple less. */\n readonly share: number;\n}\n\n/** A ring asked for since the last frame; the next frame places it. */\ninterface Pending {\n readonly x: number;\n readonly y: number;\n readonly share: number;\n /** Whether `x` and `y` are viewport coordinates (a pointer) rather than the box's own (the idle ripple). */\n readonly client: boolean;\n}\n\n/**\n * One alpha step's lit stretches, as polylines packed end to end into buffers that\n * keep their capacity frame to frame: `points` holds `[x, y, x, y, …]` with `length`\n * entries in use, `starts` the index in `points` where each run begins, `runs` in use.\n */\ninterface GlowRuns {\n points: Float64Array;\n length: number;\n starts: Uint32Array;\n runs: number;\n}\n\n/** What a frame needs to know about a live ring, worked out once per frame rather than once per sample. */\ninterface Ring {\n x: number;\n y: number;\n /** Distance the crest has travelled, in px. */\n radius: number;\n /** The band the ring reaches, from `inner` to `outer` px from its origin, and both squared. */\n inner: number;\n outer: number;\n inner2: number;\n outer2: number;\n /** Displacement at the crest before the overlap budget, in px; negative pulls inward. */\n raw: number;\n /** Displacement at the crest as drawn, within the overlap budget, in px. */\n push: number;\n /** Glow at the crest, 0..1. */\n glow: number;\n}\n\n/**\n * The most bitmap pixels the canvas is backed with, the bound `wr-waves` and\n * `wr-dot-grid` keep. On a 2x screen a box up to 625,000 CSS px² (1000 × 625)\n * keeps the full 2x; past that the canvas drops to 1x outright rather than to a\n * fraction between, because a fractional bitmap is resampled onto the screen and\n * draws neighbouring hairlines at visibly different weights (see `pixelRatio()`).\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The smallest cell the grid is laid out at. Under 12px the lines read as a\n * fill rather than a grid, and a full-page host would be hundreds of lines for\n * every ring to bend.\n */\nconst MIN_CELL = 12;\n\n/** The thinnest line drawn, in px. A canvas ignores a zero or negative `lineWidth` and keeps the last one. */\nconst MIN_LINE_WIDTH = 0.25;\n\n/** A 60 Hz frame, in ms: what the first frame of a run advances by, with no frame before it to measure from. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, in ms, so a tab that wakes after a pause does not jump the rings. */\nconst MAX_FRAME_MS = 50;\n\n/** A ring's half-width as a share of the cell, so a fine grid and a coarse one show the same ripple. */\nconst RING_SHARE = 0.75;\n\n/** Bounds on that half-width, in px. */\nconst MIN_RING = 16;\nconst MAX_RING = 60;\n\n/** How far either side of its crest a ring still bends anything, in half-widths: past 3 the bell is under 0.02%. */\nconst RING_REACH = 3;\n\n/** The shortest distance between the points a bent line is drawn through, in px. */\nconst MIN_SAMPLE = 6;\n\n/** Distance the pointer travels between two ripples it leaves behind, in cells, and never under 32px. */\nconst MOVE_SPACING = 2;\nconst MIN_MOVE_SPACING = 32;\n\n/** Strength of a ripple a moving pointer leaves, and of an idle ripple, as a share of a press. */\nconst MOVE_SHARE = 0.4;\nconst IDLE_SHARE = 0.6;\n\n/** The shortest idle interval, in ms: shorter would stack rings at the centre faster than they spread. */\nconst MIN_IDLE_MS = 500;\n\n/**\n * The most rings alive at once. A new one past it replaces the faintest live\n * ring, the oldest of the faintest on a tie, and is dropped itself when every\n * live ring is still stronger than it starts out — a trail ring among presses.\n */\nconst MAX_RIPPLES = 16;\n\n/**\n * The longest a ring lives, in ms, fading out over the last `FADE_MS` of it.\n * Only `speed` 0 with `decay` 0 ever reaches it — a ring that neither travels\n * nor fades would otherwise keep the loop running for good.\n */\nconst MAX_AGE_MS = 6000;\nconst FADE_MS = 1000;\n\n/**\n * The bend at a ring's crest, as a share of the ring's half-width, that the\n * rings reaching any one point may add up to: what keeps the grid from folding.\n *\n * A line folds back over itself where the field's slope reaches 1. One ring's\n * slope is at most 2.3 × its crest bend ÷ its half-width — measured over every\n * radius, the steepest point being where the ease into the origin meets the\n * crest's flank — and the slopes of overlapping rings add. Held to 0.39 of a\n * half-width, the summed slope stays under 0.9 wherever the rings overlap, so a\n * line keeps its course and its neighbours' order however hard it is pressed:\n * 11.7px at the default cell. Bounding the summed BEND instead, point by point,\n * folds anyway, and kinks the lines where the sum crosses the bound.\n */\nconst PUSH_PER_WIDTH = 0.39;\n\n/**\n * How long a ring takes to reach its full bend, in ms. Born at full strength it\n * would take its share of the overlap budget in one frame and shrink every ring\n * it overlaps by a step; ramped in, they give way smoothly. At the default speed\n * the crest is still inside its first half-width by then, where the ease keeps\n * the bend small anyway.\n */\nconst BIRTH_MS = 100;\n\n/** Alpha steps the lit stretches are batched into, one path and one stroke each. */\nconst GLOW_LEVELS = 16;\n\n/** Below this a ring's glow rounds to no alpha step at all. */\nconst GLOW_REST = 1 / (2 * GLOW_LEVELS);\n\n/** Below this, in px, a ring's displacement counts as gone. */\nconst PUSH_REST = 0.1;\n\n/** What the lines fall back to when the colour cannot be read: `--wr-border-base` in light. */\nconst LINE_FALLBACK = 'rgb(209, 213, 219)';\n\n/** What the lit stretches fall back to: the shipped primary. */\nconst RIPPLE_FALLBACK = 'rgb(57, 105, 226)';\n\n/** The colours an unset, empty or blank input leaves to the hooks. */\nconst LINE_TOKEN = 'var(--wr-ripple-grid-line-color)';\nconst RIPPLE_TOKEN = 'var(--wr-ripple-grid-ripple-color)';\n\n/** An empty run list for one alpha step, with room for a few lit stretches before it first grows. */\nconst glowRuns = (): GlowRuns => ({ points: new Float64Array(256), length: 0, starts: new Uint32Array(32), runs: 0 });\n\n/** Open a new lit run at `(x0, y0)` – `(x1, y1)`, growing the buffers only when they are full. */\nfunction startRun(level: GlowRuns, x0: number, y0: number, x1: number, y1: number): void {\n if (level.runs === level.starts.length) {\n const starts = new Uint32Array(level.starts.length * 2);\n starts.set(level.starts);\n level.starts = starts;\n }\n level.starts[level.runs++] = level.length;\n addPoint(level, x0, y0);\n addPoint(level, x1, y1);\n}\n\n/** Carry the open run on to `(x, y)`. */\nfunction addPoint(level: GlowRuns, x: number, y: number): void {\n if (level.length + 2 > level.points.length) {\n const points = new Float64Array(level.points.length * 2);\n points.set(level.points);\n level.points = points;\n }\n level.points[level.length++] = x;\n level.points[level.length++] = y;\n}\n\n/**\n * A line position moved so its stroke's edges fall on bitmap pixel boundaries:\n * `stroke` bitmap pixels wide at `ratio` bitmap pixels per CSS pixel. A 1px line\n * left on a pixel boundary covers two pixels at half alpha — at 1x every line of\n * the grid did — and at a fractional ratio neighbouring lines land on different\n * fractions and draw at visibly different weights. A stroke under one bitmap\n * pixel goes on a pixel's centre: Skia draws it as a one-pixel hairline at\n * reduced alpha, which only lands inside a single pixel from there.\n */\nfunction snap(position: number, ratio: number, stroke: number): number {\n const width = Math.max(1, stroke);\n return (Math.round(position * ratio - width / 2) + width / 2) / ratio;\n}\n\n/**\n * Ripple-grid background — thin grid lines on a 2D canvas that the pointer\n * sends ripples through. A press sends out a ring that travels outward, bends\n * the lines it passes away from where it started and lights them in the ripple\n * colour, and fades as it goes; with `rippleOnMove` a moving pointer leaves a\n * trail of softer rings behind it. Fills its nearest positioned ancestor; put\n * it behind hero copy or section content.\n *\n * With nothing pressed or moved the grid is still, and asks for no frames at\n * all. `idleInterval` sends a softer ring out from the centre every so many\n * milliseconds on its own; it is off by default, so a page that wants a grid\n * which moves before anyone touches it asks for one. Each ring lives until it\n * has faded or left the box, and never longer than six seconds.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * grid answers a pointer that is over the content in front of it 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 * only an idle ring ever moves it. Content placed after it needs\n * `position: relative` (or any z-index) to paint above it, as with any\n * 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 a ring is alive, and never\n * while the host is off screen or has no box to draw on; an idle ring comes due\n * only while frames run, so a hidden tab does not queue them up.\n *\n * Under `prefers-reduced-motion` one still grid is drawn, in the line colour,\n * with no ripples, no idle rings and no pointer listeners; it is drawn again\n * when an input, the box or the page's colour scheme changes. The preference\n * is read live, and switching it on drops the rings already travelling.\n *\n * Colours are theme tokens by default — the lines `--wr-border-base`, the lit\n * stretches `--wr-color-primary` — through the `--wr-ripple-grid-line-color`\n * and `--wr-ripple-grid-ripple-color` hooks, and resolved before they reach\n * the canvas, which reads neither a `var()` nor a `light-dark()`.\n *\n * Until the canvas has drawn — the seconds before hydration on a prerendered\n * page — the stylesheet paints the same grid, at the same cell, width and\n * colour, and `.wr-ripple-grid--painted` retires it.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-ripple-grid />\n * <h1 style=\"position: relative\">Hero copy above the grid</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/ripple-grid\n */\n@Component({\n selector: 'wr-ripple-grid',\n template: '<canvas #canvas class=\"wr-ripple-grid__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './ripple-grid.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-ripple-grid',\n // Feed the CSS stand-in grid (see `styles/_rules.scss`) so it draws at the\n // real cell, width and colour, and switch it off once the canvas has painted.\n '[style.--wr-ripple-grid-cell-size.px]': 'cell()',\n '[style.--wr-ripple-grid-line-width.px]': 'stroke()',\n '[style.--wr-ripple-grid-input-line-color]': 'standInColor()',\n '[class.wr-ripple-grid--painted]': 'painted()',\n },\n})\nexport class WrRippleGrid {\n /** Distance between grid lines in px, never under 12. @default 40 */\n readonly cellSize = input(40, { transform: numAttr(40) });\n\n /** Line colour, any CSS colour. When unset, `--wr-ripple-grid-line-color` decides. */\n readonly lineColor = input<string | null>(null);\n\n /** Colour a line takes where a ripple passes, any CSS colour. When unset, `--wr-ripple-grid-ripple-color` decides. */\n readonly rippleColor = input<string | null>(null);\n\n /** Line width in px, never under 0.25. @default 1 */\n readonly lineWidth = input(1, { transform: numAttr(1) });\n\n /**\n * How far a fresh ripple from a press bends the lines at its crest, in px;\n * a ripple the moving pointer leaves bends them 40% as far. `0` keeps the\n * lines straight and only lights them, and a negative value bends them\n * inward. A bend steep enough to fold a line back over itself is never\n * drawn: one ring, or several where they overlap, bends a line at most about\n * 0.3 of a cell for cells from 22 to 80px — 11.7px at the default 40 — and\n * rings that overlap share that bound rather than adding past it. @default 10\n */\n readonly strength = input(10, { transform: numAttr(10) });\n\n /** How fast a ripple travels outward, in px per second; `0` keeps it where it started. @default 320 */\n readonly speed = input(320, { transform: numAttr(320) });\n\n /**\n * How fast a ripple fades, per second: its strength is multiplied by\n * `e^-decay` every second, so `1.4` halves it in half a second. `0` keeps it\n * at full strength until it leaves the box. @default 1.4\n */\n readonly decay = input(1.4, { transform: numAttr(1.4) });\n\n /** Whether a moving pointer leaves ripples behind it, and not only a press. @default true */\n readonly rippleOnMove = input(true, { transform: coerceBooleanProperty });\n\n /**\n * Milliseconds between the softer ripples sent out from the centre on their\n * own, never under 500; `0` sends none, and the grid then stays still until\n * the pointer moves it. @default 0\n */\n readonly idleInterval = input(0, { transform: numAttr(0) });\n\n /** The cell the grid is laid out at: `cellSize`, floored. */\n protected readonly cell = computed(() => Math.max(MIN_CELL, this.cellSize()));\n\n /** The line width drawn: `lineWidth`, floored. */\n protected readonly stroke = computed(() => Math.max(MIN_LINE_WIDTH, this.lineWidth()));\n\n /**\n * A line colour the author chose, published so the stand-in draws in it too;\n * `null` while the input is unset. On a variable of its own rather than on the\n * hook, which an author sets from outside: a `null` binding wipes a value of its\n * own name from the host's `style` attribute.\n */\n protected readonly standInColor = computed(() => (this.lineColor() ?? '').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 lineExpression = computed(() => (this.lineColor() ?? '').trim() || LINE_TOKEN);\n private readonly rippleExpression = computed(() => (this.rippleColor() ?? '').trim() || RIPPLE_TOKEN);\n\n /** The idle interval in force, in ms; 0 when there is none. */\n private readonly idle = computed(() => {\n const interval = this.idleInterval();\n return interval > 0 ? Math.max(MIN_IDLE_MS, interval) : 0;\n });\n\n /** A ring's half-width, in px: a share of the cell, within bounds. */\n private readonly ringWidth = computed(() => clamp(this.cell() * RING_SHARE, MIN_RING, MAX_RING));\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 lines at the\n * same cell 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 readonly ripples: Ripple[] = [];\n private readonly pending: Pending[] = [];\n /** The live rings as this frame draws them, reused frame to frame. */\n private readonly rings: Ring[] = [];\n private ringCount = 0;\n /** The rings that reach the line being traced, reused line to line. */\n private readonly near: Ring[] = [];\n /** Lit stretches by alpha step, reused frame to frame. */\n private readonly lit: GlowRuns[] = Array.from({ length: GLOW_LEVELS }, glowRuns);\n private anyLit = false;\n /** Where the last trail ripple was left, in viewport coordinates, while the pointer is over the parent. */\n private readonly anchor = { x: 0, y: 0, set: false };\n /** The ring half-width and sample step this frame draws with, read once rather than per sample. */\n private frameRing = MIN_RING;\n private frameStep = MIN_SAMPLE;\n /** One sample, displaced: written by `displace()`, read straight after. */\n private sx = 0;\n private sy = 0;\n private sg = 0;\n private rafId = 0;\n private lastTime = -1;\n /** The frame callback, made once rather than a closure per frame. */\n private readonly frame = (time: number): void => this.tick(time);\n /** Whether the idle timer has fired and the next frame owes the centre a ring. */\n private idleDue = false;\n /** Schedules a frame while motion is allowed; `null` under reduced motion. */\n private wake: (() => void) | null = null;\n /** Arms the next idle ring while motion is allowed; `null` under reduced motion. */\n private armIdle: (() => void) | null = null;\n\n /** Flips once `boot()` has a context and a box — everything after waits for it. */\n private readonly booted = signal(false);\n\n /**\n * Whether the box has an area. The intersection observer calls a zero-area host\n * intersecting and sends no entry when it gains a size, so this reads the box instead.\n */\n private readonly hasBox = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\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, with no ring alive — and so it is the\n // moment motion stops. `speed` and `decay` are left out: with no ring alive\n // they change nothing on screen, and with one alive the loop is already\n // running. After render rather than an `effect`, which runs before a\n // directive's host binding: a still grid drawn then would miss a hook it\n // changes in the same pass.\n afterRenderEffect(() => {\n if (!this.booted()) return;\n const still = this.platform.prefersReducedMotion();\n this.cell();\n this.stroke();\n this.lineColor();\n this.rippleColor();\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. Toggling `rippleOnMove` or the idle interval\n // restarts the listeners and the timer; rings already travelling carry on.\n // With no box there is nothing to ripple, so neither runs: an idle ring kept\n // the loop and the timer going in a box with no area.\n effect(onCleanup => {\n if (!this.booted() || !this.hasBox() || this.platform.prefersReducedMotion()) return;\n const trail = this.rippleOnMove();\n const idle = this.idle();\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact(trail, idle))));\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\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, keep the idle timer and draw on demand; returns what stops all three. */\n private interact(trail: boolean, interval: number): () => 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, keeps no timer and takes no\n // pointer. Without an observer (jsdom) it counts as visible. 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 idleTimer = 0;\n const wake = (): void => {\n if (visible && this.rafId === 0) this.rafId = requestAnimationFrame(this.frame);\n };\n // Re-armed from the frame that places the ring rather than from the timer\n // itself, so a background tab — no frames, timers still firing — owes the\n // centre one ring at most, not one per interval it was away.\n const armIdle = (): void => {\n if (interval <= 0 || !visible || idleTimer !== 0 || this.idleDue) return;\n idleTimer = window.setTimeout(() => {\n idleTimer = 0;\n this.idleDue = true;\n wake();\n }, interval);\n };\n const halt = (): void => {\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.lastTime = -1;\n window.clearTimeout(idleTimer);\n idleTimer = 0;\n };\n\n const onDown = (event: PointerEvent): void => {\n if (!visible) return;\n this.emit(event.clientX, event.clientY, 1);\n wake();\n };\n // The first move only marks where the pointer came in: a ring on arrival\n // would ripple the grid every time the pointer crossed into the section.\n const onMove = (event: PointerEvent): void => {\n if (!visible) return;\n const anchor = this.anchor;\n const spacing = Math.max(MIN_MOVE_SPACING, this.cell() * MOVE_SPACING);\n if (anchor.set && Math.hypot(event.clientX - anchor.x, event.clientY - anchor.y) < spacing) return;\n if (anchor.set) {\n this.emit(event.clientX, event.clientY, MOVE_SHARE);\n wake();\n } else {\n anchor.x = event.clientX;\n anchor.y = event.clientY;\n anchor.set = true;\n }\n };\n const onLeave = (): void => {\n this.anchor.set = false;\n };\n target?.addEventListener('pointerdown', onDown, { passive: true });\n if (trail) {\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n }\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) {\n wake();\n armIdle();\n } else {\n halt();\n }\n },\n { threshold: [0, 1e-9] }\n );\n io?.observe(hostEl);\n\n this.wake = wake;\n this.armIdle = armIdle;\n wake();\n armIdle();\n\n return () => {\n target?.removeEventListener('pointerdown', onDown);\n if (trail) {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n }\n io?.disconnect();\n halt();\n this.wake = null;\n this.armIdle = null;\n this.idleDue = false;\n this.anchor.set = false;\n };\n }\n\n /** Queue a ring at a viewport point for the next frame, and mark the point for the trail. */\n private emit(clientX: number, clientY: number, share: number): void {\n this.pending.push({ x: clientX, y: clientY, share, client: true });\n this.anchor.x = clientX;\n this.anchor.y = clientY;\n this.anchor.set = true;\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 if (!this.fit(width, height)) return;\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. The\n // rings keep their place in the box; a running loop simply carries on.\n this.draw();\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 const ratio = this.pixelRatio(width * height);\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 this.hasBox.set(width > 0 && height > 0);\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 /**\n * Bitmap pixels per CSS pixel for a box of `area` CSS px². The screen's own\n * ratio when it fits under 2 and the pixel budget, so a bitmap pixel is a\n * device pixel and a snapped line lands on whole device pixels; otherwise the\n * largest whole fraction of it that fits — a half, a third — so each bitmap\n * pixel still covers a whole number of device pixels and every line keeps one\n * weight. A ratio between (1.31 on a 2x screen) is resampled onto the screen\n * by a fraction, which no snapping survives. Never below 1 on a 1x screen or\n * better: a 1.5x screen past the budget takes 1 and accepts the resampling,\n * rather than a bitmap coarser than the CSS box.\n */\n private pixelRatio(area: number): number {\n const device = window.devicePixelRatio || 1;\n const limit = Math.min(2, area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2);\n let ratio = device;\n for (let k = 2; ratio > limit; k++) ratio = device / k;\n return ratio < 1 ? Math.min(1, device) : ratio;\n }\n\n /** No ring alive, none queued, none owed. */\n private rest(): void {\n this.ripples.length = 0;\n this.pending.length = 0;\n this.idleDue = false;\n this.anchor.set = false;\n }\n\n // Frame loop\n\n private tick(time: number): void {\n this.rafId = 0;\n // A ring is a function of its age, so stepping it by the time that really\n // passed is what keeps a 120 Hz screen from running it twice as fast. A\n // timestamp that did not advance counts as one 60 Hz frame, and so does\n // the first frame of a run, which has nothing to measure from.\n const elapsed = time - this.lastTime;\n const step = this.lastTime < 0 || elapsed <= 0 ? FRAME_MS : Math.min(MAX_FRAME_MS, elapsed);\n this.lastTime = time;\n\n this.advance(step);\n this.draw();\n\n // No ring left: sleep until the pointer, the idle timer, a resize or a new\n // input asks for another frame. The frame that retired the last ring has\n // just drawn the grid straight.\n if (this.ripples.length > 0 && this.wake) this.rafId = requestAnimationFrame(this.frame);\n else this.lastTime = -1;\n }\n\n /** Age every ring by `step` ms, place the queued ones, and retire the ones with nothing left to show. */\n private advance(step: number): void {\n for (const ripple of this.ripples) ripple.age += step;\n\n if (this.idleDue) {\n this.idleDue = false;\n this.pending.push({ x: this.box.width / 2, y: this.box.height / 2, share: IDLE_SHARE, client: false });\n this.armIdle?.();\n }\n if (this.pending.length > 0) {\n // The rect is the box as drawn, after any transform on it or an ancestor,\n // and the grid is laid out in the box before them, so a pointer is scaled\n // back into it. Read once a frame, not once an event.\n let needsRect = false;\n for (let i = 0; i < this.pending.length && !needsRect; i++) needsRect = this.pending[i].client;\n const rect = needsRect ? this.host.nativeElement.getBoundingClientRect() : null;\n const sx = rect && rect.width > 0 ? this.box.width / rect.width : 1;\n const sy = rect && rect.height > 0 ? this.box.height / rect.height : 1;\n for (const entry of this.pending) {\n const x = entry.client && rect ? (entry.x - rect.left) * sx : entry.x;\n const y = entry.client && rect ? (entry.y - rect.top) * sy : entry.y;\n this.place(x, y, entry.share);\n }\n this.pending.length = 0;\n }\n\n const speed = Math.max(0, this.speed());\n const strength = Math.abs(this.strength());\n const reach = this.ringWidth() * RING_REACH;\n const { width, height } = this.box;\n for (let i = this.ripples.length - 1; i >= 0; i--) {\n const ripple = this.ripples[i];\n const energy = this.energy(ripple);\n // Past its farthest corner (plus the margin a bend can reach) the ring\n // has nothing left in the box to bend.\n const far = Math.hypot(Math.max(ripple.x, width - ripple.x), Math.max(ripple.y, height - ripple.y));\n const gone = (speed * ripple.age) / 1000 - reach > far + this.margin();\n // A ring at the end of its life has faded to nothing, so this retires it too.\n if (gone || (energy < GLOW_REST && energy * strength < PUSH_REST)) {\n this.ripples.splice(i, 1);\n }\n }\n }\n\n /**\n * Start a ring at a box point. Past `MAX_RIPPLES` it replaces the faintest live\n * ring — the oldest of the faintest on a tie — so a press in the middle of a\n * fast drag is not the one that vanishes; when every live ring is still\n * stronger than this one starts out, this one is the faintest, and it is dropped.\n */\n private place(x: number, y: number, share: number): void {\n const ripples = this.ripples;\n if (ripples.length < MAX_RIPPLES) {\n ripples.push({ x, y, age: 0, share });\n return;\n }\n let faintest = 0;\n let least = Infinity;\n for (let i = 0; i < ripples.length; i++) {\n const energy = this.energy(ripples[i]);\n if (energy < least || (energy === least && ripples[i].age > ripples[faintest].age)) {\n faintest = i;\n least = energy;\n }\n }\n if (share < least) return;\n ripples[faintest] = { x, y, age: 0, share };\n }\n\n /** How strong a ring still is, 0..1: its share, faded by `decay` and by the end of its life. */\n private energy(ripple: Ripple): number {\n const decay = Math.max(0, this.decay());\n const end = clamp((MAX_AGE_MS - ripple.age) / FADE_MS, 0, 1);\n return ripple.share * Math.exp((-decay * ripple.age) / 1000) * end;\n }\n\n /**\n * How far past each edge the lines run, so a bend never pulls a line's end into\n * view: a cell, plus the most the rings at one point can bend it — the overlap\n * budget, or every ring at full strength when that is less.\n */\n private margin(): number {\n return this.cell() + Math.min(Math.abs(this.strength()) * MAX_RIPPLES, PUSH_PER_WIDTH * this.ringWidth());\n }\n\n // Drawing\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 { width, height } = this.box;\n ctx.clearRect(0, 0, width, height);\n this.prepareRings();\n for (let i = 0; i < GLOW_LEVELS; i++) {\n this.lit[i].length = 0;\n this.lit[i].runs = 0;\n }\n this.anyLit = false;\n\n // The stroke in whole bitmap pixels, and every line moved so its edges fall\n // on them (see `snap()`). Under one bitmap pixel the width is kept, so a\n // thin line stays faint, and lands centred in a single pixel.\n const ratio = this.ratio || 1;\n const scaled = this.stroke() * ratio;\n const stroke = scaled >= 1 ? Math.round(scaled) : scaled;\n const cell = this.cell();\n const margin = this.margin();\n ctx.globalAlpha = 1;\n ctx.lineWidth = stroke / ratio;\n ctx.lineCap = 'butt';\n ctx.lineJoin = 'round';\n ctx.strokeStyle = resolveCssColor(canvas, this.lineExpression(), LINE_FALLBACK);\n ctx.beginPath();\n // A cell centred on the box, then every `cell` out, far enough past each\n // edge that a bend never brings an undrawn line into view — where the\n // stand-in's centred tiles put them, give or take the half device pixel\n // the snap moves a line by.\n const cx = width / 2;\n const cy = height / 2;\n const columns = Math.ceil((cx + margin) / cell);\n const rows = Math.ceil((cy + margin) / cell);\n for (let i = -columns; i < columns; i++) {\n this.trace(ctx, snap(cx + (i + 0.5) * cell, ratio, stroke), -margin, height + margin, true);\n }\n for (let i = -rows; i < rows; i++) {\n this.trace(ctx, snap(cy + (i + 0.5) * cell, ratio, stroke), -margin, width + margin, false);\n }\n ctx.stroke();\n if (!this.anyLit) return;\n\n // Lit stretches go over their own lines in the ripple colour, at their glow\n // as alpha: the blend of the two colours without parsing either, so an\n // `oklch()` or a `display-p3` colour works as well as an `rgb()`.\n ctx.strokeStyle = resolveCssColor(canvas, this.rippleExpression(), RIPPLE_FALLBACK);\n for (let i = 0; i < GLOW_LEVELS; i++) {\n const level = this.lit[i];\n if (level.runs === 0) continue;\n const points = level.points;\n ctx.globalAlpha = (i + 1) / GLOW_LEVELS;\n ctx.beginPath();\n for (let run = 0; run < level.runs; run++) {\n const start = level.starts[run];\n const end = run + 1 < level.runs ? level.starts[run + 1] : level.length;\n ctx.moveTo(points[start], points[start + 1]);\n for (let p = start + 2; p < end; p += 2) ctx.lineTo(points[p], points[p + 1]);\n }\n ctx.stroke();\n }\n ctx.globalAlpha = 1;\n }\n\n /**\n * Work out where every live ring stands at its current age, for this frame's\n * samples, and hold the rings that overlap to the bend budget together.\n *\n * Each ring's bend is scaled by its share of the budget against every ring\n * whose band meets its own: wherever two rings both reach a point their bands\n * meet, so the rings acting on any point add up to the budget at most (see\n * `PUSH_PER_WIDTH`), while a ring that meets no other keeps its full bend. A\n * pair counts in full once its bands meet and fades out over one half-width of\n * gap before that, so two rings drifting into each other give way smoothly\n * rather than both shrinking in one frame.\n */\n private prepareRings(): void {\n const speed = Math.max(0, this.speed());\n const strength = this.strength();\n const width = this.ringWidth();\n this.frameRing = width;\n this.frameStep = Math.max(MIN_SAMPLE, width / 4);\n const reach = width * RING_REACH;\n const rings = this.rings;\n this.ringCount = 0;\n for (const ripple of this.ripples) {\n const energy = this.energy(ripple);\n const radius = (speed * ripple.age) / 1000;\n const inner = Math.max(0, radius - reach);\n const outer = radius + reach;\n const ring = (rings[this.ringCount] ??= {\n x: 0,\n y: 0,\n radius: 0,\n inner: 0,\n outer: 0,\n inner2: 0,\n outer2: 0,\n raw: 0,\n push: 0,\n glow: 0,\n });\n ring.x = ripple.x;\n ring.y = ripple.y;\n ring.radius = radius;\n ring.inner = inner;\n ring.outer = outer;\n ring.inner2 = inner * inner;\n ring.outer2 = outer * outer;\n ring.raw = strength * energy * Math.min(1, ripple.age / BIRTH_MS);\n ring.glow = energy;\n this.ringCount++;\n }\n\n const budget = PUSH_PER_WIDTH * width;\n for (let i = 0; i < this.ringCount; i++) {\n const ring = rings[i];\n let shared = 0;\n for (let j = 0; j < this.ringCount; j++) {\n const other = rings[j];\n const between = Math.hypot(ring.x - other.x, ring.y - other.y);\n const gap = Math.max(\n between - ring.outer - other.outer,\n ring.inner - other.outer - between,\n other.inner - ring.outer - between\n );\n if (gap < width) shared += Math.abs(other.raw) * (gap <= 0 ? 1 : 1 - gap / width);\n }\n ring.push = shared > budget ? (ring.raw * budget) / shared : ring.raw;\n }\n }\n\n /**\n * Add one grid line to the path — vertical at `x = fixed` or horizontal at\n * `y = fixed`, from `from` to `to` along it — and file its lit stretches by\n * alpha step. A line no ring reaches is two points; one a ring reaches is\n * sampled only across the stretch the rings can bend, and straight either\n * side of it. Where that stretch runs to an end of the line, the end is the\n * displaced sample itself: drawing the undisplaced end before it would step\n * the line back along itself.\n */\n private trace(ctx: CanvasRenderingContext2D, fixed: number, from: number, to: number, vertical: boolean): void {\n const near = this.near;\n let count = 0;\n // The stretch of the line any ring's band can reach.\n let start = to;\n let end = from;\n for (let i = 0; i < this.ringCount; i++) {\n const ring = this.rings[i];\n const across = Math.abs(fixed - (vertical ? ring.x : ring.y));\n if (across >= ring.outer) continue;\n const along = vertical ? ring.y : ring.x;\n // Every point of the line already inside the band's inner edge: the ring\n // has passed it.\n if (ring.inner >= Math.hypot(across, Math.max(along - from, to - along))) continue;\n const half = Math.sqrt(ring.outer2 - across * across);\n start = Math.min(start, along - half);\n end = Math.max(end, along + half);\n near[count++] = ring;\n }\n\n if (count === 0 || start >= end) {\n if (vertical) {\n ctx.moveTo(fixed, from);\n ctx.lineTo(fixed, to);\n } else {\n ctx.moveTo(from, fixed);\n ctx.lineTo(to, fixed);\n }\n return;\n }\n\n // Samples on a lattice fixed to the line rather than to the band, so a\n // travelling ring does not slide the points it bends through.\n const step = this.frameStep;\n const first = Math.max(from, from + Math.floor((start - from) / step) * step);\n const last = Math.min(to, from + Math.ceil((end - from) / step) * step);\n if (first > from) {\n if (vertical) ctx.moveTo(fixed, from);\n else ctx.moveTo(from, fixed);\n }\n\n // The alpha step of the lit run being extended, 0 while none is open.\n let runLevel = 0;\n let px = 0;\n let py = 0;\n let pg = 0;\n for (let t = first, s = 0; ; s++, t = Math.min(last, first + s * step)) {\n if (vertical) this.displace(fixed, t, count);\n else this.displace(t, fixed, count);\n const x = this.sx;\n const y = this.sy;\n const g = this.sg;\n if (s === 0 && first <= from) ctx.moveTo(x, y);\n else ctx.lineTo(x, y);\n if (s > 0) {\n const level = Math.min(GLOW_LEVELS, Math.round(((pg + g) / 2) * GLOW_LEVELS));\n if (level === 0) {\n runLevel = 0;\n } else if (runLevel === level) {\n addPoint(this.lit[level - 1], x, y);\n } else {\n startRun(this.lit[level - 1], px, py, x, y);\n runLevel = level;\n this.anyLit = true;\n }\n }\n px = x;\n py = y;\n pg = g;\n if (t >= last) break;\n }\n if (last < to) {\n if (vertical) ctx.lineTo(fixed, to);\n else ctx.lineTo(to, fixed);\n }\n }\n\n /**\n * Where the grid point `(x, y)` is drawn and how lit it is, with the first\n * `count` rings in `near` acting on it; written to `sx`, `sy` and `sg`.\n *\n * Each ring pushes the point along the line from its origin. The push is a\n * bell around the crest, shaped by a cosine so the crest bends outward and a\n * shallow trough either side bends back in — what reads as a ripple rather\n * than a bulge — and eased in over the first half-width, so the lines through\n * the origin itself do not tear. The glow is the bell alone. Overlapping rings\n * add up; `prepareRings()` has already held them to a budget that cannot fold\n * the grid, so nothing is clamped here.\n */\n private displace(x: number, y: number, count: number): void {\n const width = this.frameRing;\n let dx = 0;\n let dy = 0;\n let glow = 0;\n for (let i = 0; i < count; i++) {\n const ring = this.near[i];\n const ox = x - ring.x;\n const oy = y - ring.y;\n const d2 = ox * ox + oy * oy;\n if (d2 <= ring.inner2 || d2 >= ring.outer2) continue;\n const d = Math.sqrt(d2);\n const u = (d - ring.radius) / width;\n const bell = Math.exp(-u * u);\n const ease = d < width ? d / width : 1;\n const amount = (ring.push * bell * Math.cos(2 * u) * ease) / d;\n dx += ox * amount;\n dy += oy * amount;\n glow += ring.glow * bell * ease;\n }\n this.sx = x + dx;\n this.sy = y + dy;\n this.sg = Math.min(1, glow);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAmGA,MAAM,oBAAoB;AAO1B,MAAM,WAAW;AAGjB,MAAM,iBAAiB;AAGvB,MAAM,WAAW,MAAO;AAGxB,MAAM,eAAe;AAGrB,MAAM,aAAa;AAGnB,MAAM,WAAW;AACjB,MAAM,WAAW;AAGjB,MAAM,aAAa;AAGnB,MAAM,aAAa;AAGnB,MAAM,eAAe;AACrB,MAAM,mBAAmB;AAGzB,MAAM,aAAa;AACnB,MAAM,aAAa;AAGnB,MAAM,cAAc;AAOpB,MAAM,cAAc;AAOpB,MAAM,aAAa;AACnB,MAAM,UAAU;AAehB,MAAM,iBAAiB;AASvB,MAAM,WAAW;AAGjB,MAAM,cAAc;AAGpB,MAAM,YAAY,IAAK;AAGvB,MAAM,YAAY;AAGlB,MAAM,gBAAgB;AAGtB,MAAM,kBAAkB;AAGxB,MAAM,aAAa;AACnB,MAAM,eAAe;AAGrB,MAAM,kBAA4B;CAAE,wBAAQ,IAAI,aAAa,GAAG;CAAG,QAAQ;CAAG,wBAAQ,IAAI,YAAY,EAAE;CAAG,MAAM;AAAC;AAGlH,SAAS,SAAS,OAAiB,IAAY,IAAY,IAAY,IAAU;CAC/E,IAAI,MAAM,SAAS,MAAM,OAAO,QAAQ;EACtC,MAAM,SAAS,IAAI,YAAY,MAAM,OAAO,SAAS,CAAC;EACtD,OAAO,IAAI,MAAM,MAAM;EACvB,MAAM,SAAS;CACjB;CACA,MAAM,OAAO,MAAM,UAAU,MAAM;CACnC,SAAS,OAAO,IAAI,EAAE;CACtB,SAAS,OAAO,IAAI,EAAE;AACxB;AAGA,SAAS,SAAS,OAAiB,GAAW,GAAS;CACrD,IAAI,MAAM,SAAS,IAAI,MAAM,OAAO,QAAQ;EAC1C,MAAM,SAAS,IAAI,aAAa,MAAM,OAAO,SAAS,CAAC;EACvD,OAAO,IAAI,MAAM,MAAM;EACvB,MAAM,SAAS;CACjB;CACA,MAAM,OAAO,MAAM,YAAY;CAC/B,MAAM,OAAO,MAAM,YAAY;AACjC;AAWA,SAAS,KAAK,UAAkB,OAAe,QAAc;CAC3D,MAAM,QAAQ,KAAK,IAAI,GAAG,MAAM;CAChC,QAAQ,KAAK,MAAM,WAAW,QAAQ,QAAQ,CAAC,IAAI,QAAQ,KAAK;AAClE;AAyEA,IAAa,eAAb,MAAa,aAAY;CAEd,WAAW,MAAM,IAAE;EAAA,GAAA,YAAA,EAAA,WAAA,WAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,EAAE;CAAC,CAAA;CAG7C,YAAY,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGtC,cAAc,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAGxC,YAAY,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,YAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,CAAC;CAAC,CAAA;CAW5C,WAAW,MAAM,IAAE;EAAA,GAAA,YAAA,EAAA,WAAA,WAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,EAAE;CAAC,CAAA;CAG7C,QAAQ,MAAM,KAAG;EAAA,GAAA,YAAA,EAAA,WAAA,QAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,GAAG;CAAC,CAAA;CAO5C,QAAQ,MAAM,KAAG;EAAA,GAAA,YAAA,EAAA,WAAA,QAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,GAAG;CAAC,CAAA;CAG5C,eAAe,MAAM,MAAI;EAAA,GAAA,YAAA,EAAA,WAAA,eAAA,+BAAA,CAAA;EAAI,WAAW;CAAqB,CAAA;CAO7D,eAAe,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,eAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,CAAC;CAAC,CAAA;CAGrC,OAAO,eAAe,KAAK,IAAI,UAAU,KAAK,SAAQ,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAG1D,SAAS,eAAe,KAAK,IAAI,gBAAgB,KAAK,UAAS,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAQnE,eAAe,gBAAgB,KAAK,UAAS,KAAM,GAAA,CAAI,KAAI,KAAM,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAGxE,iBAAiB,gBAAgB,KAAK,UAAS,KAAM,GAAA,CAAI,KAAI,KAAM,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAC9E,mBAAmB,gBAAgB,KAAK,YAAW,KAAM,GAAA,CAAI,KAAI,KAAM,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAGpF,OAAO,eAAc;EACpC,MAAM,WAAW,KAAK,aAAY;EAClC,OAAO,WAAW,IAAI,KAAK,IAAI,aAAa,QAAQ,IAAI;CAC1D,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAGe,YAAY,eAAe,MAAM,KAAK,KAAI,IAAK,YAAY,UAAU,QAAQ,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAS7E,UAAU,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEzB,YAAY,UAAU,SAAwC,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEvE,OAAO,OAAgC,UAAU;CACjD,OAAO,OAAO,MAAM;CACpB,aAAa,OAAO,UAAU;CAC9B,WAAW,OAAO,UAAU;CAC5B,YAAY,kBAAkB,OAAO,WAAW,CAAC;CAE1D,MAAuC;CAEvC,MAAM;EAAE,OAAO;EAAG,QAAQ;CAAC;CAE3B,QAAQ;CACC,UAAoB,CAAA;CACpB,UAAqB,CAAA;CAErB,QAAgB,CAAA;CACzB,YAAY;CAEH,OAAe,CAAA;CAEf,MAAkB,MAAM,KAAK,EAAE,QAAQ,YAAW,GAAI,QAAQ;CACvE,SAAS;CAEA,SAAS;EAAE,GAAG;EAAG,GAAG;EAAG,KAAK;CAAK;CAE1C,YAAY;CACZ,YAAY;CAEZ,KAAK;CACL,KAAK;CACL,KAAK;CACL,QAAQ;CACR,WAAW;CAEF,SAAS,SAAuB,KAAK,KAAK,IAAI;CAEvD,UAAU;CAEV,OAA4B;CAE5B,UAA+B;CAGtB,SAAS,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAMtB,SAAS,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAEvC,cAAA;EACE,IAAI,CAAC,KAAK,WAAW;EAErB,sBAAsB,KAAK,KAAK,wBAAwB,KAAK,KAAI,CAAE,CAAC;EAUpE,wBAAuB;GACrB,IAAI,CAAC,KAAK,OAAM,GAAI;GACpB,MAAM,QAAQ,KAAK,SAAS,qBAAoB;GAChD,KAAK,KAAI;GACT,KAAK,OAAM;GACX,KAAK,UAAS;GACd,KAAK,YAAW;GAChB,KAAK,SAAQ;GACb,gBAAe;IACb,IAAI,OAAO,KAAK,KAAI;IACpB,KAAK,YAAW;GAClB,CAAC;EACH,CAAC;EAID,uBAAuB,KAAK,YAAW,CAAE;EAOzC,QAAO,cAAY;GACjB,IAAI,CAAC,KAAK,OAAM,KAAM,CAAC,KAAK,OAAM,KAAM,KAAK,SAAS,qBAAoB,GAAI;GAC9E,MAAM,QAAQ,KAAK,aAAY;GAC/B,MAAM,OAAO,KAAK,KAAI;GACtB,UAAU,gBAAgB,KAAK,KAAK,wBAAwB,KAAK,SAAS,OAAO,IAAI,CAAC,CAAC,CAAC;EAC1F,CAAC;CACH;CAIQ,OAAI;EACV,MAAM,SAAS,KAAK,UAAS,CAAE,CAAC;EAChC,KAAK,MAAM,OAAO,WAAW,IAAI;EACjC,IAAI,CAAC,KAAK,KAAK;EAMf,MAAM,SAAS,KAAK,KAAK;EACzB,KAAK,IAAI,OAAO,aAAa,OAAO,YAAY;EAKhD,MAAM,iBAAuB,KAAK,OAAO,KAAK,IAAI,OAAO,KAAK,IAAI,MAAM;EACxE,OAAO,iBAAiB,UAAU,QAAQ;EAC1C,MAAM,KAAK,IAAI,gBAAe,YAAU;GACtC,MAAM,OAAO,QAAQ,QAAQ,SAAS,EAAE,EAAE;GAC1C,IAAI,MAAM,KAAK,OAAO,KAAK,OAAO,KAAK,MAAM;EAC/C,CAAC;EACD,GAAG,QAAQ,MAAM;EACjB,KAAK,WAAW,gBAAe;GAC7B,OAAO,oBAAoB,UAAU,QAAQ;GAC7C,GAAG,WAAU;EACf,CAAC;EACD,KAAK,OAAO,IAAI,IAAI;CACtB;CAGQ,SAAS,OAAgB,UAAgB;EAC/C,MAAM,SAAS,KAAK,KAAK;EAMzB,MAAM,OAAO,OAAO,YAAW;EAC/B,MAAM,SACJ,OAAO,kBAAkB,gBAAgB,cAAc,KAAK,gBAAgB,cAAc,KAAK,OAAO;EAOxG,IAAI,UAAU,OAAO,yBAAyB;EAC9C,IAAI,YAAY;EAChB,MAAM,aAAkB;GACtB,IAAI,WAAW,KAAK,UAAU,GAAG,KAAK,QAAQ,sBAAsB,KAAK,KAAK;EAChF;EAIA,MAAM,gBAAqB;GACzB,IAAI,YAAY,KAAK,CAAC,WAAW,cAAc,KAAK,KAAK,SAAS;GAClE,YAAY,OAAO,iBAAgB;IACjC,YAAY;IACZ,KAAK,UAAU;IACf,KAAI;GACN,GAAG,QAAQ;EACb;EACA,MAAM,aAAkB;GACtB,qBAAqB,KAAK,KAAK;GAC/B,KAAK,QAAQ;GACb,KAAK,WAAW;GAChB,OAAO,aAAa,SAAS;GAC7B,YAAY;EACd;EAEA,MAAM,UAAU,UAA6B;GAC3C,IAAI,CAAC,SAAS;GACd,KAAK,KAAK,MAAM,SAAS,MAAM,SAAS,CAAC;GACzC,KAAI;EACN;EAGA,MAAM,UAAU,UAA6B;GAC3C,IAAI,CAAC,SAAS;GACd,MAAM,SAAS,KAAK;GACpB,MAAM,UAAU,KAAK,IAAI,kBAAkB,KAAK,KAAI,IAAK,YAAY;GACrE,IAAI,OAAO,OAAO,KAAK,MAAM,MAAM,UAAU,OAAO,GAAG,MAAM,UAAU,OAAO,CAAC,IAAI,SAAS;GAC5F,IAAI,OAAO,KAAK;IACd,KAAK,KAAK,MAAM,SAAS,MAAM,SAAS,UAAU;IAClD,KAAI;GACN,OAAO;IACL,OAAO,IAAI,MAAM;IACjB,OAAO,IAAI,MAAM;IACjB,OAAO,MAAM;GACf;EACF;EACA,MAAM,gBAAqB;GACzB,KAAK,OAAO,MAAM;EACpB;EACA,QAAQ,iBAAiB,eAAe,QAAQ,EAAE,SAAS,KAAI,CAAE;EACjE,IAAI,OAAO;GACT,QAAQ,iBAAiB,eAAe,QAAQ,EAAE,SAAS,KAAI,CAAE;GACjE,QAAQ,iBAAiB,gBAAgB,SAAS,EAAE,SAAS,KAAI,CAAE;GACnE,QAAQ,iBAAiB,iBAAiB,SAAS,EAAE,SAAS,KAAI,CAAE;EACtE;EAEA,MAAM,KACJ,OAAO,yBAAyB,cAC5B,OACA,IAAI,sBACF,YAAU;GACR,MAAM,EAAE,gBAAgB,sBAAsB,QAAQ,QAAQ,SAAS;GACvE,UAAU,kBAAkB,oBAAoB;GAChD,IAAI,SAAS;IACX,KAAI;IACJ,QAAO;GACT,OACE,KAAI;EAER,GACA,EAAE,WAAW,CAAC,GAAG,IAAI,EAAC,CAAE;EAEhC,IAAI,QAAQ,MAAM;EAElB,KAAK,OAAO;EACZ,KAAK,UAAU;EACf,KAAI;EACJ,QAAO;EAEP,aAAY;GACV,QAAQ,oBAAoB,eAAe,MAAM;GACjD,IAAI,OAAO;IACT,QAAQ,oBAAoB,eAAe,MAAM;IACjD,QAAQ,oBAAoB,gBAAgB,OAAO;IACnD,QAAQ,oBAAoB,iBAAiB,OAAO;GACtD;GACA,IAAI,WAAU;GACd,KAAI;GACJ,KAAK,OAAO;GACZ,KAAK,UAAU;GACf,KAAK,UAAU;GACf,KAAK,OAAO,MAAM;EACpB;CACF;CAGQ,KAAK,SAAiB,SAAiB,OAAa;EAC1D,KAAK,QAAQ,KAAK;GAAE,GAAG;GAAS,GAAG;GAAS;GAAO,QAAQ;EAAI,CAAE;EACjE,KAAK,OAAO,IAAI;EAChB,KAAK,OAAO,IAAI;EAChB,KAAK,OAAO,MAAM;CACpB;CAGQ,cAAW;EACjB,IAAI,CAAC,KAAK,OAAM,GAAI;EACpB,IAAI,KAAK,MAAM,KAAK,KAAI;OACnB,IAAI,KAAK,SAAS,qBAAoB,GAAI,KAAK,KAAI;CAC1D;CAGQ,OAAO,OAAe,QAAc;EAC1C,IAAI,CAAC,KAAK,IAAI,OAAO,MAAM,GAAG;EAK9B,KAAK,KAAI;CACX;CAOQ,IAAI,OAAe,QAAc;EACvC,MAAM,QAAQ,KAAK,WAAW,QAAQ,MAAM;EAC5C,IAAI,UAAU,KAAK,IAAI,SAAS,WAAW,KAAK,IAAI,UAAU,UAAU,KAAK,OAAO,OAAO;EAE3F,KAAK,MAAM;GAAE;GAAO;EAAM;EAC1B,KAAK,QAAQ;EACb,KAAK,OAAO,IAAI,QAAQ,KAAK,SAAS,CAAC;EACvC,MAAM,SAAS,KAAK,UAAS,CAAE,CAAC;EAChC,OAAO,QAAQ,KAAK,MAAM,QAAQ,KAAK;EACvC,OAAO,SAAS,KAAK,MAAM,SAAS,KAAK;EAGzC,KAAK,KAAK,aAAa,OAAO,GAAG,GAAG,OAAO,GAAG,CAAC;EAC/C,OAAO;CACT;CAaQ,WAAW,MAAY;EAC7B,MAAM,SAAS,OAAO,oBAAoB;EAC1C,MAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,IAAI,KAAK,KAAK,oBAAoB,IAAI,IAAI,CAAC;EAC5E,IAAI,QAAQ;EACZ,KAAK,IAAI,IAAI,GAAG,QAAQ,OAAO,KAAK,QAAQ,SAAS;EACrD,OAAO,QAAQ,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI;CAC3C;CAGQ,OAAI;EACV,KAAK,QAAQ,SAAS;EACtB,KAAK,QAAQ,SAAS;EACtB,KAAK,UAAU;EACf,KAAK,OAAO,MAAM;CACpB;CAIQ,KAAK,MAAY;EACvB,KAAK,QAAQ;EAKb,MAAM,UAAU,OAAO,KAAK;EAC5B,MAAM,OAAO,KAAK,WAAW,KAAK,WAAW,IAAI,WAAW,KAAK,IAAI,cAAc,OAAO;EAC1F,KAAK,WAAW;EAEhB,KAAK,QAAQ,IAAI;EACjB,KAAK,KAAI;EAKT,IAAI,KAAK,QAAQ,SAAS,KAAK,KAAK,MAAM,KAAK,QAAQ,sBAAsB,KAAK,KAAK;OAClF,KAAK,WAAW;CACvB;CAGQ,QAAQ,MAAY;EAC1B,KAAK,MAAM,UAAU,KAAK,SAAS,OAAO,OAAO;EAEjD,IAAI,KAAK,SAAS;GAChB,KAAK,UAAU;GACf,KAAK,QAAQ,KAAK;IAAE,GAAG,KAAK,IAAI,QAAQ;IAAG,GAAG,KAAK,IAAI,SAAS;IAAG,OAAO;IAAY,QAAQ;GAAK,CAAE;GACrG,KAAK,UAAS;EAChB;EACA,IAAI,KAAK,QAAQ,SAAS,GAAG;GAI3B,IAAI,YAAY;GAChB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,UAAU,CAAC,WAAW,KAAK,YAAY,KAAK,QAAQ,EAAE,CAAC;GACxF,MAAM,OAAO,YAAY,KAAK,KAAK,cAAc,sBAAqB,IAAK;GAC3E,MAAM,KAAK,QAAQ,KAAK,QAAQ,IAAI,KAAK,IAAI,QAAQ,KAAK,QAAQ;GAClE,MAAM,KAAK,QAAQ,KAAK,SAAS,IAAI,KAAK,IAAI,SAAS,KAAK,SAAS;GACrE,KAAK,MAAM,SAAS,KAAK,SAAS;IAChC,MAAM,IAAI,MAAM,UAAU,QAAQ,MAAM,IAAI,KAAK,QAAQ,KAAK,MAAM;IACpE,MAAM,IAAI,MAAM,UAAU,QAAQ,MAAM,IAAI,KAAK,OAAO,KAAK,MAAM;IACnE,KAAK,MAAM,GAAG,GAAG,MAAM,KAAK;GAC9B;GACA,KAAK,QAAQ,SAAS;EACxB;EAEA,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAK,CAAE;EACtC,MAAM,WAAW,KAAK,IAAI,KAAK,SAAQ,CAAE;EACzC,MAAM,QAAQ,KAAK,UAAS,IAAK;EACjC,MAAM,EAAE,OAAO,WAAW,KAAK;EAC/B,KAAK,IAAI,IAAI,KAAK,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;GACjD,MAAM,SAAS,KAAK,QAAQ;GAC5B,MAAM,SAAS,KAAK,OAAO,MAAM;GAGjC,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,OAAO,GAAG,QAAQ,OAAO,CAAC,GAAG,KAAK,IAAI,OAAO,GAAG,SAAS,OAAO,CAAC,CAAC;GAGlG,IAFc,QAAQ,OAAO,MAAO,MAAO,QAAQ,MAAM,KAAK,OAAM,KAEvD,SAAS,aAAa,SAAS,WAAW,WACrD,KAAK,QAAQ,OAAO,GAAG,CAAC;EAE5B;CACF;CAQQ,MAAM,GAAW,GAAW,OAAa;EAC/C,MAAM,UAAU,KAAK;EACrB,IAAI,QAAQ,SAAS,aAAa;GAChC,QAAQ,KAAK;IAAE;IAAG;IAAG,KAAK;IAAG;GAAK,CAAE;GACpC;EACF;EACA,IAAI,WAAW;EACf,IAAI,QAAQ;EACZ,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;GACvC,MAAM,SAAS,KAAK,OAAO,QAAQ,EAAE;GACrC,IAAI,SAAS,SAAU,WAAW,SAAS,QAAQ,EAAE,CAAC,MAAM,QAAQ,SAAS,CAAC,KAAM;IAClF,WAAW;IACX,QAAQ;GACV;EACF;EACA,IAAI,QAAQ,OAAO;EACnB,QAAQ,YAAY;GAAE;GAAG;GAAG,KAAK;GAAG;EAAK;CAC3C;CAGQ,OAAO,QAAc;EAC3B,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAK,CAAE;EACtC,MAAM,MAAM,OAAO,aAAa,OAAO,OAAO,SAAS,GAAG,CAAC;EAC3D,OAAO,OAAO,QAAQ,KAAK,IAAK,CAAC,QAAQ,OAAO,MAAO,GAAI,IAAI;CACjE;CAOQ,SAAM;EACZ,OAAO,KAAK,KAAI,IAAK,KAAK,IAAI,KAAK,IAAI,KAAK,SAAQ,CAAE,IAAI,aAAa,iBAAiB,KAAK,UAAS,CAAE;CAC1G;CAIQ,OAAI;EACV,MAAM,MAAM,KAAK;EACjB,IAAI,CAAC,KAAK;EAGV,IAAI,CAAC,KAAK,QAAO,GAAI,KAAK,QAAQ,IAAI,IAAI;EAE1C,MAAM,SAAS,KAAK,UAAS,CAAE,CAAC;EAChC,MAAM,EAAE,OAAO,WAAW,KAAK;EAC/B,IAAI,UAAU,GAAG,GAAG,OAAO,MAAM;EACjC,KAAK,aAAY;EACjB,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAAK;GACpC,KAAK,IAAI,EAAE,CAAC,SAAS;GACrB,KAAK,IAAI,EAAE,CAAC,OAAO;EACrB;EACA,KAAK,SAAS;EAKd,MAAM,QAAQ,KAAK,SAAS;EAC5B,MAAM,SAAS,KAAK,OAAM,IAAK;EAC/B,MAAM,SAAS,UAAU,IAAI,KAAK,MAAM,MAAM,IAAI;EAClD,MAAM,OAAO,KAAK,KAAI;EACtB,MAAM,SAAS,KAAK,OAAM;EAC1B,IAAI,cAAc;EAClB,IAAI,YAAY,SAAS;EACzB,IAAI,UAAU;EACd,IAAI,WAAW;EACf,IAAI,cAAc,gBAAgB,QAAQ,KAAK,eAAc,GAAI,aAAa;EAC9E,IAAI,UAAS;EAKb,MAAM,KAAK,QAAQ;EACnB,MAAM,KAAK,SAAS;EACpB,MAAM,UAAU,KAAK,MAAM,KAAK,UAAU,IAAI;EAC9C,MAAM,OAAO,KAAK,MAAM,KAAK,UAAU,IAAI;EAC3C,KAAK,IAAI,IAAI,CAAC,SAAS,IAAI,SAAS,KAClC,KAAK,MAAM,KAAK,KAAK,MAAM,IAAI,MAAO,MAAM,OAAO,MAAM,GAAG,CAAC,QAAQ,SAAS,QAAQ,IAAI;EAE5F,KAAK,IAAI,IAAI,CAAC,MAAM,IAAI,MAAM,KAC5B,KAAK,MAAM,KAAK,KAAK,MAAM,IAAI,MAAO,MAAM,OAAO,MAAM,GAAG,CAAC,QAAQ,QAAQ,QAAQ,KAAK;EAE5F,IAAI,OAAM;EACV,IAAI,CAAC,KAAK,QAAQ;EAKlB,IAAI,cAAc,gBAAgB,QAAQ,KAAK,iBAAgB,GAAI,eAAe;EAClF,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAAK;GACpC,MAAM,QAAQ,KAAK,IAAI;GACvB,IAAI,MAAM,SAAS,GAAG;GACtB,MAAM,SAAS,MAAM;GACrB,IAAI,eAAe,IAAI,KAAK;GAC5B,IAAI,UAAS;GACb,KAAK,IAAI,MAAM,GAAG,MAAM,MAAM,MAAM,OAAO;IACzC,MAAM,QAAQ,MAAM,OAAO;IAC3B,MAAM,MAAM,MAAM,IAAI,MAAM,OAAO,MAAM,OAAO,MAAM,KAAK,MAAM;IACjE,IAAI,OAAO,OAAO,QAAQ,OAAO,QAAQ,EAAE;IAC3C,KAAK,IAAI,IAAI,QAAQ,GAAG,IAAI,KAAK,KAAK,GAAG,IAAI,OAAO,OAAO,IAAI,OAAO,IAAI,EAAE;GAC9E;GACA,IAAI,OAAM;EACZ;EACA,IAAI,cAAc;CACpB;CAcQ,eAAY;EAClB,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAK,CAAE;EACtC,MAAM,WAAW,KAAK,SAAQ;EAC9B,MAAM,QAAQ,KAAK,UAAS;EAC5B,KAAK,YAAY;EACjB,KAAK,YAAY,KAAK,IAAI,YAAY,QAAQ,CAAC;EAC/C,MAAM,QAAQ,QAAQ;EACtB,MAAM,QAAQ,KAAK;EACnB,KAAK,YAAY;EACjB,KAAK,MAAM,UAAU,KAAK,SAAS;GACjC,MAAM,SAAS,KAAK,OAAO,MAAM;GACjC,MAAM,SAAU,QAAQ,OAAO,MAAO;GACtC,MAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,KAAK;GACxC,MAAM,QAAQ,SAAS;GACvB,MAAM,OAAQ,MAAM,KAAK,eAAe;IACtC,GAAG;IACH,GAAG;IACH,QAAQ;IACR,OAAO;IACP,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,KAAK;IACL,MAAM;IACN,MAAM;GACP;GACD,KAAK,IAAI,OAAO;GAChB,KAAK,IAAI,OAAO;GAChB,KAAK,SAAS;GACd,KAAK,QAAQ;GACb,KAAK,QAAQ;GACb,KAAK,SAAS,QAAQ;GACtB,KAAK,SAAS,QAAQ;GACtB,KAAK,MAAM,WAAW,SAAS,KAAK,IAAI,GAAG,OAAO,MAAM,QAAQ;GAChE,KAAK,OAAO;GACZ,KAAK;EACP;EAEA,MAAM,SAAS,iBAAiB;EAChC,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,WAAW,KAAK;GACvC,MAAM,OAAO,MAAM;GACnB,IAAI,SAAS;GACb,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,WAAW,KAAK;IACvC,MAAM,QAAQ,MAAM;IACpB,MAAM,UAAU,KAAK,MAAM,KAAK,IAAI,MAAM,GAAG,KAAK,IAAI,MAAM,CAAC;IAC7D,MAAM,MAAM,KAAK,IACf,UAAU,KAAK,QAAQ,MAAM,OAC7B,KAAK,QAAQ,MAAM,QAAQ,SAC3B,MAAM,QAAQ,KAAK,QAAQ,OAAO;IAEpC,IAAI,MAAM,OAAO,UAAU,KAAK,IAAI,MAAM,GAAG,KAAK,OAAO,IAAI,IAAI,IAAI,MAAM;GAC7E;GACA,KAAK,OAAO,SAAS,SAAU,KAAK,MAAM,SAAU,SAAS,KAAK;EACpE;CACF;CAWQ,MAAM,KAA+B,OAAe,MAAc,IAAY,UAAiB;EACrG,MAAM,OAAO,KAAK;EAClB,IAAI,QAAQ;EAEZ,IAAI,QAAQ;EACZ,IAAI,MAAM;EACV,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,WAAW,KAAK;GACvC,MAAM,OAAO,KAAK,MAAM;GACxB,MAAM,SAAS,KAAK,IAAI,SAAS,WAAW,KAAK,IAAI,KAAK,EAAE;GAC5D,IAAI,UAAU,KAAK,OAAO;GAC1B,MAAM,QAAQ,WAAW,KAAK,IAAI,KAAK;GAGvC,IAAI,KAAK,SAAS,KAAK,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,KAAK,KAAK,CAAC,GAAG;GAC1E,MAAM,OAAO,KAAK,KAAK,KAAK,SAAS,SAAS,MAAM;GACpD,QAAQ,KAAK,IAAI,OAAO,QAAQ,IAAI;GACpC,MAAM,KAAK,IAAI,KAAK,QAAQ,IAAI;GAChC,KAAK,WAAW;EAClB;EAEA,IAAI,UAAU,KAAK,SAAS,KAAK;GAC/B,IAAI,UAAU;IACZ,IAAI,OAAO,OAAO,IAAI;IACtB,IAAI,OAAO,OAAO,EAAE;GACtB,OAAO;IACL,IAAI,OAAO,MAAM,KAAK;IACtB,IAAI,OAAO,IAAI,KAAK;GACtB;GACA;EACF;EAIA,MAAM,OAAO,KAAK;EAClB,MAAM,QAAQ,KAAK,IAAI,MAAM,OAAO,KAAK,OAAO,QAAQ,QAAQ,IAAI,IAAI,IAAI;EAC5E,MAAM,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,MAAM,MAAM,QAAQ,IAAI,IAAI,IAAI;EACtE,IAAI,QAAQ,MAAM;GAChB,IAAI,UAAU,IAAI,OAAO,OAAO,IAAI;QAC/B,IAAI,OAAO,MAAM,KAAK;EAC7B;EAGA,IAAI,WAAW;EACf,IAAI,KAAK;EACT,IAAI,KAAK;EACT,IAAI,KAAK;EACT,KAAK,IAAI,IAAI,OAAO,IAAI,IAAK,KAAK,IAAI,KAAK,IAAI,MAAM,QAAQ,IAAI,IAAI,GAAG;GACtE,IAAI,UAAU,KAAK,SAAS,OAAO,GAAG,KAAK;QACtC,KAAK,SAAS,GAAG,OAAO,KAAK;GAClC,MAAM,IAAI,KAAK;GACf,MAAM,IAAI,KAAK;GACf,MAAM,IAAI,KAAK;GACf,IAAI,MAAM,KAAK,SAAS,MAAM,IAAI,OAAO,GAAG,CAAC;QACxC,IAAI,OAAO,GAAG,CAAC;GACpB,IAAI,IAAI,GAAG;IACT,MAAM,QAAQ,KAAK,IAAI,aAAa,KAAK,OAAQ,KAAK,KAAK,IAAK,WAAW,CAAC;IAC5E,IAAI,UAAU,GACZ,WAAW;SACN,IAAI,aAAa,OACtB,SAAS,KAAK,IAAI,QAAQ,IAAI,GAAG,CAAC;SAC7B;KACL,SAAS,KAAK,IAAI,QAAQ,IAAI,IAAI,IAAI,GAAG,CAAC;KAC1C,WAAW;KACX,KAAK,SAAS;IAChB;GACF;GACA,KAAK;GACL,KAAK;GACL,KAAK;GACL,IAAI,KAAK,MAAM;EACjB;EACA,IAAI,OAAO,IAAI;GACb,IAAI,UAAU,IAAI,OAAO,OAAO,EAAE;QAC7B,IAAI,OAAO,IAAI,KAAK;EAC3B;CACF;CAcQ,SAAS,GAAW,GAAW,OAAa;EAClD,MAAM,QAAQ,KAAK;EACnB,IAAI,KAAK;EACT,IAAI,KAAK;EACT,IAAI,OAAO;EACX,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,MAAM,OAAO,KAAK,KAAK;GACvB,MAAM,KAAK,IAAI,KAAK;GACpB,MAAM,KAAK,IAAI,KAAK;GACpB,MAAM,KAAK,KAAK,KAAK,KAAK;GAC1B,IAAI,MAAM,KAAK,UAAU,MAAM,KAAK,QAAQ;GAC5C,MAAM,IAAI,KAAK,KAAK,EAAE;GACtB,MAAM,KAAK,IAAI,KAAK,UAAU;GAC9B,MAAM,OAAO,KAAK,IAAI,CAAC,IAAI,CAAC;GAC5B,MAAM,OAAO,IAAI,QAAQ,IAAI,QAAQ;GACrC,MAAM,SAAU,KAAK,OAAO,OAAO,KAAK,IAAI,IAAI,CAAC,IAAI,OAAQ;GAC7D,MAAM,KAAK;GACX,MAAM,KAAK;GACX,QAAQ,KAAK,OAAO,OAAO;EAC7B;EACA,KAAK,KAAK,IAAI;EACd,KAAK,KAAK,IAAI;EACd,KAAK,KAAK,KAAK,IAAI,GAAG,IAAI;CAC5B;CAtvBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAY,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAZ,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAY,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,UAAA;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,aAAA;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,UAAA;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,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,cAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,cAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA;GAAA,YAAA;IAAA,uCAAA;IAAA,wCAAA;IAAA,2CAAA;IAAA,iCAAA;GAAA;GAAA,gBAAA;EAAA;EAAA,aAAA,CAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,QAAA;GAAA,aAAA;GAAA,UAAA;EAAA,CAAA;EAAA,UAAA;EAAA,UAbb;EAA6E,UAAA;EAAA,QAAA,CAAA,2gCAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAa5E,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAY,YAAA,CAAA;EAfxB,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAgB,UAChB;GAA6E,eAExE,kBAAkB;GAAI,MAC/B;IACJ,OAAO;IAGP,yCAAyC;IACzC,0CAA0C;IAC1C,6CAA6C;IAC7C,mCAAmC;GACpC;GAAA,QAAA,CAAA,2gCAAA;EAAA,CAAA;CAkF8E,CAAA;CAAA,sBAAA,CAAA;CAAA,gBAAA;EAAA,UAAA,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,aAAA,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,UAAA,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,OAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,cAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,cAAA,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"}
|