ngwr 15.3.0 → 15.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/bits/border-glow/styles/_rules.scss +60 -49
  2. package/bits/click-spark/click-spark.scss +5 -1
  3. package/bits/click-spark/styles/_index.scss +10 -15
  4. package/bits/click-spark/styles/_rules.scss +20 -0
  5. package/fesm2022/ngwr-bits-aurora.mjs +108 -52
  6. package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
  7. package/fesm2022/ngwr-bits-border-glow-testing.mjs +8 -8
  8. package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
  9. package/fesm2022/ngwr-bits-border-glow.mjs +29 -26
  10. package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
  11. package/fesm2022/ngwr-bits-dot-grid.mjs +11 -14
  12. package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
  13. package/fesm2022/ngwr-bits-fuzzy-text.mjs +18 -4
  14. package/fesm2022/ngwr-bits-fuzzy-text.mjs.map +1 -1
  15. package/fesm2022/ngwr-bits-ripple-grid.mjs +14 -13
  16. package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
  17. package/fesm2022/ngwr-bits-splash-cursor.mjs +21 -10
  18. package/fesm2022/ngwr-bits-splash-cursor.mjs.map +1 -1
  19. package/fesm2022/ngwr-bits-waves.mjs +10 -15
  20. package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
  21. package/fesm2022/ngwr-pipes.mjs +162 -51
  22. package/fesm2022/ngwr-pipes.mjs.map +1 -1
  23. package/fesm2022/ngwr-statistic.mjs +11 -16
  24. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  25. package/fesm2022/ngwr-version.mjs +1 -1
  26. package/fesm2022/ngwr-version.mjs.map +1 -1
  27. package/llms-full.txt +2 -2
  28. package/package.json +1 -1
  29. package/schematics/use/symbol-map.json +1 -0
  30. package/skills/ngwr/SKILL.md +3 -1
  31. package/types/ngwr-bits-aurora.d.ts +24 -2
  32. package/types/ngwr-bits-border-glow-testing.d.ts +9 -5
  33. package/types/ngwr-bits-border-glow.d.ts +10 -4
  34. package/types/ngwr-bits-fuzzy-text.d.ts +2 -1
  35. package/types/ngwr-bits-ripple-grid.d.ts +7 -2
  36. package/types/ngwr-bits-splash-cursor.d.ts +2 -1
  37. package/types/ngwr-pipes.d.ts +142 -32
  38. package/types/ngwr-version.d.ts +1 -1
@@ -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":"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"}
@@ -29,6 +29,7 @@ function createFluidSimulation(canvas, config) {
29
29
  }
30
30
  let active = true;
31
31
  let rafId = 0;
32
+ let hasBox = false;
32
33
  const shaders = [];
33
34
  const programs = [];
34
35
  const buffers = [];
@@ -519,6 +520,7 @@ function createFluidSimulation(canvas, config) {
519
520
  if (!active || !visible) return;
520
521
  const dt = calcDeltaTime();
521
522
  if (resizeCanvas()) initFramebuffers();
523
+ if (!hasBox) return;
522
524
  updateColors(dt);
523
525
  applyInputs();
524
526
  step(dt);
@@ -534,12 +536,11 @@ function createFluidSimulation(canvas, config) {
534
536
  function resizeCanvas() {
535
537
  const width = scaleByPixelRatio(canvas.clientWidth);
536
538
  const height = scaleByPixelRatio(canvas.clientHeight);
537
- if (canvas.width !== width || canvas.height !== height) {
538
- canvas.width = width;
539
- canvas.height = height;
540
- return true;
541
- }
542
- return false;
539
+ hasBox = width > 0 && height > 0;
540
+ if (!hasBox || canvas.width === width && canvas.height === height) return false;
541
+ canvas.width = width;
542
+ canvas.height = height;
543
+ return true;
543
544
  }
544
545
  function updateColors(dt) {
545
546
  colorUpdateTimer += dt * config.colorUpdateSpeed;
@@ -797,19 +798,29 @@ function createFluidSimulation(canvas, config) {
797
798
  window.addEventListener("touchstart", onTouchStart, { passive: true });
798
799
  window.addEventListener("touchmove", onTouchMove, { passive: true });
799
800
  let visible = true;
800
- const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => {
801
- visible = entries[entries.length - 1].isIntersecting;
802
- if (visible && active && rafId === 0) {
801
+ const wake = () => {
802
+ if (visible && hasBox && active && rafId === 0) {
803
803
  lastUpdateTime = Date.now();
804
804
  rafId = requestAnimationFrame(updateFrame);
805
805
  }
806
- });
806
+ };
807
+ const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => {
808
+ const { isIntersecting, intersectionRatio } = entries[entries.length - 1];
809
+ visible = isIntersecting && intersectionRatio > 0;
810
+ wake();
811
+ }, { threshold: [0, 1e-9] });
807
812
  io?.observe(canvas);
813
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => {
814
+ hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;
815
+ wake();
816
+ });
817
+ ro?.observe(canvas);
808
818
  updateFrame();
809
819
  return () => {
810
820
  active = false;
811
821
  cancelAnimationFrame(rafId);
812
822
  io?.disconnect();
823
+ ro?.disconnect();
813
824
  window.removeEventListener("mousedown", onMouseDown);
814
825
  window.removeEventListener("mousemove", onMouseMove);
815
826
  window.removeEventListener("touchstart", onTouchStart);