@pixerate/editor 0.160.0 → 0.162.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 (146) hide show
  1. package/dist/agent/index.cjs +36 -35
  2. package/dist/agent/index.js +7 -6
  3. package/dist/animation/index.cjs +5 -4
  4. package/dist/animation/index.js +4 -3
  5. package/dist/animation-editor/index.cjs +62 -61
  6. package/dist/animation-editor/index.js +6 -5
  7. package/dist/canvas/index.cjs +33 -32
  8. package/dist/canvas/index.js +6 -5
  9. package/dist/chart/index.cjs +88 -87
  10. package/dist/chart/index.js +7 -6
  11. package/dist/{chunk-QMI5HZWA.cjs → chunk-236TKNUR.cjs} +3 -3
  12. package/dist/{chunk-6UPGM3JB.js → chunk-2TKHLX6H.js} +1 -1
  13. package/dist/{chunk-3YDQLQH2.cjs → chunk-32MMQYCQ.cjs} +81 -81
  14. package/dist/{chunk-MHEXLG3M.cjs → chunk-3GJH2MGH.cjs} +37 -37
  15. package/dist/{chunk-AKDWAWSY.cjs → chunk-3IHNXRUV.cjs} +45 -45
  16. package/dist/{chunk-VKF3PWKR.cjs → chunk-3OSXHLIT.cjs} +259 -28
  17. package/dist/{chunk-TVO6W2Q6.cjs → chunk-4NGVHHJB.cjs} +17 -17
  18. package/dist/chunk-4VU4CP7R.js +769 -0
  19. package/dist/chunk-6DM3PUQD.cjs +767 -0
  20. package/dist/{chunk-DBIVICAO.js → chunk-7CKPUUN4.js} +1 -1
  21. package/dist/{chunk-57JLBGJ5.cjs → chunk-7YBDRJXT.cjs} +8 -8
  22. package/dist/{chunk-KY3FYKP3.js → chunk-BQWGN5WH.js} +1 -1
  23. package/dist/{chunk-LEUZ4FRC.cjs → chunk-CIPIESX4.cjs} +3 -3
  24. package/dist/chunk-CJE5MOIS.js +347 -0
  25. package/dist/{chunk-5QBRAIVK.js → chunk-CX6JLDFC.js} +3 -3
  26. package/dist/{chunk-R3QY7PPL.js → chunk-DTSTNRPH.js} +1 -1
  27. package/dist/{chunk-6LUYFHHN.cjs → chunk-EQ46F75O.cjs} +51 -51
  28. package/dist/{chunk-UKCJ43YL.cjs → chunk-ES3UEVUT.cjs} +130 -898
  29. package/dist/{chunk-UM3RPYC5.js → chunk-EVPZ5IKN.js} +1 -1
  30. package/dist/{chunk-XNQ5EFB4.cjs → chunk-EY22AY6R.cjs} +16 -16
  31. package/dist/chunk-FKUKJ3XA.cjs +347 -0
  32. package/dist/{chunk-YABNLNY7.js → chunk-GLMP22FT.js} +3 -3
  33. package/dist/{chunk-SUF6WRXR.cjs → chunk-GTELFJRE.cjs} +2 -2
  34. package/dist/{chunk-72BGL3I2.js → chunk-GWV4T5NZ.js} +1 -1
  35. package/dist/{chunk-AY65VQSU.js → chunk-H3OEM3Q2.js} +1 -1
  36. package/dist/{chunk-4YWAEV4W.cjs → chunk-HDGBM7EA.cjs} +26 -26
  37. package/dist/{chunk-H5XX4KHB.js → chunk-HLOKB7WW.js} +5 -3
  38. package/dist/{chunk-WNAAWLZ5.js → chunk-KGTF55R3.js} +2 -2
  39. package/dist/{chunk-WPPJH6X4.cjs → chunk-LL3H5XUH.cjs} +6 -4
  40. package/dist/{chunk-4T44CJ5K.cjs → chunk-NA4UVVJ4.cjs} +15 -15
  41. package/dist/{chunk-Q6PA362P.js → chunk-NO5QQDHZ.js} +2 -2
  42. package/dist/{chunk-AA34BA7G.js → chunk-QYZGBVNC.js} +30 -798
  43. package/dist/chunk-S6X2QRVU.cjs +769 -0
  44. package/dist/{chunk-SSCL7QQ4.js → chunk-XDCFJEU7.js} +2 -2
  45. package/dist/{chunk-IQ6KKXOU.js → chunk-YN4AYYBJ.js} +243 -12
  46. package/dist/chunk-ZGOHKS3X.js +767 -0
  47. package/dist/color/index.cjs +6 -2
  48. package/dist/color/index.d.cts +1 -0
  49. package/dist/color/index.d.ts +1 -0
  50. package/dist/color/index.js +28 -24
  51. package/dist/color/suggest.d.cts +12 -0
  52. package/dist/color/suggest.d.ts +12 -0
  53. package/dist/effects/index.cjs +44 -771
  54. package/dist/effects/index.js +34 -761
  55. package/dist/effects/ui.d.cts +20 -3
  56. package/dist/effects/ui.d.ts +20 -3
  57. package/dist/export/index.cjs +6 -5
  58. package/dist/export/index.js +5 -4
  59. package/dist/extensions/index.cjs +9 -8
  60. package/dist/extensions/index.js +8 -7
  61. package/dist/gradient/index.cjs +28 -26
  62. package/dist/gradient/index.js +7 -5
  63. package/dist/image-editor/index.cjs +638 -637
  64. package/dist/image-editor/index.js +5 -4
  65. package/dist/index.cjs +15 -14
  66. package/dist/index.js +9 -8
  67. package/dist/paint-editor/index.cjs +17 -16
  68. package/dist/paint-editor/index.js +6 -5
  69. package/dist/typography/accessibility.d.cts +44 -0
  70. package/dist/typography/accessibility.d.ts +44 -0
  71. package/dist/typography/catalog.d.cts +98 -0
  72. package/dist/typography/catalog.d.ts +98 -0
  73. package/dist/typography/index.cjs +1099 -0
  74. package/dist/typography/index.d.cts +16 -0
  75. package/dist/typography/index.d.ts +16 -0
  76. package/dist/typography/index.js +1099 -0
  77. package/dist/typography/metrics.d.cts +31 -0
  78. package/dist/typography/metrics.d.ts +31 -0
  79. package/dist/typography/picker.d.cts +132 -0
  80. package/dist/typography/picker.d.ts +132 -0
  81. package/dist/typography/registry.d.cts +66 -0
  82. package/dist/typography/registry.d.ts +66 -0
  83. package/dist/typography/scale.d.cts +62 -0
  84. package/dist/typography/scale.d.ts +62 -0
  85. package/dist/typography/segment.d.cts +26 -0
  86. package/dist/typography/segment.d.ts +26 -0
  87. package/dist/typography/stack.d.cts +26 -0
  88. package/dist/typography/stack.d.ts +26 -0
  89. package/dist/typography/tools.d.cts +34 -0
  90. package/dist/typography/tools.d.ts +34 -0
  91. package/dist/typography/types.d.cts +96 -0
  92. package/dist/typography/types.d.ts +96 -0
  93. package/dist/typography/weight.d.cts +13 -0
  94. package/dist/typography/weight.d.ts +13 -0
  95. package/dist/vector/index.cjs +950 -949
  96. package/dist/vector/index.js +5 -4
  97. package/dist/video-editor/audio.d.cts +9 -0
  98. package/dist/video-editor/audio.d.ts +9 -0
  99. package/dist/video-editor/check.d.cts +1 -1
  100. package/dist/video-editor/check.d.ts +1 -1
  101. package/dist/video-editor/controller.d.cts +47 -0
  102. package/dist/video-editor/controller.d.ts +47 -0
  103. package/dist/video-editor/dialogue-tools.d.cts +19 -0
  104. package/dist/video-editor/dialogue-tools.d.ts +19 -0
  105. package/dist/video-editor/effect-authoring.d.cts +54 -0
  106. package/dist/video-editor/effect-authoring.d.ts +54 -0
  107. package/dist/video-editor/evaluate.d.cts +11 -0
  108. package/dist/video-editor/evaluate.d.ts +11 -0
  109. package/dist/video-editor/export.d.cts +7 -1
  110. package/dist/video-editor/export.d.ts +7 -1
  111. package/dist/video-editor/index.cjs +1857 -531
  112. package/dist/video-editor/index.d.cts +7 -0
  113. package/dist/video-editor/index.d.ts +7 -0
  114. package/dist/video-editor/index.js +1516 -190
  115. package/dist/video-editor/lut.d.cts +12 -2
  116. package/dist/video-editor/lut.d.ts +12 -2
  117. package/dist/video-editor/mask.d.cts +48 -0
  118. package/dist/video-editor/mask.d.ts +48 -0
  119. package/dist/video-editor/mix.d.cts +107 -0
  120. package/dist/video-editor/mix.d.ts +107 -0
  121. package/dist/video-editor/music-tools.d.cts +19 -0
  122. package/dist/video-editor/music-tools.d.ts +19 -0
  123. package/dist/video-editor/parse.d.cts +3 -1
  124. package/dist/video-editor/parse.d.ts +3 -1
  125. package/dist/video-editor/plan.d.cts +75 -0
  126. package/dist/video-editor/plan.d.ts +75 -0
  127. package/dist/video-editor/render.d.cts +1 -1
  128. package/dist/video-editor/render.d.ts +1 -1
  129. package/dist/video-editor/timeline.d.cts +2 -0
  130. package/dist/video-editor/timeline.d.ts +2 -0
  131. package/dist/video-editor/tools.d.cts +90 -0
  132. package/dist/video-editor/tools.d.ts +90 -0
  133. package/dist/video-editor/transcript-controller.d.cts +106 -0
  134. package/dist/video-editor/transcript-controller.d.ts +106 -0
  135. package/dist/video-editor/transcript.d.cts +40 -0
  136. package/dist/video-editor/transcript.d.ts +40 -0
  137. package/dist/video-editor/types.d.cts +62 -2
  138. package/dist/video-editor/types.d.ts +62 -2
  139. package/dist/video-editor/ui.d.cts +21 -1
  140. package/dist/video-editor/ui.d.ts +21 -1
  141. package/package.json +11 -1
  142. package/skills/README.md +3 -0
  143. package/skills/pixerate-bgm-alignment/SKILL.md +82 -0
  144. package/skills/pixerate-talking-head-edit/SKILL.md +80 -0
  145. package/skills/pixerate-typography/SKILL.md +91 -0
  146. package/skills/pixerate-video-editor/SKILL.md +40 -2
