youtube/remotion/src/folge09/teile.tsx
2026-11-07 19:11:58 +00:00

116 lines
6.2 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 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: Hals runter (Clip C9 vorwärts),
// Drachen ins Maul, Hals hoch (Clip rückwärts, bleibt auf dem ersten Clipbild stehen). Der Drachen hängt
// die ganze Zeit an seiner oberen Spitze im Maul. Keine Pose lulu_A_apfel: dort ist der Apfel gemalt.
// 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};
const bild = f < plan.hoch ? C9_UNTEN : Math.max(0, 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);
// Die obere Spitze (42 px über dem Leistenkreuz) steckt im Maul. Beim Hochheben kippt der Drachen
// nach vorn (Lulu schaut nach links), damit er ihr Gesicht nicht verdeckt.
const w = 35 * Math.min(1, Math.max(0, (f - plan.hoch) / 40));
const r = (w * Math.PI) / 180;
return {x: maul.x - 40 * Math.sin(r), y: maul.y + 40 * Math.cos(r), drehung: w};
};
// `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 />;
if (danach) return <LuluImWind pose={danach} />;
// solange sie den Drachen hält: Hals oben, letztes Bild des rückwärts laufenden Clips
return <ClipFigur name="lulu_C9_hals-runter" start={plan.hoch} tempo={1.25} bis={C9_UNTEN / 24} rueckwaerts x={PLATZ.lulu} y={LULU_Y} />;
};