@vectoriox/iox-ui 5.76.1 → 5.77.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.
@@ -38,8 +38,12 @@ declare enum FrameExtension {
38
38
  Webp = "webp",
39
39
  Jpeg = "jpg"
40
40
  }
41
- /** The most pictures one set may hold. Past this a phone runs out of memory before the visitor runs out of scroll. */
42
- declare const MAX_FRAMES = 300;
41
+ /**
42
+ * The most pictures one set may hold. A film's smoothness is its density, pictures per screen of scrolling: 210 over 18
43
+ * screens stepped on Doron's iPhone as a glide slowed, 1050 did not (2026-11-05). Memory is not what limits it: the player
44
+ * keeps the downloaded files (about 10 kB a phone picture) and only a window of them unpacked (`unpackedFrameCount`).
45
+ */
46
+ declare const MAX_FRAMES = 1200;
43
47
  /** The most one picture may weigh when uploaded. */
44
48
  declare const MAX_FRAME_BYTES: number;
45
49
  /** A sequence's name in storage: what the browser generates and what the server accepts in a key. */
@@ -109,8 +113,6 @@ declare function fittedFrameSize(width: number, height: number, longSide: number
109
113
  declare function videoFrameCount(seconds: number): number;
110
114
  /** Which of `sourceCount` pictures make a set of `count`, spread evenly, first and last always in. */
111
115
  declare function sampledFrameIndexes(sourceCount: number, count: number): number[];
112
- /** A phone plays half the pictures: it has less memory, and half is plenty on a small screen. */
113
- declare function phoneFrameCount(wideCount: number): number;
114
116
  /** Rendered frames in their real order: `frame2` before `frame10`. */
115
117
  declare function sortFrameFiles<T extends {
116
118
  name: string;
@@ -471,5 +473,5 @@ declare function attachVideos(container: ParentNode, options?: VideoRuntimeOptio
471
473
  /** The runtime already running at `el`, if any. */
472
474
  declare function videoRuntimeOf(el: Element | null | undefined): VideoRuntime | null;
473
475
 
474
- export { CONSENT_GRANT_EVENT, CONSENT_PREFERENCES_EVENT, FRAMES_PHONE_MAX_WIDTH, FRAMES_SHOWING_ATTRIBUTE, FRAMES_UNPACKED_BYTES, FRAME_BLEND_STEPS, FRAME_SEQUENCE_ID, FrameExtension, FramePlayer, FrameSet, FramesClass, MAX_FRAMES, MAX_FRAME_BYTES, PHONE_LONG_SIDE, VIDEO_EMBED_ORIGINS, VIDEO_NODE_TYPE, VIDEO_PROVIDER_COMPANY, VIDEO_PROVIDER_LABEL, VIDEO_WORDS, VideoAttribute, VideoClass, VideoIcon, VideoKind, VideoMode, VideoRuntime, VideoSource, VideoTrait, WIDE_LONG_SIDE, attachVideoAt, attachVideos, documentConsentTransport, fittedFrameSize, frameBlendAt, frameFileName, frameIndexAt, frameLoadOrder, framePosition, frameSetFor, frameSummary, frameUrl, framesToHold, isThirdPartyPoster, nearestLoadedFrame, normalizeFrameSequence, parseVideoUrl, phoneFrameCount, phoneFrameSrcset, planVideo, readConsentRecord, sampledFrameIndexes, serializeVideoAttributes, sortFrameFiles, unpackedFrameCount, videoConsentCategory, videoEmbedUrl, videoFrameCount, videoInnerHtml, videoPlayLabel, videoPosterFileName, videoProp, videoRefOf, videoRootAttributes, videoRuntimeOf, videoSourceOf, videoThumbnailRequest, videoWords };
476
+ export { CONSENT_GRANT_EVENT, CONSENT_PREFERENCES_EVENT, FRAMES_PHONE_MAX_WIDTH, FRAMES_SHOWING_ATTRIBUTE, FRAMES_UNPACKED_BYTES, FRAME_BLEND_STEPS, FRAME_SEQUENCE_ID, FrameExtension, FramePlayer, FrameSet, FramesClass, MAX_FRAMES, MAX_FRAME_BYTES, PHONE_LONG_SIDE, VIDEO_EMBED_ORIGINS, VIDEO_NODE_TYPE, VIDEO_PROVIDER_COMPANY, VIDEO_PROVIDER_LABEL, VIDEO_WORDS, VideoAttribute, VideoClass, VideoIcon, VideoKind, VideoMode, VideoRuntime, VideoSource, VideoTrait, WIDE_LONG_SIDE, attachVideoAt, attachVideos, documentConsentTransport, fittedFrameSize, frameBlendAt, frameFileName, frameIndexAt, frameLoadOrder, framePosition, frameSetFor, frameSummary, frameUrl, framesToHold, isThirdPartyPoster, nearestLoadedFrame, normalizeFrameSequence, parseVideoUrl, phoneFrameSrcset, planVideo, readConsentRecord, sampledFrameIndexes, serializeVideoAttributes, sortFrameFiles, unpackedFrameCount, videoConsentCategory, videoEmbedUrl, videoFrameCount, videoInnerHtml, videoPlayLabel, videoPosterFileName, videoProp, videoRefOf, videoRootAttributes, videoRuntimeOf, videoSourceOf, videoThumbnailRequest, videoWords };
475
477
  export type { ConsentGrantDetail, ConsentTransport, FrameBlend, FramePlayerOptions, FrameSequence, FrameSetInfo, FrameSizes, VideoMarkupOptions, VideoNodeLike, VideoPlan, VideoRef, VideoRuntimeOptions, VideoThumbnailRequest, VideoWords };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectoriox/iox-ui",
3
- "version": "5.76.1",
3
+ "version": "5.77.0",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=20.0.0",
6
6
  "@angular/core": ">=20.0.0",
@@ -218,6 +218,23 @@ declare const SCENE_MAX_SCREENS = 20;
218
218
  */
219
219
  declare function sceneScreensOf(height: string | null | undefined): number | null;
220
220
  declare function sceneHeightForScreens(screens: number): string;
221
+ /**
222
+ * Pictures per screen of scrolling at which a played film stays smooth as a glide slows to a stop. Measured on Doron's
223
+ * iPhone (2026-11-05): Studio Alma's film at about 11 a screen stepped, at about 55 it did not.
224
+ */
225
+ declare const SCENE_FILM_SMOOTH = 40;
226
+ interface SceneFilmDensity {
227
+ /** Pictures per screen of scrolling over most of the play: half its scroll holds at least this many. */
228
+ perScreen: number;
229
+ /** The screens of scrolling the film plays over; where it holds still is not counted. */
230
+ screens: number;
231
+ smooth: boolean;
232
+ }
233
+ /**
234
+ * How densely a film of `count` pictures plays on a Scene of `screens`, by its Play track; null when nothing plays. A
235
+ * film's smoothness is its density, never the drawing: spread thin, a slowing glide holds each picture for many refreshes.
236
+ */
237
+ declare function sceneFilmDensity(tracks: ReadonlyArray<SceneTrack> | null | undefined, screens: number | null, count: number): SceneFilmDensity | null;
221
238
  /** Motion v2 §3d's limits for scroll-linked work on one page. */
222
239
  declare const SCENE_BUDGET: {
223
240
  readonly heldSections: 1;
@@ -402,5 +419,5 @@ declare function revealStagger(groups: number, by: TextRevealBy): number;
402
419
  */
403
420
  declare function playTextReveal(el: HTMLElement, by: TextRevealBy, timing: KeyframeAnimationOptions): Animation | null;
404
421
 
405
- export { LIGHT_UP_PIECE_CAP, REVEAL_PIECE_CAP, SCENE_BUDGET, SCENE_DAMPING, SCENE_FLOW_RULES, SCENE_LENGTH, SCENE_MAX_SCREENS, SCENE_MIN_SCREENS, SCENE_PHONE_LENGTH, SCENE_PHONE_MAX_WIDTH, SCENE_REDUCED_MOTION_CSS, SCENE_REVEAL_DEFAULT, SCENE_STAGE_HEIGHT, SCENE_TRACK_DEFS, SCENE_WORDS, SCENE_WORD_DIM, SceneAttribute, SceneProp, SceneRevealFrom, ScrollDriver, TextRevealBy, applySceneFirstFrame, attachScenes, buildSceneRecipe, colourAt, dampToward, lineIndexes, movedSceneKey, normalizeTracks, playTextReveal, revealClipPath, revealStagger, sceneBudget, sceneColourKeyAt, sceneDisplayValue, sceneFirstFrame, sceneFrame, sceneFrameDeclarations, sceneHasKeyAt, sceneHeightForScreens, sceneKeyPosition, sceneLayersOf, sceneNodeAttributes, scenePlayed, sceneRevealFromOf, sceneRootOnPath, sceneScreensOf, sceneScrollState, sceneTrackDef, sceneTrackSpan, sceneWordOpacity, sceneWordsLit, scenesIn, splitText, syncSceneAttributes, textPieces, valueAt, withSceneKey, withScenePlayback, withSceneRevealFrom, withoutSceneKey, withoutScenePlayback };
406
- export type { SceneBudget, SceneFrame, SceneFrameOptions, SceneFrameState, SceneKey, SceneLayerNode, SceneNodeSource, SceneTrack, SceneTrackDef, SceneTreeNode, SceneWords, ScrollDriverOptions, SplitText, SplitTextOptions, TextPiece };
422
+ export { LIGHT_UP_PIECE_CAP, REVEAL_PIECE_CAP, SCENE_BUDGET, SCENE_DAMPING, SCENE_FILM_SMOOTH, SCENE_FLOW_RULES, SCENE_LENGTH, SCENE_MAX_SCREENS, SCENE_MIN_SCREENS, SCENE_PHONE_LENGTH, SCENE_PHONE_MAX_WIDTH, SCENE_REDUCED_MOTION_CSS, SCENE_REVEAL_DEFAULT, SCENE_STAGE_HEIGHT, SCENE_TRACK_DEFS, SCENE_WORDS, SCENE_WORD_DIM, SceneAttribute, SceneProp, SceneRevealFrom, ScrollDriver, TextRevealBy, applySceneFirstFrame, attachScenes, buildSceneRecipe, colourAt, dampToward, lineIndexes, movedSceneKey, normalizeTracks, playTextReveal, revealClipPath, revealStagger, sceneBudget, sceneColourKeyAt, sceneDisplayValue, sceneFilmDensity, sceneFirstFrame, sceneFrame, sceneFrameDeclarations, sceneHasKeyAt, sceneHeightForScreens, sceneKeyPosition, sceneLayersOf, sceneNodeAttributes, scenePlayed, sceneRevealFromOf, sceneRootOnPath, sceneScreensOf, sceneScrollState, sceneTrackDef, sceneTrackSpan, sceneWordOpacity, sceneWordsLit, scenesIn, splitText, syncSceneAttributes, textPieces, valueAt, withSceneKey, withScenePlayback, withSceneRevealFrom, withoutSceneKey, withoutScenePlayback };
423
+ export type { SceneBudget, SceneFilmDensity, SceneFrame, SceneFrameOptions, SceneFrameState, SceneKey, SceneLayerNode, SceneNodeSource, SceneTrack, SceneTrackDef, SceneTreeNode, SceneWords, ScrollDriverOptions, SplitText, SplitTextOptions, TextPiece };
@@ -42,8 +42,12 @@ export enum FrameExtension {
42
42
  Jpeg = 'jpg',
43
43
  }
44
44
 
45
- /** The most pictures one set may hold. Past this a phone runs out of memory before the visitor runs out of scroll. */
46
- export const MAX_FRAMES = 300;
45
+ /**
46
+ * The most pictures one set may hold. A film's smoothness is its density, pictures per screen of scrolling: 210 over 18
47
+ * screens stepped on Doron's iPhone as a glide slowed, 1050 did not (2026-11-05). Memory is not what limits it: the player
48
+ * keeps the downloaded files (about 10 kB a phone picture) and only a window of them unpacked (`unpackedFrameCount`).
49
+ */
50
+ export const MAX_FRAMES = 1200;
47
51
  /** The most one picture may weigh when uploaded. */
48
52
  export const MAX_FRAME_BYTES = 3 * 1024 * 1024;
49
53
  /** A sequence's name in storage: what the browser generates and what the server accepts in a key. */
@@ -206,10 +210,10 @@ export interface FrameSizes {
206
210
  * tall stage by its height, so a picture sized to the phone's width would be enlarged five times. */
207
211
  export const WIDE_LONG_SIDE = 1600;
208
212
  export const PHONE_LONG_SIDE = 1100;
209
- /** How many pictures a second of video becomes, before the cap. */
210
- const VIDEO_FRAMES_PER_SECOND = 24;
211
- /** A video's cap: the pictures a scroll can show, at a weight a page can carry. A set of pictures is the author's own count. */
212
- const VIDEO_MAX_FRAMES = 180;
213
+ /** How many pictures a second of video becomes, before the cap: what most recordings hold, so few are the same twice. */
214
+ const VIDEO_FRAMES_PER_SECOND = 30;
215
+ /** A video's cap: half a minute at that rate. A set of pictures is the author's own count, up to `MAX_FRAMES`. */
216
+ const VIDEO_MAX_FRAMES = 900;
213
217
 
214
218
  /** `width`×`height` fitted inside `longSide`, never enlarged. */
215
219
  export function fittedFrameSize(width: number, height: number, longSide: number): FrameSizes {
@@ -229,11 +233,6 @@ export function sampledFrameIndexes(sourceCount: number, count: number): number[
229
233
  return Array.from({ length: count }, (_, i) => Math.round((i * (sourceCount - 1)) / (count - 1)));
230
234
  }
231
235
 
232
- /** A phone plays half the pictures: it has less memory, and half is plenty on a small screen. */
233
- export function phoneFrameCount(wideCount: number): number {
234
- return Math.max(2, Math.ceil(wideCount / 2));
235
- }
236
-
237
236
  const NATURAL = new Intl.Collator('en', { numeric: true, sensitivity: 'base' });
238
237
 
239
238
  /** Rendered frames in their real order: `frame2` before `frame10`. */
@@ -238,6 +238,41 @@ export function sceneHeightForScreens(screens: number): string {
238
238
  return `${roundTo((held + 1) * 100, 1)}vh`;
239
239
  }
240
240
 
241
+ /**
242
+ * Pictures per screen of scrolling at which a played film stays smooth as a glide slows to a stop. Measured on Doron's
243
+ * iPhone (2026-11-05): Studio Alma's film at about 11 a screen stepped, at about 55 it did not.
244
+ */
245
+ export const SCENE_FILM_SMOOTH = 40;
246
+
247
+ export interface SceneFilmDensity {
248
+ /** Pictures per screen of scrolling over most of the play: half its scroll holds at least this many. */
249
+ perScreen: number;
250
+ /** The screens of scrolling the film plays over; where it holds still is not counted. */
251
+ screens: number;
252
+ smooth: boolean;
253
+ }
254
+
255
+ /**
256
+ * How densely a film of `count` pictures plays on a Scene of `screens`, by its Play track; null when nothing plays. A
257
+ * film's smoothness is its density, never the drawing: spread thin, a slowing glide holds each picture for many refreshes.
258
+ */
259
+ export function sceneFilmDensity(tracks: ReadonlyArray<SceneTrack> | null | undefined, screens: number | null, count: number): SceneFilmDensity | null {
260
+ const keys = normalizeTracks(tracks).find(track => track.prop === SceneProp.Play)?.keys ?? [];
261
+ if (count < 2 || !screens || screens <= 0) return null;
262
+ const stretches: { perScreen: number; screens: number }[] = [];
263
+ for (let i = 1; i < keys.length; i++) {
264
+ const scrolled = (keys[i][0] - keys[i - 1][0]) * screens;
265
+ const played = Math.abs(Number(keys[i][1]) - Number(keys[i - 1][1]));
266
+ if (scrolled > 0 && played > 0) stretches.push({ perScreen: ((played / 100) * (count - 1)) / scrolled, screens: scrolled });
267
+ }
268
+ if (!stretches.length) return null;
269
+ stretches.sort((a, b) => a.perScreen - b.perScreen);
270
+ const total = stretches.reduce((sum, stretch) => sum + stretch.screens, 0);
271
+ let covered = 0;
272
+ const typical = stretches.find(stretch => (covered += stretch.screens) >= total / 2)!;
273
+ return { perScreen: Math.round(typical.perScreen), screens: roundTo(total, 1), smooth: typical.perScreen >= SCENE_FILM_SMOOTH };
274
+ }
275
+
241
276
  /** Motion v2 §3d's limits for scroll-linked work on one page. */
242
277
  export const SCENE_BUDGET = { heldSections: 1, movingLayers: 8 } as const;
243
278