youtube/remotion/src/folge90/vorlagen.tsx
Lara 36aabd55f5 Zähl-Shorts: Stimmen und Untertitel fehlten (HochUntertitel ergänzt)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:21:06 +00:00

228 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);