Gehen ohne rutschende Füße (baukasten/gang.ts, gemessen an den Geh-Clips), Folge 8 „Noas Laterne“ in Arbeit, Lied 1: Pino hüpft wieder nach oben

- gang.ts: Geschwindigkeit aus der Fußbewegung im Clip, Start/Ende mit geschlossenen Füßen, ganze Schritte;
  GangTest als Prüfstand (Stiefel steht 21 Bilder auf ±1 px). Lulus Geh-Clip ist kein Gehen – nicht verschieben.
- Lied 1: huepfer() ist negativ und wurde abgezogen – Pino sank bei jeder Zahl in den Boden.
- folge08: Texte DE/ES/FR, Szene 2–7, Abendgarten mit Hauswand, Laterne mit Sternen/Igel, Wand-Schatten

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lara 2026-11-05 19:50:15 +00:00
parent 75b28a9cab
commit c6ca31402d
20 changed files with 1088 additions and 1 deletions

View file

@ -2,6 +2,7 @@ import React from 'react';
import {Composition} from 'remotion'; import {Composition} from 'remotion';
import {HintergrundAbend, PapierStruktur, VordergrundAbend} from './szenen/Hintergruende'; import {HintergrundAbend, PapierStruktur, VordergrundAbend} from './szenen/Hintergruende';
import {LaterneTest} from './szenen/LaterneTest'; import {LaterneTest} from './szenen/LaterneTest';
import {GANGTEST_DAUER, GangTest} from './szenen/GangTest';
import {VerformTest} from './szenen/VerformTest'; import {VerformTest} from './szenen/VerformTest';
import {FolgenKompositionen} from './baukasten/folge'; import {FolgenKompositionen} from './baukasten/folge';
import {NEST_BILD} from './baukasten/natur'; import {NEST_BILD} from './baukasten/natur';
@ -9,6 +10,7 @@ import {NestStill, ORTE, OrtStill} from './baukasten/orte';
import {F01} from './folge01/Folge01'; import {F01} from './folge01/Folge01';
import {F02} from './folge02/Folge02'; import {F02} from './folge02/Folge02';
import {F03} from './folge03/Folge03'; import {F03} from './folge03/Folge03';
import {F08} from './folge08/Folge08';
import {Banner, BannerRahmen, Profilbild} from './kanal/KanalBilder'; import {Banner, BannerRahmen, Profilbild} from './kanal/KanalBilder';
import {VorschauF01} from './kanal/Vorschaubild'; import {VorschauF01} from './kanal/Vorschaubild';
import {ShortKompositionen} from './shorts/Short'; import {ShortKompositionen} from './shorts/Short';
@ -23,6 +25,7 @@ export const Root: React.FC = () => (
<> <>
<Composition id="Lied01" component={Lied01} durationInFrames={LIED01_DAUER} {...BILD} /> <Composition id="Lied01" component={Lied01} durationInFrames={LIED01_DAUER} {...BILD} />
<Composition id="Lied03-DE" component={Lied03} durationInFrames={LIED03_DAUER} {...BILD} /> <Composition id="Lied03-DE" component={Lied03} durationInFrames={LIED03_DAUER} {...BILD} />
<Composition id="GangTest" component={GangTest} durationInFrames={GANGTEST_DAUER} {...BILD} />
<Composition id="LaterneTest" component={LaterneTest} durationInFrames={420} {...BILD} /> <Composition id="LaterneTest" component={LaterneTest} durationInFrames={420} {...BILD} />
<Composition id="VerformTest" component={VerformTest} durationInFrames={240} {...BILD} /> <Composition id="VerformTest" component={VerformTest} durationInFrames={240} {...BILD} />
<Composition id="HintergrundAbend" component={HintergrundAbend} durationInFrames={1} {...BILD} /> <Composition id="HintergrundAbend" component={HintergrundAbend} durationInFrames={1} {...BILD} />
@ -31,6 +34,7 @@ export const Root: React.FC = () => (
<FolgenKompositionen folge={F01} /> <FolgenKompositionen folge={F01} />
<FolgenKompositionen folge={F02} /> <FolgenKompositionen folge={F02} />
<FolgenKompositionen folge={F03} /> <FolgenKompositionen folge={F03} />
<FolgenKompositionen folge={F08} />
<ShortKompositionen szenen={SZENEN_F01} shorts={SHORTS_F01} /> <ShortKompositionen szenen={SZENEN_F01} shorts={SHORTS_F01} />
<Composition id="Kanal-Profilbild" component={Profilbild} durationInFrames={1} fps={30} width={800} height={800} /> <Composition id="Kanal-Profilbild" component={Profilbild} durationInFrames={1} fps={30} width={800} height={800} />
<Composition id="Vorschau-F01" component={VorschauF01} durationInFrames={1} {...BILD} /> <Composition id="Vorschau-F01" component={VorschauF01} durationInFrames={1} {...BILD} />

View file

@ -0,0 +1,65 @@
import {GROESSE, NOA_HOEHE} from './figur';
import {CLIPS, FIGUREN} from './posen';
// Gehen ohne rutschende Füße.
// In den Geh-Clips (*_C1_gehen-schleife) geht die Figur auf der Stelle; der aufgesetzte Fuß wandert
// dabei gleichmäßig nach hinten. Bewegt sich die Figur im Bild genau so schnell nach vorn, bleibt der
// Fuß am Boden stehen. Gemessen am 05.11.2026 an den Alpha-Kanten der Füße (Bodenkontakt je Clipbild):
// Noa: 10,1 px pro Clipbild (Video 292 px breit), Füße zusammen bei Clipbild ≈ 10,5, alle 10,25 Bilder ein Schritt
// Pino: 3,6 px pro Clipbild (Video 438 px breit), Füße zusammen bei Clipbild ≈ 15, alle 15 Bilder ein Schritt
// Lulus Clip ist kein Gehen (drei Beine bleiben stehen) – Lulu nie mit dem Clip über den Boden schieben.
// Ein Gang beginnt und endet mit geschlossenen Füßen; die Strecke wird auf ganze Schritte gerundet.
type Messung = {pxProClipbild: number; videoBreite: number; fuesseZusammen: number; schrittBilder: number};
const MESSUNG: Record<'noa' | 'pino', Messung> = {
noa: {pxProClipbild: 10.1, videoBreite: 292, fuesseZusammen: 10.5, schrittBilder: 10.25},
pino: {pxProClipbild: 3.6, videoBreite: 438, fuesseZusammen: 15, schrittBilder: 15},
};
const CLIP_FPS = 24;
const FPS = 30;
// Bildschirm-Pixel (in Szenenkoordinaten) je Clipbild bei Normalgröße
const pxProClipbild = (figur: 'noa' | 'pino', hoehe = NOA_HOEHE * GROESSE[figur]) => {
const m = MESSUNG[figur];
const c = CLIPS[`${figur}_C1_gehen-schleife`];
return ((m.pxProClipbild * c.b) / m.videoBreite) * (hoehe / FIGUREN[figur].refHoehe);
};
export type Gang = {
figur: 'noa' | 'pino';
start: number; // Frame, an dem die Figur losgeht
ende: number; // Frame, an dem sie wieder steht
von: number;
nach: number; // tatsächliches Ziel (auf ganze Schritte gerundet)
tempo: number; // für ClipFigur
clipStart: number; // für ClipFigur: so steht der Clip bei `start` auf „Füße zusammen“
spiegeln: boolean; // Clips schauen nach links; nach rechts gehen = spiegeln
x: (f: number) => number;
geht: (f: number) => boolean;
};
// Plant einen Gang von `von` nach (ungefähr) `nach`, Start bei Frame `start`.
// tempo: wie schnell der Clip läuft (1,25 = jedes Videobild ein Clipbild); die Geschwindigkeit folgt daraus.
export const gang = (figur: 'noa' | 'pino', start: number, von: number, nach: number, tempo = 1.25, hoehe?: number): Gang => {
const m = MESSUNG[figur];
const clipbilderProFrame = (tempo * CLIP_FPS) / FPS;
const proSchritt = pxProClipbild(figur, hoehe) * m.schrittBilder;
const schritte = Math.max(1, Math.round(Math.abs(nach - von) / proSchritt));
const richtung = Math.sign(nach - von) || 1;
const ziel = von + richtung * schritte * proSchritt;
const dauer = (schritte * m.schrittBilder) / clipbilderProFrame;
const ende = start + dauer;
return {
figur,
start,
ende,
von,
nach: ziel,
tempo,
clipStart: start - m.fuesseZusammen / clipbilderProFrame,
spiegeln: richtung > 0,
x: (f) => (f <= start ? von : f >= ende ? ziel : von + ((ziel - von) * (f - start)) / dauer),
geht: (f) => f >= start && f < ende,
};
};

View file

@ -0,0 +1,45 @@
import React from 'react';
import {INTRO_OPTIONEN, Intro, OUTRO, Outro, introZeilen} from '../baukasten/buch';
import {baueFolge, type SzenenEintrag} from '../baukasten/folge';
import {mitKlang} from '../baukasten/geraeusche';
import {TITEL, plane} from './plan';
import {S2, Szene2} from './szene2';
import {S3, Szene3} from './szene3';
// Folge 8 „Noas Laterne“ (läuft am Mi 11.11., St. Martin) – per Code aus der Figuren-Bibliothek,
// Drehbuch drehbuecher/neu-noas-laterne/de.md. Gehen nur mit baukasten/gang.ts (keine rutschenden Füße).
const INTRO = plane('intro', introZeilen(TITEL), INTRO_OPTIONEN);
export const SZENEN: SzenenEintrag[] = [
{
name: 'Intro',
datei: 'Folge08.tsx',
plan: INTRO,
K: mitKlang(() => <Intro plan={INTRO} titel={TITEL} posen={['pino_A3_lachen', 'noa_A3_lachen', 'lulu_A3_lachen']} />, INTRO, {
toene: [{name: 'buch_auf', bei: 'buch', versatz: 0.7, lautstaerke: 0.35}],
}),
musik: {datei: 'musik/folge01/intro.mp3', lautstaerke: 0.35, rein: 0.2, raus: 1.2},
},
{
name: 'Was wird das',
datei: 'szene2.tsx',
plan: S2,
K: mitKlang(Szene2, S2, {atmo: 'zimmer', atmoLaut: 0.1, toene: [{name: 'rascheln', bei: 'bild0', versatz: 0.3, lautstaerke: 0.2, laenge: 1.5}]}),
},
{
name: 'Es wird dunkel',
datei: 'szene3.tsx',
plan: S3,
K: mitKlang(Szene3, S3, {atmo: 'garten_abend', atmoLaut: 0.12, toene: [{name: 'klick', bei: 'klick', versatz: 0.35, lautstaerke: 0.5}]}),
},
{
name: 'Outro',
datei: 'Folge08.tsx',
plan: OUTRO,
K: mitKlang(() => <Outro />, OUTRO, {toene: [{name: 'buch_zu', bei: 'buch', versatz: 3.2, lautstaerke: 0.45}]}),
musik: {datei: 'musik/folge01/schlaflied.mp3', lautstaerke: 0.3, rein: 0.1, raus: 2},
},
];
export const F08 = baueFolge('F08', SZENEN);

View file

@ -0,0 +1,29 @@
import {SPRACHE, type FolgenTexte} from '../baukasten/sprache';
import {plane as basis, type PlanOptionen, type Zeile} from '../baukasten/zeit';
import DE from './texte/de.json';
import ES from './texte/es.json';
import FR from './texte/fr.json';
import STIMMEN_DE from './stimmen/de.json';
import STIMMEN_ES from './stimmen/es.json';
import STIMMEN_FR from './stimmen/fr.json';
// Sprechtexte stehen nur in texte/<sprache>.json (Schlüssel „szene/zeile“),
// die Szenen enthalten nur Ablauf und Bewegung. Aufnahmen je Sprache in
// stimmen/<sprache>.json bzw. public/stimmen/folge08/<sprache>/ (erzeugt von
// werkzeuge/stimmen.mts).
export const TEXTE: FolgenTexte = {de: DE, es: ES, fr: FR}[SPRACHE];
const AUFNAHMEN = {de: STIMMEN_DE, es: STIMMEN_ES, fr: STIMMEN_FR}[SPRACHE];
export const TITEL = TEXTE.titel;
// Zeitplan einer Szene dieser Folge, mit den Texten der gewählten Sprache
export const plane = (szene: string, zeilen: Zeile[], opt: PlanOptionen = {}) =>
basis(
zeilen.map((z) => {
if (z.text !== undefined || !z.wer) return z;
const text = TEXTE.zeilen[`${szene}/${z.id}`];
return {...z, text: text ?? `[fehlt: ${szene}/${z.id}]`};
}),
{...opt, szene, aufnahmen: AUFNAHMEN, ordner: `stimmen/folge08/${SPRACHE}`},
);

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1,133 @@
import React from 'react';
import {interpolate, useCurrentFrame} from 'remotion';
import {Figur, Kamera, POSEN_SKALA} from '../baukasten/figur';
import {Blende, Vignette, atmen, huepfer, kamFahrt} from '../baukasten/helfer';
import {Funkeln} from '../baukasten/natur';
import {Ort} from '../baukasten/orte';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {Laterne} from './teile';
// Szene 2: Was wird das? Nachmittags in Noas Zimmer. Noa bastelt am Tisch, Pino sitzt auf dem Tisch,
// Lulu schaut durchs Fenster herein. Bei jeder Zahl klebt Noa einen Stern auf (Nahaufnahme), dann
// den Papier-Igel; Lulu pustet den Kleber trocken. Aufbau wie Folge 2, Szene 6 (Noas Zimmer).
const N = 420; // Noas Größe im Zimmer (näher an der Kamera)
const NOA_X = 760;
const PINO_X = 1110;
const TISCH_Y = 802;
const LATERNE = {x: 950, y: TISCH_Y - 64};
const LULU_X = 1440;
export const S2 = plane('s2', [
{id: 'bild0', dauer: 2.5},
{id: 'l1', wer: 'LULU'},
{id: 'n1', wer: 'NOA'},
{id: 'e1', wer: 'ERZ', pause: 3},
{id: 'n2', wer: 'NOA'},
{id: 'p1', wer: 'PINO'},
{id: 'n3', wer: 'NOA'},
{id: 'z1', wer: 'NOA', pause: 0.8},
{id: 'z2', wer: 'NOA', pause: 0.8},
{id: 'z3', wer: 'NOA', pause: 0.8},
{id: 'z4', wer: 'NOA', pause: 0.8},
{id: 'z5', wer: 'NOA', pause: 0.6},
{id: 'p2', wer: 'PINO'},
{id: 'n4', wer: 'NOA'},
{id: 'igel', dauer: 1.8},
{id: 'l2', wer: 'LULU'},
{id: 'pusten', dauer: 2.6},
{id: 'p3', wer: 'PINO'},
{id: 'bild1', dauer: 1.4},
]);
const ZAHLEN = ['z1', 'z2', 'z3', 'z4', 'z5'] as const;
export const Szene2: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S2;
const sterne = ZAHLEN.filter((id) => f >= z[id].start).length;
const igel = interpolate(f, [z.igel.start + 8, z.igel.start + 26], [0, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
const pusten = f >= z.pusten.start && f < z.p3.ende;
const wind = pusten ? Math.sin(((f - z.pusten.start) / 30) * Math.PI * 1.6) : 0;
const nah = f >= z.n3.ende - 6 && f < z.igel.ende; // Nahaufnahme beim Zählen und Igel-Kleben
const kam = nah
? kamFahrt(f, [
[z.n3.ende - 6, LATERNE.x - 10, LATERNE.y - 10, 2.6],
[z.igel.ende, LATERNE.x, LATERNE.y - 4, 2.8],
])
: kamFahrt(f, [
[0, 1000, 540, 1.0],
[z.bild0.ende, 1080, 560, 1.12],
[z.l1.ende, 1060, 600, 1.3],
[z.e1.start, 960, 640, 1.45],
[z.p1.start, 1030, 650, 1.55],
[z.l2.start, 1140, 560, 1.2],
[z.pusten.start + 10, 1080, 640, 1.5],
[dauer, 1060, 640, 1.55],
]);
// Pino zeigt bei jeder Zahl mit der Pfote: kleiner Hopser; beim Pusten wehen die Stacheln, er kichert
const pinoHopser = ZAHLEN.reduce((a, id) => a + huepfer(f, z[id].start + 2, 10, 9), 0) + huepfer(f, z.p2.start, 16, 11) + huepfer(f, z.p3.start + 4, 12, 10);
const pinoWeh = pusten ? 5 * wind : 0;
return (
<>
<Kamera {...kam}>
<Ort name="zimmer_himmel" />
<Figur
pose={[
[0, 'lulu_A5_blick-links'],
[z.n2.start, 'lulu_A4_staunen'],
[z.n2.ende, 'lulu_A3_lachen'],
[z.l2.start, 'lulu_A5_blick-links'],
[z.p3.start, 'lulu_A3_lachen'],
]}
x={LULU_X}
y={1180}
hoehe={N * 2.05}
schatten={false}
drehung={pusten ? -2 + wind : 0}
/>
<Ort name="zimmer_wand" />
{/* Wind vom Pusten: drei weiche Linien vom Fenster zur Laterne */}
{pusten && (
<svg viewBox="0 0 1920 1080" style={{position: 'absolute', inset: 0, width: 1920, height: 1080}}>
{[0, 1, 2].map((i) => {
const p = ((f - z.pusten.start) / 26 + i / 3) % 1;
const x = interpolate(p, [0, 1], [1320, 1000]);
return <path key={i} d={`M ${x} ${640 + i * 40} q -40 -16 -80 0 t -80 0`} stroke="#ffffff" strokeWidth={6} strokeLinecap="round" fill="none" opacity={Math.sin(p * Math.PI) * 0.7} />;
})}
</svg>
)}
<Figur pose="noa_B_sitzt" x={NOA_X} y={1000} hoehe={N / POSEN_SKALA.noa_B_sitzt} stauchY={atmen(f, 2)} drehung={nah ? Math.sin(f * 0.3) * 0.6 : 0} schatten={false} />
<Ort name="zimmer_tisch" />
<Laterne x={LATERNE.x} y={LATERNE.y} s={1.15} sterne={sterne} igel={igel} schwung={pusten ? 7 * wind : 0} stab={false} />
{ZAHLEN.map((id, i) => (
<Funkeln key={id} x={LATERNE.x + [-34, 22, 40, -6, -44][i] * 1.15} y={LATERNE.y + [-28, -40, 6, 30, 18][i] * 1.15} start={z[id].start} dauer={22} s={0.5} />
))}
<Figur
pose={[
[0, 'pino_A6_blick-rechts'],
[z.l1.ende, 'pino_A5_blick-links'],
[z.n2.start, 'pino_A4_staunen'],
[z.p1.start, 'pino_A3_lachen'],
[z.n3.ende, 'pino_A5_blick-links'],
[z.p2.start, 'pino_A4_staunen'],
[z.n4.start, 'pino_A3_lachen'],
]}
x={PINO_X}
y={TISCH_Y + pinoHopser}
hoehe={N * 0.4}
drehung={pinoWeh}
stauchY={atmen(f, 1)}
/>
</Kamera>
<Vignette />
<Untertitel zeilen={S2.liste} />
<Blende dauer={dauer} rein={14} raus={10} />
</>
);
};

View file

@ -0,0 +1,100 @@
import React from 'react';
import {interpolate, useCurrentFrame} from 'remotion';
import {ClipFigur, Figur, Kamera} from '../baukasten/figur';
import {gang} from '../baukasten/gang';
import {Blende, Vignette, atmen, kamFahrt} from '../baukasten/helfer';
import {Funkeln} from '../baukasten/natur';
import {BODEN} from '../baukasten/orte';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {Abenddunkel, GartenAbend, Laterne, laterneInNoasHand} from './teile';
// Szene 3: Es wird dunkel. Abendgarten, rechts die Hauswand, Papa winkt im Fenster.
// Noa knipst das Lämpchen an (kein Feuer) und singt den Laternenvers; die Laterne pendelt sanft.
// Pino tippelt zu ihr (baukasten/gang.ts, Füße rutschen nicht). Lulu steht schon an der Hauswand –
// sie geht nicht durchs Bild, weil ihr Geh-Clip kein echtes Gehen ist.
const NOA_X = 760;
const LULU_X = 1640; // rechts vom Fenster, damit Papa zu sehen ist
export const S3 = plane('s3', [
{id: 'bild0', dauer: 3.0},
{id: 'e1', wer: 'ERZ'},
{id: 'klick', dauer: 1.4},
{id: 'e2', wer: 'ERZ'},
{id: 'p1', wer: 'PINO'},
{id: 'v1', wer: 'NOA'},
{id: 'v2', wer: 'NOA'},
{id: 'v3', wer: 'NOA'},
{id: 'v4', wer: 'NOA', pause: 0.6},
{id: 'e3', wer: 'ERZ', pause: 0.8},
{id: 'w1', wer: 'NOA'},
{id: 'w2', wer: 'NOA'},
{id: 'w3', wer: 'NOA'},
{id: 'w4', wer: 'NOA'},
{id: 'bild1', dauer: 1.8},
]);
export const Szene3: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S3;
const an = z.klick.start + 12;
const licht = interpolate(f, [an, an + 8], [0, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
const singt = f >= z.v1.start && f < z.w4.ende;
const schwung = singt ? Math.sin((f - z.v1.start) * 0.09) * 7 : Math.sin(f * 0.05) * 2;
const lat = laterneInNoasHand(NOA_X);
// Pino kommt beim Vers dicht an Noas Stiefel (ganze Schritte, Füße bleiben am Boden)
const PINO = gang('pino', z.v1.start + 6, 300, NOA_X - 120, 2.5);
const kam = kamFahrt(f, [
[0, 1000, 540, 1.0],
[z.bild0.ende, 980, 560, 1.06],
[z.klick.start, 760, 640, 1.55],
[z.e2.ende, 720, 640, 1.7],
[z.p1.start + 6, lat.x, lat.y, 2.3], // „Mein Igel leuchtet!“
[z.p1.ende + 10, 820, 640, 1.35],
[z.w1.start, 900, 600, 1.2],
[dauer, 920, 600, 1.15],
]);
return (
<>
<Kamera {...kam}>
<GartenAbend papaWinkt={f < z.e1.ende ? f * 0.22 : 0} />
<Figur pose={[[0, 'lulu_A5_blick-links'], [z.p1.start, 'lulu_A3_lachen']]} x={LULU_X} y={BODEN - 6} stauchY={atmen(f, 3, 0.006)} />
<Figur
pose={[
[0, 'noa_A_laterne'],
]}
x={NOA_X}
y={BODEN}
stauchY={atmen(f, 2)}
drehung={singt ? Math.sin((f - z.v1.start) * 0.045) * 1.5 : 0}
/>
<Laterne x={lat.x} y={lat.y} s={lat.s} licht={licht} schwung={schwung} stab={false} />
<Funkeln x={lat.x} y={lat.y} start={an} dauer={30} s={0.8} />
{PINO.geht(f) ? (
<ClipFigur name="pino_C1_gehen-schleife" start={PINO.clipStart} tempo={PINO.tempo} schleife spiegeln={PINO.spiegeln} x={PINO.x(f)} y={BODEN} />
) : (
<Figur
pose={[
[0, 'pino_A6_blick-rechts'],
[an, 'pino_A4_staunen'],
[z.v1.start, 'pino_A6_blick-rechts'],
[PINO.ende, 'pino_A3_lachen'],
]}
x={PINO.x(f)}
y={BODEN}
stauchY={atmen(f, 1)}
/>
)}
<Abenddunkel licht={{x: lat.x, y: lat.y}} staerke={0.55 - 0.15 * licht} radius={380 + 260 * licht} />
</Kamera>
<Vignette />
<Untertitel zeilen={S3.liste} />
<Blende dauer={dauer} rein={14} raus={14} />
</>
);
};

View file

@ -0,0 +1,56 @@
import React from 'react';
import {useCurrentFrame} from 'remotion';
import {gang} from '../baukasten/gang';
import {Blende, kamFahrt} from '../baukasten/helfer';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {PLATZ, Wandszene} from './wand';
// Szene 4: Der große Schatten. An der Hauswand steht ein riesiger, weicher Giraffen-Schatten.
// Pinos Stacheln gehen hoch, er rückt an Noas Stiefel. Noa hockt sich hin und stellt die Laterne ins Gras.
// Lulu steht etwas im Dunkeln (Kinder dürfen sie ruhig schon sehen – so bleibt es freundlich).
export const S4 = plane('s4', [
{id: 'bild0', dauer: 3.0},
{id: 'e1', wer: 'ERZ', pause: 3},
{id: 'p1', wer: 'PINO', leise: true},
{id: 'n1', wer: 'NOA'},
{id: 'e2', wer: 'ERZ'},
{id: 'e3', wer: 'ERZ', pause: 3},
]);
export const Szene4: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S4;
const erschrickt = z.bild0.start + 40;
const PINO = gang('pino', erschrickt + 30, PLATZ.pino, PLATZ.noa + 90, 2.5);
const hockt = f >= z.n1.start + 8;
const angst = f >= erschrickt && f < z.e3.ende;
const kam = kamFahrt(f, [
[0, 960, 560, 1.0],
[erschrickt, 900, 600, 1.12],
[z.e1.start, 820, 720, 1.6], // Pinos Stacheln
[z.p1.ende, 760, 700, 1.45],
[z.e3.start, 1040, 520, 1.0], // der ganze Schatten
[dauer, 1060, 520, 0.98],
]);
return (
<>
<Wandszene
f={f}
kam={kam}
laterne={hockt ? {inHand: false, licht: 1} : {inHand: true, licht: 1, schwung: Math.sin(f * 0.06) * 3}}
noa={{pose: [[0, 'noa_A_laterne'], [z.n1.start + 8, 'noa_B_hocken']]}}
pino={{
pose: [[0, 'pino_A6_blick-rechts'], [erschrickt, 'pino_B2_erschrocken'], [z.e3.ende, 'pino_A4_staunen']],
x: PINO.x(f),
gang: PINO,
drehung: angst ? Math.sin(f * 1.7) * 1.6 : 0,
}}
lulu={{pose: 'lulu_A4_staunen', hell: 0.55}}
/>
<Untertitel zeilen={S4.liste} />
<Blende dauer={dauer} rein={14} raus={6} />
</>
);
};

View file

@ -0,0 +1,47 @@
import React from 'react';
import {interpolate, useCurrentFrame} from 'remotion';
import {Blende, kamFahrt} from '../baukasten/helfer';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {PLATZ, Wandszene} from './wand';
// Szene 5: Ach so! Noa hebt die Laterne hoch, jetzt fällt das Licht auf Lulu. Lulu nickt – der Schatten
// nickt mit. Pinos Stacheln legen sich, alle lachen. Der Erzähler erklärt, wie ein Schatten entsteht.
export const S5 = plane('s5', [
{id: 'bild0', dauer: 1.6},
{id: 'l1', wer: 'LULU'},
{id: 'p1', wer: 'PINO'},
{id: 'lachen', dauer: 1.6},
{id: 'e1', wer: 'ERZ'},
{id: 'bild1', dauer: 1.0},
]);
export const Szene5: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S5;
const hell = interpolate(f, [8, 30], [0.55, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
// Lulu nickt zweimal (vor „Huch!“), der Schatten macht es mit
const nick = interpolate(f, [z.bild0.start + 18, z.bild0.ende + 12], [0, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
const nicken = Math.sin(nick * Math.PI * 4) * 3 * (nick > 0 && nick < 1 ? 1 : 0);
const kam = kamFahrt(f, [
[0, 1060, 520, 0.98],
[z.l1.start, 1180, 560, 1.15],
[z.p1.start, 1000, 560, 1.05],
[dauer, 1000, 540, 1.0],
]);
return (
<>
<Wandszene
f={f}
kam={kam}
laterne={{inHand: true, licht: 1, schwung: Math.sin(f * 0.07) * 4}}
noa={{pose: [[0, 'noa_B_hocken'], [6, 'noa_A_laterne']]}}
pino={{pose: [[0, 'pino_A4_staunen'], [z.p1.start, 'pino_A3_lachen']], x: PLATZ.noa + 90}}
lulu={{pose: [[0, 'lulu_A4_staunen'], [z.l1.start, 'lulu_A3_lachen']], hell, drehung: nicken}}
/>
<Untertitel zeilen={S5.liste} />
<Blende dauer={dauer} rein={6} raus={6} />
</>
);
};

View file

@ -0,0 +1,66 @@
import React from 'react';
import {interpolate, useCurrentFrame} from 'remotion';
import {gang} from '../baukasten/gang';
import {Blende, huepfer, kamFahrt} from '../baukasten/helfer';
import {BODEN} from '../baukasten/orte';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {PLATZ, Wandszene} from './wand';
// Szene 6: Jetzt bin ich der Größte! Noa stellt die Laterne ins Gras, Pino tippelt ganz nah heran.
// Sein Schatten wächst, bis er größer ist als Lulus. Lulu verbeugt sich vor dem großen Igel-Schatten.
export const S6 = plane('s6', [
{id: 'p1', wer: 'PINO'},
{id: 'n1', wer: 'NOA'},
{id: 'hinstellen', dauer: 1.6},
{id: 'e1', wer: 'ERZ', pause: 3},
{id: 'wachsen', dauer: 2.4},
{id: 'e2', wer: 'ERZ'},
{id: 'p2', wer: 'PINO'},
{id: 'verbeugen', dauer: 1.6},
{id: 'l1', wer: 'LULU'},
{id: 'p3', wer: 'PINO'},
{id: 'bild1', dauer: 1.6},
]);
const LATERNE = {x: 1010, y: PLATZ.laterneBoden.y};
export const Szene6: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S6;
const steht = f >= z.hinstellen.start + 8;
// Pino geht während der Frage schon los und steht beim Wachsen dicht an der Laterne
const PINO = gang('pino', z.e1.start + 20, PLATZ.noa + 90, LATERNE.x - 70, 2.5);
const wachsen = interpolate(f, [z.wachsen.start, z.wachsen.ende], [0, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
const kPino = steht ? 1.25 + wachsen * 6.2 : undefined;
const kam = kamFahrt(f, [
[0, 1000, 560, 1.1],
[z.hinstellen.start, 900, 640, 1.25],
[z.wachsen.start, 980, 520, 1.0],
[z.p2.start, 1000, 520, 1.0],
[z.verbeugen.start, 1120, 560, 1.12],
[dauer, 1040, 540, 1.02],
]);
return (
<>
<Wandszene
f={f}
kam={kam}
laterne={steht ? {inHand: false, licht: 1, x: LATERNE.x, y: LATERNE.y} : {inHand: true, licht: 1, schwung: Math.sin(f * 0.06) * 3}}
noa={{pose: [[0, 'noa_A_laterne'], [z.hinstellen.start + 8, 'noa_B_hocken'], [z.p2.start, 'noa_A3_lachen']]}}
pino={{
pose: [[0, 'pino_B1_traurig'], [z.n1.start, 'pino_A4_staunen'], [z.e2.start, 'pino_A4_staunen'], [z.p2.start, 'pino_A3_lachen']],
x: PINO.x(f),
gang: PINO,
k: kPino,
y: BODEN + huepfer(f, z.p3.start, 18, 12) + huepfer(f, z.p3.start + 16, 14, 11),
drehung: f >= z.p3.start ? Math.sin(f * 1.4) * 2 : 0, // Stacheln zittern vor Freude
}}
lulu={{pose: [[0, 'lulu_A3_lachen'], [z.verbeugen.start, 'lulu_A_hals-unten'], [z.p3.start, 'lulu_A3_lachen']]}}
/>
<Untertitel zeilen={S6.liste} />
<Blende dauer={dauer} rein={6} raus={6} />
</>
);
};

View file

@ -0,0 +1,60 @@
import React from 'react';
import {interpolate, useCurrentFrame} from 'remotion';
import {Blende, kamFahrt} from '../baukasten/helfer';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {HandSchatten} from './teile';
import {PLATZ, Wandszene} from './wand';
// Szene 7: Schattenspiel. Noa macht mit der Hand vor der Laterne einen Hasen, dann einen Igel.
// Die Kinder machen mit (Pausen). Lulu streckt den Hals lang, ihr Schatten reicht bis zum Dach.
export const S7 = plane('s7', [
{id: 'bild0', dauer: 1.2},
{id: 'n1', wer: 'NOA'},
{id: 'e1', wer: 'ERZ', pause: 3},
{id: 'e2', wer: 'ERZ'},
{id: 'hals', dauer: 2.2},
{id: 'igel', dauer: 1.2},
{id: 'p1', wer: 'PINO'},
{id: 'e3', wer: 'ERZ', pause: 3},
{id: 'e4', wer: 'ERZ'},
{id: 'bild1', dauer: 1.2},
]);
const LATERNE = {x: 1010, y: PLATZ.laterneBoden.y};
export const Szene7: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S7;
const hase = interpolate(f, [z.bild0.start + 6, z.bild0.ende, z.hals.start, z.hals.start + 10], [0, 1, 1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
const igel = interpolate(f, [z.igel.start, z.igel.start + 12, dauer - 20, dauer - 6], [0, 1, 1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
const strecken = interpolate(f, [z.hals.start, z.hals.start + 20, z.hals.ende - 10, z.hals.ende + 6], [1, 1.12, 1.12, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
const kam = kamFahrt(f, [
[0, 1000, 520, 1.0],
[z.n1.start, 1000, 470, 1.12],
[z.hals.start, 1100, 500, 0.98],
[z.igel.start, 1000, 480, 1.1],
[dauer, 1000, 500, 1.06],
]);
return (
<>
<Wandszene
f={f}
kam={kam}
laterne={{inHand: false, licht: 1, x: LATERNE.x, y: LATERNE.y}}
noa={{pose: [[0, 'noa_B_hocken'], [z.igel.start, 'noa_B_hocken']], x: PLATZ.noa + 140}}
pino={{pose: [[0, 'pino_A4_staunen'], [z.p1.start, 'pino_A3_lachen']], x: LATERNE.x - 230}}
lulu={{pose: 'lulu_A3_lachen', stauchY: strecken}}
wandExtra={
<>
<HandSchatten art="hase" x={1060} y={440} s={1.1} deckkraft={0.42 * hase} wackeln={Math.sin(f * 0.25)} />
<HandSchatten art="igel" x={1040} y={450} s={1.1} deckkraft={0.42 * igel} wackeln={Math.sin(f * 0.3)} />
</>
}
/>
<Untertitel zeilen={S7.liste} />
<Blende dauer={dauer} rein={6} raus={14} />
</>
);
};

View file

@ -0,0 +1,219 @@
import React from 'react';
import {Figur, NOA_HOEHE, type Posenfolge} from '../baukasten/figur';
import {FIGUREN} from '../baukasten/posen';
import {punkt} from '../baukasten/helfer';
import {BODEN, Ort} from '../baukasten/orte';
// Bildteile für Folge 8 „Noas Laterne“: Abendgarten mit Hauswand, die gebastelte Laterne,
// Schatten an der Wand und Hand-Schatten. Alles in Szenenkoordinaten (1920 × 1080).
// Hauswand rechts im Garten (Szene 3) bzw. als ganzer Hintergrund an der Wand (Szene 4–7)
export const WAND = {x0: 1060, x1: 1980, oben: 170, unten: BODEN - 30};
export const FENSTER = {x: 1180, y: 280, b: 230, h: 210};
// Abendgarten in der „blauen Stunde“: Nachtgarten aufgehellt, damit es freundlich und nicht unheimlich wirkt
export const GartenAbend: React.FC<{wandVoll?: boolean; papaWinkt?: number}> = ({wandVoll = false, papaWinkt = 0}) => {
const x0 = wandVoll ? -60 : WAND.x0;
return (
<>
<div style={{position: 'absolute', inset: 0, filter: 'brightness(1.45) saturate(0.9)'}}>
<Ort name="garten_nacht" />
</div>
<div style={{position: 'absolute', inset: 0, background: 'linear-gradient(180deg, rgba(110,130,200,0) 0%, rgba(150,150,200,0.12) 55%, rgba(240,180,140,0.22) 72%, rgba(60,70,110,0) 100%)'}} />
<svg viewBox="0 0 1920 1080" style={{position: 'absolute', inset: 0, width: 1920, height: 1080, overflow: 'visible'}}>
<defs>
<linearGradient id="putz" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="#b9b5c9" />
<stop offset="1" stopColor="#a7a2b6" />
</linearGradient>
<radialGradient id="fensterlicht">
<stop offset="0" stopColor="#ffe3a0" stopOpacity="0.55" />
<stop offset="1" stopColor="#ffe3a0" stopOpacity="0" />
</radialGradient>
</defs>
{/* Wand mit Dachkante und Sockel */}
<rect x={x0} y={WAND.oben} width={WAND.x1 - x0} height={WAND.unten - WAND.oben} fill="url(#putz)" />
<rect x={x0 - 20} y={WAND.oben - 34} width={WAND.x1 - x0 + 40} height={40} fill="#5a4a5e" />
<rect x={x0} y={WAND.unten - 34} width={WAND.x1 - x0} height={34} fill="#8f889c" />
{!wandVoll && <rect x={x0 - 8} y={WAND.oben} width={16} height={WAND.unten - WAND.oben} fill="#9690a6" />}
{/* erleuchtetes Fenster mit Papa als Umriss */}
<ellipse cx={FENSTER.x + FENSTER.b / 2} cy={FENSTER.y + FENSTER.h / 2} rx={FENSTER.b * 1.1} ry={FENSTER.h * 1.1} fill="url(#fensterlicht)" />
<rect x={FENSTER.x} y={FENSTER.y} width={FENSTER.b} height={FENSTER.h} rx={6} fill="#f6cf7a" stroke="#4b3f55" strokeWidth={12} />
<g opacity={0.85}>
<circle cx={FENSTER.x + 130} cy={FENSTER.y + 92} r={28} fill="#6b5560" />
<path d={`M ${FENSTER.x + 82} ${FENSTER.y + FENSTER.h} C ${FENSTER.x + 86} ${FENSTER.y + 140}, ${FENSTER.x + 174} ${FENSTER.y + 140}, ${FENSTER.x + 178} ${FENSTER.y + FENSTER.h} Z`} fill="#6b5560" />
{/* winkender Arm */}
<g transform={`rotate(${-20 + 25 * Math.sin(papaWinkt)} ${FENSTER.x + 168} ${FENSTER.y + 150})`}>
<rect x={FENSTER.x + 162} y={FENSTER.y + 70} width={16} height={84} rx={8} fill="#6b5560" />
<circle cx={FENSTER.x + 170} cy={FENSTER.y + 66} r={13} fill="#6b5560" />
</g>
</g>
<line x1={FENSTER.x + FENSTER.b / 2} y1={FENSTER.y} x2={FENSTER.x + FENSTER.b / 2} y2={FENSTER.y + FENSTER.h} stroke="#4b3f55" strokeWidth={8} />
<line x1={FENSTER.x} y1={FENSTER.y + FENSTER.h / 2} x2={FENSTER.x + FENSTER.b} y2={FENSTER.y + FENSTER.h / 2} stroke="#4b3f55" strokeWidth={8} />
<rect x={FENSTER.x - 18} y={FENSTER.y + FENSTER.h} width={FENSTER.b + 36} height={16} rx={4} fill="#6d6278" />
{/* Haken für die Laterne (Szene 8) */}
<path d={`M ${FENSTER.x + FENSTER.b + 40} ${FENSTER.y + 40} l 26 0 l 0 22`} stroke="#3f3646" strokeWidth={7} fill="none" strokeLinecap="round" />
</svg>
</>
);
};
export const HAKEN = {x: FENSTER.x + FENSTER.b + 66, y: FENSTER.y + 62};
export const WAND_VOLL = {...WAND, x0: -60};
// Die gebastelte Laterne: gelbes Papier, fünf Glitzersterne, ein Papier-Igel, innen ein Lämpchen.
// (x, y) = Mitte; sterne = wie viele schon aufgeklebt sind; igel 0–1; licht 0–1.
const STERNE: [number, number][] = [
[-34, -28],
[22, -40],
[40, 6],
[-6, 30],
[-44, 18],
];
const Stern: React.FC<{x: number; y: number; r: number; glanz: number}> = ({x, y, r, glanz}) => {
const pkt = new Array(10)
.fill(0)
.map((_, i) => {
const w = (Math.PI / 5) * i - Math.PI / 2;
const rr = i % 2 ? r * 0.45 : r;
return `${x + Math.cos(w) * rr},${y + Math.sin(w) * rr}`;
})
.join(' ');
return <polygon points={pkt} fill={glanz > 0.5 ? '#fff6c2' : '#f2c84b'} stroke="#c9952a" strokeWidth={1.5} />;
};
export const Laterne: React.FC<{x: number; y: number; s?: number; sterne?: number; igel?: number; licht?: number; schwung?: number; stab?: boolean}> = ({
x,
y,
s = 1,
sterne = 5,
igel = 1,
licht = 0,
schwung = 0,
stab = true,
}) => (
<div style={{position: 'absolute', left: x, top: y, width: 0, height: 0, transform: `scale(${s}) rotate(${schwung}deg)`, transformOrigin: '0 -70px'}}>
{licht > 0 && (
<div
style={{
position: 'absolute',
left: -260,
top: -260,
width: 520,
height: 520,
borderRadius: '50%',
background: `radial-gradient(closest-side, rgba(255,214,120,${0.55 * licht}), rgba(255,200,100,${0.18 * licht}) 55%, rgba(255,200,100,0))`,
}}
/>
)}
<svg width={200} height={260} viewBox="-100 -160 200 260" style={{position: 'absolute', left: -100, top: -160, overflow: 'visible'}}>
<defs>
<radialGradient id="papier" cx="0.45" cy="0.42" r="0.65">
<stop offset="0" stopColor={licht > 0 ? '#fffbe2' : '#fbe79a'} />
<stop offset="0.6" stopColor={licht > 0 ? '#ffd65c' : '#f2cf5c'} />
<stop offset="1" stopColor={licht > 0 ? '#f0a531' : '#d9b03e'} />
</radialGradient>
</defs>
{stab && <line x1={0} y1={-70} x2={70} y2={-150} stroke="#7a5233" strokeWidth={6} strokeLinecap="round" />}
<line x1={0} y1={-70} x2={0} y2={-56} stroke="#5a3d28" strokeWidth={2} />
<rect x={-24} y={-60} width={48} height={10} rx={4} fill="#b8433a" />
<ellipse cx={0} cy={0} rx={62} ry={56} fill="url(#papier)" />
{[-0.62, -0.2, 0.2, 0.62].map((k) => (
<path key={k} d={`M ${k * 62} -50 Q ${k * 100} 0 ${k * 62} 50`} stroke="#d39a2c" strokeWidth={1.6} fill="none" opacity={0.6} />
))}
{STERNE.slice(0, sterne).map(([sx, sy], i) => (
<Stern key={i} x={sx} y={sy} r={11} glanz={licht} />
))}
{igel > 0 && (
<g opacity={igel} transform="translate(8 2)">
<ellipse cx={0} cy={0} rx={17} ry={12} fill="#8a5a36" />
{[-14, -8, -2, 4, 10].map((dx) => (
<path key={dx} d={`M ${dx} -8 l 3 -8 l 3 8`} fill="#6e4426" />
))}
<ellipse cx={-15} cy={3} rx={7} ry={6} fill="#e9c9a0" />
<circle cx={-19} cy={3} r={1.8} fill="#2a1d14" />
</g>
)}
<rect x={-24} y={50} width={48} height={10} rx={4} fill="#b8433a" />
</svg>
</div>
);
// Weicher Schatten einer Figur an der Wand. Er steht hinter der Figur, ist um `k` vergrößert und
// liegt vom Licht weg verschoben; je näher die Figur am Licht, desto größer (k aus `schattenGroesse`).
// Weich, rund und bläulich-dunkel, ohne harten Rand (Drehbuch: groß, aber nicht unheimlich).
export const schattenGroesse = (abstand: number) => Math.min(3.2, Math.max(1.05, 1 + 260 / Math.max(60, abstand)));
export const WandSchatten: React.FC<{
pose: string | Posenfolge;
x: number; // Fußpunkt der Figur
licht: {x: number; y: number};
hoehe: number;
k: number;
wand?: {x0: number; x1: number; oben: number; unten: number};
deckkraft?: number;
drehung?: number;
stauchY?: number;
}> = ({pose, x, licht, hoehe, k, wand = WAND, deckkraft = 0.42, drehung = 0, stauchY = 1}) => (
<div style={{position: 'absolute', left: wand.x0, top: wand.oben, width: wand.x1 - wand.x0, height: wand.unten - wand.oben, overflow: 'hidden'}}>
<div style={{position: 'absolute', left: -wand.x0, top: -wand.oben}}>
<Figur
pose={pose}
x={licht.x + (x - licht.x) * (1 + (k - 1) * 0.55)}
y={wand.unten - 6}
hoehe={hoehe * k}
schatten={false}
drehung={drehung}
stauchY={stauchY}
deckkraft={deckkraft}
filter="brightness(0) blur(5px)"
/>
</div>
</div>
);
// Hand-Schatten an der Wand: Hase (zwei Ohren) und Igel (Faust mit gespreizten Fingern)
export const HandSchatten: React.FC<{art: 'hase' | 'igel'; x: number; y: number; s?: number; deckkraft?: number; wackeln?: number}> = ({art, x, y, s = 1, deckkraft = 0.45, wackeln = 0}) => (
<svg width={400} height={400} viewBox="-200 -200 400 400" style={{position: 'absolute', left: x - 200, top: y - 200, transform: `scale(${s})`, overflow: 'visible', opacity: deckkraft, filter: 'blur(4px)'}}>
{art === 'hase' ? (
<g fill="#1d1a2e">
<ellipse cx={0} cy={40} rx={95} ry={70} />
<ellipse cx={-40} cy={-60} rx={22} ry={80} transform={`rotate(${-12 + wackeln * 6} -40 0)`} />
<ellipse cx={30} cy={-62} rx={22} ry={82} transform={`rotate(${10 - wackeln * 6} 30 0)`} />
<ellipse cx={-80} cy={50} rx={40} ry={30} />
<rect x={-20} y={90} width={70} height={120} rx={20} />
</g>
) : (
<g fill="#1d1a2e">
<ellipse cx={0} cy={40} rx={100} ry={64} />
{[-70, -35, 0, 35, 70].map((dx, i) => (
<ellipse key={dx} cx={dx * 1.05} cy={-30} rx={16} ry={56} transform={`rotate(${dx * 0.35 + (i % 2 ? wackeln * 4 : -wackeln * 4)} ${dx} 20)`} />
))}
<ellipse cx={-105} cy={60} rx={34} ry={24} />
<rect x={-10} y={90} width={70} height={120} rx={20} />
</g>
)}
</svg>
);
// Wo die Laterne in der Pose noa_A_laterne hängt, und wie groß unsere Laterne sein muss, um die orange
// Laterne des Bildes ganz abzudecken (gemessen: 131 × 134 Bildpunkte, Mitte bei 73/423 im Bild, Bild bei 339/156)
export const laterneInNoasHand = (noaX: number, noaY = BODEN) => ({
...punkt('noa', noaX, noaY, 339 + 73.5, 156 + 423),
s: (1.2 * NOA_HOEHE) / FIGUREN.noa.refHoehe,
});
// Abend-Dämmerung über der ganzen Szene, aufgehellt um die Laterne (warmes Licht, Rest bläulich)
export const Abenddunkel: React.FC<{licht: {x: number; y: number}; staerke?: number; radius?: number}> = ({licht, staerke = 0.4, radius = 500}) => (
<div
style={{
position: 'absolute',
left: -400,
top: -400,
width: 2720,
height: 1880,
pointerEvents: 'none',
background: `radial-gradient(circle at ${licht.x + 400}px ${licht.y + 400}px, rgba(255,210,130,0.12) 0px, rgba(30,34,70,0) ${radius * 0.45}px, rgba(24,28,62,${staerke}) ${radius * 1.6}px)`,
}}
/>
);

View file

@ -0,0 +1,58 @@
{
"titel": "Noas Laterne",
"zeilen": {
"s2/l1": "Was machst du da, Noa?",
"s2/n1": "Rate mal! Es ist rund. Es hat einen Stab. Und innen drin leuchtet es.",
"s2/e1": "Rund, mit einem Stab, und innen leuchtet es. Was bastelt Noa wohl?",
"s2/n2": "Eine Laterne!",
"s2/p1": "Eine Laterne! Mit was drauf?",
"s2/n3": "Mit Sternen. Hilfst du mir zählen?",
"s2/z1": "Eins …",
"s2/z2": "zwei …",
"s2/z3": "drei …",
"s2/z4": "vier …",
"s2/z5": "fünf!",
"s2/p2": "Und ein Igel! Da muss ein Igel drauf!",
"s2/n4": "Natürlich muss da ein Igel drauf.",
"s2/l2": "Der Kleber ist noch nass. Ich puste!",
"s2/p3": "Das kitzelt!",
"s3/e1": "Es wird Abend in Noas Garten. Jetzt ist die richtige Zeit für eine Laterne.",
"s3/e2": "Noas Laterne hat ein kleines Lämpchen. Kein Feuer. So kann nichts passieren.",
"s3/p1": "Ooooh. Mein Igel leuchtet!",
"s3/v1": "Mein kleines Licht, so rund und warm,",
"s3/v2": "ich trag es durch den Garten.",
"s3/v3": "Der Mond schaut schon, die Sterne auch,",
"s3/v4": "die wollen auf uns warten.",
"s3/e3": "Kannst du mitsingen?",
"s3/w1": "Mein kleines Licht, so rund und warm,",
"s3/w2": "ich trag es durch den Garten.",
"s3/w3": "Der Mond schaut schon, die Sterne auch,",
"s3/w4": "die wollen auf uns warten.",
"s4/e1": "Schau mal, Pinos Stacheln gehen hoch. Wie fühlt sich Pino jetzt?",
"s4/p1": "Noa … da ist was Großes an der Wand. Ich hab ein bisschen Angst.",
"s4/n1": "Gut, dass du es mir sagst. Komm, wir schauen zusammen hin.",
"s4/e2": "Wenn man Angst hat, darf man das sagen. Dann ist man nicht allein.",
"s4/e3": "Der Schatten hat vier lange Beine. Und einen ganz langen Hals. Wem gehört wohl dieser Schatten?",
"s5/l1": "Huch! Das bin ja ich!",
"s5/p1": "Das ist ja Lulus Schatten! Der ist ja noch größer als Lulu!",
"s5/e1": "Die Laterne leuchtet auf Lulu. Hinter Lulu kommt das Licht nicht hin. Da wird es dunkel. Das ist ein Schatten.",
"s6/p1": "Mein Schatten ist so klein.",
"s6/n1": "Warte mal. Ich hab eine Idee.",
"s6/e1": "Pino geht ganz nah an die Laterne. Was meinst du: Wird sein Schatten jetzt größer oder kleiner?",
"s6/e2": "Größer! Je näher man am Licht ist, desto größer wird der Schatten.",
"s6/p2": "Jetzt bin ich der Größte von allen!",
"s6/l1": "Guten Abend, großer Pino!",
"s6/p3": "Einmal klein und einmal groß!",
"s7/n1": "Schau mal, ein Hase!",
"s7/e1": "Mach mal mit deiner Hand einen Hasen. Zwei Finger hoch für die Ohren.",
"s7/e2": "Hallo, Hase!",
"s7/p1": "Das bin ja ich!",
"s7/e3": "Und jetzt einen Igel. Mach eine Faust und streck die Finger hoch, wie Stacheln.",
"s7/e4": "Hallo, kleiner Igel!",
"s8/p1": "Jetzt ist es gar nicht mehr dunkel.",
"s8/n1": "Die Laterne passt auf dich auf. Gute Nacht, Pino.",
"s8/e1": "Und bei dir? Ist da auch ein kleines Licht, wenn du schlafen gehst?",
"s8/t1": "Tschüss! Bis bald!",
"s8/p2": "… bis bald …"
}
}

View file

@ -0,0 +1,58 @@
{
"titel": "El farolillo de Noa",
"zeilen": {
"s2/l1": "¿Qué haces, Noa?",
"s2/n1": "¡Adivina! Es redondo. Tiene un palito. Y por dentro brilla.",
"s2/e1": "Redondo, con un palito, y por dentro brilla. ¿Qué estará haciendo Noa?",
"s2/n2": "¡Un farolillo!",
"s2/p1": "¡Un farolillo! ¿Y qué lleva encima?",
"s2/n3": "Estrellas. ¿Me ayudas a contar?",
"s2/z1": "Una …",
"s2/z2": "dos …",
"s2/z3": "tres …",
"s2/z4": "cuatro …",
"s2/z5": "¡cinco!",
"s2/p2": "¡Y un erizo! ¡Tiene que llevar un erizo!",
"s2/n4": "Claro que tiene que llevar un erizo.",
"s2/l2": "El pegamento todavía está mojado. ¡Yo soplo!",
"s2/p3": "¡Me hace cosquillas!",
"s3/e1": "Se hace de noche en el jardín de Noa. Ahora es el momento para un farolillo.",
"s3/e2": "El farolillo de Noa tiene una lucecita. Nada de fuego. Así no puede pasar nada.",
"s3/p1": "¡Ooooh! ¡Mi erizo brilla!",
"s3/v1": "Mi farolillo, redondo y calentito,",
"s3/v2": "lo llevo por el jardín.",
"s3/v3": "La luna mira, y las estrellas,",
"s3/v4": "nos esperan hasta el fin.",
"s3/e3": "¿Cantas con nosotros?",
"s3/w1": "Mi farolillo, redondo y calentito,",
"s3/w2": "lo llevo por el jardín.",
"s3/w3": "La luna mira, y las estrellas,",
"s3/w4": "nos esperan hasta el fin.",
"s4/e1": "Mira, a Pino se le levantan las púas. ¿Cómo se siente Pino ahora?",
"s4/p1": "Noa… hay algo muy grande en la pared. Tengo un poquito de miedo.",
"s4/n1": "Qué bien que me lo dices. Ven, vamos a mirar juntos.",
"s4/e2": "Si tienes miedo, lo puedes decir. Así no estás solo.",
"s4/e3": "La sombra tiene cuatro patas largas. Y un cuello larguísimo. ¿De quién será esta sombra?",
"s5/l1": "¡Huy! ¡Pero si soy yo!",
"s5/p1": "¡Es la sombra de Lulu! ¡Y es todavía más grande que Lulu!",
"s5/e1": "El farolillo ilumina a Lulu. Detrás de Lulu, la luz no llega. Allí se pone oscuro. Eso es una sombra.",
"s6/p1": "Mi sombra es muy pequeña.",
"s6/n1": "Espera. Tengo una idea.",
"s6/e1": "Pino se acerca mucho al farolillo. ¿Qué crees: su sombra se hará más grande o más pequeña?",
"s6/e2": "¡Más grande! Cuanto más cerca de la luz, más grande es la sombra.",
"s6/p2": "¡Ahora soy el más grande de todos!",
"s6/l1": "¡Buenas noches, gran Pino!",
"s6/p3": "¡Una vez pequeño y una vez grande!",
"s7/n1": "¡Mira, un conejo!",
"s7/e1": "Haz un conejo con tu mano. Dos dedos arriba para las orejas.",
"s7/e2": "¡Hola, conejo!",
"s7/p1": "¡Pero si soy yo!",
"s7/e3": "Y ahora un erizo. Cierra la mano y estira los dedos hacia arriba, como púas.",
"s7/e4": "¡Hola, erizo pequeño!",
"s8/p1": "Ahora ya no está nada oscuro.",
"s8/n1": "El farolillo te cuida. Buenas noches, Pino.",
"s8/e1": "¿Y tú? ¿Tienes también una lucecita cuando te vas a dormir?",
"s8/t1": "¡Adiós! ¡Hasta pronto!",
"s8/p2": "…hasta pronto…"
}
}

View file

@ -0,0 +1,58 @@
{
"titel": "La lanterne de Noa",
"zeilen": {
"s2/l1": "Qu’est-ce que tu fais, Noa ?",
"s2/n1": "Devine ! C’est rond. Il y a un bâton. Et dedans, ça brille.",
"s2/e1": "Rond, avec un bâton, et dedans, ça brille. Qu’est-ce que Noa fabrique ?",
"s2/n2": "Une lanterne !",
"s2/p1": "Une lanterne ! Avec quoi dessus ?",
"s2/n3": "Avec des étoiles. Tu m’aides à compter ?",
"s2/z1": "Une …",
"s2/z2": "deux …",
"s2/z3": "trois …",
"s2/z4": "quatre …",
"s2/z5": "cinq !",
"s2/p2": "Et un hérisson ! Il faut un hérisson dessus !",
"s2/n4": "Bien sûr qu’il faut un hérisson dessus.",
"s2/l2": "La colle est encore mouillée. Je souffle !",
"s2/p3": "Ça chatouille !",
"s3/e1": "Le soir tombe dans le jardin de Noa. C’est le bon moment pour une lanterne.",
"s3/e2": "La lanterne de Noa a une petite lampe. Pas de feu. Comme ça, il ne peut rien arriver.",
"s3/p1": "Ooooh. Mon hérisson brille !",
"s3/v1": "Ma petite lumière, ronde et douce,",
"s3/v2": "je la promène au jardin.",
"s3/v3": "La lune regarde, les étoiles aussi,",
"s3/v4": "elles nous attendent en chemin.",
"s3/e3": "Tu chantes avec nous ?",
"s3/w1": "Ma petite lumière, ronde et douce,",
"s3/w2": "je la promène au jardin.",
"s3/w3": "La lune regarde, les étoiles aussi,",
"s3/w4": "elles nous attendent en chemin.",
"s4/e1": "Regarde, les piquants de Pino se dressent. Comment se sent Pino maintenant ?",
"s4/p1": "Noa… il y a quelque chose de grand sur le mur. J’ai un petit peu peur.",
"s4/n1": "C’est bien que tu me le dises. Viens, on regarde ensemble.",
"s4/e2": "Quand on a peur, on a le droit de le dire. Comme ça, on n’est pas tout seul.",
"s4/e3": "L’ombre a quatre longues pattes. Et un très long cou. À qui est cette ombre ?",
"s5/l1": "Oh ! Mais c’est moi !",
"s5/p1": "C’est l’ombre de Lulu ! Elle est encore plus grande que Lulu !",
"s5/e1": "La lanterne éclaire Lulu. Derrière Lulu, la lumière n’arrive pas. Là, il fait sombre. C’est une ombre.",
"s6/p1": "Mon ombre est toute petite.",
"s6/n1": "Attends. J’ai une idée.",
"s6/e1": "Pino s’approche tout près de la lanterne. À ton avis : son ombre va devenir plus grande ou plus petite ?",
"s6/e2": "Plus grande ! Plus on est près de la lumière, plus l’ombre est grande.",
"s6/p2": "Maintenant, c’est moi le plus grand de tous !",
"s6/l1": "Bonsoir, grand Pino !",
"s6/p3": "Une fois petit, une fois grand !",
"s7/n1": "Regarde, un lapin !",
"s7/e1": "Fais un lapin avec ta main. Deux doigts en l’air pour les oreilles.",
"s7/e2": "Bonjour, petit lapin !",
"s7/p1": "Mais c’est moi !",
"s7/e3": "Et maintenant un hérisson. Ferme la main et lève les doigts, comme des piquants.",
"s7/e4": "Bonjour, petit hérisson !",
"s8/p1": "Maintenant, il ne fait plus du tout noir.",
"s8/n1": "La lanterne veille sur toi. Bonne nuit, Pino.",
"s8/e1": "Et toi ? Est-ce qu’il y a aussi une petite lumière quand tu vas dormir ?",
"s8/t1": "Au revoir ! À bientôt !",
"s8/p2": "…à bientôt…"
}
}

View file

@ -0,0 +1,61 @@
import React from 'react';
import {ClipFigur, Figur, Kamera, type Posenfolge} from '../baukasten/figur';
import type {Gang} from '../baukasten/gang';
import {Vignette} from '../baukasten/helfer';
import {BODEN} from '../baukasten/orte';
import {Abenddunkel, GartenAbend, Laterne, WAND_VOLL, WandSchatten, laterneInNoasHand} from './teile';
// Gemeinsame Bühne für Szene 4–7: die drei an der Hauswand, die Laterne als einzige Lichtquelle.
// Jede Figur wirft ihren Schatten an die Wand; er nimmt dieselbe Pose, Drehung und Stauchung an
// (nickt Lulu, nickt ihr Schatten mit). Feste Plätze, damit die Szenen zusammenpassen.
export const PLATZ = {noa: 700, pino: 880, lulu: 1330, laterneBoden: {x: 560, y: BODEN - 56}};
export type Buehne = {
kam: {x: number; y: number; zoom: number};
laterne: {inHand: boolean; x?: number; y?: number; licht: number; schwung?: number};
noa: {pose: string | Posenfolge; x?: number};
pino: {pose: string | Posenfolge; x: number; gang?: Gang; drehung?: number; y?: number; k?: number};
lulu: {pose: string | Posenfolge; x?: number; hell?: number; drehung?: number; stauchY?: number; k?: number};
wandExtra?: React.ReactNode; // z. B. Hand-Schatten
f: number;
};
export const Wandszene: React.FC<Buehne> = ({kam, laterne, noa, pino, lulu, wandExtra, f}) => {
const noaX = noa.x ?? PLATZ.noa;
const luluX = lulu.x ?? PLATZ.lulu;
const hand = laterneInNoasHand(noaX);
const lat = laterne.inHand ? {x: hand.x, y: hand.y} : {x: laterne.x ?? PLATZ.laterneBoden.x, y: laterne.y ?? PLATZ.laterneBoden.y};
const s = hand.s;
const kLulu = lulu.k ?? 1 + 220 / Math.max(120, Math.abs(luluX - lat.x));
const kPino = pino.k ?? 1 + 220 / Math.max(80, Math.abs(pino.x - lat.x));
const kNoa = 1 + 220 / Math.max(120, Math.abs(noaX - lat.x));
const hell = lulu.hell ?? 1;
const pinoGeht = pino.gang?.geht(f);
return (
<>
<Kamera {...kam}>
<GartenAbend wandVoll />
{/* Schatten an der Wand (hinter den Figuren) */}
<WandSchatten pose={lulu.pose} x={luluX} licht={lat} hoehe={340 * 2.05} k={kLulu} wand={WAND_VOLL} drehung={lulu.drehung} stauchY={lulu.stauchY} />
<WandSchatten pose={noa.pose} x={noaX} licht={lat} hoehe={340} k={kNoa} wand={WAND_VOLL} deckkraft={0.3} />
<WandSchatten pose={pinoGeht ? 'pino_A6_blick-rechts' : pino.pose} x={pino.x} licht={lat} hoehe={340 * 0.4} k={kPino} wand={WAND_VOLL} drehung={pino.drehung} />
{wandExtra}
<Figur pose={lulu.pose} x={luluX} y={BODEN - 6} drehung={lulu.drehung} stauchY={lulu.stauchY} filter={hell < 1 ? `brightness(${hell})` : undefined} />
<Figur pose={noa.pose} x={noaX} y={BODEN} />
{laterne.inHand ? (
<Laterne x={lat.x} y={lat.y} s={s} licht={laterne.licht} schwung={laterne.schwung ?? 0} stab={false} />
) : (
<Laterne x={lat.x} y={lat.y} s={s} licht={laterne.licht} stab={false} />
)}
{pinoGeht && pino.gang ? (
<ClipFigur name="pino_C1_gehen-schleife" start={pino.gang.clipStart} tempo={pino.gang.tempo} schleife spiegeln={pino.gang.spiegeln} x={pino.gang.x(f)} y={BODEN} />
) : (
<Figur pose={pino.pose} x={pino.x} y={pino.y ?? BODEN} drehung={pino.drehung} />
)}
<Abenddunkel licht={lat} staerke={0.42} radius={640} />
</Kamera>
<Vignette />
</>
);
};

View file

@ -329,7 +329,7 @@ const Garten: React.FC = () => {
))} ))}
<KorbVorne /> <KorbVorne />
<Figur pose={NOA} x={NOA_X} y={BODEN + (singt ? wipp(0.3, 10 * ruhig) : 0)} drehung={singt ? schwank(0, 3 * ruhig) : 0} blende={3} /> <Figur pose={NOA} x={NOA_X} y={BODEN + (singt ? wipp(0.3, 10 * ruhig) : 0)} drehung={singt ? schwank(0, 3 * ruhig) : 0} blende={3} />
<Figur pose={PINO} x={PINO_X} y={BODEN + (singt ? wipp(0, 12 * ruhig) : 0) - pinoHuepf} drehung={singt ? schwank(1, 4 * ruhig) : 0} stauchY={1 - 0.03 * schlag} blende={6} /> <Figur pose={PINO} x={PINO_X} y={BODEN + (singt ? wipp(0, 12 * ruhig) : 0) + pinoHuepf} drehung={singt ? schwank(1, 4 * ruhig) : 0} stauchY={1 - 0.03 * schlag} blende={6} />
<Ruf text={LIED.mampf} x={kopf.x - 140} y={kopf.y - 40} start={MAMPF} dauer={30} groesse={80} farbe="#c8362c" /> <Ruf text={LIED.mampf} x={kopf.x - 140} y={kopf.y - 40} start={MAMPF} dauer={30} groesse={80} farbe="#c8362c" />
<Funkeln x={kopf.x - 60} y={kopf.y + 20} start={s(T.antwort + 0.6)} dauer={50} s={1.2} /> <Funkeln x={kopf.x - 60} y={kopf.y + 20} start={s(T.antwort + 0.6)} dauer={50} s={1.2} />
</Kamera> </Kamera>

View file

@ -0,0 +1,25 @@
import React from 'react';
import {AbsoluteFill, useCurrentFrame} from 'remotion';
import {ClipFigur, Figur} from '../baukasten/figur';
import {gang} from '../baukasten/gang';
// Prüfstand für baukasten/gang.ts: Noa und Pino gehen nach rechts, die Füße dürfen nicht rutschen.
const NOA = gang('noa', 10, 300, 1500);
const PINO = gang('pino', 10, 300, 900, 2.5);
export const GangTest: React.FC = () => {
const f = useCurrentFrame();
const figur = (g: typeof NOA, y: number) =>
g.geht(f) ? (
<ClipFigur name={`${g.figur}_C1_gehen-schleife`} start={g.clipStart} tempo={g.tempo} schleife spiegeln={g.spiegeln} x={g.x(f)} y={y} />
) : (
<Figur pose={`${g.figur}_A3_lachen`} x={g.x(f)} y={y} />
);
return (
<AbsoluteFill style={{background: '#ffffff'}}>
{figur(NOA, 500)}
{figur(PINO, 950)}
</AbsoluteFill>
);
};
export const GANGTEST_DAUER = Math.ceil(Math.max(NOA.ende, PINO.ende)) + 20;