@vectoriox/iox-ui 5.77.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/design-core/index.d.ts +34 -4
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +34 -4
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +950 -15
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-harness-core.mjs +38 -3
- 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 +585 -8
- package/harness-core/index.d.ts +40 -2
- package/media-core/index.d.ts +19 -1
- package/package.json +1 -1
- package/scene-core/index.d.ts +1 -4
- package/src/design-core/flavor-spec.generated.ts +34 -4
- package/src/generation-core/compose-site.ts +25 -2
- package/src/generation-core/film.ts +91 -0
- package/src/generation-core/patterns.ts +41 -4
- package/src/generation-core/plan-wire.ts +4 -2
- package/src/generation-core/public-api.ts +1 -0
- package/src/generation-core/serialized-node.ts +3 -0
- package/src/generation-core/showcase.ts +32 -7
- package/src/generation-core/site-plan.model.ts +8 -0
- package/src/generation-core/site-spec.generated.ts +738 -1
- package/src/generation-core/validate-site-plan.ts +57 -2
- package/src/harness-core/business-types.ts +3 -0
- package/src/harness-core/generation-view.ts +3 -1
- package/src/harness-core/harness-spec.generated.ts +32 -2
- package/src/harness-core/motion.ts +8 -1
- package/src/media-core/frames.ts +23 -0
- package/src/scene-core/scene-edit.ts +3 -5
package/harness-core/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MotionId, MoodId, TintId, DensityId, DesignMotion, DesignChoices, DesignMode, StylePreset, ShapeId, AnimateId } from '@vectoriox/iox-ui/design-core';
|
|
2
2
|
import { FactKey, PathIssue, PlainObject, JsonSchema, SiteTemplate, ShapeEntry, AiGenerationStage, AiStep, SitePlan, PlanEdit, PlanChange, SiteAudit } from '@vectoriox/iox-ui/generation-core';
|
|
3
3
|
export { JsonSchema } from '@vectoriox/iox-ui/generation-core';
|
|
4
|
+
import { FrameSequence } from '@vectoriox/iox-ui/media-core';
|
|
4
5
|
|
|
5
6
|
declare const SPEC_HARNESS_SOURCE: {
|
|
6
7
|
readonly repo: "nextlevelbuilder/ui-ux-pro-max-skill";
|
|
@@ -155,6 +156,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
155
156
|
readonly animate: "subtle";
|
|
156
157
|
readonly between: "cinematic";
|
|
157
158
|
};
|
|
159
|
+
readonly ceiling: "expressive";
|
|
158
160
|
readonly imagery: "Your own photos only, large and uncropped; never stock.";
|
|
159
161
|
readonly pages: readonly ["home", "work (collection)", "project (detail)", "about", "contact"];
|
|
160
162
|
readonly needs: readonly ["collection", "detail", "about", "contact"];
|
|
@@ -217,6 +219,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
217
219
|
readonly animate: "subtle";
|
|
218
220
|
readonly between: "cinematic";
|
|
219
221
|
};
|
|
222
|
+
readonly ceiling: "expressive";
|
|
220
223
|
readonly imagery: "Wide, bright room shots; details of materials.";
|
|
221
224
|
readonly pages: readonly ["home", "projects (collection)", "project (detail)", "services", "about", "contact"];
|
|
222
225
|
readonly needs: readonly ["collection", "detail", "services", "about", "contact"];
|
|
@@ -280,6 +283,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
280
283
|
readonly animate: "subtle";
|
|
281
284
|
readonly between: "fade";
|
|
282
285
|
};
|
|
286
|
+
readonly ceiling: "expressive";
|
|
283
287
|
readonly imagery: "Large project photos and drawings.";
|
|
284
288
|
readonly pages: readonly ["home", "projects (collection)", "project (detail)", "about", "contact"];
|
|
285
289
|
readonly needs: readonly ["collection", "detail", "about", "contact"];
|
|
@@ -341,9 +345,10 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
341
345
|
readonly motion: "standard";
|
|
342
346
|
readonly moves: {
|
|
343
347
|
readonly scroll: "smooth";
|
|
344
|
-
readonly animate: "
|
|
348
|
+
readonly animate: "expressive";
|
|
345
349
|
readonly between: "slide";
|
|
346
350
|
};
|
|
351
|
+
readonly ceiling: "expressive";
|
|
347
352
|
readonly imagery: "Case-study images of your own work.";
|
|
348
353
|
readonly pages: readonly ["home", "work (collection)", "case study (detail)", "services", "about", "contact"];
|
|
349
354
|
readonly needs: readonly ["collection", "detail", "services", "about", "contact"];
|
|
@@ -398,6 +403,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
398
403
|
readonly animate: "standard";
|
|
399
404
|
readonly between: "fade";
|
|
400
405
|
};
|
|
406
|
+
readonly ceiling: "expressive";
|
|
401
407
|
readonly imagery: "Your pieces, photographed flat and true to colour.";
|
|
402
408
|
readonly pages: readonly ["home", "works (collection)", "work (detail)", "about", "contact"];
|
|
403
409
|
readonly needs: readonly ["collection", "about", "contact"];
|
|
@@ -451,9 +457,10 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
451
457
|
readonly motion: "calm";
|
|
452
458
|
readonly moves: {
|
|
453
459
|
readonly scroll: "normal";
|
|
454
|
-
readonly animate: "
|
|
460
|
+
readonly animate: "expressive";
|
|
455
461
|
readonly between: "slide";
|
|
456
462
|
};
|
|
463
|
+
readonly ceiling: "expressive";
|
|
457
464
|
readonly imagery: "Your pieces and hands at work; natural light.";
|
|
458
465
|
readonly pages: readonly ["home", "pieces (collection)", "piece (detail)", "workshops", "about", "contact"];
|
|
459
466
|
readonly needs: readonly ["collection", "detail", "about", "contact"];
|
|
@@ -509,6 +516,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
509
516
|
readonly animate: "subtle";
|
|
510
517
|
readonly between: "cinematic";
|
|
511
518
|
};
|
|
519
|
+
readonly ceiling: "expressive";
|
|
512
520
|
readonly imagery: "Close-ups on a plain ground.";
|
|
513
521
|
readonly pages: readonly ["home", "collection", "piece (detail)", "about", "contact"];
|
|
514
522
|
readonly needs: readonly ["collection", "detail", "about", "contact"];
|
|
@@ -565,6 +573,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
565
573
|
readonly animate: "standard";
|
|
566
574
|
readonly between: "slide";
|
|
567
575
|
};
|
|
576
|
+
readonly ceiling: "expressive";
|
|
568
577
|
readonly imagery: "Healed work, clean photos.";
|
|
569
578
|
readonly pages: readonly ["home", "work (collection)", "artists", "faq", "contact"];
|
|
570
579
|
readonly needs: readonly ["collection", "faq", "contact"];
|
|
@@ -620,6 +629,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
620
629
|
readonly animate: "subtle";
|
|
621
630
|
readonly between: "fade";
|
|
622
631
|
};
|
|
632
|
+
readonly ceiling: "standard";
|
|
623
633
|
readonly imagery: "Rooms, textures, hands; no faces needed.";
|
|
624
634
|
readonly pages: readonly ["home", "treatments (services)", "prices", "about", "contact"];
|
|
625
635
|
readonly needs: readonly ["services", "prices", "about", "contact"];
|
|
@@ -685,6 +695,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
685
695
|
readonly animate: "standard";
|
|
686
696
|
readonly between: "fade";
|
|
687
697
|
};
|
|
698
|
+
readonly ceiling: "lively";
|
|
688
699
|
readonly imagery: "Your own before-and-after photos.";
|
|
689
700
|
readonly pages: readonly ["home", "services", "prices", "gallery", "contact"];
|
|
690
701
|
readonly needs: readonly ["services", "prices", "gallery", "contact"];
|
|
@@ -744,6 +755,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
744
755
|
readonly animate: "subtle";
|
|
745
756
|
readonly between: "fade";
|
|
746
757
|
};
|
|
758
|
+
readonly ceiling: "lively";
|
|
747
759
|
readonly imagery: "The room, real classes, daylight.";
|
|
748
760
|
readonly pages: readonly ["home", "classes", "schedule", "teachers", "prices", "contact"];
|
|
749
761
|
readonly needs: readonly ["prices", "contact"];
|
|
@@ -804,6 +816,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
804
816
|
readonly animate: "lively";
|
|
805
817
|
readonly between: "slide";
|
|
806
818
|
};
|
|
819
|
+
readonly ceiling: "lively";
|
|
807
820
|
readonly imagery: "Real people training; movement.";
|
|
808
821
|
readonly pages: readonly ["home", "classes", "prices", "coaches", "contact"];
|
|
809
822
|
readonly needs: readonly ["prices", "contact"];
|
|
@@ -860,6 +873,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
860
873
|
readonly animate: "subtle";
|
|
861
874
|
readonly between: "none";
|
|
862
875
|
};
|
|
876
|
+
readonly ceiling: "subtle";
|
|
863
877
|
readonly imagery: "A calm room, daylight; your portrait.";
|
|
864
878
|
readonly pages: readonly ["home", "how I work", "about", "faq", "contact"];
|
|
865
879
|
readonly needs: readonly ["collection", "about", "faq", "contact"];
|
|
@@ -920,6 +934,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
920
934
|
readonly animate: "subtle";
|
|
921
935
|
readonly between: "fade";
|
|
922
936
|
};
|
|
937
|
+
readonly ceiling: "subtle";
|
|
923
938
|
readonly imagery: "Your team and rooms; no stock doctors.";
|
|
924
939
|
readonly pages: readonly ["home", "treatments (services)", "team", "faq", "contact"];
|
|
925
940
|
readonly needs: readonly ["services", "team", "faq", "contact"];
|
|
@@ -981,6 +996,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
981
996
|
readonly animate: "subtle";
|
|
982
997
|
readonly between: "fade";
|
|
983
998
|
};
|
|
999
|
+
readonly ceiling: "subtle";
|
|
984
1000
|
readonly imagery: "Your clinic and team.";
|
|
985
1001
|
readonly pages: readonly ["home", "treatments", "team", "faq", "contact"];
|
|
986
1002
|
readonly needs: readonly ["services", "team", "faq", "contact"];
|
|
@@ -1040,6 +1056,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1040
1056
|
readonly animate: "standard";
|
|
1041
1057
|
readonly between: "fade";
|
|
1042
1058
|
};
|
|
1059
|
+
readonly ceiling: "standard";
|
|
1043
1060
|
readonly imagery: "Real animals you treat (with permission).";
|
|
1044
1061
|
readonly pages: readonly ["home", "services", "team", "faq", "contact"];
|
|
1045
1062
|
readonly needs: readonly ["services", "team", "faq", "contact"];
|
|
@@ -1095,6 +1112,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1095
1112
|
readonly animate: "standard";
|
|
1096
1113
|
readonly between: "fade";
|
|
1097
1114
|
};
|
|
1115
|
+
readonly ceiling: "expressive";
|
|
1098
1116
|
readonly imagery: "Your own food and room photos.";
|
|
1099
1117
|
readonly pages: readonly ["home", "menu", "about", "contact"];
|
|
1100
1118
|
readonly needs: readonly ["menu", "about", "contact"];
|
|
@@ -1157,6 +1175,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1157
1175
|
readonly animate: "standard";
|
|
1158
1176
|
readonly between: "slide";
|
|
1159
1177
|
};
|
|
1178
|
+
readonly ceiling: "lively";
|
|
1160
1179
|
readonly imagery: "Your bakes and drinks, close up.";
|
|
1161
1180
|
readonly pages: readonly ["home", "menu", "about", "contact"];
|
|
1162
1181
|
readonly needs: readonly ["menu", "about", "contact"];
|
|
@@ -1216,6 +1235,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1216
1235
|
readonly animate: "subtle";
|
|
1217
1236
|
readonly between: "cinematic";
|
|
1218
1237
|
};
|
|
1238
|
+
readonly ceiling: "expressive";
|
|
1219
1239
|
readonly imagery: "The cellar, the bar, the bottle.";
|
|
1220
1240
|
readonly pages: readonly ["home", "menu or wines", "our story", "visit", "contact"];
|
|
1221
1241
|
readonly needs: readonly ["menu", "visit", "contact"];
|
|
@@ -1275,6 +1295,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1275
1295
|
readonly animate: "standard";
|
|
1276
1296
|
readonly between: "slide";
|
|
1277
1297
|
};
|
|
1298
|
+
readonly ceiling: "expressive";
|
|
1278
1299
|
readonly imagery: "Product photos on plain grounds; real people wearing them.";
|
|
1279
1300
|
readonly pages: readonly ["home", "collection", "product (detail)", "about", "contact"];
|
|
1280
1301
|
readonly needs: readonly ["collection", "detail", "about", "contact"];
|
|
@@ -1329,6 +1350,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1329
1350
|
readonly animate: "standard";
|
|
1330
1351
|
readonly between: "fade";
|
|
1331
1352
|
};
|
|
1353
|
+
readonly ceiling: "lively";
|
|
1332
1354
|
readonly imagery: "Your own arrangements in daylight.";
|
|
1333
1355
|
readonly pages: readonly ["home", "flowers (collection)", "weddings and events", "about", "contact"];
|
|
1334
1356
|
readonly needs: readonly ["collection", "about", "contact"];
|
|
@@ -1384,6 +1406,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1384
1406
|
readonly animate: "subtle";
|
|
1385
1407
|
readonly between: "fade";
|
|
1386
1408
|
};
|
|
1409
|
+
readonly ceiling: "expressive";
|
|
1387
1410
|
readonly imagery: "Pieces in real rooms and on plain grounds.";
|
|
1388
1411
|
readonly pages: readonly ["home", "collection", "product (detail)", "about", "contact"];
|
|
1389
1412
|
readonly needs: readonly ["collection", "detail", "about", "contact"];
|
|
@@ -1439,6 +1462,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1439
1462
|
readonly animate: "subtle";
|
|
1440
1463
|
readonly between: "fade";
|
|
1441
1464
|
};
|
|
1465
|
+
readonly ceiling: "standard";
|
|
1442
1466
|
readonly imagery: "Your portrait; diagrams over stock.";
|
|
1443
1467
|
readonly pages: readonly ["home", "services", "case studies", "about", "contact"];
|
|
1444
1468
|
readonly needs: readonly ["services", "about", "contact"];
|
|
@@ -1496,6 +1520,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1496
1520
|
readonly animate: "standard";
|
|
1497
1521
|
readonly between: "fade";
|
|
1498
1522
|
};
|
|
1523
|
+
readonly ceiling: "standard";
|
|
1499
1524
|
readonly imagery: "Your portrait, working with people.";
|
|
1500
1525
|
readonly pages: readonly ["home", "how it works", "programmes", "about", "contact"];
|
|
1501
1526
|
readonly needs: readonly ["collection", "about", "contact"];
|
|
@@ -1547,6 +1572,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1547
1572
|
readonly animate: "still";
|
|
1548
1573
|
readonly between: "none";
|
|
1549
1574
|
};
|
|
1575
|
+
readonly ceiling: "subtle";
|
|
1550
1576
|
readonly imagery: "Your team; your office.";
|
|
1551
1577
|
readonly pages: readonly ["home", "practice areas (services)", "team", "about", "contact"];
|
|
1552
1578
|
readonly needs: readonly ["services", "team", "about", "contact"];
|
|
@@ -1609,6 +1635,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1609
1635
|
readonly animate: "standard";
|
|
1610
1636
|
readonly between: "slide";
|
|
1611
1637
|
};
|
|
1638
|
+
readonly ceiling: "lively";
|
|
1612
1639
|
readonly imagery: "Your own property photos; never stock.";
|
|
1613
1640
|
readonly pages: readonly ["home", "properties (collection)", "property (detail)", "about", "contact"];
|
|
1614
1641
|
readonly needs: readonly ["collection", "detail", "about", "contact"];
|
|
@@ -1667,6 +1694,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1667
1694
|
readonly animate: "standard";
|
|
1668
1695
|
readonly between: "fade";
|
|
1669
1696
|
};
|
|
1697
|
+
readonly ceiling: "standard";
|
|
1670
1698
|
readonly imagery: "Real people you help (with permission).";
|
|
1671
1699
|
readonly pages: readonly ["home", "what we do", "stories", "get involved", "contact"];
|
|
1672
1700
|
readonly needs: readonly ["contact"];
|
|
@@ -1718,6 +1746,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1718
1746
|
readonly animate: "standard";
|
|
1719
1747
|
readonly between: "cinematic";
|
|
1720
1748
|
};
|
|
1749
|
+
readonly ceiling: "expressive";
|
|
1721
1750
|
readonly imagery: "Real events at your venue.";
|
|
1722
1751
|
readonly pages: readonly ["home", "spaces", "gallery", "faq", "contact"];
|
|
1723
1752
|
readonly needs: readonly ["gallery", "faq", "contact"];
|
|
@@ -1776,6 +1805,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1776
1805
|
readonly animate: "subtle";
|
|
1777
1806
|
readonly between: "cinematic";
|
|
1778
1807
|
};
|
|
1808
|
+
readonly ceiling: "expressive";
|
|
1779
1809
|
readonly imagery: "Rooms, views, breakfast; your own photos.";
|
|
1780
1810
|
readonly pages: readonly ["home", "rooms (collection)", "room (detail)", "about", "contact"];
|
|
1781
1811
|
readonly needs: readonly ["collection", "detail", "about", "contact"];
|
|
@@ -1834,6 +1864,7 @@ declare const SPEC_BUSINESS_TYPES: readonly [{
|
|
|
1834
1864
|
readonly animate: "lively";
|
|
1835
1865
|
readonly between: "slide";
|
|
1836
1866
|
};
|
|
1867
|
+
readonly ceiling: "lively";
|
|
1837
1868
|
readonly imagery: "Real classes (with permission).";
|
|
1838
1869
|
readonly pages: readonly ["home", "classes", "prices", "about", "contact"];
|
|
1839
1870
|
readonly needs: readonly ["prices", "about", "contact"];
|
|
@@ -2238,6 +2269,9 @@ interface BusinessType {
|
|
|
2238
2269
|
readonly motion: MotionId;
|
|
2239
2270
|
/** How its site usually scrolls, how animated it is and what plays between pages (rev 3.1). */
|
|
2240
2271
|
readonly moves: Readonly<DesignMotion>;
|
|
2272
|
+
/** SC6: the most How animated a GENERATED site of this type may start at (`motionFor`). A Scene needs Expressive,
|
|
2273
|
+
* so a type capped below it never gets one. Binds generated output only — the owner may choose any level. */
|
|
2274
|
+
readonly ceiling: DesignMotion['animate'];
|
|
2241
2275
|
readonly imagery: string;
|
|
2242
2276
|
readonly pages: ReadonlyArray<string>;
|
|
2243
2277
|
/** The needs its page set implies, in page order (derived from `pages` at contract build). */
|
|
@@ -2436,6 +2470,8 @@ interface MotionBasis {
|
|
|
2436
2470
|
references: boolean;
|
|
2437
2471
|
/** The owner asked for little movement: all three were capped. */
|
|
2438
2472
|
littleMovement: boolean;
|
|
2473
|
+
/** SC6: How animated was held at the type's ceiling (a law firm stays calm whatever the style or references). */
|
|
2474
|
+
ceiling: boolean;
|
|
2439
2475
|
}
|
|
2440
2476
|
interface MotionStart extends DesignMotion {
|
|
2441
2477
|
/** Why, in English — one line per step the start took. */
|
|
@@ -2913,11 +2949,13 @@ interface AiGenerationView {
|
|
|
2913
2949
|
stage: `${AiGenerationStage}`;
|
|
2914
2950
|
/** The model step the runner is working on; null = the owner's move. */
|
|
2915
2951
|
task: `${AiStep}` | null;
|
|
2952
|
+
/** `film`: the owner's film as pictures (SC6b), told by the wizard at Build — `placeFilm` places it. */
|
|
2916
2953
|
intake: {
|
|
2917
2954
|
prompt: string;
|
|
2918
2955
|
files: GenerationFileView[];
|
|
2919
2956
|
addresses: string[];
|
|
2920
2957
|
auto: boolean;
|
|
2958
|
+
film?: FrameSequence;
|
|
2921
2959
|
};
|
|
2922
2960
|
readers: GenerationReadersView;
|
|
2923
2961
|
reads: {
|
package/media-core/index.d.ts
CHANGED
|
@@ -111,6 +111,24 @@ declare const PHONE_LONG_SIDE = 1100;
|
|
|
111
111
|
declare function fittedFrameSize(width: number, height: number, longSide: number): FrameSizes;
|
|
112
112
|
/** How many pictures a video of `seconds` becomes. */
|
|
113
113
|
declare function videoFrameCount(seconds: number): number;
|
|
114
|
+
/**
|
|
115
|
+
* Pictures per screen of scrolling at which a played film stays smooth as a glide slows to a stop. Measured on Doron's
|
|
116
|
+
* iPhone (2026-11-05): Studio Alma's film at about 11 a screen stepped, at about 55 it did not.
|
|
117
|
+
*/
|
|
118
|
+
declare const FILM_SMOOTH_PER_SCREEN = 40;
|
|
119
|
+
/** The fewest and most screens a film the build places holds the page for (SC6b): long enough to feel, short enough to pass. */
|
|
120
|
+
declare const FILM_MIN_SCREENS = 1;
|
|
121
|
+
declare const FILM_MAX_SCREENS = 4;
|
|
122
|
+
/** What the build prepares a film to: about 60 pictures a screen at the longest hold — denser than smooth, never wasted. */
|
|
123
|
+
declare const FILM_PREPARED_PICTURES: number;
|
|
124
|
+
/** The fewest pictures a placed film may have: smooth over the shortest hold. */
|
|
125
|
+
declare const FILM_MIN_PICTURES: number;
|
|
126
|
+
/**
|
|
127
|
+
* The screens a film of `count` pictures holds the page for (SC6b, the composer's density rule): the stretches between
|
|
128
|
+
* pictures are what scroll, so `(count − 1) ÷ 40`, rounded DOWN to a tenth (150 pictures over 3.75 screens measure 39.7 a
|
|
129
|
+
* screen, thin), within the hold's limits. Null: too few pictures to play smoothly over the shortest hold.
|
|
130
|
+
*/
|
|
131
|
+
declare function filmScreensFor(count: number): number | null;
|
|
114
132
|
/** Which of `sourceCount` pictures make a set of `count`, spread evenly, first and last always in. */
|
|
115
133
|
declare function sampledFrameIndexes(sourceCount: number, count: number): number[];
|
|
116
134
|
/** Rendered frames in their real order: `frame2` before `frame10`. */
|
|
@@ -473,5 +491,5 @@ declare function attachVideos(container: ParentNode, options?: VideoRuntimeOptio
|
|
|
473
491
|
/** The runtime already running at `el`, if any. */
|
|
474
492
|
declare function videoRuntimeOf(el: Element | null | undefined): VideoRuntime | null;
|
|
475
493
|
|
|
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 };
|
|
494
|
+
export { CONSENT_GRANT_EVENT, CONSENT_PREFERENCES_EVENT, FILM_MAX_SCREENS, FILM_MIN_PICTURES, FILM_MIN_SCREENS, FILM_PREPARED_PICTURES, FILM_SMOOTH_PER_SCREEN, 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, filmScreensFor, 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 };
|
|
477
495
|
export type { ConsentGrantDetail, ConsentTransport, FrameBlend, FramePlayerOptions, FrameSequence, FrameSetInfo, FrameSizes, VideoMarkupOptions, VideoNodeLike, VideoPlan, VideoRef, VideoRuntimeOptions, VideoThumbnailRequest, VideoWords };
|
package/package.json
CHANGED
package/scene-core/index.d.ts
CHANGED
|
@@ -218,10 +218,7 @@ 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
|
-
*/
|
|
221
|
+
/** Pictures per screen of scrolling at which a played film stays smooth (media-core's, which the composer sizes by). */
|
|
225
222
|
declare const SCENE_FILM_SMOOTH = 40;
|
|
226
223
|
interface SceneFilmDensity {
|
|
227
224
|
/** Pictures per screen of scrolling over most of the play: half its scroll holds at least this many. */
|
|
@@ -1533,6 +1533,36 @@ export const SPEC_AXES = {
|
|
|
1533
1533
|
"duration": 600
|
|
1534
1534
|
}
|
|
1535
1535
|
}
|
|
1536
|
+
},
|
|
1537
|
+
"expressive": {
|
|
1538
|
+
"label": "Expressive",
|
|
1539
|
+
"he": "נועזת",
|
|
1540
|
+
"hint": "Headings rise line by line and pictures open as they arrive; one section may hold the page and move as you scroll.",
|
|
1541
|
+
"parts": {
|
|
1542
|
+
"heroText": {
|
|
1543
|
+
"type": "fadeIn",
|
|
1544
|
+
"duration": 500
|
|
1545
|
+
},
|
|
1546
|
+
"heading": {
|
|
1547
|
+
"type": "revealText",
|
|
1548
|
+
"duration": 700
|
|
1549
|
+
},
|
|
1550
|
+
"items": {
|
|
1551
|
+
"type": "moveUp",
|
|
1552
|
+
"distance": 32,
|
|
1553
|
+
"duration": 700,
|
|
1554
|
+
"stagger": 110,
|
|
1555
|
+
"cap": 8
|
|
1556
|
+
},
|
|
1557
|
+
"media": {
|
|
1558
|
+
"type": "clipReveal",
|
|
1559
|
+
"duration": 800
|
|
1560
|
+
},
|
|
1561
|
+
"rowMedia": {
|
|
1562
|
+
"type": "clipReveal",
|
|
1563
|
+
"duration": 800
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1536
1566
|
}
|
|
1537
1567
|
},
|
|
1538
1568
|
"animationFamilies": {
|
|
@@ -1937,7 +1967,7 @@ export const SPEC_STYLE_PRESETS = [
|
|
|
1937
1967
|
"pillButtons": false,
|
|
1938
1968
|
"moves": {
|
|
1939
1969
|
"scroll": "smooth",
|
|
1940
|
-
"animate": "
|
|
1970
|
+
"animate": "expressive",
|
|
1941
1971
|
"between": "slide"
|
|
1942
1972
|
},
|
|
1943
1973
|
"modes": [
|
|
@@ -2034,7 +2064,7 @@ export const SPEC_STYLE_PRESETS = [
|
|
|
2034
2064
|
"pillButtons": false,
|
|
2035
2065
|
"moves": {
|
|
2036
2066
|
"scroll": "normal",
|
|
2037
|
-
"animate": "
|
|
2067
|
+
"animate": "expressive",
|
|
2038
2068
|
"between": "fade"
|
|
2039
2069
|
},
|
|
2040
2070
|
"modes": [
|
|
@@ -2320,7 +2350,7 @@ export const SPEC_STYLE_PRESETS = [
|
|
|
2320
2350
|
"pillButtons": false,
|
|
2321
2351
|
"moves": {
|
|
2322
2352
|
"scroll": "normal",
|
|
2323
|
-
"animate": "
|
|
2353
|
+
"animate": "expressive",
|
|
2324
2354
|
"between": "slide"
|
|
2325
2355
|
},
|
|
2326
2356
|
"modes": [
|
|
@@ -2385,7 +2415,7 @@ export const SPEC_STYLE_PRESETS = [
|
|
|
2385
2415
|
"pillButtons": false,
|
|
2386
2416
|
"moves": {
|
|
2387
2417
|
"scroll": "normal",
|
|
2388
|
-
"animate": "
|
|
2418
|
+
"animate": "expressive",
|
|
2389
2419
|
"between": "slide"
|
|
2390
2420
|
},
|
|
2391
2421
|
"modes": [
|
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
} from '@vectoriox/iox-ui/composite-core';
|
|
11
11
|
|
|
12
12
|
import { EntranceMark, EntrancePart, planEntrances } from './entrances';
|
|
13
|
+
import { filmOfPlan } from './film';
|
|
13
14
|
import { NODE_BASELINES } from './node-baselines.generated';
|
|
14
15
|
import {
|
|
15
|
-
BindScope, CardBindTarget, parsePatternPath, PatternDirective, PatternNode, PatternOpens, PatternPlaceholder, PatternRecipe, SitePattern, patternById,
|
|
16
|
+
BindScope, CardBindTarget, parsePatternPath, PatternDirective, PatternNode, PatternOpens, PatternPlaceholder, PatternRecipe, PatternTrack, SitePattern, patternById,
|
|
16
17
|
} from './patterns';
|
|
17
18
|
import { recipeListBinds } from './recipe-fields';
|
|
18
19
|
import { assertStyleSlots, ComposeError, createSerializedNode, SerializedBinding, SerializedNode, setStyleProps, walkSerialized } from './serialized-node';
|
|
@@ -206,6 +207,8 @@ const IMAGE = 'Image';
|
|
|
206
207
|
const VIDEO = 'Video';
|
|
207
208
|
/** A Video's source prop (media-core `VideoTrait.Source` — not `source`, which names a data source). */
|
|
208
209
|
const VIDEO_SOURCE_PROP = 'videoSource';
|
|
210
|
+
/** A Video's pictures the scroll plays (media-core `VideoTrait.Frames`). */
|
|
211
|
+
const VIDEO_FRAMES_PROP = 'frames';
|
|
209
212
|
const DIALOG = 'Dialog';
|
|
210
213
|
const SUBMIT_BUTTON = 'SubmitButton';
|
|
211
214
|
const FORM_SUCCESS = 'FormSuccess';
|
|
@@ -575,6 +578,8 @@ function composeNode(given: PatternNode, id: string, repeater: RepeaterScope | n
|
|
|
575
578
|
// Props first: a Heading with no `es` wears its LEVEL's default style.
|
|
576
579
|
const node = create(id, type, props, pn.es ?? null);
|
|
577
580
|
if (pn.role) node.role = pn.role;
|
|
581
|
+
if (pn.tracks?.length) node.sceneTracks = pn.tracks.map(cloneTrack);
|
|
582
|
+
if (pn.phoneTracks?.length) node.scenePhoneTracks = pn.phoneTracks.map(cloneTrack);
|
|
578
583
|
markEntrance(pn, id, repeater, ctx);
|
|
579
584
|
|
|
580
585
|
// Words.
|
|
@@ -600,6 +605,13 @@ function composeNode(given: PatternNode, id: string, repeater: RepeaterScope | n
|
|
|
600
605
|
setProp(node, VIDEO_SOURCE_PROP, 'file');
|
|
601
606
|
setProp(node, 'mode', pn.video);
|
|
602
607
|
}
|
|
608
|
+
// SC6b: a film pattern's Video plays the owner's pictures (the build places the pattern only with a film).
|
|
609
|
+
if (pn.film) {
|
|
610
|
+
if (type !== VIDEO) throw new ComposeError(`${id}: "film" belongs on a Video`);
|
|
611
|
+
const film = filmOfPlan(ctx.plan);
|
|
612
|
+
if (!film) throw new ComposeError(`${id}: a film's pattern on a plan with no film`);
|
|
613
|
+
setProp(node, VIDEO_FRAMES_PROP, film.sequence);
|
|
614
|
+
}
|
|
603
615
|
if (pn.ph === PatternPlaceholder.Video) {
|
|
604
616
|
setProp(node, 'src', '');
|
|
605
617
|
ctx.placeholders.push({ nodeId: id, owner: ctx.owner, kind: PlaceholderKind.Video, text: '' });
|
|
@@ -990,6 +1002,13 @@ function wireOpeners(root: SerializedNode, openers: ReadonlyArray<string>): void
|
|
|
990
1002
|
}
|
|
991
1003
|
|
|
992
1004
|
/** How a composed section moves in (M1-6b): the look's How animated (or the section's own) on the page's direction. */
|
|
1005
|
+
/** SC6: a track's own copy, so a composed page never shares an array with the contract. */
|
|
1006
|
+
const cloneTrack = (track: PatternTrack): Record<string, unknown> => ({
|
|
1007
|
+
prop: track.prop,
|
|
1008
|
+
keys: track.keys.map(([at, value]) => [at, value]),
|
|
1009
|
+
...(track.from ? { from: track.from } : {}),
|
|
1010
|
+
});
|
|
1011
|
+
|
|
993
1012
|
interface SectionMotion {
|
|
994
1013
|
level: AnimateId | string | null;
|
|
995
1014
|
rtl: boolean;
|
|
@@ -1002,7 +1021,11 @@ function composePattern(pattern: SitePattern, id: string, ctx: Ctx, motion: Sect
|
|
|
1002
1021
|
if (!node) throw new ComposeError(`${id}: a pattern's root cannot be optional or a link`);
|
|
1003
1022
|
wireOpeners(node, own.openers ?? []);
|
|
1004
1023
|
wireShownWhen(node, own.shownWhen ?? []);
|
|
1005
|
-
|
|
1024
|
+
// A Scene moves by the scroll alone: the look's entrances would fade its pinned stage in on arrival (SC6).
|
|
1025
|
+
if (motion && !pattern.global && !pattern.scene) stampEntrances(node, pattern.family, own.entrances ?? [], motion);
|
|
1026
|
+
// SC6b, the density rule: a film's Scene holds the page as long as its pictures stay smooth, and no longer.
|
|
1027
|
+
const film = pattern.film ? filmOfPlan(ctx.plan) : null;
|
|
1028
|
+
if (film) setStyleProps(node, { height: `${film.heightVh}vh` });
|
|
1006
1029
|
node.label = pattern.label;
|
|
1007
1030
|
return node;
|
|
1008
1031
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { filmScreensFor, FrameSequence, normalizeFrameSequence } from '@vectoriox/iox-ui/media-core';
|
|
2
|
+
|
|
3
|
+
import { patternById, patternLinkSlots, patternSlots, SCENE_LEVEL, SITE_PATTERNS } from './patterns';
|
|
4
|
+
import { PageSectionPlan, SitePlan } from './site-plan.model';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The owner's film on a built site (premium sites SC6b). The owner gives a video in Analysis; the wizard's browser makes
|
|
8
|
+
* it pictures (cms-client `FramesPrepareService`); the build places it. A model never picks the film's pattern — the
|
|
9
|
+
* manifest and the plan wire leave it out — so ONE pure placement decides where it goes, run by the wizard to draw the
|
|
10
|
+
* Plan and by the server to build: they cannot disagree.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The pattern a film plays in: the one whose Video is marked `film` in the contract. */
|
|
14
|
+
export const FILM_PATTERN: string = Object.entries(SITE_PATTERNS).find(([, p]) => p.film)?.[0] ?? '';
|
|
15
|
+
|
|
16
|
+
/** Why a film stays off the site. Each is said to the owner on the Plan step. */
|
|
17
|
+
export enum FilmUnplaced {
|
|
18
|
+
/** How animated is below Expressive: no section holds the page there. */
|
|
19
|
+
NotExpressive = 'notExpressive',
|
|
20
|
+
/** The home page does not open with a hero to play it in. */
|
|
21
|
+
NoHero = 'noHero',
|
|
22
|
+
/** The home page already holds the page elsewhere (one Scene per page). */
|
|
23
|
+
AnotherScene = 'anotherScene',
|
|
24
|
+
/** Too few pictures to play smoothly over the shortest hold (media-core `FILM_MIN_PICTURES`). */
|
|
25
|
+
TooShort = 'tooShort',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Where the film plays: the home page's first section when it is a hero. */
|
|
29
|
+
export interface FilmTarget {
|
|
30
|
+
readonly page: number;
|
|
31
|
+
readonly section: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type FilmPlacement = { readonly target: FilmTarget; readonly unplaced?: undefined } | { readonly target?: undefined; readonly unplaced: FilmUnplaced };
|
|
35
|
+
|
|
36
|
+
/** Where a film WOULD play on this plan (the Plan step asks before the pictures exist). */
|
|
37
|
+
export function filmPlacement(plan: Pick<SitePlan, 'design' | 'pages'>): FilmPlacement {
|
|
38
|
+
if ((plan.design as { animate?: string }).animate !== SCENE_LEVEL) return { unplaced: FilmUnplaced.NotExpressive };
|
|
39
|
+
const page = plan.pages.findIndex(p => p.isHome);
|
|
40
|
+
const sections = page >= 0 ? plan.pages[page].sections : [];
|
|
41
|
+
const first = patternById(sections[0]?.pattern);
|
|
42
|
+
if (!first || first.family !== FILM_FAMILY) return { unplaced: FilmUnplaced.NoHero };
|
|
43
|
+
if (sections.slice(1).some(s => patternById(s.pattern)?.scene)) return { unplaced: FilmUnplaced.AnotherScene };
|
|
44
|
+
return { target: { page, section: 0 } };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const FILM_FAMILY = SITE_PATTERNS[FILM_PATTERN]?.family ?? 'hero';
|
|
48
|
+
|
|
49
|
+
export interface PlacedFilm {
|
|
50
|
+
readonly plan: SitePlan;
|
|
51
|
+
readonly unplaced?: FilmUnplaced;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The plan with the owner's film in it: `film` set, and the home page's hero becomes the film's pattern, keeping the
|
|
56
|
+
* hero's words and links that the film's pattern has. A film that cannot play (unreadable, too few pictures) or a plan
|
|
57
|
+
* with no place for it leaves the plan as it was — the hero keeps its picture, and the builder can add the film later.
|
|
58
|
+
*/
|
|
59
|
+
export function placeFilm(plan: SitePlan, sequence: unknown): PlacedFilm {
|
|
60
|
+
const film = normalizeFrameSequence(sequence);
|
|
61
|
+
if (!film || filmScreensFor(film.wide.count) === null) return { plan, unplaced: FilmUnplaced.TooShort };
|
|
62
|
+
const placement = filmPlacement(plan);
|
|
63
|
+
if (!placement.target) return { plan, unplaced: placement.unplaced };
|
|
64
|
+
const { page, section } = placement.target;
|
|
65
|
+
const pages = plan.pages.map((p, i) => i !== page ? p : {
|
|
66
|
+
...p,
|
|
67
|
+
sections: p.sections.map((s, j) => (j === section ? filmSection(s) : s)),
|
|
68
|
+
});
|
|
69
|
+
return { plan: { ...plan, pages, film: { sequence: film } } };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function filmSection(hero: PageSectionPlan): PageSectionPlan {
|
|
73
|
+
const pattern = SITE_PATTERNS[FILM_PATTERN];
|
|
74
|
+
const slots = new Set(patternSlots(pattern));
|
|
75
|
+
const links = new Set(patternLinkSlots(pattern));
|
|
76
|
+
const keep = (values: Readonly<Record<string, string>> | undefined, allowed: Set<string>) => {
|
|
77
|
+
const kept = Object.entries(values ?? {}).filter(([key]) => allowed.has(key));
|
|
78
|
+
return kept.length ? Object.fromEntries(kept) : undefined;
|
|
79
|
+
};
|
|
80
|
+
const keptSlots = keep(hero.slots, slots);
|
|
81
|
+
const keptLinks = keep(hero.links, links);
|
|
82
|
+
return { pattern: FILM_PATTERN, ...(keptSlots ? { slots: keptSlots } : {}), ...(keptLinks ? { links: keptLinks } : {}) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The film's pictures as the composer stamps them, and the height its Scene holds the page for. */
|
|
86
|
+
export function filmOfPlan(plan: Pick<SitePlan, 'film'>): { sequence: FrameSequence; heightVh: number } | null {
|
|
87
|
+
const sequence = normalizeFrameSequence(plan.film?.sequence);
|
|
88
|
+
const screens = sequence ? filmScreensFor(sequence.wide.count) : null;
|
|
89
|
+
// A Scene's root holds the page for its height less the one screen its stage fills.
|
|
90
|
+
return sequence && screens !== null ? { sequence, heightVh: Math.round((screens + 1) * 100) } : null;
|
|
91
|
+
}
|
|
@@ -90,6 +90,21 @@ export interface PatternNode {
|
|
|
90
90
|
readonly side?: `${EntranceSide}`;
|
|
91
91
|
/** L0-3: a link to the page that shows this scope's item (the plan's template page of its type). */
|
|
92
92
|
readonly detail?: `${BindScope.Item}` | `${BindScope.Next}`;
|
|
93
|
+
/** SC6: a Scene layer's scene-core tracks, written to the node's `sceneTracks` (and `scenePhoneTracks`). */
|
|
94
|
+
readonly tracks?: ReadonlyArray<PatternTrack>;
|
|
95
|
+
readonly phoneTracks?: ReadonlyArray<PatternTrack>;
|
|
96
|
+
/** SC6b: the Video a film pattern plays the owner's film in (its `frames`). */
|
|
97
|
+
readonly film?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* SC6: one scene-core track as the contract writes it — `{ prop, keys: [[progress, value]], from? }`. Typed here, not
|
|
102
|
+
* imported: generation-core stamps it verbatim and scene-core reads (and normalizes) it where it plays.
|
|
103
|
+
*/
|
|
104
|
+
export interface PatternTrack {
|
|
105
|
+
readonly prop: string;
|
|
106
|
+
readonly keys: ReadonlyArray<readonly [number, number | string]>;
|
|
107
|
+
readonly from?: string;
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
|
|
@@ -138,6 +153,11 @@ export interface SitePattern {
|
|
|
138
153
|
readonly global?: `${PatternGlobalPosition}`;
|
|
139
154
|
/** A navigation pattern's phone shape (CC1): `planNavShape` adds it after the pattern is stamped. */
|
|
140
155
|
readonly phoneMenu?: 'drawer' | 'bar' | 'both';
|
|
156
|
+
/** SC6: a Scene section — held while the page scrolls, moved by the scroll. Only at How animated Expressive, at
|
|
157
|
+
* most one per page and on at most two pages (`validateSitePlan`); it never takes the look's entrances. */
|
|
158
|
+
readonly scene?: boolean;
|
|
159
|
+
/** SC6b: a Scene that plays the owner's film. Placed by `placeFilm`, never chosen: no manifest or wire lists it. */
|
|
160
|
+
readonly film?: boolean;
|
|
141
161
|
readonly node: PatternNode;
|
|
142
162
|
}
|
|
143
163
|
|
|
@@ -295,7 +315,12 @@ export function patternHasForm(pattern: SitePattern): boolean {
|
|
|
295
315
|
return patternNodes(pattern).some(n => !!n.preset);
|
|
296
316
|
}
|
|
297
317
|
|
|
298
|
-
/**
|
|
318
|
+
/** SC6: the level a Scene needs — the site's How animated, never a section's own `enter` (a Scene takes no entrance). */
|
|
319
|
+
export const SCENE_LEVEL = 'expressive';
|
|
320
|
+
/** SC6: Scenes on the home page and at most this many other pages, one per page (`validateSitePlan`). */
|
|
321
|
+
export const SCENE_OTHER_PAGES = 1;
|
|
322
|
+
|
|
323
|
+
/** The library's sections (contract `site.library`), each with whether it is composed. */
|
|
299
324
|
export const SITE_LIBRARY: ReadonlyArray<{ readonly id: string; readonly family: string; readonly composed: boolean }> = SPEC_SITE_LIBRARY;
|
|
300
325
|
|
|
301
326
|
/** One pattern as the model reads it (L0-2): what it is for, what it may write and how long, and what it binds. */
|
|
@@ -316,11 +341,22 @@ export interface PatternManifestEntry {
|
|
|
316
341
|
roles: string[];
|
|
317
342
|
optionalParts: string[];
|
|
318
343
|
form: boolean;
|
|
344
|
+
/** SC6: it holds the page as it scrolls — one per page, the home page and at most one other. */
|
|
345
|
+
scene?: boolean;
|
|
319
346
|
}
|
|
320
347
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
348
|
+
export interface PatternManifestOptions {
|
|
349
|
+
/** The site's How animated: below `SCENE_LEVEL` the Scenes are left out — a model cannot choose what it never sees. */
|
|
350
|
+
animate?: string | null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* The composed library as the model reads it — every pattern, its budgets under `density` (M1-3's manifest). Given the
|
|
355
|
+
* site's How animated (SC6), the Scenes are listed only at `SCENE_LEVEL`, marked `scene`; without it, all of them.
|
|
356
|
+
*/
|
|
357
|
+
export function patternManifest(density?: string | null, options: PatternManifestOptions = {}): PatternManifestEntry[] {
|
|
358
|
+
const scenes = !('animate' in options) || options.animate === SCENE_LEVEL;
|
|
359
|
+
return Object.entries(SITE_PATTERNS).filter(([, p]) => !p.film && (scenes || !p.scene)).map(([id, p]) => ({
|
|
324
360
|
id, family: p.family, label: p.label, intent: p.intent, ...(p.global ? { global: p.global } : {}),
|
|
325
361
|
slots: Object.fromEntries(Object.keys(patternSlotBudgets(p)).map(slot => [slot, slotBudget(p, slot, density) as number])),
|
|
326
362
|
links: patternLinkSlots(p),
|
|
@@ -328,6 +364,7 @@ export function patternManifest(density?: string | null): PatternManifestEntry[]
|
|
|
328
364
|
roles: [...new Set(patternRoleNeeds(p).map(r => `${r.scope}.@${r.role}${r.optional ? OPTIONAL_MARK : ''}`))],
|
|
329
365
|
optionalParts: patternOptionalParts(p),
|
|
330
366
|
form: patternHasForm(p),
|
|
367
|
+
...(p.scene ? { scene: true } : {}),
|
|
331
368
|
}));
|
|
332
369
|
}
|
|
333
370
|
|