@mkbabb/glass-ui 8.0.0 → 9.0.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 (142) hide show
  1. package/dist/aurora.js +371 -349
  2. package/dist/blob.js +42 -42
  3. package/dist/{checkbox-BnXuGgJ3.js → checkbox-B1qn65se.js} +1 -1
  4. package/dist/checkbox.js +1 -1
  5. package/dist/color.glsl-DTJhdekG.js +34 -0
  6. package/dist/color.wgsl-BTszLY39.js +398 -0
  7. package/dist/{command-BuBnYpxF.js → command-tEzAaVY_.js} +1 -1
  8. package/dist/command.js +1 -1
  9. package/dist/components/_shared/control.d.ts +0 -1
  10. package/dist/components/_shared/index.d.ts +0 -1
  11. package/dist/components/aurora/Aurora.vue.d.ts +1 -0
  12. package/dist/components/aurora/composables/frameLoop.d.ts +6 -5
  13. package/dist/components/aurora/composables/runtime.d.ts +26 -1
  14. package/dist/components/aurora/composables/useAurora.d.ts +28 -0
  15. package/dist/components/aurora/composables/wgpuSetup.d.ts +1 -1
  16. package/dist/components/aurora/constants/presets.d.ts +44 -5
  17. package/dist/components/aurora/index.d.ts +1 -1
  18. package/dist/components/configurator/Configurator.vue.d.ts +25 -2
  19. package/dist/components/configurator/ConfiguratorRow.vue.d.ts +4 -2
  20. package/dist/components/configurator/styles.css +1 -1
  21. package/dist/components/dock/DockSeparator.vue.d.ts +2 -3
  22. package/dist/components/dock/GlassDock.vue.d.ts +1 -0
  23. package/dist/components/dock/composables/dockContext.d.ts +1 -8
  24. package/dist/components/dock/composables/useDockRun.d.ts +20 -0
  25. package/dist/components/dock/composables/useDockSearch.d.ts +1 -1
  26. package/dist/components/dock/composables/useDockShellProps.d.ts +18 -101
  27. package/dist/components/dock/composables/useDockState.d.ts +2 -9
  28. package/dist/components/dock/constants.d.ts +15 -0
  29. package/dist/components/dock/index.d.ts +2 -2
  30. package/dist/components/dock/styles/controls/icon-button.css +1 -1
  31. package/dist/components/dock/styles/controls/tab-button.css +1 -1
  32. package/dist/components/dock/styles/controls/triggers.css +1 -1
  33. package/dist/components/dock/styles/controls.css +1 -1
  34. package/dist/components/dock/styles/density.css +1 -1
  35. package/dist/components/dock/styles/index.css +1 -1
  36. package/dist/components/dock/styles/layer-group.css +1 -1
  37. package/dist/components/dock/styles/layers.css +1 -1
  38. package/dist/components/dock/styles/morph.css +1 -1
  39. package/dist/components/dock/styles/run.css +1 -0
  40. package/dist/components/dock/styles/search.css +1 -1
  41. package/dist/components/dock/styles/shape.css +1 -1
  42. package/dist/components/dock/styles/shell-regions.css +1 -1
  43. package/dist/components/dock/styles/shell.css +1 -1
  44. package/dist/components/fourier-field/FourierField.vue.d.ts +28 -25
  45. package/dist/components/fourier-field/clock.d.ts +35 -0
  46. package/dist/components/fourier-field/constants.d.ts +78 -103
  47. package/dist/components/fourier-field/index.d.ts +3 -28
  48. package/dist/components/fourier-field/math.d.ts +69 -77
  49. package/dist/components/fourier-field/renderer/mint.d.ts +50 -0
  50. package/dist/components/fourier-field/renderer/uniforms.d.ts +84 -0
  51. package/dist/components/fourier-field/renderer/wgpu.d.ts +28 -0
  52. package/dist/components/fourier-field/shaders/compute.wgsl.d.ts +1 -0
  53. package/dist/components/fourier-field/shaders/render.wgsl.d.ts +1 -0
  54. package/dist/components/fourier-field/useFourierField.d.ts +49 -0
  55. package/dist/components/handmark/HandMark.vue.d.ts +18 -14
  56. package/dist/components/handmark/index.d.ts +4 -23
  57. package/dist/components/handmark/stroke.d.ts +92 -0
  58. package/dist/composables/dark/darkModeSyncScript.d.ts +19 -0
  59. package/dist/composables/motion/pointer/pointerFieldMappings.d.ts +0 -41
  60. package/dist/{components/search/composables → composables/search}/index.d.ts +2 -2
  61. package/dist/{components/search/composables/fuzzySearchIndex.d.ts → composables/search/match.d.ts} +8 -3
  62. package/dist/configurator-BHJymdrF.js +339 -0
  63. package/dist/configurator.js +1 -1
  64. package/dist/constellation.js +2 -2
  65. package/dist/control-DJp15L4U.js +7 -0
  66. package/dist/{dark-z_P5QwqI.js → dark-CLlmo7dD.js} +2 -2
  67. package/dist/dark-mode-toggle.js +1 -1
  68. package/dist/dark.js +1 -1
  69. package/dist/dock.js +712 -486
  70. package/dist/dom.js +90 -8
  71. package/dist/easing.js +3 -3
  72. package/dist/expandable-container-BY1hQXi4.js +142 -0
  73. package/dist/expandable-container.js +2 -142
  74. package/dist/fourier-field.js +643 -855
  75. package/dist/fourier-math.js +6 -6
  76. package/dist/glass-ui.css +1 -1
  77. package/dist/glass-ui.js +85 -72
  78. package/dist/handmark.js +330 -718
  79. package/dist/index.d.ts +1 -1
  80. package/dist/labeled-field.js +2 -2
  81. package/dist/{menu-wLu1XGKn.js → menu-TDMNtZjG.js} +1 -1
  82. package/dist/menu.js +1 -1
  83. package/dist/motion-core.js +34 -34
  84. package/dist/{participation-B0hdsIRo.js → participation-Di4UHbzk.js} +8 -8
  85. package/dist/{pointerFieldMappings-DP5tla5z.js → pointerFieldMappings-DXwlU5uj.js} +7 -23
  86. package/dist/{popover-DBMKTPkL.js → popover-C04Rrdcz.js} +1 -1
  87. package/dist/popover.js +1 -1
  88. package/dist/{radio-group-Ci8o-pDx.js → radio-group-BNV7PN1O.js} +1 -1
  89. package/dist/radio-group.js +1 -1
  90. package/dist/{select-CDgwGH_P.js → select-COox45Kv.js} +1 -1
  91. package/dist/select.js +1 -1
  92. package/dist/{slider-gsc8jDIo.js → slider-5ChjjWSR.js} +1 -1
  93. package/dist/slider.js +1 -1
  94. package/dist/styles/components.css +1 -1
  95. package/dist/styles/glass/material.css +1 -1
  96. package/dist/styles/glass/overlay-plate.css +1 -1
  97. package/dist/{switch-DxZzDrmV.js → switch-lLkbREB-.js} +1 -1
  98. package/dist/switch.js +1 -1
  99. package/dist/{tabs-CYscnq5H.js → tabs-DSJMd1Et.js} +1 -1
  100. package/dist/tabs.js +1 -1
  101. package/dist/{toggle-group-11aPGMfE.js → toggle-group-DUcmKnDw.js} +1 -1
  102. package/dist/toggle-group.js +1 -1
  103. package/dist/{tooltip-DqKMZ4dn.js → tooltip-BKk2qVDn.js} +1 -1
  104. package/dist/tooltip.js +1 -1
  105. package/dist/{resolveCanvasColor-6MCrswTB.js → useCanvas2D-CQRElMyE.js} +1 -11
  106. package/dist/useResolveTokenColor-B2FfJnd9.js +24 -0
  107. package/package.json +2 -20
  108. package/dist/canvas.d.ts +0 -1
  109. package/dist/canvas.js +0 -2
  110. package/dist/color.wgsl-B0N3mKMb.js +0 -412
  111. package/dist/compile-D7VYG3gi.js +0 -19
  112. package/dist/components/dock/composables/useDockOverflowFit.d.ts +0 -20
  113. package/dist/components/dock/composables/useDockTouchGate.d.ts +0 -42
  114. package/dist/components/dock/styles/overflow.css +0 -1
  115. package/dist/components/fourier-field/composables/fourierFieldGLSetup.d.ts +0 -24
  116. package/dist/components/fourier-field/composables/fourierFieldWGPUSetup.d.ts +0 -31
  117. package/dist/components/fourier-field/composables/uniformBridgeWGPU.d.ts +0 -39
  118. package/dist/components/fourier-field/composables/useFourierField.d.ts +0 -56
  119. package/dist/components/fourier-field/shaders/fourier-field.compute.wgsl.d.ts +0 -1
  120. package/dist/components/fourier-field/shaders/fourier-field.glsl.d.ts +0 -4
  121. package/dist/components/fourier-field/shaders/fourier-field.render.wgsl.d.ts +0 -1
  122. package/dist/components/fourier-field/shaders/fourier-field.ribbon.d.ts +0 -51
  123. package/dist/components/handmark/brush.d.ts +0 -269
  124. package/dist/components/handmark/composables/useHandMark.d.ts +0 -99
  125. package/dist/components/handmark/constants.d.ts +0 -69
  126. package/dist/components/handmark/freehand.d.ts +0 -69
  127. package/dist/components/handmark/geometry.d.ts +0 -62
  128. package/dist/components/handmark/ink.d.ts +0 -11
  129. package/dist/components/handmark/noise.d.ts +0 -30
  130. package/dist/components/handmark/texture.d.ts +0 -39
  131. package/dist/components/handmark/types.d.ts +0 -70
  132. package/dist/components/search/SearchBar.vue.d.ts +0 -38
  133. package/dist/components/search/index.d.ts +0 -8
  134. package/dist/components/search/searchVariants.d.ts +0 -16
  135. package/dist/configurator-BoeScnDL.js +0 -311
  136. package/dist/control-Da4XeT43.js +0 -11
  137. package/dist/search.d.ts +0 -1
  138. package/dist/search.js +0 -61
  139. package/dist/useFuzzySearch-CUnZknpm.js +0 -177
  140. package/dist/useResolveTokenColor-CspQv_ik.js +0 -107
  141. /package/dist/{components/search/composables → composables/search}/types.d.ts +0 -0
  142. /package/dist/{components/search/composables → composables/search}/useFuzzySearch.d.ts +0 -0
