@vectoriox/iox-ui 5.78.0 → 5.79.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.
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +307 -7
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-harness-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-media-core.mjs +23 -1
- package/fesm2022/vectoriox-iox-ui-media-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs +3 -6
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs.map +1 -1
- package/generation-core/index.d.ts +178 -5
- package/harness-core/index.d.ts +3 -0
- package/media-core/index.d.ts +19 -1
- package/package.json +1 -1
- package/scene-core/index.d.ts +1 -4
- package/src/generation-core/compose-site.ts +13 -0
- package/src/generation-core/film.ts +91 -0
- package/src/generation-core/patterns.ts +5 -1
- package/src/generation-core/plan-wire.ts +4 -2
- package/src/generation-core/public-api.ts +1 -0
- package/src/generation-core/showcase.ts +16 -2
- package/src/generation-core/site-plan.model.ts +8 -0
- package/src/generation-core/site-spec.generated.ts +161 -1
- package/src/generation-core/validate-site-plan.ts +33 -0
- package/src/harness-core/generation-view.ts +3 -1
- package/src/media-core/frames.ts +23 -0
- package/src/scene-core/scene-edit.ts +3 -5
|
@@ -12,7 +12,11 @@ export const SPEC_SITE_PLAN_SHAPE = {
|
|
|
12
12
|
"contentTypes": "contentType[]",
|
|
13
13
|
"dataSources": "(cmsDataSource | relativeDataSource)[] — merged by alias into the org library",
|
|
14
14
|
"pages": "page[]",
|
|
15
|
-
"globals": "globals"
|
|
15
|
+
"globals": "globals",
|
|
16
|
+
"film?": "film — the owner's film as pictures (SC6b), set by the build and placed by placeFilm; never a model's"
|
|
17
|
+
},
|
|
18
|
+
"film": {
|
|
19
|
+
"sequence": "object — a media-core FrameSequence: where the numbered pictures live, how many, their size"
|
|
16
20
|
},
|
|
17
21
|
"sourceTemplate": {
|
|
18
22
|
"kind": "'template'",
|
|
@@ -582,6 +586,157 @@ export const SPEC_SITE_PATTERNS = {
|
|
|
582
586
|
]
|
|
583
587
|
}
|
|
584
588
|
},
|
|
589
|
+
"hero.scene-object": {
|
|
590
|
+
"family": "hero",
|
|
591
|
+
"scene": true,
|
|
592
|
+
"film": true,
|
|
593
|
+
"label": "Hero — your film plays as you scroll",
|
|
594
|
+
"intent": "The studio's own film plays forward and back with the scroll behind the headline while the page holds still.",
|
|
595
|
+
"node": {
|
|
596
|
+
"t": "Section",
|
|
597
|
+
"es": "section",
|
|
598
|
+
"scheme": "inverse",
|
|
599
|
+
"role": "scene",
|
|
600
|
+
"lay": {
|
|
601
|
+
"position": "relative",
|
|
602
|
+
"display": "block",
|
|
603
|
+
"width": "100%",
|
|
604
|
+
"height": "300vh",
|
|
605
|
+
"padding": "0px"
|
|
606
|
+
},
|
|
607
|
+
"c": [
|
|
608
|
+
{
|
|
609
|
+
"t": "Container",
|
|
610
|
+
"role": "scene.stage",
|
|
611
|
+
"lay": {
|
|
612
|
+
"position": "sticky",
|
|
613
|
+
"top": "0px",
|
|
614
|
+
"display": "block",
|
|
615
|
+
"width": "100%",
|
|
616
|
+
"height": "100svh",
|
|
617
|
+
"overflow": "hidden",
|
|
618
|
+
"padding": "0px"
|
|
619
|
+
},
|
|
620
|
+
"c": [
|
|
621
|
+
{
|
|
622
|
+
"t": "Video",
|
|
623
|
+
"es": "media",
|
|
624
|
+
"film": true,
|
|
625
|
+
"lay": {
|
|
626
|
+
"position": "absolute",
|
|
627
|
+
"top": "0px",
|
|
628
|
+
"left": "0px",
|
|
629
|
+
"width": "100%",
|
|
630
|
+
"height": "100%",
|
|
631
|
+
"aspectRatio": "auto",
|
|
632
|
+
"borderRadius": "0px"
|
|
633
|
+
},
|
|
634
|
+
"tracks": [
|
|
635
|
+
{
|
|
636
|
+
"prop": "play",
|
|
637
|
+
"keys": [
|
|
638
|
+
[
|
|
639
|
+
0,
|
|
640
|
+
0
|
|
641
|
+
],
|
|
642
|
+
[
|
|
643
|
+
1,
|
|
644
|
+
100
|
|
645
|
+
]
|
|
646
|
+
]
|
|
647
|
+
}
|
|
648
|
+
]
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
"t": "Container",
|
|
652
|
+
"es": "media-scrim",
|
|
653
|
+
"lay": {
|
|
654
|
+
"display": "flex",
|
|
655
|
+
"flexDirection": "column",
|
|
656
|
+
"gap": "14px",
|
|
657
|
+
"position": "absolute",
|
|
658
|
+
"left": "0px",
|
|
659
|
+
"bottom": "0px",
|
|
660
|
+
"width": "100%",
|
|
661
|
+
"padding": "48px 32px 56px 32px"
|
|
662
|
+
},
|
|
663
|
+
"c": [
|
|
664
|
+
{
|
|
665
|
+
"t": "Heading",
|
|
666
|
+
"es": "display",
|
|
667
|
+
"level": "h1",
|
|
668
|
+
"slot": "headline",
|
|
669
|
+
"max": 56,
|
|
670
|
+
"fromBusiness": "name",
|
|
671
|
+
"tracks": [
|
|
672
|
+
{
|
|
673
|
+
"prop": "y",
|
|
674
|
+
"keys": [
|
|
675
|
+
[
|
|
676
|
+
0,
|
|
677
|
+
0
|
|
678
|
+
],
|
|
679
|
+
[
|
|
680
|
+
1,
|
|
681
|
+
-6
|
|
682
|
+
]
|
|
683
|
+
]
|
|
684
|
+
}
|
|
685
|
+
]
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
"t": "Text",
|
|
689
|
+
"es": "lead",
|
|
690
|
+
"slot": "lead",
|
|
691
|
+
"max": 180,
|
|
692
|
+
"fromBusiness": "tagline",
|
|
693
|
+
"ph": "copy",
|
|
694
|
+
"tracks": [
|
|
695
|
+
{
|
|
696
|
+
"prop": "opacity",
|
|
697
|
+
"keys": [
|
|
698
|
+
[
|
|
699
|
+
0.3,
|
|
700
|
+
0
|
|
701
|
+
],
|
|
702
|
+
[
|
|
703
|
+
0.45,
|
|
704
|
+
1
|
|
705
|
+
]
|
|
706
|
+
]
|
|
707
|
+
}
|
|
708
|
+
]
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
"t": "ButtonBlock",
|
|
712
|
+
"es": "button-primary",
|
|
713
|
+
"href": "/contact",
|
|
714
|
+
"slot": "cta",
|
|
715
|
+
"max": 24,
|
|
716
|
+
"text": "Get in touch",
|
|
717
|
+
"tracks": [
|
|
718
|
+
{
|
|
719
|
+
"prop": "opacity",
|
|
720
|
+
"keys": [
|
|
721
|
+
[
|
|
722
|
+
0.35,
|
|
723
|
+
0
|
|
724
|
+
],
|
|
725
|
+
[
|
|
726
|
+
0.5,
|
|
727
|
+
1
|
|
728
|
+
]
|
|
729
|
+
]
|
|
730
|
+
}
|
|
731
|
+
]
|
|
732
|
+
}
|
|
733
|
+
]
|
|
734
|
+
}
|
|
735
|
+
]
|
|
736
|
+
}
|
|
737
|
+
]
|
|
738
|
+
}
|
|
739
|
+
},
|
|
585
740
|
"story.scene-chapters": {
|
|
586
741
|
"family": "story",
|
|
587
742
|
"scene": true,
|
|
@@ -5491,6 +5646,11 @@ export const SPEC_SITE_LIBRARY = [
|
|
|
5491
5646
|
"family": "hero",
|
|
5492
5647
|
"composed": true
|
|
5493
5648
|
},
|
|
5649
|
+
{
|
|
5650
|
+
"id": "hero.scene-object",
|
|
5651
|
+
"family": "hero",
|
|
5652
|
+
"composed": true
|
|
5653
|
+
},
|
|
5494
5654
|
{
|
|
5495
5655
|
"id": "proof.logo-strip",
|
|
5496
5656
|
"family": "proof",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { flavorPackById, parseHexColor, SPEC_AXES } from '@vectoriox/iox-ui/design-core';
|
|
2
2
|
import { formPresetById } from '@vectoriox/iox-ui/forms-core';
|
|
3
|
+
import { filmScreensFor, normalizeFrameSequence } from '@vectoriox/iox-ui/media-core';
|
|
3
4
|
import { SEO_DESCRIPTION_MAX, SEO_TITLE_MAX } from '@vectoriox/iox-ui/seo-core';
|
|
4
5
|
|
|
5
6
|
import {
|
|
@@ -72,6 +73,9 @@ export enum SitePlanIssueCode {
|
|
|
72
73
|
/** SC6: a Scene below How animated Expressive, a second Scene on a page, or Scenes on more than the home page and
|
|
73
74
|
* one other — the pages answer by choosing another pattern. */
|
|
74
75
|
SceneBudget = 'scene-budget',
|
|
76
|
+
/** SC6b: the owner's film — pictures that cannot play, too few to play smoothly, or a film's pattern on a plan with
|
|
77
|
+
* no film (or twice). Set by the build, never by a model. */
|
|
78
|
+
Film = 'film',
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
export type SitePlanIssue = PathIssue<SitePlanIssueCode>;
|
|
@@ -110,6 +114,7 @@ const SEED_FIXED_KEYS = shapeKeys(SHAPE['seed']);
|
|
|
110
114
|
const CMS_SOURCE_KEYS = shapeKeys(SHAPE['cmsDataSource']);
|
|
111
115
|
const RELATIVE_SOURCE_KEYS = shapeKeys(SHAPE['relativeDataSource']);
|
|
112
116
|
const PAGE_KEYS = shapeKeys(SHAPE['page']);
|
|
117
|
+
const FILM_KEYS = shapeKeys(SHAPE['film']);
|
|
113
118
|
const SECTION_KEYS = shapeKeys(SHAPE['section']);
|
|
114
119
|
const GLOBAL_POSITION: Readonly<Record<`${GlobalSlot}`, `${PatternGlobalPosition}`>> = {
|
|
115
120
|
[GlobalSlot.Header]: PatternGlobalPosition.Before,
|
|
@@ -476,6 +481,33 @@ function validateScenes(c: Collector, pages: unknown[], animate: string | null):
|
|
|
476
481
|
});
|
|
477
482
|
}
|
|
478
483
|
|
|
484
|
+
/** SC6b: a film plays only when its pictures can (and smoothly over the shortest hold); its pattern only with a film, once. */
|
|
485
|
+
function validateFilm(c: Collector, film: unknown, pages: unknown[]): void {
|
|
486
|
+
let playable = false;
|
|
487
|
+
if (film !== undefined) {
|
|
488
|
+
if (!isObject(film)) {
|
|
489
|
+
c.add(SitePlanIssueCode.Shape, 'film', 'must be an object');
|
|
490
|
+
} else {
|
|
491
|
+
c.keys(film, FILM_KEYS, 'film');
|
|
492
|
+
const sequence = normalizeFrameSequence(film['sequence']);
|
|
493
|
+
if (!sequence) c.add(SitePlanIssueCode.Film, 'film.sequence', 'not a film\'s pictures (a media-core FrameSequence)');
|
|
494
|
+
else if (filmScreensFor(sequence.wide.count) === null) c.add(SitePlanIssueCode.Film, 'film.sequence', `${sequence.wide.count} pictures — too few to play smoothly`);
|
|
495
|
+
else playable = true;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
let placed = 0;
|
|
499
|
+
pages.forEach((raw, i) => {
|
|
500
|
+
if (!isObject(raw) || !Array.isArray(raw['sections'])) return;
|
|
501
|
+
raw['sections'].forEach((s, j) => {
|
|
502
|
+
const pattern = isObject(s) && typeof s['pattern'] === 'string' ? patternById(s['pattern']) : null;
|
|
503
|
+
if (!pattern?.film) return;
|
|
504
|
+
const path = `pages[${i}].sections[${j}].pattern`;
|
|
505
|
+
if (!playable) c.add(SitePlanIssueCode.Film, path, `"${s['pattern']}" plays the owner's film — this plan has none`);
|
|
506
|
+
else if (++placed > 1) c.add(SitePlanIssueCode.Film, path, 'the film plays once on a site');
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
479
511
|
function validatePageSeo(c: Collector, seo: unknown, path: string): void {
|
|
480
512
|
if (seo === undefined) return;
|
|
481
513
|
if (!isObject(seo)) return c.add(SitePlanIssueCode.Shape, path, 'must be an object');
|
|
@@ -523,6 +555,7 @@ export function validateSitePlan(plan: unknown): SitePlanValidation {
|
|
|
523
555
|
validatePages(c, c.array(plan, 'pages', ''), types, sources, typeof design['density'] === 'string' ? design['density'] : null);
|
|
524
556
|
validateGlobals(c, plan['globals']);
|
|
525
557
|
validateScenes(c, c.array(plan, 'pages', ''), typeof design['animate'] === 'string' ? design['animate'] : null);
|
|
558
|
+
validateFilm(c, plan['film'], c.array(plan, 'pages', ''));
|
|
526
559
|
return { valid: c.issues.length === 0, issues: c.issues };
|
|
527
560
|
}
|
|
528
561
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DesignChoices } from '@vectoriox/iox-ui/design-core';
|
|
2
|
+
import type { FrameSequence } from '@vectoriox/iox-ui/media-core';
|
|
2
3
|
import type { AiGenerationStage, AiStep, PlanChange, PlanEdit, SitePlan } from '@vectoriox/iox-ui/generation-core';
|
|
3
4
|
|
|
4
5
|
import type { StyleRead } from './rank';
|
|
@@ -78,7 +79,8 @@ export interface AiGenerationView {
|
|
|
78
79
|
stage: `${AiGenerationStage}`;
|
|
79
80
|
/** The model step the runner is working on; null = the owner's move. */
|
|
80
81
|
task: `${AiStep}` | null;
|
|
81
|
-
|
|
82
|
+
/** `film`: the owner's film as pictures (SC6b), told by the wizard at Build — `placeFilm` places it. */
|
|
83
|
+
intake: { prompt: string; files: GenerationFileView[]; addresses: string[]; auto: boolean; film?: FrameSequence };
|
|
82
84
|
readers: GenerationReadersView;
|
|
83
85
|
reads: { analysis?: { value: SiteAnalysis; model: string; usageIds: string[] }; style?: StyleRead | null };
|
|
84
86
|
/** What Design and Plan use: the owner's edit, else the reading. */
|
package/src/media-core/frames.ts
CHANGED
|
@@ -226,6 +226,29 @@ export function videoFrameCount(seconds: number): number {
|
|
|
226
226
|
return Math.min(VIDEO_MAX_FRAMES, Math.max(2, Math.ceil(seconds * VIDEO_FRAMES_PER_SECOND)));
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Pictures per screen of scrolling at which a played film stays smooth as a glide slows to a stop. Measured on Doron's
|
|
231
|
+
* iPhone (2026-11-05): Studio Alma's film at about 11 a screen stepped, at about 55 it did not.
|
|
232
|
+
*/
|
|
233
|
+
export const FILM_SMOOTH_PER_SCREEN = 40;
|
|
234
|
+
/** The fewest and most screens a film the build places holds the page for (SC6b): long enough to feel, short enough to pass. */
|
|
235
|
+
export const FILM_MIN_SCREENS = 1;
|
|
236
|
+
export const FILM_MAX_SCREENS = 4;
|
|
237
|
+
/** What the build prepares a film to: about 60 pictures a screen at the longest hold — denser than smooth, never wasted. */
|
|
238
|
+
export const FILM_PREPARED_PICTURES = FILM_MAX_SCREENS * 60 + 1;
|
|
239
|
+
/** The fewest pictures a placed film may have: smooth over the shortest hold. */
|
|
240
|
+
export const FILM_MIN_PICTURES = FILM_MIN_SCREENS * FILM_SMOOTH_PER_SCREEN + 1;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The screens a film of `count` pictures holds the page for (SC6b, the composer's density rule): the stretches between
|
|
244
|
+
* pictures are what scroll, so `(count − 1) ÷ 40`, rounded DOWN to a tenth (150 pictures over 3.75 screens measure 39.7 a
|
|
245
|
+
* screen, thin), within the hold's limits. Null: too few pictures to play smoothly over the shortest hold.
|
|
246
|
+
*/
|
|
247
|
+
export function filmScreensFor(count: number): number | null {
|
|
248
|
+
if (!Number.isInteger(count) || count < FILM_MIN_PICTURES) return null;
|
|
249
|
+
return Math.min(FILM_MAX_SCREENS, Math.floor(((count - 1) / FILM_SMOOTH_PER_SCREEN) * 10) / 10);
|
|
250
|
+
}
|
|
251
|
+
|
|
229
252
|
/** Which of `sourceCount` pictures make a set of `count`, spread evenly, first and last always in. */
|
|
230
253
|
export function sampledFrameIndexes(sourceCount: number, count: number): number[] {
|
|
231
254
|
if (count >= sourceCount) return Array.from({ length: sourceCount }, (_, i) => i);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SceneRole } from '@vectoriox/iox-ui/composite-core';
|
|
2
|
+
import { FILM_SMOOTH_PER_SCREEN } from '@vectoriox/iox-ui/media-core';
|
|
2
3
|
|
|
3
4
|
import { normalizeTracks, SCENE_REVEAL_DEFAULT, SceneKey, SceneNodeSource, SceneProp, SceneRevealFrom, SceneTrack, valueAt } from './scene';
|
|
4
5
|
|
|
@@ -238,11 +239,8 @@ export function sceneHeightForScreens(screens: number): string {
|
|
|
238
239
|
return `${roundTo((held + 1) * 100, 1)}vh`;
|
|
239
240
|
}
|
|
240
241
|
|
|
241
|
-
/**
|
|
242
|
-
|
|
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;
|
|
242
|
+
/** Pictures per screen of scrolling at which a played film stays smooth (media-core's, which the composer sizes by). */
|
|
243
|
+
export const SCENE_FILM_SMOOTH = FILM_SMOOTH_PER_SCREEN;
|
|
246
244
|
|
|
247
245
|
export interface SceneFilmDensity {
|
|
248
246
|
/** Pictures per screen of scrolling over most of the play: half its scroll holds at least this many. */
|