morfeo 0.3.3 → 0.4.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/assets/fuentes/Silkscreen.ttf +0 -0
- package/bases/biografia/Mundo.tsx +35 -0
- package/bases/biografia/guion.mjs +34 -0
- package/bases/construiste/Mundo.tsx +273 -0
- package/bases/construiste/guion.mjs +84 -0
- package/bases/escalada/Mundo.tsx +45 -0
- package/bases/escalada/guion.mjs +24 -0
- package/bases/mito/Mundo.tsx +55 -0
- package/bases/mito/guion.mjs +29 -0
- package/bases/serie/guion.mjs +32 -0
- package/bin/morfeo.mjs +14 -6
- package/index.d.ts +51 -3
- package/lib/config.mjs +23 -4
- package/lib/estructura.mjs +9 -3
- package/lib/formulas.mjs +179 -0
- package/lib/guion.mjs +29 -3
- package/lib/nuevo-video.mjs +12 -3
- package/lib/palabras.mjs +2 -2
- package/package.json +1 -1
- package/python/musica.py +9 -1
- package/python/preparar.py +1 -1
- package/remotion/estilo.ts +2 -0
- package/remotion/kit/armar.tsx +245 -0
- package/remotion/kit/index.ts +1 -0
- package/remotion/motor/Video.tsx +214 -15
- package/remotion/motor/capas.tsx +2 -0
- package/remotion/motor/historias.tsx +440 -0
- package/remotion/motor/modos.tsx +120 -0
- package/remotion/motor/subtitulos.tsx +17 -2
- package/remotion/motor/tema.ts +10 -2
- package/remotion/motor/tierra-datos.ts +2 -0
- package/remotion/tipos.ts +1 -0
package/python/musica.py
CHANGED
|
@@ -237,7 +237,15 @@ def mix(vid, style, outdir, TIM, SPEC, durs, ev):
|
|
|
237
237
|
for i in range(len(sp["rows"])): add(sfx, 0.2 * whoosh(rng, 0.15), t0 + m["speech"] * 0.55 + i * 0.12)
|
|
238
238
|
if ty == "cta": add(sfx, 0.4 * tick(), t0 + 1.6)
|
|
239
239
|
# motor de capas: cada capa suena cuando entra (las explícitas siempre; las automáticas, más bajas y sin amontonarse)
|
|
240
|
-
if sp.get("rotulo"):
|
|
240
|
+
if sp.get("rotulo"):
|
|
241
|
+
# el sello cae de golpe y la letra pixel suena como pantalla vieja; los demás rótulos entran con un whoosh
|
|
242
|
+
est = sp.get("rotuloEstilo") or ""
|
|
243
|
+
ef, vol = {"sello": ("golpe", 0.9), "pixel": ("tick", 0.8)}.get(est, ("whoosh", 0.5))
|
|
244
|
+
add(sfx, vol * efecto(ef, rng), t0 + momento(sp.get("rotuloEn", 0.12), ev, si, m) - (0.0 if est == "sello" else 0.05))
|
|
245
|
+
# modo de imagen (rayos X, plano, holograma): barrido al entrar, salvo que siga de la escena anterior
|
|
246
|
+
prev = SPEC[si - 1] if si > 0 else None
|
|
247
|
+
if sp.get("modo") and not (prev and prev.get("modo") == sp.get("modo") and prev.get("modoHasta") is None and sp.get("modoEn") is None):
|
|
248
|
+
add(sfx, 0.35 * efecto("whoosh", rng), t0 + momento(sp.get("modoEn", 0), ev, si, m))
|
|
241
249
|
usados = []
|
|
242
250
|
for capa, ef, explicito in sorted(capas_con_sonido(sp), key=lambda x: momento(x[0].get("en"), ev, si, m)):
|
|
243
251
|
tc = t0 + momento(capa.get("en"), ev, si, m)
|
package/python/preparar.py
CHANGED
|
@@ -264,7 +264,7 @@ def preparar(job):
|
|
|
264
264
|
subs = [dict(a=round(c[0][1], 3), b=round(c[-1][2], 3), w=[[x[0].upper(), round(x[1], 3), round(x[2], 3)] for x in c])
|
|
265
265
|
for ws in WORDS for c in trozos(ws)]
|
|
266
266
|
datos = dict(id=vid, titulo=v.get("title", vid), indice=job.get("indice", 0), duracion=round(sum(durs), 3),
|
|
267
|
-
estilo=v.get("estilo", "datos-curiosos"), tema=v.get("tema") or {}, hud=v.get("hud") or [], mundo=v.get("mundo") or [], serie=v.get("serie"),
|
|
267
|
+
estilo=v.get("estilo", "datos-curiosos"), tema=v.get("tema") or {}, hud=v.get("hud") or [], mundo=v.get("mundo") or [], lugares=v.get("lugares") or {}, serie=v.get("serie"),
|
|
268
268
|
audio=f"v/{vid}/mix.wav", whisper=reales, escenas=escenas, subtitulos=subs)
|
|
269
269
|
json.dump(datos, open(os.path.join(outdir, "datos.json"), "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
|
270
270
|
print(f" tiempos por palabra: {'Whisper' if reales else 'reparto por frase'} ({reales}/{len(TIM)} escenas)", flush=True)
|
package/remotion/estilo.ts
CHANGED
|
@@ -36,10 +36,12 @@ export const fondoDe = (indice: number) => FONDOS[indice % FONDOS.length];
|
|
|
36
36
|
export const ANTON = 'Anton';
|
|
37
37
|
export const MONT = 'Montserrat';
|
|
38
38
|
export const MONO = 'JetBrains Mono';
|
|
39
|
+
export const PIXEL = 'Silkscreen';
|
|
39
40
|
export const fuentesListas = Promise.all([
|
|
40
41
|
loadFont({family: ANTON, url: staticFile('fuentes/Anton.ttf')}),
|
|
41
42
|
loadFont({family: MONT, url: staticFile('fuentes/Montserrat.ttf'), weight: '100 900'}),
|
|
42
43
|
loadFont({family: MONO, url: staticFile('fuentes/JetBrainsMono.ttf'), weight: '100 800'}),
|
|
44
|
+
loadFont({family: PIXEL, url: staticFile('fuentes/Silkscreen.ttf')}),
|
|
43
45
|
]);
|
|
44
46
|
|
|
45
47
|
// Curvas
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// Piezas 3D para los formatos "construiste un X" y "escalada absurda" (un objeto que suma partes escena por escena),
|
|
2
|
+
// para multitudes ("30 000 hombres") y para la mano en primera persona que aprieta un control o sostiene algo.
|
|
3
|
+
// Todo depende de `t` (segundos) y de props; nada de useFrame.
|
|
4
|
+
//
|
|
5
|
+
// <Aparece t={t} en={cuando('ruedas')} desde="arriba"><Ruedas /></Aparece> (cae, rebota y suena por su cuenta)
|
|
6
|
+
// <Multitud3D t={t} n={400} ancho={16} fondo={10} colores={['#2F6FE0', '#F2B632']} destacar={[0, 7]} />
|
|
7
|
+
// <Mano3D t={t} pos={[0.5, -1.2, 3]} sostiene="control" aprieta={cuando('botón')} />
|
|
8
|
+
import React, {useMemo} from 'react';
|
|
9
|
+
import * as THREE from 'three';
|
|
10
|
+
import {azarN} from './ilustrado';
|
|
11
|
+
import {grupo, type Pieza, type V3} from './tres';
|
|
12
|
+
|
|
13
|
+
const clamp01 = (x: number) => Math.max(0, Math.min(1, x));
|
|
14
|
+
const suave = (x: number) => {
|
|
15
|
+
const c = clamp01(x);
|
|
16
|
+
return c * c * (3 - 2 * c);
|
|
17
|
+
};
|
|
18
|
+
/** Rebote al caer: llega en 0.55 del tramo, rebota dos veces y se queda. */
|
|
19
|
+
const caida = (k: number) => {
|
|
20
|
+
if (k >= 1) return 1;
|
|
21
|
+
if (k < 0.55) return (k / 0.55) ** 2;
|
|
22
|
+
const r = (k - 0.55) / 0.45;
|
|
23
|
+
return 1 - Math.abs(Math.sin(r * Math.PI * 2)) * (1 - r) * 0.12;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const Mate: React.FC<{c: string; r?: number}> = ({c, r = 0.75}) => <meshStandardMaterial color={c} roughness={r} flatShading />;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Una parte que llega en su momento: cae desde arriba con rebote (`desde: 'arriba'`), crece desde cero (`'crece'`),
|
|
30
|
+
* entra de lado (`'izq'`/`'der'`) o sube del suelo (`'abajo'`). Antes de `en` no se ve. `dur` en segundos, `altura` de caída.
|
|
31
|
+
*/
|
|
32
|
+
export const Aparece: React.FC<Pieza & {t: number; en: number; desde?: 'arriba' | 'crece' | 'izq' | 'der' | 'abajo'; dur?: number; altura?: number; hasta?: number; children: React.ReactNode}> = ({t, en, desde = 'arriba', dur = 0.55, altura = 4, hasta, children, ...p}) => {
|
|
33
|
+
if (t < en) return null;
|
|
34
|
+
const k = clamp01((t - en) / dur);
|
|
35
|
+
const fuera = hasta != null ? suave((t - hasta) / 0.35) : 0;
|
|
36
|
+
if (fuera >= 1) return null;
|
|
37
|
+
const g = grupo(p);
|
|
38
|
+
let [x, y, z] = g.position;
|
|
39
|
+
let esc = g.scale * (1 - fuera);
|
|
40
|
+
if (desde === 'arriba') y += (1 - caida(k)) * altura;
|
|
41
|
+
else if (desde === 'abajo') y -= (1 - suave(k)) * altura;
|
|
42
|
+
else if (desde === 'izq') x -= (1 - suave(k)) * altura;
|
|
43
|
+
else if (desde === 'der') x += (1 - suave(k)) * altura;
|
|
44
|
+
else esc *= k < 1 ? Math.sin(suave(k) * Math.PI * 0.62) / Math.sin(Math.PI * 0.62) : 1;
|
|
45
|
+
// pequeño aplastamiento al tocar el suelo
|
|
46
|
+
const apl = desde === 'arriba' && k > 0.5 && k < 0.8 ? Math.sin(((k - 0.5) / 0.3) * Math.PI) * 0.12 : 0;
|
|
47
|
+
return (
|
|
48
|
+
<group position={[x, y, z]} rotation={g.rotation} scale={[esc * (1 + apl * 0.5), esc * (1 - apl), esc * (1 + apl * 0.5)]}>
|
|
49
|
+
{children}
|
|
50
|
+
</group>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Multitud de figuras simples (cuerpo + cabeza) en una rejilla con algo de desorden, en un solo `instancedMesh` (miles
|
|
56
|
+
* sin costo). `n`, `ancho`×`fondo` (unidades), `colores` del cuerpo, `destacar: [índices]` en `colorDestacado`, `brinca`
|
|
57
|
+
* para que salten. Aparece de adelante hacia atrás desde `en`.
|
|
58
|
+
*/
|
|
59
|
+
export const Multitud3D: React.FC<Pieza & {t: number; n?: number; ancho?: number; fondo?: number; colores?: string[]; colorDestacado?: string; destacar?: number[]; brinca?: boolean; en?: number; altura?: number}> = ({t, n = 300, ancho = 14, fondo = 10, colores = ['#2F6FE0', '#3D7DF0', '#2A5FC8'], colorDestacado = '#F2B632', destacar = [], brinca = false, en = 0, altura = 1.1, ...p}) => {
|
|
60
|
+
const cols = Math.max(1, Math.round(Math.sqrt((n * ancho) / fondo)));
|
|
61
|
+
const filas = Math.ceil(n / cols);
|
|
62
|
+
const datos = useMemo(() => {
|
|
63
|
+
let semilla = n * 3 + 7;
|
|
64
|
+
const r = () => azarN(semilla++);
|
|
65
|
+
return Array.from({length: n}, (_, i) => {
|
|
66
|
+
const c = i % cols;
|
|
67
|
+
const f = Math.floor(i / cols);
|
|
68
|
+
const x = (c / Math.max(1, cols - 1) - 0.5) * ancho + (r() - 0.5) * (ancho / cols) * 0.5;
|
|
69
|
+
const z = (f / Math.max(1, filas - 1) - 0.5) * fondo + (r() - 0.5) * (fondo / filas) * 0.5;
|
|
70
|
+
return {x, z, fase: r() * 6.28, escala: 0.9 + r() * 0.2, color: destacar.includes(i) ? colorDestacado : colores[Math.floor(r() * colores.length)], fila: f};
|
|
71
|
+
});
|
|
72
|
+
}, [n, cols, filas, ancho, fondo, colores.join(), colorDestacado, destacar.join()]);
|
|
73
|
+
const cuerpo = useMemo(() => new THREE.CapsuleGeometry(0.22 * altura, 0.5 * altura, 3, 6), [altura]);
|
|
74
|
+
const cabeza = useMemo(() => new THREE.IcosahedronGeometry(0.2 * altura, 0), [altura]);
|
|
75
|
+
const m = useMemo(() => new THREE.Matrix4(), []);
|
|
76
|
+
const color = useMemo(() => new THREE.Color(), []);
|
|
77
|
+
const refC = React.useRef<THREE.InstancedMesh>(null);
|
|
78
|
+
const refH = React.useRef<THREE.InstancedMesh>(null);
|
|
79
|
+
// las matrices se escriben en cada render (depende de t), no en un efecto
|
|
80
|
+
const aplicar = () => {
|
|
81
|
+
if (!refC.current || !refH.current) return;
|
|
82
|
+
datos.forEach((d, i) => {
|
|
83
|
+
const k = clamp01((t - en - d.fila * 0.04) / 0.35);
|
|
84
|
+
const s = d.escala * (k < 1 ? caida(k) : 1);
|
|
85
|
+
const salto = brinca ? Math.max(0, Math.sin(t * 6 + d.fase)) * 0.25 : 0;
|
|
86
|
+
m.compose(new THREE.Vector3(d.x, (0.47 * altura + salto) * s, d.z), new THREE.Quaternion(), new THREE.Vector3(s, s, s));
|
|
87
|
+
refC.current!.setMatrixAt(i, m);
|
|
88
|
+
refC.current!.setColorAt(i, color.set(d.color));
|
|
89
|
+
m.compose(new THREE.Vector3(d.x, (1.05 * altura + salto) * s, d.z), new THREE.Quaternion(), new THREE.Vector3(s, s, s));
|
|
90
|
+
refH.current!.setMatrixAt(i, m);
|
|
91
|
+
refH.current!.setColorAt(i, color.set('#F2C9A4'));
|
|
92
|
+
});
|
|
93
|
+
refC.current.instanceMatrix.needsUpdate = true;
|
|
94
|
+
refH.current.instanceMatrix.needsUpdate = true;
|
|
95
|
+
if (refC.current.instanceColor) refC.current.instanceColor.needsUpdate = true;
|
|
96
|
+
if (refH.current.instanceColor) refH.current.instanceColor.needsUpdate = true;
|
|
97
|
+
};
|
|
98
|
+
React.useLayoutEffect(aplicar);
|
|
99
|
+
return (
|
|
100
|
+
<group {...grupo(p)}>
|
|
101
|
+
<instancedMesh ref={refC} args={[cuerpo, undefined, n]} castShadow>
|
|
102
|
+
<meshStandardMaterial roughness={0.7} flatShading />
|
|
103
|
+
</instancedMesh>
|
|
104
|
+
<instancedMesh ref={refH} args={[cabeza, undefined, n]} castShadow>
|
|
105
|
+
<meshStandardMaterial roughness={0.7} flatShading />
|
|
106
|
+
</instancedMesh>
|
|
107
|
+
</group>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/** Control remoto o de portón: caja oscura con botones. */
|
|
112
|
+
const Control: React.FC<{apretado: number}> = ({apretado}) => (
|
|
113
|
+
<group>
|
|
114
|
+
<mesh castShadow>
|
|
115
|
+
<boxGeometry args={[0.55, 0.95, 0.16]} />
|
|
116
|
+
<Mate c="#26282E" />
|
|
117
|
+
</mesh>
|
|
118
|
+
<mesh position={[0, 0.2, 0.09 - apretado * 0.03]}>
|
|
119
|
+
<cylinderGeometry args={[0.13, 0.13, 0.06, 16]} />
|
|
120
|
+
<meshStandardMaterial color="#E5484D" roughness={0.4} emissive="#E5484D" emissiveIntensity={apretado * 0.8} />
|
|
121
|
+
</mesh>
|
|
122
|
+
{[-0.13, 0.13].map((x) => (
|
|
123
|
+
<mesh key={x} position={[x, -0.15, 0.09]} rotation={[Math.PI / 2, 0, 0]}>
|
|
124
|
+
<cylinderGeometry args={[0.07, 0.07, 0.04, 12]} />
|
|
125
|
+
<Mate c="#D7D9DE" />
|
|
126
|
+
</mesh>
|
|
127
|
+
))}
|
|
128
|
+
</group>
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
/** Teléfono: losa negra con pantalla encendida. */
|
|
132
|
+
const Movil: React.FC<{pantalla?: string}> = ({pantalla = '#3A6FF2'}) => (
|
|
133
|
+
<group>
|
|
134
|
+
<mesh castShadow>
|
|
135
|
+
<boxGeometry args={[0.55, 1.1, 0.08]} />
|
|
136
|
+
<Mate c="#111214" r={0.4} />
|
|
137
|
+
</mesh>
|
|
138
|
+
<mesh position={[0, 0, 0.045]}>
|
|
139
|
+
<planeGeometry args={[0.48, 1.0]} />
|
|
140
|
+
<meshStandardMaterial color={pantalla} emissive={pantalla} emissiveIntensity={0.7} />
|
|
141
|
+
</mesh>
|
|
142
|
+
</group>
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Mano en primera persona (entra desde abajo del cuadro): palma, cuatro dedos que rodean lo que sostiene y pulgar que
|
|
147
|
+
* aprieta en `aprieta` (segundos). `sostiene: 'control' | 'movil' | 'nada'`, `piel`, `manga` (color de la manga).
|
|
148
|
+
* Ponla cerca de la cámara (2–4 u) y abajo del centro.
|
|
149
|
+
*/
|
|
150
|
+
export const Mano3D: React.FC<Pieza & {t: number; sostiene?: 'control' | 'movil' | 'nada'; aprieta?: number; piel?: string; manga?: string; en?: number; pantalla?: string}> = ({t, sostiene = 'control', aprieta, piel = '#E8B48F', manga = '#2F6FE0', en = 0, pantalla, ...p}) => {
|
|
151
|
+
const entra = suave((t - en) / 0.45);
|
|
152
|
+
const dt = aprieta != null ? t - aprieta : -1;
|
|
153
|
+
const apretado = dt >= 0 && dt < 0.5 ? Math.sin((dt / 0.5) * Math.PI) : 0;
|
|
154
|
+
const g = grupo(p);
|
|
155
|
+
const [x, y, z] = g.position;
|
|
156
|
+
const tiembla = Math.sin(t * 2.1) * 0.01;
|
|
157
|
+
return (
|
|
158
|
+
<group position={[x, y - (1 - entra) * 1.6 + tiembla, z]} rotation={g.rotation} scale={g.scale}>
|
|
159
|
+
{/* manga y muñeca */}
|
|
160
|
+
<mesh position={[0, -0.95, -0.05]} rotation={[0.25, 0, 0]} castShadow>
|
|
161
|
+
<cylinderGeometry args={[0.24, 0.3, 0.9, 8]} />
|
|
162
|
+
<Mate c={manga} />
|
|
163
|
+
</mesh>
|
|
164
|
+
<mesh position={[0, -0.45, 0]} castShadow>
|
|
165
|
+
<cylinderGeometry args={[0.17, 0.2, 0.3, 8]} />
|
|
166
|
+
<Mate c={piel} />
|
|
167
|
+
</mesh>
|
|
168
|
+
{/* palma detrás del objeto */}
|
|
169
|
+
<mesh position={[0, -0.05, -0.14]} castShadow>
|
|
170
|
+
<boxGeometry args={[0.6, 0.62, 0.2]} />
|
|
171
|
+
<Mate c={piel} />
|
|
172
|
+
</mesh>
|
|
173
|
+
{/* lo que sostiene */}
|
|
174
|
+
<group position={[0, 0.15, 0]}>{sostiene === 'control' ? <Control apretado={apretado} /> : sostiene === 'movil' ? <Movil pantalla={pantalla} /> : null}</group>
|
|
175
|
+
{/* dedos que rodean por la derecha */}
|
|
176
|
+
{[0.22, 0.02, -0.18, -0.36].map((yy, i) => (
|
|
177
|
+
<mesh key={i} position={[0.33, yy + 0.1, 0.02]} rotation={[0, 0, 0.1]} castShadow>
|
|
178
|
+
<boxGeometry args={[0.14, 0.15, 0.24]} />
|
|
179
|
+
<Mate c={piel} />
|
|
180
|
+
</mesh>
|
|
181
|
+
))}
|
|
182
|
+
{/* pulgar: baja sobre el botón al apretar */}
|
|
183
|
+
<group position={[-0.32, -0.05, 0.05]} rotation={[0, 0, -0.9 + apretado * 0.35]}>
|
|
184
|
+
<mesh position={[0, 0.25, 0.1 - apretado * 0.04]} castShadow>
|
|
185
|
+
<boxGeometry args={[0.16, 0.42, 0.16]} />
|
|
186
|
+
<Mate c={piel} />
|
|
187
|
+
</mesh>
|
|
188
|
+
</group>
|
|
189
|
+
</group>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/** Partes listas para armar objetos que crecen: rueda, motor y asiento (low-poly, se combinan con Aparece). */
|
|
194
|
+
export const Rueda: React.FC<Pieza & {radio?: number; t?: number; gira?: number; color?: string}> = ({radio = 0.35, t = 0, gira = 0, color = '#1E1F24', ...p}) => (
|
|
195
|
+
<group {...grupo(p)}>
|
|
196
|
+
<group rotation={[0, 0, -t * gira]}>
|
|
197
|
+
<mesh rotation={[Math.PI / 2, 0, 0]} castShadow>
|
|
198
|
+
<cylinderGeometry args={[radio, radio, radio * 0.6, 14]} />
|
|
199
|
+
<Mate c={color} r={0.9} />
|
|
200
|
+
</mesh>
|
|
201
|
+
<mesh rotation={[Math.PI / 2, 0, 0]} position={[0, 0, 0.01]}>
|
|
202
|
+
<cylinderGeometry args={[radio * 0.5, radio * 0.5, radio * 0.62, 8]} />
|
|
203
|
+
<Mate c="#B9BEC8" r={0.5} />
|
|
204
|
+
</mesh>
|
|
205
|
+
</group>
|
|
206
|
+
</group>
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
export const Motor: React.FC<Pieza & {t?: number; color?: string}> = ({t = 0, color = '#F2B632', ...p}) => {
|
|
210
|
+
const vibra = Math.sin(t * 60) * 0.01;
|
|
211
|
+
return (
|
|
212
|
+
<group {...grupo(p)} position={[(p.pos?.[0] ?? 0) + vibra, p.pos?.[1] ?? 0, p.pos?.[2] ?? 0]}>
|
|
213
|
+
<mesh castShadow>
|
|
214
|
+
<boxGeometry args={[0.7, 0.5, 0.5]} />
|
|
215
|
+
<Mate c={color} />
|
|
216
|
+
</mesh>
|
|
217
|
+
<mesh position={[0, 0.32, 0]} castShadow>
|
|
218
|
+
<boxGeometry args={[0.4, 0.14, 0.34]} />
|
|
219
|
+
<Mate c="#2A2C33" />
|
|
220
|
+
</mesh>
|
|
221
|
+
<mesh position={[0.42, 0.1, 0]} rotation={[0, 0, Math.PI / 2]} castShadow>
|
|
222
|
+
<cylinderGeometry args={[0.06, 0.06, 0.2, 8]} />
|
|
223
|
+
<Mate c="#6B7080" />
|
|
224
|
+
</mesh>
|
|
225
|
+
</group>
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const Asiento: React.FC<Pieza & {color?: string}> = ({color = '#E5484D', ...p}) => (
|
|
230
|
+
<group {...grupo(p)}>
|
|
231
|
+
<mesh position={[0, 0, 0]} castShadow>
|
|
232
|
+
<boxGeometry args={[0.6, 0.14, 0.55]} />
|
|
233
|
+
<Mate c={color} />
|
|
234
|
+
</mesh>
|
|
235
|
+
<mesh position={[0, 0.32, -0.24]} castShadow>
|
|
236
|
+
<boxGeometry args={[0.6, 0.6, 0.12]} />
|
|
237
|
+
<Mate c={color} />
|
|
238
|
+
</mesh>
|
|
239
|
+
<mesh position={[0, -0.25, 0]} castShadow>
|
|
240
|
+
<cylinderGeometry args={[0.05, 0.05, 0.4, 6]} />
|
|
241
|
+
<Mate c="#6B7080" />
|
|
242
|
+
</mesh>
|
|
243
|
+
</group>
|
|
244
|
+
);
|
|
245
|
+
|
package/remotion/kit/index.ts
CHANGED
package/remotion/motor/Video.tsx
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import {AbsoluteFill, Audio, staticFile} from 'remotion';
|
|
4
4
|
import {Avatar, MarcaContexto, useMarca} from '../piezas';
|
|
5
|
-
import {FPS, H, W, ancho, clamp, easeIO, easeOut, lerp, resorte} from '../estilo';
|
|
5
|
+
import {FPS, H, PIXEL, W, ancho, clamp, easeIO, easeOut, lerp, resorte} from '../estilo';
|
|
6
6
|
import type {Datos, Escena} from '../tipos';
|
|
7
7
|
import {Capas} from './capas';
|
|
8
|
+
import {Modo} from './modos';
|
|
8
9
|
import {FondoTema, Post, Textura, Vineta} from './fondo';
|
|
9
10
|
import {capasDe} from './plantillas';
|
|
10
11
|
import {Subtitulos} from './subtitulos';
|
|
@@ -12,6 +13,8 @@ import {mezclar, temaDe, type Tema} from './tema';
|
|
|
12
13
|
import {azar, cuando, partesResaltadas, type CtxEscena} from './util';
|
|
13
14
|
|
|
14
15
|
const TRANSICION = 0.35;
|
|
16
|
+
// estilos con fondo claro (cielo, papel): los modos de imagen invierten para que el fondo quede oscuro
|
|
17
|
+
const LOOKS_CLAROS = ['3d', 'maqueta', 'explicativo', 'infografia', 'minimal'];
|
|
15
18
|
|
|
16
19
|
/** Valor animado de cámara: número fijo o [inicio, fin] a lo largo de la escena. */
|
|
17
20
|
const rango = (v: unknown, k: number, porDefecto: number) => (Array.isArray(v) ? lerp(v[0], v[1], k) : typeof v === 'number' ? v : porDefecto);
|
|
@@ -50,6 +53,21 @@ const EscenaVista: React.FC<{e: Escena; t: number; tema: Tema; semilla: string;
|
|
|
50
53
|
);
|
|
51
54
|
};
|
|
52
55
|
|
|
56
|
+
/** Lugar (mundo) de la escena `i`: el suyo, o el de la última escena anterior que dijo uno, o el primero de `lugares`. */
|
|
57
|
+
export function lugarDe(d: Datos, i: number): string | null {
|
|
58
|
+
const nombres = Object.keys(d.lugares ?? {});
|
|
59
|
+
if (!nombres.length) return null;
|
|
60
|
+
for (let j = i; j >= 0; j--) {
|
|
61
|
+
const l = d.escenas[j]?.p.lugar;
|
|
62
|
+
if (typeof l === 'string' && d.lugares![l]) return l;
|
|
63
|
+
}
|
|
64
|
+
return d.mundo?.length ? null : nombres[0];
|
|
65
|
+
}
|
|
66
|
+
const capasDeLugar = (d: Datos, i: number) => {
|
|
67
|
+
const l = lugarDe(d, i);
|
|
68
|
+
return (l ? d.lugares![l] : d.mundo) ?? [];
|
|
69
|
+
};
|
|
70
|
+
|
|
53
71
|
type Toma = {x: number; y: number; zoom: number; giro: number};
|
|
54
72
|
const TOMA0: Toma = {x: 540, y: 960, zoom: 1, giro: 0};
|
|
55
73
|
|
|
@@ -59,8 +77,10 @@ const TOMA0: Toma = {x: 540, y: 960, zoom: 1, giro: 0};
|
|
|
59
77
|
* Sin `toma`, la escena conserva la anterior. `corte: true` salta sin viaje.
|
|
60
78
|
*/
|
|
61
79
|
export function camaraMundo(d: Datos, si: number, t: number): Toma {
|
|
80
|
+
// cada lugar tiene su propia cámara: al buscar la toma anterior no se cruza a otro lugar
|
|
62
81
|
const destino = (i: number): Toma => {
|
|
63
|
-
|
|
82
|
+
const l = lugarDe(d, i);
|
|
83
|
+
for (let j = i; j >= 0 && lugarDe(d, j) === l; j--) if (d.escenas[j].p.toma) return {...TOMA0, ...d.escenas[j].p.toma};
|
|
64
84
|
return TOMA0;
|
|
65
85
|
};
|
|
66
86
|
const final = (i: number): Toma => {
|
|
@@ -73,7 +93,8 @@ export function camaraMundo(d: Datos, si: number, t: number): Toma {
|
|
|
73
93
|
const tm = e.p.toma ?? {};
|
|
74
94
|
const a = destino(si);
|
|
75
95
|
const b = final(si);
|
|
76
|
-
const
|
|
96
|
+
const cambiaLugar = si > 0 && lugarDe(d, si) !== lugarDe(d, si - 1);
|
|
97
|
+
const viaje = tm.corte || si === 0 || cambiaLugar ? 0 : tm.duracion ?? Math.min(1.1, e.dur * 0.55);
|
|
77
98
|
const kd = clamp((t - viaje * 0.6) / Math.max(0.01, e.dur - viaje * 0.6));
|
|
78
99
|
const kq = tm.a ? easeIO(kd) : kd;
|
|
79
100
|
const actual: Toma = {x: lerp(a.x, b.x, kq), y: lerp(a.y, b.y, kq), zoom: lerp(a.zoom, b.zoom, kq), giro: lerp(a.giro, b.giro, kq)};
|
|
@@ -96,7 +117,7 @@ const Mundo: React.FC<{d: Datos; si: number; t: number; T: number; tema: Tema}>
|
|
|
96
117
|
const propias = capasDe(e.tipo, e.p, temaEsc, marca).filter((c: any) => c.enMundo);
|
|
97
118
|
return (
|
|
98
119
|
<AbsoluteFill style={{transform: `translate(540px, 960px) scale(${cam.zoom}) rotate(${cam.giro}deg) translate(${-cam.x}px, ${-cam.y}px)`, transformOrigin: '0 0'}}>
|
|
99
|
-
<Capas capas={d
|
|
120
|
+
<Capas capas={capasDeLugar(d, si) as any} ctx={ctxMundo} />
|
|
100
121
|
{propias.length ? <Capas capas={propias} ctx={ctxEsc} /> : null}
|
|
101
122
|
</AbsoluteFill>
|
|
102
123
|
);
|
|
@@ -136,6 +157,9 @@ const Rotulo: React.FC<{e: Escena; t: number; tema: Tema}> = ({e, t, tema}) => {
|
|
|
136
157
|
} catch {}
|
|
137
158
|
const claro = tema.colores.texto !== '#FFFFFF';
|
|
138
159
|
const estilo = e.p.rotuloEstilo ?? tema.rotulo?.estilo ?? 'normal';
|
|
160
|
+
if (estilo === 'sello') return <RotuloSello texto={texto} t={t - t0} tema={tema} e={e} />;
|
|
161
|
+
if (estilo === 'paso') return <RotuloPaso texto={texto} t={t - t0} tema={tema} e={e} />;
|
|
162
|
+
if (estilo === 'pixel') return <RotuloPixel texto={texto} t={t - t0} tema={tema} e={e} />;
|
|
139
163
|
const contorno = estilo === 'contorno' || (estilo === 'normal' && !claro && (S.contorno ?? 0) > 0);
|
|
140
164
|
const grosor = estilo === 'contorno' ? Math.max(8, Math.round(tam * 0.11)) : S.contorno;
|
|
141
165
|
const pastilla = estilo === 'pastilla';
|
|
@@ -167,6 +191,168 @@ const Rotulo: React.FC<{e: Escena; t: number; tema: Tema}> = ({e, t, tema}) => {
|
|
|
167
191
|
);
|
|
168
192
|
};
|
|
169
193
|
|
|
194
|
+
/** Colores con nombre para `rotuloColor` (o cualquier color CSS). */
|
|
195
|
+
const COLORES_ROTULO: Record<string, string> = {rojo: '#E0312B', verde: '#22A04B', azul: '#2F5BEA', amarillo: '#F5B70A', naranja: '#F2711C', gris: '#8A8F98', negro: '#1B1B1F', morado: '#7B4FD6'};
|
|
196
|
+
const colorRotulo = (c: unknown, porDefecto: string) => (typeof c === 'string' && c ? COLORES_ROTULO[c] ?? c : porDefecto);
|
|
197
|
+
|
|
198
|
+
// textura de tinta gastada para el sello: ruido fractal convertido en máscara (huecos donde la tinta no pegó)
|
|
199
|
+
const TINTA = `url("data:image/svg+xml,${encodeURIComponent(
|
|
200
|
+
"<svg xmlns='http://www.w3.org/2000/svg' width='600' height='240'><filter id='f'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.6 2.05'/></filter><rect width='100%' height='100%' filter='url(#f)'/></svg>",
|
|
201
|
+
)}")`;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Sello de goma: texto en mayúsculas con borde doble, inclinado, que cae de golpe (escala 1.7 → 1 en 0,16 s y un
|
|
205
|
+
* pequeño rebote). La tinta tiene huecos como un sello real. `rotuloColor` (rojo por defecto) y `rotuloInclinar`.
|
|
206
|
+
*/
|
|
207
|
+
const RotuloSello: React.FC<{texto: string; t: number; tema: Tema; e: Escena}> = ({texto, t, tema, e}) => {
|
|
208
|
+
const color = colorRotulo(e.p.rotuloColor ?? tema.rotulo?.color, '#E0312B');
|
|
209
|
+
const caida = clamp(t / 0.16);
|
|
210
|
+
const rebote = t > 0.16 ? Math.sin(clamp((t - 0.16) / 0.22) * Math.PI) * 0.05 : 0;
|
|
211
|
+
const escala = lerp(1.7, 1, easeIO(caida)) - rebote;
|
|
212
|
+
const inclinar = e.p.rotuloInclinar ?? tema.rotulo?.inclinar ?? -7;
|
|
213
|
+
let tam = e.p.rotuloTam ?? tema.rotulo?.tam ?? 92;
|
|
214
|
+
const limpio = texto.replace(/\*/g, '').toUpperCase();
|
|
215
|
+
try {
|
|
216
|
+
const w = ancho(limpio, tema.fuentes.titulo, tam, 900);
|
|
217
|
+
if (w > 820) tam = Math.max(44, Math.floor((tam * 820) / w));
|
|
218
|
+
} catch {}
|
|
219
|
+
// lejos de los subtítulos: si van a media altura, el sello se pone arriba de ellos
|
|
220
|
+
const S = tema.subtitulos;
|
|
221
|
+
const y = e.p.rotuloY ?? tema.rotulo?.y ?? (S.tipo !== 'ninguno' && Math.abs(S.y - 1180) < 260 ? S.y - 300 : 1180);
|
|
222
|
+
return (
|
|
223
|
+
<div style={{position: 'absolute', left: 0, width: W, top: y, display: 'flex', justifyContent: 'center', pointerEvents: 'none', opacity: clamp(caida * 4)}}>
|
|
224
|
+
<div
|
|
225
|
+
style={{
|
|
226
|
+
transform: `translate(0, -50%) rotate(${inclinar}deg) scale(${escala})`,
|
|
227
|
+
fontFamily: tema.fuentes.titulo,
|
|
228
|
+
fontWeight: 900,
|
|
229
|
+
fontSize: tam,
|
|
230
|
+
lineHeight: 1,
|
|
231
|
+
letterSpacing: tam * 0.03,
|
|
232
|
+
color,
|
|
233
|
+
textTransform: 'uppercase',
|
|
234
|
+
padding: `${tam * 0.2}px ${tam * 0.34}px ${tam * 0.16}px`,
|
|
235
|
+
border: `${Math.max(5, tam * 0.08)}px solid ${color}`,
|
|
236
|
+
borderRadius: tam * 0.12,
|
|
237
|
+
outline: `${Math.max(2, tam * 0.03)}px solid ${color}`,
|
|
238
|
+
outlineOffset: -Math.max(9, tam * 0.15),
|
|
239
|
+
background: 'rgba(255,255,255,0.82)',
|
|
240
|
+
boxShadow: '0 10px 26px rgba(0,0,0,0.22)',
|
|
241
|
+
WebkitMaskImage: TINTA,
|
|
242
|
+
maskImage: TINTA,
|
|
243
|
+
WebkitMaskSize: '600px 240px',
|
|
244
|
+
whiteSpace: 'nowrap',
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
{limpio}
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Titular de paso: arriba a la izquierda, en dos renglones, con `*clave*` en color y una insignia con el número del
|
|
255
|
+
* paso (`rotuloPaso: 3`). Entra deslizándose de la izquierda. Para explicadores "paso a paso".
|
|
256
|
+
*/
|
|
257
|
+
const RotuloPaso: React.FC<{texto: string; t: number; tema: Tema; e: Escena}> = ({texto, t, tema, e}) => {
|
|
258
|
+
const k = easeIO(clamp(t / 0.35));
|
|
259
|
+
const color = colorRotulo(e.p.rotuloColor ?? tema.rotulo?.color, tema.colores.acento);
|
|
260
|
+
const tam = e.p.rotuloTam ?? tema.rotulo?.tam ?? 64;
|
|
261
|
+
// si los subtítulos van arriba, el titular baja para no encimarse con ellos
|
|
262
|
+
const S = tema.subtitulos;
|
|
263
|
+
const y = e.p.rotuloY ?? tema.rotulo?.y ?? (S.tipo !== 'ninguno' && S.y < 600 ? S.y + 120 : 250);
|
|
264
|
+
const n = e.p.rotuloPaso;
|
|
265
|
+
return (
|
|
266
|
+
<div style={{position: 'absolute', left: 56, right: 56, top: y, display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 24, pointerEvents: 'none'}}>
|
|
267
|
+
<div style={{clipPath: `inset(0 ${(1 - k) * 100}% 0 0)`, transform: `translateX(${(1 - k) * -40}px)`, maxWidth: n != null ? 800 : 968}}>
|
|
268
|
+
<span
|
|
269
|
+
style={{
|
|
270
|
+
fontFamily: tema.fuentes.titulo,
|
|
271
|
+
fontWeight: 900,
|
|
272
|
+
fontSize: tam,
|
|
273
|
+
lineHeight: 1.02,
|
|
274
|
+
color: '#FFFFFF',
|
|
275
|
+
textTransform: 'uppercase',
|
|
276
|
+
WebkitTextStroke: `${Math.round(tam * 0.1)}px #15151A`,
|
|
277
|
+
paintOrder: 'stroke fill',
|
|
278
|
+
textShadow: '0 5px 0 rgba(0,0,0,0.25)',
|
|
279
|
+
}}
|
|
280
|
+
>
|
|
281
|
+
{partesResaltadas(texto).map(([p, r], i) => (
|
|
282
|
+
<span key={i} style={r ? {color} : undefined}>
|
|
283
|
+
{p}
|
|
284
|
+
</span>
|
|
285
|
+
))}
|
|
286
|
+
</span>
|
|
287
|
+
</div>
|
|
288
|
+
{n != null ? (
|
|
289
|
+
<div
|
|
290
|
+
style={{
|
|
291
|
+
flex: 'none',
|
|
292
|
+
minWidth: tam * 0.95,
|
|
293
|
+
height: tam * 0.95,
|
|
294
|
+
padding: `0 ${tam * 0.2}px`,
|
|
295
|
+
borderRadius: tam * 0.16,
|
|
296
|
+
background: '#15151A',
|
|
297
|
+
border: '4px solid #FFFFFF',
|
|
298
|
+
color: '#FFFFFF',
|
|
299
|
+
fontFamily: tema.fuentes.titulo,
|
|
300
|
+
fontWeight: 900,
|
|
301
|
+
fontSize: tam * 0.62,
|
|
302
|
+
display: 'flex',
|
|
303
|
+
alignItems: 'center',
|
|
304
|
+
justifyContent: 'center',
|
|
305
|
+
transform: `scale(${resorte(clamp((t - 0.15) / 0.3), 0.35)})`,
|
|
306
|
+
boxShadow: '0 8px 18px rgba(0,0,0,0.3)',
|
|
307
|
+
}}
|
|
308
|
+
>
|
|
309
|
+
{String(n)}
|
|
310
|
+
</div>
|
|
311
|
+
) : null}
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Rótulo pixel: letra de videojuego en una caja de color (verde = bien, rojo = mal; `rotuloColor`). Las letras
|
|
318
|
+
* aparecen de una en una, como en una pantalla vieja. Arriba por defecto.
|
|
319
|
+
*/
|
|
320
|
+
const RotuloPixel: React.FC<{texto: string; t: number; tema: Tema; e: Escena}> = ({texto, t, tema, e}) => {
|
|
321
|
+
const fondo = colorRotulo(e.p.rotuloColor ?? tema.rotulo?.color, '#2F5BEA');
|
|
322
|
+
const limpio = texto.replace(/\*/g, '').toUpperCase();
|
|
323
|
+
const letras = [...limpio];
|
|
324
|
+
const vistas = Math.min(letras.length, Math.floor(t / 0.035) + 1);
|
|
325
|
+
let tam = e.p.rotuloTam ?? tema.rotulo?.tam ?? 64;
|
|
326
|
+
try {
|
|
327
|
+
const w = ancho(limpio, PIXEL, tam, 400);
|
|
328
|
+
if (w > 900) tam = Math.max(30, Math.floor((tam * 900) / w));
|
|
329
|
+
} catch {}
|
|
330
|
+
const S = tema.subtitulos;
|
|
331
|
+
const y = e.p.rotuloY ?? tema.rotulo?.y ?? (S.tipo !== 'ninguno' && S.y < 600 ? S.y + 150 : 230);
|
|
332
|
+
return (
|
|
333
|
+
<div style={{position: 'absolute', left: 0, width: W, top: y, display: 'flex', justifyContent: 'center', pointerEvents: 'none'}}>
|
|
334
|
+
<div
|
|
335
|
+
style={{
|
|
336
|
+
transform: 'translateY(-50%)',
|
|
337
|
+
fontFamily: PIXEL,
|
|
338
|
+
fontSize: tam,
|
|
339
|
+
lineHeight: 1.15,
|
|
340
|
+
color: '#FFFFFF',
|
|
341
|
+
background: fondo,
|
|
342
|
+
padding: `${tam * 0.18}px ${tam * 0.3}px ${tam * 0.12}px`,
|
|
343
|
+
border: '4px solid rgba(255,255,255,0.9)',
|
|
344
|
+
boxShadow: '6px 6px 0 rgba(0,0,0,0.45)',
|
|
345
|
+
whiteSpace: 'nowrap',
|
|
346
|
+
imageRendering: 'pixelated',
|
|
347
|
+
}}
|
|
348
|
+
>
|
|
349
|
+
<span>{letras.slice(0, vistas).join('')}</span>
|
|
350
|
+
<span style={{visibility: 'hidden'}}>{letras.slice(vistas).join('')}</span>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
);
|
|
354
|
+
};
|
|
355
|
+
|
|
170
356
|
/** Pastilla fija de la serie arriba al centro: "GYM CON CIENCIA · PARTE 5". */
|
|
171
357
|
const Serie: React.FC<{texto: string; tema: Tema}> = ({texto, tema}) => {
|
|
172
358
|
const claro = tema.colores.texto !== '#FFFFFF';
|
|
@@ -205,10 +391,12 @@ export const VideoMotor: React.FC<{d: Datos; T: number; estilos?: Record<string,
|
|
|
205
391
|
const e = d.escenas[si];
|
|
206
392
|
const t = T - e.t0;
|
|
207
393
|
const semilla = d.id;
|
|
208
|
-
const conMundo = !!d.mundo?.length;
|
|
394
|
+
const conMundo = !!d.mundo?.length || Object.keys(d.lugares ?? {}).length > 0;
|
|
209
395
|
const DUR = tema.movimiento?.transicion ?? TRANSICION;
|
|
210
|
-
// con mundo, la transición es el viaje de la cámara: las capas de pantalla solo entran con su propia animación
|
|
211
|
-
|
|
396
|
+
// con mundo, la transición es el viaje de la cámara: las capas de pantalla solo entran con su propia animación.
|
|
397
|
+
// Al cambiar de lugar sí hay transición (la de la escena; corte si no dice), y el lugar anterior sale con ella.
|
|
398
|
+
const cambiaLugar = conMundo && si > 0 && lugarDe(d, si) !== lugarDe(d, si - 1);
|
|
399
|
+
const trans = conMundo ? (cambiaLugar ? ((e.p.transicion as string) ?? 'corte') : 'corte') : ((e.p.transicion ?? tema.transicion) as string);
|
|
212
400
|
const enTransicion = si > 0 && t < DUR && trans !== 'corte';
|
|
213
401
|
const lin = clamp(t / DUR);
|
|
214
402
|
const k = easeOut(lin);
|
|
@@ -244,15 +432,26 @@ export const VideoMotor: React.FC<{d: Datos; T: number; estilos?: Record<string,
|
|
|
244
432
|
}
|
|
245
433
|
return (
|
|
246
434
|
<AbsoluteFill style={{background: '#000', overflow: 'hidden'}}>
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
435
|
+
<Modo d={d} si={si} t={t} T={T} claro={LOOKS_CLAROS.includes(d.estilo ?? '') || tema.colores.texto !== '#FFFFFF'}>
|
|
436
|
+
{conMundo && enTransicion && prev ? (
|
|
437
|
+
<AbsoluteFill style={estiloPrevio}>
|
|
438
|
+
<Mundo d={d} si={si - 1} t={prev.dur + t} T={T} tema={tema} />
|
|
439
|
+
</AbsoluteFill>
|
|
440
|
+
) : null}
|
|
441
|
+
{conMundo ? (
|
|
442
|
+
<AbsoluteFill style={enTransicion ? estiloActual : undefined}>
|
|
443
|
+
<Mundo d={d} si={si} t={t} T={T} tema={tema} />
|
|
444
|
+
</AbsoluteFill>
|
|
445
|
+
) : null}
|
|
446
|
+
{enTransicion && prev ? (
|
|
447
|
+
<AbsoluteFill style={estiloPrevio}>
|
|
448
|
+
<EscenaVista e={prev} t={prev.dur + t} tema={tema} semilla={semilla} sinFondo={conMundo} />
|
|
449
|
+
</AbsoluteFill>
|
|
450
|
+
) : null}
|
|
451
|
+
<AbsoluteFill style={estiloActual}>
|
|
452
|
+
<EscenaVista e={e} t={t} tema={tema} semilla={semilla} sinFondo={conMundo} />
|
|
251
453
|
</AbsoluteFill>
|
|
252
|
-
|
|
253
|
-
<AbsoluteFill style={estiloActual}>
|
|
254
|
-
<EscenaVista e={e} t={t} tema={tema} semilla={semilla} sinFondo={conMundo} />
|
|
255
|
-
</AbsoluteFill>
|
|
454
|
+
</Modo>
|
|
256
455
|
{destello > 0 ? <AbsoluteFill style={{background: '#fff', opacity: destello}} /> : null}
|
|
257
456
|
<Post tema={tema} />
|
|
258
457
|
{/* la textura va quieta, fuera de la cámara: si se moviera con el zoom, cada cuadro sería distinto y el video pesaría 10 veces más */}
|
package/remotion/motor/capas.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import {Avatar, Icono, tamIcono, useMarca} from '../piezas';
|
|
|
6
6
|
import {H, W, ancho, backOut, clamp, easeIO, easeOut, easeOutExpo, lerp, resorte} from '../estilo';
|
|
7
7
|
import {ANOTACIONES, ANOTACIONES_SIN_ENTRADA, Anotacion} from './anotaciones';
|
|
8
8
|
import {FICHAS, Fichas} from './fichas';
|
|
9
|
+
import {HISTORIAS, Historias} from './historias';
|
|
9
10
|
import {azar, cuando, formatoNumero, partesResaltadas, recurso, type CtxEscena} from './util';
|
|
10
11
|
import {Particulas} from './fondo';
|
|
11
12
|
import {Escena3D, Modelo3D} from './modelo3d';
|
|
@@ -280,6 +281,7 @@ const Contenido: React.FC<{c: Capa; ctx: CtxEscena}> = ({c, ctx}) => {
|
|
|
280
281
|
default:
|
|
281
282
|
if (ANOTACIONES.includes(c.tipo)) return <Anotacion c={c} ctx={ctx} />;
|
|
282
283
|
if (FICHAS.includes(c.tipo)) return <Fichas c={c} ctx={ctx} />;
|
|
284
|
+
if (HISTORIAS.includes(c.tipo)) return <Historias c={c} ctx={ctx} />;
|
|
283
285
|
return <div style={{color: 'red', fontSize: 40, fontFamily: 'sans-serif', background: '#fff', padding: 10}}>capa desconocida: {c.tipo}</div>;
|
|
284
286
|
}
|
|
285
287
|
};
|