youtube/remotion/src/folge02/szene3.tsx
Pascal Scholz 0b326e6e9c Folge 2 „Pino ist müde“ komplett in Remotion (ohne Ton, ohne Higgsfield)
Szenen aus der Figuren-Bibliothek: freigestellte Standbilder mit
Posenwechsel, freigestellte Clips (WebM mit Alpha in /shared), Orte als
vorberechnete Aquarell-SVGs, Untertitel statt Stimmen. Zeitplan aus den
Sprechzeilen, damit ElevenLabs-Aufnahmen später nur die Dauer setzen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 02:09:06 +00:00

167 lines
5.7 KiB
TypeScript

import React from 'react';
import {useCurrentFrame} from 'remotion';
import {kurve} from '../bewegung';
import {ClipFigur, Figur, Kamera} from './figur';
import {Blende, Vignette, atmen, gehPose, gehWippen, hopfStauch, huepfer, kamFahrt} from './helfer';
import {Blaetterwind, Blatt, Voegel} from './natur';
import {Ort} from './orte';
import {BODEN, NOA_X, PINO_X} from './szene2';
import {Untertitel} from './untertitel';
import {plane} from './zeit';
// Szene 3: Lulu sieht es von oben. Winterschlaf wird erklärt, Pino ist traurig.
export const LULU_X = 1520;
export const S3 = plane([
{id: 'bild0', dauer: 3.2},
{id: 'bild1', dauer: 2.4},
{id: 'l1', wer: 'LULU', text: 'Guten Morgen, ihr zwei!'},
{id: 'l2', wer: 'LULU', text: 'Von hier oben sehe ich: Die Vögel fliegen weg, ganz weit in den Süden.'},
{id: 'l3', wer: 'LULU', text: 'Und die Bäume sind kahl. Der Winter kommt.'},
{id: 'e1', wer: 'ERZ', text: 'Wenn es kalt wird, finden Igel kaum noch etwas zu fressen.'},
{id: 'e2', wer: 'ERZ', text: 'Darum machen sie etwas ganz Besonderes. Sie schlafen.'},
{id: 'e3', wer: 'ERZ', text: 'Den ganzen Winter lang, bis zum Frühling. Das heißt Winterschlaf.'},
{id: 'p1', wer: 'PINO', text: 'Den ganzen Winter? Aber dann verpasse ich ja das Winterfest!'},
{id: 'bild2', dauer: 2.4},
{id: 'e4', wer: 'ERZ', text: 'Schau mal, Pinos Stacheln hängen ganz schlaff herunter.'},
{id: 'e5', wer: 'ERZ', text: 'Wie fühlt sich Pino jetzt?', pause: 3},
{id: 'e6', wer: 'ERZ', text: 'Ja. Pino ist traurig.'},
{id: 'bild3', dauer: 2.6},
{id: 'n1', wer: 'NOA', text: 'Ach, Pino. Bis zum Winterfest ist es doch noch ganz lange hin.'},
]);
// Das große Blatt, auf das Pino sich setzt
const GROSSES_BLATT_X = PINO_X + 50;
export const Szene3: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S3;
// Lulu stakst von rechts herein, beugt dann den Hals herunter (Clip),
// hebt ihn für „von hier oben“ wieder (Clip rückwärts).
const luluAn = z.bild0.ende;
const luluX = kurve(f, [
[0, 2250],
[luluAn, LULU_X],
]);
const luluGeht = f < luluAn;
const halsRunter = z.bild1.start;
const halsHoch = z.l2.start - 10;
const obenStart = z.l2.start + 50;
const obenEnde = z.e1.ende + 5;
const imOben = f >= obenStart && f < obenEnde;
let lulu: React.ReactNode;
const luluY = BODEN - 10;
if (luluGeht) {
lulu = <Figur pose={gehPose('lulu', f, 5)} x={luluX} y={luluY + gehWippen(f, 5, 8)} />;
} else if (f < halsHoch) {
lulu = <ClipFigur name="lulu_C9_hals-runter" start={halsRunter} tempo={1.25} x={LULU_X} y={luluY} />;
} else if (f < halsHoch + 110) {
lulu = <ClipFigur name="lulu_C9_hals-runter" start={halsHoch} tempo={1.5} rueckwaerts x={LULU_X} y={luluY} />;
} else {
lulu = (
<Figur
pose={[
[0, 'lulu_A5_blick-links'],
[z.bild2.start, 'lulu_B1_traurig'],
[z.bild3.start, 'lulu_A5_blick-links'],
]}
x={LULU_X}
y={luluY}
stauchY={atmen(f, 3, 0.006)}
/>
);
}
// Pino
const pinoSchreck = z.p1.start;
const aufsBlatt = z.bild2.start + 10;
const pinoX = kurve(f, [
[aufsBlatt, PINO_X],
[aufsBlatt + 24, GROSSES_BLATT_X],
]);
const traurig = kurve(f, [
[aufsBlatt + 20, 0],
[aufsBlatt + 50, 1],
]);
const anlehnen = kurve(f, [
[z.bild3.start + 40, 0],
[z.bild3.start + 70, 1],
]);
const pinoPose: [number, string][] = [
[0, 'pino_A2_augen-halb'],
[z.bild0.start + 40, 'pino_A6_blick-rechts'],
[z.bild1.start + 20, 'pino_B_blick-nach-oben'],
[z.l1.start + 20, 'pino_A3_lachen'],
[z.l2.start, 'pino_B_blick-nach-oben'],
[z.e2.start, 'pino_A2_augen-halb'],
[z.e3.start + 30, 'pino_A4_staunen'],
[pinoSchreck, 'pino_B2_erschrocken'],
[aufsBlatt + 20, 'pino_B1_traurig'],
[z.bild3.start + 50, 'pino_A1_augen-zu'],
];
const pinoY = BODEN + huepfer(f, pinoSchreck, 34, 12) + huepfer(f, aufsBlatt, 14, 22) - (f > aufsBlatt + 20 ? 6 : 0);
const pinoStauchY = atmen(f, 1) * hopfStauch(f, pinoSchreck) * (1 - 0.06 * traurig + 0.06 * anlehnen * 0);
const pinoDreh = -9 * anlehnen + 3 * traurig * (1 - anlehnen);
// Noa kniet sich zu Pino
const knien = z.bild3.start + 10;
const noaX = kurve(f, [
[knien, NOA_X + 20],
[knien + 30, NOA_X + 140],
]);
const noaPose: [number, string][] = [
[0, 'noa_A6_blick-rechts'],
[z.bild1.start + 20, 'noa_A4_staunen'],
[z.l1.start + 15, 'noa_A3_lachen'],
[z.e1.start, 'noa_A6_blick-rechts'],
[z.bild2.start + 30, 'noa_B1_traurig'],
[knien + 18, 'noa_B_hocken'],
];
const noaStauch = kurve(f, [
[knien + 8, 1],
[knien + 18, 0.94],
[knien + 30, 1],
]);
const kam = kamFahrt(f, [
[0, 1150, 820, 1.7],
[z.bild0.ende - 10, 1150, 820, 1.7],
[z.bild1.start + 30, 1150, 640, 1.15],
[z.l1.ende, 1120, 700, 1.3],
[z.l2.start, 1250, 520, 1.1],
[obenStart, 1300, 420, 1.2],
[obenEnde, 1050, 800, 2.2],
[z.e3.ende, 1060, 820, 2.4],
[z.p1.start + 10, 1050, 810, 2.1],
[z.bild2.start + 20, 1090, 820, 2.5],
[z.e6.ende, 1100, 830, 2.7],
[z.bild3.start + 30, 960, 790, 1.9],
[dauer, 950, 790, 2.0],
]);
return (
<>
{imOben ? (
<Kamera x={kurve(f, [[obenStart, 900], [obenEnde, 1060]])} y={540} zoom={1.05}>
<Ort name="oben" />
<Voegel start={obenStart - 40} skala={1.9} />
</Kamera>
) : (
<Kamera {...kam}>
<Ort name="garten" />
<Figur pose={noaPose} x={noaX} y={BODEN} stauchY={atmen(f, 2) * noaStauch} />
{f > aufsBlatt - 30 && <Blatt x={GROSSES_BLATT_X} y={BODEN - 8} r={88} s={2.6} farbe="#c8642c" deckkraft={Math.min(1, (f - aufsBlatt + 30) / 12)} />}
<Figur pose={pinoPose} x={pinoX} y={pinoY} stauchY={pinoStauchY} drehung={pinoDreh} blende={6} />
{lulu}
</Kamera>
)}
{!imOben && <Blaetterwind anzahl={4} seed="w3" />}
<Vignette />
<Untertitel zeilen={S3.liste} />
<Blende dauer={dauer} rein={0} raus={0} />
</>
);
};