Folge 2 überarbeitet nach den Lehren aus Folge 1

- Noas Hocke (Szenen 3 und 4) als weiche Überblendung der Standbilder statt
  Hinsetz-Clip (anders gezeichnet); Beugen/Schieben am Nest bleiben
- Noas Schritttempo passend zur Geschwindigkeit (Szene 2: 1.6, Szene 4 zügiger)
- Pino schaut beim Zählen zu Noa (gespiegelt)
- leeres Nest: kein Schatten ohne Blätter (doppelter Schatten entfernt)
- Lied (Folge 1 und 2): Kamera etwas tiefer, Figuren höher im Bild

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018MyLusGH2U5AudjoARey3N
This commit is contained in:
Lara 2026-11-02 16:06:32 +01:00
parent 083e878b97
commit 4f83fbc587
6 changed files with 39 additions and 28 deletions

View file

@ -174,7 +174,7 @@ export const NEST_BILD = {b: 440, h: 260, x: 220, y: 180}; // Bildgröße und La
export const NestBlaetter: React.FC<{voll?: number; seite: 'hinten' | 'vorne'}> = ({voll = 1, seite}) => ( export const NestBlaetter: React.FC<{voll?: number; seite: 'hinten' | 'vorne'}> = ({voll = 1, seite}) => (
<> <>
{seite === 'hinten' && ( {seite === 'hinten' && (
<div style={{position: 'absolute', left: -170, top: -30, width: 340, height: 60, borderRadius: '50%', background: 'radial-gradient(closest-side, rgba(70,45,25,0.45), rgba(70,45,25,0))'}} /> <div style={{position: 'absolute', left: -170, top: -30, width: 340, height: 60, borderRadius: '50%', background: 'radial-gradient(closest-side, rgba(70,45,25,0.45), rgba(70,45,25,0))', opacity: Math.min(1, voll * 3)}} />
)} )}
{NEST[seite].map((b, i) => {NEST[seite].map((b, i) =>
b.reihe < voll ? <Blatt key={i} x={b.x * (0.5 + 0.5 * voll)} y={b.y * (0.4 + 0.6 * voll)} r={b.r} s={b.s} farbe={b.farbe} /> : null, b.reihe < voll ? <Blatt key={i} x={b.x * (0.5 + 0.5 * voll)} y={b.y * (0.4 + 0.6 * voll)} r={b.r} s={b.s} farbe={b.farbe} /> : null,
@ -190,9 +190,6 @@ export const Nest: React.FC<{x: number; y: number; voll?: number; seite: 'hinten
/> />
) : ( ) : (
<div style={{position: 'absolute', left: x, top: y, transform: `scale(${s})`, transformOrigin: '0 0', filter}}> <div style={{position: 'absolute', left: x, top: y, transform: `scale(${s})`, transformOrigin: '0 0', filter}}>
{seite === 'hinten' && (
<div style={{position: 'absolute', left: -170, top: -30, width: 340, height: 60, borderRadius: '50%', background: 'radial-gradient(closest-side, rgba(70,45,25,0.45), rgba(70,45,25,0))'}} />
)}
<NestBlaetter voll={voll} seite={seite} /> <NestBlaetter voll={voll} seite={seite} />
</div> </div>
); );

View file

@ -65,7 +65,7 @@ export const Lied: React.FC = () => {
]; ];
return ( return (
<> <>
<Kamera x={1000} y={600} zoom={1.3}> <Kamera x={1000} y={690} zoom={1.3}>
<Ort name="garten_herbst" /> <Ort name="garten_herbst" />
<Laubhaufen x={HAUFEN_X + 380} y={BODEN + 6} /> <Laubhaufen x={HAUFEN_X + 380} y={BODEN + 6} />
<Figur pose={luluPose} x={1300} y={BODEN} drehung={schwank(0.5, 2.2)} stauchY={1 - 0.012 * schlag} blende={8} /> <Figur pose={luluPose} x={1300} y={BODEN} drehung={schwank(0.5, 2.2)} stauchY={1 - 0.012 * schlag} blende={8} />

View file

@ -36,7 +36,7 @@ export const Lied: React.FC = () => {
const schwank = (phase: number, g: number) => Math.sin((f * Math.PI) / (2 * TAKT) + phase) * g; const schwank = (phase: number, g: number) => Math.sin((f * Math.PI) / (2 * TAKT) + phase) * g;
return ( return (
<> <>
<Kamera x={980} y={600} zoom={1.3}> <Kamera x={980} y={690} zoom={1.3}>
<Ort name="garten" /> <Ort name="garten" />
<Figur pose="lulu_A3_lachen" x={1240} y={BODEN} drehung={schwank(0.5, 2.2)} stauchY={1 - 0.012 * schlag} /> <Figur pose="lulu_A3_lachen" x={1240} y={BODEN} drehung={schwank(0.5, 2.2)} stauchY={1 - 0.012 * schlag} />
<Figur <Figur

View file

@ -125,7 +125,7 @@ export const Szene2: React.FC = () => {
<> <>
<Kamera {...kam}> <Kamera {...kam}>
<Ort name="garten" /> <Ort name="garten" />
{noaGeht ? <Geher figur="noa" spiegeln x={noaX} y={BODEN} /> : <Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2) * kichStauch * (1 + 0.02 * vorbeugen)} drehung={kichDreh + 7 * vorbeugen} />} {noaGeht ? <Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.6} /> : <Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2) * kichStauch * (1 + 0.02 * vorbeugen)} drehung={kichDreh + 7 * vorbeugen} />}
{!pinoDa ? null : pinoGeht ? <Geher figur="pino" x={pinoX} y={BODEN} tempo={1.8} /> : <Figur pose={pinoPose} x={pinoX} y={pinoY} stauchY={pinoStauchY} drehung={pinoDreh} blende={4} />} {!pinoDa ? null : pinoGeht ? <Geher figur="pino" x={pinoX} y={BODEN} tempo={1.8} /> : <Figur pose={pinoPose} x={pinoX} y={pinoY} stauchY={pinoStauchY} drehung={pinoDreh} blende={4} />}
<Zzz x={PINO_X + 40} y={BODEN - 150} start={z.p2.ende} ende={schreck} s={0.7} /> <Zzz x={PINO_X + 40} y={BODEN - 150} start={z.p2.ende} ende={schreck} s={0.7} />
</Kamera> </Kamera>

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import {useCurrentFrame} from 'remotion'; import {useCurrentFrame} from 'remotion';
import {kurve} from '../bewegung'; import {kurve} from '../bewegung';
import {ClipFigur, Figur, Geher, Kamera, NoaHocke} from '../baukasten/figur'; import {ClipFigur, Figur, Geher, Kamera} from '../baukasten/figur';
import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt} from '../baukasten/helfer'; import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt} from '../baukasten/helfer';
import {Blaetterwind, Blatt, Voegel} from '../baukasten/natur'; import {Blaetterwind, Blatt, Voegel} from '../baukasten/natur';
import {BODEN, Ort} from '../baukasten/orte'; import {BODEN, Ort} from '../baukasten/orte';
@ -125,7 +125,11 @@ export const Szene3: React.FC = () => {
[z.l1.start + 15, 'noa_A3_lachen'], [z.l1.start + 15, 'noa_A3_lachen'],
[z.e1.start, 'noa_A6_blick-rechts'], [z.e1.start, 'noa_A6_blick-rechts'],
[z.bild2.start + 30, 'noa_B1_traurig'], [z.bild2.start + 30, 'noa_B1_traurig'],
[knien, 'noa_B_hocken'],
]; ];
// in die Hocke: Standbilder weich überblenden, dabei kurz absinken
const HOCKE_BLENDE = 14;
const hockeStauch = f >= knien && f < knien + HOCKE_BLENDE ? 1 - 0.05 * Math.sin(((f - knien) / HOCKE_BLENDE) * Math.PI) : 1;
const noaGeht = f >= knien - 36 && f < knien; const noaGeht = f >= knien - 36 && f < knien;
const kam = kamFahrt(f, [ const kam = kamFahrt(f, [
@ -154,7 +158,7 @@ export const Szene3: React.FC = () => {
) : ( ) : (
<Kamera {...kam}> <Kamera {...kam}>
<Ort name="garten" /> <Ort name="garten" />
{noaGeht ? <Geher figur="noa" spiegeln x={noaX} y={BODEN} /> : f >= knien ? <NoaHocke start={knien} x={noaX} y={BODEN} /> : <Figur pose={noaPose} x={noaX} y={BODEN} stauchY={atmen(f, 2)} />} {noaGeht ? <Geher figur="noa" spiegeln x={noaX} y={BODEN} /> : <Figur pose={noaPose} x={noaX} y={BODEN} stauchY={atmen(f, 2) * hockeStauch} blende={f >= knien ? HOCKE_BLENDE : 5} />}
{f > blattFaellt && <Blatt x={blattX} y={blattY} r={blattR} s={2.6} farbe="#c8642c" />} {f > blattFaellt && <Blatt x={blattX} y={blattY} r={blattR} s={2.6} farbe="#c8642c" />}
<Figur pose={pinoPose} x={pinoX} y={pinoY} stauchY={pinoStauchY} drehung={pinoDreh} blende={6} /> <Figur pose={pinoPose} x={pinoX} y={pinoY} stauchY={pinoStauchY} drehung={pinoDreh} blende={6} />
{lulu} {lulu}

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import {useCurrentFrame} from 'remotion'; import {useCurrentFrame} from 'remotion';
import {kurve} from '../bewegung'; import {kurve} from '../bewegung';
import {Figur, Geher, Kamera, NOA_HOCKE_FRAMES, NoaHocke} from '../baukasten/figur'; import {Figur, Geher, Kamera} from '../baukasten/figur';
import {Blende, LULU_MAUL_A1, LULU_MAUL_LINKS, Vignette, atmen, hopfStauch, huepfer, kamFahrt, punkt, ruck} from '../baukasten/helfer'; import {Blende, LULU_MAUL_A1, LULU_MAUL_LINKS, Vignette, atmen, hopfStauch, huepfer, kamFahrt, punkt, ruck} from '../baukasten/helfer';
import {Blaetterregen, Blatt, LAUB, Nest, Ruf} from '../baukasten/natur'; import {Blaetterregen, Blatt, LAUB, Nest, Ruf} from '../baukasten/natur';
import {BODEN, NEST_X, Ort} from '../baukasten/orte'; import {BODEN, NEST_X, Ort} from '../baukasten/orte';
@ -54,13 +54,13 @@ export const Szene4: React.FC = () => {
// Noa // Noa
const noaGehtVon = zumStapel + 20; const noaGehtVon = zumStapel + 20;
const noaGehtBis = noaGehtVon + 90; const noaGehtBis = noaGehtVon + 60;
const noaX = kurve(f, [ const noaX = kurve(f, [
[noaGehtVon, NOA_X + 140], [noaGehtVon, NOA_X + 140],
[noaGehtBis, NOA_ZAEHL_X], [noaGehtBis, NOA_ZAEHL_X],
]); ]);
const noaGeht = f > noaGehtVon && f < noaGehtBis; const noaGeht = f > noaGehtVon && f < noaGehtBis;
const noaPose: [number, string][] = [ const noaStehPose: [number, string][] = [
[0, 'noa_A3_lachen'], [0, 'noa_A3_lachen'],
[noaGehtBis, 'noa_A6_blick-rechts'], [noaGehtBis, 'noa_A6_blick-rechts'],
[z.c5.ende, 'noa_A3_lachen'], [z.c5.ende, 'noa_A3_lachen'],
@ -68,17 +68,19 @@ export const Szene4: React.FC = () => {
[z.l2.ende, 'noa_A3_lachen'], [z.l2.ende, 'noa_A3_lachen'],
[z.e4.start, 'noa_A3_lachen'], [z.e4.start, 'noa_A3_lachen'],
]; ];
// Hocke: Clip vorwärts (runter) oder rückwärts (aufstehen); sonst Standbilder // Hocke: Abschnitte, in denen Noa hockt; Wechsel als weiche Überblendung der Standbilder
const hocke = (() => { // (der Hinsetz-Clip ist anders gezeichnet), dabei kurz absinken bzw. sich strecken
const w = (start: number, auf: boolean) => ({start, auf}); const HOCKE_BLENDE = 14;
if (f < z.n1.start + 30) return w(-1000, false); const hockZeiten: [number, number][] = [
if (f < z.n1.start + 30 + NOA_HOCKE_FRAMES) return w(z.n1.start + 30, true); [-1000, z.n1.start + 30],
if (f >= z.n2.start - 15 && f < z.c5.ende) return w(z.n2.start - 15, false); [z.n2.start - 15, z.c5.ende],
if (f >= z.c5.ende && f < z.c5.ende + NOA_HOCKE_FRAMES) return w(z.c5.ende, true); [zurueck, z.e4.start],
if (f >= zurueck && f < z.e4.start) return w(-1000, false); ];
if (f >= z.e4.start && f < z.e4.start + NOA_HOCKE_FRAMES) return w(z.e4.start, true); const inHocke = hockZeiten.some(([a, b]) => f >= a && f < b);
return null; const stehPose = (t: number) => noaStehPose.filter(([s]) => s <= t).pop()![1];
})(); const noaPose: [number, string][] = [...noaStehPose, ...hockZeiten.flatMap(([a, b]): [number, string][] => [[a, 'noa_B_hocken'], [b, stehPose(b)]])].sort((p, q) => p[0] - q[0]);
const bump = (t: number) => (f >= t && f < t + HOCKE_BLENDE ? Math.sin(((f - t) / HOCKE_BLENDE) * Math.PI) : 0);
const hockeStauch = hockZeiten.reduce((s, [a, b]) => s * (1 - 0.05 * bump(a)) * (1 + 0.03 * bump(b)), 1);
// beim Hinlegen jedes Blatts kurz nach vorn beugen // beim Hinlegen jedes Blatts kurz nach vorn beugen
const beugen = zaehlen.reduce((s, c) => s + Math.max(0, Math.sin(Math.min(1, Math.max(0, (f - c + 8) / 16)) * Math.PI)), 0); const beugen = zaehlen.reduce((s, c) => s + Math.max(0, Math.sin(Math.min(1, Math.max(0, (f - c + 8) / 16)) * Math.PI)), 0);
// beim Nestbau schieben (vor und zurück) // beim Nestbau schieben (vor und zurück)
@ -104,10 +106,12 @@ export const Szene4: React.FC = () => {
const pinoGeht = (f > pinoGehtVon && f < pinoGehtBis) || (f > zurueck + 10 && f < zurueck + 60); const pinoGeht = (f > pinoGehtVon && f < pinoGehtBis) || (f > zurueck + 10 && f < zurueck + 60);
const pinoSpiegel = f > pinoGehtVon && f < pinoGehtBis; // nach rechts const pinoSpiegel = f > pinoGehtVon && f < pinoGehtBis; // nach rechts
const imNest = f >= z.bild5.start; const imNest = f >= z.bild5.start;
// Pino sitzt beim Zählen rechts von Noa: seine Bilder schauen nach rechts, darum gespiegelt (schaut zu ihr)
const pinoZuNoa = f >= pinoGehtBis && f <= zurueck + 10;
const pinoPose: [number, string][] = [ const pinoPose: [number, string][] = [
[0, 'pino_A1_augen-zu'], [0, 'pino_A1_augen-zu'],
[z.n1.start + 20, 'pino_A2_augen-halb'], [z.n1.start + 20, 'pino_A2_augen-halb'],
[pinoGehtBis, 'pino_A5_blick-links'], [pinoGehtBis, 'pino_A6_blick-rechts'],
...zaehlen.map((c) => [c, 'pino_A4_staunen'] as [number, string]), ...zaehlen.map((c) => [c, 'pino_A4_staunen'] as [number, string]),
[z.c5.ende, 'pino_A3_lachen'], [z.c5.ende, 'pino_A3_lachen'],
[niesen + 4, 'pino_B2_erschrocken'], [niesen + 4, 'pino_B2_erschrocken'],
@ -217,11 +221,16 @@ export const Szene4: React.FC = () => {
)} )}
<Nest x={NEST_X} y={BODEN} seite="hinten" voll={f < zurueck ? 0 : nestVoll} /> <Nest x={NEST_X} y={BODEN} seite="hinten" voll={f < zurueck ? 0 : nestVoll} />
{noaGeht ? ( {noaGeht ? (
<Geher figur="noa" spiegeln x={noaX} y={BODEN} /> <Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.15} />
) : hocke ? (
<NoaHocke start={hocke.start} auf={hocke.auf} x={noaX + schieben * 10} y={BODEN} stauchY={1 - 0.03 * beugen} drehung={beugen * 3 + schieben * 2} />
) : ( ) : (
<Figur pose={noaPose} x={noaX} y={BODEN} stauchY={atmen(f, 2)} /> <Figur
pose={noaPose}
x={noaX + (inHocke ? schieben * 10 : 0)}
y={BODEN}
stauchY={atmen(f, 2) * hockeStauch * (inHocke ? 1 - 0.03 * beugen : 1)}
drehung={inHocke ? beugen * 3 + schieben * 2 : 0}
blende={HOCKE_BLENDE}
/>
)} )}
{/* das große Blatt aus Szene 3 bleibt liegen */} {/* das große Blatt aus Szene 3 bleibt liegen */}
{f < zurueck + 40 && <Blatt x={GROSSES_BLATT_X} y={BODEN - 8} r={88} s={2.6} farbe="#c8642c" deckkraft={f > zurueck ? 1 - (f - zurueck) / 40 : 1} />} {f < zurueck + 40 && <Blatt x={GROSSES_BLATT_X} y={BODEN - 8} r={88} s={2.6} farbe="#c8642c" deckkraft={f > zurueck ? 1 - (f - zurueck) / 40 : 1} />}
@ -234,6 +243,7 @@ export const Szene4: React.FC = () => {
) : ( ) : (
<Figur <Figur
pose={pinoPose} pose={pinoPose}
spiegeln={pinoZuNoa}
x={pinoX} x={pinoX}
y={BODEN - 6 * sitzt + zaehlHuepfer + huepfer(f, niesen + 2, 30, 12) + insNest} y={BODEN - 6 * sitzt + zaehlHuepfer + huepfer(f, niesen + 2, 30, 12) + insNest}
stauchY={atmen(f, 1) * hopfStauch(f, niesen + 2) * hopfStauch(f, z.bild5.start - 20, 20)} stauchY={atmen(f, 1) * hopfStauch(f, niesen + 2) * hopfStauch(f, z.bild5.start - 20, 20)}