@@ -85,3 +85,43 @@ export declare function videoWordsRange(words: readonly VideoTimelineWord[], a:
85
85
  export declare function videoTranscriptEdit(assetId: string, text: string): VideoEdit;
86
86
  /** The word playing at a timeline time, as its index in `words`, or -1. */
87
87
  export declare function videoWordAt(words: readonly VideoTimelineWord[], time: number): number;
88
+ export interface VideoRepetitionOptions {
89
+ /** Longest phrase to look for repeated (words, default 3). */
90
+ maxPhrase?: number;
91
+ /** Longest gap between a phrase and its repeat that still counts as a stutter (ms, default 1000). */
92
+ maxGap?: number;
93
+ }
94
+ /**
95
+ * Words said twice in a row ("I I think", "we should we should"), as timeline ranges covering the
96
+ * first saying and the gap up to the repeat, so cutting them leaves the phrase said once. Longer
97
+ * phrases are tried first; "I I I" gives two ranges. Some repeats are meant ("very very"): check them.
98
+ */
99
+ export declare function findVideoRepetitions(words: readonly VideoTimelineWord[], options?: VideoRepetitionOptions): VideoTranscriptRange[];
100
+ export type VideoDisfluencyKind = 'filler' | 'pause' | 'repetition';
101
+ export declare const VIDEO_DISFLUENCY_KINDS: readonly ["filler", "pause", "repetition"];
102
+ /** Something to tidy out of speech: where it is (timeline ms), what kind and what was said. */
103
+ export interface VideoDisfluency extends VideoTranscriptRange {
104
+ kind: VideoDisfluencyKind;
105
+ /** Indexes in the word list of the first and last word it covers (for a pause: the words either side). */
106
+ words: [number, number];
107
+ }
108
+ export interface VideoDisfluencyOptions {
109
+ /** Which kinds to look for (default all). */
110
+ kinds?: readonly VideoDisfluencyKind[];
111
+ /** Filler words and phrases (default `VIDEO_DEFAULT_FILLERS`). */
112
+ fillers?: readonly string[];
113
+ /** How pauses are measured (default: over 700 ms, keeping 150 ms of breath either side). */
114
+ pauses?: VideoPauseOptions;
115
+ repetitions?: VideoRepetitionOptions;
116
+ }
117
+ /**
118
+ * Fillers, long pauses and stutters in one pass, in time order, each with the words it covers, so
119
+ * a transcript can mark them and an agent can review them before cutting. Overlapping finds are
120
+ * all kept (a filler inside a repetition): `mergeVideoRanges` joins them when they are cut.
121
+ */
122
+ export declare function detectVideoDisfluencies(words: readonly VideoTimelineWord[], options?: VideoDisfluencyOptions): VideoDisfluency[];
123
+ /** Cut ranges shrunk by `keep` ms at each end, so a little breath stays either side; ranges that vanish are dropped. */
124
+ export declare function videoCutsWithMargin<T extends {
125
+ from: number;
126
+ to: number;
127
+ }>(ranges: readonly T[], keep: number): T[];
@@ -160,6 +160,12 @@ export interface VideoGradeLut {
160
160
  strength?: number;
161
161
  /** A label for the look (default the file's TITLE or name). */
162
162
  name?: string;
163
+ /**
164
+ * How much of the LUT's change to hold back on skin tones, 0–1 (default 0): at 1 a pixel that
165
+ * looks like skin keeps its graded-but-un-LUT'd colour, so a heavy creative look leaves faces
166
+ * natural. Skin is found by chroma (see `videoSkinToneWeight`), with a soft edge.
167
+ */
168
+ preserveSkinTones?: number;
163
169
  }
164
170
  /**
165
171
  * Colour correction for a clip's picture (see `VIDEO_GRADE_CONTROLS` for ranges). Applied in a fixed
@@ -222,6 +228,41 @@ interface VideoClipBase {
222
228
  transition?: VideoTransition;
223
229
  /** Provider effects on the picture, applied in order (see `VideoClipEffect`). */
224
230
  effects?: VideoClipEffect[];
231
+ /** A shape that cuts the picture out (see `VideoClipMask`). */
232
+ mask?: VideoClipMask;
233
+ }
234
+ /**
235
+ * The shape of a clip mask: `rectangle` (with `cornerRadius`), `ellipse`, `linear` (a straight
236
+ * split: what is on the side the angle points away from shows) or `polygon` (`points`).
237
+ */
238
+ export type VideoMaskShape = 'rectangle' | 'ellipse' | 'linear' | 'polygon';
239
+ export declare const VIDEO_MASK_SHAPES: readonly ["rectangle", "ellipse", "linear", "polygon"];
240
+ /**
241
+ * A parametric mask on a clip's picture: only the inside of the shape shows (the outside with
242
+ * `inverted`). Positions and sizes are fractions of the clip's placed picture (0–1 across its
243
+ * width and height), so the mask moves, scales and turns with the clip.
244
+ */
245
+ export interface VideoClipMask {
246
+ shape: VideoMaskShape;
247
+ /** Centre of the shape (or a point on a linear split's edge), as fractions of the picture (default 0.5, 0.5). */
248
+ x?: number;
249
+ y?: number;
250
+ /** Size as fractions of the picture's width and height (default 0.5 each; unused by linear). */
251
+ width?: number;
252
+ height?: number;
253
+ /** Degrees clockwise (default 0). For linear, 0 shows the top half. */
254
+ rotation?: number;
255
+ /** Soft edge width, in frame pixels (default 0). */
256
+ feather?: number;
257
+ /** Rectangle corners, as a fraction of half the shorter side (0–1, default 0). */
258
+ cornerRadius?: number;
259
+ /** Show the outside of the shape instead. */
260
+ inverted?: boolean;
261
+ /** For polygon: at least 3 points, as fractions of the picture. */
262
+ points?: {
263
+ x: number;
264
+ y: number;
265
+ }[];
225
266
  }
