@aglyn/plugins-ai 1.0.0-beta.266 → 1.0.0-beta.269
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/package.json +18 -18
- package/src/lib/components/ai-job-site.d.ts +8 -1
- package/src/lib/components/ai-job-site.js +7 -0
- package/src/lib/components/ai-job-site.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +37 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +25 -6
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-steps.component.d.ts +8 -2
- package/src/lib/components/ai-site-start-steps.component.js +55 -26
- package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
- package/src/lib/components/ai-site-start-tracking.d.ts +11 -3
- package/src/lib/components/ai-site-start-tracking.js +28 -9
- package/src/lib/components/ai-site-start-tracking.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +24 -2
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/jobs/ai-empty-site-sweep.d.ts +154 -0
- package/src/lib/jobs/ai-empty-site-sweep.js +346 -0
- package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +7 -1
- package/src/lib/jobs/ai-job-page-language.js +16 -3
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-site-look.d.ts +2 -1
- package/src/lib/jobs/ai-job-site-look.js +6 -2
- package/src/lib/jobs/ai-job-site-look.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +11 -6
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-beat.d.ts +11 -3
- package/src/lib/jobs/ai-jobs-beat.js +23 -6
- package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
- package/src/lib/jobs/ai-jobs.js +46 -7
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +43 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +735 -22
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.d.ts +35 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js +379 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.d.ts +35 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js +294 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-mexicano-recordings.d.ts +31 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-mexicano-recordings.js +325 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-mexicano-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-design.js +5 -0
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-motion.d.ts +60 -7
- package/src/lib/layout-language/ai-layout-motion.js +231 -4
- package/src/lib/layout-language/ai-layout-motion.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +8 -0
- package/src/lib/layout-language/ai-layout-pictures.js +68 -4
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +20 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-look.d.ts +10 -1
- package/src/lib/model/ai-site-look.js +14 -3
- package/src/lib/model/ai-site-look.js.map +1 -1
- package/src/lib/model/ai-site-start-handoff.d.ts +49 -0
- package/src/lib/model/ai-site-start-handoff.js +88 -0
- package/src/lib/model/ai-site-start-handoff.js.map +1 -0
|
@@ -127,6 +127,223 @@ import { applyElementMotion } from "@aglyn/aglyn/app-utils/element-motion";
|
|
|
127
127
|
portfolio: 'gallery',
|
|
128
128
|
photography: 'gallery'
|
|
129
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* The motion styles a site's look chooses among (AGL-3941). Persisted on the
|
|
132
|
+
* site's style; never rename one.
|
|
133
|
+
*/ export const AI_SITE_MOTION_STYLES = [
|
|
134
|
+
'sides',
|
|
135
|
+
'rise',
|
|
136
|
+
'scale',
|
|
137
|
+
'cascade',
|
|
138
|
+
'still'
|
|
139
|
+
];
|
|
140
|
+
/**
|
|
141
|
+
* Each motion style, by what it does to each kind of section:
|
|
142
|
+
* - SIDES — a split's picture slides in from its own side and its words
|
|
143
|
+
* from the other; the rest rises and fades in turn. Pictures never zoom,
|
|
144
|
+
* so a split's picture always takes its side.
|
|
145
|
+
* - RISE — everything fades up into place; a split's columns rise one
|
|
146
|
+
* after the other.
|
|
147
|
+
* - SCALE — pictures scale into place, from smaller or from larger; the
|
|
148
|
+
* hero's words fade, and later sections fade and rise in turn.
|
|
149
|
+
* - CASCADE — cards and lists ripple in one after another at the widest
|
|
150
|
+
* stagger, quick, often with overshoot.
|
|
151
|
+
* - STILL — near-static: a short, soft fade, and nothing slides or scales.
|
|
152
|
+
*
|
|
153
|
+
* The hero's main heading never moves under any of them (`element-motion.ts`),
|
|
154
|
+
* and every one is written as the same `aglynAnimation*` props, so a visitor
|
|
155
|
+
* who asks for reduced motion sees none of it.
|
|
156
|
+
*/ export const AI_SITE_MOTION_STYLE_RANGES = {
|
|
157
|
+
sides: {
|
|
158
|
+
words: "a split section's picture slides in from its own side and its words from the other; the rest rises",
|
|
159
|
+
presets: [
|
|
160
|
+
'slide-up',
|
|
161
|
+
'fade'
|
|
162
|
+
],
|
|
163
|
+
heroPresets: [
|
|
164
|
+
'slide-up',
|
|
165
|
+
'fade'
|
|
166
|
+
],
|
|
167
|
+
media: [
|
|
168
|
+
'fade',
|
|
169
|
+
'slide-up'
|
|
170
|
+
],
|
|
171
|
+
eases: [
|
|
172
|
+
'smooth',
|
|
173
|
+
'gentle-end'
|
|
174
|
+
],
|
|
175
|
+
duration: [
|
|
176
|
+
550,
|
|
177
|
+
750
|
|
178
|
+
],
|
|
179
|
+
stagger: [
|
|
180
|
+
80,
|
|
181
|
+
110
|
|
182
|
+
],
|
|
183
|
+
hero: [
|
|
184
|
+
100,
|
|
185
|
+
130
|
|
186
|
+
],
|
|
187
|
+
sides: true
|
|
188
|
+
},
|
|
189
|
+
rise: {
|
|
190
|
+
words: 'everything fades up into place, section after section',
|
|
191
|
+
presets: [
|
|
192
|
+
'slide-up'
|
|
193
|
+
],
|
|
194
|
+
heroPresets: [
|
|
195
|
+
'slide-up'
|
|
196
|
+
],
|
|
197
|
+
media: [
|
|
198
|
+
'slide-up',
|
|
199
|
+
'fade'
|
|
200
|
+
],
|
|
201
|
+
eases: [
|
|
202
|
+
'smooth',
|
|
203
|
+
'gentle-end',
|
|
204
|
+
'gentle-both'
|
|
205
|
+
],
|
|
206
|
+
duration: [
|
|
207
|
+
500,
|
|
208
|
+
700
|
|
209
|
+
],
|
|
210
|
+
stagger: [
|
|
211
|
+
70,
|
|
212
|
+
100
|
|
213
|
+
],
|
|
214
|
+
hero: [
|
|
215
|
+
100,
|
|
216
|
+
130
|
|
217
|
+
],
|
|
218
|
+
sides: false
|
|
219
|
+
},
|
|
220
|
+
scale: {
|
|
221
|
+
words: 'pictures scale into place while the words fade in',
|
|
222
|
+
presets: [
|
|
223
|
+
'fade',
|
|
224
|
+
'slide-up'
|
|
225
|
+
],
|
|
226
|
+
heroPresets: [
|
|
227
|
+
'fade'
|
|
228
|
+
],
|
|
229
|
+
media: [
|
|
230
|
+
'zoom-in',
|
|
231
|
+
'zoom-out'
|
|
232
|
+
],
|
|
233
|
+
eases: [
|
|
234
|
+
'smooth',
|
|
235
|
+
'gentle-end'
|
|
236
|
+
],
|
|
237
|
+
duration: [
|
|
238
|
+
550,
|
|
239
|
+
750
|
|
240
|
+
],
|
|
241
|
+
stagger: [
|
|
242
|
+
80,
|
|
243
|
+
110
|
|
244
|
+
],
|
|
245
|
+
hero: [
|
|
246
|
+
100,
|
|
247
|
+
130
|
|
248
|
+
],
|
|
249
|
+
sides: false
|
|
250
|
+
},
|
|
251
|
+
cascade: {
|
|
252
|
+
words: 'cards and lists ripple in one after another, quick and lively',
|
|
253
|
+
presets: [
|
|
254
|
+
'slide-up',
|
|
255
|
+
'fade'
|
|
256
|
+
],
|
|
257
|
+
heroPresets: [
|
|
258
|
+
'slide-up'
|
|
259
|
+
],
|
|
260
|
+
groupPresets: [
|
|
261
|
+
'slide-up',
|
|
262
|
+
'zoom-in'
|
|
263
|
+
],
|
|
264
|
+
media: [
|
|
265
|
+
'slide-up',
|
|
266
|
+
'zoom-in'
|
|
267
|
+
],
|
|
268
|
+
eases: [
|
|
269
|
+
'overshoot',
|
|
270
|
+
'smooth'
|
|
271
|
+
],
|
|
272
|
+
duration: [
|
|
273
|
+
450,
|
|
274
|
+
600
|
|
275
|
+
],
|
|
276
|
+
stagger: [
|
|
277
|
+
105,
|
|
278
|
+
120
|
|
279
|
+
],
|
|
280
|
+
hero: [
|
|
281
|
+
110,
|
|
282
|
+
140
|
|
283
|
+
],
|
|
284
|
+
sides: false
|
|
285
|
+
},
|
|
286
|
+
still: {
|
|
287
|
+
words: 'near-still: a short, soft fade, and nothing slides or scales',
|
|
288
|
+
presets: [
|
|
289
|
+
'fade'
|
|
290
|
+
],
|
|
291
|
+
heroPresets: [
|
|
292
|
+
'fade'
|
|
293
|
+
],
|
|
294
|
+
media: [
|
|
295
|
+
'fade'
|
|
296
|
+
],
|
|
297
|
+
eases: [
|
|
298
|
+
'gentle-end',
|
|
299
|
+
'gentle-both'
|
|
300
|
+
],
|
|
301
|
+
duration: [
|
|
302
|
+
400,
|
|
303
|
+
500
|
|
304
|
+
],
|
|
305
|
+
stagger: [
|
|
306
|
+
60,
|
|
307
|
+
75
|
|
308
|
+
],
|
|
309
|
+
hero: [
|
|
310
|
+
80,
|
|
311
|
+
100
|
|
312
|
+
],
|
|
313
|
+
sides: false
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
/** The motion styles each mood suits, which the look is offered and the seed draws among. */ export const AI_SITE_MOTION_SUITS = {
|
|
317
|
+
calm: [
|
|
318
|
+
'still',
|
|
319
|
+
'rise',
|
|
320
|
+
'sides'
|
|
321
|
+
],
|
|
322
|
+
bold: [
|
|
323
|
+
'sides',
|
|
324
|
+
'scale',
|
|
325
|
+
'cascade',
|
|
326
|
+
'rise'
|
|
327
|
+
],
|
|
328
|
+
gallery: [
|
|
329
|
+
'scale',
|
|
330
|
+
'sides',
|
|
331
|
+
'still',
|
|
332
|
+
'cascade'
|
|
333
|
+
],
|
|
334
|
+
standard: [
|
|
335
|
+
'sides',
|
|
336
|
+
'rise',
|
|
337
|
+
'scale',
|
|
338
|
+
'cascade'
|
|
339
|
+
]
|
|
340
|
+
};
|
|
341
|
+
/** A motion style, read defensively: `null` for anything outside the vocabulary. */ export function aiSiteMotionStyle(value) {
|
|
342
|
+
return typeof value === 'string' && AI_SITE_MOTION_STYLES.includes(value) ? value : null;
|
|
343
|
+
}
|
|
344
|
+
/** The motion styles that suit a kind of site. */ export function aiSiteMotionStylesFor(kind) {
|
|
345
|
+
return AI_SITE_MOTION_SUITS[aiSiteMotionMood(kind)];
|
|
346
|
+
}
|
|
130
347
|
/** The mood a kind of site moves in. */ export function aiSiteMotionMood(kind) {
|
|
131
348
|
return kind && AI_SITE_MOTION_KINDS[kind] || 'standard';
|
|
132
349
|
}
|
|
@@ -145,21 +362,31 @@ import { applyElementMotion } from "@aglyn/aglyn/app-utils/element-motion";
|
|
|
145
362
|
}
|
|
146
363
|
const pick = (next, list)=>list[Math.floor(next() * list.length) % list.length];
|
|
147
364
|
/** A value in an inclusive range, on a step: 10 ms for a duration, 5 ms for a gap. */ const within = (next, [low, high], step)=>low + Math.floor(next() * (Math.floor((high - low) / step) + 1)) * step;
|
|
148
|
-
/**
|
|
149
|
-
|
|
365
|
+
/**
|
|
366
|
+
* The motion character of a site: its motion style where its look chose one,
|
|
367
|
+
* else its kind's mood, turned by its seed.
|
|
368
|
+
*/ export function aiSiteMotion(input) {
|
|
369
|
+
var _range_sides;
|
|
370
|
+
var _range_heroPresets, _range_groupPresets;
|
|
371
|
+
const style = aiSiteMotionStyle(input.motion);
|
|
372
|
+
const range = style ? AI_SITE_MOTION_STYLE_RANGES[style] : AI_SITE_MOTION_MOODS[aiSiteMotionMood(input.kind)];
|
|
150
373
|
const next = random(input.seed >>> 0);
|
|
151
374
|
const first = Math.floor(next() * range.presets.length) % range.presets.length;
|
|
152
375
|
const sectionPresets = range.presets.map((_, index)=>range.presets[(first + index) % range.presets.length]);
|
|
153
|
-
|
|
376
|
+
const character = {
|
|
154
377
|
preset: sectionPresets[0],
|
|
155
378
|
sectionPresets,
|
|
156
|
-
sides: true,
|
|
379
|
+
sides: (_range_sides = range.sides) != null ? _range_sides : true,
|
|
157
380
|
mediaPreset: pick(next, range.media),
|
|
158
381
|
ease: pick(next, range.eases),
|
|
159
382
|
durationMs: within(next, range.duration, 10),
|
|
160
383
|
staggerStepMs: within(next, range.stagger, 5),
|
|
161
384
|
heroStepMs: within(next, range.hero, 5)
|
|
162
385
|
};
|
|
386
|
+
// Drawn after the rest, so a mood, which names neither, draws the same numbers either way.
|
|
387
|
+
if ((_range_heroPresets = range.heroPresets) == null ? void 0 : _range_heroPresets.length) character.preset = pick(next, range.heroPresets);
|
|
388
|
+
if ((_range_groupPresets = range.groupPresets) == null ? void 0 : _range_groupPresets.length) character.groupPreset = pick(next, range.groupPresets);
|
|
389
|
+
return character;
|
|
163
390
|
}
|
|
164
391
|
/**
|
|
165
392
|
* A page the AI built, with its site's entrance motion written onto it —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-motion.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AnimationEase, AnimationPreset } from '@aglyn/aglyn/app-utils/element-animation'\nimport {\n applyElementMotion,\n type ElementMotionCharacter,\n} from '@aglyn/aglyn/app-utils/element-motion'\n\n/**\n * How an AI-built site moves (AGL-3838): a small motion CHARACTER per kind of\n * site, turned by the site's design seed, which the platform's entrance pass\n * (`element-motion.ts`) writes onto every page the AI builds.\n *\n * Decided in code, never by the model: no prompt names a preset, no answer\n * carries one, and no credit is spent on it. The same site — same kind, same\n * seed — always moves the same way on every page, and two sites of one kind\n * are drawn apart by their seeds, the way their looks are.\n *\n * The moods, by the kinds `ai-site-kinds.ts` lists:\n * - CALM — practices, health, reading: fades and rises taken in turn, on a\n * gentle curve, slower.\n * - BOLD — studios, fitness, music, events: a rise with overshoot, and\n * pictures that zoom in.\n * - GALLERY — portfolios and photography: pictures zoom in, one after\n * another; the words fade or rise.\n * - STANDARD — everything else: rises and fades taken in turn.\n *\n * In every mood a page's later sections take the mood's presets in turn,\n * starting where the seed says, and a column of words beside a picture comes\n * in from its own side (Zach 2026-10-16: \"some slide in ones would be cool\n * too\"). A page is never one move repeated.\n *\n * Every number stays inside the vocabulary's caps, and inside 400–800 ms for\n * a duration and 60–120 ms between staggered items: slower reads as a site\n * that is loading, faster as one that is twitching.\n */\n\nexport type AiSiteMotionMood = 'calm' | 'bold' | 'gallery' | 'standard'\n\ninterface AiSiteMotionRange {\n /** The word presets: the seed picks the first, and later sections take them in turn from there. */\n presets: readonly AnimationPreset[]\n /** The picture presets the seed picks among. */\n media: readonly AnimationPreset[]\n eases: readonly AnimationEase[]\n /** Inclusive millisecond ranges. */\n duration: readonly [number, number]\n stagger: readonly [number, number]\n hero: readonly [number, number]\n}\n\n/** Each mood's range. The seed picks one character from it. */\nexport const AI_SITE_MOTION_MOODS: Readonly<Record<AiSiteMotionMood, AiSiteMotionRange>> = {\n calm: {\n presets: ['fade', 'slide-up'],\n media: ['fade'],\n eases: ['gentle-end', 'gentle-both'],\n duration: [650, 800],\n stagger: [100, 120],\n hero: [130, 160],\n },\n bold: {\n presets: ['slide-up'],\n media: ['zoom-in'],\n eases: ['overshoot'],\n duration: [450, 600],\n stagger: [60, 80],\n hero: [80, 100],\n },\n gallery: {\n presets: ['fade', 'slide-up'],\n media: ['zoom-in'],\n eases: ['smooth'],\n duration: [600, 750],\n stagger: [80, 110],\n hero: [110, 130],\n },\n standard: {\n presets: ['slide-up', 'fade'],\n media: ['slide-up', 'fade'],\n eases: ['smooth'],\n duration: [500, 700],\n stagger: [70, 100],\n hero: [100, 130],\n },\n}\n\n/** Each site kind's mood; a kind not listed is standard. */\nexport const AI_SITE_MOTION_KINDS: Readonly<Record<string, AiSiteMotionMood>> = {\n professional: 'calm',\n wellness: 'calm',\n yoga: 'calm',\n beauty: 'calm',\n blog: 'calm',\n studio: 'bold',\n fitness: 'bold',\n music: 'bold',\n events: 'bold',\n portfolio: 'gallery',\n photography: 'gallery',\n}\n\n/** The mood a kind of site moves in. */\nexport function aiSiteMotionMood(kind: string | null | undefined): AiSiteMotionMood {\n return (kind && AI_SITE_MOTION_KINDS[kind]) || 'standard'\n}\n\n/**\n * A small deterministic generator over a seed (mulberry32), mixed with its\n * own constant so a site's motion does not move in step with its layout.\n */\nfunction random(seed: number): () => number {\n let state = (seed ^ 0x6a09e667) >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nconst pick = <T>(next: () => number, list: readonly T[]): T => list[Math.floor(next() * list.length) % list.length] as T\n/** A value in an inclusive range, on a step: 10 ms for a duration, 5 ms for a gap. */\nconst within = (next: () => number, [low, high]: readonly [number, number], step: number): number =>\n low + Math.floor(next() * (Math.floor((high - low) / step) + 1)) * step\n\n/** The motion character of a site: its kind's mood, turned by its seed. */\nexport function aiSiteMotion(input: { kind?: string | null; seed: number }): ElementMotionCharacter {\n const range = AI_SITE_MOTION_MOODS[aiSiteMotionMood(input.kind)]\n const next = random(input.seed >>> 0)\n const first = Math.floor(next() * range.presets.length) % range.presets.length\n const sectionPresets = range.presets.map((_, index) => range.presets[(first + index) % range.presets.length])\n return {\n preset: sectionPresets[0],\n sectionPresets,\n sides: true,\n mediaPreset: pick(next, range.media),\n ease: pick(next, range.eases),\n durationMs: within(next, range.duration, 10),\n staggerStepMs: within(next, range.stagger, 5),\n heroStepMs: within(next, range.hero, 5),\n }\n}\n\n/**\n * A page the AI built, with its site's entrance motion written onto it —\n * or the same node map, when there was nothing left to move.\n */\nexport function aiWithSiteMotion<T extends Record<string, any>>(\n nodes: T,\n site: { kind?: string | null; seed: number },\n page: { rootId?: string; sectionIds?: readonly string[] } = {},\n): T {\n return applyElementMotion(nodes, {\n character: aiSiteMotion(site),\n ...(page.rootId ? { rootId: page.rootId } : {}),\n ...(page.sectionIds?.length ? { sectionIds: page.sectionIds } : {}),\n })\n}\n"],"names":["applyElementMotion","AI_SITE_MOTION_MOODS","calm","presets","media","eases","duration","stagger","hero","bold","gallery","standard","AI_SITE_MOTION_KINDS","professional","wellness","yoga","beauty","blog","studio","fitness","music","events","portfolio","photography","aiSiteMotionMood","kind","random","seed","state","t","Math","imul","pick","next","list","floor","length","within","low","high","step","aiSiteMotion","input","range","first","sectionPresets","map","_","index","preset","sides","mediaPreset","ease","durationMs","staggerStepMs","heroStepMs","aiWithSiteMotion","nodes","site","page","character","rootId","sectionIds"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-motion.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AnimationEase, AnimationPreset } from '@aglyn/aglyn/app-utils/element-animation'\nimport {\n applyElementMotion,\n type ElementMotionCharacter,\n} from '@aglyn/aglyn/app-utils/element-motion'\n\n/**\n * How an AI-built site moves (AGL-3838, AGL-3941): a motion CHARACTER per\n * site, turned by the site's design seed, which the platform's entrance pass\n * (`element-motion.ts`) writes onto every page the AI builds.\n *\n * The character comes from one of two places:\n * - the site's MOTION STYLE, when its look chose one (`siteStyle.motion`,\n * asked with the rest of the look in `submit_site_look`): a small closed\n * vocabulary of named characters, {@link AI_SITE_MOTION_STYLES}, each of\n * which says how a hero, a split, a grid of cards and every other section\n * arrive;\n * - else the site's KIND, mapped to a mood: what every site built before\n * the look chose motion, a replay of one, and any job without a look\n * moves in.\n *\n * Either way the seed picks within the character's ranges — the first\n * preset, the picture preset, the curve, the timing — so two sites that chose\n * the same style still move apart, and one site moves the same way on every\n * page. No page prompt names a preset and no page answer carries one.\n *\n * The moods, by the kinds `ai-site-kinds.ts` lists:\n * - CALM — practices, health, reading: fades and rises taken in turn, on a\n * gentle curve, slower.\n * - BOLD — studios, fitness, music, events: a rise with overshoot, and\n * pictures that zoom in.\n * - GALLERY — portfolios and photography: pictures zoom in, one after\n * another; the words fade or rise.\n * - STANDARD — everything else: rises and fades taken in turn.\n *\n * In every mood a page's later sections take the mood's presets in turn,\n * starting where the seed says, and a column of words beside a picture comes\n * in from its own side (Zach 2026-10-16: \"some slide in ones would be cool\n * too\").\n *\n * Every number stays inside the vocabulary's caps, and inside 400–800 ms for\n * a duration and 60–120 ms between staggered items: slower reads as a site\n * that is loading, faster as one that is twitching.\n */\n\nexport type AiSiteMotionMood = 'calm' | 'bold' | 'gallery' | 'standard'\n\ninterface AiSiteMotionRange {\n /** The word presets: the seed picks the first, and later sections take them in turn from there. */\n presets: readonly AnimationPreset[]\n /** The picture presets the seed picks among. */\n media: readonly AnimationPreset[]\n eases: readonly AnimationEase[]\n /** Inclusive millisecond ranges. */\n duration: readonly [number, number]\n stagger: readonly [number, number]\n hero: readonly [number, number]\n /** Whether a split's picture and words come in from their own sides; absent, they do. */\n sides?: boolean\n /** The first section's presets the seed picks among; absent, it opens on the first section preset. */\n heroPresets?: readonly AnimationPreset[]\n /** The presets a group of like items — a grid of cards, a list — the seed picks among; absent, the section's own. */\n groupPresets?: readonly AnimationPreset[]\n}\n\n/** Each mood's range. The seed picks one character from it. */\nexport const AI_SITE_MOTION_MOODS: Readonly<Record<AiSiteMotionMood, AiSiteMotionRange>> = {\n calm: {\n presets: ['fade', 'slide-up'],\n media: ['fade'],\n eases: ['gentle-end', 'gentle-both'],\n duration: [650, 800],\n stagger: [100, 120],\n hero: [130, 160],\n },\n bold: {\n presets: ['slide-up'],\n media: ['zoom-in'],\n eases: ['overshoot'],\n duration: [450, 600],\n stagger: [60, 80],\n hero: [80, 100],\n },\n gallery: {\n presets: ['fade', 'slide-up'],\n media: ['zoom-in'],\n eases: ['smooth'],\n duration: [600, 750],\n stagger: [80, 110],\n hero: [110, 130],\n },\n standard: {\n presets: ['slide-up', 'fade'],\n media: ['slide-up', 'fade'],\n eases: ['smooth'],\n duration: [500, 700],\n stagger: [70, 100],\n hero: [100, 130],\n },\n}\n\n/** Each site kind's mood; a kind not listed is standard. */\nexport const AI_SITE_MOTION_KINDS: Readonly<Record<string, AiSiteMotionMood>> = {\n professional: 'calm',\n wellness: 'calm',\n yoga: 'calm',\n beauty: 'calm',\n blog: 'calm',\n studio: 'bold',\n fitness: 'bold',\n music: 'bold',\n events: 'bold',\n portfolio: 'gallery',\n photography: 'gallery',\n}\n\n/**\n * The motion styles a site's look chooses among (AGL-3941). Persisted on the\n * site's style; never rename one.\n */\nexport const AI_SITE_MOTION_STYLES = ['sides', 'rise', 'scale', 'cascade', 'still'] as const\n\nexport type AiSiteMotionStyle = (typeof AI_SITE_MOTION_STYLES)[number]\n\n/**\n * Each motion style, by what it does to each kind of section:\n * - SIDES — a split's picture slides in from its own side and its words\n * from the other; the rest rises and fades in turn. Pictures never zoom,\n * so a split's picture always takes its side.\n * - RISE — everything fades up into place; a split's columns rise one\n * after the other.\n * - SCALE — pictures scale into place, from smaller or from larger; the\n * hero's words fade, and later sections fade and rise in turn.\n * - CASCADE — cards and lists ripple in one after another at the widest\n * stagger, quick, often with overshoot.\n * - STILL — near-static: a short, soft fade, and nothing slides or scales.\n *\n * The hero's main heading never moves under any of them (`element-motion.ts`),\n * and every one is written as the same `aglynAnimation*` props, so a visitor\n * who asks for reduced motion sees none of it.\n */\nexport const AI_SITE_MOTION_STYLE_RANGES: Readonly<Record<AiSiteMotionStyle, AiSiteMotionRange & { words: string }>> = {\n sides: {\n words: \"a split section's picture slides in from its own side and its words from the other; the rest rises\",\n presets: ['slide-up', 'fade'],\n heroPresets: ['slide-up', 'fade'],\n media: ['fade', 'slide-up'],\n eases: ['smooth', 'gentle-end'],\n duration: [550, 750],\n stagger: [80, 110],\n hero: [100, 130],\n sides: true,\n },\n rise: {\n words: 'everything fades up into place, section after section',\n presets: ['slide-up'],\n heroPresets: ['slide-up'],\n media: ['slide-up', 'fade'],\n eases: ['smooth', 'gentle-end', 'gentle-both'],\n duration: [500, 700],\n stagger: [70, 100],\n hero: [100, 130],\n sides: false,\n },\n scale: {\n words: 'pictures scale into place while the words fade in',\n presets: ['fade', 'slide-up'],\n heroPresets: ['fade'],\n media: ['zoom-in', 'zoom-out'],\n eases: ['smooth', 'gentle-end'],\n duration: [550, 750],\n stagger: [80, 110],\n hero: [100, 130],\n sides: false,\n },\n cascade: {\n words: 'cards and lists ripple in one after another, quick and lively',\n presets: ['slide-up', 'fade'],\n heroPresets: ['slide-up'],\n groupPresets: ['slide-up', 'zoom-in'],\n media: ['slide-up', 'zoom-in'],\n eases: ['overshoot', 'smooth'],\n duration: [450, 600],\n stagger: [105, 120],\n hero: [110, 140],\n sides: false,\n },\n still: {\n words: 'near-still: a short, soft fade, and nothing slides or scales',\n presets: ['fade'],\n heroPresets: ['fade'],\n media: ['fade'],\n eases: ['gentle-end', 'gentle-both'],\n duration: [400, 500],\n stagger: [60, 75],\n hero: [80, 100],\n sides: false,\n },\n}\n\n/** The motion styles each mood suits, which the look is offered and the seed draws among. */\nexport const AI_SITE_MOTION_SUITS: Readonly<Record<AiSiteMotionMood, readonly AiSiteMotionStyle[]>> = {\n calm: ['still', 'rise', 'sides'],\n bold: ['sides', 'scale', 'cascade', 'rise'],\n gallery: ['scale', 'sides', 'still', 'cascade'],\n standard: ['sides', 'rise', 'scale', 'cascade'],\n}\n\n/** A motion style, read defensively: `null` for anything outside the vocabulary. */\nexport function aiSiteMotionStyle(value: unknown): AiSiteMotionStyle | null {\n return typeof value === 'string' && (AI_SITE_MOTION_STYLES as readonly string[]).includes(value) ? (value as AiSiteMotionStyle) : null\n}\n\n/** The motion styles that suit a kind of site. */\nexport function aiSiteMotionStylesFor(kind: string | null | undefined): readonly AiSiteMotionStyle[] {\n return AI_SITE_MOTION_SUITS[aiSiteMotionMood(kind)]\n}\n\n/** The mood a kind of site moves in. */\nexport function aiSiteMotionMood(kind: string | null | undefined): AiSiteMotionMood {\n return (kind && AI_SITE_MOTION_KINDS[kind]) || 'standard'\n}\n\n/**\n * A small deterministic generator over a seed (mulberry32), mixed with its\n * own constant so a site's motion does not move in step with its layout.\n */\nfunction random(seed: number): () => number {\n let state = (seed ^ 0x6a09e667) >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nconst pick = <T>(next: () => number, list: readonly T[]): T => list[Math.floor(next() * list.length) % list.length] as T\n/** A value in an inclusive range, on a step: 10 ms for a duration, 5 ms for a gap. */\nconst within = (next: () => number, [low, high]: readonly [number, number], step: number): number =>\n low + Math.floor(next() * (Math.floor((high - low) / step) + 1)) * step\n\n/**\n * The motion character of a site: its motion style where its look chose one,\n * else its kind's mood, turned by its seed.\n */\nexport function aiSiteMotion(input: { kind?: string | null; seed: number; motion?: string | null }): ElementMotionCharacter {\n const style = aiSiteMotionStyle(input.motion)\n const range = style ? AI_SITE_MOTION_STYLE_RANGES[style] : AI_SITE_MOTION_MOODS[aiSiteMotionMood(input.kind)]\n const next = random(input.seed >>> 0)\n const first = Math.floor(next() * range.presets.length) % range.presets.length\n const sectionPresets = range.presets.map((_, index) => range.presets[(first + index) % range.presets.length])\n const character: ElementMotionCharacter = {\n preset: sectionPresets[0],\n sectionPresets,\n sides: range.sides ?? true,\n mediaPreset: pick(next, range.media),\n ease: pick(next, range.eases),\n durationMs: within(next, range.duration, 10),\n staggerStepMs: within(next, range.stagger, 5),\n heroStepMs: within(next, range.hero, 5),\n }\n // Drawn after the rest, so a mood, which names neither, draws the same numbers either way.\n if (range.heroPresets?.length) character.preset = pick(next, range.heroPresets)\n if (range.groupPresets?.length) character.groupPreset = pick(next, range.groupPresets)\n return character\n}\n\n/**\n * A page the AI built, with its site's entrance motion written onto it —\n * or the same node map, when there was nothing left to move.\n */\nexport function aiWithSiteMotion<T extends Record<string, any>>(\n nodes: T,\n site: { kind?: string | null; seed: number; motion?: string | null },\n page: { rootId?: string; sectionIds?: readonly string[] } = {},\n): T {\n return applyElementMotion(nodes, {\n character: aiSiteMotion(site),\n ...(page.rootId ? { rootId: page.rootId } : {}),\n ...(page.sectionIds?.length ? { sectionIds: page.sectionIds } : {}),\n })\n}\n"],"names":["applyElementMotion","AI_SITE_MOTION_MOODS","calm","presets","media","eases","duration","stagger","hero","bold","gallery","standard","AI_SITE_MOTION_KINDS","professional","wellness","yoga","beauty","blog","studio","fitness","music","events","portfolio","photography","AI_SITE_MOTION_STYLES","AI_SITE_MOTION_STYLE_RANGES","sides","words","heroPresets","rise","scale","cascade","groupPresets","still","AI_SITE_MOTION_SUITS","aiSiteMotionStyle","value","includes","aiSiteMotionStylesFor","kind","aiSiteMotionMood","random","seed","state","t","Math","imul","pick","next","list","floor","length","within","low","high","step","aiSiteMotion","input","range","style","motion","first","sectionPresets","map","_","index","character","preset","mediaPreset","ease","durationMs","staggerStepMs","heroStepMs","groupPreset","aiWithSiteMotion","nodes","site","page","rootId","sectionIds"],"mappings":"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"}
|
|
@@ -190,6 +190,14 @@ export declare function aiLayoutSeedNumber(seed: string): number;
|
|
|
190
190
|
* people; a gallery picture never takes it, even to avoid a repeat.
|
|
191
191
|
*/
|
|
192
192
|
export declare function aiLayoutStarterPhotos(slots: readonly AiLayoutPictureSlot[], seed: string): AiLayoutPicturePhoto[];
|
|
193
|
+
/**
|
|
194
|
+
* A neutral starter for each slot a library could not fill, where the site
|
|
195
|
+
* has a library (AGL-3923): each at most once on a page, the hero served
|
|
196
|
+
* first and each pool turned by the seed, and none for a portrait of the
|
|
197
|
+
* business's people or a product, nor for a slot past the two. A slot given
|
|
198
|
+
* none keeps the no-photo treatment.
|
|
199
|
+
*/
|
|
200
|
+
export declare function aiLayoutNeutralStarterPhotos(slots: readonly AiLayoutPictureSlot[], seed: string): Array<AiLayoutPicturePhoto | null>;
|
|
193
201
|
/**
|
|
194
202
|
* The page with each slot filled by its photo: the `image` given its source,
|
|
195
203
|
* its intrinsic size and how it loads (the hero eagerly, as the starter home
|
|
@@ -16,7 +16,14 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/ import { DEFAULT_SITE_IMAGES } from "@aglyn/aglyn/app-utils/default-site";
|
|
18
18
|
import { aiLayoutLabel, aiLayoutSpeaksOtherThanEnglish } from "./ai-layout-labels.js";
|
|
19
|
-
/** Words that say a picture shows people, or a section is about them. */ const PEOPLE = /\b(
|
|
19
|
+
/** Words that say a picture shows people, or a section is about them. */ const PEOPLE = /\b(team|story|owner|owners|founder|founders|staff|crew|people|person|portrait|meet|family|families|who we are|our (?:team|crew|people|story)|therapist|counsel(?:l)?or|coach|doctor|dentist|stylist|trainer|smil(?:e|es|ing)|headshot)\b/i;
|
|
20
|
+
/**
|
|
21
|
+
* "About" as what a section or a picture is (AGL-3923): leading its words
|
|
22
|
+
* ("About us", "About the studio"), never the preposition inside them. The
|
|
23
|
+
* beta.267 Brightvale closing band, "Ready to talk about your marketing?",
|
|
24
|
+
* was read as a picture of people and searched "ready talk marketing" among
|
|
25
|
+
* portraits, which answered a tin-can telephone.
|
|
26
|
+
*/ const ABOUT = /^\W*about\b/i;
|
|
20
27
|
/**
|
|
21
28
|
* An `aspectRatio` (`'16 / 9'`) as a number, or `null` where none is named.
|
|
22
29
|
* A responsive one (`{ xs: '4 / 3', md: '4 / 5' }`, as a designed page's
|
|
@@ -59,7 +66,7 @@ import { aiLayoutLabel, aiLayoutSpeaksOtherThanEnglish } from "./ai-layout-label
|
|
|
59
66
|
}
|
|
60
67
|
/** A slot's role: the first section's picture is the hero; people make an about picture. */ export function aiLayoutPictureRole(input) {
|
|
61
68
|
if (input.sectionIndex === 0) return 'hero';
|
|
62
|
-
if (PEOPLE.test(input.sectionName) || PEOPLE.test(input.alt)) return 'about';
|
|
69
|
+
if (PEOPLE.test(input.sectionName) || PEOPLE.test(input.alt) || ABOUT.test(input.sectionName) || ABOUT.test(input.alt)) return 'about';
|
|
63
70
|
return 'gallery';
|
|
64
71
|
}
|
|
65
72
|
/**
|
|
@@ -229,6 +236,45 @@ function rotate(list, by) {
|
|
|
229
236
|
};
|
|
230
237
|
});
|
|
231
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* The starters that show no one's business (AGL-3923): a team at a table in a
|
|
241
|
+
* bright office, a tidy desk. Never the workbench, the coffee shop's owner or
|
|
242
|
+
* the houseplants, which read as another business's own photos (AGL-3831).
|
|
243
|
+
*/ const NEUTRAL_STARTERS = [
|
|
244
|
+
'hero',
|
|
245
|
+
'desk'
|
|
246
|
+
];
|
|
247
|
+
/**
|
|
248
|
+
* A neutral starter for each slot a library could not fill, where the site
|
|
249
|
+
* has a library (AGL-3923): each at most once on a page, the hero served
|
|
250
|
+
* first and each pool turned by the seed, and none for a portrait of the
|
|
251
|
+
* business's people or a product, nor for a slot past the two. A slot given
|
|
252
|
+
* none keeps the no-photo treatment.
|
|
253
|
+
*/ export function aiLayoutNeutralStarterPhotos(slots, seed) {
|
|
254
|
+
const pool = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(seed));
|
|
255
|
+
const order = [
|
|
256
|
+
'hero',
|
|
257
|
+
'gallery',
|
|
258
|
+
'about'
|
|
259
|
+
];
|
|
260
|
+
const served = slots.map((slot, index)=>({
|
|
261
|
+
slot,
|
|
262
|
+
index
|
|
263
|
+
})).filter(({ slot })=>!slot.people && !slot.product).sort((a, b)=>order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index);
|
|
264
|
+
const photos = slots.map(()=>null);
|
|
265
|
+
served.forEach(({ index }, step)=>{
|
|
266
|
+
const name = pool[step];
|
|
267
|
+
if (!name) return;
|
|
268
|
+
const photo = AI_LAYOUT_STARTER_PHOTOS[name];
|
|
269
|
+
photos[index] = {
|
|
270
|
+
src: photo.src,
|
|
271
|
+
width: photo.width,
|
|
272
|
+
height: photo.height,
|
|
273
|
+
alt: photo.alt
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
return photos;
|
|
277
|
+
}
|
|
232
278
|
/**
|
|
233
279
|
* The page with each slot filled by its photo: the `image` given its source,
|
|
234
280
|
* its intrinsic size and how it loads (the hero eagerly, as the starter home
|
|
@@ -382,15 +428,33 @@ function rotate(list, by) {
|
|
|
382
428
|
// The starters' words are English; a site in another language says it
|
|
383
429
|
// shows an illustrative photo, true of any photo, as a stock photo's does.
|
|
384
430
|
const otherLanguage = aiLayoutSpeaksOtherThanEnglish(input.language);
|
|
385
|
-
const
|
|
431
|
+
const said = (photo)=>otherLanguage ? _extends({}, photo, {
|
|
386
432
|
alt: aiLayoutLabel('illustrativePhoto', input.language)
|
|
387
|
-
}) : photo
|
|
433
|
+
}) : photo;
|
|
434
|
+
// With a library, a starter is only ever a neutral one (AGL-3923): the
|
|
435
|
+
// beta.267 Brightvale start, whose library refused its copies, showed a
|
|
436
|
+
// pottery bench and a coffee shop's owner as a marketer's pictures.
|
|
437
|
+
const starters = (input.source ? aiLayoutNeutralStarterPhotos(slots, input.seed) : aiLayoutStarterPhotos(slots, input.seed)).map((photo)=>photo ? said(photo) : null);
|
|
388
438
|
const photos = slots.map((_slot, index)=>{
|
|
389
439
|
var _starters_index;
|
|
390
440
|
const answer = found[index];
|
|
391
441
|
if (answer) return answer.src ? answer : null;
|
|
392
442
|
return (_starters_index = starters[index]) != null ? _starters_index : null;
|
|
393
443
|
});
|
|
444
|
+
// The page floor (AGL-3923; Zach, 2026-10-12: a starter photo before no
|
|
445
|
+
// photo at all): a page the library answered that would show no picture
|
|
446
|
+
// keeps one neutral starter, in its hero, else its first slot.
|
|
447
|
+
if (input.source && !photos.some((photo)=>photo == null ? void 0 : photo.src)) {
|
|
448
|
+
const at = Math.max(0, slots.findIndex((slot)=>slot.role === 'hero'));
|
|
449
|
+
const [floor] = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(input.seed));
|
|
450
|
+
const photo = floor ? AI_LAYOUT_STARTER_PHOTOS[floor] : null;
|
|
451
|
+
if (photo) photos[at] = said({
|
|
452
|
+
src: photo.src,
|
|
453
|
+
width: photo.width,
|
|
454
|
+
height: photo.height,
|
|
455
|
+
alt: photo.alt
|
|
456
|
+
});
|
|
457
|
+
}
|
|
394
458
|
return aiLayoutDropEmptyPictures(aiLayoutPlacePictures(nodes, slots, photos), slots, photos, input.sectionIds);
|
|
395
459
|
} catch (error) {
|
|
396
460
|
console.error('ai layout pictures: the page keeps its empty picture slots', {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-pictures.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DEFAULT_SITE_IMAGES } from '@aglyn/aglyn/app-utils/default-site'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiLayoutLabel, aiLayoutSpeaksOtherThanEnglish } from './ai-layout-labels'\n\n/**\n * THE PICTURES OF A LANGUAGE PAGE (AGL-3660).\n *\n * The compiler writes a picture slot as a frame holding an icon and an\n * `image` with alt text and no source (`ai-layout-compiler.ts`), and stays\n * pure: it never looks a photo up. This module is what runs after it, on the\n * stored page — it finds each empty slot, decides what kind of picture the\n * slot is, and fills it with a photo the site can serve from its own origin.\n *\n * Two sources fill a slot, in order:\n *\n * 1. A stock photo searched for by the slot's own description and copied into\n * the site's media library (`jobs/ai-layout-stock-photos.ts`), when the\n * deployment has a stock-photo key.\n * 2. Where the library cannot be asked at all — no stock-photo key, the\n * library down, refusing or out of time before it answered — one of the\n * STARTER PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`,\n * served at `/_static/starter/` by the tenant and the console alike,\n * credited in the CREDITS file beside them). They are the platform's own\n * files, so a visitor's browser asks no other host for them, and a\n * starter reads better than no picture when there is no library.\n *\n * Where the library answered and nothing in it fits a slot\n * ({@link AI_LAYOUT_NO_PHOTO}), the slot never takes a starter: the beta.257\n * Copper Kettle Catering start (2026-10-15) opened a barbecue caterer's home\n * page on the starters' pottery workbench (AGL-3831). Nor does it ship empty\n * (AGL-3856): its picture is taken out, frame and all\n * ({@link aiLayoutDropEmptyPictures}). A cover's frame is a black scrim laid\n * over the whole section, and with nothing in it the beta.261 Synchronicity\n * Gym hero read as a solid black block; a split's picture card is a rounded\n * gray box, and the beta.262 Adult Hybrid Learning Center hero read as\n * broken. Without them the section shows its own band, a cover's section\n * still pinned to the site's dark scheme so the words written for a dark\n * photo stay readable, and a split's words take the row's width.\n *\n * A slot keeps the alt text the model wrote only when it describes the photo\n * placed: a stock photo that is not what it says is described by what it is\n * (`aiStockPhotoAlt`), and a starter photo always by its own words, since\n * the model never wrote for it (AGL-3831). A stand-in the compiler drew where\n * the model described no picture is decorative while empty; the photo placed\n * there is described by its own words and is no longer decorative (AGL-3856).\n */\n\n/** What a picture slot is for, read from where it sits and what it says. */\nexport type AiLayoutPictureRole = 'hero' | 'about' | 'gallery'\n\n/** One empty picture slot of a stored page, in document order. */\nexport interface AiLayoutPictureSlot {\n /** The `image` node. */\n imageId: string\n /** The frame around it, which sets the slot's shape. */\n frameId: string | null\n /** The placeholder icon sharing the frame, taken out once a photo fills it. */\n iconId: string | null\n alt: string\n /** The frame's shape as width over height; 1 when it names none. */\n aspect: number\n /** The plan index of the section it sits in, or -1. */\n sectionIndex: number\n role: AiLayoutPictureRole\n /**\n * A product's photo (AGL-3676): what it is of, most specific first — its\n * name, then what its photo should show — searched for with the shop's\n * category and held to it, so a wick trimmer is never a laptop on a desk.\n */\n product?: { subjects: readonly string[] }\n /**\n * A stand-in the compiler drew where the model described no picture\n * (AGL-3856): its words are what a search looks for, never what a photo\n * shows, so a photo placed there is described by its own, and is no longer\n * decorative once it is.\n */\n standIn?: boolean\n /**\n * A portrait of one of the business's people (AGL-3856): a record of a\n * team, staff or instructors list. Its photo shows an adult, in the role\n * its words or its list name where one is listed, never a child or a thing.\n */\n people?: boolean\n /**\n * A dataset record's cover (AGL-3856): what the record is filed under — the\n * value of its list's category-like field (\"Weddings and elopements\") —\n * read after its own name for what its photo is OF. A photographer's record\n * is the photographed moment, never the photographer or the camera.\n */\n record?: { category?: string }\n}\n\n/** A photo a slot is filled with. */\nexport interface AiLayoutPicturePhoto {\n /** The site-served address: a starter path or the media library's own. */\n src: string\n width: number\n height: number\n /**\n * What the photo shows, where the slot's own alt text does not say it: the\n * slot takes it in place of the words the model wrote before the photo was\n * found. Absent, the slot's words describe it.\n */\n alt?: string\n}\n\n/** Words that say a picture shows people, or a section is about them. */\nconst PEOPLE =\n /\\b(about|team|story|owner|owners|founder|founders|staff|crew|people|person|portrait|meet|family|families|who we are|our (?:team|crew|people|story)|therapist|counsel(?:l)?or|coach|doctor|dentist|stylist|trainer|smil(?:e|es|ing)|headshot)\\b/i\n\n/**\n * An `aspectRatio` (`'16 / 9'`) as a number, or `null` where none is named.\n * A responsive one (`{ xs: '4 / 3', md: '4 / 5' }`, as a designed page's\n * pictures carry, AGL-3660) reads at the desktop breakpoint first: one photo\n * serves every width, and the wide screen is where its shape shows most.\n */\nfunction aspectOf(value: unknown): number | null {\n if (value && typeof value === 'object') {\n const byBreakpoint = value as Record<string, unknown>\n for (const breakpoint of ['md', 'lg', 'xl', 'sm', 'xs']) {\n const ratio = aspectOf(byBreakpoint[breakpoint])\n if (ratio !== null) return ratio\n }\n return null\n }\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*\\/\\s*(\\d+(?:\\.\\d+)?)\\s*$/.exec(String(value ?? ''))\n if (!match) return null\n const ratio = Number(match[1]) / Number(match[2])\n return Number.isFinite(ratio) && ratio > 0 ? ratio : null\n}\n\n/** The shape a full-bleed cover's photo is searched for: it fills a wide band. */\nconst COVER_ASPECT = 16 / 9\n\n/**\n * A slot's shape (AGL-3660): the image's own `aspectRatio` (a designed\n * picture card sets it on the image), else its frame's, else — for a frame\n * laid absolutely over its whole section, a designed photo cover — a wide\n * band's. 1 where nothing says.\n */\nfunction slotAspect(image: { sx?: Record<string, unknown> } | undefined, frame: { sx?: Record<string, unknown> } | undefined): number {\n const own = aspectOf(image?.sx?.['aspectRatio']) ?? aspectOf(frame?.sx?.['aspectRatio'])\n if (own !== null) return own\n const sx = frame?.sx\n if (sx?.['position'] === 'absolute' && sx['width'] === '100%' && sx['height'] === '100%') return COVER_ASPECT\n return 1\n}\n\n/** A slot's role: the first section's picture is the hero; people make an about picture. */\nexport function aiLayoutPictureRole(input: {\n sectionIndex: number\n sectionName: string\n alt: string\n}): AiLayoutPictureRole {\n if (input.sectionIndex === 0) return 'hero'\n if (PEOPLE.test(input.sectionName) || PEOPLE.test(input.alt)) return 'about'\n return 'gallery'\n}\n\n/**\n * Every empty picture slot of a stored page, walked from its root in\n * document order. A slot that already has a source — one the owner placed, or\n * an earlier pass filled — is not a slot.\n */\nexport function aiLayoutPictureSlots(\n nodes: NodesMap,\n rootId: string,\n sections: { ids: readonly string[]; names: readonly string[] },\n): AiLayoutPictureSlot[] {\n const map = nodes as unknown as Record<\n string,\n { componentId?: string; props?: Record<string, unknown>; sx?: Record<string, unknown>; nodes?: string[] }\n >\n const slots: AiLayoutPictureSlot[] = []\n const seen = new Set<string>()\n const visit = (id: string, parentId: string | null, sectionIndex: number) => {\n if (seen.has(id)) return\n seen.add(id)\n const node = map[id]\n if (!node) return\n const index = sections.ids.indexOf(id)\n const here = index >= 0 ? index : sectionIndex\n if (node.componentId === 'image') {\n const src = node.props?.['src']\n const alt = typeof node.props?.['alt'] === 'string' ? (node.props['alt'] as string).trim() : ''\n if ((typeof src !== 'string' || !src.trim()) && alt) {\n const frame = parentId ? map[parentId] : undefined\n const iconId =\n frame?.nodes?.find((child) => child !== id && map[child]?.componentId === 'icon') ?? null\n slots.push({\n imageId: id,\n frameId: frame ? parentId : null,\n iconId,\n alt,\n aspect: slotAspect(node, frame),\n sectionIndex: here,\n ...(node.props?.['decorative'] === true ? { standIn: true } : {}),\n role: aiLayoutPictureRole({\n sectionIndex: here,\n sectionName: here >= 0 ? (sections.names[here] ?? '') : '',\n alt,\n }),\n })\n }\n }\n for (const child of node.nodes ?? []) visit(child, id, here)\n }\n visit(rootId, null, -1)\n return slots\n}\n\n/** The starter photos, by name, each with the words that describe it. */\nexport const AI_LAYOUT_STARTER_PHOTOS = DEFAULT_SITE_IMAGES\n\n/**\n * A source's answer for a slot the library answered and nothing in it fits\n * (AGL-3831): the slot keeps its no-photo treatment rather than take a\n * starter. Its source is empty, so a caller reading `src` to fill something\n * that must hold a photo (a product's, a record's) falls back as it would on\n * `null`.\n */\nexport const AI_LAYOUT_NO_PHOTO: Readonly<AiLayoutPicturePhoto> = Object.freeze({ src: '', width: 0, height: 0 })\n\ntype StarterName = keyof typeof AI_LAYOUT_STARTER_PHOTOS\n\n/**\n * The starter photos each role draws from, best first. A hero draws from the\n * wide photos that read as a page's opening, an about picture leads with the\n * owner, and the rest are the gallery's.\n */\nconst STARTER_POOLS: Readonly<Record<AiLayoutPictureRole, readonly StarterName[]>> = {\n hero: ['hero', 'desk', 'craft'],\n about: ['about', 'hero'],\n gallery: ['desk', 'plants', 'craft'],\n}\n\nconst ALL_STARTERS = Object.keys(AI_LAYOUT_STARTER_PHOTOS) as StarterName[]\n\n/** A small stable number from a seed, so the same job always picks the same photos. */\nexport function aiLayoutSeedNumber(seed: string): number {\n let hash = 2166136261\n for (let index = 0; index < seed.length; index += 1) {\n hash ^= seed.charCodeAt(index)\n hash = Math.imul(hash, 16777619)\n }\n return hash >>> 0\n}\n\nfunction rotate<T>(list: readonly T[], by: number): T[] {\n if (!list.length) return []\n const start = by % list.length\n return [...list.slice(start), ...list.slice(0, start)]\n}\n\n/**\n * A starter photo for each slot (AGL-3660). No photo repeats on a page while\n * an unused one remains, and every pool turns by the seed, so sites do not\n * all open with the same photo while one job always picks the same.\n *\n * The slots are served by what they are for, not where they sit: the hero\n * first, then the pictures of people (an about picture leads with the owner),\n * then the rest. The live Juniper Clay start (2026-10-08) had six slots and\n * five starters, and the owner's photo went to a stoneware card before the\n * About section that described the artist asked for it. Only a page with more\n * slots than starters repeats one: the photo placed longest ago, never the\n * one in a slot beside it. The owner's portrait goes only to a picture of\n * people; a gallery picture never takes it, even to avoid a repeat.\n */\nexport function aiLayoutStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): AiLayoutPicturePhoto[] {\n const turn = aiLayoutSeedNumber(seed)\n const order: AiLayoutPictureRole[] = ['hero', 'about', 'gallery']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const names: Array<StarterName | undefined> = slots.map(() => undefined)\n /** When each starter was last placed, by its turn in the serving order. */\n const placedAt = new Map<StarterName, number>()\n served.forEach(({ slot, index }, step) => {\n // An about picture leads with the owner on every site; the others turn.\n const pool = slot.role === 'about' ? [...STARTER_POOLS.about] : rotate(STARTER_POOLS[slot.role], turn)\n const rest = rotate(\n ALL_STARTERS.filter((name) => !pool.includes(name)),\n turn,\n )\n // The owner's portrait is a picture of a person, and only a picture of\n // people takes it: never a vase in a gallery (AGL-3660). A portrait of\n // one of the business's people takes only people, repeated before a\n // workbench stands in for a teacher (AGL-3856).\n const candidates = slot.people\n ? [...STARTER_POOLS.about]\n : [...pool, ...rest].filter((name) => slot.role === 'about' || name !== 'about')\n const unused = candidates.find((candidate) => !placedAt.has(candidate))\n const beside = new Set([names[index - 1], names[index + 1]].filter(Boolean))\n const name =\n unused ??\n [...candidates]\n .sort((a, b) => (placedAt.get(a) ?? -1) - (placedAt.get(b) ?? -1))\n .find((candidate) => !beside.has(candidate)) ??\n candidates[0]\n names[index] = name\n placedAt.set(name, step)\n })\n return names.map((name) => {\n const photo = AI_LAYOUT_STARTER_PHOTOS[name as StarterName]\n return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n}\n\n/**\n * The page with each slot filled by its photo: the `image` given its source,\n * its intrinsic size and how it loads (the hero eagerly, as the starter home\n * page loads its own), and the placeholder icon taken out of the frame. The\n * alt text is kept as the model wrote it unless the photo brings its own\n * words. A slot given no photo is left as the compiler wrote it.\n */\nexport function aiLayoutPlacePictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n): NodesMap {\n const next = { ...(nodes as unknown as Record<string, Record<string, unknown>>) }\n // Only the page's first picture loads eagerly (rule 16, AGL-3660): a hero\n // of several photos loaded them all eagerly, and a live Portfolio page was\n // refused for it (2026-10-10). The slots are in document order; every later\n // picture leaves `loading` unset, which the renderer loads when reached.\n const lead = slots.findIndex((_, index) => Boolean(photos[index] && next[slots[index].imageId]))\n slots.forEach((slot, index) => {\n const photo = photos[index]\n const node = next[slot.imageId]\n if (!photo || !node) return\n const props = { ...((node['props'] as Record<string, unknown> | undefined) ?? {}) }\n delete props['loading']\n // A photo placed is described by its words, never hidden from a screen\n // reader: a stand-in was decorative only while it showed nothing (AGL-3856).\n if (photo.alt?.trim()) delete props['decorative']\n next[slot.imageId] = {\n ...node,\n props: {\n ...props,\n src: photo.src,\n intrinsicWidth: photo.width,\n intrinsicHeight: photo.height,\n ...(photo.alt?.trim() ? { alt: photo.alt.trim() } : {}),\n ...(index === lead && slot.role === 'hero' ? { loading: 'eager' } : {}),\n },\n }\n if (slot.iconId && slot.frameId && next[slot.frameId]) {\n const frame = next[slot.frameId]\n next[slot.frameId] = {\n ...frame,\n nodes: ((frame['nodes'] as string[] | undefined) ?? []).filter((child) => child !== slot.iconId),\n }\n delete next[slot.iconId]\n }\n })\n return next as unknown as NodesMap\n}\n\n/** Whether a node is a grid row: a `muiGrid` container. */\nconst isGridRow = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && (node['props'] as Record<string, unknown> | undefined)?.['container'] === true\n\n/** Whether a node is a column of a grid row: a sized `muiGrid` item. */\nconst isGridColumn = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && !isGridRow(node)\n\n/**\n * The page without the pictures left with no photo (AGL-3856). A slot whose\n * photo is missing loses its image, its icon and its frame where the frame\n * holds nothing else, and so does every wrapper that holds nothing once it\n * is gone, up to its section. A cover's black scrim over nothing read as a\n * solid black block (the beta.261 Synchronicity Gym hero), and an empty\n * rounded gray card as a broken picture (the beta.262 Adult Hybrid Learning\n * Center hero). A column of a split row taken out leaves the words' column\n * the row's full width. A cover's section stays pinned to the site's dark\n * scheme, which paints that scheme's background and text colors, so the\n * words written for a dark photo stay readable. A slot a photo fills, a\n * starter's included, is kept.\n */\nexport function aiLayoutDropEmptyPictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n sectionIds: readonly string[] = [],\n): NodesMap {\n const map = nodes as unknown as Record<string, Record<string, unknown>>\n const empty = slots.filter((slot, index) => {\n if (photos[index]?.src) return false\n const src = (map[slot.imageId]?.['props'] as Record<string, unknown> | undefined)?.['src']\n return Boolean(map[slot.imageId]) && !(typeof src === 'string' && src.trim())\n })\n if (!empty.length) return nodes\n const next = { ...map }\n const children = (id: string) => (next[id]?.['nodes'] as string[] | undefined) ?? []\n const parentOf = (id: string) => Object.keys(next).find((key) => children(key).includes(id)) ?? null\n const remove = (id: string) => {\n const parent = parentOf(id)\n if (parent) next[parent] = { ...next[parent], nodes: children(parent).filter((child) => child !== id) }\n delete next[id]\n return parent\n }\n const sections = new Set(sectionIds)\n for (const slot of empty) {\n const own = new Set([slot.imageId, slot.iconId].filter(Boolean) as string[])\n const frameHoldsOnly = Boolean(slot.frameId && next[slot.frameId] && children(slot.frameId).every((child) => own.has(child)))\n let parent = remove(slot.imageId)\n if (slot.iconId && next[slot.iconId]) remove(slot.iconId)\n if (!frameHoldsOnly) continue\n // The frame, then each wrapper it leaves empty: never a section, nor the page's root.\n let at: string | null = slot.frameId\n while (at && next[at] && !children(at).length && !sections.has(at) && parentOf(at) !== null) {\n const column = isGridColumn(next[at])\n parent = remove(at)\n at = column ? null : parent\n }\n // A split row left with one column gives its words the row's width.\n if (parent && isGridRow(next[parent])) {\n const [only, ...rest] = children(parent)\n if (only && !rest.length && isGridColumn(next[only])) {\n const column = next[only] as Record<string, unknown>\n const sx = { ...((column['sx'] as Record<string, unknown> | undefined) ?? {}) }\n delete sx['order']\n next[only] = {\n ...column,\n props: { ...((column['props'] as Record<string, unknown> | undefined) ?? {}), size: 'xs:12' },\n sx: Object.keys(sx).length ? sx : null,\n }\n }\n }\n }\n return next as unknown as NodesMap\n}\n\n/**\n * A source of photos found for the slots themselves — a stock search copied\n * into the site's library — answering per slot, in order: a photo; or\n * {@link AI_LAYOUT_NO_PHOTO} where the library answered and nothing fits,\n * which leaves the slot without a photo; or `null` where the library could\n * not be asked, which takes a starter photo. It may throw, and then every\n * slot takes a starter.\n */\nexport type AiLayoutPictureSource = (\n slots: readonly AiLayoutPictureSlot[],\n) => Promise<ReadonlyArray<AiLayoutPicturePhoto | null>>\n\n/**\n * Fills a stored page's empty picture slots (AGL-3660): from `source` where\n * one is given and it answers; a slot it answers with nothing fitting stays\n * without a photo and is taken out ({@link aiLayoutDropEmptyPictures}); every\n * other slot takes a starter photo, described by its own words (AGL-3831). Never throws, and never leaves a page worse than it\n * came: a picture is decoration, and a job never fails over one.\n */\nexport async function aiResolveLayoutPictures(\n nodes: NodesMap,\n input: {\n rootId: string\n sectionIds: readonly string[]\n sectionNames: readonly string[]\n /** A per-job word, so two sites do not open with the same photo. */\n seed: string\n source?: AiLayoutPictureSource | null\n /** The site's language (AGL-3750); a starter photo's English words are not its own. */\n language?: string\n },\n): Promise<NodesMap> {\n try {\n const slots = aiLayoutPictureSlots(nodes, input.rootId, {\n ids: input.sectionIds,\n names: input.sectionNames,\n })\n if (!slots.length) return nodes\n let found: ReadonlyArray<AiLayoutPicturePhoto | null> = []\n if (input.source) {\n try {\n found = await input.source(slots)\n } catch (error) {\n console.warn('ai layout pictures: the stock source failed; starter photos fill the page', { error })\n found = []\n }\n }\n // The starters' words are English; a site in another language says it\n // shows an illustrative photo, true of any photo, as a stock photo's does.\n const otherLanguage = aiLayoutSpeaksOtherThanEnglish(input.language)\n const starters = aiLayoutStarterPhotos(slots, input.seed).map((photo) =>\n otherLanguage ? { ...photo, alt: aiLayoutLabel('illustrativePhoto', input.language) } : photo,\n )\n const photos = slots.map((_slot, index) => {\n const answer = found[index]\n if (answer) return answer.src ? answer : null\n return starters[index] ?? null\n })\n return aiLayoutDropEmptyPictures(aiLayoutPlacePictures(nodes, slots, photos), slots, photos, input.sectionIds)\n } catch (error) {\n console.error('ai layout pictures: the page keeps its empty picture slots', { error })\n return nodes\n }\n}\n"],"names":["DEFAULT_SITE_IMAGES","aiLayoutLabel","aiLayoutSpeaksOtherThanEnglish","PEOPLE","aspectOf","value","byBreakpoint","breakpoint","ratio","match","exec","String","Number","isFinite","COVER_ASPECT","slotAspect","image","frame","own","sx","aiLayoutPictureRole","input","sectionIndex","test","sectionName","alt","aiLayoutPictureSlots","nodes","rootId","sections","map","slots","seen","Set","visit","id","parentId","node","has","add","index","ids","indexOf","here","componentId","src","props","trim","undefined","iconId","find","child","push","imageId","frameId","aspect","standIn","role","names","AI_LAYOUT_STARTER_PHOTOS","AI_LAYOUT_NO_PHOTO","Object","freeze","width","height","STARTER_POOLS","hero","about","gallery","ALL_STARTERS","keys","aiLayoutSeedNumber","seed","hash","length","charCodeAt","Math","imul","rotate","list","by","start","slice","aiLayoutStarterPhotos","turn","order","served","slot","sort","a","b","placedAt","Map","forEach","step","unused","pool","rest","filter","name","includes","candidates","people","candidate","beside","Boolean","get","set","photo","aiLayoutPlacePictures","photos","next","lead","findIndex","_","intrinsicWidth","intrinsicHeight","loading","isGridRow","isGridColumn","aiLayoutDropEmptyPictures","sectionIds","empty","children","parentOf","key","remove","parent","frameHoldsOnly","every","at","column","only","size","aiResolveLayoutPictures","sectionNames","found","source","error","console","warn","otherLanguage","language","starters","_slot","answer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-pictures.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DEFAULT_SITE_IMAGES } from '@aglyn/aglyn/app-utils/default-site'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiLayoutLabel, aiLayoutSpeaksOtherThanEnglish } from './ai-layout-labels'\n\n/**\n * THE PICTURES OF A LANGUAGE PAGE (AGL-3660).\n *\n * The compiler writes a picture slot as a frame holding an icon and an\n * `image` with alt text and no source (`ai-layout-compiler.ts`), and stays\n * pure: it never looks a photo up. This module is what runs after it, on the\n * stored page — it finds each empty slot, decides what kind of picture the\n * slot is, and fills it with a photo the site can serve from its own origin.\n *\n * Two sources fill a slot, in order:\n *\n * 1. A stock photo searched for by the slot's own description and copied into\n * the site's media library (`jobs/ai-layout-stock-photos.ts`), when the\n * deployment has a stock-photo key.\n * 2. Where the library cannot be asked at all — no stock-photo key, the\n * library down, refusing or out of time before it answered — one of the\n * STARTER PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`,\n * served at `/_static/starter/` by the tenant and the console alike,\n * credited in the CREDITS file beside them). They are the platform's own\n * files, so a visitor's browser asks no other host for them, and a\n * starter reads better than no picture when there is no library.\n *\n * Where the library answered and nothing in it fits a slot\n * ({@link AI_LAYOUT_NO_PHOTO}), the slot never takes a starter: the beta.257\n * Copper Kettle Catering start (2026-10-15) opened a barbecue caterer's home\n * page on the starters' pottery workbench (AGL-3831). Nor does it ship empty\n * (AGL-3856): its picture is taken out, frame and all\n * ({@link aiLayoutDropEmptyPictures}). A cover's frame is a black scrim laid\n * over the whole section, and with nothing in it the beta.261 Synchronicity\n * Gym hero read as a solid black block; a split's picture card is a rounded\n * gray box, and the beta.262 Adult Hybrid Learning Center hero read as\n * broken. Without them the section shows its own band, a cover's section\n * still pinned to the site's dark scheme so the words written for a dark\n * photo stay readable, and a split's words take the row's width.\n *\n * A slot keeps the alt text the model wrote only when it describes the photo\n * placed: a stock photo that is not what it says is described by what it is\n * (`aiStockPhotoAlt`), and a starter photo always by its own words, since\n * the model never wrote for it (AGL-3831). A stand-in the compiler drew where\n * the model described no picture is decorative while empty; the photo placed\n * there is described by its own words and is no longer decorative (AGL-3856).\n */\n\n/** What a picture slot is for, read from where it sits and what it says. */\nexport type AiLayoutPictureRole = 'hero' | 'about' | 'gallery'\n\n/** One empty picture slot of a stored page, in document order. */\nexport interface AiLayoutPictureSlot {\n /** The `image` node. */\n imageId: string\n /** The frame around it, which sets the slot's shape. */\n frameId: string | null\n /** The placeholder icon sharing the frame, taken out once a photo fills it. */\n iconId: string | null\n alt: string\n /** The frame's shape as width over height; 1 when it names none. */\n aspect: number\n /** The plan index of the section it sits in, or -1. */\n sectionIndex: number\n role: AiLayoutPictureRole\n /**\n * A product's photo (AGL-3676): what it is of, most specific first — its\n * name, then what its photo should show — searched for with the shop's\n * category and held to it, so a wick trimmer is never a laptop on a desk.\n */\n product?: { subjects: readonly string[] }\n /**\n * A stand-in the compiler drew where the model described no picture\n * (AGL-3856): its words are what a search looks for, never what a photo\n * shows, so a photo placed there is described by its own, and is no longer\n * decorative once it is.\n */\n standIn?: boolean\n /**\n * A portrait of one of the business's people (AGL-3856): a record of a\n * team, staff or instructors list. Its photo shows an adult, in the role\n * its words or its list name where one is listed, never a child or a thing.\n */\n people?: boolean\n /**\n * A dataset record's cover (AGL-3856): what the record is filed under — the\n * value of its list's category-like field (\"Weddings and elopements\") —\n * read after its own name for what its photo is OF. A photographer's record\n * is the photographed moment, never the photographer or the camera.\n */\n record?: { category?: string }\n}\n\n/** A photo a slot is filled with. */\nexport interface AiLayoutPicturePhoto {\n /** The site-served address: a starter path or the media library's own. */\n src: string\n width: number\n height: number\n /**\n * What the photo shows, where the slot's own alt text does not say it: the\n * slot takes it in place of the words the model wrote before the photo was\n * found. Absent, the slot's words describe it.\n */\n alt?: string\n}\n\n/** Words that say a picture shows people, or a section is about them. */\nconst PEOPLE =\n /\\b(team|story|owner|owners|founder|founders|staff|crew|people|person|portrait|meet|family|families|who we are|our (?:team|crew|people|story)|therapist|counsel(?:l)?or|coach|doctor|dentist|stylist|trainer|smil(?:e|es|ing)|headshot)\\b/i\n\n/**\n * \"About\" as what a section or a picture is (AGL-3923): leading its words\n * (\"About us\", \"About the studio\"), never the preposition inside them. The\n * beta.267 Brightvale closing band, \"Ready to talk about your marketing?\",\n * was read as a picture of people and searched \"ready talk marketing\" among\n * portraits, which answered a tin-can telephone.\n */\nconst ABOUT = /^\\W*about\\b/i\n\n/**\n * An `aspectRatio` (`'16 / 9'`) as a number, or `null` where none is named.\n * A responsive one (`{ xs: '4 / 3', md: '4 / 5' }`, as a designed page's\n * pictures carry, AGL-3660) reads at the desktop breakpoint first: one photo\n * serves every width, and the wide screen is where its shape shows most.\n */\nfunction aspectOf(value: unknown): number | null {\n if (value && typeof value === 'object') {\n const byBreakpoint = value as Record<string, unknown>\n for (const breakpoint of ['md', 'lg', 'xl', 'sm', 'xs']) {\n const ratio = aspectOf(byBreakpoint[breakpoint])\n if (ratio !== null) return ratio\n }\n return null\n }\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*\\/\\s*(\\d+(?:\\.\\d+)?)\\s*$/.exec(String(value ?? ''))\n if (!match) return null\n const ratio = Number(match[1]) / Number(match[2])\n return Number.isFinite(ratio) && ratio > 0 ? ratio : null\n}\n\n/** The shape a full-bleed cover's photo is searched for: it fills a wide band. */\nconst COVER_ASPECT = 16 / 9\n\n/**\n * A slot's shape (AGL-3660): the image's own `aspectRatio` (a designed\n * picture card sets it on the image), else its frame's, else — for a frame\n * laid absolutely over its whole section, a designed photo cover — a wide\n * band's. 1 where nothing says.\n */\nfunction slotAspect(image: { sx?: Record<string, unknown> } | undefined, frame: { sx?: Record<string, unknown> } | undefined): number {\n const own = aspectOf(image?.sx?.['aspectRatio']) ?? aspectOf(frame?.sx?.['aspectRatio'])\n if (own !== null) return own\n const sx = frame?.sx\n if (sx?.['position'] === 'absolute' && sx['width'] === '100%' && sx['height'] === '100%') return COVER_ASPECT\n return 1\n}\n\n/** A slot's role: the first section's picture is the hero; people make an about picture. */\nexport function aiLayoutPictureRole(input: {\n sectionIndex: number\n sectionName: string\n alt: string\n}): AiLayoutPictureRole {\n if (input.sectionIndex === 0) return 'hero'\n if (PEOPLE.test(input.sectionName) || PEOPLE.test(input.alt) || ABOUT.test(input.sectionName) || ABOUT.test(input.alt)) return 'about'\n return 'gallery'\n}\n\n/**\n * Every empty picture slot of a stored page, walked from its root in\n * document order. A slot that already has a source — one the owner placed, or\n * an earlier pass filled — is not a slot.\n */\nexport function aiLayoutPictureSlots(\n nodes: NodesMap,\n rootId: string,\n sections: { ids: readonly string[]; names: readonly string[] },\n): AiLayoutPictureSlot[] {\n const map = nodes as unknown as Record<\n string,\n { componentId?: string; props?: Record<string, unknown>; sx?: Record<string, unknown>; nodes?: string[] }\n >\n const slots: AiLayoutPictureSlot[] = []\n const seen = new Set<string>()\n const visit = (id: string, parentId: string | null, sectionIndex: number) => {\n if (seen.has(id)) return\n seen.add(id)\n const node = map[id]\n if (!node) return\n const index = sections.ids.indexOf(id)\n const here = index >= 0 ? index : sectionIndex\n if (node.componentId === 'image') {\n const src = node.props?.['src']\n const alt = typeof node.props?.['alt'] === 'string' ? (node.props['alt'] as string).trim() : ''\n if ((typeof src !== 'string' || !src.trim()) && alt) {\n const frame = parentId ? map[parentId] : undefined\n const iconId =\n frame?.nodes?.find((child) => child !== id && map[child]?.componentId === 'icon') ?? null\n slots.push({\n imageId: id,\n frameId: frame ? parentId : null,\n iconId,\n alt,\n aspect: slotAspect(node, frame),\n sectionIndex: here,\n ...(node.props?.['decorative'] === true ? { standIn: true } : {}),\n role: aiLayoutPictureRole({\n sectionIndex: here,\n sectionName: here >= 0 ? (sections.names[here] ?? '') : '',\n alt,\n }),\n })\n }\n }\n for (const child of node.nodes ?? []) visit(child, id, here)\n }\n visit(rootId, null, -1)\n return slots\n}\n\n/** The starter photos, by name, each with the words that describe it. */\nexport const AI_LAYOUT_STARTER_PHOTOS = DEFAULT_SITE_IMAGES\n\n/**\n * A source's answer for a slot the library answered and nothing in it fits\n * (AGL-3831): the slot keeps its no-photo treatment rather than take a\n * starter. Its source is empty, so a caller reading `src` to fill something\n * that must hold a photo (a product's, a record's) falls back as it would on\n * `null`.\n */\nexport const AI_LAYOUT_NO_PHOTO: Readonly<AiLayoutPicturePhoto> = Object.freeze({ src: '', width: 0, height: 0 })\n\ntype StarterName = keyof typeof AI_LAYOUT_STARTER_PHOTOS\n\n/**\n * The starter photos each role draws from, best first. A hero draws from the\n * wide photos that read as a page's opening, an about picture leads with the\n * owner, and the rest are the gallery's.\n */\nconst STARTER_POOLS: Readonly<Record<AiLayoutPictureRole, readonly StarterName[]>> = {\n hero: ['hero', 'desk', 'craft'],\n about: ['about', 'hero'],\n gallery: ['desk', 'plants', 'craft'],\n}\n\nconst ALL_STARTERS = Object.keys(AI_LAYOUT_STARTER_PHOTOS) as StarterName[]\n\n/** A small stable number from a seed, so the same job always picks the same photos. */\nexport function aiLayoutSeedNumber(seed: string): number {\n let hash = 2166136261\n for (let index = 0; index < seed.length; index += 1) {\n hash ^= seed.charCodeAt(index)\n hash = Math.imul(hash, 16777619)\n }\n return hash >>> 0\n}\n\nfunction rotate<T>(list: readonly T[], by: number): T[] {\n if (!list.length) return []\n const start = by % list.length\n return [...list.slice(start), ...list.slice(0, start)]\n}\n\n/**\n * A starter photo for each slot (AGL-3660). No photo repeats on a page while\n * an unused one remains, and every pool turns by the seed, so sites do not\n * all open with the same photo while one job always picks the same.\n *\n * The slots are served by what they are for, not where they sit: the hero\n * first, then the pictures of people (an about picture leads with the owner),\n * then the rest. The live Juniper Clay start (2026-10-08) had six slots and\n * five starters, and the owner's photo went to a stoneware card before the\n * About section that described the artist asked for it. Only a page with more\n * slots than starters repeats one: the photo placed longest ago, never the\n * one in a slot beside it. The owner's portrait goes only to a picture of\n * people; a gallery picture never takes it, even to avoid a repeat.\n */\nexport function aiLayoutStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): AiLayoutPicturePhoto[] {\n const turn = aiLayoutSeedNumber(seed)\n const order: AiLayoutPictureRole[] = ['hero', 'about', 'gallery']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const names: Array<StarterName | undefined> = slots.map(() => undefined)\n /** When each starter was last placed, by its turn in the serving order. */\n const placedAt = new Map<StarterName, number>()\n served.forEach(({ slot, index }, step) => {\n // An about picture leads with the owner on every site; the others turn.\n const pool = slot.role === 'about' ? [...STARTER_POOLS.about] : rotate(STARTER_POOLS[slot.role], turn)\n const rest = rotate(\n ALL_STARTERS.filter((name) => !pool.includes(name)),\n turn,\n )\n // The owner's portrait is a picture of a person, and only a picture of\n // people takes it: never a vase in a gallery (AGL-3660). A portrait of\n // one of the business's people takes only people, repeated before a\n // workbench stands in for a teacher (AGL-3856).\n const candidates = slot.people\n ? [...STARTER_POOLS.about]\n : [...pool, ...rest].filter((name) => slot.role === 'about' || name !== 'about')\n const unused = candidates.find((candidate) => !placedAt.has(candidate))\n const beside = new Set([names[index - 1], names[index + 1]].filter(Boolean))\n const name =\n unused ??\n [...candidates]\n .sort((a, b) => (placedAt.get(a) ?? -1) - (placedAt.get(b) ?? -1))\n .find((candidate) => !beside.has(candidate)) ??\n candidates[0]\n names[index] = name\n placedAt.set(name, step)\n })\n return names.map((name) => {\n const photo = AI_LAYOUT_STARTER_PHOTOS[name as StarterName]\n return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n}\n\n/**\n * The starters that show no one's business (AGL-3923): a team at a table in a\n * bright office, a tidy desk. Never the workbench, the coffee shop's owner or\n * the houseplants, which read as another business's own photos (AGL-3831).\n */\nconst NEUTRAL_STARTERS: readonly StarterName[] = ['hero', 'desk']\n\n/**\n * A neutral starter for each slot a library could not fill, where the site\n * has a library (AGL-3923): each at most once on a page, the hero served\n * first and each pool turned by the seed, and none for a portrait of the\n * business's people or a product, nor for a slot past the two. A slot given\n * none keeps the no-photo treatment.\n */\nexport function aiLayoutNeutralStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): Array<AiLayoutPicturePhoto | null> {\n const pool = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(seed))\n const order: AiLayoutPictureRole[] = ['hero', 'gallery', 'about']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot }) => !slot.people && !slot.product)\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n served.forEach(({ index }, step) => {\n const name = pool[step]\n if (!name) return\n const photo = AI_LAYOUT_STARTER_PHOTOS[name]\n photos[index] = { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n return photos\n}\n\n/**\n * The page with each slot filled by its photo: the `image` given its source,\n * its intrinsic size and how it loads (the hero eagerly, as the starter home\n * page loads its own), and the placeholder icon taken out of the frame. The\n * alt text is kept as the model wrote it unless the photo brings its own\n * words. A slot given no photo is left as the compiler wrote it.\n */\nexport function aiLayoutPlacePictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n): NodesMap {\n const next = { ...(nodes as unknown as Record<string, Record<string, unknown>>) }\n // Only the page's first picture loads eagerly (rule 16, AGL-3660): a hero\n // of several photos loaded them all eagerly, and a live Portfolio page was\n // refused for it (2026-10-10). The slots are in document order; every later\n // picture leaves `loading` unset, which the renderer loads when reached.\n const lead = slots.findIndex((_, index) => Boolean(photos[index] && next[slots[index].imageId]))\n slots.forEach((slot, index) => {\n const photo = photos[index]\n const node = next[slot.imageId]\n if (!photo || !node) return\n const props = { ...((node['props'] as Record<string, unknown> | undefined) ?? {}) }\n delete props['loading']\n // A photo placed is described by its words, never hidden from a screen\n // reader: a stand-in was decorative only while it showed nothing (AGL-3856).\n if (photo.alt?.trim()) delete props['decorative']\n next[slot.imageId] = {\n ...node,\n props: {\n ...props,\n src: photo.src,\n intrinsicWidth: photo.width,\n intrinsicHeight: photo.height,\n ...(photo.alt?.trim() ? { alt: photo.alt.trim() } : {}),\n ...(index === lead && slot.role === 'hero' ? { loading: 'eager' } : {}),\n },\n }\n if (slot.iconId && slot.frameId && next[slot.frameId]) {\n const frame = next[slot.frameId]\n next[slot.frameId] = {\n ...frame,\n nodes: ((frame['nodes'] as string[] | undefined) ?? []).filter((child) => child !== slot.iconId),\n }\n delete next[slot.iconId]\n }\n })\n return next as unknown as NodesMap\n}\n\n/** Whether a node is a grid row: a `muiGrid` container. */\nconst isGridRow = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && (node['props'] as Record<string, unknown> | undefined)?.['container'] === true\n\n/** Whether a node is a column of a grid row: a sized `muiGrid` item. */\nconst isGridColumn = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && !isGridRow(node)\n\n/**\n * The page without the pictures left with no photo (AGL-3856). A slot whose\n * photo is missing loses its image, its icon and its frame where the frame\n * holds nothing else, and so does every wrapper that holds nothing once it\n * is gone, up to its section. A cover's black scrim over nothing read as a\n * solid black block (the beta.261 Synchronicity Gym hero), and an empty\n * rounded gray card as a broken picture (the beta.262 Adult Hybrid Learning\n * Center hero). A column of a split row taken out leaves the words' column\n * the row's full width. A cover's section stays pinned to the site's dark\n * scheme, which paints that scheme's background and text colors, so the\n * words written for a dark photo stay readable. A slot a photo fills, a\n * starter's included, is kept.\n */\nexport function aiLayoutDropEmptyPictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n sectionIds: readonly string[] = [],\n): NodesMap {\n const map = nodes as unknown as Record<string, Record<string, unknown>>\n const empty = slots.filter((slot, index) => {\n if (photos[index]?.src) return false\n const src = (map[slot.imageId]?.['props'] as Record<string, unknown> | undefined)?.['src']\n return Boolean(map[slot.imageId]) && !(typeof src === 'string' && src.trim())\n })\n if (!empty.length) return nodes\n const next = { ...map }\n const children = (id: string) => (next[id]?.['nodes'] as string[] | undefined) ?? []\n const parentOf = (id: string) => Object.keys(next).find((key) => children(key).includes(id)) ?? null\n const remove = (id: string) => {\n const parent = parentOf(id)\n if (parent) next[parent] = { ...next[parent], nodes: children(parent).filter((child) => child !== id) }\n delete next[id]\n return parent\n }\n const sections = new Set(sectionIds)\n for (const slot of empty) {\n const own = new Set([slot.imageId, slot.iconId].filter(Boolean) as string[])\n const frameHoldsOnly = Boolean(slot.frameId && next[slot.frameId] && children(slot.frameId).every((child) => own.has(child)))\n let parent = remove(slot.imageId)\n if (slot.iconId && next[slot.iconId]) remove(slot.iconId)\n if (!frameHoldsOnly) continue\n // The frame, then each wrapper it leaves empty: never a section, nor the page's root.\n let at: string | null = slot.frameId\n while (at && next[at] && !children(at).length && !sections.has(at) && parentOf(at) !== null) {\n const column = isGridColumn(next[at])\n parent = remove(at)\n at = column ? null : parent\n }\n // A split row left with one column gives its words the row's width.\n if (parent && isGridRow(next[parent])) {\n const [only, ...rest] = children(parent)\n if (only && !rest.length && isGridColumn(next[only])) {\n const column = next[only] as Record<string, unknown>\n const sx = { ...((column['sx'] as Record<string, unknown> | undefined) ?? {}) }\n delete sx['order']\n next[only] = {\n ...column,\n props: { ...((column['props'] as Record<string, unknown> | undefined) ?? {}), size: 'xs:12' },\n sx: Object.keys(sx).length ? sx : null,\n }\n }\n }\n }\n return next as unknown as NodesMap\n}\n\n/**\n * A source of photos found for the slots themselves — a stock search copied\n * into the site's library — answering per slot, in order: a photo; or\n * {@link AI_LAYOUT_NO_PHOTO} where the library answered and nothing fits,\n * which leaves the slot without a photo; or `null` where the library could\n * not be asked, which takes a starter photo. It may throw, and then every\n * slot takes a starter.\n */\nexport type AiLayoutPictureSource = (\n slots: readonly AiLayoutPictureSlot[],\n) => Promise<ReadonlyArray<AiLayoutPicturePhoto | null>>\n\n/**\n * Fills a stored page's empty picture slots (AGL-3660): from `source` where\n * one is given and it answers; a slot it answers with nothing fitting stays\n * without a photo and is taken out ({@link aiLayoutDropEmptyPictures}); every\n * other slot takes a starter photo, described by its own words (AGL-3831). Never throws, and never leaves a page worse than it\n * came: a picture is decoration, and a job never fails over one.\n */\nexport async function aiResolveLayoutPictures(\n nodes: NodesMap,\n input: {\n rootId: string\n sectionIds: readonly string[]\n sectionNames: readonly string[]\n /** A per-job word, so two sites do not open with the same photo. */\n seed: string\n source?: AiLayoutPictureSource | null\n /** The site's language (AGL-3750); a starter photo's English words are not its own. */\n language?: string\n },\n): Promise<NodesMap> {\n try {\n const slots = aiLayoutPictureSlots(nodes, input.rootId, {\n ids: input.sectionIds,\n names: input.sectionNames,\n })\n if (!slots.length) return nodes\n let found: ReadonlyArray<AiLayoutPicturePhoto | null> = []\n if (input.source) {\n try {\n found = await input.source(slots)\n } catch (error) {\n console.warn('ai layout pictures: the stock source failed; starter photos fill the page', { error })\n found = []\n }\n }\n // The starters' words are English; a site in another language says it\n // shows an illustrative photo, true of any photo, as a stock photo's does.\n const otherLanguage = aiLayoutSpeaksOtherThanEnglish(input.language)\n const said = (photo: AiLayoutPicturePhoto): AiLayoutPicturePhoto =>\n otherLanguage ? { ...photo, alt: aiLayoutLabel('illustrativePhoto', input.language) } : photo\n // With a library, a starter is only ever a neutral one (AGL-3923): the\n // beta.267 Brightvale start, whose library refused its copies, showed a\n // pottery bench and a coffee shop's owner as a marketer's pictures.\n const starters = (input.source ? aiLayoutNeutralStarterPhotos(slots, input.seed) : aiLayoutStarterPhotos(slots, input.seed)).map(\n (photo) => (photo ? said(photo) : null),\n )\n const photos = slots.map((_slot, index) => {\n const answer = found[index]\n if (answer) return answer.src ? answer : null\n return starters[index] ?? null\n })\n // The page floor (AGL-3923; Zach, 2026-10-12: a starter photo before no\n // photo at all): a page the library answered that would show no picture\n // keeps one neutral starter, in its hero, else its first slot.\n if (input.source && !photos.some((photo) => photo?.src)) {\n const at = Math.max(0, slots.findIndex((slot) => slot.role === 'hero'))\n const [floor] = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(input.seed))\n const photo = floor ? AI_LAYOUT_STARTER_PHOTOS[floor] : null\n if (photo) photos[at] = said({ src: photo.src, width: photo.width, height: photo.height, alt: photo.alt })\n }\n return aiLayoutDropEmptyPictures(aiLayoutPlacePictures(nodes, slots, photos), slots, photos, input.sectionIds)\n } catch (error) {\n console.error('ai layout pictures: the page keeps its empty picture slots', { error })\n return nodes\n }\n}\n"],"names":["DEFAULT_SITE_IMAGES","aiLayoutLabel","aiLayoutSpeaksOtherThanEnglish","PEOPLE","ABOUT","aspectOf","value","byBreakpoint","breakpoint","ratio","match","exec","String","Number","isFinite","COVER_ASPECT","slotAspect","image","frame","own","sx","aiLayoutPictureRole","input","sectionIndex","test","sectionName","alt","aiLayoutPictureSlots","nodes","rootId","sections","map","slots","seen","Set","visit","id","parentId","node","has","add","index","ids","indexOf","here","componentId","src","props","trim","undefined","iconId","find","child","push","imageId","frameId","aspect","standIn","role","names","AI_LAYOUT_STARTER_PHOTOS","AI_LAYOUT_NO_PHOTO","Object","freeze","width","height","STARTER_POOLS","hero","about","gallery","ALL_STARTERS","keys","aiLayoutSeedNumber","seed","hash","length","charCodeAt","Math","imul","rotate","list","by","start","slice","aiLayoutStarterPhotos","turn","order","served","slot","sort","a","b","placedAt","Map","forEach","step","unused","pool","rest","filter","name","includes","candidates","people","candidate","beside","Boolean","get","set","photo","NEUTRAL_STARTERS","aiLayoutNeutralStarterPhotos","product","photos","aiLayoutPlacePictures","next","lead","findIndex","_","intrinsicWidth","intrinsicHeight","loading","isGridRow","isGridColumn","aiLayoutDropEmptyPictures","sectionIds","empty","children","parentOf","key","remove","parent","frameHoldsOnly","every","at","column","only","size","aiResolveLayoutPictures","sectionNames","found","source","error","console","warn","otherLanguage","language","said","starters","_slot","answer","some","max","floor"],"mappings":"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"}
|