egaki 0.7.2 → 0.9.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 (63) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/cli/bpm-detect.d.ts +7 -0
  3. package/dist/cli/bpm-detect.d.ts.map +1 -0
  4. package/dist/cli/bpm-detect.js +123 -0
  5. package/dist/cli/cli.d.ts.map +1 -1
  6. package/dist/cli/cli.js +115 -0
  7. package/dist/cli/loudness-detect.d.ts +13 -0
  8. package/dist/cli/loudness-detect.d.ts.map +1 -0
  9. package/dist/cli/loudness-detect.js +96 -0
  10. package/dist/vite/angled-screen-shader.d.ts +45 -0
  11. package/dist/vite/angled-screen-shader.d.ts.map +1 -0
  12. package/dist/vite/angled-screen-shader.js +573 -0
  13. package/dist/vite/angled-screen.d.ts +12 -3
  14. package/dist/vite/angled-screen.d.ts.map +1 -1
  15. package/dist/vite/angled-screen.js +12 -3
  16. package/dist/vite/code-block.d.ts.map +1 -1
  17. package/dist/vite/code-block.js +26 -5
  18. package/dist/vite/layout-editor.js +1 -1
  19. package/dist/vite/mdx-video.d.ts +4 -2
  20. package/dist/vite/mdx-video.d.ts.map +1 -1
  21. package/dist/vite/mdx-video.js +4 -2
  22. package/dist/vite/mdx-video.test.js +1 -79
  23. package/dist/vite/player-page.d.ts.map +1 -1
  24. package/dist/vite/player-page.js +6 -94
  25. package/dist/vite/render-client.d.ts +27 -0
  26. package/dist/vite/render-client.d.ts.map +1 -1
  27. package/dist/vite/render-client.js +47 -51
  28. package/dist/vite/sdk.d.ts.map +1 -1
  29. package/dist/vite/sdk.js +4 -3
  30. package/dist/vite/shader-renderer.d.ts +4 -0
  31. package/dist/vite/shader-renderer.d.ts.map +1 -1
  32. package/dist/vite/shader-renderer.js +3 -3
  33. package/dist/vite/store-hooks.d.ts +2 -0
  34. package/dist/vite/store-hooks.d.ts.map +1 -1
  35. package/dist/vite/store-hooks.js +8 -0
  36. package/dist/vite/store.d.ts +5 -0
  37. package/dist/vite/store.d.ts.map +1 -1
  38. package/dist/vite/store.js +5 -0
  39. package/dist/vite/tweakpane-hook.d.ts +12 -3
  40. package/dist/vite/tweakpane-hook.d.ts.map +1 -1
  41. package/dist/vite/tweakpane-hook.js +128 -20
  42. package/dist/vite/vite-plugin.d.ts +0 -6
  43. package/dist/vite/vite-plugin.d.ts.map +1 -1
  44. package/dist/vite/vite-plugin.js +1 -92
  45. package/package.json +9 -6
  46. package/skills/egaki/SKILL.md +26 -224
  47. package/src/cli/bpm-detect.ts +165 -0
  48. package/src/cli/cli.ts +149 -0
  49. package/src/cli/loudness-detect.ts +129 -0
  50. package/src/vite/angled-screen-shader.tsx +706 -0
  51. package/src/vite/angled-screen.tsx +13 -4
  52. package/src/vite/code-block.tsx +28 -6
  53. package/src/vite/layout-editor.tsx +1 -1
  54. package/src/vite/mdx-video.test.tsx +1 -94
  55. package/src/vite/mdx-video.tsx +4 -1
  56. package/src/vite/player-page.tsx +28 -117
  57. package/src/vite/render-client.ts +52 -51
  58. package/src/vite/sdk.ts +4 -3
  59. package/src/vite/shader-renderer.tsx +3 -3
  60. package/src/vite/store-hooks.ts +11 -0
  61. package/src/vite/store.ts +10 -0
  62. package/src/vite/tweakpane-hook.tsx +173 -28
  63. package/src/vite/vite-plugin.ts +1 -91
@@ -28,6 +28,10 @@ export interface ShaderConfig {
28
28
  /** Property controls that become uniforms and tweakpane sliders. */
29
29
  propertyControls: Record<string, PropertyControl>;
30
30
  }