226
267
  /**
227
268
  * The shape of a sound fade, the same curves ffmpeg's `afade` draws, so previews and exports agree.
@@ -306,7 +347,26 @@ export interface VideoSequenceClip extends VideoClipBase {
306
347
  export interface VideoEffectClip extends VideoClipBase, VideoEffectRef {
307
348
  type: 'effect';
308
349
  }
309
- export type VideoClip = VideoMediaClip | VideoTextClip | VideoColorClip | VideoSequenceClip | VideoEffectClip;
350
+ /**
351
+ * Which pictures an adjustment clip changes: `underneath` (every video track below its own, the
352
+ * default, as adjustment layers work in most editors), `all` (the whole frame, tracks above it
353
+ * included) or `track` (only the pictures of `targetTrack`).
354
+ */
355
+ export type VideoAdjustmentScope = 'underneath' | 'all' | 'track';
356
+ export declare const VIDEO_ADJUSTMENT_SCOPES: readonly ["underneath", "all", "track"];
357
+ /**
358
+ * An adjustment clip: no picture of its own, but while it plays its grade, provider effects and
359
+ * mask apply to the composite of the pictures in its scope (see `VideoAdjustmentScope`). Its
360
+ * opacity (and fades) mix the adjusted picture over the unadjusted one. Lives on a video track.
361
+ */
362
+ export interface VideoAdjustmentClip extends VideoClipBase {
363
+ type: 'adjustment';
364
+ /** Default `underneath`. */
365
+ scope?: VideoAdjustmentScope;
366
+ /** For scope `track`: the id of the track whose pictures it changes. */
367
+ targetTrack?: string;
368
+ }
369
+ export type VideoClip = VideoMediaClip | VideoTextClip | VideoColorClip | VideoSequenceClip | VideoEffectClip | VideoAdjustmentClip;
310
370
  export type VideoClipType = VideoClip['type'];
311
371
  /**
312
372
  * How a clip is placed where others already are: `refuse` an overlap (the default), `overwrite`
@@ -315,7 +375,7 @@ export type VideoClipType = VideoClip['type'];
315
375
  */
316
376
  export type VideoPlacementMode = 'refuse' | 'overwrite' | 'insert';
317
377
  export declare const VIDEO_PLACEMENT_MODES: readonly ["refuse", "overwrite", "insert"];
318
- export declare const VIDEO_CLIP_TYPES: readonly ["media", "text", "color", "sequence", "effect"];
378
+ export declare const VIDEO_CLIP_TYPES: readonly ["media", "text", "color", "sequence", "effect", "adjustment"];
319
379
  export interface VideoMarker {
320
380
  id: string;
321
381
  time: number;
@@ -160,6 +160,12 @@ export interface VideoGradeLut {
160
160
  strength?: number;
161
161
  /** A label for the look (default the file's TITLE or name). */
162
162
  name?: string;
163
+ /**
164
+ * How much of the LUT's change to hold back on skin tones, 0–1 (default 0): at 1 a pixel that
165
+ * looks like skin keeps its graded-but-un-LUT'd colour, so a heavy creative look leaves faces
166
+ * natural. Skin is found by chroma (see `videoSkinToneWeight`), with a soft edge.
167
+ */
168
+ preserveSkinTones?: number;
163
169
  }
164
170
  /**
165
171
  * Colour correction for a clip's picture (see `VIDEO_GRADE_CONTROLS` for ranges). Applied in a fixed
@@ -222,6 +228,41 @@ interface VideoClipBase {
222
228
  transition?: VideoTransition;
223
229
  /** Provider effects on the picture, applied in order (see `VideoClipEffect`). */
224
230
  effects?: VideoClipEffect[];
231
+ /** A shape that cuts the picture out (see `VideoClipMask`). */
232
+ mask?: VideoClipMask;
233
+ }
234
+ /**
235
+ * The shape of a clip mask: `rectangle` (with `cornerRadius`), `ellipse`, `linear` (a straight
236
+ * split: what is on the side the angle points away from shows) or `polygon` (`points`).
237
+ */
238
+ export type VideoMaskShape = 'rectangle' | 'ellipse' | 'linear' | 'polygon';
239
+ export declare const VIDEO_MASK_SHAPES: readonly ["rectangle", "ellipse", "linear", "polygon"];
240
+ /**
241
+ * A parametric mask on a clip's picture: only the inside of the shape shows (the outside with
242
+ * `inverted`). Positions and sizes are fractions of the clip's placed picture (0–1 across its
243
+ * width and height), so the mask moves, scales and turns with the clip.
244
+ */
245
+ export interface VideoClipMask {
246
+ shape: VideoMaskShape;
247
+ /** Centre of the shape (or a point on a linear split's edge), as fractions of the picture (default 0.5, 0.5). */
248
+ x?: number;
249
+ y?: number;
250
+ /** Size as fractions of the picture's width and height (default 0.5 each; unused by linear). */
251
+ width?: number;
252
+ height?: number;
253
+ /** Degrees clockwise (default 0). For linear, 0 shows the top half. */
254
+ rotation?: number;
255
+ /** Soft edge width, in frame pixels (default 0). */
256
+ feather?: number;
257
+ /** Rectangle corners, as a fraction of half the shorter side (0–1, default 0). */
258
+ cornerRadius?: number;
259
+ /** Show the outside of the shape instead. */
260
+ inverted?: boolean;
261
+ /** For polygon: at least 3 points, as fractions of the picture. */
262
+ points?: {
263
+ x: number;
264
+ y: number;
265
+ }[];
225
266
  }
