@hyperframes/core 0.8.200 → 0.8.201

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.
@@ -3,6 +3,7 @@
3
3
  * Pure functions of PCM samples, so they run the same in Node, Bun and the browser. Times are seconds into the signal.
4
4
  */
5
5
  import type { AudioAnalysisProvider } from "./audioAnalysisLadder.js";
6
+ import { type Steps } from "./audioSteps.js";
6
7
  /** Decoded audio: one Float32Array per channel, all the same length. */
7
8
  export interface AudioSignal {
8
9
  sampleRate: number;
@@ -102,6 +103,7 @@ export interface KindFeatures {
102
103
  export declare const signalDuration: (signal: AudioSignal) => number;
103
104
  /** Per-sample energy. Channels are squared first, so a reversed channel stays audible. */
104
105
  export declare function energyMix(signal: AudioSignal): Float32Array;
106
+ export declare function energyMixSteps(signal: AudioSignal): Steps<Float32Array>;
105
107
  export declare function envelope(signal: AudioSignal, options?: EnvelopeOptions): Envelope;
106
108
  export declare function onsets(signal: AudioSignal, options?: OnsetOptions): Onsets;
107
109
  export declare function moments(signal: AudioSignal, options?: MomentOptions): Moments | null;
@@ -2,6 +2,7 @@
2
2
  * Measurements of decoded audio over time: an energy envelope, onsets, the moments a sound lands, loudness and silence.
3
3
  * Pure functions of PCM samples, so they run the same in Node, Bun and the browser. Times are seconds into the signal.
4
4
  */
5
+ import { finish } from "./audioSteps.js";
5
6
  import { fft } from "./fft.js";
6
7
  import { rhythm } from "./audioRhythm.js";
7
8
  import { sections } from "./audioSections.js";
@@ -9,18 +10,26 @@ const DB_FLOOR = -120;
9
10
  export const signalDuration = (signal) => (signal.channels[0]?.length ?? 0) / signal.sampleRate;
10
11
  /** Per-sample energy. Channels are squared first, so a reversed channel stays audible. */
11
12
  export function energyMix(signal) {
13
+ return finish(energyMixSteps(signal));
14
+ }
15
+ const MIX_SAMPLES_PER_STEP = 65536;
16
+ export function* energyMixSteps(signal) {
12
17
  const [first, ...rest] = signal.channels;
13
18
  if (!first)
14
19
  return new Float32Array(0);
15
20
  if (!rest.length)
16
21
  return first;
17
22
  const mixed = new Float32Array(first.length);
18
- for (const channel of signal.channels)
19
- for (let i = 0; i < mixed.length; i++)
20
- mixed[i] += channel[i] * channel[i];
21
23
  const scale = 1 / signal.channels.length;
22
- for (let i = 0; i < mixed.length; i++)
23
- mixed[i] = Math.sqrt(mixed[i] * scale);
24
+ for (let start = 0; start < mixed.length; start += MIX_SAMPLES_PER_STEP) {
25
+ const end = Math.min(mixed.length, start + MIX_SAMPLES_PER_STEP);
26
+ for (const channel of signal.channels)
27
+ for (let i = start; i < end; i++)
28
+ mixed[i] += channel[i] * channel[i];
29
+ for (let i = start; i < end; i++)
30
+ mixed[i] = Math.sqrt(mixed[i] * scale);
31
+ yield;
32
+ }
24
33
  return mixed;
25
34
  }
26
35
  const toDb = (amplitude) => amplitude > 0 ? Math.max(DB_FLOOR, 20 * Math.log10(amplitude)) : DB_FLOOR;
@@ -100,13 +100,9 @@ export declare function audioGroupOf(el: Element): string | null;
100
100
  /**
101
101
  * Make `<hf-audio-group>` inert, once per document.
102
102
  *
103
- * The element is metadata — an id, a label, a chain, an automation lane — and
104
- * carries no content, but "no content" is not "no box": it is still an unknown
105
- * custom element, so in a flex or grid composition root it counts as an item
106
- * (taking a `gap`, shifting `justify-content`, moving every `:nth-child` after
107
- * it), and in inline formatting it can still open a line box. Authored layout
108
- * would shift by adding a group, which is not something a mixing decision is
109
- * allowed to do.
103
+ * An unknown custom element still makes a box (a flex or grid item, a line box), and a
104
+ * mixing decision must not shift authored layout. `contents`, not `none`: what sits
105
+ * inside one (a self-closed `<hf-audio-group />` takes in every later sibling) still renders, unless muted.
110
106
  *
111
107
  * `!important` because an author rule can outrank a bare type selector on
112
108
  * specificity — inertness here is a contract, not a default. Emitted from the
@@ -199,13 +199,9 @@ function isGroupableMember(el) {
199
199
  /**
200
200
  * Make `<hf-audio-group>` inert, once per document.
201
201
  *
202
- * The element is metadata — an id, a label, a chain, an automation lane — and
203
- * carries no content, but "no content" is not "no box": it is still an unknown
204
- * custom element, so in a flex or grid composition root it counts as an item
205
- * (taking a `gap`, shifting `justify-content`, moving every `:nth-child` after
206
- * it), and in inline formatting it can still open a line box. Authored layout
207
- * would shift by adding a group, which is not something a mixing decision is
208
- * allowed to do.
202
+ * An unknown custom element still makes a box (a flex or grid item, a line box), and a
203
+ * mixing decision must not shift authored layout. `contents`, not `none`: what sits
204
+ * inside one (a self-closed `<hf-audio-group />` takes in every later sibling) still renders, unless muted.
209
205
  *
210
206
  * `!important` because an author rule can outrank a bare type selector on
211
207
  * specificity — inertness here is a contract, not a default. Emitted from the
@@ -217,6 +213,6 @@ export function ensureAudioGroupInertStyle(doc) {
217
213
  return;
218
214
  const style = doc.createElement("style");
219
215
  style.id = styleId;
220
- style.textContent = `${HF_AUDIO_GROUP_TAG}{display:none!important}`;
216
+ style.textContent = `${HF_AUDIO_GROUP_TAG}{display:contents!important}${HF_AUDIO_GROUP_TAG}[data-hidden]{display:none!important}`;
221
217
  doc.head.appendChild(style);
222
218
  }
@@ -1,2 +1,4 @@
1
1
  /** Band-limited resampling by windowed-sinc interpolation, so analysis reads the same at any decode rate. */
2
+ import { type Steps } from "./audioSteps.js";
2
3
  export declare function resample(samples: Float32Array, from: number, to: number): Float32Array;
4
+ export declare function resampleSteps(samples: Float32Array, from: number, to: number): Steps<Float32Array>;
@@ -1,4 +1,5 @@
1
1
  /** Band-limited resampling by windowed-sinc interpolation, so analysis reads the same at any decode rate. */
2
+ import { finish } from "./audioSteps.js";
2
3
  const ZERO_CROSSINGS = 8;
3
4
  const STEPS_PER_CROSSING = 256;
4
5
  /** Cutoff as a share of the lower Nyquist, leaving the window's transition band below it. */
@@ -19,6 +20,10 @@ function kernelAt(x) {
19
20
  return KERNEL[i] * (1 - frac) + KERNEL[i + 1] * frac;
20
21
  }
21
22
  export function resample(samples, from, to) {
23
+ return finish(resampleSteps(samples, from, to));
24
+ }
25
+ const SAMPLES_PER_STEP = 4096;
26
+ export function* resampleSteps(samples, from, to) {
22
27
  if (from === to)
23
28
  return samples;
24
29
  const ratio = to / from;
@@ -26,6 +31,8 @@ export function resample(samples, from, to) {
26
31
  const reach = ZERO_CROSSINGS / scale;
27
32
  const out = new Float32Array(Math.floor(samples.length * ratio));
28
33
  for (let n = 0; n < out.length; n++) {
34
+ if (n % SAMPLES_PER_STEP === 0)
35
+ yield;
29
36
  const t = n / ratio;
30
37
  const last = Math.min(samples.length - 1, Math.floor(t + reach));
31
38
  let sum = 0;
@@ -3,6 +3,7 @@
3
3
  * tempo with a log-normal prior, and dynamic-programming beat placement (Ellis 2007). Times are seconds into the signal.
4
4
  */
5
5
  import type { AudioSignal } from "./audioAnalysis.js";
6
+ import { type Steps } from "./audioSteps.js";
6
7
  export interface Beats {
7
8
  /** Empty when there is no steady beat, with `reason` saying so. */
8
9
  times: number[];
@@ -46,3 +47,5 @@ export declare function tempoOf(times: readonly number[]): Tempo;
46
47
  /** Band energies of `signal`, computed once per signal for every capability that reads them. */
47
48
  export declare function bandFramesOf(signal: AudioSignal): BandFrames;
48
49
  export declare function rhythm(signal: AudioSignal): Rhythm;
50
+ /** `rhythm`, yielding between short pieces of work. */
51
+ export declare function rhythmSteps(signal: AudioSignal): Steps<Rhythm>;
@@ -2,7 +2,8 @@
2
2
  * Beats, downbeats and tempo from signal processing alone: a log-band spectral-flux onset curve, an autocorrelation
3
3
  * tempo with a log-normal prior, and dynamic-programming beat placement (Ellis 2007). Times are seconds into the signal.
4
4
  */
5
- import { resample } from "./audioResample.js";
5
+ import { resampleSteps } from "./audioResample.js";
6
+ import { finish } from "./audioSteps.js";
6
7
  import { fft } from "./fft.js";
7
8
  /** Every signal is analyzed at this rate and as the mean of its channels' power, so decode rate and layout cannot change a result. */
8
9
  const ANALYSIS_RATE = 22050;
@@ -45,7 +46,7 @@ function addFramePower(samples, center, window, edges, into) {
45
46
  }
46
47
  }
47
48
  /** Per-band log power of every frame, averaged over channels so a polarity-flipped channel cannot cancel. */
48
- function bandPower(channels) {
49
+ function* bandPower(channels) {
49
50
  const window = hann(FRAME_SIZE);
50
51
  const edges = bandEdges();
51
52
  const hop = ANALYSIS_RATE / FRAMES_PER_SECOND;
@@ -55,6 +56,7 @@ function bandPower(channels) {
55
56
  for (const channel of channels)
56
57
  addFramePower(channel, Math.round(f * hop), window, edges, power);
57
58
  bands.push(power.map((p) => Math.log1p((1e9 * p) / channels.length)));
59
+ yield;
58
60
  }
59
61
  return bands;
60
62
  }
@@ -65,7 +67,7 @@ const PITCH_CLASS = Array.from({ length: CHROMA_SIZE / 2 }, (_, bin) => {
65
67
  return -1;
66
68
  return (((Math.round(12 * Math.log2(hz / 440)) + 9) % 12) + 12) % 12;
67
69
  });
68
- function chromaFrames(channels) {
70
+ function* chromaFrames(channels) {
69
71
  const window = hann(CHROMA_SIZE);
70
72
  const hop = ANALYSIS_RATE / CHROMA_FPS;
71
73
  const re = new Float64Array(CHROMA_SIZE);
@@ -87,15 +89,18 @@ function chromaFrames(channels) {
87
89
  });
88
90
  }
89
91
  frames.push(chroma);
92
+ yield;
90
93
  }
91
94
  return frames;
92
95
  }
93
- function bandFrames(signal) {
94
- const channels = signal.channels.map((c) => resample(c, signal.sampleRate, ANALYSIS_RATE));
96
+ function* bandFrames(signal) {
97
+ const channels = [];
98
+ for (const c of signal.channels)
99
+ channels.push(yield* resampleSteps(c, signal.sampleRate, ANALYSIS_RATE));
95
100
  return {
96
101
  fps: FRAMES_PER_SECOND,
97
- bands: bandPower(channels),
98
- chroma: chromaFrames(channels),
102
+ bands: yield* bandPower(channels),
103
+ chroma: yield* chromaFrames(channels),
99
104
  chromaFps: CHROMA_FPS,
100
105
  };
101
106
  }
@@ -182,7 +187,7 @@ function smoothed(curve, period) {
182
187
  });
183
188
  }
184
189
  /** Ellis's recursion: each frame's best score from a predecessor about one period back, penalized by log deviation. */
185
- function cumulativeScore(local, periodAt) {
190
+ function* cumulativeScore(local, periodAt) {
186
191
  const score = new Float64Array(local.length);
187
192
  const back = new Int32Array(local.length).fill(-1);
188
193
  for (let i = 0; i < local.length; i++) {
@@ -196,6 +201,7 @@ function cumulativeScore(local, periodAt) {
196
201
  }
197
202
  score[i] = local[i] + Math.max(0, best);
198
203
  back[i] = best > 0 ? from : -1;
204
+ yield;
199
205
  }
200
206
  return { score, back };
201
207
  }
@@ -226,7 +232,7 @@ function trimmed(beats, local) {
226
232
  }
227
233
  const LOCAL_WINDOW_SECONDS = 8;
228
234
  /** The period through the piece: eight-second windows a second apart, held within 10% of the global period, median-smoothed. */
229
- function localPeriods(curve, fps, period) {
235
+ function* localPeriods(curve, fps, period) {
230
236
  const window = Math.round(LOCAL_WINDOW_SECONDS * fps);
231
237
  const hop = Math.round(fps);
232
238
  const range = {
@@ -239,6 +245,7 @@ function localPeriods(curve, fps, period) {
239
245
  const end = Math.min(curve.length, start + window);
240
246
  periods.push(beatPeriod(curve.subarray(start, end), fps, range).period);
241
247
  centres.push((start + end) / 2);
248
+ yield;
242
249
  }
243
250
  const median = periods.map((_, i) => {
244
251
  const near = periods.slice(Math.max(0, i - 2), i + 3).sort((a, b) => a - b);
@@ -256,9 +263,10 @@ function interpolate(xs, ys, x) {
256
263
  }
257
264
  return ys[ys.length - 1];
258
265
  }
259
- function beatFrames(curve, fps, period) {
266
+ function* beatFrames(curve, fps, period) {
260
267
  const local = smoothed(curve, period);
261
- const { score, back } = cumulativeScore(local, localPeriods(curve, fps, period));
268
+ const periodAt = yield* localPeriods(curve, fps, period);
269
+ const { score, back } = yield* cumulativeScore(local, periodAt);
262
270
  const beats = [];
263
271
  for (let i = lastBeat(score); i >= 0; i = back[i])
264
272
  beats.push(i);
@@ -352,14 +360,16 @@ export const STEADY_BEAT_CONFIDENCE = 0.15;
352
360
  /** Clarity of the period, scaled down when the beats are under three times as strong as the onsets around them. */
353
361
  const beatConfidence = (clarity, lift) => round(Math.max(0, Math.min(1, clarity)) * Math.max(0, Math.min(1, lift / 3)));
354
362
  /** Beats and downbeats of `frames`, a signal's band energies and chroma. */
355
- function rhythmOf(frames) {
363
+ function* rhythmOf(frames) {
356
364
  const curves = onsetCurves(frames);
365
+ yield;
357
366
  const { period, clarity } = beatPeriod(curves.broad, curves.fps);
367
+ yield;
358
368
  if (curves.broad.length < SHORTEST_SECONDS * curves.fps)
359
369
  return noRhythm(0, "too short for a steady beat");
360
370
  if (!curves.broad.some((v) => v > 0) || curves.broad.length < 2 * period)
361
371
  return noRhythm(0);
362
- const { frames: beatList, local } = beatFrames(curves.broad, curves.fps, period);
372
+ const { frames: beatList, local } = yield* beatFrames(curves.broad, curves.fps, period);
363
373
  const confidence = beatConfidence(clarity, onsetLift(beatList, local));
364
374
  if (beatList.length < 4 || confidence < STEADY_BEAT_CONFIDENCE)
365
375
  return noRhythm(confidence);
@@ -393,15 +403,22 @@ export function tempoOf(times) {
393
403
  return { value: bpm(interval), curve };
394
404
  }
395
405
  const measured = new WeakMap();
396
- /** Band energies of `signal`, computed once per signal for every capability that reads them. */
397
- export function bandFramesOf(signal) {
406
+ function* measuredSteps(signal) {
398
407
  let entry = measured.get(signal);
399
408
  if (!entry)
400
- measured.set(signal, (entry = { frames: bandFrames(signal) }));
401
- return entry.frames;
409
+ measured.set(signal, (entry = { frames: yield* bandFrames(signal) }));
410
+ return entry;
411
+ }
412
+ /** Band energies of `signal`, computed once per signal for every capability that reads them. */
413
+ export function bandFramesOf(signal) {
414
+ return finish(measuredSteps(signal)).frames;
402
415
  }
403
416
  export function rhythm(signal) {
404
- const frames = bandFramesOf(signal);
405
- const entry = measured.get(signal);
406
- return (entry.rhythm ??= rhythmOf(frames));
417
+ return finish(rhythmSteps(signal));
418
+ }
419
+ /** `rhythm`, yielding between short pieces of work. */
420
+ export function* rhythmSteps(signal) {
421
+ const entry = yield* measuredSteps(signal);
422
+ entry.rhythm ??= yield* rhythmOf(entry.frames);
423
+ return entry.rhythm;
407
424
  }
@@ -0,0 +1,3 @@
1
+ /** A long computation that yields between short pieces of work, so a caller can pause it. */
2
+ export type Steps<T> = Generator<void, T, void>;
3
+ export declare function finish<T>(steps: Steps<T>): T;
@@ -0,0 +1,7 @@
1
+ export function finish(steps) {
2
+ for (;;) {
3
+ const next = steps.next();
4
+ if (next.done)
5
+ return next.value;
6
+ }
7
+ }
@@ -1,5 +1,6 @@
1
- import { energyMix } from "../audioAnalysis.js";
2
- import { bandFramesOf, rhythm, tempoOf } from "../audioRhythm.js";
1
+ import { energyMixSteps } from "../audioAnalysis.js";
2
+ import { rhythmSteps, tempoOf } from "../audioRhythm.js";
3
+ import { finish } from "../audioSteps.js";
3
4
  const STRENGTH_WINDOW_S = 0.05; // ±50ms RMS window
4
5
  /** Local RMS amplitude at a given audio-file time. */
5
6
  export function computeRmsAt(channelData, sampleRate, time) {
@@ -28,14 +29,27 @@ function beatStrengths(beats, levels, sampleRate) {
28
29
  const peak = Math.max(...energies, 1e-6);
29
30
  return { strengths: energies.map((e) => Math.min(1, e / peak)), peak };
30
31
  }
32
+ /** Longest stretch the analysis runs before awaiting `pause`. */
33
+ const SLICE_MS = 8;
34
+ async function sliced(steps, pause) {
35
+ if (!pause)
36
+ return finish(steps);
37
+ let start = performance.now();
38
+ for (;;) {
39
+ const next = steps.next();
40
+ if (next.done)
41
+ return next.value;
42
+ if (performance.now() - start >= SLICE_MS) {
43
+ await pause();
44
+ start = performance.now();
45
+ }
46
+ }
47
+ }
31
48
  /** Beats, tempo and per-beat strength of decoded audio; no beats and a null bpm when it has no steady beat. */
32
49
  export async function analyzeMusicFromSignal(signal, { pause } = {}) {
33
50
  await pause?.();
34
- bandFramesOf(signal);
35
- await pause?.();
36
- const { beats } = rhythm(signal);
37
- await pause?.();
38
- const channelData = energyMix(signal);
51
+ const { beats } = await sliced(rhythmSteps(signal), pause);
52
+ const channelData = await sliced(energyMixSteps(signal), pause);
39
53
  const confidence = beats.confidence ?? 0;
40
54
  const times = beats.times;
41
55
  const { strengths, peak } = beatStrengths(times, channelData, signal.sampleRate);
@@ -55,6 +69,7 @@ export function analyzeMusicFromBuffer(audioBuffer, options = {}) {
55
69
  return analyzeMusicFromSignal({ sampleRate: audioBuffer.sampleRate, channels }, options);
56
70
  }
57
71
  export async function analyzeMusicFromUrl(url, options = {}) {
72
+ await options.pause?.();
58
73
  const audioContext = new AudioContext();
59
74
  try {
60
75
  const response = await fetch(url);
@@ -16,6 +16,7 @@ import { resolve, relative, dirname, isAbsolute, sep } from "path";
16
16
  import { extractStandaloneEntryFromIndex, isTemplateEntry } from "./standaloneEntry.js";
17
17
  import { decodeCssEscapes, decodeWellFormedEscapes, encodeUrlPath, decodedUrlPath, splitUrlSuffix, } from "@hyperframes/parsers/asset-paths";
18
18
  import { CSS_URL_RE, isNonRelativeUrl } from "./assetPaths.js";
19
+ import { rewriteSrcsetUrls } from "./srcset.js";
19
20
  import { compileHtml } from "./htmlCompiler.js";
20
21
  import { RUNTIME_BOOTSTRAP_ATTR, escapeInlineScriptSource, insertRuntimeTag, parseHTMLContent, stripEmbeddedRuntimeScripts, } from "./htmlDocument.js";
21
22
  // rewriteSubCompPaths functions are used by inlineSubCompositions (shared module)
@@ -125,43 +126,6 @@ function rebaseRelativePath(urlValue, fromDir, toDir) {
125
126
  const rebased = encodeUrlPath(relative(resolve(toDir), absolutePath).split(sep).join("/"));
126
127
  return appendSuffixToUrl(rebased, suffix);
127
128
  }
128
- function rewriteSrcsetUrls(srcset, rewriteUrl) {
129
- const parts = [];
130
- let position = 0;
131
- let written = 0;
132
- // Preserve URL commas per https://html.spec.whatwg.org/multipage/images.html#parse-a-srcset-attribute
133
- while (position < srcset.length) {
134
- while (position < srcset.length && /[ \t\n\r\f,]/.test(srcset.charAt(position)))
135
- position++;
136
- const start = position;
137
- while (position < srcset.length && !/[ \t\n\r\f]/.test(srcset.charAt(position)))
138
- position++;
139
- let end = position;
140
- while (end > start && srcset.charAt(end - 1) === ",")
141
- end--;
142
- if (end > start) {
143
- const url = srcset.slice(start, end);
144
- const rewritten = rewriteUrl(url);
145
- if (rewritten !== url) {
146
- parts.push(srcset.slice(written, start), rewritten);
147
- written = end;
148
- }
149
- }
150
- if (end < position)
151
- continue;
152
- let inParens = false;
153
- while (position < srcset.length) {
154
- const char = srcset.charAt(position++);
155
- if (char === "(")
156
- inParens = true;
157
- else if (char === ")")
158
- inParens = false;
159
- else if (char === "," && !inParens)
160
- break;
161
- }
162
- }
163
- return parts.length > 0 ? parts.join("") + srcset.slice(written) : srcset;
164
- }
165
129
  function rebaseColorGradingLutPath(value, fromDir, toDir) {
166
130
  if (!value.trim().startsWith("{"))
167
131
  return value;
@@ -0,0 +1 @@
1
+ export declare function rewriteSrcsetUrls(srcset: string, rewriteUrl: (url: string) => string): string;
@@ -0,0 +1,46 @@
1
+ function skipSrcsetDescriptors(srcset, position) {
2
+ let inParens = false;
3
+ while (position < srcset.length) {
4
+ const char = srcset.charAt(position++);
5
+ if (char === "(")
6
+ inParens = true;
7
+ else if (char === ")")
8
+ inParens = false;
9
+ else if (char === "," && !inParens)
10
+ break;
11
+ }
12
+ return position;
13
+ }
14
+ function readSrcsetUrl(srcset, position) {
15
+ while (position < srcset.length && /[ \t\n\r\f,]/.test(srcset.charAt(position)))
16
+ position++;
17
+ const start = position;
18
+ while (position < srcset.length && !/[ \t\n\r\f]/.test(srcset.charAt(position)))
19
+ position++;
20
+ let end = position;
21
+ while (end > start && srcset.charAt(end - 1) === ",")
22
+ end--;
23
+ return { start, end, next: position };
24
+ }
25
+ export function rewriteSrcsetUrls(srcset, rewriteUrl) {
26
+ const parts = [];
27
+ let position = 0;
28
+ let written = 0;
29
+ // Preserve URL commas per https://html.spec.whatwg.org/multipage/images.html#parse-a-srcset-attribute
30
+ while (position < srcset.length) {
31
+ const { start, end, next } = readSrcsetUrl(srcset, position);
32
+ position = next;
33
+ if (end > start) {
34
+ const url = srcset.slice(start, end);
35
+ const rewritten = rewriteUrl(url);
36
+ if (rewritten !== url) {
37
+ parts.push(srcset.slice(written, start), rewritten);
38
+ written = end;
39
+ }
40
+ }
41
+ if (end < position)
42
+ continue;
43
+ position = skipSrcsetDescriptors(srcset, position);
44
+ }
45
+ return parts.length > 0 ? parts.join("") + srcset.slice(written) : srcset;
46
+ }
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/build-position-edits-render.ts; do not edit.
2
- const POSITION_EDITS_RENDER_IIFE = '"use strict";(()=>{function P(){return globalThis}function T(e,t){if(typeof window>"u")return;let o=P(),r=o.__hf?.onSwallowed;if(r)try{r({label:e,error:t})}catch(s){}(o.__hfDebug||o.__HYPERFRAMES_DEBUG)&&console.debug(`[hyperframes] ${e} swallowed:`,t)}var x=null;function R(e,t){if(x)try{x({source:"hf-preview",type:"analytics",event:e,properties:t??{}})}catch(o){T("runtime.analytics.site1",o)}}var W="http://www.w3.org/1999/xhtml";function N(e){return e!=null&&e.nodeType===1}function k(e){return N(e)&&(e.namespaceURI===W||e.namespaceURI==="http://www.w3.org/2000/svg")}var w="data-hf-edit-base-x",y="data-hf-edit-base-y",m="data-hf-edit-original-translate",_=e=>{let t=parseFloat(e??"");return Number.isFinite(t)?t:0},C=e=>{let t=[],o=0,r="";for(let s of e.trim())s==="("&&(o+=1),s===")"&&(o=Math.max(0,o-1)),/\\s/.test(s)&&o===0?(r&&t.push(r),r=""):r+=s;return r&&t.push(r),t},L=/^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)px$/,E=(e,t)=>L.test(e)&&L.test(t)?`${parseFloat(e)+parseFloat(t)}px`:`calc(${e} + ${t})`,O=(e,t,o)=>{if(!e||e==="none")return`${t} ${o}`;let[r,s,a]=C(e);if(r===void 0)return`${t} ${o}`;if(s===void 0)return`${E(r,t)} ${o}`;let i=a===void 0?"":` ${a}`;return`${E(r,t)} ${E(s,o)}${i}`},V=e=>{try{e.ownerDocument.defaultView?.gsap?.getProperty?.(e,"x")}catch{}},j=e=>{let t=e.style.getPropertyValue("translate").trim();if(t)return t==="none"?"":t;try{let o=e.ownerDocument.defaultView,r=o?o.getComputedStyle(e).getPropertyValue("translate").trim():"";return r==="none"?"":r}catch{return""}},b=new WeakMap;function B(e,t){let o=b.get(e);if(!t?.force&&o!==void 0&&e.style.getPropertyValue("translate")!==o){R("position_edit_fold_skipped",{hfId:e.getAttribute("data-hf-id")});return}let r=_(e.getAttribute("data-x"))-_(e.getAttribute(w)),s=_(e.getAttribute("data-y"))-_(e.getAttribute(y));e.hasAttribute(m)||e.setAttribute(m,j(e)),o===void 0&&V(e);let a=e.getAttribute(m)??"",i=O(a,`${r}px`,`${s}px`);e.style.setProperty("translate",i),b.set(e,e.style.getPropertyValue("translate"))}function S(e,t){let o=e.querySelectorAll(`[${m}]:not([${w}]):not([${y}])`);for(let a=0;a<o.length;a++){let i=o[a];if(i===void 0||!k(i))continue;let g=i.getAttribute(m)??"";g===""?i.style.removeProperty("translate"):i.style.setProperty("translate",g),i.removeAttribute(m),b.delete(i)}let r=e.querySelectorAll(`[${w}], [${y}]`),s=0;for(let a=0;a<r.length;a++){let i=r[a];i===void 0||!k(i)||(B(i,t),s+=1)}return s}var $="__hfPositionEditsSeekReapplyWrapped",D=new WeakSet,F=new WeakMap,I=new WeakMap;function M(e){let t=e,o=()=>{try{S(t.document)}catch{}},r=n=>typeof n=="function"&&(D.has(n)||!!n[$]),s=n=>{D.add(n);try{Object.defineProperty(n,$,{value:!0})}catch{}},a=n=>{if(typeof n!="function"||r(n))return n;let l=function(...u){let d=n.apply(this,u);return o(),d};return s(l),l},i=(n,l)=>{let u=F.get(n);if(u?.has(l))return!0;let d=Object.getOwnPropertyDescriptor(n,l);if(d?.configurable===!1){let c=n[l];return typeof c=="function"&&(n[l]=a(c),o()),!1}let p=n[l],f=d?.set;return Object.defineProperty(n,l,{configurable:!0,enumerable:d?.enumerable??!0,get:()=>p,set:c=>{p=a(c),f?.call(n,c)}}),p=a(p),u??(u=new Set),u.add(l),F.set(n,u),o(),!0},g=(n,l)=>{let u=I.get(t),d=Object.getOwnPropertyDescriptor(t,n);if(!u?.has(n)){if(d?.configurable===!1){let c=t[n];return c?i(c,l):!1}let f=t[n];Object.defineProperty(t,n,{configurable:!0,enumerable:d?.enumerable??!0,get:()=>f,set:c=>{f=c,f&&i(f,l)}}),u??(u=new Set),u.add(n),I.set(t,u)}let p=t[n];return p?i(p,l):!1},h=()=>{let n=g("__hf","seek"),l=g("__player","renderSeek");return n&&l};if(h())return;let v=120,A=t.setInterval(()=>{if(h()){t.clearInterval(A);return}v-=1,v<=0&&t.clearInterval(A)},50)}function H(){document.querySelector(`[${w}], [${y}]`)&&(S(document),M(window))}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",H,{once:!0}):H();})();\n';
2
+ const POSITION_EDITS_RENDER_IIFE = '"use strict";(()=>{function x(){return globalThis}function P(e,t){if(typeof window>"u")return;let o=x(),r=o.__hf?.onSwallowed;if(r)try{r({label:e,error:t})}catch(s){}(o.__hfDebug||o.__HYPERFRAMES_DEBUG)&&console.debug(`[hyperframes] ${e} swallowed:`,t)}var T=null;function R(e,t){if(T)try{T({source:"hf-preview",type:"analytics",event:e,properties:t??{}})}catch(o){P("runtime.analytics.site1",o)}}var M="http://www.w3.org/1999/xhtml";function N(e){return e!=null&&e.nodeType===1}function _(e){return N(e)&&(e.namespaceURI===M||e.namespaceURI==="http://www.w3.org/2000/svg")}var y="data-hf-edit-base-x",w="data-hf-edit-base-y",m="data-hf-edit-original-translate",k=e=>{let t=parseFloat(e??"");return Number.isFinite(t)?t:0},C=e=>{let t=[],o=0,r="";for(let s of e.trim())s==="("&&(o+=1),s===")"&&(o=Math.max(0,o-1)),/\\s/.test(s)&&o===0?(r&&t.push(r),r=""):r+=s;return r&&t.push(r),t},$=/^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)px$/,E=(e,t)=>$.test(e)&&$.test(t)?`${parseFloat(e)+parseFloat(t)}px`:`calc(${e} + ${t})`,O=(e,t,o)=>{if(!e||e==="none")return`${t} ${o}`;let[r,s,a]=C(e);if(r===void 0)return`${t} ${o}`;if(s===void 0)return`${E(r,t)} ${o}`;let i=a===void 0?"":` ${a}`;return`${E(r,t)} ${E(s,o)}${i}`},j=e=>{try{e.ownerDocument.defaultView?.gsap?.getProperty?.(e,"x")}catch{}},V=e=>{let t=e.style.getPropertyValue("translate").trim();if(t)return t==="none"?"":t;try{let o=e.ownerDocument.defaultView,r=o?o.getComputedStyle(e).getPropertyValue("translate").trim():"";return r==="none"?"":r}catch{return""}},b=new WeakMap;function B(e,t){let o=b.get(e);if(!t?.force&&o!==void 0&&e.style.getPropertyValue("translate")!==o){R("position_edit_fold_skipped",{hfId:e.getAttribute("data-hf-id")});return}let r=k(e.getAttribute("data-x"))-k(e.getAttribute(y)),s=k(e.getAttribute("data-y"))-k(e.getAttribute(w));e.hasAttribute(m)||e.setAttribute(m,V(e)),o===void 0&&j(e);let a=e.getAttribute(m)??"",i=O(a,`${r}px`,`${s}px`);e.style.setProperty("translate",i),b.set(e,e.style.getPropertyValue("translate"))}function h(e,t){let o=e.querySelectorAll(`[${m}]:not([${y}]):not([${w}])`);for(let a=0;a<o.length;a++){let i=o[a];if(i===void 0||!_(i))continue;let g=i.getAttribute(m)??"";g===""?i.style.removeProperty("translate"):i.style.setProperty("translate",g),i.removeAttribute(m),b.delete(i)}let r=e.querySelectorAll(`[${y}], [${w}]`),s=0;for(let a=0;a<r.length;a++){let i=r[a];i===void 0||!_(i)||(B(i,t),s+=1)}return s}var L="__hfPositionEditsSeekReapplyWrapped",D=new WeakSet,F=new WeakMap,H=new WeakMap;function W(e){let t=e,o=()=>{try{h(t.document)}catch{}},r=n=>typeof n=="function"&&(D.has(n)||!!n[L]),s=n=>{D.add(n);try{Object.defineProperty(n,L,{value:!0})}catch{}},a=n=>{if(typeof n!="function"||r(n))return n;let l=function(...u){let d=n.apply(this,u);return o(),d};return s(l),l},i=(n,l)=>{let u=F.get(n);if(u?.has(l))return!0;let d=Object.getOwnPropertyDescriptor(n,l);if(d?.configurable===!1){let c=n[l];return typeof c=="function"&&(n[l]=a(c),o()),!1}let p=n[l],f=d?.set;return Object.defineProperty(n,l,{configurable:!0,enumerable:d?.enumerable??!0,get:()=>p,set:c=>{p=a(c),f?.call(n,c)}}),p=a(p),u??(u=new Set),u.add(l),F.set(n,u),o(),!0},g=(n,l)=>{let u=H.get(t),d=Object.getOwnPropertyDescriptor(t,n);if(!u?.has(n)){if(d?.configurable===!1){let c=t[n];return c?i(c,l):!1}let f=t[n];Object.defineProperty(t,n,{configurable:!0,enumerable:d?.enumerable??!0,get:()=>f,set:c=>{f=c,f&&i(f,l)}}),u??(u=new Set),u.add(n),H.set(t,u)}let p=t[n];return p?i(p,l):!1},S=()=>{let n=g("__hf","seek"),l=g("__player","renderSeek");return n&&l};if(S())return;let v=120,A=t.setInterval(()=>{if(S()){t.clearInterval(A);return}v-=1,v<=0&&t.clearInterval(A)},50)}function I(){document.querySelector(`[${y}], [${w}]`)&&(h(document),W(window))}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",I,{once:!0}):I();})();\n';
3
3
  /** Returns the pre-built position-edits render IIFE as a string constant. */
4
4
  export function getPositionEditsRenderScript() {
5
5
  return POSITION_EDITS_RENDER_IIFE;