@@ -0,0 +1,50 @@
1
+ import { type BasisComponent } from "../math.js";
2
+ /**
3
+ * The reference stage diagonal in device px — the derivation is on the line above. Every
4
+ * axis maximum in the tree is this number's consequence, so it is declared once.
5
+ */
6
+ export declare const FOURIER_REFERENCE_DIAGONAL_PX = 856;
7
+ /** A term survives the mint iff it moves the curve at least this far at the reference stage. */
8
+ export declare const FOURIER_PAINT_FLOOR_PX = 0.5;
9
+ /**
10
+ * A minted spectrum: the DC anchor, the amplitude-ordered surviving terms, and the model
11
+ * geometry the fit and the ring law both read. Immutable and reference-stable — the
12
+ * renderer's re-upload guard is an identity check against this object.
13
+ */
14
+ export interface MintedSpectrum {
15
+ /** The amplitude-ordered, paint-floored terms. `length` IS the N axis maximum. */
16
+ readonly terms: readonly BasisComponent[];
17
+ /** The hoisted DC term (the curve's centroid) — the anchor the scene is centred on. */
18
+ readonly dc: readonly [number, number];
19
+ /** The full reconstruction's bbox half-extents in model units (DC removed). */
20
+ readonly halfSpanX: number;
21
+ readonly halfSpanY: number;
22
+ /** The bbox centre in model units (DC removed) — the fit's centre. */
23
+ readonly centerX: number;
24
+ readonly centerY: number;
25
+ /** The bbox diagonal in model units — the reference-stage mapping's denominator. */
26
+ readonly diagonal: number;
27
+ /** `Σ|c_k|` over the terms — the chain's maximum reach from the centre. */
28
+ readonly chainReach: number;
29
+ }
30
+ /**
31
+ * Mint a raw spectrum into the shipped one. The input is anything `dftFromPoints`,
32
+ * `makeHarmonicFigure` or `makeEllipticSpectrum` produced; the output is what the GPU
33
+ * ever sees.
34
+ *
35
+ * Deterministic: the same input array yields the same output, term for term. An empty or
36
+ * all-sub-floor input still emits its single largest term — a spectrum that draws nothing
37
+ * is not a fallback, it is a blank stage, and the machine must always have one ring.
38
+ */
39
+ export declare function mintSpectrum(raw: readonly BasisComponent[]): MintedSpectrum;
40
+ /**
41
+ * THE RING LAW. A ring is DRAWN iff its own diameter reaches the mark's stroke at the
42
+ * LIVE stage: `2·|c_k| ≥ markStroke`, both sides in device pixels. Below that rung the
43
+ * term is still SUMMED and still CHAINED — the scaffold arm and the joint dot stay — only
44
+ * the circle outline elides, because a circle narrower than the line drawing it is a blob,
45
+ * not a circle.
46
+ *
47
+ * This is why the machine reads at every size with ZERO media queries: the question is
48
+ * asked of the stage the component is actually in.
49
+ */
50
+ export declare function ringsAt(minted: MintedSpectrum, stageDiagonalDevicePx: number, markStrokeDevicePx: number): number;
@@ -0,0 +1,84 @@
1
+ import { type OklchStop } from "../../../composables/color/index.js";
2
+ import type { BasisComponent } from "../math.js";
3
+ import type { MintedSpectrum } from "./mint.js";
4
+ /** One phasor lane in the storage buffer: (re, im, index, _pad). */
5
+ export declare const FOURIER_PHASOR_BYTES = 16;
6
+ export interface FourierUniformScratch {
7
+ buffer: ArrayBuffer;
8
+ f32: Float32Array;
9
+ i32: Int32Array;
10
+ }
11
+ export declare function createFourierComputeScratch(): FourierUniformScratch;
12
+ export declare function packFourierComputeUniforms(scratch: FourierUniformScratch, headT: number, trailArc: number, harmonicN: number, sampleCount: number, termCount: number): FourierUniformScratch;
13
+ export declare function createFourierRenderScratch(): FourierUniformScratch;
14
+ /** The model→clip fit transform — computed once per spectrum, never per N edit. */
15
+ export interface FourierFit {
16
+ centerX: number;
17
+ centerY: number;
18
+ /** The uniform scale s mapping (model − center)·s into clip [-1,1] with margin. */
19
+ scale: number;
20
+ }
21
+ /**
22
+ * The view fit, read straight off the mint. It unions the curve's own bbox with the
23
+ * chain's reach when the machine shows, because a chain that leaves the stage is a
24
+ * machine you cannot check. It is FIXED under N: the mint is the only input, so an N edit
25
+ * never rescales the figure and the "N=1 is genuinely this big" claim stays true.
26
+ */
27
+ export declare function computeFourierFit(minted: MintedSpectrum, showMachine: boolean): FourierFit;
28
+ /**
29
+ * Convert a CSS-px stroke to MODEL units. The fit maps model → clip; clip spans [-1,1]
30
+ * over the min CSS dimension, so cssPx-per-clip is `canvasCssMin/2`.
31
+ */
32
+ export declare function strokeToModel(strokePx: number, scale: number, canvasCssMin: number): number;
33
+ /**
34
+ * The resolved ramp: a linear-sRGB LUT plus the two derived pigments the paint law names.
35
+ * Rebuilt only when the palette itself changes.
36
+ */
37
+ export interface FourierRamp {
38
+ /** `FOURIER_LUT_SIZE` linear-sRGB entries, index 0 the ramp head. */
39
+ readonly lut: Float32Array;
40
+ /** The ink: the ramp head one step DOWN in L, gamut-mapped at fixed L and hue. */
41
+ readonly ink: [number, number, number];
42
+ /** The head core: the ramp head lifted by half a step, chroma preserved. */
43
+ readonly head: [number, number, number];
44
+ }
45
+ /**
46
+ * Resolve a palette into the shipped ramp. Chroma is floored at every entry — a ramp that
47
+ * falls to grey paints a smear where the tail should still be pigment — and every entry
48
+ * is gamut-mapped, so an out-of-gamut author stop becomes a real colour rather than a
49
+ * clipped one.
50
+ */
51
+ export declare function resolveFourierRamp(palette: readonly OklchStop[]): FourierRamp;
52
+ export interface FourierRenderPack {
53
+ fit: FourierFit;
54
+ aspect: number;
55
+ markHalfModel: number;
56
+ inkOffsetStrokes: number;
57
+ scaffoldFrac: number;
58
+ edgeMargin: number;
59
+ peakAlpha: number;
60
+ glow: number;
61
+ squash: number;
62
+ trailFadeExp: number;
63
+ trailFloor: number;
64
+ hueSweep: number;
65
+ ringStrokeModel: number;
66
+ showMachine: boolean;
67
+ rainbowChain: boolean;
68
+ sampleCount: number;
69
+ /** The number of chain arms this frame draws — the hue sweep's denominator. */
70
+ chainCount: number;
71
+ ramp: FourierRamp;
72
+ }
73
+ /**
74
+ * Pack the render uniforms in place. `peakAlpha` arrives already multiplied by the
75
+ * intensity envelope and CLAMPED here — the one place loudness can leave the legal range,
76
+ * closed at pack time so no fragment can composite past 1.
77
+ */
78
+ export declare function packFourierRenderUniforms(scratch: FourierUniformScratch, p: FourierRenderPack): FourierUniformScratch;
79
+ /**
80
+ * Pack the phasor storage table (re, im, index, _pad) from the minted spectrum. The
81
+ * buffer is sized per-spectrum by the caller — there is no table ceiling, so a 61-term ℱ
82
+ * and a 2-term foil each pay for exactly what they carry.
83
+ */
84
+ export declare function packPhasorTable(terms: readonly BasisComponent[]): Float32Array;
@@ -0,0 +1,28 @@
1
+ import type { WebGPUCanvasFrame } from "../../../composables/glass/webgpu/useWebGPUCanvas.js";
2
+ import type { WebGLCanvasFrame } from "../../../composables/glass/webgl/useWebGLCanvas.js";
3
+ import type { OklchStop } from "../../../composables/color/index.js";
4
+ import { type FourierFieldConfig } from "../constants.js";
5
+ import type { MintedSpectrum } from "./mint.js";
6
+ export interface FourierRendererDeps {
7
+ canvas: HTMLCanvasElement;
8
+ config: FourierFieldConfig;
9
+ /** The minted spectrum — re-read each frame; identity change triggers the re-upload. */
10
+ getSpectrum: () => MintedSpectrum;
11
+ /** The resolved palette stops, re-read each frame so a live edit reaches the ramp. */
12
+ getPalette: () => OklchStop[];
13
+ /** The single loop parameter `head_t ∈ [0,1)`. */
14
+ getHeadT: () => number;
15
+ /** Demand-gate (the renderer's quiescence layer). */
16
+ shouldContinue: () => boolean;
17
+ /** The per-frame hook — the clock advances here, inside the substrate's own frame. */
18
+ onFrame?: (timeSec: number) => void;
19
+ }
20
+ /** Build the fourier-field `setupWGPU(device, context, format)` callback. */
21
+ export declare function createFourierWGPUSetup(deps: FourierRendererDeps): (device: GPUDevice, context: GPUCanvasContext, format: GPUTextureFormat) => WebGPUCanvasFrame;
22
+ /**
23
+ * The WebGL2 seam, declared closed. The substrate picker requires a `setupGL`; this one
24
+ * acquires nothing and draws nothing — it throws the sentence above, which the picker
25
+ * surfaces as a renderer error. The result is exactly what the law asks for: no second
26
+ * renderer, zero canvas pixels, and a status a consumer can read and show.
27
+ */
28
+ export declare function createFourierUnsupportedSetup(): (gl: WebGL2RenderingContext) => WebGLCanvasFrame;
@@ -0,0 +1 @@
1
+ export declare const FOURIER_FIELD_COMPUTE_WGSL = "\nconst TAU: f32 = 6.283185307179586;\n\nstruct Phasor {\n // (re, im, index, _pad)\n data: vec4<f32>,\n};\n\nstruct ComputeUniforms {\n // c0: (headT, trailArc, _pad, _pad)\n c0: vec4<f32>,\n // ints: (harmonicN, sampleCount, termCount, _pad)\n ints: vec4<i32>,\n};\n\n@group(0) @binding(0) var<uniform> u: ComputeUniforms;\n@group(0) @binding(1) var<storage, read> phasors: array<Phasor>;\n// curveSamples[i] = (x, y, age, _pad) in MODEL space\n@group(0) @binding(2) var<storage, read_write> curveSamples: array<vec4<f32>>;\n// chainTips[k] = (x, y, _pad, _pad) in MODEL space\n@group(0) @binding(3) var<storage, read_write> chainTips: array<vec4<f32>>;\n\n// The truncated inverse DFT at parameter t over the first n terms \u2014 the transcription of\n// math.ts partialSumAt: c += vec2(re*cos - im*sin, re*sin + im*cos), angle = TAU*index*t.\nfn partialSumAt(t: f32, n: i32) -> vec2<f32> {\n var c = vec2<f32>(0.0, 0.0);\n let lim = min(n, i32(arrayLength(&phasors)));\n for (var i = 0; i < lim; i = i + 1) {\n let p = phasors[i].data;\n let angle = TAU * p.z * t;\n let cs = cos(angle);\n let sn = sin(angle);\n c = c + vec2<f32>(p.x * cs - p.y * sn, p.x * sn + p.y * cs);\n }\n return c;\n}\n\n@compute @workgroup_size(64)\nfn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = i32(gid.x);\n let headT = u.c0.x;\n let trailArc = u.c0.y;\n let harmonicN = u.ints.x;\n let sampleCount = u.ints.y;\n\n // \u2500\u2500 The comet body \u2500\u2500\n if (i < sampleCount) {\n let denom = max(f32(sampleCount - 1), 1.0);\n let frac = f32(i) / denom; // 0 = head, 1 = tail\n let age = 1.0 - frac; // 1 = head, 0 = tail\n var t = headT - trailArc * frac;\n t = t - floor(t); // fract -> [0,1)\n let pt = partialSumAt(t, harmonicN);\n curveSamples[i] = vec4<f32>(pt.x, pt.y, age, 0.0);\n }\n\n // \u2500\u2500 The chain tips (k = 0..N). chainTips[0] is the origin. \u2500\u2500\n if (i <= harmonicN && i < i32(arrayLength(&chainTips))) {\n let tip = partialSumAt(headT, i);\n chainTips[i] = vec4<f32>(tip.x, tip.y, 0.0, 0.0);\n }\n}\n";
@@ -0,0 +1 @@
1
+ export declare const FOURIER_FIELD_RENDER_WGSL = "\n// 0 chain \u00B7 1 trail \u00B7 2 head \u2014 a pipeline-overridable constant, three pipelines, one module.\noverride LAYER: i32 = 0;\n\n// THE SPLICE CONTRACT, honoured. OKLCH_MATRICES_WGSL states it at color.wgsl.ts:52-53 \u2014\n// \"PI must be in scope (the consumer defines it first, as the GLSL chunk requires)\" \u2014 and\n// spends it at :105 folding the OKLCh hue to [0, 2pi). This module splices that chunk\n// below and defined PI nowhere, so every CreateShaderModule on this source failed\n// validation with \"unresolved value 'PI'\" and the stage painted NOTHING. The two other\n// WGSL primaries carry the same line for the same reason (aurora.wgsl.ts:49,\n// metaball.wgsl.ts:53); this is that house idiom, not a local patch. The CLASS is gated\n// by tests/components/fourier-field/wgsl-splice-contract.test.ts, which resolves every\n// assembled module against the chunks it splices.\nconst PI: f32 = 3.141592653589793;\n\nconst TANGENT_EPS: f32 = 1e-4;\nconst SPEED_REF_HALFWIDTHS: f32 = 6.0;\n// The trail's taper: the mark keeps this fraction of its width at the oldest tail.\nconst TAIL_TAPER: f32 = 0.55;\n\nstruct RenderUniforms {\n // r0: (centerX, centerY, scale, aspect)\n r0: vec4<f32>,\n // r1: (markHalf, inkOffsetStrokes, scaffoldFrac, edgeMargin)\n r1: vec4<f32>,\n // r2: (peakAlpha, glow, squash, trailFadeExp)\n r2: vec4<f32>,\n // r3: (trailFloor, hueSweep, ringStrokeModel, showMachine)\n r3: vec4<f32>,\n // ink: (linear rgb, rainbowChain)\n ink: vec4<f32>,\n // head: (linear rgb, _pad)\n head: vec4<f32>,\n // ints: (sampleCount, lutSize, chainCount, _pad)\n ints: vec4<i32>,\n // the CPU-resolved linear-sRGB ramp \u2014 the length is the TS constant, not a second copy\n lut: array<vec4<f32>, 16>,\n};\n\n@group(0) @binding(0) var<uniform> u: RenderUniforms;\n@group(0) @binding(1) var<storage, read> curveSamples: array<vec4<f32>>;\n@group(0) @binding(2) var<storage, read> chainTips: array<vec4<f32>>;\n\n\nfn srgbToLinearCh(c: f32) -> f32 {\n if (c <= 0.04045) { return c / 12.92; }\n return pow((c + 0.055) / 1.055, 2.4);\n}\nfn srgbToLinear(c: vec3<f32>) -> vec3<f32> {\n return vec3<f32>(srgbToLinearCh(c.r), srgbToLinearCh(c.g), srgbToLinearCh(c.b));\n}\n\nfn linearToSrgbCh(c: f32) -> f32 {\n if (c <= 0.0031308) { return c * 12.92; }\n return 1.055 * pow(c, 1.0 / 2.4) - 0.055;\n}\nfn linearToSrgb(c: vec3<f32>) -> vec3<f32> {\n return vec3<f32>(linearToSrgbCh(c.r), linearToSrgbCh(c.g), linearToSrgbCh(c.b));\n}\n\n// value.js LINEAR_SRGB_TO_LMS, row-major \u2192 WGSL columns (= the transpose).\nconst LINEAR_SRGB_TO_LMS: mat3x3<f32> = mat3x3<f32>(\n 0.4122214708, 0.2119034982, 0.0883024619,\n 0.5363325363, 0.6806995451, 0.2817188376,\n 0.0514459929, 0.1073969566, 0.6299787005\n);\n\n// value.js srgbToOKLab's INLINE LMS\u2192OKLab coefficients, row-major \u2192 WGSL columns.\nconst LMS_TO_OKLAB: mat3x3<f32> = mat3x3<f32>(\n 0.2104542553, 1.9779984951, 0.0259040371,\n 0.7936177850, -2.4285922050, 0.7827717662,\n -0.0040720468, 0.4505937099, -0.8086757660\n);\n\n// value.js OKLAB_TO_LMS_COEFF, row-major \u2192 WGSL columns.\nconst OKLAB_TO_LMS: mat3x3<f32> = mat3x3<f32>(\n 1.0, 1.0, 1.0,\n 0.3963377774, -0.1055613458, -0.0894841775,\n 0.2158037573, -0.0638541728, -1.2914855480\n);\n\n// value.js LMS_TO_LINEAR_SRGB, row-major \u2192 WGSL columns.\nconst LMS_TO_LINEAR_SRGB: mat3x3<f32> = mat3x3<f32>(\n 4.0767416621, -1.2684380046, -0.0041960863,\n -3.3077115913, 2.6097574011, -0.7034186147,\n 0.2309699292, -0.3413193965, 1.7076147010\n);\n\nfn cbrt3(v: vec3<f32>) -> vec3<f32> {\n return sign(v) * pow(abs(v), vec3<f32>(1.0 / 3.0));\n}\n\n// Gamma sRGB \u2192 raw OKLab (L, a, b). Mirrors value.js srgbToOKLab.\nfn srgbToOklab(c: vec3<f32>) -> vec3<f32> {\n let lin = srgbToLinear(c);\n let lms = LINEAR_SRGB_TO_LMS * lin;\n return LMS_TO_OKLAB * cbrt3(lms);\n}\n\n// Raw OKLab (L, a, b) \u2192 linear sRGB. Mirrors value.js oklabToLinearSRGB.\nfn oklabToLinearSrgb(lab: vec3<f32>) -> vec3<f32> {\n let lms_ = OKLAB_TO_LMS * lab;\n let lms = lms_ * lms_ * lms_;\n return LMS_TO_LINEAR_SRGB * lms;\n}\n\n// OKLab \u2192 OKLCh: H in RADIANS, folded to [0, 2pi). Mirrors value.js rawOklabToOklch.\nfn oklabToOklch(lab: vec3<f32>) -> vec3<f32> {\n let C = length(lab.yz);\n var H = atan2(lab.z, lab.y);\n if (H < 0.0) { H = H + 2.0 * PI; }\n return vec3<f32>(lab.x, C, H);\n}\n\n// OKLCh (H radians) \u2192 OKLab. Mirrors value.js rawOklchToOklab.\nfn oklchToOklab(lch: vec3<f32>) -> vec3<f32> {\n return vec3<f32>(lch.x, lch.y * cos(lch.z), lch.y * sin(lch.z));\n}\n\n// Read the CPU-resolved ramp. No colour-space conversion happens here \u2014 the LUT already\n// carries the chroma floor and the gamut map.\nfn sampleRamp(t: f32) -> vec3<f32> {\n let n = u.ints.y;\n let ft = clamp(t, 0.0, 1.0) * f32(n - 1);\n let i0 = i32(floor(ft));\n let i1 = min(i0 + 1, n - 1);\n return mix(u.lut[i0].rgb, u.lut[i1].rgb, ft - f32(i0));\n}\n\n// The chain's hue sweep, taken ONCE PER INSTANCE in the vertex stage: the ramp entry for\n// this arm, rotated about the palette anchor by \u00B1hueSweep. Anchored warm, the sweep is\n// bounded by its own arithmetic \u2014 it cannot arrive anywhere the palette is not.\nfn chainTint(seg: f32) -> vec3<f32> {\n let base = sampleRamp(seg * 0.6);\n if (u.ink.w < 0.5) { return base; }\n let lab = LMS_TO_OKLAB * cbrt3(LINEAR_SRGB_TO_LMS * base);\n let lch = oklabToOklch(lab);\n let swept = vec3<f32>(lch.x, lch.y, lch.z + mix(-u.r3.y, u.r3.y, clamp(seg, 0.0, 1.0)));\n return oklabToLinearSrgb(oklchToOklab(swept));\n}\n\nfn segDist(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>) -> f32 {\n let pa = p - a;\n let ba = b - a;\n let h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-8), 0.0, 1.0);\n return length(pa - ba * h);\n}\n\n// The head's travel frame + its volume-preserving anisotropic metric.\nstruct HeadFrame { T: vec2<f32>, sHat: f32, };\nfn headFrame(head: vec2<f32>, back: vec2<f32>, halfW: f32) -> HeadFrame {\n let d = head - back;\n let s = length(d);\n var out: HeadFrame;\n if (s <= TANGENT_EPS) {\n out.T = vec2<f32>(1.0, 0.0);\n out.sHat = 0.0;\n return out;\n }\n out.T = d / s;\n out.sHat = clamp(s / max(halfW * SPEED_REF_HALFWIDTHS, 1e-6), 0.0, 1.0);\n return out;\n}\n\nfn headAniso(p: vec2<f32>, head: vec2<f32>, fr: HeadFrame, k: f32) -> f32 {\n let g = 1.0 + k * fr.sHat;\n let n = vec2<f32>(-fr.T.y, fr.T.x);\n let rel = p - head;\n return length(vec2<f32>(dot(rel, fr.T) / g, dot(rel, n) * g));\n}\n\nfn quadCorner(vi: u32) -> vec2<f32> {\n var c = array<vec2<f32>, 6>(\n vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 0.0), vec2<f32>(0.0, 1.0),\n vec2<f32>(0.0, 1.0), vec2<f32>(1.0, 0.0), vec2<f32>(1.0, 1.0),\n );\n return c[vi];\n}\n\nstruct VSOut {\n @builtin(position) pos: vec4<f32>,\n @location(0) p: vec2<f32>,\n @location(1) a: vec2<f32>,\n @location(2) b: vec2<f32>,\n // (age, strokeHalf, drawRing)\n @location(3) seg: vec3<f32>,\n @location(4) @interpolate(flat) tint: vec3<f32>,\n};\n\n@vertex\nfn vs_main(@builtin(vertex_index) vi: u32, @builtin(instance_index) inst: u32) -> VSOut {\n let corner = quadCorner(vi);\n let center = u.r0.xy;\n let scale = max(u.r0.z, 1e-6);\n let aspect = max(u.r0.w, 1e-4);\n let markHalf = u.r1.x;\n let margin = u.r1.w;\n\n var a = vec2<f32>(0.0);\n var b = vec2<f32>(0.0);\n var strokeHalf = markHalf;\n var age = 0.0;\n var drawRing = 0.0;\n var tint = vec3<f32>(0.0);\n var isCapsule = true;\n var ext = markHalf;\n\n if (LAYER == 0) {\n // CHAIN \u2014 the AABB around this arm's own orbit (radius = |b \u2212 a| = |c_k|).\n a = chainTips[inst].xy;\n b = chainTips[inst + 1u].xy;\n let radius = length(b - a);\n // THE RING LAW, asked of the live stage: a circle narrower than its own line is a blob.\n if (2.0 * radius >= u.r3.z) { drawRing = 1.0; }\n strokeHalf = markHalf * u.r1.z;\n tint = chainTint(f32(inst) / max(f32(u.ints.z) - 1.0, 1.0));\n ext = radius + strokeHalf * 2.0;\n isCapsule = false;\n } else if (LAYER == 1) {\n // TRAIL \u2014 the curve capsule, padded for the ink's own offset.\n a = curveSamples[inst].xy;\n b = curveSamples[inst + 1u].xy;\n age = max(curveSamples[inst].z, curveSamples[inst + 1u].z);\n tint = sampleRamp(1.0 - age);\n ext = markHalf * (1.0 + 2.0 * max(u.r1.y, 0.0));\n isCapsule = true;\n } else {\n // HEAD \u2014 centred on the newest curve sample; the AABB is the halo.\n a = curveSamples[0].xy;\n b = curveSamples[1].xy;\n tint = u.head.rgb;\n ext = markHalf * 5.0;\n isCapsule = false;\n }\n ext = ext + margin;\n\n var pos: vec2<f32>;\n if (isCapsule) {\n let d = b - a;\n let len = length(d);\n var dir = vec2<f32>(1.0, 0.0);\n if (len > 1e-6) { dir = d / len; }\n let perp = vec2<f32>(-dir.y, dir.x);\n pos = mix(a - dir * ext, b + dir * ext, corner.x) + perp * (corner.y * 2.0 - 1.0) * ext;\n } else {\n pos = a + (corner * 2.0 - vec2<f32>(1.0)) * ext;\n }\n\n var out: VSOut;\n out.pos = vec4<f32>((pos.x - center.x) * scale / aspect, (pos.y - center.y) * scale, 0.0, 1.0);\n out.p = pos;\n out.a = a;\n out.b = b;\n out.seg = vec3<f32>(age, strokeHalf, drawRing);\n out.tint = tint;\n return out;\n}\n\n@fragment\nfn fs_main(in: VSOut) -> @location(0) vec4<f32> {\n let p = in.p;\n let aa = max(fwidth(p.x) + fwidth(p.y), 1e-5) * 0.75;\n let peak = u.r2.x;\n let glow = u.r2.y;\n let squash = u.r2.z;\n let fadeExp = u.r2.w;\n let trailFloor = u.r3.x;\n let markHalf = u.r1.x;\n let strokeHalf = in.seg.y;\n let age = in.seg.x;\n let a = in.a;\n let b = in.b;\n\n if (LAYER == 0) {\n // \u2500\u2500 THE MACHINE \u2014 ring (conditionally), arm, joint dot; one scaffold rung \u2500\u2500\n let radius = length(b - a);\n var m = 0.0;\n if (in.seg.z > 0.5) {\n let dRing = abs(length(p - a) - radius);\n m = max(m, 1.0 - smoothstep(strokeHalf, strokeHalf + aa, dRing));\n }\n m = max(m, 1.0 - smoothstep(strokeHalf, strokeHalf + aa, segDist(p, a, b)));\n // The joint dot is the mark's own diameter \u2014 the machine's one solid rung.\n m = max(m, 1.0 - smoothstep(markHalf, markHalf + aa, length(p - a)));\n let alpha = clamp(m, 0.0, 1.0) * peak;\n let rgb = clamp(linearToSrgb(in.tint), vec3<f32>(0.0), vec3<f32>(1.0));\n return vec4<f32>(rgb * alpha, alpha);\n } else if (LAYER == 1) {\n // \u2500\u2500 THE CURVE \u2014 ink under mark, composited at \u03B11, then ONE envelope \u2500\u2500\n let taper = TAIL_TAPER + (1.0 - TAIL_TAPER) * age;\n let half = markHalf * taper;\n // The ink rides THIS segment's own tangent, opposite the travel: the head runs from\n // b toward a, so the ink lands behind at (b \u2212 a).\n var dir = vec2<f32>(1.0, 0.0);\n let d = b - a;\n let len = length(d);\n if (len > 1e-6) { dir = d / len; }\n let off = dir * (markHalf * 2.0 * u.r1.y);\n\n let aMark = 1.0 - smoothstep(half, half + aa, segDist(p, a, b));\n let aInk = 1.0 - smoothstep(half, half + aa, segDist(p, a + off, b + off));\n\n let markRgb = clamp(linearToSrgb(in.tint), vec3<f32>(0.0), vec3<f32>(1.0));\n let inkRgb = clamp(linearToSrgb(u.ink.rgb), vec3<f32>(0.0), vec3<f32>(1.0));\n\n // Mark OVER ink, both fully opaque where they cover \u2014 the pair, then the envelope.\n let aPair = aMark + aInk * (1.0 - aMark);\n let rgbPair = markRgb * aMark + inkRgb * aInk * (1.0 - aMark);\n let env = peak * max(pow(max(age, 0.0), fadeExp), trailFloor);\n return vec4<f32>(rgbPair * env, aPair * env);\n } else {\n // \u2500\u2500 THE HEAD \u2014 palette-lifted core over its own halo. Light-led, never white. \u2500\u2500\n let fr = headFrame(a, b, markHalf);\n let dHead = headAniso(p, a, fr, squash);\n let coreR = markHalf * 1.5;\n let haloR = coreR * 3.0;\n let rgb = clamp(linearToSrgb(in.tint), vec3<f32>(0.0), vec3<f32>(1.0));\n let halo = (1.0 - smoothstep(0.0, haloR, dHead)) * glow;\n let core = (1.0 - smoothstep(coreR, coreR + aa, dHead)) * peak;\n let alpha = clamp(max(core, halo), 0.0, 1.0);\n return vec4<f32>(rgb * alpha, alpha);\n }\n}\n";
@@ -0,0 +1,49 @@
1
+ import { type Ref } from "vue";
2
+ import { type GpuBackend, type RendererStatus } from "../../composables/glass/webgpu/useGpuSubstrate.js";
3
+ import type { OklchStop } from "../../composables/color/index.js";
4
+ import { type FourierFieldConfig } from "./constants.js";
5
+ import type { MintedSpectrum } from "./renderer/mint.js";
6
+ export interface UseFourierFieldOptions {
7
+ config: FourierFieldConfig;
8
+ /** The minted spectrum (identity-stable until the source or seed changes). */
9
+ getSpectrum: () => MintedSpectrum;
10
+ /** The resolved palette stops as OKLCh. */
11
+ getPalette: () => OklchStop[];
12
+ /**
13
+ * Whether the pointer drives the clock. ONE authority — the component's own
14
+ * `interactive` prop — read live rather than copied into the config.
15
+ */
16
+ interactive: () => boolean;
17
+ /** Hold the clock at its current parameter and never advance. */
18
+ freeze?: () => boolean;
19
+ /** `"capture"` → renderAt-only (the deterministic capture path). Default `"live"`. */
20
+ mode?: "live" | "capture";
21
+ }
22
+ /** The handle a consumer wires its lifecycle and its transport against. */
23
+ export interface FourierFieldHandle {
24
+ /** The resolved backend. Always `"webgpu"` where the field paints at all. */
25
+ readonly backend: GpuBackend;
26
+ /** Park the loop (the pause seam). */
27
+ pause: () => void;
28
+ /** Re-arm the loop. */
29
+ resume: () => void;
30
+ /** Re-arm a parked loop on demand. */
31
+ wake: () => void;
32
+ /** The live `prefers-reduced-motion: reduce` state. */
33
+ readonly reducedMotion: boolean;
34
+ /** The current loop parameter `head_t ∈ [0,1)`. */
35
+ readonly headT: number;
36
+ /**
37
+ * The same parameter as a REACTIVE read, refreshed off the one clock at a rate a
38
+ * reader (or a screen reader) can follow. It is a sample of the frame loop, never a
39
+ * second one.
40
+ */
41
+ readonly headTLive: Readonly<Ref<number>>;
42
+ readonly rendererStatus: Readonly<Ref<RendererStatus>>;
43
+ /** Scrub the clock directly. Takes no spring — a hand's position is not a physics event. */
44
+ setHeadT: (t: number) => void;
45
+ /** Inject one flick impulse, in turns per second. Capped and floored like any other. */
46
+ flick: (turnsPerSec: number) => void;
47
+ dispose: () => void;
48
+ }
49
+ export declare function useFourierField(canvasRef: Ref<HTMLCanvasElement | null>, options: UseFourierFieldOptions): FourierFieldHandle;
@@ -1,23 +1,27 @@
1
- import type { HandMarkProps } from "./types.js";
1
+ import { type HandShape } from "./stroke.js";
2
+ type __VLS_Props = {
3
+ shape?: HandShape;
4
+ color?: string;
5
+ weight?: number;
6
+ seed?: number;
7
+ draw?: boolean;
8
+ };
2
9
  declare function play(): void;