226
267
  /**
227
268
  * The shape of a sound fade, the same curves ffmpeg's `afade` draws, so previews and exports agree.
@@ -306,7 +347,26 @@ export interface VideoSequenceClip extends VideoClipBase {
306
347
  export interface VideoEffectClip extends VideoClipBase, VideoEffectRef {
307
348
  type: 'effect';
308
349
  }
309
- export type VideoClip = VideoMediaClip | VideoTextClip | VideoColorClip | VideoSequenceClip | VideoEffectClip;
350
+ /**
351
+ * Which pictures an adjustment clip changes: `underneath` (every video track below its own, the
352
+ * default, as adjustment layers work in most editors), `all` (the whole frame, tracks above it
353
+ * included) or `track` (only the pictures of `targetTrack`).
354
+ */
355
+ export type VideoAdjustmentScope = 'underneath' | 'all' | 'track';
356
+ export declare const VIDEO_ADJUSTMENT_SCOPES: readonly ["underneath", "all", "track"];
357
+ /**
358
+ * An adjustment clip: no picture of its own, but while it plays its grade, provider effects and
359
+ * mask apply to the composite of the pictures in its scope (see `VideoAdjustmentScope`). Its
360
+ * opacity (and fades) mix the adjusted picture over the unadjusted one. Lives on a video track.
361
+ */
362
+ export interface VideoAdjustmentClip extends VideoClipBase {
363
+ type: 'adjustment';
364
+ /** Default `underneath`. */
365
+ scope?: VideoAdjustmentScope;
366
+ /** For scope `track`: the id of the track whose pictures it changes. */
367
+ targetTrack?: string;
368
+ }
369
+ export type VideoClip = VideoMediaClip | VideoTextClip | VideoColorClip | VideoSequenceClip | VideoEffectClip | VideoAdjustmentClip;
310
370
  export type VideoClipType = VideoClip['type'];
311
371
  /**
312
372
  * How a clip is placed where others already are: `refuse` an overlap (the default), `overwrite`
@@ -315,7 +375,7 @@ export type VideoClipType = VideoClip['type'];
315
375
  */
316
376
  export type VideoPlacementMode = 'refuse' | 'overwrite' | 'insert';
317
377
  export declare const VIDEO_PLACEMENT_MODES: readonly ["refuse", "overwrite", "insert"];