31
+ /** Parse a hex color string (#RGB, #RRGGBB, #RRGGBBAA) to [r, g, b, a] in 0-1 range. */
32
+ export declare function parseHexColor(hex: string): [number, number, number, number];
33
+ export declare function compileGlShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader;
34
+ export declare function linkGlProgram(gl: WebGL2RenderingContext, vs: WebGLShader, fs: WebGLShader): WebGLProgram;
31
35
  /**
32
36
  * Create a React component that renders a WebGL2 fragment shader full-screen.
33
37
  *
@@ -1 +1 @@
1
- {"version":3,"file":"shader-renderer.d.ts","sourceRoot":"","sources":["../../src/vite/shader-renderer.tsx"],"names":[],"mappings":"AA8BA,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAOnE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAA;IAC1B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAE/D,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAClD;AAoLD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY;YAsEtC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;QAAE,KAAK,CAAC,EAAE,aAAa,CAAA;KAAE;;EA+MzD"}
1
+ {"version":3,"file":"shader-renderer.d.ts","sourceRoot":"","sources":["../../src/vite/shader-renderer.tsx"],"names":[],"mappings":"AA8BA,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAOnE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAA;IAC1B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAE/D,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAClD;AAMD,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAa3E;AAwFD,wBAAgB,eAAe,CAC7B,EAAE,EAAE,sBAAsB,EAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,WAAW,CAWb;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,sBAAsB,EAC1B,EAAE,EAAE,WAAW,EACf,EAAE,EAAE,WAAW,GACd,YAAY,CAYd;AAuCD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY;YAsEtC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;QAAE,KAAK,CAAC,EAAE,aAAa,CAAA;KAAE;;EA+MzD"}
@@ -33,7 +33,7 @@ import { useTweakpane } from "./tweakpane-hook.js";
33
33
  // Color parsing
34
34
  // ---------------------------------------------------------------------------
35
35
  /** Parse a hex color string (#RGB, #RRGGBB, #RRGGBBAA) to [r, g, b, a] in 0-1 range. */