3
- declare var __VLS_1: {};
10
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {};
4
11
  type __VLS_Slots = {} & {
5
12
  default?: (props: typeof __VLS_1) => any;
13
+ } & {
14
+ default?: (props: typeof __VLS_3) => any;
15
+ } & {
16
+ default?: (props: typeof __VLS_5) => any;
6
17
  };
7
- declare const __VLS_base: import("vue").DefineComponent<HandMarkProps, {
18
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
8
19
  play: typeof play;
9
- }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<HandMarkProps> & Readonly<{}>, {
20
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
10
21
  color: string;
11
- seed: number;
12
- shape: import("./types.js").HandShape;
13
- animation: import("./types.js").HandAnimation;
14
- brush: import("./brush.js").BrushName | Partial<import("./brush.js").Brush>;
15
- drawMs: number;
16
- drawDelayMs: number;
17
- appear: "mount" | "visible" | "manual";
18
- boilFps: number;
19
- boilFrames: number;
20
- jagged: boolean;
22
+ shape: HandShape;
23
+ weight: number;
24
+ draw: boolean;
21
25
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
26
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
23
27
  declare const _default: typeof __VLS_export;
@@ -1,26 +1,7 @@
1
1
  /**
2
- * HandMark public barrel.
3
- * ─────────────────────────────────────────────────────────────────────────────
4
- * `HandMark` is the sole public component name. The pure L1–L3 functions ship
5
- * too — they are framework-free, directly usable in slides decks, D3 charts
6
- * (call `shapeGeom`/`ellipsePoints` to ink a path), even canvas.
7
- *
8
- * The DEFAULT is `pen` (clean ink, grain:0 ⇒ no filter, zero extra dep). The
9
- * `@mkbabb/pencil-boil` geometry is an OPTIONAL PEER. The perfect-freehand body
10
- * is vendored; only `ribbon:'hull'` (the highlighter) touches that core
11
- * (treeshaken otherwise).
12
- *
13
- * The editorial draw-on underline is
14
- * `<HandMark shape="underline" animation="draw-on">`; the natural pencil-boil
15
- * morphology is the `boil` brush.
2
+ * HandMark public barrel. The component is the surface; the pure functions ship with
3
+ * it so a caller who owns its own path can ink it with the same pen — `strokeRibbon`
4
+ * takes any polyline.
16
5
  */
17
6
  export { default as HandMark } from "./HandMark.vue.js";
18
- export { BRUSHES, resolveBrush, lerpBrush, type Brush, type BrushName, type BlendMode, type TaperSpec, type StampFn, type InkPath, } from "./brush.js";
19
- export { shapeGeom, serialize, boilLines, type ShapeGeom } from "./geometry.js";
20
- export { naturalUnderlinePoints } from "./noise.js";
21
- export { VB_W, VB_H, UNDERLINE_GAP } from "./constants.js";
22
- export { ink, type SVGFragment } from "./ink.js";
23
- export { grainFilter, hasGrain } from "./texture.js";
24
- export { getStroke, getStrokePoints, getSvgPathFromStroke, type StrokeOptions, type InputPoint, } from "./freehand.js";
25
- export { useHandMark, normalizeProps, type UseHandMarkInput, type HandMarkCore, type BoilClock, } from "./composables/useHandMark.js";
26
- export type { HandMarkProps, HandShape, HandAnimation, MarkBox, } from "./types.js";
7
+ export { SHAPES, handBand, handLine, handRing, markDuration, minJerk, serialize, strokeRibbon, type Frame, type HandShape, type Point, } from "./stroke.js";
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The hand voice — one pen, five laws, no framework.
3
+ *
4
+ * Every function here is pure and framework-free: a caller with a rect and a font
5
+ * size gets the same geometry the component draws, in 1:1 CSS pixels. `strokeRibbon`
6
+ * takes ANY polyline, so a consumer that owns its own path (a chart leader, a route)
7
+ * inks it with this pen rather than re-deriving one.
8
+ */
9
+ /** The four gestures, enumerated at runtime so a member change breaks compile-visibly. */
10
+ export declare const SHAPES: readonly ["underline", "strike", "circle", "highlight"];
11
+ export type HandShape = (typeof SHAPES)[number];
12
+ export interface Point {
13
+ x: number;
14
+ y: number;
15
+ }
16
+ /** A measured text rect, in CSS px, already expressed in the frame that draws it. */
17
+ export interface Frame {
18
+ x: number;
19
+ y: number;
20
+ width: number;
21
+ height: number;
22
+ }
23
+ export declare const CAP_HEIGHT = 0.7515;
24
+ /** L4 — x-height plus a 0.04em bleed at each end. */
25
+ export declare const BAND_HEIGHT: number;
26
+ /**
27
+ * The hand's outer envelope for the ring, in em: the declared vertical run 2b carried
28
+ * out by the largest radial creep and the largest wobble crest it admits.
29
+ */
30
+ export declare const RING_ENVELOPE: number;
31
+ /** L1 — the clampless allometric nib. `weight` is a dimensionless multiple. */
32
+ export declare function nib(weight: number, fs: number): number;
33
+ /** L3 — floorless, ceilingless pace over the path length of ONE line rect. */
34
+ export declare function markDuration(length: number): number;
35
+ /**
36
+ * The minimum-jerk draw profile as a CSS `linear()` easing.
37
+ *
38
+ * Every stop is emitted the same way — the PROFILE'S OWN VALUE at its own input — so
39
+ * `easing(0) = 0` and `easing(1) = 1` hold by construction: `10t³ − 15t⁴ + 6t⁵` is
40
+ * exactly 0 and exactly 1 at those ends. There is no endpoint special case, because an
41
+ * endpoint special case is a second expression for a quantity the loop already has,
42
+ * and the two can disagree. (They did: the branch it replaces emitted the loop INDEX,
43
+ * so `easing(1)` was `samples` and a draw animating `stroke-dashoffset` to 0 with
44
+ * `fill: both` came to rest at −23 dash periods — a mark inside its own gap.)
45
+ */
46
+ export declare function minJerk(samples?: number): string;
47
+ export interface LineOpts {
48
+ fs: number;
49
+ seed: number;
50
+ kind: "underline" | "strike";
51
+ }
52
+ /**
53
+ * L2 — the centreline. `d(u) = S·[4u(1−u) + B·4u(1−u)(1−2u)]` with |B| ≤ 0.4: both
54
+ * modes vanish at the endpoints and `d′` is a quadratic, so the commit carries at most
55
+ * two extrema in closed form for every seed. The lift-off hook is monotone in t over
56
+ * the last 6% and belongs to the release, never to the calm reading. Tilt is a shear,
57
+ * not a rotation, so the per-end reach stays exactly what the run-out declares.
58
+ */
59
+ export declare function handLine(rect: Frame, o: LineOpts): Point[];
60
+ /** The ring's semi-axes: the exact curve through the padded box corner on BOTH axes. */
61
+ export declare function ringAxes(rect: Frame, fs: number): {
62
+ a: number;
63
+ b: number;
64
+ };
65
+ /** The horizontal overshoot a ring reserves with `padding-inline` on its wrapper. */
66
+ export declare function ringReserve(rect: Frame, fs: number): number;
67
+ /** L2-RING — an n=3 superellipse, overswept so the tail closes outside the head. */
68
+ export declare function handRing(rect: Frame, o: {
69
+ fs: number;
70
+ seed: number;
71
+ }): Point[];
72
+ /**
73
+ * L4 — the band: one chisel per line rect, raked at both ends, sagging like a swipe,
74
+ * overrunning its word by 1–2% a side. Opaque, and it carries no ink colour: the page's
75
+ * own foreground reads on it in both themes.
76
+ */
77
+ export declare function handBand(rect: Frame, o: {
78
+ fs: number;
79
+ seed: number;
80
+ }): Point[];
81
+ /** `M x y L x y …` over an open polyline. */
82
+ export declare function serialize(points: Point[]): string;
83
+ /**
84
+ * The pen, over an arbitrary polyline: the ribbon is the outline of a stroke of
85
+ * maximum width `w`, emitted left side forward then right side back, so the minor axis
86
+ * at any sample is the nib the eye reads there.
87
+ */
88
+ export declare function strokeRibbon(points: Point[], w: number): string;
89
+ /** A closed polygon — the band's own emission, which is a fill and not a stroke. */
90
+ export declare function fillPolygon(points: Point[]): string;
91
+ /** The default seed: the marked text, salted by the mark's position among its siblings. */
92
+ export declare function markSeed(text: string, instanceIndex: number): number;
@@ -8,6 +8,25 @@ export interface DarkModeSyncScriptOptions {
8
8
  * uses. Override ONLY if a consumer reconfigured the vueuse storage key.
9
9
  */
10
10
  storageKey?: string;
11
+ /**
12
+ * What an ABSENT or `"auto"` stored mode resolves to. `"os"` (the default)
13
+ * follows `prefers-color-scheme`, which is right for an app. `false`/`true`
14
+ * resolve DETERMINISTICALLY, which is right for a document that must not flip
15
+ * with the projector's OS theme — a briefing, a print target, a capture.
16
+ */
17
+ defaultDark?: boolean | "os";
18
+ /**
19
+ * Honour `?light` / `?dark` in the query string, above storage and above the
20
+ * default. This is the capture-forcing seam: a screenshot pipeline asks for a
21
+ * mode in the URL rather than driving a toggle and hoping.
22
+ */
23
+ queryOverride?: boolean;
24
+ /**
25
+ * Write the RESOLVED mode back to storage, so an absent or `"auto"` value
26
+ * becomes a concrete `"dark"`/`"light"` at first paint and the runtime
27
+ * composable and the stamp cannot disagree about what `auto` meant.
28
+ */
29
+ normalize?: boolean;
11
30
  }
12
31
  /**
13
32
  * Return the inline `<head>` script STRING that resolves dark/light at parse
@@ -20,47 +20,6 @@ export interface PointerFieldSnapshot {
20
20
  /** The smoothed active-engagement envelope (0..1). */
21
21
  engagement: number;
22
22
  }
