Folge 2, Rückmeldung 2: Pino läuft ins Bild, Kichern/Vorbeugen statt Rutschen, Bommel heil, Vögel schlagen immer, Blatt bleibt zwischen Szene 3 und 4, Lulus Blätter fliegen ins Maul, langsameres Malen mit Zimmer-Zwischenbild

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pascal Scholz 2026-10-29 20:20:30 +00:00
parent 8f3ce567dd
commit 6e3e5846a4
30 changed files with 117 additions and 44 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

@ -28,7 +28,8 @@ export const punkt = (figur: FigurName, x: number, y: number, cx: number, cy: nu
};
// Bekannte Punkte in der Leinwand (gemessen an den Standbildern)
export const LULU_MAUL_LINKS = {cx: 690, cy: 140}; // lulu_A5 (Blick links)
export const LULU_MAUL_LINKS = {cx: 692, cy: 170}; // lulu_A5 (Blick links)
export const LULU_MAUL_A1 = {cx: 597, cy: 167}; // lulu_A1 (Augen zu)
export const LULU_KOPF = {cx: 714, cy: 95};
export const PINO_KOPF = {cx: 640, cy: 260};

View file

@ -98,10 +98,9 @@ export const Voegel: React.FC<{start: number; anzahl?: number; skala?: number}>
const reihe = Math.ceil(i / 2) * (i % 2 ? 1 : -1); // Spitze vorn, Äste nach hinten
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 mit kurzen Gleitphasen
const takt = t * 0.38 + r('p') * 6;
const gleiten = Math.sin(t * 0.02 + r('q') * 6) > 0.6;
const schlag = gleiten ? 0.15 : Math.sin(takt);
// Flügelschlag ohne Pause, je Vogel etwas anders schnell
const takt = t * (0.34 + r('f') * 0.1) + r('p') * 6;
const schlag = Math.sin(takt);
const g = (34 + r('g') * 8) * skala * (1 - Math.abs(reihe) * 0.03);
return <Vogel key={i} x={x} y={y} g={g} schlag={schlag} neigung={-12 + Math.cos(takt) * 3} />;
})}

View file