36
- function parseHexColor(hex) {
36
+ export function parseHexColor(hex) {
37
37
  let h = hex.replace('#', '');
38
38
  if (h.length === 3 || h.length === 4) {
39
39
  h = h
@@ -112,7 +112,7 @@ function generateFragmentSource(body, controls) {
112
112
  // ---------------------------------------------------------------------------
113
113
  // WebGL helpers
114
114
  // ---------------------------------------------------------------------------
115
- function compileGlShader(gl, type, source) {
115
+ export function compileGlShader(gl, type, source) {
116
116
  const shader = gl.createShader(type);
117
117
  if (!shader)
118
118
  throw new Error('Failed to create shader');
@@ -125,7 +125,7 @@ function compileGlShader(gl, type, source) {
125
125
  }
126
126
  return shader;
127
127
  }
128
- function linkGlProgram(gl, vs, fs) {
128
+ export function linkGlProgram(gl, vs, fs) {
129
129
  const program = gl.createProgram();
130
130
  if (!program)
131
131
  throw new Error('Failed to create program');
@@ -6,6 +6,8 @@ export declare function useMediaDurations(): Record<string, number>;
6
6
  export declare function useGenerationStatus(): GenerationStatus | null;
7
7
  /** Hook: recent generation errors (auto-cleared after 8s). */
8
8
  export declare function useGenerationErrors(): GenerationError[];
9
+ /** Hook: whether the right tweakpane sidebar is open. */
10
+ export declare function useSidebarOpen(): boolean;
9
11
  /** Hook: user modules from virtual:egaki-modules. */
10
12
  export declare function useModules(): EagerModules;
11
13
  export type { GenerationStatus, GenerationError };
@@ -1 +1 @@
1
- {"version":3,"file":"store-hooks.d.ts","sourceRoot":"","sources":["../../src/vite/store-hooks.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAA;AAGnB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AAuEvD,6CAA6C;AAC7C,wBAAgB,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE1D;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,IAAI,CAE7D;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,IAAI,eAAe,EAAE,CAEvD;AAED,qDAAqD;AACrD,wBAAgB,UAAU,IAAI,YAAY,CAEzC;AAED,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAA"}
1
+ {"version":3,"file":"store-hooks.d.ts","sourceRoot":"","sources":["../../src/vite/store-hooks.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAA;AAGnB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AA6EvD,6CAA6C;AAC7C,wBAAgB,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE1D;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,IAAI,CAE7D;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,IAAI,eAAe,EAAE,CAEvD;AAED,yDAAyD;AACzD,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,qDAAqD;AACrD,wBAAgB,UAAU,IAAI,YAAY,CAEzC;AAED,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAA"}
@@ -69,6 +69,10 @@ const subscribeGenerationErrors = (callback) => egakiStore.subscribe((state) =>
69
69
  const getGenerationErrors = () => egakiStore.getState().serverGenerationErrors;
70
70
  const EMPTY_ERRORS = [];
71
71
  const getGenerationErrorsServer = () => EMPTY_ERRORS;
72
+ // Sidebar open state
73
+ const subscribeSidebarOpen = (callback) => egakiStore.subscribe((state) => state.sidebarOpen, callback);
74
+ const getSidebarOpen = () => egakiStore.getState().sidebarOpen;
75
+ const getSidebarOpenServer = () => false;
72
76
  // Modules
73
77
  const subscribeModules = (callback) => egakiStore.subscribe((state) => state.modules, callback);
74
78
  const getModules = () => egakiStore.getState().modules;
@@ -87,6 +91,10 @@ export function useGenerationStatus() {
87
91
  export function useGenerationErrors() {
88
92
  return useSyncExternalStore(subscribeGenerationErrors, getGenerationErrors, getGenerationErrorsServer);
89
93
  }
94
+ /** Hook: whether the right tweakpane sidebar is open. */
95
+ export function useSidebarOpen() {
96
+ return useSyncExternalStore(subscribeSidebarOpen, getSidebarOpen, getSidebarOpenServer);
97
+ }
90
98
  /** Hook: user modules from virtual:egaki-modules. */
91
99
  export function useModules() {
92
100
  return useSyncExternalStore(subscribeModules, getModules, getModules);
@@ -54,6 +54,9 @@ export interface EgakiClientState {
54
54
  /** Recent generation errors from the server progress stream.
55
55
  * Errors auto-clear after ERROR_DISPLAY_DURATION_MS. */
56
56
  serverGenerationErrors: GenerationError[];
57
+ /** Whether the right tweakpane sidebar is open. Closed by default so the
58
+ * video takes the full width. */
59
+ sidebarOpen: boolean;
57
60
  }
58
61
  export declare const egakiStore: Omit<import("zustand/vanilla").StoreApi<EgakiClientState>, "subscribe"> & {
59
62
  subscribe: {
@@ -64,6 +67,8 @@ export declare const egakiStore: Omit<import("zustand/vanilla").StoreApi<EgakiCl
64
67
  } | undefined): () => void;
65
68
  };
66
69
  };
70
+ /** Open or close the right tweakpane sidebar. */
71
+ export declare function setSidebarOpen(open: boolean): void;
67
72
  /**
68
73
  * Derive per-section max media durations.
69
74
  * Returns Record<string, number> (section-index-as-string → seconds).
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/vite/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,2EAA2E;AAC3E,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,OAAO,EAAE,YAAY,CAAA;IAErB;;;;OAIG;IACH,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAEhD,oFAAoF;IACpF,sBAAsB,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAE/C,mEAAmE;IACnE,uBAAuB,EAAE,uBAAuB,EAAE,CAAA;IAElD;6DACyD;IACzD,sBAAsB,EAAE,eAAe,EAAE,CAAA;CAC1C;AAMD,eAAO,MAAM,UAAU;;;;;;;;CAQtB,CAAA;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWpF;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,IAAI,CAEvF"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/vite/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,2EAA2E;AAC3E,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,OAAO,EAAE,YAAY,CAAA;IAErB;;;;OAIG;IACH,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAEhD,oFAAoF;IACpF,sBAAsB,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAE/C,mEAAmE;IACnE,uBAAuB,EAAE,uBAAuB,EAAE,CAAA;IAElD;6DACyD;IACzD,sBAAsB,EAAE,eAAe,EAAE,CAAA;IAEzC;sCACkC;IAClC,WAAW,EAAE,OAAO,CAAA;CACrB;AAMD,eAAO,MAAM,UAAU;;;;;;;;CAStB,CAAA;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,QAE3C;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWpF;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,IAAI,CAEvF"}
@@ -23,7 +23,12 @@ export const egakiStore = createStore()(subscribeWithSelector(() => ({
23
23
  serverGenerationStatus: null,
24
24
  serverGenerationEntries: [],
25
25
  serverGenerationErrors: [],
26
+ sidebarOpen: false,
26
27
  })));
28
+ /** Open or close the right tweakpane sidebar. */
29
+ export function setSidebarOpen(open) {
30
+ egakiStore.setState({ sidebarOpen: open });
31
+ }
27
32
  // ---------------------------------------------------------------------------
28
33
  // Selectors — derive values from state
29
34
  // ---------------------------------------------------------------------------
@@ -71,15 +71,24 @@ export declare let TWEAKPANE_DISABLED: boolean;
71
71
  * Returns a reactive object with current values. Updating a control in the
72
72
  * pane immediately triggers a React re-render with fresh values.
73
73
  *
74
+ * Values follow live props: each key returns the schema value from the
75
+ * CURRENT render until the user touches that control in the pane, so
76
+ * animated props (e.g. `translateX={interpolate(frame, ...)}`) flow through
77
+ * every frame. Once touched, the user's pane value wins for that key until
78
+ * the component remounts.
79
+ *
74
80
  * On unmount the folder is disposed and removed from the pane.
75
81
  */
76
82
  export declare function useTweakpane<T extends ParamSchema>(label: string, schema: T): ResolvedValues<T>;
77
83
  /** Sidebar width constant shared with player-page layout. */
78
84
  export declare const SIDEBAR_WIDTH = 280;
79
85
  /**
80
- * Right sidebar that hosts the tweakpane Pane. Always visible in the page
81
- * layout; the Pane mounts into the scrollable inner div. Also provides
82
- * player metadata (frame, fps, sections) to the copy prompt button.
86
+ * Right sidebar that hosts the tweakpane Pane. Collapsed by default so the
87
+ * video takes the full width; a fixed top-right button opens it. The sidebar
88
+ * container stays mounted while collapsed (hidden via display: none) because
89
+ * useTweakpane registers controls into it — unmounting would lose all
90
+ * registrations until components remount. Also provides player metadata
91
+ * (frame, fps, sections) to the copy prompt button.
83
92
  */
84
93
  export declare function TweakpaneRoot({ playerRef, fps, sections, entryPath, }: {
85
94
  playerRef: React.RefObject<PlayerRef | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"tweakpane-hook.d.ts","sourceRoot":"","sources":["../../src/vite/tweakpane-hook.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tweakpane-hook.d.ts","sourceRoot":"","sources":["../../src/vite/tweakpane-hook.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  /**
4
4
  * Tweakpane integration for the egaki video player.
5
5
  *
@@ -18,9 +18,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
18
  * Tweakpane is pure DOM (no React dependency), so there are no dual-React
19
19
  * or SSR issues. During SSR (typeof window === 'undefined') the hook
20
20
  * returns the raw default values and skips all DOM work.
21
+ *
22
+ * Live props vs user overrides: each param follows the LIVE schema value
23
+ * passed on every render (so animated props like translateX={interpolate(...)}
24
+ * work) until the user touches that control in the pane. Once touched, the
25
+ * user's value wins for that key until the component remounts. Untouched
26
+ * controls are refreshed in the pane every render so animated values are
27
+ * visible while playing. Programmatic refreshes are guarded so tweakpane's
28
+ * change events (which fire on refresh() too) don't mark keys as touched.
21
29
  */
22
30
  import { useCallback, useEffect, useRef, useState, useSyncExternalStore, createContext, useContext } from 'react';
23
31
  import { LayoutContainerContext, useIsPremounting } from "./mdx-video.js";
32
+ import { setSidebarOpen } from "./store.js";
33
+ import { useSidebarOpen } from "./store-hooks.js";
24
34
  export const TweakpaneContext = createContext(null);
25
35
  // ---------------------------------------------------------------------------
26
36
  // Global disable switch
@@ -57,6 +67,22 @@ function cloneValue(value) {
57
67
  return { ...value };
58
68
  return value;
59
69
  }
70
+ /** Deep-equal for param values: primitives, small arrays (bezier tuples),
71
+ * and flat objects (range {min, max}). */
72
+ function valuesEqual(a, b) {
73
+ if (a === b)
74
+ return true;
75
+ if (Array.isArray(a) && Array.isArray(b)) {
76
+ return a.length === b.length && a.every((x, i) => x === b[i]);
77
+ }
78
+ if (typeof a === 'object' && a !== null && typeof b === 'object' && b !== null) {
79
+ const ra = a;
80
+ const rb = b;
81
+ const ka = Object.keys(ra);
82
+ return ka.length === Object.keys(rb).length && ka.every((k) => ra[k] === rb[k]);
83
+ }
84
+ return false;
85
+ }
60
86
  function resolveDefault(v) {
61
87
  if (isCubicBezierParam(v))
62
88
  return [...v.value];
@@ -176,7 +202,10 @@ function serializeTweakpanePrompt(ctx) {
176
202
  const changed = [];
177
203
  for (const [key, current] of Object.entries(entry.params)) {
178
204
  const def = entry.defaults[key];
179
- if (current !== def) {
205
+ // Only user-touched keys count as changed. Untouched keys mirror live
206
+ // (possibly animated) props, so comparing them to mount-time defaults
207
+ // would produce false positives on every playing frame.
208
+ if (entry.touched.has(key) && !valuesEqual(current, def)) {
180
209
  changed.push(` ${key}: ${formatValue(current)}`);
181
210
  }
182
211
  }
@@ -259,6 +288,12 @@ function CopyChangesButton() {
259
288
  * Returns a reactive object with current values. Updating a control in the
260
289
  * pane immediately triggers a React re-render with fresh values.
261
290
  *
291
+ * Values follow live props: each key returns the schema value from the
292
+ * CURRENT render until the user touches that control in the pane, so
293
+ * animated props (e.g. `translateX={interpolate(frame, ...)}`) flow through
294
+ * every frame. Once touched, the user's pane value wins for that key until
295
+ * the component remounts.
296
+ *
262
297
  * On unmount the folder is disposed and removed from the pane.
263
298
  */
264
299
  export function useTweakpane(label, schema) {
@@ -281,6 +316,21 @@ export function useTweakpane(label, schema) {
281
316
  // Track the schema for updates
282
317
  const schemaRef = useRef(schema);
283
318
  schemaRef.current = schema;
319
+ // Keys the user changed via the pane UI. Untouched keys follow the live
320
+ // schema value on every render (so animated props work); touched keys keep
321
+ // the user's pane value until remount.
322
+ const touchedRef = useRef(new Set());
323
+ // Latest user override values, mirrored from state so the mount effect can
324
+ // seed rebuilt folders without re-running on every pane change.
325
+ const valuesRef = useRef(values);
326
+ valuesRef.current = values;
327
+ // Live handles to the mounted folder + bound params, used by the per-render
328
+ // sync effect below to push animated prop values into the pane.
329
+ const liveRef = useRef(null);
330
+ // True while we call folder.refresh() programmatically. tweakpane emits
331
+ // change events on refresh() too, so handlers must ignore those or every
332
+ // animated frame would mark keys as user-touched.
333
+ const refreshingRef = useRef(false);
284
334
  // Stable ID for this hook instance
285
335
  const idRef = useRef(`${label}-${Math.random().toString(36).slice(2, 8)}`);
286
336
  useEffect(() => {
@@ -297,7 +347,10 @@ export function useTweakpane(label, schema) {
297
347
  const def = resolveDefault(param);
298
348
  // Clone object/array values so tweakpane's in-place mutations on params
299
349
  // don't corrupt the defaults (used by "Copy changes" diffing).
300
- params[key] = cloneValue(def);
350
+ // Touched keys keep the user's override when the folder is rebuilt
351
+ // (e.g. premount → active transition re-runs this effect).
352
+ const touched = touchedRef.current.has(key);
353
+ params[key] = cloneValue(touched ? valuesRef.current[key] : def);
301
354
  defaults[key] = cloneValue(def);
302
355
  }
303
356
  let folder = null;
@@ -318,6 +371,8 @@ export function useTweakpane(label, schema) {
318
371
  picker: 'inline',
319
372
  });
320
373
  blade.on('change', (ev) => {
374
+ if (refreshingRef.current)
375
+ return;
321
376
  const val = ev.value;
322
377
  const tuple = [
323
378
  Math.round(val.x1 * 1000) / 1000,
@@ -326,6 +381,7 @@ export function useTweakpane(label, schema) {
326
381
  Math.round(val.y2 * 1000) / 1000,
327
382
  ];
328
383
  params[key] = tuple;
384
+ touchedRef.current.add(key);
329
385
  setValues((prev) => ({ ...prev, [key]: tuple }));
330
386
  });
331
387
  continue;
@@ -337,7 +393,10 @@ export function useTweakpane(label, schema) {
337
393
  max: param.max,
338
394
  step: param.step,
339
395
  }).on('change', (ev) => {
396
+ if (refreshingRef.current)
397
+ return;
340
398
  const val = ev.value;
399
+ touchedRef.current.add(key);
341
400
  setValues((prev) => ({ ...prev, [key]: { min: val.min, max: val.max } }));
342
401
  });
343
402
  continue;
@@ -382,15 +441,20 @@ export function useTweakpane(label, schema) {
382
441
  }
383
442
  }
384
443
  folder.addBinding(params, key, opts).on('change', (ev) => {
444
+ if (refreshingRef.current)
445
+ return;
446
+ touchedRef.current.add(key);
385
447
  setValues((prev) => ({ ...prev, [key]: ev.value }));
386
448
  });
387
449
  }
450
+ liveRef.current = { folder, params };
388
451
  // Register for copy prompt
389
- activeFolders.set(id, { label, params, defaults });
452
+ activeFolders.set(id, { label, params, defaults, touched: touchedRef.current });
390
453
  notifyFolderListeners();
391
454
  })();
392
455
  return () => {
393
456
  disposed = true;
457
+ liveRef.current = null;
394
458
  if (folder) {
395
459
  folder.dispose();
396
460
  folder = null;
@@ -403,17 +467,59 @@ export function useTweakpane(label, schema) {
403
467
  }
404
468
  };
405
469
  }, [label, skip]);
406
- return values;
470
+ // Push live (possibly animated) prop values into the pane for untouched
471
+ // keys. Runs after every render — during playback the component re-renders
472
+ // each frame, so animated values are visible moving in the pane UI.
473
+ // folder.refresh() emits change events; refreshingRef makes handlers
474
+ // ignore them so refreshes never mark keys as touched.
475
+ useEffect(() => {
476
+ const live = liveRef.current;
477
+ if (!live)
478
+ return;
479
+ let dirty = false;
480
+ for (const [key, param] of Object.entries(schemaRef.current)) {
481
+ if (touchedRef.current.has(key))
482
+ continue;
483
+ // Bezier blades are not bindings — refresh() won't read params for
484
+ // them. The returned value still follows live props below.
485
+ if (isCubicBezierParam(param))
486
+ continue;
487
+ const liveVal = resolveDefault(param);
488
+ if (!valuesEqual(live.params[key], liveVal)) {
489
+ live.params[key] = cloneValue(liveVal);
490
+ dirty = true;
491
+ }
492
+ }
493
+ if (dirty) {
494
+ refreshingRef.current = true;
495
+ live.folder.refresh();
496
+ refreshingRef.current = false;
497
+ }
498
+ });
499
+ // Merge: user-touched keys return the pane value, untouched keys follow
500
+ // the live schema value so animated props work without any extra wiring.
501
+ const merged = {};
502
+ for (const [key, param] of Object.entries(schema)) {
503
+ merged[key] = touchedRef.current.has(key) ? values[key] : resolveDefault(param);
504
+ }
505
+ return merged;
407
506
  }
408
507
  // ---------------------------------------------------------------------------
409
508
  // TweakpaneRoot — sidebar container for the tweakpane pane
410
509
  // ---------------------------------------------------------------------------
411
510
  /** Sidebar width constant shared with player-page layout. */
412
511
  export const SIDEBAR_WIDTH = 280;
512
+ /** Lucide-style panel-right icon used for both opening and closing the sidebar. */
513
+ function PanelRightIcon() {
514
+ return (_jsxs("svg", { width: '16', height: '16', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '1.5', strokeLinecap: 'round', strokeLinejoin: 'round', children: [_jsx("rect", { x: '3', y: '3', width: '18', height: '18', rx: '2' }), _jsx("line", { x1: '15', y1: '3', x2: '15', y2: '21' })] }));
515
+ }
413
516
  /**
414
- * Right sidebar that hosts the tweakpane Pane. Always visible in the page
415
- * layout; the Pane mounts into the scrollable inner div. Also provides
416
- * player metadata (frame, fps, sections) to the copy prompt button.
517
+ * Right sidebar that hosts the tweakpane Pane. Collapsed by default so the
518
+ * video takes the full width; a fixed top-right button opens it. The sidebar
519
+ * container stays mounted while collapsed (hidden via display: none) because
520
+ * useTweakpane registers controls into it — unmounting would lose all
521
+ * registrations until components remount. Also provides player metadata
522
+ * (frame, fps, sections) to the copy prompt button.
417
523
  */
418
524
  export function TweakpaneRoot({ playerRef, fps, sections, entryPath, }) {
419
525
  // Ref callback sets the container synchronously when the DOM node mounts,
@@ -435,16 +541,18 @@ export function TweakpaneRoot({ playerRef, fps, sections, entryPath, }) {
435
541
  contextRef = null;
436
542
  };
437
543
  }, [playerRef, fps, sections, entryPath]);
438
- return (_jsxs("div", { className: 'egaki-sidebar', style: {
439
- width: SIDEBAR_WIDTH,
440
- minWidth: SIDEBAR_WIDTH,
441
- height: '100vh',
442
- position: 'sticky',
443
- top: 0,
444
- overflowY: 'auto',
445
- overflowX: 'hidden',
446
- background: '#111',
447
- borderLeft: '1px solid rgba(255,255,255,0.08)',
448
- flexShrink: 0,
449
- }, children: [_jsx(CopyChangesButton, {}), _jsx("div", { ref: containerRefCallback, id: 'egaki-tweakpane', style: { width: '100%' } })] }));
544
+ const sidebarOpen = useSidebarOpen();
545
+ return (_jsxs(_Fragment, { children: [!sidebarOpen && (_jsx("button", { onClick: () => setSidebarOpen(true), title: 'Open controls sidebar', className: 'fixed top-4 right-4 z-10 flex items-center justify-center rounded-lg w-8 h-8 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer', children: _jsx(PanelRightIcon, {}) })), _jsxs("div", { className: 'egaki-sidebar', style: {
546
+ width: SIDEBAR_WIDTH,
547
+ minWidth: SIDEBAR_WIDTH,
548
+ height: '100vh',
549
+ position: 'sticky',
550
+ top: 0,
551
+ overflowY: 'auto',
552
+ overflowX: 'hidden',
553
+ background: '#111',
554
+ borderLeft: '1px solid rgba(255,255,255,0.08)',
555
+ flexShrink: 0,
556
+ display: sidebarOpen ? undefined : 'none',
557
+ }, children: [_jsx("div", { className: 'flex items-center justify-end px-2 pt-2', children: _jsx("button", { onClick: () => setSidebarOpen(false), title: 'Close controls sidebar', className: 'flex items-center justify-center rounded-lg w-8 h-8 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer', children: _jsx(PanelRightIcon, {}) }) }), _jsx(CopyChangesButton, {}), _jsx("div", { ref: containerRefCallback, id: 'egaki-tweakpane', style: { width: '100%' } })] })] }));
450
558
  }
@@ -18,11 +18,5 @@ export interface VideoPluginOptions {
18
18
  * All other .mdx files in the project become additional routes. */
19
19
  entry?: string;
20
20
  }
21
- /**
22
- * Compare old and new MDX sources section-by-section. Returns the index
23
- * of the first added or edited section, or null if nothing changed (or
24
- * only deletions occurred, which we skip per design).
25
- */
26
- export declare function findChangedSectionIndex(oldSource: string, newSource: string): number | null;
27
21
  export declare function video(options?: VideoPluginOptions): PluginOption[];
28
22
  //# sourceMappingURL=vite-plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../../src/vite/vite-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAA;AA4BhD,MAAM,WAAW,kBAAkB;IACjC;;wEAEoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAuCD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB3F;AAoCD,wBAAgB,KAAK,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,YAAY,EAAE,CAwalE"}
1
+ {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../../src/vite/vite-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAA;AA4BhD,MAAM,WAAW,kBAAkB;IACjC;;wEAEoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAiDD,wBAAgB,KAAK,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,YAAY,EAAE,CAoYlE"}
@@ -46,60 +46,6 @@ function resolveSourceToFile(root, source) {
46
46
  }
47
47
  return undefined;
48
48
  }
49
- /**
50
- * Extract raw source text ranges per section directly from the mdast.
51
- * Works with heading nodes' positions to capture the FULL raw heading
52
- * text including props like `duration=3s` and `transition=20` that
53
- * `splitIntoSections` strips during parsing. Each section spans from
54
- * its heading's start offset to the next heading's start (or EOF).
55
- */
56
- function sectionRawTexts(source, mdast) {
57
- // Collect the start offsets of each heading node
58
- const headingStarts = [];
59
- for (const node of mdast.children) {
60
- if (node.type === 'heading' && node.position?.start?.offset != null) {
61
- headingStarts.push(node.position.start.offset);
62
- }
63
- }
64
- // Slice source between consecutive headings, trimming trailing whitespace
65
- // so boundary changes (adding/removing a following section) don't cause
66
- // false positives on the preceding section.
67
- return headingStarts.map((start, i) => {
68
- const end = i + 1 < headingStarts.length ? headingStarts[i + 1] : source.length;
69
- return source.slice(start, end).trimEnd();
70
- });
71
- }
72
- /**
73
- * Compare old and new MDX sources section-by-section. Returns the index
74
- * of the first added or edited section, or null if nothing changed (or
75
- * only deletions occurred, which we skip per design).
76
- */
77
- export function findChangedSectionIndex(oldSource, newSource) {
78
- try {
79
- const oldTexts = sectionRawTexts(oldSource, mdxParse(oldSource));
80
- const newTexts = sectionRawTexts(newSource, mdxParse(newSource));
81
- const maxLen = Math.max(oldTexts.length, newTexts.length);
82
- for (let i = 0; i < maxLen; i++) {
83
- if (i >= oldTexts.length)
84
- return i; // new section added
85
- if (i >= newTexts.length)
86
- return null; // section deleted, skip
87
- if (oldTexts[i] !== newTexts[i]) {
88
- // Distinguish edit from deletion: if the next old section matches
89
- // the current new section, a section was deleted (shifted down).
90
- // Look ahead to detect deletions at any position, not just the tail.
91
- if (newTexts.length < oldTexts.length && oldTexts[i + 1] === newTexts[i]) {
92
- return null; // deletion, skip
93
- }
94
- return i; // content changed
95
- }
96
- }
97
- return null; // no scene-level change (frontmatter/preamble only)
98
- }
99
- catch {
100
- return null; // parse error, don't seek
101
- }
102
- }
103
49
  /** Discover .mdx files in the project root directory (non-recursive).
104
50
  * Only root-level files become entries to avoid import resolution issues
105
51
  * with nested paths. Returns a map of routePath → absolutePath. */
@@ -146,8 +92,6 @@ export function video(options) {
146
92
  let defaultRoute = '';
147
93
  /** Whether the user's project has `motion` (framer-motion) installed. */
148
94
  let hasMotion = false;
149
- /** Cached previous MDX sources for section-level diff detection, keyed by abs path. */
150
- const previousMdxSources = new Map();
151
95
  /** Set of absolute paths of all entry MDX files for quick lookup. */
152
96
  let entryPathSet = new Set();
153
97
  /** Is this file referenced inside a <Server> block of any entry MDX?
@@ -193,13 +137,6 @@ export function video(options) {
193
137
  }
194
138
  defaultRoute = resolveDefaultRoute(mdxEntries, options?.entry, root);
195
139
  entryPathSet = new Set(mdxEntries.values());
196
- // Seed previous sources for section-level diff detection.
197
- for (const [, absPath] of mdxEntries) {
198
- try {
199
- previousMdxSources.set(absPath, fs.readFileSync(absPath, 'utf-8'));
200
- }
201
- catch { /* ignore read errors */ }
202
- }
203
140
  // Auto-generate egaki-env.d.ts so MDX LSP knows about built-in
204
141
  // components via the global MDXProvidedComponents type. Same
205
142
  // pattern Vite uses for vite-env.d.ts.
@@ -407,21 +344,7 @@ export function video(options) {
407
344
  mdxEntries.set(relPath.replace(/\.mdx$/, ''), absEntry);
408
345
  }
409
346
  defaultRoute = resolveDefaultRoute(mdxEntries, options?.entry, root);
410
- const nextEntryPathSet = new Set(mdxEntries.values());
411
- // Sync previousMdxSources: seed new entries, prune removed ones.
412
- for (const file of previousMdxSources.keys()) {
413
- if (!nextEntryPathSet.has(file))
414
- previousMdxSources.delete(file);
415
- }
416
- for (const file of nextEntryPathSet) {
417
- if (!previousMdxSources.has(file)) {
418
- try {
419
- previousMdxSources.set(file, fs.readFileSync(file, 'utf-8'));
420
- }
421
- catch { /* file may be mid-write */ }
422
- }
423
- }
424
- entryPathSet = nextEntryPathSet;
347
+ entryPathSet = new Set(mdxEntries.values());
425
348
  invalidateVirtual([RESOLVED_APP, RESOLVED_MDX, RESOLVED_MODULES]);
426
349
  if (this.environment.name === 'client') {
427
350
  ctx.server.environments.client?.hot.send({ type: 'full-reload' });
@@ -431,25 +354,11 @@ export function video(options) {
431
354
  if (isEntryMdx) {
432
355
  invalidateVirtual([RESOLVED_APP, RESOLVED_MDX]);
433
356
  if (this.environment.name === 'client') {
434
- // Section-level diff detection for the changed entry.
435
- const newMdxSource = fs.readFileSync(normalizedFile, 'utf-8');
436
- const prevSource = previousMdxSources.get(normalizedFile);
437
- const changedSection = prevSource != null
438
- ? findChangedSectionIndex(prevSource, newMdxSource)
439
- : null;
440
- previousMdxSources.set(normalizedFile, newMdxSource);
441
357
  ctx.server.environments.client?.hot.send({
442
358
  type: 'custom',
443
359
  event: 'rsc:update',
444
360
  data: { file: ctx.file },
445
361
  });
446
- if (changedSection != null) {
447
- ctx.server.environments.client?.hot.send({
448
- type: 'custom',
449
- event: 'egaki:scene-changed',
450
- data: { sectionIndex: changedSection, file: normalizedFile },
451
- });
452
- }
453
362
  }
454
363
  return [];
455
364
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "egaki",
3
- "version": "0.7.2",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "AI image, video, and speech generation CLI with MDX-to-video framework",
6
6
  "main": "./dist/cli/main.js",
@@ -88,9 +88,9 @@
88
88
  "@ai-sdk/xai": "^3.0.92",
89
89
  "@clack/prompts": "^1.0.1",
90
90
  "@fal-ai/client": "^1.10.1",
91
- "@remotion/media": "4.0.475",
92
- "@remotion/player": "4.0.475",
93
- "@remotion/web-renderer": "4.0.475",
91
+ "@remotion/media": "4.0.494",
92
+ "@remotion/player": "4.0.494",
93
+ "@remotion/web-renderer": "4.0.494",
94
94
  "@tailwindcss/vite": "^4.2.2",
95
95
  "@tweakpane/core": "^2.0.5",
96
96
  "@tweakpane/plugin-essentials": "^0.2.1",
@@ -98,12 +98,12 @@
98
98
  "eventsource-parser": "^3.0.6",
99
99
  "goke": "^6.12.1",
100
100
  "js-yaml": "^4.1.1",
101
- "mediabunny": "^1.46.0",
101
+ "mediabunny": "^1.50.8",
102
102
  "moveable": "^0.53.0",
103
103
  "picocolors": "^1.1.1",
104
104
  "react": "19.2.7",
105
105
  "react-dom": "19.2.7",
106
- "remotion": "4.0.475",
106
+ "remotion": "4.0.494",
107
107
  "safe-mdx": "^1.11.3",
108
108
  "shiki": "^4.2.0",
109
109
  "string-dedent": "^3.0.2",
@@ -135,6 +135,9 @@
135
135
  "optional": true
136
136
  }
137
137
  },
138
+ "optionalDependencies": {
139
+ "node-web-audio-api": ">=1"
140
+ },
138
141
  "devDependencies": {
139
142
  "@types/js-yaml": "^4.0.9",
140
143
  "@types/react": "^19.2.17",