23
- /** The default bias gain — the curve advances at most ±BIAS_GAIN faster/slower (≤0.15). */
24
- export declare const FOURIER_BIAS_GAIN = 0.15;
25
- /** The default centroid-lean fraction (the subtle lean). */
26
- export declare const FOURIER_FOLLOW_LEAN = 0.15;
27
- export interface FourierLeanGeometry {
28
- /** The canvas backing aspect (w/h) — the model→clip fit inverse. */
29
- aspect: number;
30
- /** The fit scale (`model = center + clip·aspect / scale`). */
31
- scale: number;
32
- /**
33
- * The head→cursor direction in CLIP space (the renderer computes it from the head
34
- * model position + the cursor clip position). Its dot with the head's travel tangent
35
- * is the draw-bias alignment. Omit (or zero) → no draw-bias (phaseRateMul = 1).
36
- */
37
- headToCursor?: PointerVec2;
38
- /** The head's travel TANGENT (clip space), for the draw-bias alignment. Omit → no bias. */
39
- headTangent?: PointerVec2;
40
- }
41
- export interface FourierLeanOptions {
42
- /** The centroid-lean fraction (default {@link FOURIER_FOLLOW_LEAN}). */
43
- lean?: number;
44
- /** The draw-bias gain (default {@link FOURIER_BIAS_GAIN}; clamped ≤0.15). */
45
- biasGain?: number;
46
- }
47
- export interface FourierLeanResult {
48
- /** The MODEL-space centroid lean the setups subtract from `fit.center`. */
49
- leanX: number;
50
- leanY: number;
51
- /**
52
- * The clock phase-rate multiplier (1 = neutral). >1 draws toward the cursor a hair
53
- * faster; <1 a hair slower. Bounded [1-biasGain, 1+biasGain].
54
- */
55
- phaseRateMul: number;
56
- }
57
- /**
58
- * The fourier draw-bias + subtle-lean mapping (PURE). The lean uses the SAME [0,1]→clip
59
- * (y-inverted) → model math the renderer inverts, at the subtle FOLLOW_LEAN (NOT the retired
60
- * FOLLOW_REACH teleport), scaled by engagement. The draw-bias modulates the clock phase-rate
61
- * by the head-tangent · head→cursor alignment, bounded by biasGain.
62
- */
63
- export declare function fourierLeanMapping(snap: PointerFieldSnapshot, geom: FourierLeanGeometry, options?: FourierLeanOptions): FourierLeanResult;
64
23
  /** The steady velocity-lead gain (the body anticipates the heading). */
