228 lines
9.5 KiB
TypeScript
228 lines
9.5 KiB
TypeScript
import React from 'react';
|
||
import {interpolate, useCurrentFrame} from 'remotion';
|
||
import {Figur} from '../baukasten/figur';
|
||
import {Blende, atmen, huepfer, kamFahrt} from '../baukasten/helfer';
|
||
import {Blaetterwind, Blume, Funkeln} from '../baukasten/natur';
|
||
import {Ort} from '../baukasten/orte';
|
||
import {ApfelBild, BlattBild, SternBild} from './bilder';
|
||
import {BildKarte, Buehne, FrageKarte, HochUntertitel, Hochformat, Wort} from './hochformat';
|
||
import {TEXTE, plane} from './plan';
|
||
|
||
// Vorlagen für die schnellen Shorts (je ca. 20–30 s): „Wer bin ich?“, „Zähl mit!“, „Welche Farbe?“.
|
||
// Gleicher Rahmen wie die Lern-Shorts: Frage-Karte oben, Untertitel über den Shorts-Knöpfen.
|
||
|
||
const WIESE = 'linear-gradient(#e9e3d2 0 50%, #93ad5c 50% 100%)';
|
||
const t = (k: string) => TEXTE.zeilen[k];
|
||
|
||
// ---------------------------------------------------------------- Wer bin ich?
|
||
// Die Figur steht erst als dunkler Umriss da, drei Hinweise (Punkte unter der Frage füllen sich),
|
||
// Mitmach-Pause, dann wird sie hell und sagt ihren Namen.
|
||
|
||
type Wer = 'pino' | 'noa' | 'lulu';
|
||
const WER: Record<Wer, {pose: string; hoehe: number; sprecher: 'PINO' | 'NOA' | 'LULU'}> = {
|
||
pino: {pose: 'pino_A3_lachen', hoehe: 300, sprecher: 'PINO'},
|
||
noa: {pose: 'noa_A3_lachen', hoehe: 520, sprecher: 'NOA'},
|
||
lulu: {pose: 'lulu_A3_lachen', hoehe: 600, sprecher: 'LULU'},
|
||
};
|
||
|
||
export const werPlan = (k: string, wer: Wer) =>
|
||
plane(k, [
|
||
{id: 'bild0', dauer: 0.8},
|
||
{id: 'e1', wer: 'ERZ'},
|
||
{id: 'h1', wer: wer.toUpperCase() as 'PINO', pause: 0.5},
|
||
{id: 'h2', wer: wer.toUpperCase() as 'PINO', pause: 0.5},
|
||
{id: 'h3', wer: wer.toUpperCase() as 'PINO'},
|
||
{id: 'e2', wer: 'ERZ', pause: 3},
|
||
{id: 'a1', wer: wer.toUpperCase() as 'PINO'},
|
||
{id: 'e3', wer: 'ERZ'},
|
||
{id: 'bild1', dauer: 1.0},
|
||
]);
|
||
|
||
const Punkte: React.FC<{an: number[]}> = ({an}) => {
|
||
const f = useCurrentFrame();
|
||
return (
|
||
<div style={{position: 'absolute', top: 390, left: 0, right: 0, display: 'flex', justifyContent: 'center', gap: 34}}>
|
||
{an.map((ab, i) => {
|
||
const v = interpolate(f, [ab, ab + 8], [0, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
|
||
return <div key={i} style={{width: 46, height: 46, borderRadius: '50%', border: '6px solid #3f5a8a', background: `rgba(63,90,138,${v})`, transform: `scale(${1 + 0.25 * v * (1 - v) * 4})`}} />;
|
||
})}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export const WerBinIch: React.FC<{k: string; wer: Wer; plan: ReturnType<typeof werPlan>}> = ({k, wer, plan}) => {
|
||
const f = useCurrentFrame();
|
||
const {z, dauer} = plan;
|
||
const w = WER[wer];
|
||
const hell = interpolate(f, [z.a1.start - 8, z.a1.start + 6], [0, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
|
||
return (
|
||
<Hochformat hintergrund={WIESE}>
|
||
<Buehne x={960} z={1.6}>
|
||
<Ort name="garten_herbst" />
|
||
<Blaetterwind anzahl={6} seed={k} tempo={0.5} yBereich={[300, 800]} />
|
||
<Figur
|
||
pose={w.pose}
|
||
x={960}
|
||
y={815 + huepfer(f, z.a1.start, 18, 10)}
|
||
hoehe={w.hoehe}
|
||
stauchY={atmen(f, 2)}
|
||
schatten={hell > 0.5}
|
||
filter={hell < 1 ? `brightness(${hell}) opacity(${0.82 + 0.18 * hell})` : undefined}
|
||
/>
|
||
<Funkeln x={960} y={815 - w.hoehe * 0.6} start={z.a1.start} s={2.2} />
|
||
</Buehne>
|
||
<FrageKarte text={t(`${k}/frage`)} />
|
||
<Punkte an={[z.h1.start, z.h2.start, z.h3.start]} />
|
||
<HochUntertitel zeilen={plan.liste} />
|
||
<Blende dauer={dauer} rein={8} raus={14} />
|
||
</Hochformat>
|
||
);
|
||
};
|
||
|
||
// ---------------------------------------------------------------- Zähl mit!
|
||
// Dinge ploppen nacheinander auf (mit Zahl), dann sagt eine Figur unten etwas dazu.
|
||
|
||
type Ding = 'apfel' | 'blatt' | 'stern';
|
||
const ZAHLEN_PLATZ = [
|
||
[230, 560],
|
||
[540, 560],
|
||
[850, 560],
|
||
[230, 820],
|
||
[540, 820],
|
||
[850, 820],
|
||
];
|
||
const BLATT_FARBEN = ['#d4522a', '#e9a83a', '#c2412a', '#f0c048', '#b8662a', '#e07a2e'];
|
||
|
||
export const zaehlPlan = (k: string, n: number, sprecher: 'PINO' | 'NOA' | 'LULU') =>
|
||
plane(k, [
|
||
{id: 'bild0', dauer: 0.8},
|
||
{id: 'e1', wer: 'ERZ', pause: 0.6},
|
||
...Array.from({length: n}, (_, i) => ({id: `c${i + 1}`, wer: 'ERZ' as const, pause: 0.45})),
|
||
{id: 'e2', wer: 'ERZ'},
|
||
{id: 'f1', wer: sprecher},
|
||
{id: 'bild1', dauer: 1.0},
|
||
]);
|
||
|
||
export const ZaehlMit: React.FC<{k: string; n: number; ding: Ding; wer: Wer; plan: ReturnType<typeof zaehlPlan>}> = ({k, n, ding, wer, plan}) => {
|
||
const f = useCurrentFrame();
|
||
const {z, dauer} = plan;
|
||
const w = WER[wer];
|
||
return (
|
||
<Hochformat hintergrund={WIESE}>
|
||
<Buehne x={960} z={1.6}>
|
||
<Ort name="garten_herbst" />
|
||
<Blaetterwind anzahl={6} seed={k} tempo={0.5} yBereich={[300, 800]} />
|
||
<Figur pose={[[0, wer === 'pino' ? 'pino_B_blick-nach-oben' : `${wer}_A4_staunen`], [z.e2.start, w.pose]]} x={960} y={850 + huepfer(f, z.f1.start, 18, 10)} hoehe={w.hoehe * 0.75} stauchY={atmen(f, 2)} />
|
||
</Buehne>
|
||
<FrageKarte text={t(`${k}/frage`)} />
|
||
{Array.from({length: n}, (_, i) => {
|
||
const [x, y] = ZAHLEN_PLATZ[i];
|
||
const ab = z[`c${i + 1}`].start;
|
||
return (
|
||
<React.Fragment key={i}>
|
||
<BildKarte x={x} y={y} ab={ab} r={120}>
|
||
{ding === 'apfel' ? <ApfelBild /> : ding === 'stern' ? <SternBild /> : <BlattBild farbe={BLATT_FARBEN[i]} />}
|
||
</BildKarte>
|
||
<Wort text={String(i + 1)} x={x + 95} y={y + 85} ab={ab + 4} groesse={74} farbe="#3f5a8a" puls={[z.e2.start + i * 4]} />
|
||
</React.Fragment>
|
||
);
|
||
})}
|
||
{/* spielt auch die Stimmen ab */}
|
||
<HochUntertitel zeilen={plan.liste} />
|
||
<Blende dauer={dauer} rein={8} raus={14} />
|
||
</Hochformat>
|
||
);
|
||
};
|
||
|
||
// ---------------------------------------------------------------- Welche Farbe?
|
||
// Drei Fragen nacheinander. „garten“: Apfel, Blume, Blatt als Bildkarte; „freunde“: die Kamera fährt
|
||
// nah an Pinos Mütze, Lulus Schal und Noas Stiefel. Nach jeder Pause erscheint das Farbwort.
|
||
|
||
export const farbePlan = (k: string, sprecher: 'PINO' | 'NOA' | 'LULU') =>
|
||
plane(k, [
|
||
{id: 'bild0', dauer: 0.8},
|
||
{id: 'q1', wer: 'ERZ', pause: 2.5},
|
||
{id: 'a1', wer: 'ERZ', pause: 0.4},
|
||
{id: 'q2', wer: 'ERZ', pause: 2.5},
|
||
{id: 'a2', wer: 'ERZ', pause: 0.4},
|
||
{id: 'q3', wer: 'ERZ', pause: 2.5},
|
||
{id: 'a3', wer: 'ERZ', pause: 0.4},
|
||
{id: 'f1', wer: sprecher},
|
||
{id: 'bild1', dauer: 1.0},
|
||
]);
|
||
|
||
type FarbWort = {text: string; farbe: string};
|
||
|
||
export const WelcheFarbeGarten: React.FC<{k: string; worte: FarbWort[]; plan: ReturnType<typeof farbePlan>}> = ({k, worte, plan}) => {
|
||
const f = useCurrentFrame();
|
||
const {z, dauer} = plan;
|
||
const bilder = [<ApfelBild key="a" />, <BlumeBild key="b" />, <BlattBild key="c" farbe="#f0c048" />];
|
||
return (
|
||
<Hochformat hintergrund={WIESE}>
|
||
<Buehne x={960} z={1.6}>
|
||
<Ort name="garten_herbst" />
|
||
<Blaetterwind anzahl={6} seed={k} tempo={0.5} yBereich={[300, 800]} />
|
||
<Figur pose={[[0, 'pino_A4_staunen'], [z.f1.start - 4, 'pino_A3_lachen']]} x={960} y={850 + huepfer(f, z.f1.start, 18, 10)} hoehe={240} stauchY={atmen(f, 1, 0.015)} />
|
||
</Buehne>
|
||
<FrageKarte text={t(`${k}/frage`)} />
|
||
{[1, 2, 3].map((i) => (
|
||
<React.Fragment key={i}>
|
||
<BildKarte x={540} y={720} ab={z[`q${i}`].start} aus={i < 3 ? z[`q${i + 1}`].start - 6 : z.f1.start} r={200}>
|
||
<div style={{transform: 'scale(1.5)'}}>{bilder[i - 1]}</div>
|
||
</BildKarte>
|
||
{f < (i < 3 ? z[`q${i + 1}`].start - 6 : z.f1.start) && (
|
||
<Wort text={worte[i - 1].text} x={540} y={980} ab={z[`a${i}`].start} groesse={110} farbe={worte[i - 1].farbe} />
|
||
)}
|
||
</React.Fragment>
|
||
))}
|
||
{/* zum Schluss alle drei Wörter nebeneinander */}
|
||
{worte.map((w, i) => (
|
||
<Wort key={w.text} text={w.text} x={200 + i * 340} y={760} ab={z.f1.start + i * 6} groesse={84} farbe={w.farbe} />
|
||
))}
|
||
<HochUntertitel zeilen={plan.liste} />
|
||
<Blende dauer={dauer} rein={8} raus={14} />
|
||
</Hochformat>
|
||
);
|
||
};
|
||
|
||
// Freunde nebeneinander wie im Größen-Short; die Kamera zeigt nacheinander Mütze, Schal und Stiefel
|
||
const FREUNDE = {pino: 700, noa: 870, lulu: 1110, y: 815};
|
||
export const WelcheFarbeFreunde: React.FC<{k: string; worte: FarbWort[]; plan: ReturnType<typeof farbePlan>}> = ({k, worte, plan}) => {
|
||
const f = useCurrentFrame();
|
||
const {z, dauer} = plan;
|
||
const kam = kamFahrt(f, [
|
||
[0, 960, 540, 1.5],
|
||
[z.q1.start, 960, 540, 1.5],
|
||
[z.q1.start + 24, FREUNDE.pino + 10, FREUNDE.y - 110, 3.4], // Pinos Mütze
|
||
[z.a1.frei, FREUNDE.pino + 10, FREUNDE.y - 110, 3.4],
|
||
[z.q2.start + 24, FREUNDE.lulu - 40, 420, 2.4], // Lulus Schal
|
||
[z.a2.frei, FREUNDE.lulu - 40, 420, 2.4],
|
||
[z.q3.start + 24, FREUNDE.noa, FREUNDE.y - 30, 3.2], // Noas Stiefel
|
||
[z.a3.frei, FREUNDE.noa, FREUNDE.y - 30, 3.2],
|
||
[z.f1.start + 10, 960, 540, 1.5],
|
||
[dauer, 960, 540, 1.5],
|
||
]);
|
||
return (
|
||
<Hochformat hintergrund={WIESE}>
|
||
<Buehne x={kam.x} y={kam.y} z={kam.zoom}>
|
||
<Ort name="garten_herbst" />
|
||
<Blaetterwind anzahl={6} seed={k} tempo={0.5} yBereich={[300, 800]} />
|
||
<Figur pose="lulu_A3_lachen" x={FREUNDE.lulu} y={FREUNDE.y} stauchY={atmen(f, 3)} />
|
||
<Figur pose={[[0, 'noa_A5_blick-links'], [z.f1.start, 'noa_A3_lachen']]} x={FREUNDE.noa} y={FREUNDE.y + huepfer(f, z.f1.start, 18, 10)} stauchY={atmen(f, 2)} />
|
||
<Figur pose={[[0, 'pino_A6_blick-rechts'], [z.a1.start, 'pino_A3_lachen']]} x={FREUNDE.pino} y={FREUNDE.y} stauchY={atmen(f, 1, 0.015)} />
|
||
</Buehne>
|
||
<FrageKarte text={t(`${k}/frage`)} />
|
||
{[1, 2, 3].map((i) => (
|
||
<Wort key={i} text={worte[i - 1].text} x={540} y={520} ab={z[`a${i}`].start} groesse={120} farbe={worte[i - 1].farbe} sichtbarBis={i < 3 ? z[`q${i + 1}`].start : z.f1.start} />
|
||
))}
|
||
<HochUntertitel zeilen={plan.liste} />
|
||
<Blende dauer={dauer} rein={8} raus={14} />
|
||
</Hochformat>
|
||
);
|
||
};
|
||
|
||
const BlumeBild: React.FC = () => (
|
||
<div style={{position: 'relative', width: 220, height: 220}}>
|
||
<Blume x={110} y={200} s={1.9} />
|
||
</div>
|
||
);
|