youtube/remotion/src/folge09/teile.tsx
2026-11-07 14:11:29 +00:00

113 lines
6.1 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 {random, useCurrentFrame} from 'remotion';
import {ClipFigur, Figur, GROESSE, NOA_HOEHE} from '../baukasten/figur';
import {Drachen, type DrachenLage, drachenHalt} from '../baukasten/drachen';
import {Drachenschnur, spulenPunkt} from '../baukasten/drachenschnur';
import {luluMaulC9, punkt} from '../baukasten/helfer';
import {Blaetterwind} from '../baukasten/natur';
import {BODEN, Ort} from '../baukasten/orte';
// Bildteile für Folge 9 „Lulu und der Drachen“. Der Wind weht die ganze Folge von links nach rechts:
// Blätter, Grashalme, Drachenschwanz und Lulus Schal (Clip lulu_C11_schal-wind) zeigen alle nach rechts.
// Feste Plätze mitten auf der Wiese, weit weg vom Apfelbaum (links bei x ≈ 340).
// Lulu steht links (windaufwärts), Noa rechts: Steigt der Drachen nach rechts (mit dem Wind), läuft die
// Schnur von Noa nach rechts oben – nie an Lulus Hals vorbei. Pino ganz links am Boden.
export const PLATZ = {noa: 1340, lulu: 1000, pino: 600};
export const LULU_Y = BODEN - 6;
// Lulu senkt den Hals (Clip C9) bis Clipbild 105: dann ist ihr Maul hier, knapp über dem Drachen im Gras
const C9_TEMPO = 1.25;
export const C9_START = 17;
export const C9_UNTEN = 105;
export const c9 = (bilder: number) => (bilder * 30) / 24 / C9_TEMPO;
const MAUL_UNTEN = luluMaulC9(C9_UNTEN);
export const DRACHEN_IM_GRAS = {x: punkt('lulu', PLATZ.lulu, LULU_Y, MAUL_UNTEN.cx, MAUL_UNTEN.cy).x, y: BODEN - 40};
// In der Pose lulu_A_apfel hält Lulu den Apfel bei 574/193 (Leinwand) – dort hält sie jetzt den Drachen
export const DRACHEN_IM_MAUL = punkt('lulu', PLATZ.lulu, LULU_Y, 574.5, 193);
// In der Pose noa_A_haende-oben sind die Fäustlinge um 674/218 – der Drachen liegt dazwischen
export const drachenUeberNoa = (noaX: number) => punkt('noa', noaX, BODEN, 674.5, 80); // über dem Kopf, nicht vor dem Gesicht
// Windiger Herbstgarten: Blätter wehen nach rechts, Grashalme biegen sich im Takt der Böen
export const WindGarten: React.FC<{abend?: boolean; wind?: number; boeen?: number}> = ({abend = false, wind = 1, boeen = 0}) => {
const f = useCurrentFrame();
const boe = 0.6 + 0.4 * Math.sin(f * 0.05) + boeen;
return (
<>
<Ort name={abend ? 'garten_herbst_abend' : 'garten_herbst'} />
{wind > 0 && <Blaetterwind anzahl={Math.round(8 * wind + 10 * boeen)} seed="drachen" tempo={wind * (1 + boeen)} yBereich={[380, 900]} />}
<svg viewBox="0 0 1920 1080" style={{position: 'absolute', inset: 0, width: 1920, height: 1080}}>
{new Array(70).fill(0).map((_, i) => {
const x = random(`gh${i}`) * 1960 - 20;
const y = BODEN + 10 + random(`ghy${i}`) * 130;
const l = 22 + random(`ghl${i}`) * 26;
const biegen = wind * (14 + 10 * boe) * (0.8 + random(`ghb${i}`) * 0.4) + Math.sin(f * 0.2 + i) * 3 * wind;
return <path key={i} d={`M ${x} ${y} q ${biegen * 0.4} ${-l * 0.6} ${biegen} ${-l}`} stroke={random(`ghf${i}`) < 0.5 ? '#7f9a4c' : '#93ad5c'} strokeWidth={4} fill="none" strokeLinecap="round" />;
})}
</svg>
</>
);
};
// Lulu im Wind: Clip mit wehendem Schal (Schleife), sonst eine Pose
export const LuluImWind: React.FC<{pose?: string; stauchY?: number}> = ({pose, stauchY}) =>
pose ? (
<Figur pose={pose} x={PLATZ.lulu} y={LULU_Y} stauchY={stauchY} />
) : (
<ClipFigur name="lulu_C11_schal-wind" start={0} schleife tempo={1.25} x={PLATZ.lulu} y={LULU_Y} stauchY={stauchY} ersatz="lulu_A3_lachen" />
);
// Drachen samt Schnur zu Noas Spule (Pose noa_A_drachenschnur). Schaut Noa nach links (spiegeln),
// läuft die Schnur hinter ihr – deshalb vor ihr zeichnen lassen (vorNoa = false).
export const SchnurUndDrachen: React.FC<{lage: DrachenLage; noaX: number; spiegeln: boolean; schnur?: boolean; flattern?: number; leuchten?: [number, number, number]}> = ({
lage,
noaX,
spiegeln,
schnur = true,
flattern,
leuchten,
}) => {
const hoch = lage.y < BODEN - 160;
const schwanz = lage.liegt ? 0 : hoch ? 30 : 12;
return (
<>
{schnur && (
<Drachenschnur
von={spulenPunkt(noaX, BODEN, NOA_HOEHE * GROESSE.noa, spiegeln)}
nach={drachenHalt(lage.x, lage.y, 1, lage.drehung)}
durchhang={hoch ? 0.05 : 0.12}
/>
)}
<Drachen {...lage} schwanz={schwanz} flattern={flattern ?? (lage.liegt ? 0 : 14)} boden={BODEN} leuchten={leuchten} />
</>
);
};
// Lulu holt den Drachen aus dem Gras und hält ihn mit dem Maul hoch (wie den Apfel in Folge 1):
// Hals runter (Clip C9 vorwärts), Drachen ins Maul, Hals hoch (Clip rückwärts), dann die Pose
// lulu_A_apfel – der Drachen liegt genau über dem gemalten Apfel. Die Schnur hält immer Noa.
export const hebenPlan = (start: number) => {
const unten = start + c9(C9_UNTEN - C9_START);
const hoch = unten + 10;
const oben = hoch + c9(C9_UNTEN);
return {start, unten, hoch, oben};
};
export const drachenBeiLulu = (f: number, plan: ReturnType<typeof hebenPlan>): DrachenLage => {
if (f < plan.unten) return {x: DRACHEN_IM_GRAS.x, y: BODEN - 36, drehung: 90, liegt: true};
if (f >= plan.oben) return {x: DRACHEN_IM_MAUL.x, y: DRACHEN_IM_MAUL.y + 95, drehung: -15};
const bild = f < plan.hoch ? C9_UNTEN : C9_UNTEN - ((f - plan.hoch) * 1.25 * 24) / 30;
const m = luluMaulC9(bild);
const maul = punkt('lulu', PLATZ.lulu, LULU_Y, m.cx, m.cy);
// am Maul hängt der Drachen an seiner oberen Spitze, also unter dem Maul (nicht vor dem Gesicht)
return {x: maul.x, y: maul.y + 95, drehung: -15 * Math.min(1, Math.max(0, (f - plan.hoch) / 20))};
};
// `danach`: was Lulu nach dem Loslassen zeigt ('wind' = Schal-Clip) – nie die Apfel-Pose ohne Drachen!
export const LuluHebt: React.FC<{plan: ReturnType<typeof hebenPlan>; danach?: string}> = ({plan, danach}) => {
const f = useCurrentFrame();
if (f < plan.start) return <LuluImWind />;
if (f < plan.hoch) return <ClipFigur name="lulu_C9_hals-runter" start={plan.start - c9(C9_START)} tempo={1.25} bis={C9_UNTEN / 24} x={PLATZ.lulu} y={LULU_Y} />;
if (f < plan.oben) return <ClipFigur name="lulu_C9_hals-runter" start={plan.hoch} tempo={1.25} bis={C9_UNTEN / 24} rueckwaerts x={PLATZ.lulu} y={LULU_Y} />;
if (danach === 'wind') return <LuluImWind />;
return danach ? <LuluImWind pose={danach} /> : <LuluImWind pose="lulu_A_apfel" />;
};