@ -355,6 +355,32 @@ export const KalenderSVG: React.FC<{leer?: boolean}> = ({leer = false}) => {
</svg>
);
};
// Der Kalender auf dem Papierbogen am Tisch (Zimmer-Ansicht), Fortschritt p wie KalenderMalen.
const PAPIER = [[560, 790], [900, 784], [930, 822], [540, 828]];
const aufPapier = (u: number, v: number) => {
const [a, b, c, d] = PAPIER;
const ox = a[0] + (b[0] - a[0]) * u, oy = a[1] + (b[1] - a[1]) * u;
const ux = d[0] + (c[0] - d[0]) * u, uy = d[1] + (c[1] - d[1]) * u;
return `${ox + (ux - ox) * v},${oy + (uy - oy) * v}`;
};
export const KalenderAufTisch: React.FC<{p: number}> = ({p}) => {
const tp = Math.min(1, p / 0.08);
const tuerP = Math.max(0, (p - 0.08) / 0.92) * 24;
const viereck = (u0: number, v0: number, u1: number, v1: number) => [aufPapier(u0, v0), aufPapier(u1, v0), aufPapier(u1, v1), aufPapier(u0, v1)].join(' ');
return (
<svg viewBox={`0 0 ${B} ${H}`} style={{position: 'absolute', inset: 0, width: '100%', height: '100%'}}>
<polygon points={viereck(0.25, 0.1, 0.25 + 0.5 * tp, 0.2)} fill="#c8642c" opacity={0.8} />
{new Array(24).fill(0).map((_, i) => {
const anteil = Math.min(1, tuerP - i);
if (anteil <= 0) return null;
const u0 = 0.1 + (i % 6) * 0.135;
const v0 = 0.3 + Math.floor(i / 6) * 0.17;
return <polygon key={i} points={viereck(u0, v0, u0 + 0.11 * Math.max(0.3, anteil), v0 + 0.13)} fill={kTuer(i).farbe} opacity={0.85} />;
})}
</svg>
);
};
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`).

View file

@ -33,12 +33,14 @@ export const Szene2: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S2;
// Pino kommt von rechts getippelt und setzt sich
// Pino kommt von rechts (außerhalb des Bildes) getippelt und setzt sich
const pinoAn = z.bild1.ende + 6;
const pinoLos = z.bild1.start - 120;
const pinoX = kurve(f, [
[z.bild1.start - 10, 1560],
[pinoLos, 2000],
[pinoAn, PINO_X],
]);
const pinoDa = f >= pinoLos;
const pinoGeht = f < pinoAn;
const schreck = z.bild3.start;
const pinoPose: [number, string][] = [
@ -77,10 +79,6 @@ export const Szene2: React.FC = () => {
const noaX = kurve(f, [
[z.bild1.start + 10, -180],
[noaAn, NOA_X],
[z.n2.start, NOA_X],
[z.n2.start + 20, NOA_X + 60],
[z.p3.ende, NOA_X + 60],
[z.p3.ende + 30, NOA_X + 20],
]);
const noaGeht = f > z.bild1.start + 10 && f < noaAn;
const noaPose: [number, string][] = [
@ -92,9 +90,22 @@ export const Szene2: React.FC = () => {
[z.p3.start + 10, 'noa_A3_lachen'],
[z.e4.start, 'noa_A6_blick-rechts'],
];
// Kichern: schnelles kleines Hüpfen
const kichern = f > z.bild2.start + 15 && f < z.bild2.ende ? -Math.abs(Math.sin(f * 0.5)) * 6 : 0;
const noaY = BODEN + kichern + huepfer(f, z.n1.start, 18, 10);
// Kichern: schwillt an und ab, kleine Hüpfer mit Wiegen und Stauchen
const kichA = z.bild2.start + 15;
const kichE = z.bild2.ende;
const kichHuell = f > kichA && f < kichE ? Math.sin(((f - kichA) / (kichE - kichA)) * Math.PI) : 0;
const kichPhase = (f - kichA) * (0.42 + 0.08 * Math.sin((f - kichA) * 0.05));
const kichern = -Math.abs(Math.sin(kichPhase)) * 7 * kichHuell;
const kichStauch = 1 - 0.035 * Math.abs(Math.cos(kichPhase)) * kichHuell;
const kichDreh = (Math.sin(kichPhase * 0.5) * 2.5 + 2) * kichHuell;
// „Pino? Pino!“: Noa beugt sich zu Pino hinüber
const vorbeugen = kurve(f, [
[z.n2.start, 0],
[z.n2.start + 14, 1],
[z.p3.start, 1],
[z.p3.start + 16, 0],
]);
const noaY = BODEN + kichern + huepfer(f, z.n1.start, 18, 10) + huepfer(f, z.n2.start + 30, 8, 10);
const kam = kamFahrt(f, [
[0, 960, 540, 1.02],
@ -114,8 +125,8 @@ export const Szene2: React.FC = () => {
<>
<Kamera {...kam}>
<Ort name="garten" />
{noaGeht ? <Geher figur="noa" spiegeln x={noaX} y={BODEN} /> : <Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2)} />}
{pinoGeht ? <Geher figur="pino" x={pinoX} y={BODEN} tempo={1.3} /> : <Figur pose={pinoPose} x={pinoX} y={pinoY} stauchY={pinoStauchY} drehung={pinoDreh} blende={4} />}
{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} />}
{!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} />
</Kamera>
<Blaetterwind anzahl={6} />

View file

@ -32,7 +32,7 @@ export const S3 = plane('s3', [
]);
// Das große Blatt, auf das Pino sich setzt
const GROSSES_BLATT_X = PINO_X + 50;
export const GROSSES_BLATT_X = PINO_X + 50;
export const Szene3: React.FC = () => {
const f = useCurrentFrame();
@ -116,7 +116,7 @@ export const Szene3: React.FC = () => {
// Noa kniet sich zu Pino
const knien = z.bild3.start + 10;
const noaX = kurve(f, [
[knien - 36, NOA_X + 20],
[knien - 36, NOA_X],
[knien, NOA_X + 140],
]);
const noaPose: [number, string][] = [

View file

@ -2,10 +2,11 @@ import React from 'react';
import {useCurrentFrame} from 'remotion';
import {kurve} from '../bewegung';
import {Figur, Geher, Kamera, NOA_HOCKE_FRAMES, NoaHocke} from '../baukasten/figur';
import {Blende, 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 {BODEN, NEST_X, Ort} from '../baukasten/orte';
import {NOA_X, PINO_X} from './szene2';
import {GROSSES_BLATT_X} from './szene3';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
@ -90,9 +91,15 @@ export const Szene4: React.FC = () => {
[pinoGehtBis, PINO_ZAEHL_X],
[zurueck + 10, PINO_ZAEHL_X],
[zurueck + 60, NEST_X + 150],
[z.bild4.ende - 20, NEST_X + 110],
[z.bild5.start - 20, NEST_X + 150],
[z.bild5.start, NEST_X],
]);
const insNest = huepfer(f, z.bild5.start - 20, 46, 20); // Sprung ins Nest
// Anfang wie Ende von Szene 3: Pino lehnt am großen Blatt und richtet sich auf
const sitzt = kurve(f, [
[z.n1.start + 10, 1],
[z.n1.start + 34, 0],
]);
const pinoGeht = (f > pinoGehtVon && f < pinoGehtBis) || (f > zurueck + 10 && f < zurueck + 60);
const pinoSpiegel = f > pinoGehtVon && f < pinoGehtBis; // nach rechts
const imNest = f >= z.bild5.start;
@ -130,20 +137,44 @@ export const Szene4: React.FC = () => {
]);
const luluPose: [number, string][] = [
[0, 'lulu_A5_blick-links'],
[z.l1.start + 10, 'lulu_A3_lachen'],
[kitzeln + 10, 'lulu_A1_augen-zu'],
[niesen + 18, 'lulu_B2_erschrocken'],
[z.n3.start + 20, 'lulu_A3_lachen'],
];
const luluY = BODEN - 10 - strecken * 26;
const maul = punkt('lulu', LULU_BAUM_X, luluY, LULU_MAUL_LINKS.cx, LULU_MAUL_LINKS.cy);
const buendel = f > amBaum + 50 && f < niesen;
// Blätter fliegen vom Ast ins Maul
const zupfen = [0, 1, 2, 3].map((i) => {
const s = amBaum + 55 + i * 14;
const t = Math.min(1, Math.max(0, (f - s) / 16));
return {x: maul.x - 160 + i * 50 + (maul.x - (maul.x - 160 + i * 50)) * t, y: maul.y - 120 + 120 * t, sichtbar: f > s - 20 && t < 1};
const luluStauch = atmen(f, 3, 0.006) * (1 + 0.02 * strecken);
const luluDreh = zurueckLehnen * 4;
// Punkt im Bild von Lulu, mit Drehung und Stauchung wie die Figur selbst
const luluPunkt = (c: {cx: number; cy: number}) => {
const p = punkt('lulu', 0, 0, c.cx, c.cy);
const dy = p.y * luluStauch;
const a = (luluDreh * Math.PI) / 180;
return {x: LULU_BAUM_X + p.x * Math.cos(a) - dy * Math.sin(a), y: luluY + p.x * Math.sin(a) + dy * Math.cos(a)};
};
// Maul wandert mit dem Posenwechsel A5 → A1 mit
const m5 = luluPunkt(LULU_MAUL_LINKS);
const m1 = luluPunkt(LULU_MAUL_A1);
const zuA1 = kurve(f, [
[kitzeln + 10, 0],
[kitzeln + 15, 1],
]);
const maul = {x: m5.x + (m1.x - m5.x) * zuA1, y: m5.y + (m1.y - m5.y) * zuA1};
// Blätter hängen am Ast, Lulu zupft eins nach dem anderen ab; das Bündel im Maul wächst mit
const astMaul = punkt('lulu', LULU_BAUM_X, BODEN - 36, LULU_MAUL_LINKS.cx, LULU_MAUL_LINKS.cy);
const zupfen = [0, 1, 2, 3, 4].map((i) => {
const ast = {x: astMaul.x - 150 + i * 55, y: astMaul.y - 115 - (i % 2) * 35};
const los = amBaum + 50 + i * 18;
const t = Math.min(1, Math.max(0, (f - los) / 20));
const e = t * t * (3 - 2 * t);
return {
x: ast.x + (maul.x - ast.x) * e,
y: ast.y + (maul.y - ast.y) * e - Math.sin(t * Math.PI) * 30,
r: t > 0 ? 160 + t * 380 : 160 + Math.sin(f * 0.12 + i * 2) * 12,
deck: Math.min(1, Math.max(0, (f - amBaum - i * 3) / 12)),
angekommen: t >= 1,
};
});
const imMaul = f < niesen ? zupfen.filter((b) => b.angekommen).length : 0;
// Kamera
const kam = kamFahrt(f, [
@ -165,7 +196,7 @@ export const Szene4: React.FC = () => {
]);
kam.y += ruck(f, niesen, 9, 22);
const lulu = <Figur pose={luluPose} x={LULU_BAUM_X} y={luluY} stauchY={atmen(f, 3, 0.006) * (1 + 0.02 * strecken)} drehung={zurueckLehnen * 4} />;
const lulu = <Figur pose={luluPose} x={LULU_BAUM_X} y={luluY} stauchY={luluStauch} drehung={luluDreh} />;
const luluBeimNest = f >= zurueck && <Figur pose="lulu_A_hals-unten" x={2030} y={BODEN - 4} drehung={schieben * 1.2} />;
return (
@ -173,14 +204,10 @@ export const Szene4: React.FC = () => {
<Kamera {...kam}>
<Ort name="garten" />
{f >= amBaum && f < zurueck && lulu}
{buendel && (
<div style={{position: 'absolute', left: 0, top: 0}}>
{LAUB.slice(0, 5).map((c, i) => (
<Blatt key={i} x={maul.x - 6 + (i - 2) * 9} y={maul.y + 14 + Math.abs(i - 2) * 4} r={(i - 2) * 30 + 180} s={0.42} farbe={c} />
{f >= amBaum && f < niesen && zupfen.map((b, i) => (!b.angekommen ? <Blatt key={i} x={b.x} y={b.y} r={b.r} s={0.42} farbe={LAUB[i]} deckkraft={b.deck} /> : null))}
{LAUB.slice(0, imMaul).map((c, i) => (
<Blatt key={`m${i}`} x={maul.x - 4 + (i - 2) * 9} y={maul.y + 12 + Math.abs(i - 2) * 4} r={(i - 2) * 30 + 180} s={0.42} farbe={c} />
))}
</div>
)}
{zupfen.map((b, i) => (b.sichtbar ? <Blatt key={i} x={b.x} y={b.y} r={f * 8 + i * 70} s={0.42} farbe={LAUB[i]} /> : null))}
{/* Reihe aus fünf Blättern vor dem Holzstapel */}
{f < zurueck + 40 &&
@ -195,6 +222,8 @@ export const Szene4: React.FC = () => {
) : (
<Figur pose={noaPose} x={noaX} y={BODEN} stauchY={atmen(f, 2)} />
)}
{/* 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} />}
{imNest ? (
<div style={{position: 'absolute', inset: 0, clipPath: `inset(0 0 ${1080 - BODEN - 4}px 0)`}}>
<Figur pose={pinoPose} x={NEST_X} y={pinoImNestY} schatten={false} />
@ -205,8 +234,9 @@ export const Szene4: React.FC = () => {
<Figur
pose={pinoPose}
x={pinoX}
y={BODEN + zaehlHuepfer + huepfer(f, niesen + 2, 30, 12)}
stauchY={atmen(f, 1) * hopfStauch(f, niesen + 2)}
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)}
drehung={-9 * sitzt}
/>
)}
<Nest x={NEST_X} y={BODEN} seite="vorne" voll={f < zurueck ? 0 : nestVoll} />

View file

@ -4,7 +4,7 @@ import {kurve} from '../bewegung';
import {ClipFigur, Figur, Kamera} from '../baukasten/figur';
import {Blende, Vignette, atmen, huepfer, kamFahrt} from '../baukasten/helfer';
import {Funkeln} from '../baukasten/natur';
import {KALENDER_TUER1, KalenderMalen, Ort} from '../baukasten/orte';
import {KALENDER_TUER1, KalenderAufTisch, KalenderMalen, Ort} from '../baukasten/orte';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
@ -18,7 +18,7 @@ const LULU_X6 = 1440;
export const S6 = plane('s6', [
{id: 'bild0', dauer: 3.0},
{id: 'malen', dauer: 4.0},
{id: 'malen', dauer: 5.5},
{id: 'l1', wer: 'LULU', text: 'Was malst du da, Noa?'},
{id: 'n1', wer: 'NOA', text: 'Einen Kalender! Mit 24 Türchen.'},
{id: 'n2', wer: 'NOA', text: 'Wir öffnen jeden Tag eins. Und hinter jedem Türchen steckt etwas, das Pino wach hält.'},
@ -34,10 +34,14 @@ export const Szene6: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S6;
// Noa malt: Blick von oben aufs Blatt, während „malen“ und während sie antwortet (n1)
// Noa malt: Blick von oben aufs Blatt, während „malen“ und während sie antwortet (n1).
// Langsamer Anfang, dazwischen Zimmer (es geht weiter), bei n1 ist er fast fertig.
const malP = kurve(f, [
[z.malen.start, 0],
[z.n1.ende, 1],
[z.malen.start + 50, 0.03],
[z.malen.ende, 0.2],
[z.n1.start, 0.8],
[z.n1.ende, 0.96],
]);
const malSicht = (f >= z.malen.start && f < z.l1.start) || (f >= z.n1.start && f < z.n1.ende + 6);
const malt = f < z.n1.ende;
@ -60,7 +64,8 @@ export const Szene6: React.FC = () => {
const kam = kamFahrt(f, [
[0, 960, 540, 1.0],
[z.bild0.ende, 1060, 560, 1.15],
[z.n1.start, 900, 640, 1.45],
[z.l1.start, 1080, 640, 1.5],
[z.n1.ende, 1060, 650, 1.5],
[z.p1.start, 980, 650, 1.6],
[z.n4.ende, 960, 640, 1.5],
[sprung + 10, 1050, 700, 2.0],
@ -144,6 +149,7 @@ export const Szene6: React.FC = () => {
schatten={false}
/>
<Ort name="zimmer_tisch" />
{f >= z.malen.start && <KalenderAufTisch p={malP} />}
{imSprung ? (
<ClipFigur name="pino_C3_freudensprung" start={sprung} tempo={1.4} x={PINO_X6} y={TISCH_Y} hoehe={N * 0.4} />
) : (