diff --git a/remotion/src/baukasten/figur.tsx b/remotion/src/baukasten/figur.tsx index 256ae4b..e12eadf 100644 --- a/remotion/src/baukasten/figur.tsx +++ b/remotion/src/baukasten/figur.tsx @@ -19,7 +19,8 @@ export const posenStand = (frame: number, folge: Posenfolge, blende = 5) => { let i = 0; while (i + 1 < folge.length && frame >= folge[i + 1][0]) i++; const [start, pose] = folge[i]; - const mix = i > 0 ? Math.min(1, (frame - start) / blende) : 1; + const lin = i > 0 ? Math.min(1, (frame - start) / blende) : 1; + const mix = lin * lin * (3 - 2 * lin); // weich an- und auslaufen return {pose, von: mix < 1 ? folge[i - 1][1] : undefined, mix}; }; @@ -81,13 +82,24 @@ type FigurProps = Omit & { export const NOA_HOEHE = 340; +// Manche Bilder der Bibliothek sind auf dieselbe Höhe gezeichnet wie die stehende Figur, +// obwohl die Figur darin hockt, sitzt oder liegt. Hier die richtige Größe relativ zur +// Standhöhe; beim Überblenden geht die Größe fließend mit (der Kopf wandert nach unten). +export const POSEN_SKALA: Record = { + noa_B_hocken: 0.72, + noa_B_sitzt: 0.7, + lulu_B_liegt: 0.65, +}; +const skala = (pose: string) => POSEN_SKALA[pose] ?? 1; + export const Figur: React.FC = ({pose, hoehe, frame, blende, ...lage}) => { const jetzt = useCurrentFrame(); const folge: Posenfolge = typeof pose === 'string' ? [[0, pose]] : pose; const st = posenStand(frame ?? jetzt, folge, blende); const figur = POSEN[st.pose].figur as FigurName; + const k = st.von ? skala(st.von) + (skala(st.pose) - skala(st.von)) * st.mix : skala(st.pose); return ( - + {st.von && } diff --git a/remotion/src/baukasten/natur.tsx b/remotion/src/baukasten/natur.tsx index 1ecceed..4ff9591 100644 --- a/remotion/src/baukasten/natur.tsx +++ b/remotion/src/baukasten/natur.tsx @@ -69,20 +69,22 @@ export const Blaetterregen: React.FC<{start: number; x0: number; y0: number; bod ); }; -// Ein Vogel von der Seite (fliegt nach rechts). `schlag` -1 … 1 = Flügel unten … oben. +// Ein Vogel wie im Bilderbuch: zwei weiche Flügelbögen („m“), kleiner Körper. +// `schlag` -1 … 1 = Flügel unten … oben. const Vogel: React.FC<{x: number; y: number; g: number; schlag: number; neigung: number}> = ({x, y, g, schlag, neigung}) => { - const h = -8 - schlag * 24; // Flügelspitze relativ zur Schulter - const fluegel = (dx: number, dy: number) => `M ${3 + dx} ${-3 + dy} Q ${-3 + dx} ${h * 0.55 + dy} ${-15 + dx} ${h + dy} Q ${-6 + dx} ${h * 0.35 + dy} ${-7 + dx} ${1 + dy} Z`; + const h = 4 + schlag * 13; // Höhe der Flügelbögen (oben = positiv) + const spitze = 3 - schlag * 9; // Flügelspitzen sinken beim Abschlag return ( - - - - - - - - + + ); }; @@ -99,10 +101,10 @@ export const Voegel: React.FC<{start: number; anzahl?: number; skala?: number}> const x = 160 + t * 5.5 - Math.abs(reihe) * 75 * skala + r('x') * 20; const y = 600 - t * 1.5 + reihe * 42 * skala + Math.sin(t * 0.05 + r('w') * 6) * 8; // Flügelschlag ohne Pause, je Vogel etwas anders schnell - const takt = t * (0.34 + r('f') * 0.1) + r('p') * 6; + const takt = t * (0.22 + r('f') * 0.06) + r('p') * 6; const schlag = Math.sin(takt); const g = (34 + r('g') * 8) * skala * (1 - Math.abs(reihe) * 0.03); - return ; + return ; })} ); @@ -348,3 +350,31 @@ export const Laubhaufen: React.FC<{x: number; y: number; s?: number; wackeln?: n ); }; + +// Klatsch-Hände fürs Lied (Noas rote Fäustlinge): klatschen auf jeden zweiten Schlag, +// damit Kinder den Rhythmus mitklatschen können. `takt` = Bilder pro Schlag. +export const Klatschen: React.FC<{x: number; y: number; takt: number; s?: number; deckkraft?: number}> = ({x, y, takt, s = 1, deckkraft = 1}) => { + const frame = useCurrentFrame(); + // 0 = Hände zusammen (Klatsch auf Schlag 1, 3, 5 …), 1 = weit auseinander + const auf = Math.abs(Math.sin((Math.PI * (frame - takt)) / (2 * takt))); + const weit = 18 + auf * 52; + const klatsch = auf < 0.18; + const Faust: React.FC<{seite: 1 | -1}> = ({seite}) => ( + + + + + + + ); + return ( + + {klatsch && + [-60, -30, 0, 30, 60].map((w) => ( + + ))} + + + + ); +}; diff --git a/remotion/src/baukasten/orte.tsx b/remotion/src/baukasten/orte.tsx index 07be8a2..efef68f 100644 --- a/remotion/src/baukasten/orte.tsx +++ b/remotion/src/baukasten/orte.tsx @@ -501,7 +501,7 @@ export const KalenderAufTisch: React.FC<{p: number}> = ({p}) => { export const KALENDER_TUER1 = {x: 330 + 4 * 215 + 90, y: 250 + 1 * 180 + 75}; // Türchen „1“ (Index 10) // Noa malt den Kalender (Blick von oben auf das Blatt, über `kalender_leer`). -// `p` 0…1: Titel, dann die Türchen Zeile für Zeile. Die Stiftspitze folgt dem Strich. +// `p` 0…1: Titel, dann die Türchen Zeile für Zeile. const drehe = (x: number, y: number) => { const w = (-2 * Math.PI) / 180; const dx = x - 960; @@ -569,22 +569,8 @@ export const KalenderMalen: React.FC<{p: number; wackeln: number}> = ({p, wackel })} - {/* Noas Hand mit Buntstift, von unten rechts */} - - - - - - {/* Ärmel */} - - - {/* Handrücken und Finger, die den Stift halten */} - - - - - - + {/* Aus Noas Sicht aufs Blatt: die Striche entstehen von selbst, ohne Hand und Stift + (der Arm von unten wirkte wie ein schwebender Stift) */} ); }; diff --git a/remotion/src/folge01/lied.tsx b/remotion/src/folge01/lied.tsx index 9e10900..4758b16 100644 --- a/remotion/src/folge01/lied.tsx +++ b/remotion/src/folge01/lied.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {random, useCurrentFrame} from 'remotion'; import {Figur, Kamera} from '../baukasten/figur'; import {Blende, Vignette} from '../baukasten/helfer'; -import {Blume, Laubhaufen} from '../baukasten/natur'; +import {Blume, Klatschen, Laubhaufen} from '../baukasten/natur'; import {BODEN, Ort} from '../baukasten/orte'; import {SCHRIFT, Untertitel} from '../baukasten/untertitel'; import {TEXTE, plane} from './plan'; @@ -85,6 +85,8 @@ export const Lied: React.FC = () => {
♫ {TEXTE.lied} ♫
+ {/* Klatsch-Hände: auf jeden zweiten Schlag zum Mitklatschen */} + diff --git a/remotion/src/folge01/szene2.tsx b/remotion/src/folge01/szene2.tsx index 4507091..59ce7d9 100644 --- a/remotion/src/folge01/szene2.tsx +++ b/remotion/src/folge01/szene2.tsx @@ -148,7 +148,7 @@ export const Szene2: React.FC = () => { {imHaufen && } {imHaufen ? null : noaGeht ? ( - + ) : ( = hocke ? HOCKE_BLENDE : 5} /> )} diff --git a/remotion/src/folge01/szene3.tsx b/remotion/src/folge01/szene3.tsx index 0ddd25d..fd011b0 100644 --- a/remotion/src/folge01/szene3.tsx +++ b/remotion/src/folge01/szene3.tsx @@ -99,7 +99,7 @@ export const Szene3: React.FC = () => { {noaDa && (noaGeht ? ( - + ) : ( ))} diff --git a/remotion/src/folge01/szene4.tsx b/remotion/src/folge01/szene4.tsx index 9e77342..b1738ee 100644 --- a/remotion/src/folge01/szene4.tsx +++ b/remotion/src/folge01/szene4.tsx @@ -19,7 +19,7 @@ export const LULU_APFEL_X = APFEL.x + 149; // Den Hals senkt sie mit dem Clip lulu_C9_hals-runter; der Apfel bleibt dabei im Maul // (Maul-Bahn gemessen, luluMaulC9). Unten liegt das Maul direkt neben Pino. const LULU_WARTE_X = 930; // bis zum Pflücken etwas rechts vom Apfel -const C9_TEMPO = 1.3; +const C9_TEMPO = 1.25; // 1,25 × 24 fps = 30 fps: jedes Videobild genau ein Clipbild (ruckelfrei) const C9_START = 17; // vorher steht sie im Clip still const C9_UNTEN = 105; // Kopf unten const c9Frames = (bilder: number) => (bilder * 30) / 24 / C9_TEMPO; @@ -140,7 +140,7 @@ export const Szene4: React.FC = () => { {apfelAmBaum && } {luluGeht ? ( - + ) : imClip ? ( f < hoch ? ( diff --git a/remotion/src/folge01/szene5.tsx b/remotion/src/folge01/szene5.tsx index 89236da..359a914 100644 --- a/remotion/src/folge01/szene5.tsx +++ b/remotion/src/folge01/szene5.tsx @@ -45,7 +45,7 @@ export const Szene5: React.FC = () => { const {z, dauer} = S5; // Lulu senkt den Hals (Clip) bis zur Hälfte, bleibt wackelnd stecken, hebt ihn wieder (Clip rückwärts) - const C9_TEMPO = 1.1; + const C9_TEMPO = 1.25; // ruckelfrei (siehe Szene 4) const C9_START = 17; const C9_HALB = 52; // so weit kommt sie const c9 = (bilder: number) => (bilder * 30) / 24 / C9_TEMPO; diff --git a/remotion/src/folge01/szene6.tsx b/remotion/src/folge01/szene6.tsx index ab73203..45076af 100644 --- a/remotion/src/folge01/szene6.tsx +++ b/remotion/src/folge01/szene6.tsx @@ -74,7 +74,7 @@ export const Szene6: React.FC = () => { const BLENDE = 14; // Hände: noa_B_hocken auf den Knien (Leinwand ca. 700, 520), noa_A_arme-hoch rechte Hand (800, 190); Fuß (675, 700), Maßstab 340/535 const m = 340 / 535; - const HAND_HOCKE = {x: NOA_BLUME_X + (700 - 675) * m, y: BODEN + (520 - 700) * m}; + const HAND_HOCKE = {x: NOA_BLUME_X + (700 - 675) * m * 0.72, y: BODEN + (520 - 700) * m * 0.72}; // Hocke-Bild wird auf 72 % verkleinert (POSEN_SKALA) const HAND_OBEN = {x: NOA_BLUME_X + (800 - 675) * m, y: BODEN + (190 - 700) * m}; let blume: {x: number; y: number; s: number} = {x: BLUME_X, y: BLUME_Y, s: 0.8}; if (f >= pflueck) blume = {x: pfoten.x, y: pfoten.y + 20, s: 0.55}; @@ -148,9 +148,9 @@ export const Szene6: React.FC = () => { {pinoGehtHin ? ( - + ) : pinoGehtZurueck ? ( - + ) : ( )} diff --git a/remotion/src/folge02/lied.tsx b/remotion/src/folge02/lied.tsx index 43f9125..ebca6fa 100644 --- a/remotion/src/folge02/lied.tsx +++ b/remotion/src/folge02/lied.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {random, useCurrentFrame} from 'remotion'; import {Figur, Kamera} from '../baukasten/figur'; import {Blende, Vignette} from '../baukasten/helfer'; +import {Klatschen} from '../baukasten/natur'; import {BODEN, Ort} from '../baukasten/orte'; import {SCHRIFT, Untertitel} from '../baukasten/untertitel'; import {TEXTE, plane} from './plan'; @@ -58,6 +59,8 @@ export const Lied: React.FC = () => {
♫ {TEXTE.lied} ♫
+ {/* Klatsch-Hände: auf jeden zweiten Schlag zum Mitklatschen */} + diff --git a/remotion/src/folge02/szene2.tsx b/remotion/src/folge02/szene2.tsx index 00d29ad..dfcbdf5 100644 --- a/remotion/src/folge02/szene2.tsx +++ b/remotion/src/folge02/szene2.tsx @@ -125,8 +125,8 @@ export const Szene2: React.FC = () => { <> - {noaGeht ? : } - {!pinoDa ? null : pinoGeht ? : } + {noaGeht ? : } + {!pinoDa ? null : pinoGeht ? : } diff --git a/remotion/src/folge02/szene3.tsx b/remotion/src/folge02/szene3.tsx index 415eca9..666a6ef 100644 --- a/remotion/src/folge02/szene3.tsx +++ b/remotion/src/folge02/szene3.tsx @@ -55,11 +55,11 @@ export const Szene3: React.FC = () => { let lulu: React.ReactNode; const luluY = BODEN - 10; if (luluGeht) { - lulu = ; + lulu = ; } else if (f < halsHoch) { lulu = ; } else if (f < halsHoch + 110) { - lulu = ; + lulu = ; } else { lulu = ( { [aufsBlatt, PINO_X], [aufsBlatt + 24, GROSSES_BLATT_X], ]); - // Das große Blatt segelt vom Apfelbaum herunter und landet neben Pino - const blattFaellt = aufsBlatt - 80; - const u = Math.min(1, Math.max(0, (f - blattFaellt) / 70)); + // Das große Blatt liegt von Anfang an neben Pino im Gras (vorher fiel es außerhalb des + // Bildausschnitts und tauchte dann plötzlich unter ihm auf); Pino setzt sich darauf + const blattFaellt = -1; + const u = 1; const schaukeln = Math.sin(u * 8) * (1 - u); const blattX = GROSSES_BLATT_X - 180 * (1 - u) + schaukeln * 70; const blattY = BODEN - 8 - 560 * (1 - u); @@ -158,7 +159,7 @@ export const Szene3: React.FC = () => { ) : ( - {noaGeht ? : = knien ? HOCKE_BLENDE : 5} />} + {noaGeht ? : = knien ? HOCKE_BLENDE : 5} />} {f > blattFaellt && } {lulu} diff --git a/remotion/src/folge02/szene4.tsx b/remotion/src/folge02/szene4.tsx index de9a139..1396fb1 100644 --- a/remotion/src/folge02/szene4.tsx +++ b/remotion/src/folge02/szene4.tsx @@ -221,7 +221,7 @@ export const Szene4: React.FC = () => { )} {noaGeht ? ( - + ) : ( { ) : pinoGeht ? ( - + ) : ( { stauchY={atmen(f, 3, 0.006)} /> ) : ( - + ); const ideeStart = z.bild4.start; diff --git a/remotion/src/folge02/szene6.tsx b/remotion/src/folge02/szene6.tsx index 1f7e768..51a6233 100644 --- a/remotion/src/folge02/szene6.tsx +++ b/remotion/src/folge02/szene6.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {useCurrentFrame} from 'remotion'; import {kurve} from '../bewegung'; -import {ClipFigur, Figur, Kamera} from '../baukasten/figur'; +import {ClipFigur, Figur, Kamera, POSEN_SKALA} from '../baukasten/figur'; import {Blende, Vignette, atmen, huepfer, kamFahrt} from '../baukasten/helfer'; import {Funkeln} from '../baukasten/natur'; import {KALENDER_TUER1, KalenderAufTisch, KalenderMalen, Ort} from '../baukasten/orte'; @@ -143,7 +143,7 @@ export const Szene6: React.FC = () => { ]} x={NOA_X6} y={1000} - hoehe={N} + hoehe={N / POSEN_SKALA.noa_B_sitzt} // Szene ist auf die volle Bildgröße gebaut (Noa sitzt nah an der Kamera am Tisch) stauchY={atmen(f, 2)} drehung={malt ? Math.sin(f * 0.3) * 0.8 : 0} schatten={false} @@ -151,7 +151,7 @@ export const Szene6: React.FC = () => { {f >= z.malen.start && } {imSprung ? ( - + ) : ( { filter={NACHT} /> ) : ( - + )}