morfeo 0.3.4 → 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.
@@ -0,0 +1,35 @@
1
+ // Mundo de __ID__ (base biografía): tres sets en un solo componente, uno por `lugar` del guion (props.set):
2
+ // 'ciudad' (de día, el origen), 'casa' (cuarto de noche, los años difíciles) y 'noche' (ciudad de noche, el éxito).
3
+ // Cada set tiene su toma con un acercamiento lento (`a`); la cámara corta al cambiar de lugar.
4
+ // Piezas: Lienzo3D, Ciudad, Camino, Cuarto3D, Persona3D, Mesa, Pila. Cambia ropa, expresión y pose por set.
5
+ import React from 'react';
6
+ import {Camino, Ciudad, Cuarto3D, Lienzo3D, Persona3D, type Toma3D} from 'morfeo/kit';
7
+
8
+ type Props = {t: number; set?: 'ciudad' | 'casa' | 'noche'; escena?: number; tEscena?: number; inicios?: number[]};
9
+
10
+ const TOMAS: Record<string, Toma3D> = {
11
+ ciudad: {pos: [-6, 4.5, 10], mirar: [0, 1.2, 0], a: {pos: [-3.5, 2.6, 6.5]}},
12
+ casa: {pos: [2.6, 2.2, 4.6], mirar: [0, 1.1, -0.5], fov: 40, a: {pos: [1.8, 1.9, 3.6]}},
13
+ noche: {pos: [0, 6, 14], mirar: [0, 2, -3], a: {pos: [0, 4, 10]}},
14
+ };
15
+
16
+ export default function __COMPONENTE__({t, set = 'ciudad', tEscena = 0}: Props) {
17
+ const toma = TOMAS[set] ?? TOMAS.ciudad;
18
+ const noche = set !== 'ciudad';
19
+ return (
20
+ <Lienzo3D tomas={[toma]} escena={1} tEscena={tEscena} inicios={[0]} luz={noche ? 'calida' : 'dia'} hora={set === 'noche' ? 21 : set === 'casa' ? 20 : 12} fondo={noche ? '#141A33' : '#BFE6FF'} niebla={[25, 70]}>
21
+ {set === 'casa' ? (
22
+ <>
23
+ <Cuarto3D />
24
+ <Persona3D pos={[0, 0, 0]} t={t} expresion="pensativo" pose="sentado" ropa="#2F6FE0" semilla={1} />
25
+ </>
26
+ ) : (
27
+ <>
28
+ <Ciudad t={t} noche={noche} pos={[0, 0, -4]} filas={4} columnas={6} />
29
+ <Camino largo={50} ancho={5} />
30
+ <Persona3D pos={[0, 0, 1.5]} t={t} expresion={set === 'noche' ? 'feliz' : 'neutral'} pose={set === 'noche' ? 'brazosArriba' : 'caminar'} ropa="#2F6FE0" semilla={1} />
31
+ </>
32
+ )}
33
+ </Lienzo3D>
34
+ );
35
+ }
@@ -0,0 +1,34 @@
1
+ // __ID__ · base de biografía (npx morfeo nuevo-video __ID__ --look biografia). Fórmula "biografia": gancho de
2
+ // escándalo o secreto → origen (globo con pin) → ascenso año por año (tarjeta de año, cintillo con nombre) →
3
+ // el secreto (vigilancia, ficha) → final abierto. Cada época es un `lugar` (un set del mundo) y la cámara corta entre ellos.
4
+ export default {
5
+ estilo: 'documental',
6
+ formula: 'biografia',
7
+ voice: 'narrador',
8
+ music: 'synth',
9
+ title: 'El hombre que nadie vio venir',
10
+ descripcion: 'De un cuarto sin luz a la empresa más vigilada del mundo.',
11
+ tags: ['historias', 'biografías'],
12
+ lugares: {
13
+ ciudad: [{tipo: 'componente', nombre: '__COMPONENTE__', x: 540, y: 960, entrada: 'ninguna', props: {set: 'ciudad'}}],
14
+ casa: [{tipo: 'componente', nombre: '__COMPONENTE__', x: 540, y: 960, entrada: 'ninguna', props: {set: 'casa'}}],
15
+ noche: [{tipo: 'componente', nombre: '__COMPONENTE__', x: 540, y: 960, entrada: 'ninguna', props: {set: 'noche'}}],
16
+ },
17
+ scenes: [
18
+ // 1 · gancho de secreto, con el protagonista ya en pantalla
19
+ {say: 'A Daniel no lo descubrió su familia: lo descubrió una cámara.', claves: ['cámara'], lugar: 'noche', capas: [{tipo: 'vigilancia', x: 540, y: 960, hora: '23:47:10', etiqueta: 'CAM 03', entrada: 'ninguna'}]},
20
+ // 2 · origen con el globo
21
+ {say: 'Imagina un pueblo junto al mar: ahí empieza todo.', claves: ['mar'], lugar: 'ciudad', transicion: 'destello', capas: [{tipo: 'tierra', x: 540, y: 1000, lat: 19.4, lon: -99.1, etiqueta: 'MÉXICO · 1980'}]},
22
+ {say: 'Daniel era el más pequeño de cinco hermanos.', claves: ['Daniel'], capas: [{tipo: 'cintillo', x: 330, y: 1560, nombre: 'Daniel Ramos', cargo: 'fundador', en: 'Daniel'}]},
23
+ // 3 · años difíciles
24
+ {say: 'A los quince años trabajaba de noche lavando platos.', claves: ['quince'], lugar: 'casa', transicion: 'barrido', capas: [{tipo: 'fecha', x: 540, y: 1480, anio: 1995, de: 1980, pie: 'lava platos de noche', en: 'quince'}]},
25
+ {say: '¿Y de dónde sacó la idea?', rotulo: '¿DE *DÓNDE*?'},
26
+ {say: 'De un cuaderno donde anotaba todo lo que se rompía.', claves: ['cuaderno']},
27
+ // 4 · ascenso
28
+ {say: 'En dos mil diez abrió su primera empresa.', show: 'En 2010 abrió su primera empresa.', claves: ['empresa'], lugar: 'noche', transicion: 'destello', capas: [{tipo: 'fecha', x: 540, y: 1480, anio: 2010, de: 1995, pie: 'su primera empresa', en: '2010'}]},
29
+ {say: 'Quince años después, la vigilan tres gobiernos.', claves: ['vigilan'], rotulo: 'VIGILADA', rotuloEstilo: 'sello', rotuloEn: 'vigilan', capas: [{tipo: 'calendario', x: 860, y: 560, titulo: 'AÑOS', de: 0, a: 15, en: 'después'}]},
30
+ // 5 · final abierto
31
+ {say: 'Y la cámara que lo descubrió todavía sigue grabando.', claves: ['grabando'], capas: [{tipo: 'vigilancia', x: 540, y: 960, hora: '23:59:40', etiqueta: 'CAM 03', entrada: 'ninguna'}]},
32
+ {type: 'cta', pide: 'comenta', palabra: 'PARTE 2', say: 'Escribe PARTE DOS y te cuento qué grabó.'},
33
+ ],
34
+ };
@@ -0,0 +1,273 @@
1
+ // Diorama de referencia del look "maqueta" (estilo maqueta): un cruce con semáforos inteligentes sobre una isla con
2
+ // corte de tierra. Una toma por escena (cortes cada ~3 s), tráfico que hace fila en el rojo, sensores bajo el asfalto,
3
+ // cable en el corte, gabinete con rayos X, cambio a noche, ambulancia y confeti. Todo depende de `t`.
4
+ // Va como `mundo`: mundo: [{tipo: 'componente', nombre: '__COMPONENTE__', props: {ambulancia: {tipo: 'recurso', ...}}}]
5
+ import React from 'react';
6
+ import {Arbol, Arbusto, Bloque, Coche, Confeti3D, Corte, Lienzo3D, Maqueta, Modelo, Persona, Trafico, Tubo, azarN, type Etiqueta3D, type Toma3D} from 'morfeo/kit';
7
+
8
+ type Props = {t: number; escena?: number; tEscena?: number; inicios?: number[]; ambulancia?: {src: string}; camion?: {src: string}};
9
+ type V3 = [number, number, number];
10
+
11
+ const ANCHO = 24;
12
+ const FONDO = 18;
13
+ const FRENTE = FONDO / 2; // z de la cara del corte que mira a la cámara
14
+ const ZC = 6; // centro de la calle este-oeste (cerca del corte para ver los cables)
15
+ const CARRIL_E = ZC + 0.8; // hacia +x
16
+ const CARRIL_O = ZC - 0.8; // hacia -x
17
+ const CARRIL_N = 0.8; // calle norte-sur: hacia +z
18
+
19
+ // estado del semáforo este-oeste por escena (V verde, R rojo, I intermitente). El norte-sur va al revés.
20
+ const EW = 'VVRRVVRRRRVVVRVVRVVVVRIRVVVVV';
21
+
22
+ const TOMAS: Toma3D[] = [
23
+ /* 1 imagina */ {pos: [-17, 14, 27], mirar: [0, -1, 3], fov: 40, a: {pos: [-15, 12.5, 24]}},
24
+ /* 2 30 mil */ {pos: [-10, 3.6, 14], mirar: [-3, 0.4, CARRIL_E], a: {pos: [-8.5, 3.2, 13]}},
25
+ /* 3 caos */ {pos: [-0.5, 2.6, 12.8], mirar: [-7, 0.4, CARRIL_E], corte: true},
26
+ /* 4 tontos */ {pos: [-6.8, 3.8, 13.4], mirar: [-2.4, 2.5, 8.2], a: {pos: [-6.2, 3.5, 12.6]}},
27
+ /* 5 90 s */ {pos: [-7, 5.5, 15], mirar: [-1, 0.8, 7]},
28
+ /* 6 ¿cuántos? */ {pos: [0.5, 21, 13], mirar: [0, 0, 5], fov: 42},
29
+ /* 7 sensores */ {pos: [-6.8, 0.9, 9.6], mirar: [-3.6, 0.05, CARRIL_E], a: {pos: [-6, 0.8, 9.2]}},
30
+ /* 8 espiras */ {pos: [-3, -1.6, 16], mirar: [-2.5, -1.4, FRENTE], a: {pos: [-1.5, -1.5, 15]}},
31
+ /* 9 avisan */ {pos: [-5.8, 2.4, 10.5], mirar: [-3.6, 0.3, CARRIL_E]},
32
+ /* 10 cámara */ {pos: [5.5, 6.4, 12.5], mirar: [2.4, 4.6, 8.2], a: {pos: [4.8, 5.8, 11.5]}},
33
+ /* 11 cable */ {pos: [-2, -0.8, 16.5], mirar: [-1, -1.1, FRENTE], a: {pos: [3.5, -0.8, 15.5], mirar: [3.5, -1.1, FRENTE]}},
34
+ /* 12 gabinete */ {pos: [7.2, 2.6, 13.4], mirar: [4.4, 1, 8.2]},
35
+ /* 13 cerebro */ {pos: [5.6, 1.9, 11.6], mirar: [4.4, 1, 8.2], a: {pos: [5.1, 1.6, 10.8]}},
36
+ /* 14 cuentas */ {pos: [4.7, 1.25, 10.1], mirar: [4.4, 0.95, 8.2]},
37
+ /* 15 más verde */ {pos: [-9, 6.5, 17], mirar: [0, 0, ZC], a: {pos: [-7.5, 5.5, 15.5]}},
38
+ /* 16 vacía */ {pos: [6.5, 4.2, -8], mirar: [0.8, 0, -1.5], corte: true},
39
+ /* 17 ¿resultado? */ {pos: [0, 10, 21], mirar: [0, 1, 4]},
40
+ /* 18 mitad */ {pos: [-11, 4, 12.5], mirar: [-2, 0.4, ZC]},
41
+ /* 19 vecinos */ {pos: [15, 25, 22], mirar: [0, -2, 0], fov: 46, a: {pos: [13, 23, 19]}},
42
+ /* 20 ola verde */ {pos: [11, 27, 15], mirar: [0, -1, 1], fov: 46},
43
+ /* 21 8 cuadras */ {pos: [6.5, 1.6, 10.6], mirar: [0, 0.6, CARRIL_E], a: {pos: [5, 1.5, 10]}},
44
+ /* 22 noche */ {pos: [-15, 12, 23], mirar: [0, 0, 3], fov: 40, a: {pos: [-13, 10.5, 20.5]}},
45
+ /* 23 intermitente */ {pos: [-6.4, 3.4, 12.8], mirar: [-2.4, 2.6, 8.2]},
46
+ /* 24 ambulancia */ {pos: [-11, 2.2, 11.5], mirar: [-5, 0.8, CARRIL_E], a: {pos: [-9, 2, 11]}},
47
+ /* 25 toda la avenida */ {pos: [-13, 7.5, 15], mirar: [2, 0, ZC]},
48
+ /* 26 próxima vez */ {pos: [8.5, 6, 16.5], mirar: [0, 1, ZC], a: {pos: [7.5, 5.4, 15]}},
49
+ /* 27 no fue suerte */ {pos: [4.6, 2.6, 12.2], mirar: [0, 0.6, CARRIL_E]},
50
+ /* 28 felicidades */ {pos: [-6.4, 3.3, 13.8], mirar: [-2.8, 1.9, 8.2], a: {pos: [-5.8, 3, 12.8]}},
51
+ /* 29 cierre */ {pos: [-17, 14, 27], mirar: [0, -1, 3], fov: 40},
52
+ ];
53
+
54
+ const ETIQUETAS: Etiqueta3D[] = [
55
+ {escena: 4, pos: [-2.4, 3.2, 8.2], texto: 'SEMÁFORO', color: '#2D7BFF', lado: 'der', en: 0.4},
56
+ {escena: [7, 9], pos: [-3.6, 0.05, CARRIL_E], texto: 'SENSOR', color: '#FF7A1A', lado: 'arriba', en: 0.5},
57
+ {escena: 8, pos: [-3.6, -0.55, FRENTE + 0.1], texto: 'ESPIRA DE CABLE', color: '#FF7A1A', lado: 'arriba', en: 0.6},
58
+ {escena: 10, pos: [2.4, 4.85, 8.2], texto: 'CÁMARA', color: '#8E5BD9', lado: 'izq', en: 0.4},
59
+ {escena: 11, pos: [1, -0.95, FRENTE + 0.2], texto: 'CABLE', color: '#2BB673', lado: 'arriba', en: 0.5},
60
+ {escena: 12, pos: [4.4, 1.5, 8.2], texto: 'GABINETE', color: '#2D7BFF', lado: 'arriba', en: 0.4},
61
+ {escena: 13, pos: [4.4, 1.05, 8.3], texto: 'COMPUTADORA', color: '#2BB673', lado: 'izq', en: 1.2},
62
+ {escena: 24, pos: [-5, 1.3, CARRIL_E], texto: 'PRIORIDAD', color: '#E5484D', lado: 'arriba', en: 0.8},
63
+ ];
64
+
65
+ /** Semáforo de poste: `estado` 'V' | 'A' | 'R' | 'I' (amarillo intermitente). `rot` en grados hacia donde mira. */
66
+ const Semaforo: React.FC<{pos: V3; rot: number; estado: string; t: number}> = ({pos, rot, estado, t}) => {
67
+ const luz = (c: string, on: boolean, y: number) => (
68
+ <mesh position={[0, y, 0.2]}>
69
+ <sphereGeometry args={[0.13, 14, 10]} />
70
+ <meshStandardMaterial color={on ? c : '#2A2C33'} emissive={on ? c : '#000000'} emissiveIntensity={on ? 2.2 : 0} roughness={0.4} />
71
+ </mesh>
72
+ );
73
+ const parpadea = Math.sin(t * 7) > 0;
74
+ return (
75
+ <group position={pos} rotation={[0, (rot * Math.PI) / 180, 0]}>
76
+ <Bloque tam={[0.16, 3, 0.16]} pos={[0, 1.5, 0]} color="#5A5F6B" metal={0.4} rugosidad={0.5} />
77
+ <group position={[0, 2.85, 0.05]}>
78
+ <Bloque tam={[0.42, 1.05, 0.3]} pos={[0, 0, 0]} color="#1E2026" rugosidad={0.6} />
79
+ {luz('#FF3B30', estado === 'R', 0.32)}
80
+ {luz('#FFC400', estado === 'A' || (estado === 'I' && parpadea), 0)}
81
+ {luz('#34E07A', estado === 'V', -0.32)}
82
+ </group>
83
+ </group>
84
+ );
85
+ };
86
+
87
+ const Farola: React.FC<{pos: V3; noche: number}> = ({pos, noche}) => (
88
+ <group position={pos}>
89
+ <Bloque tam={[0.1, 2.6, 0.1]} pos={[0, 1.3, 0]} color="#4A4F5A" metal={0.5} rugosidad={0.5} />
90
+ <mesh position={[0, 2.62, 0]}>
91
+ <sphereGeometry args={[0.2, 12, 8]} />
92
+ <meshStandardMaterial color="#FFF3C4" emissive="#FFD27A" emissiveIntensity={0.2 + noche * 3} />
93
+ </mesh>
94
+ {noche > 0.05 ? <pointLight position={[0, 2.4, 0]} color="#FFD58A" intensity={noche * 9} distance={7} decay={1.6} /> : null}
95
+ </group>
96
+ );
97
+
98
+ export default function __COMPONENTE__({t, escena = 1, tEscena = 0, inicios = [0], ambulancia, camion}: Props) {
99
+ const ini = (n: number) => inicios[n - 1] ?? 1e9;
100
+ const dur = (n: number) => ini(n + 1) - ini(n);
101
+ const est = (n: number) => EW[Math.max(0, Math.min(EW.length - 1, n - 1))];
102
+ // estado de cada sentido en este cuadro: amarillo los últimos 0,7 s antes de pasar de verde a rojo
103
+ const ew = (() => {
104
+ const e = est(escena);
105
+ if (e === 'V' && est(escena + 1) === 'R' && tEscena > dur(escena) - 0.7) return 'A';
106
+ return e;
107
+ })();
108
+ const ns = (() => {
109
+ if (ew === 'I') return 'I';
110
+ if (escena >= 24 && escena <= 25) return 'R'; // ambulancia: toda la avenida en verde
111
+ const e = est(escena) === 'V' || est(escena) === 'A' ? 'R' : 'V';
112
+ if (e === 'V' && est(escena + 1) === 'V' && tEscena > dur(escena) - 0.7) return 'A';
113
+ return e;
114
+ })();
115
+ // intervalos cerrados (rojo) de cada sentido, en segundos del video
116
+ const cerrados = (rojo: (n: number) => boolean): [number, number][] => {
117
+ const out: [number, number][] = [];
118
+ for (let n = 1; n <= EW.length; n++) {
119
+ if (!rojo(n)) continue;
120
+ const a = ini(n);
121
+ const b = ini(n + 1);
122
+ if (out.length && Math.abs(out[out.length - 1][1] - a) < 0.01) out[out.length - 1][1] = b;
123
+ else out.push([a, b]);
124
+ }
125
+ return out;
126
+ };
127
+ const rojoEW = cerrados((n) => est(n) === 'R');
128
+ const rojoNS = cerrados((n) => est(n) === 'V' || (n >= 24 && n <= 25));
129
+
130
+ // hora: de día; anochece en la escena 22 y amanece en la 26
131
+ const hora = escena < 22 ? 12 : escena === 22 ? 13 + Math.min(1, tEscena / Math.max(1, dur(22))) * 8.5 : escena < 26 ? 21.5 : 21.5 + Math.min(1, tEscena / 1.2) * 14.5;
132
+ const noche = escena >= 22 && escena <= 26 ? Math.max(0, Math.min(1, (hora - 18.5) / 2.5, (escena === 26 ? 1 - tEscena / 1.2 : 1))) : 0;
133
+
134
+ // sensores: brillan cuando hay fila en el rojo
135
+ const sensorOn = ew === 'R' && escena >= 7;
136
+ // rayos X del gabinete: se abre en la escena 13 y se queda abierto hasta la 14
137
+ const abrir = escena === 13 ? Math.min(1, Math.max(0, (tEscena - 0.5) / 0.9)) : escena === 14 ? 1 : 0;
138
+ // pulsos por el cable (escenas 8, 9, 11): el "agua" del tubo avanza y se reinicia
139
+ const pulso = [8, 9, 11, 12].includes(escena) ? (tEscena * 0.7) % 1.15 : 0;
140
+ // la ambulancia cruza en las escenas 24 y 25
141
+ const tAmb = escena === 24 ? tEscena : escena === 25 ? dur(24) + tEscena : -1;
142
+ const xAmb = -13 + tAmb * 4.2;
143
+
144
+ return (
145
+ <Lienzo3D
146
+ tomas={TOMAS}
147
+ escena={escena}
148
+ tEscena={tEscena}
149
+ inicios={inicios}
150
+ hora={hora}
151
+ niebla={[40, 95]}
152
+ etiquetas={ETIQUETAS}
153
+ >
154
+ <Maqueta
155
+ ancho={ANCHO}
156
+ fondo={FONDO}
157
+ semilla={4}
158
+ libres={[
159
+ [0, ZC, ANCHO, 4.6],
160
+ [0, 0, 4.6, FONDO],
161
+ [4.4, 8.2, 1.6, 1.4],
162
+ ]}
163
+ />
164
+ {/* calles, banquetas y rayas */}
165
+ <Bloque tam={[ANCHO, 0.05, 3.2]} pos={[0, 0.03, ZC]} color="#3E4149" />
166
+ <Bloque tam={[3.2, 0.05, FONDO]} pos={[0, 0.031, 0]} color="#3E4149" />
167
+ {Array.from({length: 10}, (_, i) => {
168
+ const x = -11 + i * 2.4;
169
+ return Math.abs(x) < 2 ? null : <Bloque key={`e${i}`} tam={[1.1, 0.02, 0.12]} pos={[x, 0.065, ZC]} color="#F4F4F4" />;
170
+ })}
171
+ {Array.from({length: 8}, (_, i) => {
172
+ const z = -8 + i * 2.4;
173
+ return Math.abs(z - ZC) < 2 ? null : <Bloque key={`n${i}`} tam={[0.12, 0.02, 1.1]} pos={[0, 0.066, z]} color="#F4F4F4" />;
174
+ })}
175
+ {/* paso de cebra */}
176
+ {Array.from({length: 6}, (_, i) => (
177
+ <Bloque key={`c${i}`} tam={[0.9, 0.02, 0.32]} pos={[-2.1, 0.067, ZC - 1.25 + i * 0.5]} color="#FFFFFF" />
178
+ ))}
179
+ {/* sensores bajo el carril (espiras) */}
180
+ {[-4.4, -3.2].map((x, i) => (
181
+ <mesh key={i} position={[x, 0.07, CARRIL_E]} rotation={[-Math.PI / 2, 0, 0]}>
182
+ <ringGeometry args={[0.32, 0.42, 4, 1]} />
183
+ <meshStandardMaterial color={sensorOn ? '#FFB020' : '#7B7F88'} emissive={sensorOn ? '#FF9A00' : '#000000'} emissiveIntensity={sensorOn ? 1.6 + Math.sin(t * 10) * 0.6 : 0} />
184
+ </mesh>
185
+ ))}
186
+ {/* espira y cable en el corte: del sensor al gabinete */}
187
+ <Tubo
188
+ puntos={[
189
+ [-4.4, -0.35, FRENTE + 0.14],
190
+ [-3.2, -0.35, FRENTE + 0.14],
191
+ [-3.2, -1.1, FRENTE + 0.14],
192
+ [4.4, -1.1, FRENTE + 0.14],
193
+ [4.4, -0.1, FRENTE + 0.14],
194
+ ]}
195
+ radio={0.12}
196
+ color="#B5BAC4"
197
+ opacidad={0.5}
198
+ agua={Math.min(1, pulso)}
199
+ colorAgua="#FFD23F"
200
+ />
201
+ {/* semáforos en las esquinas */}
202
+ <Semaforo pos={[-2.4, 0, 8.2]} rot={-90} estado={ew} t={t} />
203
+ <Semaforo pos={[2.4, 0, 3.8]} rot={90} estado={ew} t={t} />
204
+ <Semaforo pos={[-2.4, 0, 3.8]} rot={180} estado={ns} t={t} />
205
+ {/* poste con cámara */}
206
+ <group position={[2.4, 0, 8.2]}>
207
+ <Bloque tam={[0.14, 4.6, 0.14]} pos={[0, 2.3, 0]} color="#5A5F6B" metal={0.4} rugosidad={0.5} />
208
+ <Bloque tam={[0.9, 0.08, 0.08]} pos={[-0.4, 4.55, 0]} color="#5A5F6B" />
209
+ <Bloque tam={[0.42, 0.24, 0.24]} pos={[-0.8, 4.4, 0]} color="#ECEFF4" rugosidad={0.4} />
210
+ <mesh position={[-1.02, 4.4, 0]} rotation={[0, 0, Math.PI / 2]}>
211
+ <cylinderGeometry args={[0.08, 0.08, 0.06, 12]} />
212
+ <meshStandardMaterial color="#1B1B1F" emissive="#3A7BFF" emissiveIntensity={0.6} />
213
+ </mesh>
214
+ </group>
215
+ {/* gabinete con rayos X: la carcasa se recorta y deja ver la computadora */}
216
+ <group position={[4.4, 0, 8.2]}>
217
+ <Corte k={abrir} eje="z" centro={0} tam={0.5}>
218
+ <Bloque tam={[1.1, 1.5, 0.7]} pos={[0, 0.75, 0]} color="#A8B4C2" metal={0.3} rugosidad={0.5} />
219
+ </Corte>
220
+ <Bloque tam={[0.9, 1.2, 0.04]} pos={[0, 0.8, -0.2]} color="#1F7A4A" rugosidad={0.6} />
221
+ {[0.25, 0.55, 0.85, 1.1].map((y, i) => (
222
+ <Bloque key={i} tam={[0.5 - (i % 2) * 0.2, 0.14, 0.08]} pos={[-0.1 + (i % 2) * 0.15, y, -0.15]} color="#1B1B1F" />
223
+ ))}
224
+ {[0.3, 0.6, 0.9].map((y, i) => (
225
+ <mesh key={i} position={[0.32, y, -0.12]}>
226
+ <sphereGeometry args={[0.04, 8, 6]} />
227
+ <meshStandardMaterial color="#34E07A" emissive="#34E07A" emissiveIntensity={Math.sin(t * 8 + i * 2) > 0 ? 2.5 : 0.2} />
228
+ </mesh>
229
+ ))}
230
+ </group>
231
+ {/* tráfico */}
232
+ <Trafico camino={[[-11.6, 0.06, CARRIL_E], [11.6, 0.06, CARRIL_E]]} t={t} n={5} velocidad={2.6} separacion={3.2} pluma={{d: 11.6 - 2.9, cerrada: rojoEW}} semilla={0} />
233
+ <Trafico camino={[[11.6, 0.06, CARRIL_O], [-11.6, 0.06, CARRIL_O]]} t={t} n={4} velocidad={2.6} separacion={3.4} desfase={5} pluma={{d: 11.6 - 2.9, cerrada: rojoEW}} semilla={3} />
234
+ <Trafico camino={[[CARRIL_N, 0.06, -8.6], [CARRIL_N, 0.06, 8.6]]} t={t} n={4} velocidad={2.4} separacion={3.2} desfase={2} pluma={{d: 8.6 + ZC - 2.4, cerrada: rojoNS}} semilla={5} />
235
+ {tAmb >= 0 && Math.abs(xAmb) < 11.6 ? (
236
+ ambulancia ? (
237
+ <Modelo src={ambulancia} pos={[xAmb, 0.06, CARRIL_E - 0.05]} rot={[0, 90, 0]} tam={2.6} />
238
+ ) : (
239
+ <Coche pos={[xAmb, 0.06, CARRIL_E]} color="#FFFFFF" techo="#E5484D" t={t} />
240
+ )
241
+ ) : null}
242
+ {camion ? <Modelo src={camion} pos={[-8.5, 0.06, 2.4]} rot={[0, 90, 0]} tam={3} /> : null}
243
+ {/* gente */}
244
+ <Persona pos={[5.5, 0, 7.6]} rot={[0, -60, 0]} pose={escena === 12 ? 'saludar' : escena === 13 || escena === 14 ? 'senalar' : 'parado'} t={t} escala={0.9} />
245
+ {escena >= 27 ? <Persona pos={[-3.6, 0, 8.4]} rot={[0, 20, 0]} pose="celebrar" t={t} escala={0.9} casco={false} chaleco={false} ropa="#E5484D" /> : null}
246
+ <Confeti3D pos={[-2.4, 3.3, 8.2]} t={t} en={ini(28) + 0.3} n={90} />
247
+ {/* farolas */}
248
+ {([[-6, 0, 8.4], [6.5, 0, 3.6], [-6.5, 0, 3.6], [8, 0, 8.4]] as V3[]).map((p, i) => (
249
+ <Farola key={i} pos={p} noche={noche} />
250
+ ))}
251
+ {/* casas y árboles alrededor */}
252
+ {([[-8, 0, -4, '#F2E6D0', '#D9533B'], [-4.5, 0, -6, '#DCEBFA', '#3D6FB6'], [6, 0, -5, '#FBE3C8', '#7A4FD1'], [8.5, 0, -1.5, '#E9F4E2', '#2E8B57']] as const).map(([x, y, z, c, tc], i) => (
253
+ <group key={i} position={[x, y, z]} scale={1.15}>
254
+ <Bloque tam={[2.2, 1.6, 2]} pos={[0, 0.8, 0]} color={c} />
255
+ <mesh position={[0, 2.05, 0]} rotation={[0, Math.PI / 4, 0]} castShadow>
256
+ <coneGeometry args={[1.8, 0.95, 4]} />
257
+ <meshStandardMaterial color={tc} roughness={0.8} flatShading />
258
+ </mesh>
259
+ </group>
260
+ ))}
261
+ {Array.from({length: 18}, (_, i) => {
262
+ const x = -11 + azarN(i * 3 + 7) * 22;
263
+ const z = -8.3 + azarN(i * 5 + 1) * 16.6;
264
+ if (Math.abs(z - ZC) < 2.6 || Math.abs(x) < 2.6) return null;
265
+ if ([[-8, -4], [-4.5, -6], [6, -5], [8.5, -1.5], [4.4, 8.2]].some(([hx, hz]) => Math.hypot(x - hx, z - hz) < 2.2)) return null;
266
+ return <Arbol key={i} pos={[x, 0, z]} semilla={i} escala={0.9 + azarN(i) * 0.6} color={i % 3 ? '#3F9B3A' : '#58B04A'} />;
267
+ })}
268
+ {([[-10.5, 0, 8.3], [10.8, 0, 8.2], [-10.8, 0, 3.4], [10.6, 0, 3.6]] as V3[]).map((p, i) => (
269
+ <Arbusto key={i} pos={p} escala={0.8 + azarN(i + 3) * 0.4} />
270
+ ))}
271
+ </Lienzo3D>
272
+ );
273
+ }
@@ -0,0 +1,84 @@
1
+ // __ID__ · base "construiste" (npx morfeo nuevo-video __ID__ --look construiste). Fórmula "construiste": lo obvio →
2
+ // falla ("pero…") → arreglo con la pieza real, 4 a 7 rondas → "¡Felicidades, construiste X!". El mundo
3
+ // (escenas/__COMPONENTE__.tsx) es un diorama con una toma por escena: TOMAS y el patrón del semáforo (EW) tienen una
4
+ // entrada por escena; si agregas o quitas escenas, ajústalos. Las palabras de `en:` deben seguir en el `say`.
5
+ const ARRIBA = 640; // altura de las tarjetas (bajo el rótulo, sobre el subtítulo)
6
+
7
+ export default {
8
+ estilo: 'maqueta',
9
+ formula: 'construiste',
10
+ voice: 'narrador',
11
+ music: 'chillpop',
12
+ title: 'Construiste un semáforo inteligente',
13
+ mundo: [
14
+ {
15
+ tipo: 'componente',
16
+ nombre: '__COMPONENTE__',
17
+ x: 540,
18
+ y: 960,
19
+ entrada: 'ninguna',
20
+ props: {ambulancia: {tipo: 'recurso', buscar: 'ambulancia', estilo: '3d'}, camion: {tipo: 'recurso', buscar: 'camión', estilo: '3d'}},
21
+ },
22
+ ],
23
+ scenes: [
24
+ {say: 'Si pones tres luces en un cruce, ya tienes un semáforo.', claves: ['semáforo']},
25
+ {say: 'Pero por tu cruce pasan treinta mil coches al día.', show: 'Pero por tu cruce pasan 30 000 coches al día.', claves: ['treinta'], rotulo: '30 000 *COCHES*', rotuloEn: '30'},
26
+ {say: 'Y si las cambias a mano, se arma el caos.', claves: ['caos'], rotulo: '¡CAOS!', rotuloEstilo: 'pastilla', rotuloEn: 'caos'},
27
+ {
28
+ say: 'Así que le pones un reloj, como los semáforos de antes.',
29
+ claves: ['tontos'],
30
+ capas: [{tipo: 'ficha', titulo: 'Credencial del cruce', campos: [['Modelo', '1995'], ['Cerebro', 'Temporizador'], ['Sensores', 'Ninguno']], x: 540, y: 1420, ancho: 600, color: '#2D7BFF', en: 'semáforos'}],
31
+ },
32
+ {
33
+ say: 'Pero cambia cada noventa segundos, aunque no haya nadie.',
34
+ show: 'Pero cambia cada 90 segundos, aunque no haya nadie.',
35
+ claves: ['noventa'],
36
+ rotulo: 'CADA *90 S*',
37
+ capas: [{tipo: 'cronometro', de: 0, a: 90, max: 90, x: 540, y: ARRIBA + 60, tam: 260, en: '90', duracion: 1.6}],
38
+ },
39
+ {say: '¿Y cómo sabe cuántos coches vienen?', claves: ['cuántos'], rotulo: '¿CUÁNTOS *VIENEN*?'},
40
+ {say: 'Le metes sensores escondidos bajo el pavimento.', claves: ['sensores']},
41
+ {say: 'Espiras de cable que sienten el metal de cada coche.', claves: ['metal']},
42
+ {say: 'Pero si es una moto chica, casi no avisan.', claves: ['avisan']},
43
+ {say: 'Así que en lo alto pones una cámara que cuenta la fila.', claves: ['cámara']},
44
+ {say: 'Pero los datos sueltos no sirven: los mandas por un cable…', claves: ['cable']},
45
+ {say: 'hasta este gabinete en la esquina.', claves: ['gabinete']},
46
+ {say: 'Y adentro le pones un cerebro.', claves: ['cerebro'], rotulo: 'TU *CEREBRO*', rotuloEn: 'cerebro'},
47
+ {
48
+ say: 'Una computadora que hace cuentas cada segundo.',
49
+ claves: ['cuentas'],
50
+ capas: [{tipo: 'checklist', items: [{texto: 'Cuenta coches', en: 'computadora'}, {texto: 'Mide la fila', en: 'cuentas'}, {texto: 'Decide', en: 'segundo'}], x: 540, y: ARRIBA - 40, tam: 48}],
51
+ },
52
+ {say: 'Si la fila crece de este lado, le da más verde.', claves: ['verde']},
53
+ {say: 'Si la calle está vacía, no la hace esperar.', claves: ['vacía']},
54
+ {say: '¿El resultado?', claves: ['resultado'], rotulo: '¿EL *RESULTADO*?'},
55
+ {
56
+ say: 'Las esperas bajan casi a la mitad.',
57
+ claves: ['mitad'],
58
+ rotulo: '*MITAD* DE ESPERA',
59
+ rotuloEn: 'mitad',
60
+ capas: [{tipo: 'cronometro', de: 90, a: 45, max: 90, x: 540, y: ARRIBA + 60, tam: 260, color: '#2BB673', en: 'esperas', duracion: 1.4}],
61
+ },
62
+ {say: 'Pero el siguiente cruce te frena igual, así que conectas a los vecinos…', claves: ['vecinos']},
63
+ {
64
+ say: 'se ponen de acuerdo para hacer una ola verde.',
65
+ claves: ['ola'],
66
+ rotulo: 'OLA *VERDE*',
67
+ rotuloEn: 'ola',
68
+ capas: [{tipo: 'mapa', distancia: '8 CUADRAS', color: '#34C76A', x: 540, y: ARRIBA + 40, ancho: 560, en: 'acuerdo'}],
69
+ },
70
+ {say: 'Un coche puede cruzar ocho cuadras sin frenar.', show: 'Un coche puede cruzar 8 cuadras sin frenar.', claves: ['ocho']},
71
+ {
72
+ say: 'Pero de noche casi no pasa nadie.',
73
+ claves: ['noche'],
74
+ capas: [{tipo: 'reloj', de: 0, a: 22, max: 24, segmentos: [[0, 7, '#2D3A6B'], [7, 19, '#FFC928'], [19, 24, '#2D3A6B']], x: 540, y: ARRIBA + 40, tam: 260, en: 'noche'}],
75
+ },
76
+ {say: 'Así que si no viene nadie, se pone en amarillo intermitente.', claves: ['amarillo']},
77
+ {say: 'Pero si se acerca una ambulancia…', claves: ['ambulancia'], rotulo: '¡AMBULANCIA!', rotuloEstilo: 'pastilla', rotuloEn: 'ambulancia'},
78
+ {say: 'le abre paso a toda la avenida.', claves: ['paso']},
79
+ {say: 'Así que la próxima vez que te toque verde…', claves: ['verde']},
80
+ {say: 'no fue suerte: alguien te estaba contando.', claves: ['suerte']},
81
+ {say: '¡Felicidades, construiste un semáforo inteligente!', claves: ['felicidades'], rotulo: '¡FELICIDADES!', rotuloEn: 'felicidades'},
82
+ {type: 'cta', pide: 'comenta', palabra: 'PUENTE', say: 'Escribe PUENTE y lo construimos en el próximo.'},
83
+ ],
84
+ };
@@ -0,0 +1,45 @@
1
+ // Mundo de __ID__ (base escalada): una mochila que suma ruedas, motor y asiento en la palabra de cada escena,
2
+ // luego la multitud y la mano en primera persona que aprieta el control.
3
+ import React from 'react';
4
+ import {Aparece, Asiento, Bloque, Camino, Lienzo3D, Mano3D, Motor, Multitud3D, Persona3D, Rueda, type Toma3D} from 'morfeo/kit';
5
+
6
+ type Props = {t: number; cuando: (en: unknown) => number; escena?: number; tEscena?: number; inicios?: number[]};
7
+
8
+ const TOMAS: Toma3D[] = [
9
+ {pos: [3.2, 2.2, 5.2], mirar: [0, 0.6, 0], a: {pos: [2.6, 1.9, 4.4]}},
10
+ {pos: [-3, 2, 4.6], mirar: [0, 0.5, 0], a: {pos: [-2.4, 1.7, 4]}},
11
+ {pos: [2.6, 1.6, 4], mirar: [0, 0.6, 0]},
12
+ {pos: [0, 2.4, 5.5], mirar: [0, 0.8, 0], a: {pos: [0.4, 2, 4.6]}},
13
+ {pos: [-12, 9, 16], mirar: [0, 0, -2], a: {pos: [-10, 8, 13]}},
14
+ {pos: [0, 1.6, 6], mirar: [0, 0.9, 0], fov: 40},
15
+ {pos: [-4, 3, 7], mirar: [0, 0.8, 0], a: {pos: [-3.4, 2.6, 6]}},
16
+ ];
17
+
18
+ export default function __COMPONENTE__({t, cuando, escena = 1, tEscena = 0, inicios = [0]}: Props) {
19
+ const lejos = escena === 5;
20
+ return (
21
+ <Lienzo3D tomas={TOMAS} escena={escena} tEscena={tEscena} inicios={inicios} luz="dia" fondo="#CFE9FF" niebla={[25, 70]}>
22
+ <Camino largo={60} ancho={6} />
23
+ {!lejos ? (
24
+ <group>
25
+ <Bloque pos={[0, 0.7, 0]} tam={[0.9, 1.0, 0.6]} color="#2F6FE0" radio={0.12} />
26
+ <Bloque pos={[0, 0.95, 0.33]} tam={[0.6, 0.35, 0.08]} color="#F2B632" />
27
+ <Aparece t={t} en={cuando('ruedas')} desde="arriba">
28
+ <Rueda pos={[-0.5, 0.25, 0]} radio={0.25} />
29
+ <Rueda pos={[0.5, 0.25, 0]} radio={0.25} />
30
+ </Aparece>
31
+ <Aparece t={t} en={cuando('motor')} desde="crece">
32
+ <Motor pos={[0, 0.45, -0.65]} t={t} />
33
+ </Aparece>
34
+ <Aparece t={t} en={cuando('asiento')} desde="arriba" altura={3}>
35
+ <Asiento pos={[0, 1.4, 0]} />
36
+ </Aparece>
37
+ {escena <= 4 ? <Persona3D pos={[1.5, 0, 0.5]} t={t} expresion={escena >= 3 ? 'sorpresa' : 'neutral'} pose={escena === 4 ? 'brazosArriba' : 'parado'} ropa="#E04A4A" semilla={3} /> : null}
38
+ </group>
39
+ ) : (
40
+ <Multitud3D t={t} n={600} ancho={18} fondo={12} pos={[0, 0, -2]} destacar={[0, 13, 77, 140, 301]} en={inicios[4] ?? 0} brinca />
41
+ )}
42
+ {escena === 6 ? <Mano3D t={t} pos={[0.32, 0.72, 3.4]} rot={[0, -12, 0]} escala={0.6} sostiene="control" aprieta={cuando('aprietas')} en={inicios[5] ?? 0} /> : null}
43
+ </Lienzo3D>
44
+ );
45
+ }
@@ -0,0 +1,24 @@
1
+ // __ID__ · base de escalada absurda (npx morfeo nuevo-video __ID__ --look escalada). Fórmula "escalada": un objeto
2
+ // cotidiano suma piezas porque cada arreglo causa otro problema → "Felicidades, has inventado X básica".
3
+ // El mundo (escenas/__COMPONENTE__.tsx) usa `Aparece` del kit: cada pieza cae y rebota en su palabra (ruedas, motor,
4
+ // asiento). Cambia las piezas y las palabras juntas: el `cuando('ruedas')` del mundo debe estar en el `say`.
5
+ export default {
6
+ estilo: '3d',
7
+ formula: 'escalada',
8
+ voice: 'narrador',
9
+ music: 'house',
10
+ title: 'Has inventado una mochila básica',
11
+ descripcion: 'Cada arreglo trae un problema nuevo.',
12
+ tags: ['humor', 'inventos'],
13
+ mundo: [{tipo: 'componente', nombre: '__COMPONENTE__', x: 540, y: 960, entrada: 'ninguna'}],
14
+ tema: {subtitulos: {tipo: 'palabra', y: 1180, tam: 58, maxPalabras: 1, mayusculas: true, contorno: 10, color: '#FFFFFF', pendiente: '#FFFFFF', resalte: '', coloresClave: ['#FF3B30', '#FF9F0A', '#FFD60A', '#34C759']}},
15
+ scenes: [
16
+ {say: 'Tu mochila pesa tanto que le pones ruedas.', claves: ['mochila', 'ruedas']},
17
+ {say: 'Pero no sube sola, así que le metes un motor.', claves: ['motor'], rotulo: '¡UN MOTOR!', rotuloEstilo: 'sello', rotuloEn: 'motor'},
18
+ {say: 'Y como te cansas, le sueldas un asiento.', claves: ['asiento']},
19
+ {say: 'Felicidades, has inventado una mochila básica.', claves: ['felicidades'], rotulo: 'MOCHILA *BÁSICA*', rotuloEstilo: 'contorno'},
20
+ {say: 'Y seiscientas personas ya la quieren.', show: 'Y 600 personas ya la quieren.', claves: ['personas'], rotulo: '600 PEDIDOS', rotuloEstilo: 'pixel', rotuloColor: 'verde'},
21
+ {say: 'Aprietas el botón y sale sola a repartir.', claves: ['aprietas'], rotulo: '¡SALE SOLA!', rotuloEstilo: 'sello', rotuloEn: 'sale'},
22
+ {type: 'cta', pide: 'comparte', a: 'tu amigo que carga todo', say: 'Envíaselo a tu amigo que carga todo.'},
23
+ ],
24
+ };
@@ -0,0 +1,55 @@
1
+ // Mundo 2D de __ID__ (base mito, 2160×1920): a la izquierda la calle, a la derecha el gimnasio con la cinta.
2
+ // La cámara (`toma` de cada escena) va de un lado al otro; el corredor corre en los dos lados todo el video.
3
+ // Piezas: Calle, Persona2D, Filtros (kit 2D). La cinta se dibuja aquí mismo con rectángulos: cámbiala por lo que
4
+ // desmientas (un microondas, una báscula…).
5
+ import React from 'react';
6
+ import {Calle, Filtros, Persona2D, type Camara} from 'morfeo/kit';
7
+
8
+ type Props = {t: number; cuando: (en: unknown) => number; escena?: number; inicios?: number[]; camara?: Camara};
9
+
10
+ const SUELO = 1500;
11
+
12
+ /** Cinta de correr de perfil: banda que corre, base, poste y consola con números. */
13
+ const Cinta: React.FC<{x: number; t: number; inclinacion?: number}> = ({x, t, inclinacion = 0}) => {
14
+ const corre = (t * 600) % 80;
15
+ return (
16
+ <g transform={`translate(${x} ${SUELO}) rotate(${-inclinacion} 0 0)`}>
17
+ <rect x={-330} y={-60} width={660} height={60} rx={26} fill="#2B2E36" />
18
+ <rect x={-310} y={-78} width={620} height={26} rx={13} fill="#14161B" />
19
+ {Array.from({length: 9}, (_, i) => (
20
+ <rect key={i} x={-300 + ((i * 80 + corre) % 620)} y={-74} width={24} height={18} rx={4} fill="#3A3E48" />
21
+ ))}
22
+ <rect x={250} y={-560} width={34} height={500} rx={14} fill="#3A3E48" />
23
+ <rect x={150} y={-640} width={240} height={120} rx={20} fill="#1E2027" />
24
+ <text x={270} y={-566} textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontWeight={800} fontSize={54} fill="#5BE37D">
25
+ {inclinacion ? '1,0%' : '0,0%'}
26
+ </text>
27
+ </g>
28
+ );
29
+ };
30
+
31
+ export default function __COMPONENTE__({t, camara, escena = 1}: Props) {
32
+ // del 4.º paso en adelante la cinta lleva 1 % de pendiente (se ve inclinada)
33
+ const inclinada = escena >= 4 ? 3 : 0;
34
+ return (
35
+ <svg width={2160} height={1920} viewBox="0 0 2160 1920" style={{overflow: 'visible', display: 'block'}}>
36
+ <Filtros />
37
+ {/* izquierda: la calle */}
38
+ <g>
39
+ <Calle t={t} camara={camara} ancho={1080} suelo={SUELO} />
40
+ <Persona2D x={540} y={SUELO + 30} alto={600} t={t} ropa="#2F6FE0" pose="caminar" expresion="feliz" />
41
+ </g>
42
+ {/* derecha: el gimnasio */}
43
+ <g>
44
+ <rect x={1080} y={0} width={1080} height={1920} fill="#E9ECF2" />
45
+ <rect x={1080} y={SUELO} width={1080} height={420} fill="#C9CED8" />
46
+ {Array.from({length: 6}, (_, i) => (
47
+ <rect key={i} x={1140 + i * 170} y={240} width={120} height={520} rx={10} fill="#D8DDE6" />
48
+ ))}
49
+ <Cinta x={1620} t={t} inclinacion={inclinada} />
50
+ <Persona2D x={1560} y={SUELO - 70} alto={600} t={t} ropa="#E04A4A" pose="caminar" expresion="neutral" />
51
+ </g>
52
+ <rect x={1078} y={0} width={4} height={1920} fill="#FFFFFF" opacity={0.8} />
53
+ </svg>
54
+ );
55
+ }
@@ -0,0 +1,29 @@
1
+ // __ID__ · base de mito desmentido (npx morfeo nuevo-video __ID__ --look mito). Fórmula "mito": afirmación tajante →
2
+ // por qué → la prueba → comparación lado a lado (pantalla dividida) → matiz → "envíaselo a ese amigo que…".
3
+ // Mundo 2D (escenas/__COMPONENTE__.tsx): la calle a la izquierda y el gimnasio a la derecha; `toma` mueve la cámara
4
+ // de un lado al otro (x 540 = calle, x 1620 = cinta). Medidores, barras, sellos y un modo plano para "ver por dentro".
5
+ export default {
6
+ estilo: 'infografia',
7
+ formula: 'mito',
8
+ voice: 'narrador',
9
+ music: 'chillpop',
10
+ title: 'Tu cinta de correr te miente',
11
+ descripcion: 'Diez kilómetros en la cinta no son diez en la calle.',
12
+ tags: ['ciencia', 'deporte', 'mitos'],
13
+ serie: 'EL CUERPO MIENTE',
14
+ mundo: [{tipo: 'componente', nombre: '__COMPONENTE__', x: 0, y: 960, ancla: 'izquierda', entrada: 'ninguna'}],
15
+ scenes: [
16
+ // 1 · afirmación tajante con el dato en pantalla
17
+ {say: 'Diez kilómetros en la cinta y diez en la calle: mismo reloj, y no hiciste lo mismo.', show: '10 km en la cinta y 10 en la calle: mismo reloj, y no hiciste lo mismo.', claves: ['mismo'], rotulo: 'TE MIENTE', rotuloEstilo: 'sello', rotuloEn: 'hiciste', toma: {x: 1620, y: 1000, zoom: 1, a: {x: 540, y: 1000, zoom: 1}}},
18
+ // 2 · por qué: el aire (modo plano para ver la física)
19
+ {say: 'En la calle, tu cuerpo choca contra el aire y apartarlo gasta.', claves: ['aire'], modo: 'plano', modoEn: 'aire', toma: {x: 540, y: 1050, zoom: 1.15}, capas: [{tipo: 'medidor', x: 540, y: 1000, valor: 72, titulo: 'GASTO EN LA CALLE', en: 'gasta'}]},
20
+ // 3 · pregunta antes de revelar
21
+ {say: '¿Y cuánto le falta a la cinta para igualarlo?', claves: ['cinta'], rotulo: '¿CUÁNTO *FALTA*?', toma: {x: 1620, y: 1050, zoom: 1.1}},
22
+ {say: 'Un uno por ciento de inclinación. Casi nadie lo pone.', show: 'Un 1 % de inclinación. Casi nadie lo pone.', claves: ['uno', 'nadie'], rotulo: '1 %', rotuloEstilo: 'pixel', rotuloColor: 'verde', rotuloEn: '1', toma: {x: 1700, y: 900, zoom: 1.6}},
23
+ // 4 · comparación lado a lado
24
+ {say: 'Con esa pendiente, cinta y calle quedan casi iguales.', claves: ['iguales'], capas: [{tipo: 'dividida', x: 540, y: 960, izq: {etiqueta: 'CINTA 0 %', color: '#5B6170', capas: [{tipo: 'barra', x: 540, y: 980, valor: 80, titulo: 'GASTO', ancho: 420}]}, der: {etiqueta: 'CINTA 1 %', color: '#2FA36B', capas: [{tipo: 'barra', x: 540, y: 980, valor: 100, titulo: 'GASTO', ancho: 420}]}}]},
25
+ // 5 · matiz honesto
26
+ {say: 'Si vas suave, ese uno por ciento casi no cambia nada.', show: 'Si vas suave, ese 1 % casi no cambia nada.', claves: ['suave'], toma: {x: 1080, y: 1000, zoom: 0.9}},
27
+ {type: 'cta', toma: {x: 1620, y: 1000, zoom: 1}, pide: 'comparte', a: 'ese amigo que dice que la cinta no cuenta', say: 'Envíaselo a ese amigo que dice que la cinta no cuenta.'},
28
+ ],
29
+ };
@@ -0,0 +1,32 @@
1
+ // __ID__ · base de serie interactiva (npx morfeo nuevo-video __ID__ --look serie). Fórmula "serie": recuento de lo
2
+ // que votaron → capítulo en tiempo real (teléfono, llamada, vigilancia) → decisión A/B con horario del siguiente.
3
+ // No necesita mundo: todo son capas (bloqueo, llamada, recuento, vigilancia, noticia, capitulo). Cambia el texto,
4
+ // los nombres y las opciones; en cada capítulo nuevo actualiza el recuento con lo que votaron.
5
+ export default {
6
+ estilo: 'historia',
7
+ formula: 'serie',
8
+ voice: 'narrador',
9
+ music: 'synth',
10
+ title: 'Llamada perdida · Capítulo 2',
11
+ descripcion: 'Tú decides qué pasa. Comenta tu opción: la más votada gana.',
12
+ tags: ['historias', 'serie', 'misterio'],
13
+ serie: 'LLAMADA PERDIDA · CAP. 2',
14
+ tema: {fondo: {tipo: 'color', color: '#07080F'}, particulas: {tipo: 'ninguna'}},
15
+ scenes: [
16
+ // 1 · recuento de la votación del capítulo anterior (el gancho de los capítulos 2 en adelante)
17
+ {say: 'La mayoría de ustedes votó que no tomaras el metro.', claves: ['metro'], capas: [{tipo: 'recuento', x: 540, y: 980, cabecera: 'VOTACIÓN · CAPÍTULO 1', opciones: [{texto: 'NO LO TOMAS', valor: 90, sub: 'le haces caso al teléfono'}, {texto: 'LO TOMAS', valor: 10, sub: 'vas igual'}]}]},
18
+ // 2 · el tiempo corre: la hora en pantalla y algo imposible
19
+ {say: 'Despiertas, y el teléfono dice que pasaron cinco años.', claves: ['cinco'], rotulo: '¿CINCO *AÑOS*?', rotuloEn: 'cinco', capas: [{tipo: 'bloqueo', x: 540, y: 1060, hora: '08:14', fecha: 'lunes, 10 de noviembre', anio: [2026, 2031], anioEn: 'cinco'}]},
20
+ {say: 'Y te llega un mensaje desde tu propio número.', claves: ['propio'], capas: [{tipo: 'bloqueo', x: 540, y: 1060, hora: '08:15', anio: 2031, notificaciones: [{app: 'Mensajes', titulo: 'Tú', texto: 'No le abras la puerta a Darío.', en: 'mensaje'}]}]},
21
+ // 3 · las noticias confirman que algo pasó
22
+ {say: 'En las noticias, el metro estuvo detenido tres horas.', claves: ['tres'], capas: [{tipo: 'noticia', x: 540, y: 1000, etiqueta: 'ÚLTIMA HORA', titular: 'Línea B: tren detenido tres horas en el túnel', cintillo: 'Servicio restablecido · los pasajeros no recuerdan nada', vivo: true}]},
23
+ // 4 · escalada: la llamada a medianoche
24
+ {say: 'A medianoche, el teléfono suena otra vez.', claves: ['suena'], rotulo: '¡SUENA!', rotuloEstilo: 'sello', rotuloEn: 'suena', capas: [{tipo: 'llamada', x: 540, y: 1060, estado: 'entrante', nombre: 'Yo'}]},
25
+ {say: 'Contestas, y solo se oye una respiración.', claves: ['respiración'], capas: [{tipo: 'llamada', x: 540, y: 1060, estado: 'curso', nombre: 'Yo'}]},
26
+ // 5 · la cámara del pasillo: alguien en la puerta
27
+ {say: 'La cámara del pasillo graba a alguien frente a tu puerta.', claves: ['cámara'], capas: [{tipo: 'escenario', nombre: 'pasillo', x: 540, y: 960}, {tipo: 'vigilancia', x: 540, y: 960, hora: '23:59:12', etiqueta: 'PASILLO · CAM 2', entrada: 'ninguna'}]},
28
+ {say: 'Tres golpes. Y una voz que conoces: es Darío.', claves: ['Darío'], rotulo: 'ES *DARÍO*', rotuloEn: 'Darío', capas: [{tipo: 'escenario', nombre: 'pasillo', x: 540, y: 960}]},
29
+ // 6 · decisión A/B: la tarjeta de fin de capítulo pide el comentario
30
+ {say: '¿Le abres a Darío? Comenta tu opción, la más votada gana.', capas: [{tipo: 'capitulo', x: 540, y: 960, serie: 'LLAMADA PERDIDA', capitulo: 'Capítulo 2', pregunta: '¿LE ABRES A DARÍO?', opciones: [{texto: 'ABRIR', sub: 'le abres la puerta'}, {texto: 'NO ABRIR', sub: 'te quedas adentro'}], siguiente: 'capítulo 3', horario: 'Mañana · 15:30'}]},
31
+ ],
32
+ };