@nodaro/prompts 1.26.1 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2776 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1611 -3
- package/dist/index.d.ts +1611 -3
- package/dist/index.js +2761 -31
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/picker-art-images.test.ts +191 -0
- package/src/__tests__/transitions-scope.test.ts +178 -5
- package/src/catalog-sections.ts +24 -0
- package/src/index.ts +3 -0
- package/src/picker-art/character-art-files.generated.ts +1118 -0
- package/src/picker-art/images.ts +83 -0
- package/src/picker-art/index.ts +13 -0
- package/src/picker-art/look-preview-sets.ts +489 -0
- package/src/picker-art/paths.ts +53 -0
- package/src/picker-art/sound-art-files.generated.ts +319 -0
- package/src/picker-art/sound-art-map.ts +722 -0
- package/src/picker-art/sound-art-types.ts +19 -0
- package/src/picker-catalogs.ts +73 -25
- package/src/styling.ts +18 -0
- package/src/transitions.ts +11 -11
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The dimension fields of each music / voice catalog that carries art. */
|
|
2
|
+
export interface SoundArtFields {
|
|
3
|
+
"music-genre": "genre" | "subgenre" | "era"
|
|
4
|
+
"music-mood": "energy" | "emotion" | "vibe"
|
|
5
|
+
instrumentation: "instruments" | "production" | "vocalPresence" | "singingStyle"
|
|
6
|
+
"voice-character": "age" | "gender" | "language" | "accent" | "timbre"
|
|
7
|
+
"voice-delivery": "pace" | "emotion" | "archetype"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type SoundArtCatalogId = keyof SoundArtFields
|
|
11
|
+
|
|
12
|
+
/** `emoji/<slug>` or `flags/<code>` — a key of SOUND_ART_FILES. */
|
|
13
|
+
export type SoundArtKey = `emoji/${string}` | `flags/${string}`
|
|
14
|
+
|
|
15
|
+
export type SoundArtMap = {
|
|
16
|
+
readonly [C in SoundArtCatalogId]: {
|
|
17
|
+
readonly [F in SoundArtFields[C]]: Readonly<Record<string, SoundArtKey>>
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/picker-catalogs.ts
CHANGED
|
@@ -81,7 +81,8 @@ import { ANIMALS, ANIMAL_SUBCATEGORY_LABELS, ANIMAL_SUBCATEGORY_ORDER, getAnimal
|
|
|
81
81
|
import { VEHICLES, VEHICLE_SUBCATEGORY_LABELS, VEHICLE_SUBCATEGORY_ORDER } from "@nodaro/shared"
|
|
82
82
|
import { WEAPONS, WEAPON_SUBCATEGORY_LABELS, WEAPON_SUBCATEGORY_ORDER } from "@nodaro/shared"
|
|
83
83
|
import { FURNITURE, FURNITURE_SUBCATEGORY_LABELS, FURNITURE_SUBCATEGORY_ORDER } from "@nodaro/shared"
|
|
84
|
-
import type { ProjectedCatalog, ProjectedCatalogOption, ProjectedCatalogDimension } from "@nodaro/shared"
|
|
84
|
+
import type { ProjectedCatalog, ProjectedCatalogOption, ProjectedCatalogDimension, ProjectedCatalogSection } from "@nodaro/shared"
|
|
85
|
+
import { pickerOptionImageUrl, pickerSectionImageUrl, type PickerImageOptions } from "./picker-art/images.js"
|
|
85
86
|
import { HELD_PROPS, HELD_PROP_CATEGORY_LABELS, HELD_PROP_CATEGORY_ORDER } from "./held-prop.js"
|
|
86
87
|
import { FRAMINGS, FRAMING_FIELD_BY_CATEGORY, FRAMING_CATEGORY_LABELS } from "./framing.js"
|
|
87
88
|
import { LIGHTINGS, LIGHTING_FIELD_BY_CATEGORY, LIGHTING_CATEGORY_ORDER, LIGHTING_CATEGORY_LABELS } from "./lighting.js"
|
|
@@ -89,6 +90,7 @@ import { TEMPORALS, TEMPORAL_FIELD_BY_CATEGORY, TEMPORAL_CATEGORY_LABELS } from
|
|
|
89
90
|
import { EXPOSURE_SETTINGS, EXPOSURE_FIELD_BY_CATEGORY, EXPOSURE_CATEGORY_LABELS } from "./exposure-settings.js"
|
|
90
91
|
import { PEOPLE, PERSON_FIELD_BY_DIMENSION, PERSON_DIMENSION_ORDER, PERSON_DIMENSION_LABELS } from "./person.js"
|
|
91
92
|
import { STYLINGS, STYLING_FIELD_BY_DIMENSION, STYLING_DIMENSION_ORDER, STYLING_DIMENSION_LABELS } from "./styling.js"
|
|
93
|
+
import { catalogTopics } from "./catalog-sections.js"
|
|
92
94
|
import { MUSIC_GENRES, MUSIC_ERAS } from "./music-genre.js"
|
|
93
95
|
import { MUSIC_ENERGIES, MUSIC_EMOTIONS, MUSIC_VIBES } from "./music-mood.js"
|
|
94
96
|
import { INSTRUMENTS, PRODUCTION_STYLES, VOCAL_PRESENCE, SINGING_STYLES } from "./instrumentation.js"
|
|
@@ -908,23 +910,41 @@ export interface PickerCatalogSummary {
|
|
|
908
910
|
readonly fields?: readonly string[]
|
|
909
911
|
/** single: options.length; multi: sum of every dimension's options. */
|
|
910
912
|
readonly optionCount: number
|
|
913
|
+
/** How many of those options carry an `imageUrl` (0 without picture options). */
|
|
914
|
+
readonly imageCount: number
|
|
911
915
|
}
|
|
912
916
|
|
|
913
|
-
/**
|
|
914
|
-
|
|
915
|
-
return
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
917
|
+
/** Every (field, option) pair a catalog's detail call returns, whichever kind. */
|
|
918
|
+
function everyOption(c: PickerCatalog): ReadonlyArray<{ readonly field: string | undefined; readonly option: PickerOption }> {
|
|
919
|
+
return [
|
|
920
|
+
...(c.options ?? []).map((option) => ({ field: c.valueField, option })),
|
|
921
|
+
...(c.dimensions ?? []).flatMap((d) => d.options.map((option) => ({ field: d.field, option }))),
|
|
922
|
+
]
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Lightweight directory of every picker catalog — no option payloads. With
|
|
927
|
+
* `images`, `imageCount` says how many options the detail call pictures.
|
|
928
|
+
*/
|
|
929
|
+
export function summarizePickerCatalogs(opts: { readonly images?: PickerImageOptions } = {}): readonly PickerCatalogSummary[] {
|
|
930
|
+
return getRegisteredPickerCatalogs().map((c) => {
|
|
931
|
+
const all = everyOption(c)
|
|
932
|
+
const images = opts.images
|
|
933
|
+
return {
|
|
934
|
+
nodeType: c.nodeType,
|
|
935
|
+
label: c.label,
|
|
936
|
+
catalogId: c.catalogId,
|
|
937
|
+
kind: c.kind,
|
|
938
|
+
valueField: c.valueField,
|
|
939
|
+
fields: c.fields,
|
|
940
|
+
// Every option the detail call will return, whichever kind — a single-dim
|
|
941
|
+
// catalog with secondary dimensions carries both.
|
|
942
|
+
optionCount: all.length,
|
|
943
|
+
imageCount: images
|
|
944
|
+
? all.filter(({ field, option }) => pickerOptionImageUrl(c, field, option.id, images) !== undefined).length
|
|
945
|
+
: 0,
|
|
946
|
+
}
|
|
947
|
+
})
|
|
928
948
|
}
|
|
929
949
|
|
|
930
950
|
export type PickerCatalogDetail = "compact" | "full"
|
|
@@ -936,6 +956,12 @@ export interface ProjectPickerCatalogOptions {
|
|
|
936
956
|
readonly category?: string
|
|
937
957
|
/** multi-dim: keep only this dimension field. */
|
|
938
958
|
readonly field?: string
|
|
959
|
+
/**
|
|
960
|
+
* Attach each option's absolute `imageUrl` (and the Person / Styling topic
|
|
961
|
+
* pictures). Left out, no option carries one — the caller (the API) knows
|
|
962
|
+
* which host this installation is reached at, the package does not.
|
|
963
|
+
*/
|
|
964
|
+
readonly images?: PickerImageOptions
|
|
939
965
|
}
|
|
940
966
|
|
|
941
967
|
// The projection shape now lives in `@nodaro/shared` (Apache) as the tag-free
|
|
@@ -946,10 +972,12 @@ export type ProjectedPickerOption = ProjectedCatalogOption
|
|
|
946
972
|
export type ProjectedPickerDimension = ProjectedCatalogDimension
|
|
947
973
|
export type ProjectedPickerCatalog = ProjectedCatalog
|
|
948
974
|
|
|
949
|
-
function projectOption(o: PickerOption, detail: PickerCatalogDetail): ProjectedPickerOption {
|
|
975
|
+
function projectOption(o: PickerOption, detail: PickerCatalogDetail, imageUrl?: string): ProjectedPickerOption {
|
|
950
976
|
// `term` rides at BOTH detail levels: a thin client renders `label` and
|
|
951
977
|
// injects `term`, so compact must carry it — it is what makes compact hint
|
|
952
|
-
// mode possible without a second round-trip for the full payload.
|
|
978
|
+
// mode possible without a second round-trip for the full payload. So does
|
|
979
|
+
// `imageUrl` (display data), present only when the option has a picture.
|
|
980
|
+
const extras = { ...(o.motion ? { motion: o.motion } : {}), ...(imageUrl ? { imageUrl } : {}) }
|
|
953
981
|
return detail === "full"
|
|
954
982
|
? {
|
|
955
983
|
id: o.id,
|
|
@@ -959,9 +987,25 @@ function projectOption(o: PickerOption, detail: PickerCatalogDetail): ProjectedP
|
|
|
959
987
|
promptHint: o.promptHint,
|
|
960
988
|
term: o.term,
|
|
961
989
|
icon: o.icon,
|
|
962
|
-
...
|
|
990
|
+
...extras,
|
|
963
991
|
}
|
|
964
|
-
: { id: o.id, label: o.label, category: o.category, term: o.term, icon: o.icon, ...
|
|
992
|
+
: { id: o.id, label: o.label, category: o.category, term: o.term, icon: o.icon, ...extras }
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* The topics a catalog's settings are grouped under in the editor (Person,
|
|
997
|
+
* Styling), as node-data fields; only the fields the catalog has are listed.
|
|
998
|
+
*/
|
|
999
|
+
function catalogSections(c: PickerCatalog, images: PickerImageOptions | undefined): ProjectedCatalogSection[] | undefined {
|
|
1000
|
+
const source = catalogTopics(c.catalogId)
|
|
1001
|
+
if (!source) return undefined
|
|
1002
|
+
const present = new Set((c.dimensions ?? []).map((d) => d.field))
|
|
1003
|
+
return source
|
|
1004
|
+
.map((s) => {
|
|
1005
|
+
const imageUrl = images ? pickerSectionImageUrl(s.label, images) : undefined
|
|
1006
|
+
return { label: s.label, fields: s.fields.filter((f) => present.has(f)), ...(imageUrl ? { imageUrl } : {}) }
|
|
1007
|
+
})
|
|
1008
|
+
.filter((s) => s.fields.length > 0)
|
|
965
1009
|
}
|
|
966
1010
|
|
|
967
1011
|
/** Project a catalog to the wire shape: compact by default, optional category/field filter. */
|
|
@@ -981,17 +1025,20 @@ export function projectPickerCatalog(
|
|
|
981
1025
|
categoryLabels: c.categoryLabels,
|
|
982
1026
|
detail,
|
|
983
1027
|
}
|
|
1028
|
+
const images = opts.images
|
|
1029
|
+
const imageOf = (field: string | undefined, o: PickerOption) =>
|
|
1030
|
+
images ? pickerOptionImageUrl(c, field, o.id, images) : undefined
|
|
984
1031
|
const projectDims = (dims: readonly PickerDimension[]) =>
|
|
985
1032
|
dims.map((d) => ({
|
|
986
1033
|
field: d.field,
|
|
987
1034
|
label: d.label,
|
|
988
|
-
options: d.options.map((o) => projectOption(o, detail)),
|
|
1035
|
+
options: d.options.map((o) => projectOption(o, detail, imageOf(d.field, o))),
|
|
989
1036
|
}))
|
|
990
1037
|
|
|
991
1038
|
if (c.kind === "single") {
|
|
992
1039
|
let options = c.options ?? []
|
|
993
1040
|
if (opts.category) options = options.filter((o) => o.category === opts.category)
|
|
994
|
-
const projected = { ...base, options: options.map((o) => projectOption(o, detail)) }
|
|
1041
|
+
const projected = { ...base, options: options.map((o) => projectOption(o, detail, imageOf(c.valueField, o))) }
|
|
995
1042
|
// A single-dim catalog may still carry secondary parameter dimensions
|
|
996
1043
|
// (transition's position/duration/intensity). They are additive — a
|
|
997
1044
|
// consumer reading only `options` is unaffected — but dropping them here
|
|
@@ -1003,7 +1050,8 @@ export function projectPickerCatalog(
|
|
|
1003
1050
|
}
|
|
1004
1051
|
let dims = c.dimensions ?? []
|
|
1005
1052
|
if (opts.field) dims = dims.filter((d) => d.field === opts.field)
|
|
1006
|
-
|
|
1053
|
+
const sections = catalogSections(c, images)
|
|
1054
|
+
return { ...base, fields: c.fields, dimensions: projectDims(dims), ...(sections ? { sections } : {}) }
|
|
1007
1055
|
}
|
|
1008
1056
|
|
|
1009
1057
|
/**
|
|
@@ -1011,6 +1059,6 @@ export function projectPickerCatalog(
|
|
|
1011
1059
|
* `/v1/catalogs` wire shape. This is what the `GET /v1/catalogs` route and its
|
|
1012
1060
|
* SDK resource serve, so a deployment's vendored packs are reflected verbatim.
|
|
1013
1061
|
*/
|
|
1014
|
-
export function projectAllCatalogs(opts: { detail?: PickerCatalogDetail } = {}): ProjectedCatalog[] {
|
|
1015
|
-
return getRegisteredPickerCatalogs().map((c) => projectPickerCatalog(c, { detail: opts.detail }))
|
|
1062
|
+
export function projectAllCatalogs(opts: { detail?: PickerCatalogDetail; images?: PickerImageOptions } = {}): ProjectedCatalog[] {
|
|
1063
|
+
return getRegisteredPickerCatalogs().map((c) => projectPickerCatalog(c, { detail: opts.detail, images: opts.images }))
|
|
1016
1064
|
}
|
package/src/styling.ts
CHANGED
|
@@ -467,6 +467,24 @@ export const STYLING_DIMENSION_ORDER: ReadonlyArray<StylingDimension> = [
|
|
|
467
467
|
"wardrobe-state",
|
|
468
468
|
]
|
|
469
469
|
|
|
470
|
+
/**
|
|
471
|
+
* The Styling picker's topics, in display order (mirrors PERSON_DIMENSION_SECTIONS):
|
|
472
|
+
* every dimension belongs to exactly one — styling-sections.test.ts fails the build
|
|
473
|
+
* when a new dimension is left out or listed twice. The editor lays the open
|
|
474
|
+
* picker out by these, and the API serves them as the catalog's `sections`.
|
|
475
|
+
*/
|
|
476
|
+
export interface StylingDimensionSection {
|
|
477
|
+
readonly label: string
|
|
478
|
+
readonly dimensions: ReadonlyArray<StylingDimension>
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export const STYLING_DIMENSION_SECTIONS: ReadonlyArray<StylingDimensionSection> = [
|
|
482
|
+
{ label: "Beauty & Hair", dimensions: ["makeup", "hair-cut", "hair-treatment", "hair-state", "nails", "face-paint"] },
|
|
483
|
+
{ label: "Accessories", dimensions: ["eyewear", "headwear", "jewelry"] },
|
|
484
|
+
{ label: "Wardrobe", dimensions: ["outfit", "top", "bottom", "outerwear", "legwear", "footwear"] },
|
|
485
|
+
{ label: "Fabric & Fit", dimensions: ["fabric", "wardrobe-state"] },
|
|
486
|
+
]
|
|
487
|
+
|
|
470
488
|
export const STYLING_DIMENSION_LABELS: Readonly<Record<StylingDimension, string>> = {
|
|
471
489
|
makeup: "Makeup",
|
|
472
490
|
eyewear: "Eyewear",
|
package/src/transitions.ts
CHANGED
|
@@ -159,7 +159,7 @@ export const TRANSITIONS: ReadonlyArray<Transition> = [
|
|
|
159
159
|
{ id: "magic-sparkles", label: "Magic Sparkles", category: "element", description: "Particle dissolve à la Avengers / apparition",
|
|
160
160
|
promptHint: "the subject disintegrates into a cloud of glowing golden sparkles that scatter outward, then the sparkles converge from across the frame and re-coalesce into the new subject" },
|
|
161
161
|
{ id: "lightning-flash", label: "Lightning Strike", category: "element", description: "Lightning strikes, scene changes in flash",
|
|
162
|
-
promptHint: "a brilliant
|
|
162
|
+
promptHint: "a brilliant jagged bolt of lightning cracks across the frame and its flash turns the whole picture white. The camera stays where it is and the framing does not change. As the flash dies away, the second shot is revealed in its place. The shot ends on the second shot, fully resolved, with no bolt or flash left. The change happens inside the flash, and the second shot appears only as the white fades" },
|
|
163
163
|
{ id: "ink-splash", label: "Ink Splash", category: "element", description: "Ink splashes across, scene changes",
|
|
164
164
|
promptHint: "black ink splashes across the frame in expanding tendrils that fully cover the image, then the ink retracts inward and pulls back to reveal the new scene" },
|
|
165
165
|
{ id: "sand-storm", label: "Sand Storm", category: "element", description: "Sand storm engulfs the frame, scene changes inside",
|
|
@@ -207,21 +207,21 @@ export const TRANSITIONS: ReadonlyArray<Transition> = [
|
|
|
207
207
|
{ id: "zoom-into-screen", label: "Zoom Into Screen", category: "portal", description: "Push into TV/phone screen",
|
|
208
208
|
promptHint: "the camera pushes toward a screen visible in the scene, such as a TV, phone or monitor, the screen's image fills the frame, and the camera passes through into that image which becomes the new scene" },
|
|
209
209
|
{ id: "zoom-into-book", label: "Zoom Into Book", category: "portal", description: "Push into book page illustration",
|
|
210
|
-
promptHint: "the camera pushes down
|
|
210
|
+
promptHint: "the camera pushes down toward the illustrated page of an open book in the frame until the illustration fills the whole picture. The camera travels in one smooth line, without turning or rolling. The drawn picture comes alive, gaining depth, light and movement, and becomes the second shot. The shot ends inside the second shot, real and fully resolved, with no paper, ink lines or page edges left. The camera heads for the page from the start and goes into its illustration" },
|
|
211
211
|
{ id: "walk-through-door", label: "Walk Through Doorway", category: "portal", description: "Through doorway into new scene",
|
|
212
|
-
promptHint: "the camera follows the subject through a doorway,
|
|
212
|
+
promptHint: "the camera follows the first subject through a doorway in the frame, moving forward at the subject's pace. The camera travels straight forward, without turning, tilting or rolling. On the far side of the doorway the space is the second shot, a different place in different light. The shot ends in the second shot, fully resolved, with the doorway behind the camera. The change of place happens at the doorway itself, the moment the camera passes through it" },
|
|
213
213
|
{ id: "fall-into-hole", label: "Fall Into Hole", category: "portal", description: "Camera falls through opening",
|
|
214
214
|
promptHint: "the floor or ground opens beneath the camera and the camera falls downward through the opening, tumbling, and emerges into the new scene below" },
|
|
215
215
|
{ id: "pull-out-reveal", label: "Pull-Out Reveal", category: "portal", description: "Reveals scene was a picture in larger context",
|
|
216
|
-
promptHint: "the camera pulls back
|
|
216
|
+
promptHint: "the camera pulls straight back fast, and the whole first shot shrinks until its edges show as the border of a framed picture inside a larger space. The camera travels back in one smooth line, without turning, tilting or rolling. The larger space around the picture is the second shot, and the first shot stays inside the picture. The shot ends on the second shot, with the first shot visible as a picture within it. The first shot's image stays exactly the same as it shrinks, so it reads as the same picture all along", term: "pull-back reveal" },
|
|
217
217
|
{ id: "zoom-into-mouth", label: "Zoom Into Mouth", category: "portal", description: "Push into open mouth, emerges in new world inside",
|
|
218
|
-
promptHint: "the camera pushes into the subject's open mouth
|
|
218
|
+
promptHint: "the camera pushes straight into the first subject's open mouth until the dark interior fills the whole picture. The camera travels forward in one smooth line, without turning, tilting or rolling. It passes through the darkness, and the second shot emerges out of it. The shot ends inside the second shot, still and fully resolved, with no mouth left in view. The dark interior stays a plain darkness the camera passes through" },
|
|
219
219
|
{ id: "push-through-glass", label: "Push Through Glass", category: "portal", description: "Camera pushes through pane of glass into new world",
|
|
220
220
|
promptHint: "the camera pushes toward a pane of glass in the scene, the surface ripples like liquid as the camera passes through with a faint refraction, and the space on the other side resolves as the new scene" },
|
|
221
221
|
{ id: "soul-jump", label: "Soul Jump", category: "portal", description: "Translucent soul leaves body, enters new body",
|
|
222
222
|
promptHint: "a translucent luminous form rises out of the first subject's body and shoots forward through the frame as a ghost-like soul, then dives into a new body in the new scene where the second subject animates to life", term: "soul leaves body and enters another" },
|
|
223
223
|
{ id: "mask-transition", label: "Mask Transition", category: "portal", description: "Foreground object blacks out the frame, camera pulls through",
|
|
224
|
-
promptHint: "
|
|
224
|
+
promptHint: "a dark foreground shape sweeps across the lens and fills the frame with black. The camera keeps travelling forward through the black in one smooth line. It emerges from the darkness into the second shot. The shot ends in the second shot, fully resolved, with no dark shape left. The dark shape passes close to the lens and is gone once the second shot appears", term: "mask transition" },
|
|
225
225
|
{ id: "zoom-through", label: "Zoom Through", category: "portal", description: "Camera magnifies one detail until the new scene unfolds inside it",
|
|
226
226
|
promptHint: "zoom-through transition: the camera magnifies one small detail of the frame further and further until the detail loses its texture and fills the image entirely, and the new scene unfolds from within it", term: "zoom-through transition" },
|
|
227
227
|
|
|
@@ -231,7 +231,7 @@ export const TRANSITIONS: ReadonlyArray<Transition> = [
|
|
|
231
231
|
{ id: "explosion-blast", label: "Explosion Blast", category: "physics", description: "Explosion wipes frame, new scene emerges",
|
|
232
232
|
promptHint: "an explosion erupts from the center of the frame with a bright fireball that expands to fill the frame, and as the fireball dissipates the new scene is revealed" },
|
|
233
233
|
{ id: "shockwave", label: "Shockwave", category: "physics", description: "Shockwave ripples across, scene changes",
|
|
234
|
-
promptHint: "a
|
|
234
|
+
promptHint: "a flash at the exact centre of the frame bursts into a sharp, bright ring that races past every edge in a moment, trailing a smear of motion blur behind its rim and warping the picture as it goes. The camera stays where it is and the picture stays level. The second shot shows only inside the ring and the first only outside it, with the bright ring as the one hard border and no blending anywhere" },
|
|
235
235
|
{ id: "punch-into-camera", label: "Punch Into Camera", category: "physics", description: "Fist strikes camera, scene changes",
|
|
236
236
|
promptHint: "a fist or object swings rapidly toward the camera and strikes the lens with motion blur and impact frames, and the moment of impact reveals the new scene" },
|
|
237
237
|
{ id: "debris-shower", label: "Debris Shower", category: "physics", description: "Debris flies past, scene changes behind",
|
|
@@ -239,9 +239,9 @@ export const TRANSITIONS: ReadonlyArray<Transition> = [
|
|
|
239
239
|
{ id: "gravity-flip", label: "Gravity Flip", category: "physics", description: "Gravity inverts, camera rotates 180",
|
|
240
240
|
promptHint: "gravity inverts and the camera rotates a full 180 degrees as objects and the subject reorient to the new down, settling into the new scene oriented correctly" },
|
|
241
241
|
{ id: "building-explosion", label: "Building Explosion", category: "physics", description: "Structure detonates, scene shifts through smoke",
|
|
242
|
-
promptHint: "
|
|
242
|
+
promptHint: "the largest structure in the frame detonates in a massive fireball, and debris and dust plume outward until they fill the whole picture. The camera stays where it is and the framing does not change. As the dust cloud clears, the second shot is revealed in its place. The shot ends on the second shot, clear and fully resolved, with no dust or debris left. The blast comes from that structure itself, and the second shot appears only as the dust clears" },
|
|
243
243
|
{ id: "vehicle-explosion", label: "Vehicle Explosion", category: "physics", description: "Vehicle detonates in foreground, scene changes behind",
|
|
244
|
-
promptHint: "a vehicle in the
|
|
244
|
+
promptHint: "a vehicle in the frame bursts into a violent explosion of fire and twisted metal, and the fireball billows toward the lens until orange flame fills the whole picture. The camera stays where it is and the framing does not change. The flame gives way to thick smoke, and as the smoke parts the second shot is revealed. The shot ends on the second shot, clear and fully resolved, with no fire or smoke left. The explosion comes from that vehicle itself, and the second shot appears only as the smoke parts" },
|
|
245
245
|
{ id: "jump-match", label: "Jump Match", category: "physics", description: "Subject jumps, landing matches into new scene",
|
|
246
246
|
promptHint: "the subject jumps upward and out of frame at the end of the first shot, with matched velocity the camera follows the arc, and on landing the subject is in a new location seamlessly continuing the same jump", term: "match cut on a jump" , instant: true },
|
|
247
247
|
{ id: "hand-swipe", label: "Hand Swipe", category: "physics", description: "Hand swipes across lens, scene changes during occlusion",
|
|
@@ -261,9 +261,9 @@ export const TRANSITIONS: ReadonlyArray<Transition> = [
|
|
|
261
261
|
{ id: "color-invert", label: "Color Invert Flash", category: "light", description: "Colors invert briefly",
|
|
262
262
|
promptHint: "colors invert across the entire frame to their negative for a single beat, and when the colors snap back the scene has changed to the new setting" },
|
|
263
263
|
{ id: "sun-glare", label: "Sun Glare", category: "light", description: "Sun glare washes frame",
|
|
264
|
-
promptHint: "intense
|
|
264
|
+
promptHint: "intense warm glare floods the lens from one corner of the frame, blooming and scattering flares until the whole picture is washed out to a bright haze. The camera stays where it is and the framing does not change. As the glare fades, the second shot emerges through the haze. The shot ends on the second shot, clear and fully resolved, with no glare left. The glare comes from the lens itself, and the second shot appears only as the haze clears" },
|
|
265
265
|
{ id: "lens-crack", label: "Lens Crack", category: "light", description: "Lens cracks, scene through fractured glass",
|
|
266
|
-
promptHint: "a hairline crack
|
|
266
|
+
promptHint: "a hairline crack snaps diagonally across the lens and branches into a web of fracture lines that spreads over the whole picture. The camera stays where it is and the framing does not change. Seen through the cracks, the first shot gives way to the second shot, and then the fracture lines fade away. The shot ends on the second shot, clear and fully resolved, with no cracks left. The cracks form on the lens itself, while everything behind them stays whole" },
|
|
267
267
|
{ id: "dirty-lens-wipe", label: "Dirty Lens Wipe", category: "light", description: "Lens dust/grime wipes clean, scene changes",
|
|
268
268
|
promptHint: "the camera lens is suddenly streaked with dust, water beads, and grime that swirl across the front element, a wiping motion sweeps the lens clean from one side to the other, revealing the new scene in crisp focus" },
|
|
269
269
|
{ id: "eye-light-burst", label: "Eye Light Burst", category: "light", description: "Bright beam from subject's eyes whites out frame",
|