tegaki 0.8.0 → 0.10.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 (44) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/core/index.d.mts +2 -2
  3. package/dist/core/index.mjs +2 -2
  4. package/dist/{core-Cw5jNWFa.mjs → core-D68zOEne.mjs} +84 -14
  5. package/dist/core-D68zOEne.mjs.map +1 -0
  6. package/dist/fonts/caveat/bundle.d.mts +825 -0
  7. package/dist/fonts/caveat/bundle.mjs +11874 -0
  8. package/dist/fonts/caveat/bundle.mjs.map +1 -0
  9. package/dist/fonts/caveat/caveat.ttf +0 -0
  10. package/dist/fonts/italianno/bundle.d.mts +821 -0
  11. package/dist/fonts/italianno/bundle.mjs +16422 -0
  12. package/dist/fonts/italianno/bundle.mjs.map +1 -0
  13. package/dist/fonts/italianno/italianno.ttf +0 -0
  14. package/dist/fonts/parisienne/bundle.d.mts +825 -0
  15. package/dist/fonts/parisienne/bundle.mjs +17763 -0
  16. package/dist/fonts/parisienne/bundle.mjs.map +1 -0
  17. package/dist/fonts/parisienne/parisienne.ttf +0 -0
  18. package/dist/fonts/tangerine/bundle.d.mts +825 -0
  19. package/dist/fonts/tangerine/bundle.mjs +15417 -0
  20. package/dist/fonts/tangerine/bundle.mjs.map +1 -0
  21. package/dist/fonts/tangerine/tangerine.ttf +0 -0
  22. package/dist/index-B2v8wlaN.d.mts +34 -0
  23. package/dist/{index-YdGpXlqf.d.mts → index-BdBLaKjh.d.mts} +51 -4
  24. package/dist/index.d.mts +3 -3
  25. package/dist/index.mjs +3 -3
  26. package/dist/react/index.d.mts +3 -3
  27. package/dist/react/index.mjs +3 -3
  28. package/dist/react-Cgxpndhg.mjs +88 -0
  29. package/dist/react-Cgxpndhg.mjs.map +1 -0
  30. package/dist/solid/index.d.mts +2 -2
  31. package/dist/solid/index.mjs +2 -2
  32. package/dist/wc/index.d.mts +3 -3
  33. package/dist/wc/index.mjs +12 -4
  34. package/dist/wc/index.mjs.map +1 -1
  35. package/package.json +26 -6
  36. package/src/core/createBundle.ts +45 -0
  37. package/src/core/engine.ts +83 -16
  38. package/src/core/index.ts +1 -0
  39. package/src/react/TegakiRenderer.tsx +86 -26
  40. package/src/wc/TegakiElement.ts +8 -2
  41. package/dist/core-Cw5jNWFa.mjs.map +0 -1
  42. package/dist/index-Bmbc0eTI.d.mts +0 -39
  43. package/dist/react-DfB_wEik.mjs +0 -60
  44. package/dist/react-DfB_wEik.mjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # tegaki
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`7198553`](https://github.com/KurtGokhan/tegaki/commit/719855392734a8f1b6056db9f0718ac7a8213527) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Add controlled progress mode to allow users to specify the exact progress of the animation that is a value between 0 and 1.
8
+
9
+ ### Patch Changes
10
+
11
+ - [`b326f00`](https://github.com/KurtGokhan/tegaki/commit/b326f00d52b97ef19e0214cb4595bd31cd501cf4) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Add delay and loop gap for uncontrolled animations
12
+
13
+ - [`1449890`](https://github.com/KurtGokhan/tegaki/commit/144989014c0d9cdbf80fafbb77af646b96065832) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Add docs and example for using Tegaki with Remotion. The example is a simple composition that renders a single text prop, but the same principles apply to more complex compositions and dynamic props.
14
+
15
+ - [`1449890`](https://github.com/KurtGokhan/tegaki/commit/144989014c0d9cdbf80fafbb77af646b96065832) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Fix rendering when zoom level was not 100%.
16
+
17
+ ## 0.9.0
18
+
19
+ ### Minor Changes
20
+
21
+ - [#12](https://github.com/KurtGokhan/tegaki/pull/12) [`e43197f`](https://github.com/KurtGokhan/tegaki/commit/e43197f5719368bed5280aa106c8fcb7afe05b4e) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Add CDN-friendly font bundles and `createBundle` helper
22
+
23
+ - Built font bundles now use `new URL(..., import.meta.url)` instead of bundler-specific import attributes, making them work natively in browsers and on CDN services like esm.sh and jsDelivr
24
+ - Glyph data JSON is inlined in the built output so no import attributes are needed at runtime
25
+ - Added `createBundle()` to `tegaki/core` and `tegaki/wc` for manually assembling a font bundle from fetched glyph data and a font URL
26
+
3
27
  ## 0.8.0
4
28
 
5
29
  ### Minor Changes
@@ -1,2 +1,2 @@
1
- import { A as TegakiSingletonEffectName, C as Stroke, D as TegakiEffects, E as TegakiEffectName, O as TegakiGlyphData, S as Point, T as TegakiEffectConfigs, _ as CSSLength, a as TimeControlProp, b as LineCap, c as TimelineEntry, d as computeTextLayout, f as ensureFontFace, g as BBox, h as resolveEffects, i as TimeControlMode, j as TimedPoint, k as TegakiMultiEffectName, l as computeTimeline, m as ResolvedEffect, n as TegakiEngine, o as Timeline, p as drawGlyph, r as TegakiEngineOptions, s as TimelineConfig, t as CreateElementFn, u as TextLayout, v as FontOutput, w as TegakiBundle, x as PathCommand, y as GlyphData } from "../index-YdGpXlqf.mjs";
2
- export { BBox, CSSLength, CreateElementFn, FontOutput, GlyphData, LineCap, PathCommand, Point, ResolvedEffect, Stroke, TegakiBundle, TegakiEffectConfigs, TegakiEffectName, TegakiEffects, TegakiEngine, TegakiEngineOptions, TegakiGlyphData, TegakiMultiEffectName, TegakiSingletonEffectName, TextLayout, TimeControlMode, TimeControlProp, TimedPoint, Timeline, TimelineConfig, TimelineEntry, computeTextLayout, computeTimeline, drawGlyph, ensureFontFace, resolveEffects };
1
+ import { A as TegakiMultiEffectName, C as Point, D as TegakiEffectName, E as TegakiEffectConfigs, M as TimedPoint, O as TegakiEffects, S as PathCommand, T as TegakiBundle, _ as BBox, a as TimeControlProp, b as GlyphData, c as TimelineConfig, d as TextLayout, f as computeTextLayout, g as resolveEffects, h as ResolvedEffect, i as TimeControlMode, j as TegakiSingletonEffectName, k as TegakiGlyphData, l as TimelineEntry, m as drawGlyph, n as TegakiEngine, o as createBundle, p as ensureFontFace, r as TegakiEngineOptions, s as Timeline, t as CreateElementFn, u as computeTimeline, v as CSSLength, w as Stroke, x as LineCap, y as FontOutput } from "../index-BdBLaKjh.mjs";
2
+ export { BBox, CSSLength, CreateElementFn, FontOutput, GlyphData, LineCap, PathCommand, Point, ResolvedEffect, Stroke, TegakiBundle, TegakiEffectConfigs, TegakiEffectName, TegakiEffects, TegakiEngine, TegakiEngineOptions, TegakiGlyphData, TegakiMultiEffectName, TegakiSingletonEffectName, TextLayout, TimeControlMode, TimeControlProp, TimedPoint, Timeline, TimelineConfig, TimelineEntry, computeTextLayout, computeTimeline, createBundle, drawGlyph, ensureFontFace, resolveEffects };
@@ -1,2 +1,2 @@
1
- import { a as drawGlyph, i as ensureFontFace, n as computeTimeline, r as computeTextLayout, s as resolveEffects, t as TegakiEngine } from "../core-Cw5jNWFa.mjs";
2
- export { TegakiEngine, computeTextLayout, computeTimeline, drawGlyph, ensureFontFace, resolveEffects };
1
+ import { a as ensureFontFace, c as resolveEffects, i as computeTextLayout, n as createBundle, o as drawGlyph, r as computeTimeline, t as TegakiEngine } from "../core-D68zOEne.mjs";
2
+ export { TegakiEngine, computeTextLayout, computeTimeline, createBundle, drawGlyph, ensureFontFace, resolveEffects };
@@ -494,6 +494,35 @@ function computeTimeline(text, font, config) {
494
494
  };
495
495
  }
496
496
  //#endregion
497
+ //#region src/core/createBundle.ts
498
+ /**
499
+ * Creates a {@link TegakiBundle} from its constituent parts.
500
+ *
501
+ * Useful when loading font data from a CDN or other source where the
502
+ * pre-built bundle modules aren't available:
503
+ *
504
+ * ```js
505
+ * const glyphData = await fetch('.../glyphData.json').then(r => r.json());
506
+ * const bundle = createBundle({
507
+ * family: 'Caveat',
508
+ * fontUrl: '.../caveat.ttf',
509
+ * glyphData,
510
+ * });
511
+ * ```
512
+ */
513
+ function createBundle({ family, fontUrl, glyphData, lineCap = "round", unitsPerEm = 1e3, ascender = 800, descender = -200 }) {
514
+ return {
515
+ family,
516
+ lineCap,
517
+ fontUrl,
518
+ fontFaceCSS: `@font-face { font-family: '${family}'; src: url(${fontUrl}); }`,
519
+ unitsPerEm,
520
+ ascender,
521
+ descender,
522
+ glyphData
523
+ };
524
+ }
525
+ //#endregion
497
526
  //#region src/lib/css-properties.ts
498
527
  const CSS_TIME = "--tegaki-time";
499
528
  const CSS_PROGRESS = "--tegaki-progress";
@@ -571,7 +600,7 @@ function buildRootProps(options) {
571
600
  const font = resolveBundle(options.font);
572
601
  const fontFamily = font?.family;
573
602
  const duration = text && font ? computeTimeline(text, font, options.timing).totalDuration : 0;
574
- const time = typeof options.time === "number" ? options.time : typeof options.time === "object" && options.time?.mode === "controlled" ? options.time.value : typeof options.time === "object" && options.time?.mode === "uncontrolled" ? options.time.initialTime ?? 0 : 0;
603
+ const time = typeof options.time === "number" ? options.time : typeof options.time === "object" && options.time?.mode === "controlled" ? options.time.unit === "progress" ? options.time.value * duration : options.time.value : typeof options.time === "object" && options.time?.mode === "uncontrolled" ? options.time.initialTime ?? 0 : 0;
575
604
  const progress = duration > 0 ? time / duration : 0;
576
605
  return {
577
606
  "data-tegaki": "root",
@@ -591,7 +620,10 @@ function buildChildren(options, h) {
591
620
  const text = options.text ?? "";
592
621
  const isCss = options.time === "css" || typeof options.time === "object" && options.time?.mode === "css";
593
622
  const showOverlay = options.showOverlay;
594
- return h("div", { style: { position: "relative" } }, h("span", {
623
+ return h("span", { style: {
624
+ display: "block",
625
+ position: "relative"
626
+ } }, h("span", {
595
627
  "data-tegaki": "sentinel",
596
628
  "aria-hidden": "true",
597
629
  style: {
@@ -621,9 +653,10 @@ function buildChildren(options, h) {
621
653
  display: "inline-block",
622
654
  padding: `${PAD_V_CSS} 0.2em`
623
655
  }
624
- }, text)), h("div", {
656
+ }, text)), h("span", {
625
657
  "data-tegaki": "overlay",
626
658
  style: {
659
+ display: "block",
627
660
  userSelect: "auto",
628
661
  whiteSpace: "pre-wrap",
629
662
  overflowWrap: "break-word",
@@ -701,6 +734,8 @@ var TegakiEngine = class TegakiEngine {
701
734
  _cssTime = 0;
702
735
  _playing = true;
703
736
  _smoothedBoost = 0;
737
+ _delayRemaining = 0;
738
+ _loopGapRemaining = 0;
704
739
  _lastTs = null;
705
740
  _rafId = 0;
706
741
  _prevCompleted = false;
@@ -752,7 +787,7 @@ var TegakiEngine = class TegakiEngine {
752
787
  get currentTime() {
753
788
  const tc = this._timeControl;
754
789
  if (tc.mode === "css") return this._cssTime;
755
- if (tc.mode === "controlled") return tc.value;
790
+ if (tc.mode === "controlled") return tc.unit === "progress" ? tc.value * this._timeline.totalDuration : tc.value;
756
791
  return this._internalTime;
757
792
  }
758
793
  get duration() {
@@ -780,6 +815,8 @@ var TegakiEngine = class TegakiEngine {
780
815
  seek(time) {
781
816
  if (this._timeControl.mode !== "uncontrolled") return;
782
817
  this._internalTime = Math.max(0, Math.min(time, this._timeline.totalDuration));
818
+ this._delayRemaining = 0;
819
+ this._loopGapRemaining = 0;
783
820
  this._checkCompletion();
784
821
  this._notifyTimeChange();
785
822
  this._render();
@@ -790,6 +827,8 @@ var TegakiEngine = class TegakiEngine {
790
827
  this._internalTime = 0;
791
828
  this._playing = true;
792
829
  this._prevCompleted = false;
830
+ this._delayRemaining = this._timeControl.delay ?? 0;
831
+ this._loopGapRemaining = 0;
793
832
  this._notifyTimeChange();
794
833
  this._evaluatePlayback();
795
834
  }
@@ -817,11 +856,19 @@ var TegakiEngine = class TegakiEngine {
817
856
  const newTc = resolveTimeControl(options.time);
818
857
  const oldTc = this._timeControl;
819
858
  const modeChanged = newTc.mode !== oldTc.mode;
820
- const controlledValueChanged = newTc.mode === "controlled" && oldTc.mode === "controlled" && newTc.value !== oldTc.value;
821
- const uncontrolledChanged = newTc.mode === "uncontrolled" && oldTc.mode === "uncontrolled" && (newTc.speed !== oldTc.speed || newTc.playing !== oldTc.playing || newTc.loop !== oldTc.loop || newTc.catchUp !== oldTc.catchUp);
859
+ const controlledValueChanged = newTc.mode === "controlled" && oldTc.mode === "controlled" && (newTc.value !== oldTc.value || newTc.unit !== oldTc.unit);
860
+ const uncontrolledChanged = newTc.mode === "uncontrolled" && oldTc.mode === "uncontrolled" && (newTc.speed !== oldTc.speed || newTc.playing !== oldTc.playing || newTc.loop !== oldTc.loop || newTc.delay !== oldTc.delay || newTc.loopGap !== oldTc.loopGap || newTc.catchUp !== oldTc.catchUp);
822
861
  if (modeChanged || controlledValueChanged || uncontrolledChanged) {
823
862
  this._timeControl = newTc;
824
- if (newTc.mode === "uncontrolled") this._playing = newTc.playing ?? true;
863
+ if (newTc.mode === "uncontrolled") {
864
+ this._playing = newTc.playing ?? true;
865
+ const oldDelay = oldTc.mode === "uncontrolled" ? oldTc.delay ?? 0 : 0;
866
+ const newDelay = newTc.delay ?? 0;
867
+ if (modeChanged || oldDelay !== newDelay) {
868
+ this._delayRemaining = newDelay;
869
+ this._loopGapRemaining = 0;
870
+ }
871
+ }
825
872
  dirtyPlayback = true;
826
873
  dirtyRender = true;
827
874
  this._updateSentinelTransition();
@@ -878,7 +925,7 @@ var TegakiEngine = class TegakiEngine {
878
925
  _updateDom() {
879
926
  this._rootEl.style.fontFamily = this._font?.family ?? "";
880
927
  this._updateCssProperties();
881
- this._overlayEl.textContent = this._text;
928
+ if (this._overlayEl.textContent !== this._text) this._overlayEl.textContent = this._text;
882
929
  this._canvasFallbackEl.textContent = this._text;
883
930
  }
884
931
  _updateCssProperties() {
@@ -1032,6 +1079,24 @@ var TegakiEngine = class TegakiEngine {
1032
1079
  this._rafId = requestAnimationFrame(this._tick);
1033
1080
  return;
1034
1081
  }
1082
+ if (this._delayRemaining > 0) {
1083
+ this._delayRemaining = Math.max(0, this._delayRemaining - dtSec);
1084
+ this._rafId = requestAnimationFrame(this._tick);
1085
+ return;
1086
+ }
1087
+ if (this._loopGapRemaining > 0) {
1088
+ this._loopGapRemaining = Math.max(0, this._loopGapRemaining - dtSec);
1089
+ if (this._loopGapRemaining <= 0) {
1090
+ this._internalTime = 0;
1091
+ this._prevCompleted = false;
1092
+ this._smoothedBoost = 0;
1093
+ }
1094
+ this._notifyTimeChange();
1095
+ this._render();
1096
+ this._updateCssProperties();
1097
+ this._rafId = requestAnimationFrame(this._tick);
1098
+ return;
1099
+ }
1035
1100
  let effectiveSpeed = speed;
1036
1101
  if (catchUp > 0) {
1037
1102
  const remaining = Math.max(0, totalDur - this._internalTime);
@@ -1044,7 +1109,13 @@ var TegakiEngine = class TegakiEngine {
1044
1109
  }
1045
1110
  let next = this._internalTime + dtSec * effectiveSpeed;
1046
1111
  if (next >= totalDur) {
1047
- next = loop ? next % totalDur : totalDur;
1112
+ if (loop) {
1113
+ const loopGap = tc.loopGap ?? 0;
1114
+ if (loopGap > 0) {
1115
+ next = totalDur;
1116
+ this._loopGapRemaining = loopGap;
1117
+ } else next = next % totalDur;
1118
+ } else next = totalDur;
1048
1119
  this._smoothedBoost = 0;
1049
1120
  }
1050
1121
  this._internalTime = next;
@@ -1072,9 +1143,8 @@ var TegakiEngine = class TegakiEngine {
1072
1143
  const fontSize = this._fontSize;
1073
1144
  if (!font?.glyphData || !layout || !fontSize) return;
1074
1145
  const dpr = window.devicePixelRatio || 1;
1075
- const canvasRect = canvas.getBoundingClientRect();
1076
- const w = canvasRect.width;
1077
- const h = canvasRect.height;
1146
+ const w = canvas.offsetWidth;
1147
+ const h = canvas.offsetHeight;
1078
1148
  if (canvas.width !== Math.round(w * dpr) || canvas.height !== Math.round(h * dpr)) {
1079
1149
  canvas.width = Math.round(w * dpr);
1080
1150
  canvas.height = Math.round(h * dpr);
@@ -1123,6 +1193,6 @@ var TegakiEngine = class TegakiEngine {
1123
1193
  }
1124
1194
  };
1125
1195
  //#endregion
1126
- export { drawGlyph as a, ensureFontFace as i, computeTimeline as n, coerceToString as o, computeTextLayout as r, resolveEffects as s, TegakiEngine as t };
1196
+ export { ensureFontFace as a, resolveEffects as c, computeTextLayout as i, createBundle as n, drawGlyph as o, computeTimeline as r, coerceToString as s, TegakiEngine as t };
1127
1197
 
1128
- //# sourceMappingURL=core-Cw5jNWFa.mjs.map
1198
+ //# sourceMappingURL=core-D68zOEne.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-D68zOEne.mjs","names":[],"sources":["../src/lib/effects.ts","../src/lib/utils.ts","../src/lib/drawGlyph.ts","../src/lib/font.ts","../src/lib/textLayout.ts","../src/lib/timeline.ts","../src/core/createBundle.ts","../src/lib/css-properties.ts","../src/lib/drawFallbackGlyph.ts","../src/core/engine.ts"],"sourcesContent":["import type { TegakiEffectConfigs, TegakiEffectName } from '../types.ts';\n\nexport interface ResolvedEffect<K extends TegakiEffectName = TegakiEffectName> {\n effect: K;\n order: number;\n config: TegakiEffectConfigs[K];\n}\n\nconst defaultEffects: Record<string, any> = { pressureWidth: true };\nconst knownEffects: Set<string> = new Set(['glow', 'wobble', 'pressureWidth', 'taper', 'gradient']);\n\n/**\n * Normalizes an effects record into a sorted array of resolved effects.\n * Known keys infer the effect name; custom keys read it from the `effect` field.\n * Boolean `true` becomes an empty config. `false`/absent entries are skipped.\n */\nexport function resolveEffects(effects: Record<string, any> | undefined): ResolvedEffect[] {\n const merged = { ...defaultEffects, ...effects };\n\n const result: ResolvedEffect[] = [];\n\n for (const [key, value] of Object.entries(merged)) {\n if (value === false || value == null) continue;\n\n let effectName: TegakiEffectName;\n let config: Record<string, any>;\n let order: number;\n\n if (value === true) {\n effectName = (knownEffects.has(key) ? key : undefined) as TegakiEffectName;\n if (!effectName) continue;\n config = {};\n order = 0;\n } else {\n if (value.enabled === false) continue;\n effectName = value.effect ?? (knownEffects.has(key) ? key : undefined);\n if (!effectName) continue;\n const { effect: _, order: o, enabled: __, ...rest } = value;\n config = rest;\n order = o ?? 0;\n }\n\n result.push({ effect: effectName, order, config });\n }\n\n result.sort((a, b) => a.order - b.order);\n return result;\n}\n\n/** Check if a specific effect is active. */\nexport function findEffect<K extends TegakiEffectName>(effects: ResolvedEffect[], name: K): ResolvedEffect<K> | undefined {\n return effects.find((e) => e.effect === name) as ResolvedEffect<K> | undefined;\n}\n\n/** Get all instances of a specific effect (for duplicates). */\nexport function findEffects<K extends TegakiEffectName>(effects: ResolvedEffect[], name: K): ResolvedEffect<K>[] {\n return effects.filter((e) => e.effect === name) as ResolvedEffect<K>[];\n}\n","import type { CSSLength } from '../types.ts';\n\nconst segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n\n/** Resolve a CSSLength to pixels. Plain numbers are px, `\"Nem\"` is N * fontSize. */\nexport function resolveCSSLength(value: CSSLength, fontSize: number): number {\n if (typeof value === 'number') return value;\n return parseFloat(value) * fontSize;\n}\n\nexport function graphemes(text: string): string[] {\n return Array.from(segmenter.segment(text), (s) => s.segment);\n}\nexport type Coercible = string | number | boolean | null | undefined | readonly Coercible[];\n\nexport function coerceToString(value: unknown): string {\n if (value == null || typeof value === 'boolean') return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'bigint') return String(value);\n if (Array.isArray(value)) return value.map(coerceToString).join('');\n return '';\n}\n","import type { LineCap, TegakiGlyphData } from '../types.ts';\nimport { findEffect, findEffects, type ResolvedEffect } from './effects.ts';\nimport { resolveCSSLength } from './utils.ts';\n\ninterface GlyphPosition {\n /** X offset in CSS pixels */\n x: number;\n /** Y offset in CSS pixels (top of em square) */\n y: number;\n /** Font size in CSS pixels */\n fontSize: number;\n /** Units per em from the font */\n unitsPerEm: number;\n /** Font ascender in font units */\n ascender: number;\n /** Font descender in font units (negative) */\n descender: number;\n}\n\n// --- Color helpers ---\n\nfunction parseColor(color: string): [number, number, number, number] {\n const h = color.replace('#', '');\n if (h.length === 3) {\n return [parseInt(h[0]! + h[0]!, 16), parseInt(h[1]! + h[1]!, 16), parseInt(h[2]! + h[2]!, 16), 1];\n }\n if (h.length === 4) {\n return [parseInt(h[0]! + h[0]!, 16), parseInt(h[1]! + h[1]!, 16), parseInt(h[2]! + h[2]!, 16), parseInt(h[3]! + h[3]!, 16) / 255];\n }\n if (h.length === 8) {\n return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16), parseInt(h.slice(6, 8), 16) / 255];\n }\n return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16), 1];\n}\n\nfunction lerpColor(a: [number, number, number, number], b: [number, number, number, number], t: number): string {\n const r = Math.round(a[0] + (b[0] - a[0]) * t);\n const g = Math.round(a[1] + (b[1] - a[1]) * t);\n const bl = Math.round(a[2] + (b[2] - a[2]) * t);\n const al = a[3] + (b[3] - a[3]) * t;\n if (al >= 1) return `rgb(${r},${g},${bl})`;\n return `rgba(${r},${g},${bl},${al.toFixed(3)})`;\n}\n\nfunction gradientColor(progress: number, colors: string[], seed: number): string {\n if (colors.length === 0) return '#000';\n if (colors.length === 1) return colors[0]!;\n const t = (((progress + seed * 0.1) % 1) + 1) % 1;\n const scaledT = t * (colors.length - 1);\n const i = Math.min(Math.floor(scaledT), colors.length - 2);\n const frac = scaledT - i;\n return lerpColor(parseColor(colors[i]!), parseColor(colors[i + 1]!), frac);\n}\n\nfunction rainbowColor(progress: number, saturation: number, lightness: number, seed: number): string {\n const hue = (progress * 360 + seed * 137.5) % 360;\n return `hsl(${hue}, ${saturation}%, ${lightness}%)`;\n}\n\n// --- Noise helper for wobble ---\n\nfunction hash(x: number): number {\n let h = (x * 2654435761) | 0;\n h = ((h >>> 16) ^ h) * 0x45d9f3b;\n h = ((h >>> 16) ^ h) * 0x45d9f3b;\n h = (h >>> 16) ^ h;\n return (h & 0x7fffffff) / 0x7fffffff; // 0-1\n}\n\nfunction noise1d(x: number, seed: number): number {\n const i = Math.floor(x);\n const f = x - i;\n const t = f * f * (3 - 2 * f); // smoothstep\n return hash(i + seed * 7919) * (1 - t) + hash(i + 1 + seed * 7919) * t;\n}\n\n/**\n * Draw a single glyph's strokes onto a canvas context, animated up to `localTime`.\n * `localTime` is seconds relative to this glyph's start (0 = glyph begins).\n */\nexport function drawGlyph(\n ctx: CanvasRenderingContext2D,\n glyph: TegakiGlyphData,\n pos: GlyphPosition,\n localTime: number,\n lineCap: LineCap,\n color: string,\n effects: ResolvedEffect[] = [],\n seed = 0,\n segmentSize?: number,\n) {\n const scale = pos.fontSize / pos.unitsPerEm;\n const ox = pos.x;\n const oy = pos.y;\n\n const glowEffects = findEffects(effects, 'glow');\n const wobbleEffect = findEffect(effects, 'wobble');\n const pressureEffect = findEffect(effects, 'pressureWidth');\n const taperEffect = findEffect(effects, 'taper');\n const gradientEffect = findEffect(effects, 'gradient');\n\n // Pressure params (0 = uniform avg width, 1 = fully per-point width)\n const pressureAmount = pressureEffect ? Math.max(0, Math.min(pressureEffect.config.strength ?? 1, 1)) : 0;\n\n // Wobble params\n const wobbleAmplitude = wobbleEffect ? (wobbleEffect.config.amplitude ?? 1.5) : 0;\n const wobbleFrequency = wobbleEffect ? (wobbleEffect.config.frequency ?? 8) : 0;\n const wobbleMode = wobbleEffect?.config.mode ?? 'sine';\n\n // Taper params\n const taperStart = taperEffect ? Math.max(0, Math.min(taperEffect.config.startLength ?? 0.15, 1)) : 0;\n const taperEnd = taperEffect ? Math.max(0, Math.min(taperEffect.config.endLength ?? 0.15, 1)) : 0;\n\n // Gradient params\n const gradientColors = gradientEffect?.config.colors;\n const isRainbow = gradientColors === 'rainbow';\n const gradientColorStops = Array.isArray(gradientColors) ? gradientColors : undefined;\n const gradientSaturation = gradientEffect?.config.saturation ?? 80;\n const gradientLightness = gradientEffect?.config.lightness ?? 55;\n\n // Helper: apply wobble offset to a point in font units\n const wobbleX = (x: number, y: number, idx: number) => {\n if (!wobbleEffect) return x;\n if (wobbleMode === 'noise') {\n return x + wobbleAmplitude * (noise1d(y * 0.1 + idx * 0.7, seed) * 2 - 1);\n }\n return x + wobbleAmplitude * Math.sin(wobbleFrequency * (y * 0.01 + idx * 0.7) + seed);\n };\n const wobbleY = (x: number, y: number, idx: number) => {\n if (!wobbleEffect) return y;\n if (wobbleMode === 'noise') {\n return y + wobbleAmplitude * (noise1d(x * 0.1 + idx * 0.5, seed * 1.3 + 1000) * 2 - 1);\n }\n return y + wobbleAmplitude * Math.cos(wobbleFrequency * (x * 0.01 + idx * 0.5) + seed * 1.3);\n };\n\n // Helper: convert font-unit point to pixel\n const px = (x: number) => ox + x * scale;\n const py = (y: number) => oy + (y + pos.ascender) * scale;\n\n // Helper: get color for a given stroke progress\n const colorAt = (progress: number): string => {\n if (isRainbow) return rainbowColor(progress, gradientSaturation, gradientLightness, seed);\n if (gradientColorStops) return gradientColor(progress, gradientColorStops, seed);\n return color;\n };\n const hasGradient = !!gradientEffect;\n\n // Helper: taper multiplier (0-1) for a given stroke progress\n const taperMultiplier = (progress: number): number => {\n let m = 1;\n if (taperStart > 0 && progress < taperStart) m = Math.min(m, progress / taperStart);\n if (taperEnd > 0 && progress > 1 - taperEnd) m = Math.min(m, (1 - progress) / taperEnd);\n return m;\n };\n\n for (const stroke of glyph.s) {\n if (localTime < stroke.d) continue;\n const elapsed = localTime - stroke.d;\n const progress = Math.min(elapsed / stroke.a, 1);\n\n const pts = stroke.p;\n if (pts.length === 0) continue;\n\n const avgWidth = pts.reduce((s, p) => s + p[2], 0) / pts.length;\n const baseLineWidth = Math.max(avgWidth, 0.5) * scale;\n\n // --- Single-point dot ---\n if (pts.length === 1) {\n if (progress <= 0) continue;\n const p = pts[0]!;\n const dotX = px(wobbleX(p[0], p[1], 0));\n const dotY = py(wobbleY(p[0], p[1], 0));\n const perPointDot = Math.max(p[2], 0.5) * scale;\n let dotWidth = baseLineWidth + (perPointDot - baseLineWidth) * pressureAmount;\n dotWidth *= taperMultiplier(0.5);\n\n // Glow passes for dots\n for (const glow of glowEffects) {\n ctx.save();\n ctx.shadowBlur = resolveCSSLength(glow.config.radius ?? 8, pos.fontSize);\n ctx.shadowColor = glow.config.color ?? color;\n ctx.shadowOffsetX = (glow.config.offsetX ?? 0) * scale;\n ctx.shadowOffsetY = (glow.config.offsetY ?? 0) * scale;\n ctx.fillStyle = glow.config.color ?? color;\n ctx.beginPath();\n if (lineCap === 'round') {\n ctx.arc(dotX, dotY, dotWidth / 2, 0, Math.PI * 2);\n } else {\n ctx.rect(dotX - dotWidth / 2, dotY - dotWidth / 2, dotWidth, dotWidth);\n }\n ctx.fill();\n ctx.restore();\n }\n\n // Main dot\n ctx.fillStyle = colorAt(0);\n ctx.beginPath();\n if (lineCap === 'round') {\n ctx.arc(dotX, dotY, dotWidth / 2, 0, Math.PI * 2);\n ctx.fill();\n } else {\n ctx.fillRect(dotX - dotWidth / 2, dotY - dotWidth / 2, dotWidth, dotWidth);\n }\n continue;\n }\n\n // --- Compute total path length ---\n let totalLen = 0;\n for (let j = 1; j < pts.length; j++) {\n const dx = pts[j]![0] - pts[j - 1]![0];\n const dy = pts[j]![1] - pts[j - 1]![1];\n totalLen += Math.sqrt(dx * dx + dy * dy);\n }\n\n const drawLen = totalLen * progress;\n if (drawLen <= 0) continue;\n\n // --- Collect drawable segments ---\n const segments: {\n x0: number;\n y0: number;\n x1: number;\n y1: number;\n width0: number;\n width1: number;\n segProgress: number;\n }[] = [];\n\n let accumulated = 0;\n for (let j = 1; j < pts.length; j++) {\n const prev = pts[j - 1]!;\n const cur = pts[j]!;\n const dx = cur[0] - prev[0];\n const dy = cur[1] - prev[1];\n const segLen = Math.sqrt(dx * dx + dy * dy);\n\n if (accumulated + segLen <= drawLen) {\n segments.push({\n x0: px(wobbleX(prev[0], prev[1], j - 1)),\n y0: py(wobbleY(prev[0], prev[1], j - 1)),\n x1: px(wobbleX(cur[0], cur[1], j)),\n y1: py(wobbleY(cur[0], cur[1], j)),\n width0: prev[2],\n width1: cur[2],\n segProgress: (accumulated + segLen / 2) / totalLen,\n });\n accumulated += segLen;\n } else {\n const remaining = drawLen - accumulated;\n const frac = segLen > 0 ? remaining / segLen : 0;\n const ix = prev[0] + dx * frac;\n const iy = prev[1] + dy * frac;\n const iw = prev[2] + (cur[2] - prev[2]) * frac;\n segments.push({\n x0: px(wobbleX(prev[0], prev[1], j - 1)),\n y0: py(wobbleY(prev[0], prev[1], j - 1)),\n x1: px(wobbleX(ix, iy, j)),\n y1: py(wobbleY(ix, iy, j)),\n width0: prev[2],\n width1: iw,\n segProgress: (accumulated + remaining / 2) / totalLen,\n });\n break;\n }\n }\n\n if (segments.length === 0) continue;\n\n // Keep coarse segments for glow (shadowBlur is expensive per draw call)\n const coarseSegments = segments.slice();\n\n // --- Subdivide long segments for smooth effect transitions ---\n const effectsNeedSubdivision = pressureAmount > 0 || hasGradient || !!wobbleEffect || !!taperEffect;\n const resolvedSegmentSize = segmentSize ?? (effectsNeedSubdivision ? 2 : undefined);\n if (resolvedSegmentSize != null) {\n const maxSegLen = resolvedSegmentSize * scale;\n const subdivided: typeof segments = [];\n for (const seg of segments) {\n const dx = seg.x1 - seg.x0;\n const dy = seg.y1 - seg.y0;\n const len = Math.sqrt(dx * dx + dy * dy);\n const count = Math.max(1, Math.ceil(len / maxSegLen));\n for (let k = 0; k < count; k++) {\n const t0 = k / count;\n const t1 = (k + 1) / count;\n subdivided.push({\n x0: seg.x0 + dx * t0,\n y0: seg.y0 + dy * t0,\n x1: seg.x0 + dx * t1,\n y1: seg.y0 + dy * t1,\n width0: seg.width0 + (seg.width1 - seg.width0) * t0,\n width1: seg.width0 + (seg.width1 - seg.width0) * t1,\n segProgress: seg.segProgress,\n });\n }\n }\n for (let k = 0; k < subdivided.length; k++) {\n subdivided[k]!.segProgress = subdivided.length > 1 ? k / (subdivided.length - 1) : 0;\n }\n segments.length = 0;\n segments.push(...subdivided);\n }\n\n // Helper: compute segment line width with pressure and taper\n const segWidth = (seg: (typeof segments)[0]) => {\n const perPoint = ((seg.width0 + seg.width1) / 2) * scale;\n const w = Math.max(baseLineWidth + (perPoint - baseLineWidth) * pressureAmount, 0.5 * scale);\n return w * taperMultiplier(seg.segProgress);\n };\n\n const needsPerSegment = pressureAmount > 0 || taperEffect;\n\n const drawStrokePath = () => {\n if (needsPerSegment) {\n for (const seg of segments) {\n ctx.lineWidth = segWidth(seg);\n ctx.beginPath();\n ctx.moveTo(seg.x0, seg.y0);\n ctx.lineTo(seg.x1, seg.y1);\n ctx.stroke();\n }\n } else {\n ctx.lineWidth = baseLineWidth;\n ctx.beginPath();\n ctx.moveTo(segments[0]!.x0, segments[0]!.y0);\n for (const seg of segments) {\n ctx.lineTo(seg.x1, seg.y1);\n }\n ctx.stroke();\n }\n };\n\n const drawGradientPath = () => {\n for (const seg of segments) {\n ctx.strokeStyle = colorAt(seg.segProgress);\n if (needsPerSegment) ctx.lineWidth = segWidth(seg);\n ctx.beginPath();\n ctx.moveTo(seg.x0, seg.y0);\n ctx.lineTo(seg.x1, seg.y1);\n ctx.stroke();\n }\n };\n\n ctx.lineCap = lineCap;\n ctx.lineJoin = 'round';\n\n // --- Glow passes (use coarse segments to avoid expensive per-subsegment shadowBlur) ---\n for (const glow of glowEffects) {\n ctx.save();\n ctx.shadowBlur = resolveCSSLength(glow.config.radius ?? 8, pos.fontSize);\n ctx.shadowColor = glow.config.color ?? color;\n ctx.shadowOffsetX = (glow.config.offsetX ?? 0) * scale;\n ctx.shadowOffsetY = (glow.config.offsetY ?? 0) * scale;\n ctx.strokeStyle = glow.config.color ?? color;\n ctx.lineWidth = baseLineWidth;\n ctx.beginPath();\n ctx.moveTo(coarseSegments[0]!.x0, coarseSegments[0]!.y0);\n for (const seg of coarseSegments) {\n ctx.lineTo(seg.x1, seg.y1);\n }\n ctx.stroke();\n ctx.restore();\n }\n\n // --- Main stroke ---\n if (hasGradient) {\n drawGradientPath();\n } else {\n ctx.strokeStyle = color;\n drawStrokePath();\n }\n }\n}\n","import type { TegakiBundle } from '../types.ts';\n\nconst fontFaceCache = new Map<string, Promise<void>>();\n\n/**\n * Ensures the bundle's font face is loaded and available for rendering.\n * Resolves immediately if the font is already loaded.\n */\nexport async function ensureFontFace(bundle: TegakiBundle): Promise<void> {\n await ensureFont(bundle.family, bundle.fontUrl);\n}\n\nexport function ensureFont(family: string, url: string): Promise<void> | null {\n if (typeof document === 'undefined') return Promise.resolve();\n for (const face of document.fonts) {\n if (face.family === family) {\n if (face.status === 'loaded') return null;\n if (face.status === 'loading') return face.loaded.then(() => {});\n }\n }\n let cached = fontFaceCache.get(url);\n if (!cached) {\n cached = new FontFace(family, `url(${url})`, { featureSettings: \"'calt' 0, 'liga' 0\" }).load().then((loaded) => {\n document.fonts.add(loaded);\n });\n fontFaceCache.set(url, cached);\n }\n return cached;\n}\n","import { layoutWithLines, prepareWithSegments } from '@chenglou/pretext';\nimport { graphemes } from './utils.ts';\n\nexport interface TextLayout {\n /** Character indices per line */\n lines: number[][];\n /** Width in em per character index */\n charWidths: number[];\n /** Kerning adjustment in em between character at index i and i+1 */\n kernings: number[];\n /** Intrinsic (single-line) width in em */\n intrinsicWidth: number;\n}\n\nexport function computeTextLayout(text: string, fontFamily: string, fontSize: number, lineHeight: number, maxWidth: number): TextLayout {\n const fontStr = `${fontSize}px ${fontFamily}`;\n const chars = graphemes(text);\n\n // Measure unique character widths\n const widthCache = new Map<string, number>();\n const charWidths: number[] = [];\n for (const char of chars) {\n let w = widthCache.get(char);\n if (w === undefined) {\n if (char === '\\n') {\n w = 0;\n } else {\n const p = prepareWithSegments(char, fontStr, { whiteSpace: 'pre-wrap' });\n const r = layoutWithLines(p, Infinity, lineHeight);\n w = r.lines.length > 0 ? r.lines[0]!.width / fontSize : 0;\n }\n widthCache.set(char, w);\n }\n charWidths.push(w);\n }\n\n // Compute intrinsic width (single-line, no wrapping)\n const prepared = prepareWithSegments(text, fontStr, { whiteSpace: 'pre-wrap' });\n const singleLineResult = layoutWithLines(prepared, Infinity, lineHeight);\n const intrinsicWidth = Math.max(0, ...singleLineResult.lines.map((l) => l.width)) / fontSize;\n\n // Line breaking at actual available width\n const result = layoutWithLines(prepared, maxWidth, lineHeight);\n\n // Map line texts back to character indices (grapheme-based)\n // Build a mapping from UTF-16 offset to grapheme index\n const utf16ToCodePoint: number[] = [];\n for (let ci = 0; ci < chars.length; ci++) {\n for (let j = 0; j < chars[ci]!.length; j++) {\n utf16ToCodePoint.push(ci);\n }\n }\n\n const lines: number[][] = [];\n let utf16Offset = 0;\n for (const line of result.lines) {\n const indices: number[] = [];\n const seen = new Set<number>();\n for (let i = 0; i < line.text.length; i++) {\n const cpIdx = utf16ToCodePoint[utf16Offset + i]!;\n if (!seen.has(cpIdx)) {\n seen.add(cpIdx);\n indices.push(cpIdx);\n }\n }\n utf16Offset += line.text.length;\n // Consume the newline that caused this line break\n if (utf16Offset < text.length && text[utf16Offset] === '\\n') {\n const cpIdx = utf16ToCodePoint[utf16Offset]!;\n indices.push(cpIdx);\n utf16Offset++;\n }\n lines.push(indices);\n }\n\n // Any remaining characters (shouldn't happen, but safety)\n if (utf16Offset < text.length) {\n const indices: number[] = [];\n const seen = new Set<number>();\n for (let i = utf16Offset; i < text.length; i++) {\n const cpIdx = utf16ToCodePoint[i]!;\n if (!seen.has(cpIdx)) {\n seen.add(cpIdx);\n indices.push(cpIdx);\n }\n }\n lines.push(indices);\n }\n\n // Measure kerning between adjacent character pairs\n const kernings: number[] = [];\n const pairCache = new Map<string, number>();\n for (let i = 0; i < chars.length - 1; i++) {\n const a = chars[i]!;\n const b = chars[i + 1]!;\n if (a === '\\n' || b === '\\n') {\n kernings.push(0);\n continue;\n }\n const pair = `${a}${b}`;\n let k = pairCache.get(pair);\n if (k === undefined) {\n const p = prepareWithSegments(pair, fontStr, { whiteSpace: 'pre-wrap' });\n const r = layoutWithLines(p, Infinity, lineHeight);\n const pairWidth = r.lines.length > 0 ? r.lines[0]!.width / fontSize : 0;\n k = pairWidth - (widthCache.get(a) ?? 0) - (widthCache.get(b) ?? 0);\n if (Math.abs(k) < 0.001) k = 0;\n pairCache.set(pair, k);\n }\n kernings.push(k);\n }\n\n return { lines, charWidths, kernings, intrinsicWidth };\n}\n","import type { TegakiBundle } from '../types.ts';\nimport { graphemes } from './utils.ts';\n\nexport interface TimelineConfig {\n /** Pause between glyphs (seconds). Default: `0.1` */\n glyphGap?: number;\n /** Pause after a space character (seconds). Default: `0.15` */\n wordGap?: number;\n /** Pause after a newline / line break (seconds). Default: `0.3` */\n lineGap?: number;\n /** Duration for characters without glyph data (seconds). Default: `0.2` */\n unknownDuration?: number;\n}\n\nconst DEFAULTS: Required<TimelineConfig> = {\n glyphGap: 0.1,\n wordGap: 0.15,\n lineGap: 0.3,\n unknownDuration: 0.2,\n};\n\nexport interface TimelineEntry {\n char: string;\n offset: number;\n duration: number;\n hasGlyph: boolean;\n}\n\nexport interface Timeline {\n entries: TimelineEntry[];\n totalDuration: number;\n}\n\nexport function computeTimeline(text: string, font: TegakiBundle, config?: TimelineConfig): Timeline {\n const glyphGap = config?.glyphGap ?? DEFAULTS.glyphGap;\n const wordGap = config?.wordGap ?? DEFAULTS.wordGap;\n const lineGap = config?.lineGap ?? DEFAULTS.lineGap;\n const unknownDuration = config?.unknownDuration ?? DEFAULTS.unknownDuration;\n\n const chars = graphemes(text);\n const entries: TimelineEntry[] = [];\n let offset = 0;\n for (const char of chars) {\n const glyph = font.glyphData[char];\n const hasGlyph = !!glyph;\n const duration = hasGlyph ? (glyph.t ?? 1) : unknownDuration;\n entries.push({ char, offset, duration, hasGlyph });\n offset += duration;\n\n // Gap after this character\n if (char === '\\n') {\n offset += lineGap;\n } else if (char === ' ') {\n offset += wordGap;\n } else {\n offset += glyphGap;\n }\n }\n // Remove trailing gap\n if (entries.length > 0) {\n const lastChar = chars[chars.length - 1]!;\n const trailingGap = lastChar === '\\n' ? lineGap : lastChar === ' ' ? wordGap : glyphGap;\n offset -= trailingGap;\n }\n return { entries, totalDuration: Math.max(0, offset) };\n}\n","import type { LineCap, TegakiBundle, TegakiGlyphData } from '../types.ts';\n\n/**\n * Creates a {@link TegakiBundle} from its constituent parts.\n *\n * Useful when loading font data from a CDN or other source where the\n * pre-built bundle modules aren't available:\n *\n * ```js\n * const glyphData = await fetch('.../glyphData.json').then(r => r.json());\n * const bundle = createBundle({\n * family: 'Caveat',\n * fontUrl: '.../caveat.ttf',\n * glyphData,\n * });\n * ```\n */\nexport function createBundle({\n family,\n fontUrl,\n glyphData,\n lineCap = 'round',\n unitsPerEm = 1000,\n ascender = 800,\n descender = -200,\n}: {\n family: string;\n fontUrl: string;\n glyphData: Record<string, TegakiGlyphData>;\n lineCap?: LineCap;\n unitsPerEm?: number;\n ascender?: number;\n descender?: number;\n}): TegakiBundle {\n return {\n family,\n lineCap,\n fontUrl,\n fontFaceCSS: `@font-face { font-family: '${family}'; src: url(${fontUrl}); }`,\n unitsPerEm,\n ascender,\n descender,\n glyphData,\n };\n}\n","export const CSS_TIME = '--tegaki-time';\nexport const CSS_PROGRESS = '--tegaki-progress';\nexport const CSS_DURATION = '--tegaki-duration';\n\nexport const PADDING_H_EM = 0.2;\nexport const MIN_LINE_HEIGHT_EM = 1.8;\nexport const MIN_PADDING_V_EM = 0.2;\n\n// Register custom properties so they are animatable (typed as <number>).\n// Deferred to first use to avoid running at import time during SSR.\nlet cssPropertiesRegistered = false;\nexport function registerCssProperties() {\n if (cssPropertiesRegistered) return;\n cssPropertiesRegistered = true;\n if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {\n for (const prop of [CSS_TIME, CSS_PROGRESS, CSS_DURATION]) {\n try {\n CSS.registerProperty({ name: prop, syntax: '<number>', inherits: true, initialValue: '0' });\n } catch {\n // Already registered — ignore.\n }\n }\n }\n}\n","import { findEffect, findEffects, type ResolvedEffect } from './effects.ts';\nimport { resolveCSSLength } from './utils.ts';\n\n/**\n * Draw a fallback glyph (plain text) with applicable effects (glow, gradient, wobble).\n */\nexport function drawFallbackGlyph(\n ctx: CanvasRenderingContext2D,\n char: string,\n x: number,\n baseline: number,\n fontSize: number,\n fontFamily: string,\n color: string,\n effects: ResolvedEffect[] = [],\n seed = 0,\n) {\n const glowEffects = findEffects(effects, 'glow');\n const wobbleEffect = findEffect(effects, 'wobble');\n const gradientEffect = findEffect(effects, 'gradient');\n\n // Wobble offsets\n let dx = 0;\n let dy = 0;\n if (wobbleEffect) {\n const amplitude = (wobbleEffect.config.amplitude ?? 1.5) * (fontSize / 100);\n const frequency = wobbleEffect.config.frequency ?? 8;\n dx = amplitude * Math.sin(frequency * (baseline * 0.01) + seed);\n dy = amplitude * Math.cos(frequency * (x * 0.01) + seed * 1.3);\n }\n\n const drawX = x + dx;\n const drawY = baseline + dy;\n\n // Gradient / rainbow color\n let fillColor = color;\n if (gradientEffect) {\n const colors = gradientEffect.config.colors;\n if (colors === 'rainbow') {\n const saturation = gradientEffect.config.saturation ?? 80;\n const lightness = gradientEffect.config.lightness ?? 55;\n const hue = (seed * 137.5) % 360;\n fillColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;\n } else if (Array.isArray(colors) && colors.length > 0) {\n fillColor = colors[Math.floor(seed) % colors.length]!;\n }\n }\n\n ctx.save();\n ctx.font = `${fontSize}px ${fontFamily}`;\n ctx.textBaseline = 'alphabetic';\n\n // Glow passes\n for (const glow of glowEffects) {\n ctx.save();\n ctx.shadowBlur = resolveCSSLength(glow.config.radius ?? 8, fontSize);\n ctx.shadowColor = glow.config.color ?? color;\n ctx.shadowOffsetX = glow.config.offsetX ?? 0;\n ctx.shadowOffsetY = glow.config.offsetY ?? 0;\n ctx.fillStyle = glow.config.color ?? color;\n ctx.fillText(char, drawX, drawY);\n ctx.restore();\n }\n\n // Main text\n ctx.fillStyle = fillColor;\n ctx.fillText(char, drawX, drawY);\n\n ctx.restore();\n}\n","import {\n CSS_DURATION,\n CSS_PROGRESS,\n CSS_TIME,\n MIN_LINE_HEIGHT_EM,\n MIN_PADDING_V_EM,\n PADDING_H_EM,\n registerCssProperties,\n} from '../lib/css-properties.ts';\nimport { drawFallbackGlyph } from '../lib/drawFallbackGlyph.ts';\nimport { drawGlyph } from '../lib/drawGlyph.ts';\nimport { type ResolvedEffect, resolveEffects } from '../lib/effects.ts';\nimport { ensureFont } from '../lib/font.ts';\nimport type { TextLayout } from '../lib/textLayout.ts';\nimport { computeTextLayout } from '../lib/textLayout.ts';\nimport type { Timeline, TimelineConfig, TimelineEntry } from '../lib/timeline.ts';\nimport { computeTimeline } from '../lib/timeline.ts';\nimport { graphemes } from '../lib/utils.ts';\nimport type { TegakiBundle, TegakiEffects } from '../types.ts';\n\n// ---------------------------------------------------------------------------\n// Time control types (shared with adapters)\n// ---------------------------------------------------------------------------\n\nexport type TimeControlMode = {\n controlled: {\n mode: 'controlled';\n /** Current time in seconds (default), or progress 0–1 when `unit` is `'progress'`. */\n value: number;\n /** Interpret `value` as seconds (default) or as a 0–1 progress ratio. */\n unit?: 'seconds' | 'progress';\n };\n uncontrolled: {\n mode: 'uncontrolled';\n /** Initial time in seconds. Default: `0` */\n initialTime?: number;\n /** Playback speed multiplier. Default: `1` */\n speed?: number;\n /** Whether animation is playing. Default: `true` */\n playing?: boolean;\n /** Loop animation when it reaches the end. Default: `false` */\n loop?: boolean;\n /**\n * Delay before the animation starts (seconds). Applied once on\n * initialization and again on {@link TegakiEngine.restart}. Default: `0`\n */\n delay?: number;\n /**\n * Pause between loop iterations (seconds). Only effective when\n * `loop` is `true`. Default: `0`\n */\n loopGap?: number;\n /**\n * Catch-up strength. When positive, playback speeds up when there is a\n * large amount of remaining animation and decays back to normal gradually.\n * `0` disables catch-up (default). Higher values ramp up more aggressively.\n * Typical range: `0.2` – `2`.\n */\n catchUp?: number;\n /** Called on every frame with the current time. */\n onTimeChange?: (time: number) => void;\n };\n css: {\n mode: 'css';\n };\n};\n\n/**\n * A plain number is shorthand for `{ mode: 'controlled', value: number }`.\n * `'css'` is shorthand for `{ mode: 'css' }`.\n * Omit for uncontrolled mode with default settings.\n */\nexport type TimeControlProp = null | undefined | number | 'css' | TimeControlMode[keyof TimeControlMode];\n\n// ---------------------------------------------------------------------------\n// Engine options\n// ---------------------------------------------------------------------------\n\nexport interface TegakiEngineOptions {\n text?: string;\n /** A font bundle, or a registered bundle name (see {@link TegakiEngine.registerBundle}). */\n font?: TegakiBundle | string;\n time?: TimeControlProp;\n effects?: TegakiEffects<Record<string, any>>;\n timing?: TimelineConfig;\n segmentSize?: number;\n showOverlay?: boolean;\n onComplete?: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Render elements\n// ---------------------------------------------------------------------------\n\nexport type CreateElementFn<T> = (tag: string, props: Record<string, any>, ...children: (T | string)[]) => T;\n\nconst PAD_V_CSS = 'max(0.2em, 0.9em - 0.5lh)';\n\nfunction buildRootProps(options: TegakiEngineOptions): Record<string, any> {\n const text = options.text ?? '';\n const font = resolveBundle(options.font);\n const fontFamily = font?.family;\n\n const duration = text && font ? computeTimeline(text, font, options.timing).totalDuration : 0;\n const time =\n typeof options.time === 'number'\n ? options.time\n : typeof options.time === 'object' && options.time?.mode === 'controlled'\n ? options.time.unit === 'progress'\n ? options.time.value * duration\n : options.time.value\n : typeof options.time === 'object' && options.time?.mode === 'uncontrolled'\n ? (options.time.initialTime ?? 0)\n : 0;\n const progress = duration > 0 ? time / duration : 0;\n\n return {\n 'data-tegaki': 'root',\n style: {\n position: 'relative',\n maxWidth: '100%',\n width: 'auto',\n height: 'auto',\n fontFamily: fontFamily ?? undefined,\n [CSS_DURATION]: duration,\n [CSS_TIME]: time,\n [CSS_PROGRESS]: progress,\n },\n };\n}\n\nfunction buildChildren<T>(options: TegakiEngineOptions, h: CreateElementFn<T>): T {\n const text = options.text ?? '';\n const isCss = options.time === 'css' || (typeof options.time === 'object' && options.time?.mode === 'css');\n const showOverlay = options.showOverlay;\n\n return h(\n 'span',\n { style: { display: 'block', position: 'relative' } },\n h('span', {\n 'data-tegaki': 'sentinel',\n 'aria-hidden': 'true',\n style: {\n position: 'absolute',\n width: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n fontSize: 'inherit',\n lineHeight: 'inherit',\n visibility: 'hidden',\n transition: isCss\n ? `font-size 0.001s, line-height 0.001s, color 0.001s, ${CSS_PROGRESS} 0.001s`\n : 'font-size 0.001s, line-height 0.001s, color 0.001s',\n },\n }),\n h(\n 'canvas',\n {\n 'data-tegaki': 'canvas',\n 'aria-hidden': 'true',\n style: {\n position: 'absolute',\n inset: `calc(-1 * ${PAD_V_CSS}) -0.2em`,\n width: 'calc(100% + 0.4em)',\n height: `calc(100% + 2 * ${PAD_V_CSS})`,\n pointerEvents: 'none',\n overflow: 'visible',\n },\n },\n h(\n 'span',\n {\n 'data-tegaki': 'canvas-fallback',\n style: { display: 'inline-block', padding: `${PAD_V_CSS} 0.2em` },\n },\n text,\n ),\n ),\n h(\n 'span',\n {\n 'data-tegaki': 'overlay',\n style: {\n display: 'block',\n userSelect: 'auto',\n whiteSpace: 'pre-wrap',\n overflowWrap: 'break-word',\n paddingRight: 1,\n WebkitTextFillColor: showOverlay ? undefined : 'transparent',\n color: showOverlay ? 'rgba(255, 0, 0, 0.4)' : undefined,\n },\n },\n text,\n ),\n );\n}\n\n// ---------------------------------------------------------------------------\n// DOM createElement helper (for vanilla JS constructor)\n// ---------------------------------------------------------------------------\n\nfunction domCreateElement(tag: string, props: Record<string, any>, ...children: (HTMLElement | string)[]): HTMLElement {\n const el = document.createElement(tag);\n for (const [key, value] of Object.entries(props)) {\n if (key === 'style' && typeof value === 'object') {\n for (const [k, v] of Object.entries(value as Record<string, any>)) {\n if (v !== undefined && v !== null) {\n if (k.startsWith('--')) {\n el.style.setProperty(k, String(v));\n } else {\n (el.style as any)[k] = typeof v === 'number' && k !== 'opacity' && k !== 'zIndex' ? `${v}px` : v;\n }\n }\n }\n } else if (key === 'aria-hidden') {\n el.setAttribute('aria-hidden', String(value));\n } else if (key.startsWith('data-')) {\n el.setAttribute(key, String(value));\n }\n }\n for (const child of children) {\n if (typeof child === 'string') {\n el.appendChild(document.createTextNode(child));\n } else {\n el.appendChild(child);\n }\n }\n return el;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction resolveTimeControl(prop: TimeControlProp): TimeControlMode[keyof TimeControlMode] {\n if (prop == null) return { mode: 'uncontrolled' };\n if (typeof prop === 'number') return { mode: 'controlled', value: prop };\n if (prop === 'css') return { mode: 'css' };\n return prop;\n}\n\n// ---------------------------------------------------------------------------\n// TegakiEngine\n// ---------------------------------------------------------------------------\n\nfunction resolveBundle(font: TegakiBundle | string | undefined): TegakiBundle | undefined {\n if (typeof font === 'string') {\n const bundle = TegakiEngine.getBundle(font);\n if (!bundle) throw new Error(`TegakiEngine: no bundle registered for \"${font}\". Call TegakiEngine.registerBundle() first.`);\n return bundle;\n }\n return font;\n}\n\nexport class TegakiEngine {\n // --- Bundle registry ---\n private static _bundles = new Map<string, TegakiBundle>();\n\n /** Register a font bundle so it can be referenced by family name. */\n static registerBundle(bundle: TegakiBundle): void {\n TegakiEngine._bundles.set(bundle.family, bundle);\n }\n\n /** Look up a registered bundle by family name. */\n static getBundle(family: string): TegakiBundle | undefined {\n return TegakiEngine._bundles.get(family);\n }\n\n // --- DOM elements ---\n private _rootEl: HTMLElement;\n private _contentEl: HTMLElement | null = null; // non-null only in non-adopt mode\n private _sentinelEl: HTMLSpanElement;\n private _canvasEl: HTMLCanvasElement;\n private _overlayEl: HTMLElement;\n private _canvasFallbackEl: HTMLSpanElement;\n\n // --- Options ---\n private _text = '';\n private _font: TegakiBundle | null = null;\n private _timeControl: TimeControlMode[keyof TimeControlMode] = { mode: 'uncontrolled' };\n private _effects: Record<string, any> | undefined;\n private _timing: TimelineConfig | undefined;\n private _segmentSize: number | undefined;\n private _showOverlay = false;\n private _onComplete: (() => void) | undefined;\n\n // --- Derived / cached ---\n private _resolvedEffects: ResolvedEffect[] = resolveEffects(undefined);\n private _seed: number;\n private _timeline: Timeline = { entries: [] as TimelineEntry[], totalDuration: 0 };\n private _layout: TextLayout | null = null;\n private _fontReady = false;\n\n // --- Measured from DOM ---\n private _containerWidth = 0;\n private _fontSize = 0;\n private _lineHeight = 0;\n private _currentColor = '';\n\n // --- Playback state ---\n private _internalTime = 0;\n private _cssTime = 0;\n private _playing = true;\n private _smoothedBoost = 0;\n private _delayRemaining = 0;\n private _loopGapRemaining = 0;\n private _lastTs: number | null = null;\n private _rafId = 0;\n private _prevCompleted = false;\n private _prefersReducedMotion = false;\n private _destroyed = false;\n\n // --- Observers & listeners ---\n private _resizeObserver: ResizeObserver;\n private _mql: MediaQueryList | null = null;\n\n /**\n * Returns the props (including style) that should be applied to the container element,\n * plus the inner content tree rendered via a framework `createElement` callback.\n *\n * Each child element receives a `data-tegaki` attribute so the engine can adopt\n * pre-rendered elements later via `new TegakiEngine(container, { adopt: true })`.\n */\n static renderElements<T>(\n options: TegakiEngineOptions,\n createElement: CreateElementFn<T>,\n ): { rootProps: Record<string, any>; content: T } {\n return {\n rootProps: buildRootProps(options),\n content: buildChildren(options, createElement),\n };\n }\n\n constructor(container: HTMLElement, options?: TegakiEngineOptions & { adopt?: boolean }) {\n registerCssProperties();\n this._seed = Math.random() * 1000;\n\n // --- Resolve DOM elements ---\n // The container itself is the root element. In adopt mode, the adapter has\n // already rendered children inside it. In non-adopt mode, we create them.\n this._rootEl = container;\n\n if (options?.adopt) {\n // Adopt pre-rendered children (created by renderElements)\n } else {\n // Create DOM from scratch\n const content = buildChildren(options ?? {}, domCreateElement);\n container.appendChild(content);\n this._contentEl = content;\n // Apply root styles to the container\n const rootProps = buildRootProps(options ?? {});\n for (const [key, value] of Object.entries(rootProps.style as Record<string, any>)) {\n if (value !== undefined && value !== null) {\n if (key.startsWith('--')) {\n container.style.setProperty(key, String(value));\n } else {\n (container.style as any)[key] = typeof value === 'number' && key !== 'opacity' && key !== 'zIndex' ? `${value}px` : value;\n }\n }\n }\n container.dataset.tegaki = 'root';\n }\n\n this._sentinelEl = container.querySelector('[data-tegaki=\"sentinel\"]') as HTMLSpanElement;\n this._canvasEl = container.querySelector('[data-tegaki=\"canvas\"]') as HTMLCanvasElement;\n this._canvasFallbackEl = container.querySelector('[data-tegaki=\"canvas-fallback\"]') as HTMLSpanElement;\n this._overlayEl = container.querySelector('[data-tegaki=\"overlay\"]') as HTMLElement;\n\n // --- ResizeObserver ---\n this._resizeObserver = new ResizeObserver(this._onResize);\n this._resizeObserver.observe(this._rootEl);\n\n // --- Sentinel transitions ---\n this._sentinelEl.addEventListener('transitionend', this._onSentinelTransition);\n\n // --- Reduced motion ---\n if (typeof window !== 'undefined') {\n this._mql = window.matchMedia('(prefers-reduced-motion: reduce)');\n this._prefersReducedMotion = this._mql.matches;\n this._mql.addEventListener('change', this._onReducedMotionChange);\n }\n\n // --- Initial measurement (must run before update so layout has valid dimensions) ---\n this._measure();\n\n // --- Apply initial options ---\n if (options) this.update(options);\n }\n\n // =========================================================================\n // Public API\n // =========================================================================\n\n get currentTime(): number {\n const tc = this._timeControl;\n if (tc.mode === 'css') return this._cssTime;\n if (tc.mode === 'controlled') return tc.unit === 'progress' ? tc.value * this._timeline.totalDuration : tc.value;\n return this._internalTime;\n }\n\n get duration(): number {\n return this._timeline.totalDuration;\n }\n\n get isPlaying(): boolean {\n return this._playing;\n }\n\n get isComplete(): boolean {\n return this._timeline.totalDuration > 0 && this.currentTime >= this._timeline.totalDuration;\n }\n\n get element(): HTMLElement {\n return this._rootEl;\n }\n\n play(): void {\n if (this._timeControl.mode !== 'uncontrolled') return;\n this._playing = true;\n this._evaluatePlayback();\n }\n\n pause(): void {\n if (this._timeControl.mode !== 'uncontrolled') return;\n this._playing = false;\n this._evaluatePlayback();\n }\n\n seek(time: number): void {\n if (this._timeControl.mode !== 'uncontrolled') return;\n this._internalTime = Math.max(0, Math.min(time, this._timeline.totalDuration));\n this._delayRemaining = 0;\n this._loopGapRemaining = 0;\n this._checkCompletion();\n this._notifyTimeChange();\n this._render();\n this._updateCssProperties();\n }\n\n restart(): void {\n if (this._timeControl.mode !== 'uncontrolled') return;\n this._internalTime = 0;\n this._playing = true;\n this._prevCompleted = false;\n this._delayRemaining = this._timeControl.delay ?? 0;\n this._loopGapRemaining = 0;\n this._notifyTimeChange();\n this._evaluatePlayback();\n }\n\n update(options: Partial<TegakiEngineOptions>): void {\n if (this._destroyed) return;\n\n let dirtyTimeline = false;\n let dirtyLayout = false;\n let dirtyRender = false;\n let dirtyPlayback = false;\n\n if ('text' in options && options.text !== this._text) {\n this._text = options.text ?? '';\n dirtyTimeline = true;\n dirtyLayout = true;\n }\n\n if ('font' in options) {\n const resolved = resolveBundle(options.font) ?? null;\n if (resolved !== this._font) {\n this._loadFont(resolved);\n dirtyTimeline = true;\n dirtyLayout = true;\n dirtyPlayback = true;\n }\n }\n\n if ('time' in options) {\n const newTc = resolveTimeControl(options.time);\n const oldTc = this._timeControl;\n\n // Detect meaningful changes\n const modeChanged = newTc.mode !== oldTc.mode;\n const controlledValueChanged =\n newTc.mode === 'controlled' && oldTc.mode === 'controlled' && (newTc.value !== oldTc.value || newTc.unit !== oldTc.unit);\n const uncontrolledChanged =\n newTc.mode === 'uncontrolled' &&\n oldTc.mode === 'uncontrolled' &&\n (newTc.speed !== oldTc.speed ||\n newTc.playing !== oldTc.playing ||\n newTc.loop !== oldTc.loop ||\n newTc.delay !== oldTc.delay ||\n newTc.loopGap !== oldTc.loopGap ||\n newTc.catchUp !== oldTc.catchUp);\n\n if (modeChanged || controlledValueChanged || uncontrolledChanged) {\n this._timeControl = newTc;\n\n if (newTc.mode === 'uncontrolled') {\n this._playing = newTc.playing ?? true;\n const oldDelay = oldTc.mode === 'uncontrolled' ? (oldTc.delay ?? 0) : 0;\n const newDelay = newTc.delay ?? 0;\n if (modeChanged || oldDelay !== newDelay) {\n this._delayRemaining = newDelay;\n this._loopGapRemaining = 0;\n }\n }\n\n dirtyPlayback = true;\n dirtyRender = true;\n\n // Update sentinel transition for css mode\n this._updateSentinelTransition();\n }\n }\n\n if ('effects' in options && options.effects !== this._effects) {\n this._effects = options.effects as Record<string, any>;\n this._resolvedEffects = resolveEffects(this._effects);\n dirtyRender = true;\n }\n\n if ('timing' in options && options.timing !== this._timing) {\n this._timing = options.timing;\n dirtyTimeline = true;\n }\n\n if ('segmentSize' in options && options.segmentSize !== this._segmentSize) {\n this._segmentSize = options.segmentSize;\n dirtyRender = true;\n }\n\n if ('showOverlay' in options && options.showOverlay !== this._showOverlay) {\n this._showOverlay = options.showOverlay ?? false;\n this._updateOverlayStyle();\n dirtyRender = true;\n }\n\n if ('onComplete' in options) {\n this._onComplete = options.onComplete;\n }\n\n // --- Recompute ---\n if (dirtyTimeline) this._recomputeTimeline();\n if (dirtyLayout) this._recomputeLayout();\n if (dirtyPlayback) this._evaluatePlayback();\n if (dirtyRender || dirtyTimeline || dirtyLayout) {\n this._updateDom();\n this._render();\n }\n }\n\n destroy(): void {\n this._destroyed = true;\n this._stopLoop();\n this._resizeObserver.disconnect();\n this._sentinelEl.removeEventListener('transitionend', this._onSentinelTransition);\n this._mql?.removeEventListener('change', this._onReducedMotionChange);\n // Only remove content we created (non-adopt mode). The container is owned by the caller.\n this._contentEl?.remove();\n }\n\n // =========================================================================\n // Internal: DOM updates\n // =========================================================================\n\n /** Estimate line-height from font metrics when CSS returns \"normal\". */\n private _fallbackLineHeight(fontSize: number): number {\n if (this._font) {\n return ((this._font.ascender - this._font.descender) / this._font.unitsPerEm) * fontSize;\n }\n return fontSize * 1.2;\n }\n\n private _measure(): void {\n const styles = getComputedStyle(this._rootEl);\n this._containerWidth = this._rootEl.getBoundingClientRect().width;\n this._fontSize = Number.parseFloat(styles.fontSize);\n const parsedLh = Number.parseFloat(styles.lineHeight);\n this._lineHeight = Number.isNaN(parsedLh) ? this._fallbackLineHeight(this._fontSize) : parsedLh;\n this._currentColor = styles.color;\n }\n\n private _updateDom(): void {\n // Font family\n this._rootEl.style.fontFamily = this._font?.family ?? '';\n\n // CSS custom properties\n this._updateCssProperties();\n\n // Overlay text (guard to preserve cursor position when contentEditable)\n if (this._overlayEl.textContent !== this._text) {\n this._overlayEl.textContent = this._text;\n }\n this._canvasFallbackEl.textContent = this._text;\n }\n\n private _updateCssProperties(): void {\n const time = this.currentTime;\n const dur = this._timeline.totalDuration;\n this._rootEl.style.setProperty(CSS_DURATION, String(dur));\n this._rootEl.style.setProperty(CSS_TIME, String(time));\n this._rootEl.style.setProperty(CSS_PROGRESS, String(dur > 0 ? time / dur : 0));\n }\n\n private _updateOverlayStyle(): void {\n if (this._showOverlay) {\n this._overlayEl.style.webkitTextFillColor = '';\n this._overlayEl.style.color = 'rgba(255, 0, 0, 0.4)';\n } else {\n this._overlayEl.style.webkitTextFillColor = 'transparent';\n this._overlayEl.style.color = '';\n }\n }\n\n private _updateSentinelTransition(): void {\n const isCss = this._timeControl.mode === 'css';\n this._sentinelEl.style.transition = isCss\n ? `font-size 0.001s, line-height 0.001s, color 0.001s, ${CSS_PROGRESS} 0.001s`\n : 'font-size 0.001s, line-height 0.001s, color 0.001s';\n }\n\n // =========================================================================\n // Internal: Resize & sentinel observers\n // =========================================================================\n\n private _onResize = (entries: ResizeObserverEntry[]): void => {\n const entry = entries[0];\n if (!entry) return;\n const newWidth = entry.contentRect.width;\n const styles = getComputedStyle(this._rootEl);\n const newFontSize = Number.parseFloat(styles.fontSize);\n const parsedLh = Number.parseFloat(styles.lineHeight);\n const newLineHeight = Number.isNaN(parsedLh) ? this._fallbackLineHeight(newFontSize) : parsedLh;\n const newColor = styles.color;\n\n let changed = false;\n let layoutChanged = false;\n\n if (newWidth !== this._containerWidth) {\n this._containerWidth = newWidth;\n layoutChanged = true;\n changed = true;\n }\n if (newFontSize !== this._fontSize) {\n this._fontSize = newFontSize;\n layoutChanged = true;\n changed = true;\n }\n if (newLineHeight !== this._lineHeight) {\n this._lineHeight = newLineHeight;\n layoutChanged = true;\n changed = true;\n }\n if (newColor !== this._currentColor) {\n this._currentColor = newColor;\n changed = true;\n }\n\n if (layoutChanged) this._recomputeLayout();\n if (changed) this._render();\n };\n\n private _onSentinelTransition = (e: TransitionEvent): void => {\n const styles = getComputedStyle(this._sentinelEl);\n let changed = false;\n\n if (e.propertyName === 'font-size' || e.propertyName === 'line-height') {\n const newFontSize = Number.parseFloat(styles.fontSize);\n const parsedLh = Number.parseFloat(styles.lineHeight);\n const newLineHeight = Number.isNaN(parsedLh) ? this._fallbackLineHeight(newFontSize) : parsedLh;\n if (newFontSize !== this._fontSize || newLineHeight !== this._lineHeight) {\n this._fontSize = newFontSize;\n this._lineHeight = newLineHeight;\n this._recomputeLayout();\n changed = true;\n }\n }\n\n if (e.propertyName === 'color') {\n const newColor = styles.color;\n if (newColor !== this._currentColor) {\n this._currentColor = newColor;\n changed = true;\n }\n }\n\n if (e.propertyName === CSS_PROGRESS) {\n const rawProgress = Number(styles.getPropertyValue(CSS_PROGRESS));\n this._cssTime = rawProgress * this._timeline.totalDuration;\n changed = true;\n }\n\n if (changed) this._render();\n };\n\n // =========================================================================\n // Internal: Reduced motion\n // =========================================================================\n\n private _onReducedMotionChange = (e: MediaQueryListEvent): void => {\n this._prefersReducedMotion = e.matches;\n if (this._prefersReducedMotion && this._timeControl.mode === 'uncontrolled' && this._timeline.totalDuration > 0) {\n this._internalTime = this._timeline.totalDuration;\n }\n this._evaluatePlayback();\n this._render();\n };\n\n // =========================================================================\n // Internal: Font loading\n // =========================================================================\n\n private _loadFont(font: TegakiBundle | null): void {\n this._font = font;\n this._fontReady = false;\n\n if (!font) return;\n\n const pending = ensureFont(font.family, font.fontUrl);\n if (pending === null) {\n this._fontReady = true;\n return;\n }\n\n const currentFont = font;\n pending.then(() => {\n if (this._font === currentFont && !this._destroyed) {\n this._fontReady = true;\n this._recomputeTimeline();\n this._recomputeLayout();\n this._evaluatePlayback();\n this._updateDom();\n this._render();\n }\n });\n }\n\n // =========================================================================\n // Internal: Recomputation\n // =========================================================================\n\n private _recomputeTimeline(): void {\n if (this._font && this._text) {\n this._timeline = computeTimeline(this._text, this._font, this._timing);\n } else {\n this._timeline = { entries: [] as TimelineEntry[], totalDuration: 0 };\n }\n }\n\n private _recomputeLayout(): void {\n const fontFamily = this._font?.family;\n if (this._fontReady && fontFamily && this._fontSize && this._containerWidth && this._text) {\n this._layout = computeTextLayout(this._text, fontFamily, this._fontSize, this._lineHeight, this._containerWidth);\n } else {\n this._layout = null;\n }\n }\n\n // =========================================================================\n // Internal: Playback loop\n // =========================================================================\n\n private _evaluatePlayback(): void {\n const tc = this._timeControl;\n const shouldRun = tc.mode === 'uncontrolled' && this._playing && !!this._font && this._fontReady && !this._prefersReducedMotion;\n\n if (shouldRun) {\n this._startLoop();\n } else {\n this._stopLoop();\n }\n }\n\n private _startLoop(): void {\n if (this._rafId) return;\n this._lastTs = null;\n this._smoothedBoost = 0;\n this._rafId = requestAnimationFrame(this._tick);\n }\n\n private _stopLoop(): void {\n if (this._rafId) {\n cancelAnimationFrame(this._rafId);\n this._rafId = 0;\n }\n }\n\n private _tick = (ts: number): void => {\n if (this._destroyed) return;\n\n if (this._lastTs === null) this._lastTs = ts;\n const dtSec = (ts - this._lastTs) / 1000;\n this._lastTs = ts;\n\n const tc = this._timeControl;\n if (tc.mode !== 'uncontrolled') return;\n\n const speed = tc.speed ?? 1;\n const loop = tc.loop ?? false;\n const catchUp = tc.catchUp ?? 0;\n const totalDur = this._timeline.totalDuration;\n\n if (totalDur === 0 || (!loop && this._internalTime >= totalDur)) {\n this._internalTime = totalDur;\n this._rafId = requestAnimationFrame(this._tick);\n return;\n }\n\n // --- Initial delay ---\n if (this._delayRemaining > 0) {\n this._delayRemaining = Math.max(0, this._delayRemaining - dtSec);\n this._rafId = requestAnimationFrame(this._tick);\n return;\n }\n\n // --- Loop gap (waiting between iterations) ---\n if (this._loopGapRemaining > 0) {\n this._loopGapRemaining = Math.max(0, this._loopGapRemaining - dtSec);\n if (this._loopGapRemaining <= 0) {\n this._internalTime = 0;\n this._prevCompleted = false;\n this._smoothedBoost = 0;\n }\n this._notifyTimeChange();\n this._render();\n this._updateCssProperties();\n this._rafId = requestAnimationFrame(this._tick);\n return;\n }\n\n // Compute effective speed with catch-up\n let effectiveSpeed = speed;\n if (catchUp > 0) {\n const remaining = Math.max(0, totalDur - this._internalTime);\n const excess = Math.max(0, remaining - 2);\n const targetBoost = catchUp * excess;\n const attackRate = 4;\n const releaseRate = loop ? 30 : 2;\n const rate = targetBoost > this._smoothedBoost ? attackRate : releaseRate;\n this._smoothedBoost += (targetBoost - this._smoothedBoost) * (1 - Math.exp(-rate * dtSec));\n effectiveSpeed = speed + this._smoothedBoost;\n }\n\n let next = this._internalTime + dtSec * effectiveSpeed;\n if (next >= totalDur) {\n if (loop) {\n const loopGap = tc.loopGap ?? 0;\n if (loopGap > 0) {\n // Hold at the end and start the loop gap countdown\n next = totalDur;\n this._loopGapRemaining = loopGap;\n } else {\n next = next % totalDur;\n }\n } else {\n next = totalDur;\n }\n this._smoothedBoost = 0;\n }\n this._internalTime = next;\n\n this._notifyTimeChange();\n this._checkCompletion();\n this._render();\n this._updateCssProperties();\n\n this._rafId = requestAnimationFrame(this._tick);\n };\n\n private _notifyTimeChange(): void {\n const tc = this._timeControl;\n if (tc.mode === 'uncontrolled' && tc.onTimeChange) {\n tc.onTimeChange(this._internalTime);\n }\n }\n\n private _checkCompletion(): void {\n const complete = this._timeline.totalDuration > 0 && this.currentTime >= this._timeline.totalDuration;\n if (complete && !this._prevCompleted) {\n this._prevCompleted = true;\n this._onComplete?.();\n } else if (!complete) {\n this._prevCompleted = false;\n }\n }\n\n // =========================================================================\n // Internal: Canvas rendering\n // =========================================================================\n\n private _render(): void {\n const canvas = this._canvasEl;\n const font = this._font;\n const layout = this._layout;\n const fontSize = this._fontSize;\n\n if (!font?.glyphData || !layout || !fontSize) return;\n\n const dpr = window.devicePixelRatio || 1;\n const w = canvas.offsetWidth;\n const h = canvas.offsetHeight;\n\n const needsResize = canvas.width !== Math.round(w * dpr) || canvas.height !== Math.round(h * dpr);\n if (needsResize) {\n canvas.width = Math.round(w * dpr);\n canvas.height = Math.round(h * dpr);\n }\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, w, h);\n\n const padH = PADDING_H_EM * fontSize;\n const lineHeight = this._lineHeight;\n const padV = Math.max(MIN_PADDING_V_EM * fontSize, (MIN_LINE_HEIGHT_EM * fontSize - lineHeight) / 2);\n ctx.translate(padH, padV);\n\n const color = this._currentColor || 'black';\n const emHeight = (font.ascender - font.descender) / font.unitsPerEm;\n const emHeightPx = emHeight * fontSize;\n const halfLeading = (lineHeight - emHeightPx) / 2;\n const characters = graphemes(this._text);\n const currentTime = this.currentTime;\n\n let y = 0;\n for (const lineIndices of layout.lines) {\n let x = 0;\n for (const charIdx of lineIndices) {\n const char = characters[charIdx]!;\n if (char === '\\n') continue;\n const entry = this._timeline.entries[charIdx]!;\n const charWidth = layout.charWidths[charIdx] ?? 0;\n const kerning = layout.kernings[charIdx] ?? 0;\n const glyph = font.glyphData[char];\n\n if (glyph && entry.hasGlyph) {\n const localTime = Math.max(0, Math.min(currentTime - entry.offset, entry.duration));\n const glyphY = y + halfLeading;\n drawGlyph(\n ctx,\n glyph,\n {\n x,\n y: glyphY,\n fontSize,\n unitsPerEm: font.unitsPerEm,\n ascender: font.ascender,\n descender: font.descender,\n },\n localTime,\n font.lineCap,\n color,\n this._resolvedEffects,\n this._seed + charIdx,\n this._segmentSize,\n );\n } else if (!entry.hasGlyph && currentTime >= entry.offset + entry.duration) {\n const baseline = y + halfLeading + (font.ascender / font.unitsPerEm) * fontSize;\n drawFallbackGlyph(ctx, char, x, baseline, fontSize, font.family, color, this._resolvedEffects, this._seed + charIdx);\n }\n\n x += (charWidth + kerning) * fontSize;\n }\n y += lineHeight;\n }\n }\n}\n"],"mappings":"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"}