318
- export declare const VIDEO_CLIP_TYPES: readonly ["media", "text", "color", "sequence", "effect"];
378
+ export declare const VIDEO_CLIP_TYPES: readonly ["media", "text", "color", "sequence", "effect", "adjustment"];
319
379
  export interface VideoMarker {
320
380
  id: string;
321
381
  time: number;
@@ -1,6 +1,6 @@
1
1
  import type { VideoEdit } from './edit.cjs';
2
2
  import { type VideoKeyframeProperty } from './keyframes.cjs';
3
- import type { VideoClipAnimationEffect, VideoTransitionType, VideoAsset, VideoAssetKind, VideoBlendMode, VideoClip, VideoFadeCurve, VideoGradeLut, VideoPlacementMode, VideoProject, VideoSequence } from './types.cjs';
3
+ import type { VideoAdjustmentScope, VideoClipMask, VideoMaskShape, VideoClipAnimationEffect, VideoTransitionType, VideoAsset, VideoAssetKind, VideoBlendMode, VideoClip, VideoFadeCurve, VideoGradeLut, VideoPlacementMode, VideoProject, VideoSequence } from './types.cjs';
4
4
  export interface VideoRulerTick {
5
5
  time: number;
6
6
  /** Pixels from the timeline's left edge. */
@@ -125,3 +125,23 @@ export declare function videoKeyframeToggleEdit(project: VideoProject, clipId: s
125
125
  * `null`. Clips without a grade change are left out; one `controller.apply` makes it one undo step.
126
126
  */
127
127
  export declare function videoLutEdits(project: VideoProject, clips: readonly string[], lut: VideoGradeLut | null): VideoEdit[];
128
+ /** Labels for adjustment scopes, for inspectors. */
129
+ export declare const VIDEO_ADJUSTMENT_SCOPE_LABELS: Readonly<Record<VideoAdjustmentScope, string>>;
130
+ /** Labels for mask shapes, for inspectors. */
131
+ export declare const VIDEO_MASK_SHAPE_LABELS: Readonly<Record<VideoMaskShape, string>>;
132
+ /** What a clip is, for an inspector's heading. */
133
+ export declare const videoClipHeading: (clip: Pick<VideoClip, "type">) => string;
134
+ /**
135
+ * The edits that add an adjustment clip at `time` (default 3 s long) over everything: on the top
136
+ * track when it is a video track free there, else on a new video track added at the top. The clip
137
+ * starts with no look; give it a grade, effects or a mask.
138
+ */
139
+ export declare function videoAddAdjustmentEdits(project: VideoProject, time: number, options?: {
140
+ duration?: number;
141
+ id?: string;
142
+ grade?: VideoClip['grade'];
143
+ }): VideoEdit[];
144
+ /** The edit that sets (or with `null` removes) a clip's mask. */
145
+ export declare function videoMaskEdit(clip: string, mask: VideoClipMask | null): VideoEdit;
146
+ /** The edits that set how much clips' LUTs hold back on skin (0 turns it off); clips without a LUT are skipped. */
147
+ export declare function videoSkinProtectionEdits(project: VideoProject, clips: readonly string[], amount: number): VideoEdit[];
@@ -1,6 +1,6 @@
1
1
  import type { VideoEdit } from './edit.js';
2
2
  import { type VideoKeyframeProperty } from './keyframes.js';
3
- import type { VideoClipAnimationEffect, VideoTransitionType, VideoAsset, VideoAssetKind, VideoBlendMode, VideoClip, VideoFadeCurve, VideoGradeLut, VideoPlacementMode, VideoProject, VideoSequence } from './types.js';
3
+ import type { VideoAdjustmentScope, VideoClipMask, VideoMaskShape, VideoClipAnimationEffect, VideoTransitionType, VideoAsset, VideoAssetKind, VideoBlendMode, VideoClip, VideoFadeCurve, VideoGradeLut, VideoPlacementMode, VideoProject, VideoSequence } from './types.js';
4
4
  export interface VideoRulerTick {
5
5
  time: number;
6
6
  /** Pixels from the timeline's left edge. */
@@ -125,3 +125,23 @@ export declare function videoKeyframeToggleEdit(project: VideoProject, clipId: s
125
125
  * `null`. Clips without a grade change are left out; one `controller.apply` makes it one undo step.
126
126
  */
127
127
  export declare function videoLutEdits(project: VideoProject, clips: readonly string[], lut: VideoGradeLut | null): VideoEdit[];
128
+ /** Labels for adjustment scopes, for inspectors. */
129
+ export declare const VIDEO_ADJUSTMENT_SCOPE_LABELS: Readonly<Record<VideoAdjustmentScope, string>>;
130
+ /** Labels for mask shapes, for inspectors. */
131
+ export declare const VIDEO_MASK_SHAPE_LABELS: Readonly<Record<VideoMaskShape, string>>;
132
+ /** What a clip is, for an inspector's heading. */
133
+ export declare const videoClipHeading: (clip: Pick<VideoClip, "type">) => string;
134
+ /**
135
+ * The edits that add an adjustment clip at `time` (default 3 s long) over everything: on the top
136
+ * track when it is a video track free there, else on a new video track added at the top. The clip
137
+ * starts with no look; give it a grade, effects or a mask.
138
+ */
139
+ export declare function videoAddAdjustmentEdits(project: VideoProject, time: number, options?: {
140
+ duration?: number;
141
+ id?: string;
142
+ grade?: VideoClip['grade'];
143
+ }): VideoEdit[];
144
+ /** The edit that sets (or with `null` removes) a clip's mask. */
145
+ export declare function videoMaskEdit(clip: string, mask: VideoClipMask | null): VideoEdit;
146
+ /** The edits that set how much clips' LUTs hold back on skin (0 turns it off); clips without a LUT are skipped. */
147
+ export declare function videoSkinProtectionEdits(project: VideoProject, clips: readonly string[], amount: number): VideoEdit[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.160.0",
3
+ "version": "0.162.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -244,6 +244,16 @@
244
244
  "default": "./dist/color/index.cjs"
245
245
  }
246
246
  },
247
+ "./typography": {
248
+ "import": {
249
+ "types": "./dist/typography/index.d.ts",
250
+ "default": "./dist/typography/index.js"
251
+ },
252
+ "require": {
253
+ "types": "./dist/typography/index.d.cts",
254
+ "default": "./dist/typography/index.cjs"
255
+ }
256
+ },
247
257
  "./gradient": {
248
258
  "import": {
249
259
  "types": "./dist/gradient/index.d.ts",
package/skills/README.md CHANGED
@@ -18,11 +18,14 @@ that uses the tools can load the matching skill.
18
18
  | [`pixerate-paint-editor`](pixerate-paint-editor/SKILL.md) | Natural-media painting: paper and grain, dry-media brushes, the brush library, strokes as data, pigment mixing. |
19
19
  | [`pixerate-animation-editor`](pixerate-animation-editor/SKILL.md) | Vector animation: shots, commands, timing, Flair and Lottie. |
20
20
  | [`pixerate-video-editor`](pixerate-video-editor/SKILL.md) | Non-linear video editing: tracks, clips, transitions, export. |
21
+ | [`pixerate-talking-head-edit`](pixerate-talking-head-edit/SKILL.md) | Talking-head cleanup by transcript: fillers, long pauses and stutters found in one pass and cut as one plan reviewed before it applies. |
22
+ | [`pixerate-bgm-alignment`](pixerate-bgm-alignment/SKILL.md) | Background music: beats and cuts on the beat, ducking under speech, loudness in LUFS. |
21
23
  | [`pixerate-generation`](pixerate-generation/SKILL.md) | Planning, estimating and running paid media generation. |
22
24
  | [`pixerate-parameters`](pixerate-parameters/SKILL.md) | Effect parameters, presets and keyframes. |
23
25
  | [`pixerate-tokens`](pixerate-tokens/SKILL.md) | Design tokens: collections, modes, variables and aliases, through the whole-set document tools. |
24
26
  | [`pixerate-effect-authoring`](pixerate-effect-authoring/SKILL.md) | Authoring shaders and procedural effects: inspecting parameters and source code, setting values and presets, keyframing animation tracks, playback timing, and previewing. |
25
27
  | [`pixerate-color`](pixerate-color/SKILL.md) | Colour inspection, WCAG accessibility and contrast optimization, CVD simulation, palette audits, harmonies, and OKLab/OKLCH blends. |
28
+ | [`pixerate-typography`](pixerate-typography/SKILL.md) | Fonts and fallback stacks, font loading, modular type scales and hierarchies, fluid sizes, readability and type-aware contrast audits, text segmentation, the font picker. |
26
29
 
27
30
  ## Using them
28
31
 
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: pixerate-bgm-alignment
3
+ description: How to work with background music in the Pixerate video editor - finding a music track's tempo and beats, proposing cuts snapped to the beat as an edit plan for review, ducking music under speech with eased volume keyframes, measuring loudness in LUFS and normalising clips to streaming, podcast or broadcast targets, and shaping music outros. Use when asked to cut to the music, sync edits to a beat, lower music under dialogue, balance or normalise levels, or when find_video_beats, duck_video_music or normalize_video_loudness are available.
4
+ ---
5
+
6
+ # Pixerate music and the mix
7
+
8
+ Music sits on its own audio track under the picture and dialogue. Three jobs make it work:
9
+ **cutting to it** (cuts that land on beats feel deliberate), **ducking it** (dipping it under
10
+ speech so words stay clear) and **levelling it** (every clip at a consistent loudness, the mix
11
+ at the delivery target). The tools here measure the sound itself, from the peaks the editor
12
+ already decodes for waveforms, so the numbers match what the timeline shows.
13
+
14
+ Loudness is in **LUFS** (ITU-R BS.1770, gated): what streaming services, podcasts and
15
+ broadcasters measure. -14 LUFS for streaming and social, -16 for podcasts, -23 for EBU R128
16
+ broadcast. Peaks in dBFS should stay under -1.
17
+
18
+ This skill works with the video editor's own tools (`pixerate-video-editor`): tracks, clips,
19
+ `set_video_keyframes`, `update_video_clip` fades, `apply_video_edit_plan`, `export_video_ffmpeg`
20
+ loudness. See `pixerate-agent-tools` for errors and undo.
21
+
22
+ ## Working method
23
+
24
+ 1. **Find the music.** `get_video_project`: the music clip is usually on its own audio track.
25
+ Turn that track's `syncLocked` off (`update_video_track`) so ripple cuts in the dialogue do not
26
+ cut the music.
27
+ 2. **Find the beat.** `find_video_beats` with `clip` (timeline times) returns the `bpm`, a steady
28
+ `beats` grid and the `onsets` (each hit). If `bpm` is null the music has no steady pulse:
29
+ cut on the strongest onsets instead.
30
+ 3. **Cut on the beat.** `plan_video_beat_snap` with `music` (or `beats`) and the picture
31
+ `track` proposes rolling each cut to the nearest beat within `tolerance` (default 150 ms) as
32
+ one plan; it lists cuts it left (no beat near, already on one, not enough media). Show the
33
+ plan, then `apply_video_edit_plan`.
34
+ 4. **Duck under speech.** Once dialogue is final, `duck_video_music` with the music clips:
35
+ speech comes from the transcript (or from the dialogue track's sound with
36
+ `speech: "audio"`), and each music clip gets eased volume keyframes dipping `duck_db` under it.
37
+ 5. **Level.** `measure_video_loudness` on dialogue and music clips; `normalize_video_loudness`
38
+ sets clip volumes so each plays at a target (dialogue at -16 to -14 LUFS; music beds lower,
39
+ around -24 to -20 under speech). Normalise before ducking: ducking keys are relative to the
40
+ clip's volume when it is applied.
41
+ 6. **Finish.** A music bed ends on a fade: `update_video_clip` `fadeOut` 2000-4000 ms with
42
+ `fadeOutCurve` `exponential` (natural to silence) or `s-curve`. For delivery, export with
43
+ two-pass loudness (`export_video_loudness_measure`, then `export_video_ffmpeg` with
44
+ `loudness.measured`).
45
+
46
+ ## Best practices
47
+
48
+ - **Cut on the downbeat for big changes.** Section changes land on the first beat of a bar
49
+ (every 4th beat in 4/4); shot changes inside a section can take any beat. Fast montages cut
50
+ every 1-2 beats, slower pieces every 4-8.
51
+ - **Lead the beat slightly.** A cut a frame or two before the beat reads as on it; the eye takes
52
+ a moment to register a new shot.
53
+ - **Duck by -12 to -18 dB.** -14 dB is a good default; quieter music needs less. An attack of
54
+ 150-300 ms and a release of 300-600 ms sound natural; short releases pump.
55
+ - **Keep gaps ducked.** `merge_gap` (default 600 ms) stops the music bobbing up between
56
+ sentences; raise it to 1000-1500 ms for slow, measured speech.
57
+ - **Normalise clips, not the master.** Bring each clip to a sensible level so the mix is
58
+ balanced, then let the export's loudness pass set the final target.
59
+
60
+ ## Tool reference
61
+
62
+ | Tool | When and how to use it |
63
+ | --- | --- |
64
+ | `find_video_beats` | Read-only. The tempo (`bpm`, null when the hits do not repeat), a steady `beats` grid and the `onsets` of a `clip` (timeline ms) or `asset` (source ms), optionally between `from` and `to`. `sensitivity` (default 1.5; lower finds more hits), `min_bpm` / `max_bpm` (default 60-200). Decodes the asset the first time. Use the beats for `plan_video_beat_snap`, markers or title timing. |
65
+ | `plan_video_beat_snap` | Proposes rolling each cut on a `track` (default the first video track) to the nearest beat within `tolerance` ms (default 150), with beats from a `music` clip or given as `beats` (timeline ms). Roll edits keep everything else in place. Returns the plan (id, summary, `text`), how many cuts `moved` and the `skipped` ones with a reason. Nothing changes until `apply_video_edit_plan`. |
66
+ | `duck_video_music` | Dips `music` clips under speech with volume keyframes (one undo step): `duck_db` (default -14), `attack` (default 200 ms), `release` (default 400 ms), `merge_gap` (default 600 ms). Speech from the transcript (`speech: "transcript"`, the default when there is one) or from where `dialogue_track`'s sound is above -40 dBFS (`speech: "audio"`). Refuses clips with volume keyframes unless `replace: true`. Returns the speech stretches and each clip's keys. |
67
+ | `measure_video_loudness` | Read-only. Integrated loudness (`lufs`, null for silence) and sample `peakDb` of a `clip` or `asset` stretch, before clip volume, with `toStreaming` (the gain to -14 LUFS that keeps the peak under -1 dBFS). |
68
+ | `normalize_video_loudness` | Sets each of `clips`' volume so it plays at `target` LUFS (default -14) with its peak under `ceiling` (default -1 dBFS) and at most 4x gain (one undo step). Skips silent clips; refuses volume keyframes unless `replace: true`. Returns each clip's `lufs`, `gain`, `gainDb` and `limitedBy` when a cap held it short. |
69
+
70
+ ## Pitfalls
71
+
72
+ - **Ducking before the dialogue is final.** Ducking keys are placed at the speech as it is now.
73
+ Clean up and re-time dialogue first, then duck; if speech moves later, duck again with
74
+ `replace: true`.
75
+ - **Normalising a ducked clip.** `normalize_video_loudness` sets a constant volume, which would
76
+ replace ducking keys; it refuses unless `replace: true`. Normalise first, then duck.
77
+ - **Beat grids drift on live music.** A steady grid suits programmed music. For a live band, snap
78
+ to `onsets` (pass them as `beats`) instead of the grid.
79
+ - **Sync-locked music gets cut.** A ripple cut or extract on dialogue cuts every sync-locked
80
+ track, including the music. Turn the music track's `syncLocked` off first.
81
+ - **Peaks, not true peaks.** The tools measure sample peaks; the export's `loudnorm` limits true
82
+ peaks. Leave a decibel of headroom.
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: pixerate-talking-head-edit
3
+ description: How to clean up talking-head, interview, podcast and voiceover video in the Pixerate video editor by its transcript - finding filler words, long pauses and stutters in one pass, reviewing them, proposing the cuts as one edit plan the user previews before it applies, keeping breath around cuts, and checking the result. Use when asked to tighten dialogue, remove ums and ahs, cut dead air, clean up a talking head or make a rough cut of an interview, or when find_video_disfluencies or plan_video_dialogue_cleanup are available.
4
+ ---
5
+
6
+ # Pixerate talking-head cleanup
7
+
8
+ Spoken media in a video project carries a **transcript**: each word with its start and end in
9
+ the source. The editor maps the words through the clips onto the timeline, so a word's place is
10
+ always where it plays now, however the clips were cut. Cleaning up speech is then a matter of
11
+ finding stretches of the timeline (fillers, long pauses, stutters) and cutting them out on every
12
+ sync-locked track at once, so picture, sound and B-roll stay in sync.
13
+
14
+ Cleanup is **plan first**: propose the cuts as one plan, let the user review it (the editor
15
+ shows the plan with its length change and every stretch it cuts), then apply it as one undo step.
16
+ Never cut a whole interview's disfluencies without a review.
17
+
18
+ This skill works with the video editor's own tools (`pixerate-video-editor`): transcripts,
19
+ `get_video_transcript`, `remove_video_ranges`, `apply_video_edit_plan`, `render_video_frame`.
20
+ See `pixerate-agent-tools` for errors and undo.
21
+
22
+ ## Working method
23
+
24
+ 1. **Make sure there is a transcript.** `get_video_project` shows which assets have one; if the
25
+ speech has none, ask the host or user for a Whisper-style JSON, `.srt` or `.vtt` file and
26
+ attach it with `set_video_transcript`. Captions without word timings are approximate: cuts
27
+ on them may clip words, so prefer word-timed transcripts.
28
+ 2. **Read the speech.** `get_video_transcript` for the text and the word list (the indexes the
29
+ find tool returns refer to it).
30
+ 3. **Find what to tidy.** `find_video_disfluencies` lists fillers (um, uh, er, you know, I
31
+ mean…), pauses longer than `min_pause` (default 700 ms, keeping `keep` ms of breath either
32
+ side) and repeated words or phrases ("I I think", "we should we should"), with a total per
33
+ kind and how much time the cuts would remove.
34
+ 4. **Review.** Read the list against the transcript. Keep repeats that are meant ("very very"),
35
+ pauses that land a point, and fillers that carry meaning ("like" and "so" are not in the
36
+ defaults for that reason). Note the `from` times of finds to keep.
37
+ 5. **Propose.** `plan_video_dialogue_cleanup` with the same options and `exclude` (the `from`
38
+ times to keep) proposes the cuts as one plan; nothing changes yet. Tell the user what it does
39
+ (its `text`: stretches and seconds removed) and ask them to review it in the editor.
40
+ 6. **Apply.** Once they agree, `apply_video_edit_plan` with the plan id (or
41
+ `discard_video_edit_plan`). If they edited meanwhile, the plan is re-checked; if it no longer
42
+ applies, find and plan again.
43
+ 7. **Check.** `get_video_transcript` again (every time after a cut moved), listen at a few cuts
44
+ with `get_video_frame` (audio gains) and look with `render_video_frame` for jump cuts in the
45
+ picture: cover those with B-roll on a higher track or a slight push-in (`set_video_keyframes`
46
+ `scale` 1 to 1.05).
47
+
48
+ ## Best practices
49
+
50
+ - **Breath matters.** Cut pauses down, not out: keep 150-250 ms either side (`keep`) so speech
51
+ does not sound breathless. For fillers and stutters, `margin` 20-60 ms avoids clipping the
52
+ words around a cut when word timings are loose.
53
+ - **Brisk or relaxed.** For social clips and explainers, `min_pause` 500-600 ms; for
54
+ interviews and documentary, 900-1200 ms so thinking pauses stay.
55
+ - **One plan, one undo.** Propose the whole cleanup as one plan rather than many small cuts:
56
+ the user reviews it once and can undo it in one step.
57
+ - **Words cut with their picture.** The cut runs across every unlocked sync-locked track. A
58
+ music bed that should keep playing must have `syncLocked: false` (`update_video_track`)
59
+ before the plan is applied, or it is cut too.
60
+ - **Duck music after cleanup.** Speech has moved, so duck background music once the dialogue
61
+ is final (`duck_video_music`, in `pixerate-bgm-alignment`).
62
+
63
+ ## Tool reference
64
+
65
+ | Tool | When and how to use it |
66
+ | --- | --- |
67
+ | `find_video_disfluencies` | Read-only. Lists fillers, long pauses and stutters in time order as `{ kind, from, to, text, words }` (timeline ms; `words` are the first and last indexes in `get_video_transcript`'s list; for a pause, the words either side), with `totals` per kind and `removable` time once merged. Options: `track`, `kinds` (`filler`, `pause`, `repetition`), `fillers` (your own list), `min_pause` (ms, default 700), `keep` (breath either side of a pause cut, default 150), `max_phrase` (longest repeat checked, default 3 words). Review it before planning. |
68
+ | `plan_video_dialogue_cleanup` | Proposes cutting the finds as one plan for review (nothing changes): the same options as the find tool, plus `exclude` (the `from` times of finds to keep), `margin` (ms left at each end of filler and stutter cuts, default 0) and a `title`. Returns the plan (id, title, summary with the length change, `text`) and a hint. Apply with `apply_video_edit_plan`, or `discard_video_edit_plan`. Fails when nothing is left to cut. |
69
+
70
+ ## Pitfalls
71
+
72
+ - **Overlapping finds.** A filler inside a stutter is listed twice; the plan merges the ranges,
73
+ so it is cut once. Do not add the same stretch again through `remove_video_ranges`.
74
+ - **Stale plans.** Applying a plan after other edits re-checks it; if it is refused, run the find
75
+ and plan again rather than applying old times by hand.
76
+ - **Approximate transcripts.** Captions spread words evenly over each cue, so a "filler" may be a
77
+ neighbour's syllable. Check the find's `text` against `get_video_transcript`, and prefer
78
+ word-timed transcripts for cleanup.
79
+ - **Transcripts come from the host.** Speech recognition is the host's job: the editor never sends
80
+ media anywhere. Ask for a transcript rather than guessing words from audio levels.
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: pixerate-typography
3
+ description: How to choose and load fonts, build resilient font-family fallback stacks, generate modular type scales and heading hierarchies, compute fluid clamp() sizes, audit body text for line length and line spacing, check text contrast with the real font size and weight, split text into letters, words or lines for animation, and drive a font picker. Use when asked to pick or pair fonts, set up type tokens, check readability or legibility, or animate live text.
4
+ ---
5
+
6
+ # Typography tools
7
+
8
+ The typography module is DOM-free: it works on numbers (px sizes, unitless line-heights, em
9
+ tracking) and family names, never on rendered pixels. Fonts come from a **font provider** the
10
+ host supplies (installed system fonts, a web font service, a design system's families); by
11
+ default it is a catalog of common system fonts, which says what is *usually* installed, not
12
+ what this machine has.
13
+
14
+ ## Mental model
15
+
16
+ - A **font spec** is `{ family, weight, style, size (px), lineHeight (unitless), letterSpacing (em) }`.
17
+ - A **type scale** multiplies a base size by a ratio per step (`16 × 1.25² = 25`). A **hierarchy**
18
+ names the steps: `caption` (−2), `body-sm`, `body` (0), `h4`, `h3`, `h2`, `h1`, `display` (5).
19
+ - A **fallback stack** is the `font-family` value: your family, then a tested cross-platform stack
20
+ for its category, ending in a generic family, so text still renders when the font is missing.
21
+ - **Large text** under WCAG 2 is 24px, or 18.67px (14pt) at weight 700+. It needs 3:1 contrast
22
+ for AA instead of 4.5:1. The accessibility tool decides this from the spec for you.
23
+
24
+ ## Working method
25
+
26
+ 1. **Find fonts.** `list_fonts` with `category`, `weight`, `style` or `query`. Only use families it
27
+ returns (or that the user names), and always ship them inside a stack from `resolve_font_stack`.
28
+ 2. **Load before rendering.** `load_font` before drawing or capturing text in a web font, so the
29
+ first frame does not use the fallback. `loaded: false` means the fallback will show.
30
+ 3. **Set up the scale.** `generate_type_hierarchy` for named tokens and ready CSS custom
31
+ properties; `suggest_type_scale` for arbitrary steps. Smaller ratios (minor/major third) suit
32
+ dense UI; larger ones (perfect fourth/fifth, golden) suit editorial and marketing pages.
33
+ 4. **Make it fluid (optional).** `compute_fluid_type_size` for headings that grow between two
34
+ viewport widths. Bounds are in rem so browser zoom and font-size settings still work.
35
+ 5. **Audit the body text.** `audit_text_readability` with the real text, `fontSize`, `lineHeight`
36
+ and the column `width`: fix `lineLength.status` (45–75 characters) and `lineSpacing`
37
+ (at least 1.5) before polishing anything else.
38
+ 6. **Check contrast with the type.** `check_typography_accessibility` with foreground, background,
39
+ `fontSize` and `fontWeight`. Use `suggestion` when `passesAA` is false. For pure colour work
40
+ (palettes, dual colours) use the `pixerate-color` tools.
41
+ 7. **Animate live text.** `segment_text` splits into `grapheme`, `word` (with `wordsOnly`),
42
+ `sentence` or `line` (with `maxCharacters` to wrap) and returns UTF-16 `start` / `end`
43
+ offsets, so letters and words can be staggered without converting text to outlines.
44
+ 8. **Drive the on-screen picker.** When a font picker is mounted, `get_font_selection` shows what
45
+ is chosen, `list_picker_fonts` what it offers, and `set_font_selection` changes any fields in
46
+ one step and returns the new selection.
47
+
48
+ ## Best practices
49
+
50
+ - **Two families are usually enough**: one for text, optionally one for headings. Pair by contrast
51
+ of category (serif headings with sans text, or the reverse), not by two similar sans-serifs.
52
+ - **Body text at 16px or more**, line-height 1.5–1.7, about 66 characters per line
53
+ (`max-width: 66ch`). Headings get tighter leading (1.1–1.3) and slightly negative tracking; small
54
+ captions slightly positive tracking. The scale tools already recommend these.
55
+ - **Use weight 400+ below 24px.** Thin weights lower effective contrast even when the ratio passes.
56
+ - **Avoid long runs of capitals** for body text; keep all-caps to short labels.
57
+ - **Prefer rem and clamp()** over fixed px for type on the web, so user settings apply.
58
+ - **Never assume a font is installed.** A system font in the catalog may be missing on Linux or
59
+ Android; the fallback stack is what makes that safe.
60
+
61
+ ## Tool reference
62
+
63
+ | Tool | When to use | Returns |
64
+ | --- | --- | --- |
65
+ | `list_fonts` | Discover families the host offers; filter by `category`, `source`, `weight`, `style`, `query`; `limit` default 50 | `{ fonts: [{ family, category, weights, styles, isVariable?, source }], total, truncated }` |
66
+ | `resolve_font_stack` | Turn a family (or stack) into a resilient `font-family` value for its category; also says if the provider lists it | `{ stack, families, category, available }` |
67
+ | `suggest_type_scale` | Sizes for arbitrary integer `steps` (-12…12) from `baseSize` (px, default 16) and `ratio` (name or number, default major-third) | `{ ratio, baseSize, steps: [{ name, step, size, lineHeight, letterSpacing }] }` |
68
+ | `generate_type_hierarchy` | Named tokens caption…display on one scale, with CSS custom properties (`prefix`, `selector`) | `{ ratio, ratioName?, baseSize, steps: { body, h1, … }, css }` |
69
+ | `compute_fluid_type_size` | A size that grows from `minSize` at `minViewport` to `maxSize` at `maxViewport` (all px) | `{ clampCss, slope, intercept, samples }` |
70
+ | `audit_text_readability` | Check body text: line length (own lines, or `width` + `fontSize`), line spacing, capitals, tiny or tight type, reading time | `{ lineLength, lineSpacing, allCaps, wordCount, estimatedReadingTimeSeconds, issues }` |
71
+ | `check_typography_accessibility` | Contrast for text of a given `fontSize` (px) and `fontWeight`, deciding large text itself; suggests a passing foreground | `{ ratio, large, level, passesAA, passesAAA, required, suggestion?, largeTextMinSize, issues }` |
72
+ | `segment_text` | Split text into graphemes, words, sentences or lines with offsets, for staggered animation | `{ segments: [{ text, start, end, index, isWordLike? }], count }` |
73
+ | `load_font` | Load a family's face (`weight`, `style`) before rendering or capturing; only listed when the provider can load | `{ family, weight, style, loaded }` |
74
+ | `get_font_selection` | Read the mounted font picker's value, CSS and the chosen family's weights and styles | `{ value, css, availableWeights, availableStyles }` |
75
+ | `list_picker_fonts` | Families the mounted picker offers (by `category`, `query`), plus recent families | `{ fonts, recent, total, truncated }` |
76
+ | `set_font_selection` | Change the picker's family, weight, style, size, lineHeight or letterSpacing in one validated step; weights snap to what the family has | `{ value, css, availableWeights, availableStyles }` |
77
+
78
+ ## Pitfalls
79
+
80
+ - **Sizes are CSS px.** 12pt is 16px; 18pt is 24px. Convert before calling the tools.
81
+ - **lineHeight is a multiplier**, not px: 24px leading on 16px text is `1.5`. When it is omitted
82
+ the audit assumes the browser's `normal` (about 1.2) and says so with `assumed: true`.
83
+ - **`line` segmentation does not know the renderer's wrapping.** Without `maxCharacters` it only
84
+ splits on line breaks; with it, it wraps by character count, which approximates proportional
85
+ fonts.
86
+ - **A one-line string is not a measure.** `too-short` on a single heading is expected; pass the
87
+ column `width` and `fontSize` to estimate how paragraphs will wrap.
88
+ - **The system catalog is not detection.** Without the host opting into Local Font Access,
89
+ `list_fonts` lists commonly installed fonts. Always use `resolve_font_stack` output.
90
+ - **Picker fonts may differ from `list_fonts`.** `set_font_selection` only accepts families the
91
+ picker lists; check with `list_picker_fonts`.