@bendyline/squisq 1.5.0 → 1.5.2
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/README.md +14 -1
- package/dist/{ContentContainer-DNx460y8.d.ts → ContentContainer-BXUlIf18.d.ts} +6 -2
- package/dist/{Doc-BsMkmzH8.d.ts → Doc-BBVGq1_9.d.ts} +94 -15
- package/dist/{ImageEditDoc-DvoeHMP9.d.ts → ImageEditDoc-FuyGtt6o.d.ts} +1 -1
- package/dist/{chunk-4ZFRZBU2.js → chunk-5TYS5V3G.js} +866 -192
- package/dist/chunk-5TYS5V3G.js.map +1 -0
- package/dist/{chunk-7MQGNR76.js → chunk-A4O7GIWE.js} +2 -27
- package/dist/chunk-A4O7GIWE.js.map +1 -0
- package/dist/{chunk-OH43RPTP.js → chunk-BEQODJWV.js} +20 -3
- package/dist/chunk-BEQODJWV.js.map +1 -0
- package/dist/chunk-ET53IIEP.js +1 -0
- package/dist/chunk-ET53IIEP.js.map +1 -0
- package/dist/{chunk-KZNXZ5SM.js → chunk-GPLTCSXO.js} +18 -7
- package/dist/chunk-GPLTCSXO.js.map +1 -0
- package/dist/{chunk-OKJOQXK4.js → chunk-HWVFJAAH.js} +38 -2
- package/dist/chunk-HWVFJAAH.js.map +1 -0
- package/dist/{chunk-2TMAWB4Y.js → chunk-IMSCRLLV.js} +1 -1
- package/dist/chunk-IMSCRLLV.js.map +1 -0
- package/dist/{chunk-4AKUQPEE.js → chunk-KKNUBQ6Y.js} +1 -1
- package/dist/chunk-KKNUBQ6Y.js.map +1 -0
- package/dist/{chunk-TVSGLUID.js → chunk-LDQ2HJIX.js} +3 -3
- package/dist/{chunk-RUDYOTA6.js → chunk-LTN6I7QW.js} +56 -1
- package/dist/chunk-LTN6I7QW.js.map +1 -0
- package/dist/{chunk-2OIBZYKP.js → chunk-P3K7NLHF.js} +2 -2
- package/dist/{chunk-K32VJONL.js → chunk-T5UK6YOB.js} +41 -2
- package/dist/chunk-T5UK6YOB.js.map +1 -0
- package/dist/{chunk-FR2RBTKO.js → chunk-TQWLI6S2.js} +93 -27
- package/dist/chunk-TQWLI6S2.js.map +1 -0
- package/dist/{chunk-LRCV7N7F.js → chunk-UY7KGQ5R.js} +2 -2
- package/dist/{chunk-3E5F2XMR.js → chunk-VWUFZ6ZG.js} +29 -2
- package/dist/chunk-VWUFZ6ZG.js.map +1 -0
- package/dist/doc/index.d.ts +4 -4
- package/dist/doc/index.js +24 -9
- package/dist/generate/index.d.ts +2 -80
- package/dist/generate/index.js +2 -7
- package/dist/imageEdit/index.d.ts +4 -4
- package/dist/imageEdit/index.js +2 -2
- package/dist/index.d.ts +8 -8
- package/dist/index.js +41 -26
- package/dist/jsonForm/index.d.ts +42 -1
- package/dist/jsonForm/index.js +9 -1
- package/dist/markdown/index.d.ts +18 -2
- package/dist/markdown/index.js +5 -3
- package/dist/recommend/index.js +2 -2
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +3 -5
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.js +1 -1
- package/dist/story/index.d.ts +151 -13
- package/dist/story/index.js +24 -9
- package/dist/{themeLibrary-DQQKuRMx.d.ts → themeLibrary-CehcJhzz.d.ts} +1 -1
- package/dist/timing/index.js +3 -5
- package/dist/transform/index.d.ts +2 -2
- package/dist/transform/index.js +3 -3
- package/dist/versions/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/contentExtractor.test.ts +60 -0
- package/src/__tests__/coverBlock.test.ts +79 -3
- package/src/__tests__/customTemplateSample.test.ts +63 -2
- package/src/__tests__/customTemplatesFrontmatter.test.ts +31 -0
- package/src/__tests__/customThemesFrontmatter.test.ts +27 -0
- package/src/__tests__/inputDescriptors.test.ts +162 -0
- package/src/__tests__/jsonForm.tokens.test.ts +84 -0
- package/src/__tests__/markdown.test.ts +64 -0
- package/src/__tests__/markdownSanitize.test.ts +15 -0
- package/src/__tests__/markdownToDoc.test.ts +173 -2
- package/src/__tests__/mediaAnnotations.test.ts +27 -0
- package/src/__tests__/shapeGeometry.test.ts +37 -0
- package/src/__tests__/structuredData.test.ts +73 -3
- package/src/__tests__/templateMetadata.test.ts +11 -1
- package/src/__tests__/templates.test.ts +112 -0
- package/src/__tests__/validateDoc.test.ts +160 -0
- package/src/doc/annotationBlocks.ts +117 -0
- package/src/doc/customTemplatesFrontmatter.ts +7 -1
- package/src/doc/customThemesFrontmatter.ts +6 -1
- package/src/doc/docToMarkdown.ts +78 -29
- package/src/doc/getLayers.ts +11 -2
- package/src/doc/index.ts +12 -0
- package/src/doc/markdownToDoc.ts +80 -3
- package/src/doc/mediaAnnotations.ts +15 -33
- package/src/doc/standaloneAnnotation.ts +58 -0
- package/src/doc/structuredData.ts +92 -21
- package/src/doc/templates/__tests__/customTemplate.test.ts +21 -2
- package/src/doc/templates/comparisonBar.ts +8 -1
- package/src/doc/templates/coverBlock.ts +78 -15
- package/src/doc/templates/customTemplate.ts +10 -4
- package/src/doc/templates/diagramBlock.ts +2 -2
- package/src/doc/templates/drawingBlock.ts +2 -2
- package/src/doc/templates/factCard.ts +4 -2
- package/src/doc/templates/imageWithCaption.ts +10 -8
- package/src/doc/templates/index.ts +12 -2
- package/src/doc/templates/inputDescriptors.ts +432 -0
- package/src/doc/templates/photoGrid.ts +16 -39
- package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +267 -1
- package/src/doc/templates/tokens/resolveTokens.ts +0 -0
- package/src/doc/templates/videoWithCaption.ts +15 -14
- package/src/doc/utils/nearest.ts +59 -0
- package/src/doc/utils/shapeGeometry.ts +90 -2
- package/src/doc/validate.ts +153 -54
- package/src/generate/contentExtractor.ts +148 -35
- package/src/generate/index.ts +0 -3
- package/src/jsonForm/index.ts +2 -0
- package/src/jsonForm/tokens.ts +78 -0
- package/src/markdown/annotationCoercion.ts +1 -1
- package/src/markdown/attrTokens.ts +19 -0
- package/src/markdown/convert.ts +11 -1
- package/src/markdown/index.ts +2 -1
- package/src/markdown/sanitize.ts +22 -2
- package/src/markdown/stringify.ts +12 -3
- package/src/markdown/utils.ts +62 -1
- package/src/schemas/CustomTemplates.ts +56 -4
- package/src/schemas/Doc.ts +39 -41
- package/src/schemas/themeCompile.ts +1 -1
- package/src/storage/ContentContainer.ts +6 -2
- package/src/transform/types.ts +1 -1
- package/dist/chunk-2TMAWB4Y.js.map +0 -1
- package/dist/chunk-3E5F2XMR.js.map +0 -1
- package/dist/chunk-4AKUQPEE.js.map +0 -1
- package/dist/chunk-4X3JQXNM.js +0 -35
- package/dist/chunk-4X3JQXNM.js.map +0 -1
- package/dist/chunk-4ZFRZBU2.js.map +0 -1
- package/dist/chunk-7MQGNR76.js.map +0 -1
- package/dist/chunk-FBKP5CAO.js +0 -145
- package/dist/chunk-FBKP5CAO.js.map +0 -1
- package/dist/chunk-FR2RBTKO.js.map +0 -1
- package/dist/chunk-K32VJONL.js.map +0 -1
- package/dist/chunk-KZNXZ5SM.js.map +0 -1
- package/dist/chunk-OH43RPTP.js.map +0 -1
- package/dist/chunk-OKJOQXK4.js.map +0 -1
- package/dist/chunk-RUDYOTA6.js.map +0 -1
- package/src/__tests__/slideshowGenerator.test.ts +0 -128
- package/src/generate/slideshowGenerator.ts +0 -280
- /package/dist/{chunk-TVSGLUID.js.map → chunk-LDQ2HJIX.js.map} +0 -0
- /package/dist/{chunk-2OIBZYKP.js.map → chunk-P3K7NLHF.js.map} +0 -0
- /package/dist/{chunk-LRCV7N7F.js.map → chunk-UY7KGQ5R.js.map} +0 -0
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slideshow Generator
|
|
3
|
-
*
|
|
4
|
-
* Turns plain text + optional images into a complete squisq `Doc`.
|
|
5
|
-
*
|
|
6
|
-
* Pipeline:
|
|
7
|
-
* 1. Strip markdown (if needed)
|
|
8
|
-
* 2. Extract compelling content (stats, dates, quotes, …)
|
|
9
|
-
* 3. Estimate narration timing
|
|
10
|
-
* 4. Map extractions → template blocks
|
|
11
|
-
* 5. Interleave image slides
|
|
12
|
-
* 6. Return a ready-to-render `Doc`
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { Doc } from '../schemas/Doc.js';
|
|
16
|
-
import type {
|
|
17
|
-
TemplateBlock,
|
|
18
|
-
ImageWithCaptionInput,
|
|
19
|
-
TitleBlockInput,
|
|
20
|
-
ColorScheme,
|
|
21
|
-
AccentImage,
|
|
22
|
-
AccentPosition,
|
|
23
|
-
} from '../schemas/BlockTemplates.js';
|
|
24
|
-
import { extractContent, stripMarkdown } from './contentExtractor.js';
|
|
25
|
-
import { mapElementToBlock } from './templateMapper.js';
|
|
26
|
-
import { estimateNarrationDuration } from '../timing/narrationTiming.js';
|
|
27
|
-
import { SeededRandom, hashString } from '../random/SeededRandom.js';
|
|
28
|
-
|
|
29
|
-
// ── Public types ───────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
/** A Doc variant whose blocks are TemplateBlocks (no startTime required). */
|
|
32
|
-
export type SlideshowDoc = Omit<Doc, 'blocks'> & {
|
|
33
|
-
blocks: TemplateBlock[];
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/** Image metadata passed to the generator. */
|
|
37
|
-
export interface SlideshowImage {
|
|
38
|
-
/** Image source path or URL. */
|
|
39
|
-
src: string;
|
|
40
|
-
/** Alt text for accessibility. */
|
|
41
|
-
alt?: string;
|
|
42
|
-
/** Photo credit. */
|
|
43
|
-
credit?: string;
|
|
44
|
-
/** License identifier. */
|
|
45
|
-
license?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Options for `generateSlideshow`. */
|
|
49
|
-
export interface SlideshowOptions {
|
|
50
|
-
/** Deterministic seed (default: hash of the text). */
|
|
51
|
-
seed?: number;
|
|
52
|
-
/** Title slide text. Omit to skip the title slide. */
|
|
53
|
-
title?: string;
|
|
54
|
-
/** Subtitle for the title slide. */
|
|
55
|
-
subtitle?: string;
|
|
56
|
-
/** Explicit total duration in seconds. When omitted, estimated from text. */
|
|
57
|
-
duration?: number;
|
|
58
|
-
/** Target number of content slides (default: 5–8, auto-scaled). */
|
|
59
|
-
targetSlides?: number;
|
|
60
|
-
/** Color schemes to rotate (default: ['blue','green','purple','orange','red']). */
|
|
61
|
-
colorSchemes?: ColorScheme[];
|
|
62
|
-
/** Ambient motions to rotate on images. */
|
|
63
|
-
ambientMotions?: Array<'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight'>;
|
|
64
|
-
/** Accent positions to rotate on text slides. */
|
|
65
|
-
accentPositions?: AccentPosition[];
|
|
66
|
-
/** Minimum slide duration in seconds (default: 4). */
|
|
67
|
-
minSlideDuration?: number;
|
|
68
|
-
/** Maximum slide duration in seconds (default: 15). */
|
|
69
|
-
maxSlideDuration?: number;
|
|
70
|
-
/** Theme identifier to embed in the Doc. */
|
|
71
|
-
themeId?: string;
|
|
72
|
-
/** Whether the input text is markdown (default: false). */
|
|
73
|
-
isMarkdown?: boolean;
|
|
74
|
-
/** Minimum extraction confidence (default: 0.3). */
|
|
75
|
-
minConfidence?: number;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const DEFAULT_COLOR_SCHEMES: ColorScheme[] = ['blue', 'green', 'purple', 'orange', 'red'];
|
|
79
|
-
const DEFAULT_AMBIENT_MOTIONS: Array<'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight'> = [
|
|
80
|
-
'zoomIn',
|
|
81
|
-
'zoomOut',
|
|
82
|
-
'panLeft',
|
|
83
|
-
'panRight',
|
|
84
|
-
];
|
|
85
|
-
const DEFAULT_ACCENT_POSITIONS: AccentPosition[] = ['left-strip', 'right-strip', 'bottom-strip'];
|
|
86
|
-
|
|
87
|
-
// ── Main ───────────────────────────────────────────────────────────
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Generate a slideshow `Doc` from text and optional images.
|
|
91
|
-
*
|
|
92
|
-
* @deprecated Prefer the transform pipeline: convert content with
|
|
93
|
-
* `markdownToDoc` (content-aware auto templates are on by default) and
|
|
94
|
-
* shape it with `applyTransform` from `@bendyline/squisq/transform` —
|
|
95
|
-
* one engine for text→slides, with styles, theme pairing, pacing, and a
|
|
96
|
-
* duration budget (`TransformStyleConfig.budget`). This standalone
|
|
97
|
-
* generator is kept for API stability but no longer gains features.
|
|
98
|
-
*
|
|
99
|
-
* ```ts
|
|
100
|
-
* const doc = generateSlideshow(articleText, myImages, {
|
|
101
|
-
* title: 'Mount Rainier',
|
|
102
|
-
* themeId: 'documentary',
|
|
103
|
-
* });
|
|
104
|
-
* // doc is a complete Doc ready for DocPlayer / LinearDocView
|
|
105
|
-
* ```
|
|
106
|
-
*/
|
|
107
|
-
export function generateSlideshow(
|
|
108
|
-
text: string,
|
|
109
|
-
images: SlideshowImage[] = [],
|
|
110
|
-
options: SlideshowOptions = {},
|
|
111
|
-
): SlideshowDoc {
|
|
112
|
-
const {
|
|
113
|
-
seed,
|
|
114
|
-
title,
|
|
115
|
-
subtitle,
|
|
116
|
-
duration: explicitDuration,
|
|
117
|
-
targetSlides,
|
|
118
|
-
colorSchemes = DEFAULT_COLOR_SCHEMES,
|
|
119
|
-
ambientMotions = DEFAULT_AMBIENT_MOTIONS,
|
|
120
|
-
accentPositions = DEFAULT_ACCENT_POSITIONS,
|
|
121
|
-
minSlideDuration = 4,
|
|
122
|
-
maxSlideDuration = 15,
|
|
123
|
-
themeId,
|
|
124
|
-
isMarkdown = false,
|
|
125
|
-
minConfidence = 0.3,
|
|
126
|
-
} = options;
|
|
127
|
-
|
|
128
|
-
const plainText = isMarkdown ? stripMarkdown(text) : text;
|
|
129
|
-
const rng = new SeededRandom(seed ?? hashString(plainText));
|
|
130
|
-
|
|
131
|
-
// 1. Estimate total duration
|
|
132
|
-
const totalDuration = explicitDuration ?? estimateNarrationDuration(plainText);
|
|
133
|
-
|
|
134
|
-
// 2. Extract compelling content
|
|
135
|
-
const extraction = extractContent(plainText, { minConfidence });
|
|
136
|
-
const allElements = extraction.elements;
|
|
137
|
-
|
|
138
|
-
// 3. Decide slide budget
|
|
139
|
-
const slideBudget = targetSlides ?? computeSlideBudget(totalDuration, allElements.length);
|
|
140
|
-
|
|
141
|
-
// Allocate slots between content and images
|
|
142
|
-
const imageSlotCount = images.length > 0 ? Math.max(1, Math.round(slideBudget * 0.4)) : 0;
|
|
143
|
-
const contentSlotCount = Math.max(0, slideBudget - imageSlotCount - (title ? 1 : 0));
|
|
144
|
-
|
|
145
|
-
// Select top elements by confidence, then restore source order
|
|
146
|
-
const sorted = [...allElements].sort((a, b) => b.confidence - a.confidence);
|
|
147
|
-
const selected = sorted.slice(0, contentSlotCount);
|
|
148
|
-
selected.sort((a, b) => a.sourcePosition - b.sourcePosition);
|
|
149
|
-
|
|
150
|
-
// 4. Build slides
|
|
151
|
-
const blocks: TemplateBlock[] = [];
|
|
152
|
-
let slideIndex = 0;
|
|
153
|
-
let colorIdx = 0;
|
|
154
|
-
let accentIdx = 0;
|
|
155
|
-
|
|
156
|
-
// Determine per-slide duration
|
|
157
|
-
const totalSlideCount =
|
|
158
|
-
(title ? 1 : 0) + selected.length + Math.min(imageSlotCount, images.length);
|
|
159
|
-
const perSlide =
|
|
160
|
-
totalSlideCount > 0
|
|
161
|
-
? Math.min(maxSlideDuration, Math.max(minSlideDuration, totalDuration / totalSlideCount))
|
|
162
|
-
: minSlideDuration;
|
|
163
|
-
|
|
164
|
-
// Title slide
|
|
165
|
-
if (title) {
|
|
166
|
-
const titleBlock: TitleBlockInput = {
|
|
167
|
-
id: `slide-${slideIndex++}`,
|
|
168
|
-
template: 'title',
|
|
169
|
-
title,
|
|
170
|
-
subtitle,
|
|
171
|
-
duration: Math.min(perSlide, 6),
|
|
172
|
-
audioSegment: 0,
|
|
173
|
-
};
|
|
174
|
-
blocks.push(titleBlock);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Prepare image pool for standalone + accent images
|
|
178
|
-
const standaloneImages = images.slice(0, Math.min(imageSlotCount, images.length));
|
|
179
|
-
const accentPool =
|
|
180
|
-
images.length > standaloneImages.length ? images.slice(standaloneImages.length) : [...images]; // reuse if not enough
|
|
181
|
-
|
|
182
|
-
// Interleave pattern: insert an image slide every N content slides
|
|
183
|
-
const imageInterval =
|
|
184
|
-
standaloneImages.length > 0
|
|
185
|
-
? Math.max(1, Math.floor(selected.length / (standaloneImages.length + 1)))
|
|
186
|
-
: Infinity;
|
|
187
|
-
let imgIdx = 0;
|
|
188
|
-
let sinceImage = 0;
|
|
189
|
-
|
|
190
|
-
for (let i = 0; i < selected.length; i++) {
|
|
191
|
-
// Maybe insert a standalone image slide before this content slide
|
|
192
|
-
if (sinceImage >= imageInterval && imgIdx < standaloneImages.length) {
|
|
193
|
-
blocks.push(
|
|
194
|
-
createImageSlide(standaloneImages[imgIdx], slideIndex++, perSlide, rng, ambientMotions),
|
|
195
|
-
);
|
|
196
|
-
imgIdx++;
|
|
197
|
-
sinceImage = 0;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const element = selected[i];
|
|
201
|
-
|
|
202
|
-
// Optional accent image
|
|
203
|
-
let accentImage: AccentImage | undefined;
|
|
204
|
-
if (accentPool.length > 0) {
|
|
205
|
-
const img = accentPool[i % accentPool.length];
|
|
206
|
-
accentImage = {
|
|
207
|
-
src: img.src,
|
|
208
|
-
alt: img.alt ?? '',
|
|
209
|
-
position: accentPositions[accentIdx % accentPositions.length],
|
|
210
|
-
ambientMotion: rng.pickRequired(ambientMotions),
|
|
211
|
-
credit: img.credit,
|
|
212
|
-
license: img.license,
|
|
213
|
-
};
|
|
214
|
-
accentIdx++;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const block = mapElementToBlock(element, {
|
|
218
|
-
id: `slide-${slideIndex++}`,
|
|
219
|
-
duration: perSlide,
|
|
220
|
-
audioSegment: 0,
|
|
221
|
-
colorScheme: colorSchemes[colorIdx % colorSchemes.length],
|
|
222
|
-
accentImage,
|
|
223
|
-
});
|
|
224
|
-
blocks.push(block);
|
|
225
|
-
colorIdx++;
|
|
226
|
-
sinceImage++;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Append any remaining standalone images
|
|
230
|
-
while (imgIdx < standaloneImages.length) {
|
|
231
|
-
blocks.push(
|
|
232
|
-
createImageSlide(standaloneImages[imgIdx], slideIndex++, perSlide, rng, ambientMotions),
|
|
233
|
-
);
|
|
234
|
-
imgIdx++;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// 5. Assemble Doc
|
|
238
|
-
const doc: SlideshowDoc = {
|
|
239
|
-
articleId: title ?? 'slideshow',
|
|
240
|
-
duration: totalDuration,
|
|
241
|
-
blocks,
|
|
242
|
-
audio: {
|
|
243
|
-
segments: [{ src: '', name: 'main', duration: totalDuration, startTime: 0 }],
|
|
244
|
-
},
|
|
245
|
-
themeId,
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
return doc;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// ── Helpers ────────────────────────────────────────────────────────
|
|
252
|
-
|
|
253
|
-
function computeSlideBudget(duration: number, elementCount: number): number {
|
|
254
|
-
// Aim for ~5 slides per minute of content, clamped 3–12
|
|
255
|
-
const byDuration = Math.round((duration / 60) * 5);
|
|
256
|
-
// But don't exceed available elements + a few image slots
|
|
257
|
-
const budget = Math.min(byDuration, elementCount + 4);
|
|
258
|
-
return Math.max(3, Math.min(12, budget));
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function createImageSlide(
|
|
262
|
-
img: SlideshowImage,
|
|
263
|
-
index: number,
|
|
264
|
-
duration: number,
|
|
265
|
-
rng: SeededRandom,
|
|
266
|
-
motions: Array<'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight'>,
|
|
267
|
-
): ImageWithCaptionInput {
|
|
268
|
-
return {
|
|
269
|
-
id: `slide-${index}`,
|
|
270
|
-
template: 'imageWithCaption',
|
|
271
|
-
imageSrc: img.src,
|
|
272
|
-
imageAlt: img.alt ?? '',
|
|
273
|
-
caption: img.alt,
|
|
274
|
-
duration,
|
|
275
|
-
audioSegment: 0,
|
|
276
|
-
ambientMotion: rng.pickRequired(motions),
|
|
277
|
-
imageCredit: img.credit,
|
|
278
|
-
imageLicense: img.license,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|