65
24
  export declare const BLOB_LEAD_K = 0.09;
66
25
  /** The pseudopod stretch gain off |acceleration| (capped LOW so it swells, never taffies). */
@@ -1,5 +1,5 @@
1
1
  export { useFuzzySearch } from "./useFuzzySearch.js";
2
2
  export type { UseFuzzySearchOptions } from "./useFuzzySearch.js";
3
- export { buildIndex, searchIndex, fuzzyMatch, clearSearchCache } from "./fuzzySearchIndex.js";
4
- export type { SearchIndex } from "./fuzzySearchIndex.js";
3
+ export { buildIndex, searchIndex, fuzzyMatch, clearSearchCache } from "./match.js";
4
+ export type { SearchIndex } from "./match.js";
5
5
  export type { SearchableItem, SearchResult, FuzzySearchState } from "./types.js";
@@ -1,7 +1,12 @@
1
1
  /**
2
- * Generic fuzzy search index — builds a flat searchable index from items
3
- * and scores queries with VSCode-style fuzzy matching.
4
- * Pure functions, no Vue reactivity.
2
+ * The engine's matcher — builds a flat searchable index from items and scores queries
3
+ * with VSCode-style fuzzy matching. Pure functions, no Vue reactivity.
4
+ *
5
+ * [2026-08-25 · BK #42 W-SEARCH] Landed here from
6
+ * `src/components/search/composables/fuzzySearchIndex.ts`. The name lost its
7
+ * `fuzzySearchIndex` prefix with the directory: under `composables/search/` the words
8
+ * "search" and "fuzzy" are the path, and a file called `search/fuzzySearchIndex.ts`
9
+ * says them twice.
5
10
  */
6
11
  import type { SearchableItem, SearchResult } from "./types.js";
7
12
  interface IndexEntry<T extends SearchableItem> {