Compare commits

..

No commits in common. "083e878b97725e2c7955d1b1d7f18f46eac070e1" and "60acaa99210648d5b04aa4f488d0710f42fa7d70" have entirely different histories.

30 changed files with 40 additions and 1558 deletions

View file

@ -183,38 +183,30 @@ Pino pflückt sie, ganz genau!
Für Spanisch und Französisch übersetze ich das Lied nicht Wort für Wort. Ich dichte es neu, damit es sich in jeder Sprache reimt. Die Melodie bleibt gleich. Musik: eigene Melodie oder mit einem bezahlten Suno-Plan, sonst ist die kommerzielle Nutzung nicht erlaubt. Für Spanisch und Französisch übersetze ich das Lied nicht Wort für Wort. Ich dichte es neu, damit es sich in jeder Sprache reimt. Die Melodie bleibt gleich. Musik: eigene Melodie oder mit einem bezahlten Suno-Plan, sonst ist die kommerzielle Nutzung nicht erlaubt.
## Umsetzung in Remotion ## Bildliste und Credits
Stand 02.11.2026: Folge 1 entsteht neu wie Folge 2 in Remotion (`remotion/src/folge01/`, Baukasten), damit alle Folgen gleich aussehen. Die frühere Higgsfield-Fassung (54 Clips) liegt weiter in `Pino-Kanal\clips\folge01\`, wird aber nicht mehr verwendet. Sprechtexte stehen in `remotion/src/folge01/texte/de.json`, `es.json`, `fr.json`. Die Folge ist durchgehend animiert und besteht aus 54 Clips à 5 Sekunden. Dafür brauchen wir 16 Startbilder. Aus jedem Startbild erzeugt Kling 3.0 bei Higgsfield 3–6 Clips mit ruhiger, gleichmäßiger Bewegung.
**Schon vorhanden** | Nr. | Startbild | Szene | Clips | Wiederverwendbar |
| --- | --- | --- | --- | --- |
| 1 | Aufgeschlagenes Bilderbuch mit Serientitel | 1 | 3 | ja, jede Folge |
| 2 | Noas Garten im Herbst, Totale mit Apfelbaum | 2 | 2 | ja, Hintergrund |
| 3 | Noa springt in den Laubhaufen | 2 | 2 | |
| 4 | Pino taucht aus dem Laub auf, Stacheln hoch | 2 | 3 | |
| 5 | Noa hockt sich hin, Pinos Stacheln gehen runter | 2 | 3 | |
| 6 | Pino schaut zum Apfel hoch | 3 | 3 | |
| 7 | Pino hüpft auf Zehenspitzen | 3 | 3 | |
| 8 | Noa streckt den Arm zum untersten Ast | 3 | 3 | |
| 9 | Zwei gefleckte Beine neben dem Baum | 4 | 3 | |
| 10 | Lulus Kopf kommt von oben ins Bild | 4 | 4 | ja, Running Gag |
| 11 | Lulu reicht Pino den Apfel | 4 | 3 | |
| 12 | Kleine blaue Blume am Zaun | 5 | 4 | |
| 13 | Lulu beugt sich, wackelt, Schal verheddert | 5 | 6 | |
| 14 | Noa hält die Blume hoch, Lulu riecht daran | 6 | 4 | |
| 15 | Alle drei nebeneinander, wippen zum Lied | 6 | 4 | ja, Lied |
| 16 | Abend unter dem Apfelbaum, Pino gähnt | 7 | 4 | |
| | **Gesamt** | | **54** | |
| Was | Wo | Szene | **Rechnung:** 54 Clips × 1,5–2 Versuche × 7–10 Credits = ca. 570–1.080 Credits bei Higgsfield. Dazu kommen die 16 Startbilder mit wenigen Credits. Die Figurenblätter entstehen einmalig vorher.
| --- | --- | --- |
| Bilderbuch-Intro und -Outro | `baukasten/buch.tsx` | 1, 7 |
| Gehen (alle drei), Noa hockt, Noa winkt | `*_C1_gehen-schleife`, `noa_C5`/`NoaHocke`, `noa_A_arm-hoch-winken` | 2–6 |
| Pino als Stachelkugel (Angst), staunend, lachend, schaut hoch, auf Hinterbeinen | `pino_A_kugel`, `pino_A4`, `pino_A3`, `pino_B_blick-nach-oben`, `pino_A_hinterbeine` | 2–4 |
| Noa erschrocken, staunend, traurig, zeigt, Arme hoch, sitzt | `noa_B2`, `noa_A4`, `noa_B1`, `noa_B_zeigen`, `noa_A_arme-hoch`, `noa_B_sitzt` | 2–7 |
| Lulu frisst Apfel, Hals runter, traurig, liegt | `lulu_A_apfel`, `lulu_C9_hals-runter`/`lulu_A_hals-unten`, `lulu_B1`, `lulu_B_liegt` | 4–7 |
| Lulus Kopf kommt von oben | Kamerafahrt wie Folge 2, Szene 3 | 4 |
| Kichern, Hüpfen, Gähnen, Erschrecken | Bewegungen aus Folge 2 (`helfer.tsx`) | 2–7 |
| Blätter, Blätterregen, große Rufe | `baukasten/natur.tsx` | 2, 3 |
| Lied-Refrain mit wippenden Figuren | wie `folge02/lied.tsx`, hier mit Strophe | 6 |
**Neu zu bauen (Code, kostet nichts)** **Ton:** etwa 3 Minuten Sprechtext, dazu Lied und Pausen.
| Was | Szene | Hinweis |
| --- | --- | --- |
| Herbstgarten am Morgen: goldenes Licht, Apfelbaum mit buntem Laub, ein roter Apfel ganz oben, Zaun | 2–6 | Variante von `GartenSVG`, ohne Holzstapel (der kommt erst in Folge 2) |
| Derselbe Garten im Abendlicht | 7 | warmes Orange, längere Schatten |
| Laubhaufen als eigenes Teil, kann wackeln und aufspritzen | 2 | vor dem Hintergrund, damit Pino darin sitzen kann |
| Roter Apfel und kleine blaue Blume als Teile | 3–7 | wandern zwischen den Figuren (Lulu → Pino, Pino → Noa) |
**Vereinfacht gegenüber dem Drehbuch**
- Szene 5: Lulus Schal wickelt sich nicht um die Beine. Lulu wackelt beim Herunterbeugen, Pino und Noa kichern wie in Folge 2.
- Szene 6: Pino „pflückt“ die Blume, indem sie an seiner Pfote erscheint; Noa hält sie mit `noa_A_arme-hoch` hoch.
Keine neuen Posen nötig. Schöner wären später: Pino hält etwas in den Pfoten, Lulu riecht mit geschlossenen Augen.
**Ton:** etwa 3 Minuten Sprechtext, dazu das Lied (Refrain, Strophe, Refrain), über `werkzeuge/stimmen.mts` (ElevenLabs).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

View file

@ -6,9 +6,7 @@ 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';
import {NestStill, ORTE, OrtStill} from './baukasten/orte'; import {NestStill, ORTE, OrtStill} from './baukasten/orte';
import {F01} from './folge01/Folge01';
import {F02} from './folge02/Folge02'; import {F02} from './folge02/Folge02';
import {Banner, BannerRahmen, Profilbild} from './kanal/KanalBilder';
const BILD = {fps: 30, width: 1920, height: 1080} as const; const BILD = {fps: 30, width: 1920, height: 1080} as const;
@ -19,16 +17,12 @@ export const Root: React.FC = () => (
<Composition id="HintergrundAbend" component={HintergrundAbend} durationInFrames={1} {...BILD} /> <Composition id="HintergrundAbend" component={HintergrundAbend} durationInFrames={1} {...BILD} />
<Composition id="VordergrundAbend" component={VordergrundAbend} durationInFrames={1} {...BILD} /> <Composition id="VordergrundAbend" component={VordergrundAbend} durationInFrames={1} {...BILD} />
<Composition id="PapierStruktur" component={PapierStruktur} durationInFrames={1} {...BILD} /> <Composition id="PapierStruktur" component={PapierStruktur} durationInFrames={1} {...BILD} />
<FolgenKompositionen folge={F01} />
<FolgenKompositionen folge={F02} /> <FolgenKompositionen folge={F02} />
<Composition id="Kanal-Profilbild" component={Profilbild} durationInFrames={1} fps={30} width={800} height={800} />
<Composition id="Kanal-Banner" component={Banner} durationInFrames={1} fps={30} width={2560} height={1440} />
<Composition id="Kanal-Banner-Rahmen" component={BannerRahmen} durationInFrames={1} fps={30} width={2560} height={1440} />
{(['hinten', 'vorne'] as const).map((seite) => ( {(['hinten', 'vorne'] as const).map((seite) => (
<Composition key={seite} id={`F2Nest-${seite}`} component={NestStill} defaultProps={{seite}} durationInFrames={1} fps={30} width={NEST_BILD.b} height={NEST_BILD.h} /> <Composition key={seite} id={`F2Nest-${seite}`} component={NestStill} defaultProps={{seite}} durationInFrames={1} fps={30} width={NEST_BILD.b} height={NEST_BILD.h} />
))} ))}
{Object.keys(ORTE).map((name) => ( {Object.keys(ORTE).map((name) => (
<Composition key={name} id={`F2Ort-${name.replace(/_/g, '-')}`} component={OrtStill} defaultProps={{name}} durationInFrames={1} {...BILD} /> <Composition key={name} id={`F2Ort-${name.replace('_', '-')}`} component={OrtStill} defaultProps={{name}} durationInFrames={1} {...BILD} />
))} ))}
</> </>
); );

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import {Freeze, Img, OffthreadVideo, getStaticFiles, staticFile, useCurrentFrame, useVideoConfig} from 'remotion'; import {Freeze, Img, OffthreadVideo, staticFile, useCurrentFrame, useVideoConfig} from 'remotion';
import {CLIPS, FIGUREN, POSEN} from './posen'; import {CLIPS, FIGUREN, POSEN} from './posen';
// Figuren aus der Bibliothek (freigestellte Standbilder und Clips). // Figuren aus der Bibliothek (freigestellte Standbilder und Clips).
@ -107,29 +107,13 @@ type ClipProps = Omit<Lage, 'children' | 'figur' | 'hoehe'> & {
ersatz?: string; // Standbild, falls der Clip fehlt ersatz?: string; // Standbild, falls der Clip fehlt
}; };
// Clips liegen nicht im Git; fehlt die Datei auf diesem Rechner, nimmt die
// Figur Standbilder (beim Gehen die vier Geh-Bilder im Wechsel).
const VORHANDEN = new Set(getStaticFiles().map((d) => d.name));
export const ClipFigur: React.FC<ClipProps> = ({name, start, hoehe, tempo = 1, schleife = false, bis, rueckwaerts = false, ersatz, ...lage}) => { export const ClipFigur: React.FC<ClipProps> = ({name, start, hoehe, tempo = 1, schleife = false, bis, rueckwaerts = false, ersatz, ...lage}) => {
const frame = useCurrentFrame(); const frame = useCurrentFrame();
const {fps} = useVideoConfig(); const {fps} = useVideoConfig();
const c = CLIPS[name]; const c = CLIPS[name];
if (!c || !VORHANDEN.has(c.datei)) { if (!c) {
// Clip noch nicht freigestellt: Standbild der Figur als Ersatz // Clip noch nicht freigestellt: Standbild der Figur als Ersatz
const figurName = name.split('_')[0] as FigurName; const figurName = name.split('_')[0] as FigurName;
if (name.endsWith('_C1_gehen-schleife')) {
// vier Geh-Bilder, jeweils kurz ineinander übergeblendet
const schritt = fps / (6 * tempo);
const t = Math.max(0, frame - start);
const k = Math.floor(t / schritt);
const bild = (n: number) => `${figurName}_A7-${1 + (((n % 4) + 4) % 4)}_gehen`;
const folge: Posenfolge = [
[(k - 1) * schritt, bild(k - 1)],
[k * schritt, bild(k)],
];
return <Figur pose={folge} frame={t} hoehe={hoehe} blende={Math.max(1, Math.min(4, schritt / 2))} {...lage} />;
}
return <Figur pose={ersatz ?? ({pino: 'pino_ref_sitzend', noa: 'noa_ref_stehend', lulu: 'lulu_A6_blick-rechts'} as const)[figurName]} hoehe={hoehe} {...lage} />; return <Figur pose={ersatz ?? ({pino: 'pino_ref_sitzend', noa: 'noa_ref_stehend', lulu: 'lulu_A6_blick-rechts'} as const)[figurName]} hoehe={hoehe} {...lage} />;
} }
const figur = c.figur as FigurName; const figur = c.figur as FigurName;

View file

@ -71,23 +71,3 @@ export const hopfStauch = (frame: number, start: number, dauer = 12) => {
if (frame >= start && frame < start + dauer) return 1.04; if (frame >= start && frame < start + dauer) return 1.04;
return 1; return 1;
}; };
// Lulus Maul im Clip lulu_C9_hals-runter (gemessen an der Nase, Leinwand-Koordinaten),
// je [Clip-Bild (24 fps), cx, cy]. Für Dinge, die Lulu beim Halssenken im Maul trägt.
const C9 = {x: 294.7, y: 24.6, s: 709.43 / 662};
const C9_NASE: [number, number, number][] = [
[0, 276, 143], [17, 276, 143], [21, 260, 151], [25, 246, 158], [29, 231, 167], [33, 215, 179],
[37, 196, 197], [41, 180, 217], [45, 165, 245], [49, 151, 277], [53, 143, 301], [57, 134, 341],
[61, 130, 371], [65, 128, 403], [69, 126, 435], [73, 127, 461], [77, 129, 489], [81, 135, 520],
[85, 142, 542], [89, 147, 563], [93, 155, 577], [97, 166, 587], [101, 176, 593], [105, 181, 597], [120, 179, 596],
];
export const luluMaulC9 = (clipBild: number) => {
const b = Math.min(120, Math.max(0, clipBild));
let i = 0;
while (i < C9_NASE.length - 2 && C9_NASE[i + 1][0] < b) i++;
const [b0, x0, y0] = C9_NASE[i];
const [b1, x1, y1] = C9_NASE[i + 1];
const t = b1 === b0 ? 0 : (b - b0) / (b1 - b0);
// Maul knapp über der Unterkante der Nase
return {cx: C9.x + (x0 + (x1 - x0) * t) * C9.s, cy: C9.y + (y0 + (y1 - y0) * t - 6) * C9.s};
};

View file

@ -285,69 +285,3 @@ export const Ruf: React.FC<{text: string; x: number; y: number; start: number; d
</div> </div>
); );
}; };
// Folge 1: roter Apfel (Mitte bei x, y; ca. 56 px breit bei s = 1)
export const Apfel: React.FC<{x: number; y: number; s?: number; r?: number}> = ({x, y, s = 1, r = 0}) => (
<svg width={80} height={90} viewBox="-40 -48 80 90" style={{position: 'absolute', left: x - 40, top: y - 48, transform: `rotate(${r}deg) scale(${s})`, overflow: 'visible'}}>
<path d="M 0 -18 C -8 -26, -30 -24, -28 0 C -27 22, -12 32, 0 26 C 12 32, 27 22, 28 0 C 30 -24, 8 -26, 0 -18 Z" fill="#c8362c" />
<path d="M -16 -10 C -20 -2, -18 8, -12 14" stroke="#e8786a" strokeWidth={5} strokeLinecap="round" fill="none" opacity={0.7} />
<path d="M 0 -18 C 1 -26, 3 -32, 6 -36" stroke="#5a3a24" strokeWidth={4} strokeLinecap="round" fill="none" />
<path d="M 5 -30 C 14 -40, 26 -36, 28 -30 C 20 -26, 12 -26, 5 -30 Z" fill="#6f9a3e" />
</svg>
);
// Folge 1: kleine blaue Blume; (x, y) = Fuß des Stiels
export const Blume: React.FC<{x: number; y: number; s?: number; r?: number}> = ({x, y, s = 1, r = 0}) => (
<svg width={80} height={120} viewBox="-40 -110 80 120" style={{position: 'absolute', left: x - 40, top: y - 110, transform: `rotate(${r}deg) scale(${s})`, transformOrigin: '50% 92%', overflow: 'visible'}}>
<path d="M 0 0 C -4 -30, 4 -55, 0 -78" stroke="#5f8a4a" strokeWidth={5} fill="none" strokeLinecap="round" />
<path d="M 0 -30 C -18 -36, -26 -26, -24 -20 C -14 -18, -6 -22, 0 -30 Z" fill="#6f9a3e" />
{[0, 72, 144, 216, 288].map((w) => (
<ellipse key={w} cx={0} cy={-96} rx={10} ry={15} fill="#5b7fd1" transform={`rotate(${w} 0 -82)`} />
))}
<circle cx={0} cy={-82} r={7} fill="#f2cc4a" />
</svg>
);
// Ahornblatt (Spitze oben, ca. 90 px hoch bei s = 1) und schmales Buchenblatt, für den Laubhaufen
const AHORN = 'M0,40 L4,18 L22,27 L18,12 L40,5 L30,-2 L37,-21 L18,-14 L16,-31 L6,-22 L0,-46 L-6,-22 L-16,-31 L-18,-14 L-37,-21 L-30,-2 L-40,5 L-18,12 L-22,27 L-4,18 Z';
const AHORN_ADERN = 'M0,52 L0,-38 M0,10 L30,-14 M0,10 L-30,-14 M0,18 L18,24 M0,18 L-18,24';
const BUCHE = 'M0,-44 C 22,-30 24,0 0,30 C -24,0 -22,-30 0,-44 Z';
const BUCHE_ADERN = 'M0,40 L0,-36 M0,-14 L12,-24 M0,-2 L14,-12 M0,-8 L-12,-18 M0,4 L-13,-6';
const HAUFEN_HELL = ['#e07a2e', '#d4522a', '#e9a83a', '#c2412a', '#f0c048', '#d98a3a', '#b8662a'];
const HAUFEN_DUNKEL = ['#9e4a26', '#8a3a22', '#a8682e', '#7a3a24'];
// Folge 1: Laubhaufen aus großen Herbstblättern (Fuß bei x, y; ca. 400 × 190 px bei s = 1).
// Reicht unten 45 px unter den Fußpunkt, damit Füße darin verschwinden.
// `wackeln` 0 … 1 lässt ihn zittern, `flach` 0 … 1 drückt ihn zusammen (Noa springt hinein).
export const Laubhaufen: React.FC<{x: number; y: number; s?: number; wackeln?: number; flach?: number}> = ({x, y, s = 1, wackeln = 0, flach = 0}) => {
const frame = useCurrentFrame();
const zitter = wackeln * Math.sin(frame * 1.3) * 3;
// hinten/oben zuerst zeichnen, vorne/unten zuletzt
const blaetter = new Array(120).fill(0).map((_, i) => {
const r = (k: string) => random(`lh${k}${i}`);
const u = r('u') * 2.2 - 1.1;
const kuppe = Math.pow(Math.max(0, 1 - u * u), 0.6) * 150;
const ly = 40 - r('y') * (kuppe + 40);
return {i, u, lx: u * 180, ly, ahorn: r('a') < 0.62, s: 0.85 + r('s') * 0.65, w: r('w') * 360, dunkel: ly < -kuppe * 0.55 ? false : r('d') < 0.3, farbe: r('f')};
});
blaetter.sort((a, b) => a.ly - b.ly);
return (
<svg
width={460}
height={260}
viewBox="-230 -205 460 260"
style={{position: 'absolute', left: x - 230, top: y - 205, overflow: 'visible', transform: `scale(${s * (1 + flach * 0.08)}, ${s * (1 - flach * 0.25)}) rotate(${zitter}deg)`, transformOrigin: '50% 79%'}}
>
<ellipse cx={0} cy={36} rx={210} ry={22} fill="rgba(70,45,25,0.25)" />
{blaetter.map((b) => {
const liste = b.dunkel ? HAUFEN_DUNKEL : HAUFEN_HELL;
return (
<g key={b.i} transform={`translate(${b.lx} ${b.ly}) rotate(${b.w}) scale(${b.s})`}>
<path d={b.ahorn ? AHORN : BUCHE} fill={liste[Math.floor(b.farbe * liste.length)]} stroke="rgba(90,40,20,0.35)" strokeWidth={1.5} strokeLinejoin="round" />
<path d={b.ahorn ? AHORN_ADERN : BUCHE_ADERN} stroke="rgba(110,50,20,0.4)" strokeWidth={1.6} fill="none" strokeLinecap="round" />
</g>
);
})}
</svg>
);
};

View file

@ -83,23 +83,7 @@ const Ast: React.FC<{x: number; y: number; w: number; l: number; d: number; tief
); );
}; };
// Volle Herbstkrone aus Farbtupfern (Folge 1); Mitte bei (40, -500) im Baum-Koordinatensystem const Apfelbaum: React.FC<{x: number; y: number; s?: number; farbe: string; blaetter?: boolean}> = ({x, y, s = 1, farbe, blaetter = true}) => (
const Krone: React.FC<{abend?: boolean}> = ({abend}) => {
const farben = abend ? ['#c9763a', '#d99a45', '#b2552e', '#a8682e', '#8f4a2a', '#8a8a48'] : ['#d98a3a', '#e0b04a', '#c8642c', '#b7792f', '#a8452a', '#9aa04f'];
return (
<g filter="url(#aq)">
{new Array(190).fill(0).map((_, i) => {
const w = random('kw' + i) * Math.PI * 2;
const r = Math.sqrt(random('kr' + i));
const cx = 40 + Math.cos(w) * r * 330;
const cy = -500 + Math.sin(w) * r * 230;
return <ellipse key={i} cx={cx} cy={cy} rx={20 + random('kx' + i) * 26} ry={15 + random('ky' + i) * 17} transform={`rotate(${random('kd' + i) * 180} ${cx} ${cy})`} fill={farben[i % farben.length]} opacity={0.82} />;
})}
</g>
);
};
const Apfelbaum: React.FC<{x: number; y: number; s?: number; farbe: string; blaetter?: boolean; krone?: 'morgen' | 'abend'}> = ({x, y, s = 1, farbe, blaetter = true, krone}) => (
<g transform={`translate(${x} ${y}) scale(${s})`}> <g transform={`translate(${x} ${y}) scale(${s})`}>
<path d="M -34 0 C -26 -120, -30 -260, -14 -380 L 14 -380 C 26 -260, 22 -120, 38 0 Z" fill={farbe} filter="url(#aq)" /> <path d="M -34 0 C -26 -120, -30 -260, -14 -380 L 14 -380 C 26 -260, 22 -120, 38 0 Z" fill={farbe} filter="url(#aq)" />
<Ast x={0} y={-370} w={-90} l={150} d={22} tiefe={4} farbe={farbe} seed="a" /> <Ast x={0} y={-370} w={-90} l={150} d={22} tiefe={4} farbe={farbe} seed="a" />
@ -107,8 +91,7 @@ const Apfelbaum: React.FC<{x: number; y: number; s?: number; farbe: string; blae
<Ast x={6} y={-320} w={-35} l={180} d={20} tiefe={4} farbe={farbe} seed="c" /> <Ast x={6} y={-320} w={-35} l={180} d={20} tiefe={4} farbe={farbe} seed="c" />
<Ast x={0} y={-250} w={-175} l={120} d={14} tiefe={3} farbe={farbe} seed="d" /> <Ast x={0} y={-250} w={-175} l={120} d={14} tiefe={3} farbe={farbe} seed="d" />
<Ast x={4} y={-240} w={-10} l={130} d={14} tiefe={3} farbe={farbe} seed="e" /> <Ast x={4} y={-240} w={-10} l={130} d={14} tiefe={3} farbe={farbe} seed="e" />
{krone && <Krone abend={krone === 'abend'} />} {blaetter &&
{blaetter && !krone &&
new Array(9).fill(0).map((_, i) => { new Array(9).fill(0).map((_, i) => {
const bx = -260 + random('bl' + i) * 520; const bx = -260 + random('bl' + i) * 520;
const by = -560 + random('bly' + i) * 240; const by = -560 + random('bly' + i) * 240;
@ -221,105 +204,6 @@ export const GartenSVG: React.FC<{nacht?: boolean}> = ({nacht = false}) => (
</svg> </svg>
); );
// Folge 1: alter Apfelbaum wie in den Higgsfield-Bildern – knorriger Stamm mit
// Wurzelansatz und Rinde, weit ausladende kahle Äste, nur noch wenige Blätter.
// Ein Zweig führt zum Apfel (Apfel selbst ist ein eigenes Teil, siehe APFEL in folge01/szene2).
const AlterBaum: React.FC<{x: number; y: number; s?: number; abend?: boolean; apfel?: [number, number]}> = ({x, y, s = 1, abend, apfel}) => {
const stamm = abend ? '#5e4a3c' : '#7a6452';
const ast = abend ? '#56443a' : '#6b5646';
const rinde = abend ? '#3e3028' : '#4f3e32';
return (
<g transform={`translate(${x} ${y}) scale(${s})`}>
<Ast x={-138} y={-478} w={-128} l={175} d={20} tiefe={5} farbe={ast} seed="ba" />
<Ast x={-138} y={-478} w={-70} l={160} d={16} tiefe={4} farbe={ast} seed="bf" />
<Ast x={74} y={-512} w={-62} l={185} d={20} tiefe={5} farbe={ast} seed="bc" />
<Ast x={74} y={-512} w={-20} l={150} d={14} tiefe={4} farbe={ast} seed="bg" />
<Ast x={-20} y={-352} w={-96} l={170} d={16} tiefe={4} farbe={ast} seed="bb" />
<Ast x={-48} y={-262} w={-168} l={160} d={16} tiefe={4} farbe={ast} seed="bd" />
<Ast x={36} y={-250} w={-12} l={170} d={16} tiefe={4} farbe={ast} seed="be" />
{apfel && (
<g fill="none" stroke={ast} strokeLinecap="round">
<path d={`M 70 -500 Q ${apfel[0] * 0.5} ${apfel[1] * 0.85}, ${apfel[0] * 0.85} ${apfel[1] + 26}`} strokeWidth={11} />
<path d={`M ${apfel[0] * 0.85} ${apfel[1] + 26} Q ${apfel[0] * 0.95} ${apfel[1] - 8}, ${apfel[0]} ${apfel[1]}`} strokeWidth={4} />
</g>
)}
{/* Stamm mit Wurzelansatz, gabelt sich in zwei dicke Äste */}
<path
d="M -96 6 C -70 -2, -50 -16, -46 -60 C -40 -150, -60 -230, -50 -300 C -70 -360, -110 -420, -150 -470 L -126 -490 C -90 -440, -50 -392, -20 -352 C 0 -400, 30 -460, 60 -522 L 90 -508 C 62 -440, 40 -372, 34 -300 C 30 -200, 34 -120, 44 -70 C 50 -28, 70 -6, 104 6 C 60 10, 20 2, 0 8 C -24 2, -60 10, -96 6 Z"
fill={stamm}
filter="url(#aq)"
/>
{/* Rinde */}
{[-24, -8, 8, 22].map((dx, i) => (
<path key={i} d={`M ${dx} -20 C ${dx - 8} -110, ${dx + 8} -200, ${dx - 4} -${250 + i * 12}`} stroke={rinde} strokeWidth={3} fill="none" opacity={0.55} strokeLinecap="round" />
))}
<path d="M -60 -6 C -40 -30, -36 -40, -38 -70 M 62 -4 C 46 -24, 40 -40, 40 -66" stroke={rinde} strokeWidth={3} fill="none" opacity={0.5} />
<ellipse cx={-6} cy={-170} rx={9} ry={14} fill={rinde} opacity={0.5} />
{/* die letzten Blätter */}
{new Array(14).fill(0).map((_, i) => {
const bx = -300 + random('ab' + i) * 600;
const by = -720 + random('aby' + i) * 330;
return <ellipse key={i} cx={bx} cy={by} rx={10} ry={6} transform={`rotate(${random('abr' + i) * 180} ${bx} ${by})`} fill={['#c8642c', '#d99a2b', '#a8452a'][i % 3]} />;
})}
</g>
);
};
// Folge 1: Noas Garten im goldenen Herbst (Morgen) bzw. im Abendlicht.
// Ohne Holzstapel (der kommt erst in Folge 2); Apfel, Blume und Laubhaufen sind eigene Teile.
export const GartenHerbstSVG: React.FC<{abend?: boolean}> = ({abend = false}) => (
<svg viewBox={`0 0 ${B} ${H}`} style={{position: 'absolute', inset: 0, width: '100%', height: '100%'}}>
<Filter />
<defs>
<linearGradient id="himmelH" x1="0" y1="0" x2="0" y2="1">
{abend ? (
<>
<stop offset="0" stopColor="#e9a46a" />
<stop offset="0.55" stopColor="#f3c58e" />
<stop offset="1" stopColor="#f8dcb0" />
</>
) : (
<>
<stop offset="0" stopColor="#cfe0e6" />
<stop offset="0.55" stopColor="#f3ead2" />
<stop offset="1" stopColor="#fbf0d6" />
</>
)}
</linearGradient>
<radialGradient id="sonneH">
<stop offset="0" stopColor={abend ? '#ffd38a' : '#fff4c8'} stopOpacity="0.95" />
<stop offset="1" stopColor={abend ? '#ffd38a' : '#fff4c8'} stopOpacity="0" />
</radialGradient>
</defs>
<rect width={B} height={H} fill="url(#himmelH)" />
<circle cx={abend ? 1500 : 1620} cy={abend ? 520 : 190} r={abend ? 260 : 220} fill="url(#sonneH)" />
{/* ferne Hügel */}
<path d="M -50 640 C 300 560, 700 600, 1000 620 S 1600 560, 1990 600 L 1990 900 L -50 900 Z" fill={abend ? '#c79a78' : '#c2c9a6'} filter="url(#aq)" />
<path d="M -50 700 C 400 650, 800 690, 1200 680 S 1700 650, 1990 690 L 1990 900 L -50 900 Z" fill={abend ? '#b48a68' : '#adbb8c'} filter="url(#aq)" />
{/* ferne Bäume mit Herbstlaub */}
{[820, 1000, 1760].map((x, i) => (
<g key={i}>
<Ast x={x} y={690} w={-90} l={70} d={7} tiefe={2} farbe={abend ? '#7a5a48' : '#8f7a62'} seed={'f' + i} />
<ellipse cx={x} cy={600} rx={58} ry={46} fill={abend ? '#c98a4a' : '#d9a24e'} opacity={0.75} filter="url(#aq)" />
</g>
))}
<Zaun y={600} farbe={abend ? '#c99a6e' : '#d4b083'} dunkel={abend ? '#a87a52' : '#b08c62'} />
<g>
<path d="M -50 790 C 400 760, 900 780, 1300 770 S 1800 760, 1990 780 L 1990 1100 L -50 1100 Z" fill={abend ? '#a89a5e' : '#9fb266'} filter="url(#aq)" />
<path d="M -50 900 C 500 880, 1100 900, 1990 880 L 1990 1100 L -50 1100 Z" fill={abend ? '#988a52' : '#8fa35a'} filter="url(#aq)" opacity={0.8} />
{new Array(46).fill(0).map((_, i) => {
const bx = random('hl' + i) * B;
const by = 820 + random('hly' + i) * 250;
return (
<ellipse key={i} cx={bx} cy={by} rx={13 + random('hr' + i) * 6} ry={6} transform={`rotate(${random('hd' + i) * 50 - 25} ${bx} ${by})`} fill={['#c8642c', '#d99a2b', '#a8452a', '#e0b04a'][i % 4]} opacity={0.85} />
);
})}
</g>
<AlterBaum x={340} y={905} s={0.95} abend={abend} apfel={[274, -576]} />
<Korn staerke={0.18} />
</svg>
);
// Blick von oben über den Zaun: kahle Baumkronen, weites Land, grauer Himmel. // Blick von oben über den Zaun: kahle Baumkronen, weites Land, grauer Himmel.
export const ObenSVG: React.FC = () => ( export const ObenSVG: React.FC = () => (
<svg viewBox={`0 0 ${B} ${H}`} style={{position: 'absolute', inset: 0, width: '100%', height: '100%'}}> <svg viewBox={`0 0 ${B} ${H}`} style={{position: 'absolute', inset: 0, width: '100%', height: '100%'}}>
@ -600,8 +484,6 @@ export const Ort: React.FC<{name: string}> = ({name}) => <Img src={staticFile(`o
export const ORTE: Record<string, React.FC> = { export const ORTE: Record<string, React.FC> = {
garten: () => <GartenSVG />, garten: () => <GartenSVG />,
garten_nacht: () => <GartenSVG nacht />, garten_nacht: () => <GartenSVG nacht />,
garten_herbst: () => <GartenHerbstSVG />,
garten_herbst_abend: () => <GartenHerbstSVG abend />,
oben: () => <ObenSVG />, oben: () => <ObenSVG />,
zimmer_himmel: () => <ZimmerHimmelSVG />, zimmer_himmel: () => <ZimmerHimmelSVG />,
zimmer_wand: () => <ZimmerWandSVG />, zimmer_wand: () => <ZimmerWandSVG />,

View file

@ -25,8 +25,6 @@ type Woerter = {
heute: (titel: string) => string; heute: (titel: string) => string;
namen: Record<Sprecher, string>; namen: Record<Sprecher, string>;
leise: string; leise: string;
kanalUntertitel: string; // Banner
kanalRhythmus: string; // Banner
kalenderTitel: string; kalenderTitel: string;
hatschi: string; hatschi: string;
}; };
@ -39,8 +37,6 @@ const WOERTER: Record<Sprache, Woerter> = {
heute: (t) => `Heute: ${t}.`, heute: (t) => `Heute: ${t}.`,
namen: {ERZ: 'Erzählstimme', PINO: 'Pino', NOA: 'Noa', LULU: 'Lulu', ALLE: 'Alle', NOA_LULU: 'Noa und Lulu'}, namen: {ERZ: 'Erzählstimme', PINO: 'Pino', NOA: 'Noa', LULU: 'Lulu', ALLE: 'Alle', NOA_LULU: 'Noa und Lulu'},
leise: '(leise)', leise: '(leise)',
kanalUntertitel: 'Ruhige Geschichten für Kinder von 3 bis 6',
kanalRhythmus: 'Jeden Sonntag eine neue Geschichte',
kalenderTitel: 'Pinos Winterfest-Kalender', kalenderTitel: 'Pinos Winterfest-Kalender',
hatschi: 'Hatschi!', hatschi: 'Hatschi!',
}, },
@ -51,8 +47,6 @@ const WOERTER: Record<Sprache, Woerter> = {
heute: (t) => `Hoy: ${t}.`, heute: (t) => `Hoy: ${t}.`,
namen: {ERZ: 'Narración', PINO: 'Pino', NOA: 'Noa', LULU: 'Lulu', ALLE: 'Todos', NOA_LULU: 'Noa y Lulu'}, namen: {ERZ: 'Narración', PINO: 'Pino', NOA: 'Noa', LULU: 'Lulu', ALLE: 'Todos', NOA_LULU: 'Noa y Lulu'},
leise: '(bajito)', leise: '(bajito)',
kanalUntertitel: 'Cuentos tranquilos para niños de 3 a 6 años',
kanalRhythmus: 'Un cuento nuevo cada domingo',
kalenderTitel: 'El calendario de Pino', kalenderTitel: 'El calendario de Pino',
hatschi: '¡Achís!', hatschi: '¡Achís!',
}, },
@ -63,8 +57,6 @@ const WOERTER: Record<Sprache, Woerter> = {
heute: (t) => `Aujourd'hui : ${t}.`, heute: (t) => `Aujourd'hui : ${t}.`,
namen: {ERZ: 'Narration', PINO: 'Pino', NOA: 'Noa', LULU: 'Lulu', ALLE: 'Tous', NOA_LULU: 'Noa et Lulu'}, namen: {ERZ: 'Narration', PINO: 'Pino', NOA: 'Noa', LULU: 'Lulu', ALLE: 'Tous', NOA_LULU: 'Noa et Lulu'},
leise: '(doucement)', leise: '(doucement)',
kanalUntertitel: 'Des histoires douces pour les 3 à 6 ans',
kanalRhythmus: 'Une nouvelle histoire chaque dimanche',
kalenderTitel: 'Le calendrier de Pino', kalenderTitel: 'Le calendrier de Pino',
hatschi: 'Atchoum !', hatschi: 'Atchoum !',
}, },

View file

@ -1,29 +0,0 @@
import React from 'react';
import {INTRO_OPTIONEN, Intro, OUTRO, Outro, introZeilen} from '../baukasten/buch';
import {baueFolge, type SzenenEintrag} from '../baukasten/folge';
import {LIED, Lied} from './lied';
import {TITEL, plane} from './plan';
import {S2, Szene2} from './szene2';
import {S3, Szene3} from './szene3';
import {S4, Szene4} from './szene4';
import {S5, Szene5} from './szene5';
import {S6, Szene6} from './szene6';
import {S7, Szene7} from './szene7';
// Folge 1 „Zu klein, zu groß“ – wie Folge 2 per Code aus der Figuren-Bibliothek.
const INTRO = plane('intro', introZeilen(TITEL), INTRO_OPTIONEN);
export const SZENEN: SzenenEintrag[] = [
{name: 'Intro', datei: 'Folge01.tsx', plan: INTRO, K: () => <Intro plan={INTRO} titel={TITEL} posen={['pino_A3_lachen', 'noa_A3_lachen', 'lulu_A3_lachen']} />},
{name: 'Wer raschelt da', datei: 'szene2.tsx', plan: S2, K: Szene2},
{name: 'Der Apfel ist zu hoch', datei: 'szene3.tsx', plan: S3, K: Szene3},
{name: 'Ein Baum mit Flecken', datei: 'szene4.tsx', plan: S4, K: Szene4},
{name: 'Die Blume ist zu tief', datei: 'szene5.tsx', plan: S5, K: Szene5},
{name: 'Zusammen gehts', datei: 'szene6.tsx', plan: S6, K: Szene6},
{name: 'Lied', datei: 'lied.tsx', plan: LIED, K: Lied},
{name: 'Pino ist muede', datei: 'szene7.tsx', plan: S7, K: Szene7},
{name: 'Outro', datei: 'Folge01.tsx', plan: OUTRO, K: () => <Outro posen={['pino_B3_schlafen', 'noa_A_arm-hoch-winken', 'lulu_A3_lachen']} />},
];
export const F01 = baueFolge('F01', SZENEN);

View file

@ -1,93 +0,0 @@
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 {BODEN, Ort} from '../baukasten/orte';
import {SCHRIFT, Untertitel} from '../baukasten/untertitel';
import {TEXTE, plane} from './plan';
import {HAUFEN_X} from './szene2';
// Lied „Einer klein und einer groß“: Refrain (alle), Strophe (Erzählstimme), Refrain.
// Die drei stehen nebeneinander – klein, mittel, groß – und wippen im Takt.
const TAKT = 19; // Frames pro Schlag (ca. 95 bpm)
export const LIED = plane(
'lied',
[
{id: 'r1', wer: 'ALLE', dauer: 3.2},
{id: 'r2', wer: 'ALLE', dauer: 3.2},
{id: 'r3', wer: 'ALLE', dauer: 3.2},
{id: 'r4', wer: 'ALLE', dauer: 3.6},
{id: 's1', wer: 'ERZ', dauer: 3.2},
{id: 's2', wer: 'ERZ', dauer: 3.2},
{id: 's3', wer: 'ERZ', dauer: 3.2},
{id: 's4', wer: 'ERZ', dauer: 3.6},
{id: 'w1', wer: 'ALLE', dauer: 3.2},
{id: 'w2', wer: 'ALLE', dauer: 3.2},
{id: 'w3', wer: 'ALLE', dauer: 3.2},
{id: 'w4', wer: 'ALLE', dauer: 3.6},
],
{vorlauf: 1.0, nachlauf: 1.0},
);
const Note: React.FC<{x: number; y: number; s: number; farbe: string}> = ({x, y, s, farbe}) => (
<svg width={60} height={80} viewBox="0 0 60 80" style={{position: 'absolute', left: x, top: y, transform: `scale(${s})`}}>
<ellipse cx={18} cy={64} rx={15} ry={11} fill={farbe} transform="rotate(-20 18 64)" />
<rect x={30} y={8} width={6} height={58} fill={farbe} />
<path d="M 36 8 C 50 18, 56 30, 46 44 C 50 30, 44 24, 36 22 Z" fill={farbe} />
</svg>
);
export const Lied: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = LIED;
const schlag = Math.abs(Math.sin((f * Math.PI) / TAKT));
const wipp = (phase: number, h: number) => -Math.abs(Math.sin((f * Math.PI) / TAKT + phase)) * h;
const schwank = (phase: number, g: number) => Math.sin((f * Math.PI) / (2 * TAKT) + phase) * g;
// Strophe: „Lulu holt ihn mit dem Mund“ / „Pino pflückt sie“
const luluPose: [number, string][] = [
[0, 'lulu_A3_lachen'],
[z.s2.start, 'lulu_A_apfel'],
[z.s3.start, 'lulu_A3_lachen'],
];
const pinoPose: [number, string][] = [
[0, 'pino_A3_lachen'],
[z.s4.start, 'pino_A_hinterbeine'],
[z.w1.start, 'pino_A3_lachen'],
];
const noaPose: [number, string][] = [
[0, 'noa_A3_lachen'],
[z.r4.start, 'noa_A_arme-hoch'],
[z.s1.start, 'noa_A3_lachen'],
[z.w4.start, 'noa_A_arme-hoch'],
];
return (
<>
<Kamera x={1000} y={600} zoom={1.3}>
<Ort name="garten_herbst" />
<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={noaPose} x={980} y={BODEN + wipp(0.3, 10)} drehung={schwank(0, 3)} blende={8} />
<Figur pose={pinoPose} x={760} y={BODEN + wipp(0, 16)} drehung={schwank(1, 4)} stauchY={1 - 0.03 * schlag} blende={8} />
{f >= z.s4.start && f < z.w1.start && <Blume x={764} y={BODEN - 40} s={0.5} />}
</Kamera>
{new Array(8).fill(0).map((_, i) => {
const t = ((f + i * 25) % 200) / 200;
const x = 200 + random(`n${i}`) * 1500 + Math.sin(t * 6 + i) * 30;
return (
<div key={i} style={{opacity: Math.sin(t * Math.PI) * 0.9}}>
<Note x={x} y={700 - t * 600} s={0.8 + random(`ns${i}`) * 0.6} farbe={['#c8642c', '#3f5a8a', '#5f8a4a', '#a0526b'][i % 4]} />
</div>
);
})}
<div style={{position: 'absolute', top: 60, left: 0, right: 0, textAlign: 'center', fontFamily: SCHRIFT, fontWeight: 600, fontSize: 40, color: '#8a6a4a', opacity: Math.min(1, f / 20)}}>
♫ {TEXTE.lied} ♫
</div>
<Vignette />
<Untertitel zeilen={LIED.liste} />
<Blende dauer={dauer} rein={12} raus={12} />
</>
);
};

View file

@ -1,29 +0,0 @@
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/folge01/<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/folge01/${SPRACHE}`},
);

View file

@ -1 +0,0 @@
{}

View file

@ -1 +0,0 @@
{}

View file

@ -1 +0,0 @@
{}

View file

@ -1,163 +0,0 @@
import React from 'react';
import {useCurrentFrame} from 'remotion';
import {kurve} from '../bewegung';
import {Figur, Geher, Kamera} from '../baukasten/figur';
import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt, ruck} from '../baukasten/helfer';
import {Apfel, Blaetterregen, Blaetterwind, Blume, Laubhaufen} from '../baukasten/natur';
import {BODEN, Ort} from '../baukasten/orte';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
// Szene 2: Wer raschelt da? Noa springt in den Laubhaufen, darin sitzt Pino.
// Lage im Herbstgarten (gilt für alle Szenen der Folge)
export const HAUFEN_X = 1180; // Laubhaufen
export const APFEL = {x: 600, y: 390}; // der letzte Apfel: zu hoch für Noa, Lulu reicht mit dem Maul hin
export const BLUME_X = 1560; // blaue Blume am Zaun
export const BLUME_Y = 805;
export const NOA_X = 960; // Noa neben dem Haufen
export const PINO_HAUFEN_Y = BODEN - 128; // Pino steckt oben im Haufen (als Kugel)
export const S2 = plane('s2', [
{id: 'bild0', dauer: 2.2},
{id: 'e1', wer: 'ERZ'},
{id: 'e2', wer: 'ERZ'},
{id: 'e3', wer: 'ERZ'},
{id: 'n1', wer: 'NOA', dauer: 1.6},
{id: 'bild1', dauer: 1.6},
{id: 'bild2', dauer: 2.6},
{id: 'n2', wer: 'NOA'},
{id: 'e4', wer: 'ERZ'},
{id: 'e5', wer: 'ERZ', pause: 3},
{id: 'e6', wer: 'ERZ'},
{id: 'e7', wer: 'ERZ'},
{id: 'bild3', dauer: 2.0},
{id: 'n3', wer: 'NOA', leise: true},
{id: 'bild4', dauer: 2.2},
{id: 'p1', wer: 'PINO'},
{id: 'n4', wer: 'NOA'},
{id: 'p2', wer: 'PINO'},
]);
export const Szene2: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S2;
// Noa kommt von links, springt bei „Hui!“ mitten in den Haufen und hüpft wieder heraus
const noaAn = z.bild0.start + 10 + 165; // zügig, ca. 5,5 s
const sprung = z.n1.start + 4;
const SPRUNG_DAUER = 22;
const landung = sprung + SPRUNG_DAUER;
const raus = z.bild1.start + 10; // hüpft aus dem Haufen
const imHaufen = f >= landung && f < raus + 8;
const noaX = kurve(f, [
[z.bild0.start + 10, -200],
[noaAn, 760],
[sprung, 760],
[landung, HAUFEN_X - 30],
[raus, HAUFEN_X - 30],
[raus + 16, NOA_X],
]);
const noaGeht = f > z.bild0.start + 10 && f < noaAn;
const imSprung = f >= sprung && f < landung;
const sprungY = imSprung ? -Math.sin(((f - sprung) / SPRUNG_DAUER) * Math.PI) * 170 + ((f - sprung) / SPRUNG_DAUER) * 40 : imHaufen ? 40 : 0;
const schreck = z.bild2.start + 40; // Pino taucht auf
const hocke = z.bild3.start + 10;
const aufstehen = z.n4.start;
const noaPose: [number, string][] = [
[0, 'noa_A6_blick-rechts'],
[z.e3.start, 'noa_A3_lachen'],
[landung + 6, 'noa_A6_blick-rechts'],
[schreck, 'noa_B2_erschrocken'],
[z.n2.start + 6, 'noa_A4_staunen'],
[z.e6.start, 'noa_A6_blick-rechts'],
[hocke, 'noa_B_hocken'],
[aufstehen, 'noa_A3_lachen'],
[z.p2.start + 10, 'noa_A6_blick-rechts'],
];
// Hocke: Standbilder weich überblenden, dabei kurz absinken bzw. sich strecken
const HOCKE_BLENDE = 14;
const wechsel = (t: number) => (f >= t && f < t + HOCKE_BLENDE ? Math.sin(((f - t) / HOCKE_BLENDE) * Math.PI) : 0);
const hockeStauch = 1 - 0.05 * wechsel(hocke) + 0.03 * wechsel(aufstehen);
const noaY =
BODEN +
sprungY +
huepfer(f, raus, 60, 16) +
ruck(f, schreck, 3) +
huepfer(f, aufstehen + HOCKE_BLENDE + 4, 14, 10);
// Laubhaufen: wird bei der Landung gestaucht, später raschelt er
const flach = kurve(f, [
[landung - 4, 0],
[landung, 1],
[landung + 14, 0],
]);
const wackeln = kurve(f, [
[z.bild2.start, 0],
[z.bild2.start + 8, 1],
[schreck - 4, 1],
[schreck + 6, 0],
]);
// Pino: als Stachelkugel im Haufen, kommt hoch, öffnet sich bei bild4
const auf = z.bild4.start; // Einroll-Clip rückwärts
const AUF_FRAMES = 30;
const oeffnen = f >= auf && f < auf + AUF_FRAMES ? Math.sin(((f - auf) / AUF_FRAMES) * Math.PI) : 0;
const zuNoa = f >= auf; // die Bilder schauen nach rechts, Noa sitzt links
const pinoY = kurve(f, [
[schreck - 10, PINO_HAUFEN_Y + 140],
[schreck + 6, PINO_HAUFEN_Y],
[auf + 20, PINO_HAUFEN_Y],
[auf + AUF_FRAMES, PINO_HAUFEN_Y - 22],
]);
const pinoDa = f >= schreck - 10;
const pinoPose: [number, string][] = [
[0, 'pino_A_kugel'],
[auf + 4, 'pino_ref_sitzend'],
[z.p1.start, 'pino_A6_blick-rechts'],
[z.p1.start + 40, 'pino_A4_staunen'],
[z.p2.start, 'pino_B_blick-nach-oben'],
[z.p2.ende - 10, 'pino_A3_lachen'],
];
// Stacheln zittern vor Angst, solange er eine Kugel ist
const angst = f < auf ? Math.sin(f * 1.7) * 1.5 : 0;
const pinoStauch = atmen(f, 1, 0.01) * hopfStauch(f, schreck) * (1 + 0.06 * oeffnen);
const kam = kamFahrt(f, [
[0, 960, 540, 1.0],
[z.e3.start, 900, 600, 1.1],
[landung, 1020, 640, 1.25],
[schreck, 1120, 720, 1.55],
[z.e4.start + 10, 1170, 760, 2.1],
[z.e6.start, 1090, 700, 1.6],
[z.n3.start, 1080, 720, 1.75],
[z.bild4.start + 10, 1160, 770, 2.1],
[z.n4.start, 1080, 700, 1.6],
[dauer, 1060, 690, 1.55],
]);
kam.x += ruck(f, schreck, 5);
return (
<>
<Kamera {...kam}>
<Ort name="garten_herbst" />
<Apfel x={APFEL.x} y={APFEL.y} s={0.9} />
<Blume x={BLUME_X} y={BLUME_Y} s={0.8} />
{pinoDa && <Figur pose={pinoPose} spiegeln={zuNoa} x={HAUFEN_X + 10} y={pinoY} stauchY={pinoStauch} drehung={angst} schatten={false} blende={14} />}
{/* Im Haufen steht Noa hinter den vorderen Blättern */}
{imHaufen && <Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2) * hopfStauch(f, landung)} schatten={false} />}
<Laubhaufen x={HAUFEN_X} y={BODEN + 6} wackeln={wackeln} flach={flach} />
{imHaufen ? null : noaGeht ? (
<Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.5} />
) : (
<Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2) * hopfStauch(f, landung) * hockeStauch} blende={f >= hocke ? HOCKE_BLENDE : 5} />
)}
<Blaetterregen start={landung - 2} x0={HAUFEN_X - 60} y0={BODEN - 60} boden={BODEN - 10} anzahl={28} aus={z.e4.start} />
</Kamera>
<Blaetterwind anzahl={5} />
<Vignette />
<Untertitel zeilen={S2.liste} />
<Blende dauer={dauer} rein={12} raus={0} />
</>
);
};

View file

@ -1,114 +0,0 @@
import React from 'react';
import {useCurrentFrame} from 'remotion';
import {kurve} from '../bewegung';
import {Figur, Geher, Kamera} from '../baukasten/figur';
import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt} from '../baukasten/helfer';
import {Apfel, Blaetterwind, Blume, Laubhaufen} from '../baukasten/natur';
import {BODEN, Ort} from '../baukasten/orte';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {APFEL, BLUME_X, BLUME_Y, HAUFEN_X} from './szene2';
// Szene 3: Der Apfel ist zu hoch. Pino hüpft, Noa streckt sich – beide zu klein.
// Pino-Bilder schauen nach rechts (zum Apfel); zu Noa (links) wird er gespiegelt.
export const PINO_BAUM_X = 470; // Pino unter dem Apfel
export const NOA_BAUM_X = 330; // Noa am untersten Ast
export const S3 = plane('s3', [
{id: 'bild0', dauer: 2.6},
{id: 'p1', wer: 'PINO'},
{id: 'bild1', dauer: 2.4},
{id: 'p2', wer: 'PINO', dauer: 2.2},
{id: 'p3', wer: 'PINO'},
{id: 'n1', wer: 'NOA'},
{id: 'bild2', dauer: 2.8},
{id: 'n2', wer: 'NOA'},
{id: 'e1', wer: 'ERZ'},
{id: 'e2', wer: 'ERZ', pause: 3},
]);
export const Szene3: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S3;
// Pino: schaut hoch, stellt sich auf die Hinterbeine, hüpft zweimal, ist traurig
const hopp1 = z.p2.start + 2;
const hopp2 = z.p2.start + 32;
const pinoPose: [number, string][] = [
[0, 'pino_B_blick-nach-oben'],
[z.bild1.start, 'pino_A_hinterbeine'],
[z.p3.start, 'pino_B1_traurig'],
[z.n1.start + 8, 'pino_A4_staunen'],
[z.n2.start, 'pino_B1_traurig'],
[z.e1.start + 20, 'pino_B_blick-nach-oben'],
];
const zuNoa = f >= z.n1.start && f < z.e1.start + 20;
const strecken = kurve(f, [
[z.bild1.start, 0],
[z.bild1.start + 20, 1],
[z.p3.start, 1],
[z.p3.start + 12, 0],
]);
const pinoY = BODEN + huepfer(f, hopp1, 46, 16) + huepfer(f, hopp2, 46, 16);
const pinoStauch = atmen(f, 1, 0.01) * (1 + 0.05 * strecken) * hopfStauch(f, hopp1 + 16) * hopfStauch(f, hopp2 + 16);
// Noa kommt von links, streckt sich zum untersten Ast – reicht nicht
const noaAn = z.n1.start + 6;
const noaX = kurve(f, [
[z.p3.start - 70, -150],
[noaAn, NOA_BAUM_X],
]);
const noaGeht = f >= z.p3.start - 70 && f < noaAn;
const noaDa = f >= z.p3.start - 70;
const greifen = z.bild2.start + 6;
const noaPose: [number, string][] = [
[0, 'noa_A3_lachen'],
[greifen, 'noa_A_arm-hoch-winken'],
[z.n2.start, 'noa_B1_traurig'],
[z.e1.start + 20, 'noa_A4_staunen'],
[z.e2.start, 'noa_A3_lachen'],
];
// auf die Zehenspitzen: strecken und zweimal kurz hochfedern
const zehen = kurve(f, [
[greifen, 0],
[greifen + 14, 1],
[z.n2.start - 6, 1],
[z.n2.start + 8, 0],
]);
const noaY = BODEN + huepfer(f, greifen + 22, 14, 12) + huepfer(f, greifen + 46, 14, 12);
const kam = kamFahrt(f, [
[0, PINO_BAUM_X + 60, 830, 2.0],
[z.p1.start, 560, 560, 1.7],
[z.p1.ende, 560, 560, 1.7],
[z.bild1.start + 10, PINO_BAUM_X + 40, 800, 1.9],
[z.p3.ende, 520, 740, 1.6],
[z.n1.ende, 470, 720, 1.5],
[z.bild2.start + 30, 500, 600, 1.35],
[z.e1.start, 560, 560, 1.2],
[dauer, 600, 560, 1.15],
]);
return (
<>
<Kamera {...kam}>
<Ort name="garten_herbst" />
<Apfel x={APFEL.x} y={APFEL.y} s={0.9} r={Math.sin(f * 0.05) * 4} />
<Blume x={BLUME_X} y={BLUME_Y} s={0.8} />
<Laubhaufen x={HAUFEN_X} y={BODEN + 6} />
{noaDa &&
(noaGeht ? (
<Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.5} />
) : (
<Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2) * (1 + 0.04 * zehen)} blende={10} />
))}
<Figur pose={pinoPose} spiegeln={zuNoa} x={PINO_BAUM_X} y={pinoY} stauchY={pinoStauch} blende={10} />
</Kamera>
<Blaetterwind anzahl={4} />
<Vignette />
<Untertitel zeilen={S3.liste} />
<Blende dauer={dauer} rein={12} raus={0} />
</>
);
};

View file

@ -1,166 +0,0 @@
import React from 'react';
import {useCurrentFrame} from 'remotion';
import {kurve} from '../bewegung';
import {ClipFigur, Figur, Geher, Kamera} from '../baukasten/figur';
import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt, luluMaulC9, punkt} from '../baukasten/helfer';
import {Apfel, Blaetterwind, Blume, Laubhaufen} from '../baukasten/natur';
import {BODEN, Ort} from '../baukasten/orte';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {APFEL, BLUME_X, BLUME_Y, HAUFEN_X} from './szene2';
import {NOA_BAUM_X, PINO_BAUM_X} from './szene3';
// Szene 4: Ein Baum mit Flecken? Lulu kommt, pflückt den Apfel und reicht ihn Pino.
// Lulus Bilder schauen nach links (zu Noa und Pino), Pinos nach rechts (zu Lulu).
// Lulus Maul mit dem Apfel (lulu_A_apfel) liegt 149 px links und 542 px über dem Fußpunkt:
// so steht sie, dass sie den Apfel am Baum erreicht.
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_START = 17; // vorher steht sie im Clip still
const C9_UNTEN = 105; // Kopf unten
const c9Frames = (bilder: number) => (bilder * 30) / 24 / C9_TEMPO;
export const S4 = plane('s4', [
{id: 'bild0', dauer: 3.4},
{id: 'n1', wer: 'NOA'},
{id: 'p1', wer: 'PINO'},
{id: 'bild1', dauer: 3.2},
{id: 'l1', wer: 'LULU'},
{id: 'e1', wer: 'ERZ'},
{id: 'e2', wer: 'ERZ'},
{id: 'p2', wer: 'PINO'},
{id: 'l2', wer: 'LULU'},
{id: 'l3', wer: 'LULU'},
{id: 'bild2', dauer: 5.0},
{id: 'p3', wer: 'PINO'},
{id: 'l4', wer: 'LULU'},
{id: 'p4', wer: 'PINO'},
{id: 'bild3', dauer: 3.6},
]);
export const Szene4: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S4;
// Lulu stakst von rechts herein (tapp, tapp), wartet, tritt zum Apfel und frisst ihn
const luluAn = z.bild0.ende + 10;
const essen = z.bild2.start + 10;
const runter = essen + 30; // Hals senken (Clip)
const unten = runter + c9Frames(C9_UNTEN - C9_START);
const ablegen = unten + 10;
const hoch = z.p4.ende - 4; // Hals wieder heben (Clip rückwärts)
const oben = hoch + c9Frames(C9_UNTEN);
const luluX = kurve(f, [
[0, 1650],
[luluAn, LULU_WARTE_X],
[essen - 16, LULU_WARTE_X],
[essen + 2, LULU_APFEL_X],
]);
const luluY = BODEN - 6;
const luluGeht = f < luluAn;
const imClip = f >= runter && f < oben;
const luluPose: [number, string][] = [
[0, 'lulu_A4_staunen'],
[z.l1.start, 'lulu_A3_lachen'],
[z.l2.start, 'lulu_A5_blick-links'],
[z.l3.start + 20, 'lulu_A4_staunen'],
[essen, 'lulu_A_apfel'],
[oben - 1, 'lulu_A1_augen-zu'],
[oben + 6, 'lulu_A3_lachen'],
];
// Der Apfel: am Baum → im Maul (im Bild lulu_A_apfel) → im Maul mit nach unten → neben Pino
const apfelAmBaum = f < essen + 6;
const clipBild = Math.min(C9_UNTEN, C9_START + ((f - runter) * C9_TEMPO * 24) / 30);
const maul = luluMaulC9(clipBild);
const amMaul = punkt('lulu', luluX, luluY, maul.cx, maul.cy);
const APFEL_BODEN = {x: amMaul.x + 8, y: BODEN - 12};
const apfelSichtbar = f >= runter && f < 1e9;
const apfel =
f < ablegen
? amMaul
: {
x: kurve(f, [
[ablegen, amMaul.x],
[ablegen + 12, APFEL_BODEN.x],
]),
y: kurve(f, [
[ablegen, amMaul.y],
[ablegen + 12, APFEL_BODEN.y],
]),
};
// Pino staunt, freut sich; Stacheln halb hoch bei „noch viel größer“ (kurz aufplustern)
const pinoPose: [number, string][] = [
[0, 'pino_A4_staunen'],
[z.bild1.start + 40, 'pino_B_blick-nach-oben'],
[z.p2.start, 'pino_A4_staunen'],
[z.l2.start, 'pino_B_blick-nach-oben'],
[z.p3.start, 'pino_A4_staunen'],
[z.p4.start, 'pino_A3_lachen'],
];
const pinoY = BODEN + huepfer(f, z.p4.start + 10, 34, 14) + huepfer(f, z.p4.start + 34, 22, 12);
const pinoStauch = atmen(f, 1, 0.01) * hopfStauch(f, z.p2.start, 16) * hopfStauch(f, z.p4.start + 24);
const noaPose: [number, string][] = [
[0, 'noa_A4_staunen'],
[z.n1.start, 'noa_B_zeigen'],
[z.p1.start + 10, 'noa_A3_lachen'],
[z.bild1.start + 40, 'noa_A4_staunen'],
[z.l2.start, 'noa_A3_lachen'],
];
const noaY = BODEN + huepfer(f, z.l1.ende, 16, 10);
const kam = kamFahrt(f, [
[0, 1000, 860, 2.2],
[luluAn, 820, 850, 2.1],
[z.n1.start, 620, 820, 1.7],
[z.bild1.start, 700, 840, 1.9],
[z.bild1.start + 60, 660, 380, 1.9],
[z.l1.ende, 660, 400, 1.8],
[z.e1.start + 20, 700, 560, 1.05],
[z.e2.ende, 700, 560, 1.05],
[z.p2.start + 10, 520, 800, 1.8],
[z.l2.start + 10, 640, 430, 1.6],
[z.l3.ende, 620, 420, 1.7],
[essen + 20, 640, 470, 1.6],
[unten, 650, 640, 1.2],
[z.p4.ende, 650, 640, 1.2],
[dauer, 680, 600, 1.15],
]);
return (
<>
<Kamera {...kam}>
<Ort name="garten_herbst" />
<Blume x={BLUME_X} y={BLUME_Y} s={0.8} />
{apfelAmBaum && <Apfel x={APFEL.x} y={APFEL.y} s={0.9} r={Math.sin(f * 0.05) * 4} />}
{luluGeht ? (
<Geher figur="lulu" x={luluX} y={luluY} tempo={1} />
) : imClip ? (
f < hoch ? (
<ClipFigur name="lulu_C9_hals-runter" start={runter - c9Frames(C9_START)} tempo={C9_TEMPO} bis={C9_UNTEN / 24} x={luluX} y={luluY} />
) : (
<ClipFigur name="lulu_C9_hals-runter" start={hoch} tempo={C9_TEMPO} bis={C9_UNTEN / 24} rueckwaerts x={luluX} y={luluY} />
)
) : (
<Figur pose={luluPose} x={luluX} y={luluY} stauchY={atmen(f, 3, 0.006)} blende={14} />
)}
{/* Übergang Apfel-Bild → Clip: das Apfel-Bild blendet kurz aus */}
{f >= runter && f < runter + 8 && <Figur pose="lulu_A_apfel" x={luluX} y={luluY} deckkraft={1 - (f - runter) / 8} />}
<Laubhaufen x={HAUFEN_X} y={BODEN + 6} />
<Figur pose={noaPose} x={NOA_BAUM_X} y={noaY} stauchY={atmen(f, 2)} blende={10} />
<Figur pose={pinoPose} x={PINO_BAUM_X} y={pinoY} stauchY={pinoStauch} blende={10} />
{apfelSichtbar && <Apfel x={apfel.x} y={apfel.y} s={0.8} />}
</Kamera>
<Blaetterwind anzahl={4} />
<Vignette />
<Untertitel zeilen={S4.liste} />
<Blende dauer={dauer} rein={12} raus={0} />
</>
);
};

View file

@ -1,128 +0,0 @@
import React from 'react';
import {useCurrentFrame} from 'remotion';
import {kurve} from '../bewegung';
import {ClipFigur, Figur, Kamera} from '../baukasten/figur';
import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt} from '../baukasten/helfer';
import {Blaetterwind, Blume, Laubhaufen} from '../baukasten/natur';
import {BODEN, Ort} from '../baukasten/orte';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {BLUME_X, BLUME_Y, HAUFEN_X} from './szene2';
// Szene 5: Die Blume ist zu tief. Lulu beugt sich, wackelt, kommt nicht hinunter.
// Lulu steht rechts der Blume und schaut nach links; Pino und Noa stehen links davon.
export const LULU_BLUME_X = 1760;
export const PINO_BLUME_X = 950;
export const NOA_BLUME_X = 820;
export const S5 = plane('s5', [
{id: 'bild0', dauer: 2.6},
{id: 'l1', wer: 'LULU'},
{id: 'l2', wer: 'LULU'},
{id: 'bild1', dauer: 3.6},
{id: 'l3', wer: 'LULU'},
{id: 'l4', wer: 'LULU'},
{id: 'bild2', dauer: 1.8},
{id: 'e1', wer: 'ERZ'},
{id: 'e2', wer: 'ERZ', pause: 3},
{id: 'n1', wer: 'NOA'},
]);
// Kichern wie in Folge 2: kleine Hüpfer mit Wiegen, schwillt an und ab
export const kichern = (f: number, a: number, e: number, phase = 0) => {
const huelle = f > a && f < e ? Math.sin(((f - a) / (e - a)) * Math.PI) : 0;
const p = (f - a) * (0.42 + 0.08 * Math.sin((f - a) * 0.05)) + phase;
return {
y: -Math.abs(Math.sin(p)) * 7 * huelle,
stauch: 1 - 0.035 * Math.abs(Math.cos(p)) * huelle,
dreh: (Math.sin(p * 0.5) * 2.5 + 2) * huelle,
};
};
export const Szene5: React.FC = () => {
const f = useCurrentFrame();
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_START = 17;
const C9_HALB = 52; // so weit kommt sie
const c9 = (bilder: number) => (bilder * 30) / 24 / C9_TEMPO;
const beugA = z.bild1.start;
const halb = beugA + c9(C9_HALB - C9_START);
const wieder = z.l4.ende - 6;
const aufrecht = wieder + c9(C9_HALB);
const imClip = f >= beugA && f < aufrecht;
const steckt = kurve(f, [
[halb - 6, 0],
[halb + 6, 1],
[wieder - 6, 1],
[wieder + 4, 0],
]);
// angestrengtes Wackeln: kleine Kipp- und Federbewegung um die Füße
const luluDreh = steckt * (Math.sin(f * 0.6) * 1.6 + Math.sin(f * 0.27) * 1.2);
const luluFeder = 1 - steckt * 0.012 * (1 + Math.sin(f * 0.9));
const luluPose: [number, string][] = [
[0, 'lulu_A4_staunen'],
[z.l1.start + 10, 'lulu_A3_lachen'],
[aufrecht - 1, 'lulu_B1_traurig'],
[z.n1.start + 20, 'lulu_A4_staunen'],
];
// Pino und Noa kichern, während Lulu wackelt
const kA = z.bild1.start + 20;
const kE = z.l4.ende;
const kp = kichern(f, kA, kE);
const kn = kichern(f, kA, kE, 1.3);
const pinoPose: [number, string][] = [
[0, 'pino_A4_staunen'],
[kA, 'pino_A3_lachen'],
[z.bild2.start, 'pino_A2_augen-halb'],
[z.e2.start, 'pino_A4_staunen'],
[z.n1.start + 10, 'pino_A3_lachen'],
];
const noaPose: [number, string][] = [
[0, 'noa_A4_staunen'],
[kA, 'noa_A3_lachen'],
[z.bild2.start, 'noa_A4_staunen'],
[z.n1.start, 'noa_A3_lachen'],
[z.n1.start + 30, 'noa_B_zeigen'],
];
const kam = kamFahrt(f, [
[0, BLUME_X, 760, 2.4],
[z.l1.start, 1650, 560, 1.4],
[z.l2.ende, 1600, 560, 1.3],
[z.bild1.start + 40, 1560, 600, 1.3],
[z.bild2.start, 1500, 620, 1.25],
[z.e2.start, 1480, 640, 1.3],
[z.n1.start, 900, 760, 1.8],
[dauer, 910, 770, 1.9],
]);
return (
<>
<Kamera {...kam}>
<Ort name="garten_herbst" />
<Laubhaufen x={HAUFEN_X} y={BODEN + 6} />
<Blume x={BLUME_X} y={BLUME_Y} s={0.8} r={Math.sin(f * 0.07) * 3} />
{imClip ? (
f < wieder ? (
<ClipFigur name="lulu_C9_hals-runter" start={beugA - c9(C9_START)} tempo={C9_TEMPO} bis={C9_HALB / 24} x={LULU_BLUME_X} y={BODEN - 6} drehung={luluDreh} stauchY={luluFeder} />
) : (
<ClipFigur name="lulu_C9_hals-runter" start={wieder} tempo={C9_TEMPO} bis={C9_HALB / 24} rueckwaerts x={LULU_BLUME_X} y={BODEN - 6} drehung={luluDreh} />
)
) : (
<Figur pose={luluPose} x={LULU_BLUME_X} y={BODEN - 6} stauchY={atmen(f, 3, 0.006)} blende={12} />
)}
<Figur pose={noaPose} x={NOA_BLUME_X} y={BODEN + kn.y + huepfer(f, z.n1.start, 18, 10)} stauchY={atmen(f, 2) * kn.stauch} drehung={kn.dreh} blende={10} />
<Figur pose={pinoPose} x={PINO_BLUME_X} y={BODEN + kp.y + huepfer(f, z.n1.ende - 10, 26, 12)} stauchY={atmen(f, 1, 0.01) * kp.stauch * hopfStauch(f, z.n1.ende + 2)} drehung={kp.dreh} blende={10} />
</Kamera>
<Blaetterwind anzahl={4} />
<Vignette />
<Untertitel zeilen={S5.liste} />
<Blende dauer={dauer} rein={12} raus={0} />
</>
);
};

View file

@ -1,166 +0,0 @@
import React from 'react';
import {useCurrentFrame} from 'remotion';
import {kurve} from '../bewegung';
import {Figur, Geher, Kamera} from '../baukasten/figur';
import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt} from '../baukasten/helfer';
import {Blaetterwind, Blume, Laubhaufen} from '../baukasten/natur';
import {BODEN, Ort} from '../baukasten/orte';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {BLUME_X, BLUME_Y, HAUFEN_X} from './szene2';
import {LULU_BLUME_X, NOA_BLUME_X, PINO_BLUME_X} from './szene5';
// Szene 6: Zusammen geht's. Pino pflückt die Blume, Noa hält sie hoch, Lulu riecht daran.
const PINO_AN_BLUME = {x: BLUME_X - 40, y: BLUME_Y + 50};
// Lulus Nase in lulu_A1 (Augen zu, schaut nach links): Leinwand (597, 167), Fuß (714, 734)
const LULU_NASE = {x: LULU_BLUME_X - 117, y: BODEN - 6 - 567};
export const S6 = plane('s6', [
{id: 'bild0', dauer: 5.6},
{id: 'bild1', dauer: 2.6},
{id: 'l1', wer: 'LULU'},
{id: 'e1', wer: 'ERZ'},
{id: 'e2', wer: 'ERZ'},
{id: 'e3', wer: 'ERZ'},
{id: 'e4', wer: 'ERZ'},
]);
export const Szene6: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S6;
// Pino tippelt zur Blume (nach rechts), pflückt sie, tippelt zurück zu Noa
const hin = z.bild0.start + 4;
const da = hin + 80;
const pflueck = da + 6;
const zurueck = pflueck + 34;
const wieder = zurueck + 80;
// Weg vor dem Laubhaufen entlang (weiter vorn = tiefer im Bild)
const VORN = {x: HAUFEN_X + 10, y: BODEN + 70};
const mitteHin = (hin + da) / 2;
const mitteZurueck = (zurueck + wieder) / 2;
const pinoX = kurve(f, [
[hin, PINO_BLUME_X],
[mitteHin, VORN.x],
[da, PINO_AN_BLUME.x],
[zurueck, PINO_AN_BLUME.x],
[mitteZurueck, VORN.x],
[wieder, NOA_BLUME_X + 80],
]);
const pinoBoden = kurve(f, [
[hin, BODEN],
[mitteHin, VORN.y],
[da, PINO_AN_BLUME.y],
[zurueck, PINO_AN_BLUME.y],
[mitteZurueck, VORN.y],
[wieder, BODEN],
]);
const pinoGehtHin = f >= hin && f < da;
const pinoGehtZurueck = f >= zurueck && f < wieder;
const pinoPose: [number, string][] = [
[0, 'pino_A4_staunen'],
[da, 'pino_A_hinterbeine'],
[wieder, 'pino_A_hinterbeine'],
[z.l1.start, 'pino_A3_lachen'],
];
const s = 136 / 571; // Pino: Bildschirm-Pixel je Leinwand-Pixel
const pfoten = {x: pinoX + 4, y: pinoBoden - 210 * s};
// Die Blume: am Zaun → in Pinos Pfoten → Noa hockt sich hin und nimmt sie → steht auf und hält sie hoch
const hocke = wieder - 12;
const nehmen = wieder + 14;
const aufstehen = nehmen + 14;
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_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};
if (f >= nehmen)
blume = {
x: kurve(f, [
[nehmen, pfoten.x],
[nehmen + 10, HAND_HOCKE.x],
[aufstehen, HAND_HOCKE.x],
[aufstehen + BLENDE, HAND_OBEN.x],
]),
y: kurve(f, [
[nehmen, pfoten.y + 20],
[nehmen + 10, HAND_HOCKE.y + 12],
[aufstehen, HAND_HOCKE.y + 12],
[aufstehen + BLENDE, HAND_OBEN.y + 12],
]),
s: kurve(f, [
[nehmen, 0.55],
[aufstehen + BLENDE, 0.62],
]),
};
const wechsel = (t: number) => (f >= t && f < t + BLENDE ? Math.sin(((f - t) / BLENDE) * Math.PI) : 0);
const noaStauch = 1 - 0.05 * wechsel(hocke) + 0.03 * wechsel(aufstehen);
const noaPose: [number, string][] = [
[0, 'noa_A3_lachen'],
[hocke, 'noa_B_hocken'],
[aufstehen, 'noa_A_arme-hoch'],
[z.e3.start, 'noa_A3_lachen'],
];
const blumeImBild = f < z.e3.start;
// Nahaufnahme: Lulu riecht an der Blume (die Blume kommt von unten ins Bild)
const nahA = z.l1.start - 20;
const nahE = z.l1.ende + 10;
const nah = f >= nahA && f < nahE;
const luluPose: [number, string][] = [
[0, 'lulu_A4_staunen'],
[nahA, 'lulu_A1_augen-zu'],
[z.l1.start + 30, 'lulu_A3_lachen'],
];
const nahBlumeY = kurve(f, [
[nahA, LULU_NASE.y + 260],
[nahA + 24, LULU_NASE.y + 92],
]);
const kam = kamFahrt(f, [
[0, 1150, 760, 1.4],
[da, 1450, 780, 1.7],
[zurueck, 1350, 780, 1.6],
[wieder, 960, 740, 1.6],
[aufstehen + BLENDE, 960, 680, 1.5],
[nahA - 1, 960, 660, 1.5],
[nahA, LULU_NASE.x - 20, LULU_NASE.y + 60, 3.2],
[nahE - 1, LULU_NASE.x - 20, LULU_NASE.y + 60, 3.0],
[nahE, 1500, 560, 1.1],
[z.e1.start + 10, LULU_BLUME_X - 150, 420, 1.9],
[z.e2.start, PINO_BLUME_X + 80, 840, 2.0],
[z.e3.start, NOA_BLUME_X, 700, 1.8],
[z.e4.start, 1300, 600, 1.15],
[dauer, 1300, 600, 1.1],
]);
return (
<>
<Kamera {...kam}>
<Ort name="garten_herbst" />
<Laubhaufen x={HAUFEN_X} y={BODEN + 6} />
{f < pflueck && <Blume x={BLUME_X} y={BLUME_Y} s={0.8} r={Math.sin(f * 0.07) * 3} />}
<Figur pose={luluPose} x={LULU_BLUME_X} y={BODEN - 6} stauchY={atmen(f, 3, 0.006)} blende={12} />
<Figur pose={noaPose} x={NOA_BLUME_X} y={BODEN + huepfer(f, aufstehen + BLENDE, 14, 10)} stauchY={atmen(f, 2) * noaStauch * hopfStauch(f, aufstehen + BLENDE + 10)} blende={BLENDE} />
{pinoGehtHin ? (
<Geher figur="pino" spiegeln x={pinoX} y={pinoBoden} tempo={1.4} />
) : pinoGehtZurueck ? (
<Geher figur="pino" x={pinoX} y={pinoBoden} tempo={1.4} />
) : (
<Figur pose={pinoPose} x={pinoX} y={pinoBoden + huepfer(f, z.l1.ende, 24, 12)} stauchY={atmen(f, 1, 0.01)} blende={10} />
)}
{f >= pflueck && blumeImBild && !nah && <Blume x={pinoGehtZurueck ? pinoX - 10 : blume.x} y={pinoGehtZurueck ? pinoBoden - 40 : blume.y} s={blume.s} />}
{nah && <Blume x={LULU_NASE.x - 10} y={nahBlumeY} s={0.9} r={-8} />}
</Kamera>
<Blaetterwind anzahl={4} />
<Vignette />
<Untertitel zeilen={S6.liste} />
<Blende dauer={dauer} rein={12} raus={0} />
</>
);
};

View file

@ -1,79 +0,0 @@
import React from 'react';
import {useCurrentFrame} from 'remotion';
import {kurve} from '../bewegung';
import {Figur, Kamera} from '../baukasten/figur';
import {Blende, Vignette, atmen, kamFahrt} from '../baukasten/helfer';
import {Apfel, Blaetterwind, Laubhaufen, Zzz} from '../baukasten/natur';
import {BODEN, Ort} from '../baukasten/orte';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {HAUFEN_X} from './szene2';
// Szene 7: Pino ist müde. Abendlicht, die drei sitzen unter dem Apfelbaum, Pino gähnt.
// Lulu liegt links und schaut nach rechts, Noa sitzt rechts und schaut nach links, Pino in der Mitte.
const LULU_X = 600;
const PINO_X = 840;
const NOA_X = 1080;
export const S7 = plane('s7', [
{id: 'bild0', dauer: 2.6},
{id: 'p1', wer: 'PINO'},
{id: 'n1', wer: 'NOA'},
{id: 'e1', wer: 'ERZ'},
{id: 'e2', wer: 'ERZ', pause: 3},
{id: 'e3', wer: 'ERZ'},
{id: 'e4', wer: 'ERZ'},
{id: 't1', wer: 'ALLE', leise: true},
{id: 'bild1', dauer: 1.6},
]);
export const Szene7: React.FC = () => {
const f = useCurrentFrame();
const {z, dauer} = S7;
// Pino knabbert, gähnt lang (strecken), wird schläfrig, schläft am Ende ein
const gaehnA = z.p1.start;
const gaehn = kurve(f, [
[gaehnA, 0],
[gaehnA + 14, 1],
[gaehnA + 48, 1],
[gaehnA + 64, 0],
]);
const pinoPose: [number, string][] = [
[0, 'pino_A3_lachen'],
[gaehnA + 2, 'pino_A4_staunen'],
[gaehnA + 60, 'pino_A2_augen-halb'],
[z.e3.start, 'pino_A2_augen-halb'],
[z.t1.start + 10, 'pino_A1_augen-zu'],
[z.bild1.start, 'pino_B3_schlafen'],
];
const pinoStauch = atmen(f, 1, f > z.t1.start ? 0.02 : 0.01) * (1 + 0.07 * gaehn);
const kam = kamFahrt(f, [
[0, 840, 620, 1.25],
[z.p1.start, PINO_X, 760, 1.7],
[z.n1.ende, 900, 720, 1.5],
[z.e2.start, 860, 700, 1.4],
[z.t1.start, 860, 720, 1.5],
[dauer, PINO_X, 790, 2.0],
]);
return (
<>
<Kamera {...kam}>
<Ort name="garten_herbst_abend" />
<Laubhaufen x={HAUFEN_X + 470} y={BODEN + 6} />
<Figur pose="lulu_B_liegt" x={LULU_X} y={BODEN + 10} stauchY={atmen(f, 3, 0.008)} />
<Figur pose="noa_B_sitzt" x={NOA_X} y={BODEN + 10} stauchY={atmen(f, 2, 0.008)} />
<Apfel x={PINO_X + 70} y={BODEN - 6} s={0.62} r={-20} />
<Figur pose={pinoPose} x={PINO_X} y={BODEN + 4} stauchY={pinoStauch} blende={12} />
<Zzz x={PINO_X + 30} y={BODEN - 150} start={z.bild1.start} s={0.6} />
</Kamera>
<Blaetterwind anzahl={3} />
<Vignette staerke={0.45} />
<Untertitel zeilen={S7.liste} />
<Blende dauer={dauer} rein={12} raus={0} />
</>
);
};

View file

@ -1,68 +0,0 @@
{
"titel": "Zu klein, zu groß",
"lied": "Einer klein und einer groß",
"zeilen": {
"s2/e1": "Das ist Noa.",
"s2/e2": "Noa liebt den Herbst.",
"s2/e3": "Und am allermeisten liebt sie es, in Blätter zu springen.",
"s2/n1": "Hui!",
"s2/n2": "Oh! Wer bist du denn?",
"s2/e4": "Schau mal, die Stacheln sind ganz hoch aufgestellt.",
"s2/e5": "Was meinst du: Wie fühlt sich der Kleine gerade?",
"s2/e6": "Er hat ein bisschen Angst.",
"s2/e7": "Noa kam ja so plötzlich.",
"s2/n3": "Keine Angst. Ich tu dir nichts. Ich bin Noa.",
"s2/p1": "Ich bin Pino. Du bist aber groß.",
"s2/n4": "Groß? Ich bin doch noch klein. Ich bin erst fünf!",
"s2/p2": "Für mich bist du riesig.",
"s3/p1": "Hmm, ein Apfel! Der letzte vom ganzen Baum.",
"s3/p2": "Hopp! Hopp!",
"s3/p3": "Ich bin zu klein.",
"s3/n1": "Warte, ich helfe dir!",
"s3/n2": "Hmpf. Für den Apfel bin ich auch zu klein.",
"s3/e1": "Pino ist zu klein. Noa ist auch zu klein.",
"s3/e2": "Wer könnte den Apfel wohl erreichen?",
"s4/n1": "Pino, guck mal! Ein Baum mit Flecken!",
"s4/p1": "Bäume haben keine Flecken. Und Bäume laufen nicht!",
"s4/l1": "Hallo, ihr zwei da unten! Ich bin Lulu.",
"s4/e1": "Das ist Lulu, die Giraffe.",
"s4/e2": "Lulu ist sooo groß, dass ihr Kopf fast in den Wolken steckt.",
"s4/p2": "Du bist ja noch viel größer als Noa!",
"s4/l2": "Von hier oben sehe ich alles.",
"s4/l3": "Ich sehe den Zaun, das Dach und … einen roten Apfel!",
"s4/p3": "Für mich?",
"s4/l4": "Für dich.",
"s4/p4": "Danke, Lulu!",
"s5/l1": "Oh, wie schön! Eine blaue Blume.",
"s5/l2": "Die möchte ich so gern riechen.",
"s5/l3": "Uiuiui! Ich komme nicht runter.",
"s5/l4": "Ich bin zu groß!",
"s5/e1": "Lulu ist zu groß für die Blume.",
"s5/e2": "Aber wer ist ganz klein und ganz nah am Boden?",
"s5/n1": "Ja, genau! Pino!",
"s6/l1": "Mmmh! Danke, ihr zwei!",
"s6/e1": "Lulu kommt an die Sachen ganz oben.",
"s6/e2": "Pino kommt an die Sachen ganz unten.",
"s6/e3": "Und Noa ist genau in der Mitte.",
"s6/e4": "Zusammen kommen sie überall hin.",
"lied/r1": "Einer klein und einer groß,",
"lied/r2": "und dazwischen – na, wer bloß?",
"lied/r3": "Noa steht in der Mitte drin,",
"lied/r4": "zusammen kriegen wir alles hin!",
"lied/s1": "Oben hängt ein Apfel rund,",
"lied/s2": "Lulu holt ihn mit dem Mund.",
"lied/s3": "Unten blüht die Blume blau,",
"lied/s4": "Pino pflückt sie, ganz genau!",
"lied/w1": "Einer klein und einer groß,",
"lied/w2": "und dazwischen – na, wer bloß?",
"lied/w3": "Noa steht in der Mitte drin,",
"lied/w4": "zusammen kriegen wir alles hin!",
"s7/p1": "Hoaaah. Ich bin auf einmal so müde.",
"s7/n1": "Jetzt schon? Es ist doch noch gar nicht Nacht.",
"s7/e1": "Pino ist müde. Heute war ein langer Tag.",
"s7/e2": "Und du? Was kannst du schon richtig gut?",
"s7/e3": "Jeder kann etwas gut. Und jeder braucht mal Hilfe.",
"s7/e4": "Zusammen geht’s.",
"s7/t1": "Tschüss! Bis bald!"
}
}

View file

@ -1,68 +0,0 @@
{
"titel": "Demasiado pequeño, demasiado alta",
"lied": "Uno pequeño y otra alta",
"zeilen": {
"s2/e1": "Esta es Noa.",
"s2/e2": "A Noa le encanta el otoño.",
"s2/e3": "Y lo que más le gusta es saltar sobre las hojas.",
"s2/n1": "¡Yupiii!",
"s2/n2": "¡Oh! ¿Y tú quién eres?",
"s2/e4": "Mira, tiene todas las púas levantadas.",
"s2/e5": "¿Tú qué crees? ¿Cómo se siente el pequeño?",
"s2/e6": "Tiene un poco de miedo.",
"s2/e7": "Noa llegó muy de repente.",
"s2/n3": "No tengas miedo. No te voy a hacer nada. Soy Noa.",
"s2/p1": "Yo soy Pino. ¡Qué grande eres!",
"s2/n4": "¿Grande? Si yo todavía soy pequeña. ¡Solo tengo cinco años!",
"s2/p2": "Para mí eres enorme.",
"s3/p1": "¡Mmm, una manzana! La última de todo el árbol.",
"s3/p2": "¡Hop! ¡Hop!",
"s3/p3": "Soy demasiado pequeño.",
"s3/n1": "¡Espera, yo te ayudo!",
"s3/n2": "Uf. Para la manzana yo también soy demasiado pequeña.",
"s3/e1": "Pino es demasiado pequeño. Noa también es demasiado pequeña.",
"s3/e2": "¿Quién crees que podría alcanzar la manzana?",
"s4/n1": "¡Pino, mira! ¡Un árbol con manchas!",
"s4/p1": "Los árboles no tienen manchas. ¡Y los árboles no caminan!",
"s4/l1": "¡Hola a los dos, ahí abajo! Soy Lulu.",
"s4/e1": "Esta es Lulu, la jirafa.",
"s4/e2": "Lulu es taaan alta que su cabeza casi toca las nubes.",
"s4/p2": "¡Eres todavía más grande que Noa!",
"s4/l2": "Desde aquí arriba lo veo todo.",
"s4/l3": "Veo la valla, el tejado y… ¡una manzana roja!",
"s4/p3": "¿Para mí?",
"s4/l4": "Para ti.",
"s4/p4": "¡Gracias, Lulu!",
"s5/l1": "¡Oh, qué bonita! Una flor azul.",
"s5/l2": "Me encantaría olerla.",
"s5/l3": "¡Uy, uy, uy! No llego abajo.",
"s5/l4": "¡Soy demasiado alta!",
"s5/e1": "Lulu es demasiado alta para la flor.",
"s5/e2": "Pero ¿quién es muy pequeñito y está muy cerca del suelo?",
"s5/n1": "¡Sí, eso es! ¡Pino!",
"s6/l1": "¡Mmm! ¡Gracias a los dos!",
"s6/e1": "Lulu llega a las cosas que están muy arriba.",
"s6/e2": "Pino llega a las cosas que están muy abajo.",
"s6/e3": "Y Noa está justo en medio.",
"s6/e4": "Juntos llegan a todas partes.",
"lied/r1": "Uno pequeño y otra alta,",
"lied/r2": "y en el medio… ¿quién nos falta?",
"lied/r3": "Noa, Noa, en el centro está,",
"lied/r4": "¡y juntos sí se podrá!",
"lied/s1": "Muy arriba, una manzana –",
"lied/s2": "Lulu la alcanza con ganas.",
"lied/s3": "Muy abajo, una flor azul –",
"lied/s4": "Pino la encuentra. ¿Y la ves tú?",
"lied/w1": "Uno pequeño y otra alta,",
"lied/w2": "y en el medio… ¿quién nos falta?",
"lied/w3": "Noa, Noa, en el centro está,",
"lied/w4": "¡y juntos sí se podrá!",
"s7/p1": "Uaaah. De repente tengo muchísimo sueño.",
"s7/n1": "¿Ya? Pero si todavía no es de noche.",
"s7/e1": "Pino tiene sueño. Hoy ha sido un día largo.",
"s7/e2": "¿Y tú? ¿Qué sabes hacer ya muy bien?",
"s7/e3": "Todos sabemos hacer algo bien. Y todos necesitamos ayuda a veces.",
"s7/e4": "Juntos, sí se puede.",
"s7/t1": "¡Adiós! ¡Hasta pronto!"
}
}

View file

@ -1,68 +0,0 @@
{
"titel": "Trop petit, trop grande",
"lied": "Un tout petit, une très grande",
"zeilen": {
"s2/e1": "Voici Noa.",
"s2/e2": "Noa adore l'automne.",
"s2/e3": "Et plus que tout, elle adore sauter dans les feuilles.",
"s2/n1": "Youpiii !",
"s2/n2": "Oh ! Mais qui es-tu ?",
"s2/e4": "Regarde, ses piquants sont tout dressés.",
"s2/e5": "Qu'est-ce que tu en penses : comment se sent le petit, là ?",
"s2/e6": "Il a un peu peur.",
"s2/e7": "Noa est arrivée si vite.",
"s2/n3": "N'aie pas peur. Je ne vais pas te faire de mal. Je m'appelle Noa.",
"s2/p1": "Moi, c'est Pino. Tu es vraiment grande.",
"s2/n4": "Grande ? Mais je suis encore petite. J'ai seulement cinq ans !",
"s2/p2": "Pour moi, tu es immense.",
"s3/p1": "Miam, une pomme ! La toute dernière de l'arbre.",
"s3/p2": "Hop ! Hop !",
"s3/p3": "Je suis trop petit.",
"s3/n1": "Attends, je vais t'aider !",
"s3/n2": "Pff. Pour la pomme, moi aussi je suis trop petite.",
"s3/e1": "Pino est trop petit. Noa aussi est trop petite.",
"s3/e2": "À ton avis, qui pourrait attraper la pomme ?",
"s4/n1": "Pino, regarde ! Un arbre à taches !",
"s4/p1": "Les arbres n'ont pas de taches. Et les arbres ne marchent pas !",
"s4/l1": "Bonjour, vous deux, là en bas ! Moi, c'est Lulu.",
"s4/e1": "Voici Lulu, la girafe.",
"s4/e2": "Lulu est si grande que sa tête touche presque les nuages.",
"s4/p2": "Tu es encore plus grande que Noa !",
"s4/l2": "De là-haut, je vois tout.",
"s4/l3": "Je vois la clôture, le toit et… une pomme rouge !",
"s4/p3": "Pour moi ?",
"s4/l4": "Pour toi.",
"s4/p4": "Merci, Lulu !",
"s5/l1": "Oh, qu'elle est jolie ! Une fleur bleue.",
"s5/l2": "J'aimerais tellement la sentir.",
"s5/l3": "Oh là là là ! Je n'arrive pas jusqu'en bas.",
"s5/l4": "Je suis trop grande !",
"s5/e1": "Lulu est trop grande pour la fleur.",
"s5/e2": "Mais qui est tout petit et tout près du sol ?",
"s5/n1": "Oui, c'est ça ! Pino !",
"s6/l1": "Mmmh ! Merci, vous deux !",
"s6/e1": "Lulu attrape les choses tout en haut.",
"s6/e2": "Pino attrape les choses tout en bas.",
"s6/e3": "Et Noa est pile au milieu.",
"s6/e4": "Ensemble, ils arrivent à tout attraper.",
"lied/r1": "Un tout petit, une très grande,",
"lied/r2": "Noa au milieu de la bande.",
"lied/r3": "En haut, en bas, et entre les deux –",
"lied/r4": "ensemble, on fait de notre mieux !",
"lied/s1": "Tout en haut, une pomme jolie,",
"lied/s2": "Lulu l'attrape, Pino sourit !",
"lied/s3": "Tout en bas, une fleur bleue,",
"lied/s4": "Pino la cueille, il est heureux !",
"lied/w1": "Un tout petit, une très grande,",
"lied/w2": "Noa au milieu de la bande.",
"lied/w3": "En haut, en bas, et entre les deux –",
"lied/w4": "ensemble, on fait de notre mieux !",
"s7/p1": "Ouaaah. Tout à coup, j'ai tellement sommeil.",
"s7/n1": "Déjà ? Mais il ne fait pas encore nuit.",
"s7/e1": "Pino a sommeil. La journée a été longue.",
"s7/e2": "Et toi ? Qu'est-ce que tu sais déjà très bien faire ?",
"s7/e3": "Chacun sait bien faire quelque chose. Et chacun a parfois besoin d'aide.",
"s7/e4": "Ensemble, on y arrive.",
"s7/t1": "Au revoir ! À bientôt !"
}
}

View file

@ -1,108 +0,0 @@
import React from 'react';
import {AbsoluteFill, random} from 'remotion';
import {Figur} from '../baukasten/figur';
import {Blatt} from '../baukasten/natur';
import {W} from '../baukasten/sprache';
import {SCHRIFT} from '../baukasten/untertitel';
// Profilbild (800 × 800) und Kanalbanner (2560 × 1440) für YouTube, je Sprache:
// npx remotion still src/index.ts Kanal-Profilbild ../veroeffentlichung/kanalbilder/profilbild.png
// npx remotion still src/index.ts Kanal-Banner ../veroeffentlichung/kanalbilder/banner-de.jpg --image-format=jpeg --jpeg-quality=92
// … --props='{"sprache":"es"}'
// Farben wie der Buchdeckel im Intro. Alles Wichtige im Banner liegt im
// sicheren Bereich (1546 × 423 in der Mitte), den YouTube auf allen Geräten zeigt.
const HIMMEL = 'linear-gradient(180deg, #fbf4e4 0%, #f8e7bf 55%, #f3d79a 100%)';
// Sanfte Hügel als SVG; `boden` ist die Linie, auf der die Figuren stehen.
const Huegel: React.FC<{b: number; h: number; boden: number}> = ({b, h, boden}) => (
<svg viewBox={`0 0 ${b} ${h}`} style={{position: 'absolute', inset: 0, width: b, height: h}}>
<path d={`M 0 ${boden - 150} C ${b * 0.2} ${boden - 230}, ${b * 0.42} ${boden - 120}, ${b * 0.6} ${boden - 170} S ${b * 0.9} ${boden - 230}, ${b} ${boden - 160} L ${b} ${h} L 0 ${h} Z`} fill="#b7cf8f" />
<path d={`M 0 ${boden - 40} C ${b * 0.25} ${boden - 90}, ${b * 0.5} ${boden - 10}, ${b * 0.72} ${boden - 50} S ${b * 0.95} ${boden - 70}, ${b} ${boden - 30} L ${b} ${h} L 0 ${h} Z`} fill="#8fb36b" />
<rect x={0} y={boden + 40} width={b} height={h - boden} fill="#7aa35a" opacity={0.6} />
</svg>
);
const Sonne: React.FC<{x: number; y: number; r: number}> = ({x, y, r}) => (
<div style={{position: 'absolute', left: x - r * 2, top: y - r * 2, width: r * 4, height: r * 4, borderRadius: '50%', background: 'radial-gradient(closest-side, rgba(255,226,140,0.95) 0%, rgba(255,226,140,0.95) 45%, rgba(255,226,140,0) 100%)'}} />
);
const Wolke: React.FC<{x: number; y: number; s: number}> = ({x, y, s}) => (
<div style={{position: 'absolute', left: x, top: y, transform: `scale(${s})`, transformOrigin: '0 0'}}>
{[
[0, 30, 90],
[60, 0, 120],
[150, 25, 95],
].map(([dx, dy, d], i) => (
<div key={i} style={{position: 'absolute', left: dx, top: dy, width: d, height: d, borderRadius: '50%', background: 'rgba(255,255,255,0.85)'}} />
))}
</div>
);
// Kleine Blumen auf der Wiese
const Blumen: React.FC<{anzahl: number; b: number; von: number; bis: number; s: number}> = ({anzahl, b, von, bis, s}) => (
<>
{new Array(anzahl).fill(0).map((_, i) => {
const x = random(`bx${i}`) * b;
const y = von + random(`by${i}`) * (bis - von);
const farbe = ['#f6f1e4', '#e9c97a', '#d98a8a', '#c8a2d6'][i % 4];
return (
<svg key={i} width={30 * s} height={30 * s} viewBox="0 0 30 30" style={{position: 'absolute', left: x, top: y}}>
{[0, 72, 144, 216, 288].map((w) => (
<ellipse key={w} cx={15} cy={8} rx={5} ry={7} fill={farbe} transform={`rotate(${w} 15 15)`} />
))}
<circle cx={15} cy={15} r={4} fill="#e0a020" />
</svg>
);
})}
</>
);
export const Profilbild: React.FC = () => (
<AbsoluteFill style={{background: 'radial-gradient(circle at 50% 42%, #fbf4e4 0%, #f6dc95 62%, #e9c97a 100%)'}}>
<div style={{position: 'absolute', left: 120, top: 560, width: 560, height: 200, borderRadius: '50%', background: 'radial-gradient(closest-side, #8fb36b 0%, #8fb36b 70%, rgba(143,179,107,0) 100%)'}} />
<Blatt x={170} y={190} r={30} s={1.6} farbe="#c8642c" />
<Blatt x={640} y={420} r={-40} s={1.3} farbe="#d99a2b" />
<Figur pose="pino_A3_lachen" x={400} y={680} hoehe={560} />
</AbsoluteFill>
);
const BB = 2560;
const BH = 1440;
const SICHER = {x: (BB - 1546) / 2, y: (BH - 423) / 2, b: 1546, h: 423};
const BODEN = SICHER.y + SICHER.h - 14;
export const Banner: React.FC = () => {
const titelGroesse = Math.min(124, 1080 / (W.kanal.length * 0.58));
return (
<AbsoluteFill style={{background: HIMMEL}}>
<Sonne x={420} y={300} r={110} />
<Wolke x={900} y={180} s={1.3} />
<Wolke x={1900} y={260} s={1} />
<Wolke x={150} y={620} s={0.8} />
<Huegel b={BB} h={BH} boden={BODEN} />
<Blumen anzahl={46} b={BB} von={BODEN + 20} bis={BH - 40} s={1.4} />
{[0, 1, 2, 3, 4, 5].map((i) => (
<Blatt key={i} x={140 + i * 430 + random(`lx${i}`) * 120} y={1100 + random(`ly${i}`) * 220} r={i * 53} s={1.3} farbe={['#c8642c', '#d99a2b', '#a8452a'][i % 3]} />
))}
{/* Text links im sicheren Bereich */}
<div style={{position: 'absolute', left: SICHER.x + 30, top: SICHER.y + 40, width: 1080, textAlign: 'center', fontFamily: SCHRIFT}}>
<div style={{fontSize: titelGroesse, fontWeight: 600, color: '#c8642c', lineHeight: 1.05, whiteSpace: 'nowrap'}}>{W.kanal}</div>
<div style={{fontSize: 46, fontWeight: 500, color: '#3f5a8a', marginTop: 22}}>{W.kanalUntertitel}</div>
<div style={{fontSize: 38, fontWeight: 500, color: '#5f8a4a', marginTop: 10}}>{W.kanalRhythmus}</div>
</div>
{/* Figuren rechts im sicheren Bereich, Pino vorn */}
<Figur pose="lulu_A3_lachen" x={SICHER.x + SICHER.b - 150} y={BODEN} hoehe={395} />
<Figur pose="noa_A3_lachen" x={SICHER.x + SICHER.b - 330} y={BODEN} hoehe={265} />
<Figur pose="pino_A3_lachen" x={SICHER.x + SICHER.b - 445} y={BODEN + 4} hoehe={150} />
</AbsoluteFill>
);
};
// Zum Prüfen: Banner mit eingezeichnetem sicheren Bereich
export const BannerRahmen: React.FC = () => (
<AbsoluteFill>
<Banner />
<div style={{position: 'absolute', left: SICHER.x, top: SICHER.y, width: SICHER.b, height: SICHER.h, border: '4px dashed rgba(200,40,40,0.8)'}} />
</AbsoluteFill>
);

View file

@ -1,6 +1,6 @@
# Kanal: Beschreibung und Schlagwörter # Kanal: Beschreibung und Schlagwörter
Entwurf vom 26.10.2026, zum Einfügen in YouTube Studio (Anpassung → Basisinfo, je Sprache über „Sprache hinzufügen“). Kanalname seit 02.11.2026: „Pinos kleine Welt“ (DE); ES „El pequeño mundo de Pino“ und FR „Le petit monde de Pino“ (alle drei entschieden 02.11.2026). ES und FR am 26.10. per Rückübersetzung geprüft, hier keine Korrektur nötig. Am 02.11. angepasst: Hinweis auf die anderen Sprachkanäle statt Audiospur, KI-Absatz gestrichen. DE ist so im Kanal „Pinos kleine Welt“ veröffentlicht (02.11.2026, mit Land Deutschland und den Schlagwörtern unten plus „Pinos kleine Welt“). Entwurf vom 26.10.2026, zum Einfügen in YouTube Studio (Anpassung → Basisinfo, je Sprache über „Sprache hinzufügen“). Kanalname seit 02.11.2026: „Pinos kleine Welt“ (DE); ES „El pequeño mundo de Pino“ und FR „Le petit monde de Pino“ (alle drei entschieden 02.11.2026). ES und FR am 26.10. per Rückübersetzung geprüft, hier keine Korrektur nötig.
## Deutsch ## Deutsch
@ -11,7 +11,9 @@ Pino ist ein kleiner Igel, Lulu eine große Giraffe, und Noa ist mittendrin. Jed
Im Dezember: Pinos Adventskalender. Jeden Tag ein Türchen, vom 1. bis 24. Dezember. Im Dezember: Pinos Adventskalender. Jeden Tag ein Türchen, vom 1. bis 24. Dezember.
Neue Folgen jeden Sonntag. Alle Folgen gibt es auch auf Spanisch („El pequeño mundo de Pino“) und Französisch („Le petit monde de Pino“). Neue Folgen jeden Sonntag. Alle Folgen gibt es auch auf Spanisch und Französisch (Audiospur wechseln).
Für Eltern: Die Geschichten und Figuren sind selbst ausgedacht und geschrieben. Die Bilder werden mit KI-Werkzeugen animiert, von uns ausgewählt und geschnitten.
``` ```
**Schlagwörter:** Kindergeschichten, Gute-Nacht-Geschichte, Geschichten für Kinder ab 3, Vorschule, Adventskalender für Kinder, Igel, Giraffe, Gefühle für Kinder, Zählen lernen, Bilderbuch, Kinderserie, ruhige Kindervideos, Winterschlaf, Mitmachgeschichte **Schlagwörter:** Kindergeschichten, Gute-Nacht-Geschichte, Geschichten für Kinder ab 3, Vorschule, Adventskalender für Kinder, Igel, Giraffe, Gefühle für Kinder, Zählen lernen, Bilderbuch, Kinderserie, ruhige Kindervideos, Winterschlaf, Mitmachgeschichte
@ -25,7 +27,9 @@ Pino es un erizo pequeño, Lulu una jirafa muy alta y Noa está en medio. Cada d
En diciembre: el calendario de Adviento de Pino. Una ventanita cada día, del 1 al 24 de diciembre. En diciembre: el calendario de Adviento de Pino. Una ventanita cada día, del 1 al 24 de diciembre.
Nuevos episodios cada domingo. Todos los episodios están también en alemán («Pinos kleine Welt») y en francés («Le petit monde de Pino»). Nuevos episodios cada domingo. Todos los episodios están también en alemán y francés (cambia la pista de audio).
Para las familias: las historias y los personajes son nuestros, inventados y escritos por nosotros. Las imágenes se animan con herramientas de IA, y nosotros las elegimos y montamos.
``` ```
**Etiquetas:** cuentos para niños, cuentos para dormir, cuentos infantiles, preescolar, calendario de Adviento para niños, erizo, jirafa, emociones para niños, aprender a contar, libro ilustrado, serie infantil, videos tranquilos para niños, hibernación **Etiquetas:** cuentos para niños, cuentos para dormir, cuentos infantiles, preescolar, calendario de Adviento para niños, erizo, jirafa, emociones para niños, aprender a contar, libro ilustrado, serie infantil, videos tranquilos para niños, hibernación
@ -39,14 +43,16 @@ Pino est un petit hérisson, Lulu une très grande girafe, et Noa est au milieu.
En décembre : le calendrier de l'Avent de Pino. Une petite porte chaque jour, du 1er au 24 décembre. En décembre : le calendrier de l'Avent de Pino. Une petite porte chaque jour, du 1er au 24 décembre.
Nouveaux épisodes chaque dimanche. Tous les épisodes existent aussi en allemand (« Pinos kleine Welt ») et en espagnol (« El pequeño mundo de Pino »). Nouveaux épisodes chaque dimanche. Tous les épisodes existent aussi en allemand et en espagnol (changer la piste audio).
Pour les parents : les histoires et les personnages sont inventés et écrits par nous. Les images sont animées avec des outils d'IA, puis choisies et montées par nous.
``` ```
**Mots-clés :** histoires pour enfants, histoire du soir, contes pour enfants, maternelle, calendrier de l'Avent pour enfants, hérisson, girafe, émotions pour enfants, apprendre à compter, album illustré, série pour enfants, vidéos calmes pour enfants, hibernation **Mots-clés :** histoires pour enfants, histoire du soir, contes pour enfants, maternelle, calendrier de l'Avent pour enfants, hérisson, girafe, émotions pour enfants, apprendre à compter, album illustré, série pour enfants, vidéos calmes pour enfants, hibernation
## KI-Absatz (gestrichen am 02.11.2026) ## Warum der KI-Satz drinsteht
Bis 02.11. stand am Ende ein Absatz für Eltern, dass Geschichten und Figuren selbst gemacht sind und die Bilder mit KI-Werkzeugen entstehen. Auf Wunsch gestrichen. Pflicht ist er für gezeichnete Figuren nicht; die Kennzeichnung „veränderte oder synthetische Inhalte“ gilt in YouTube Studio je Video nur für realistisch wirkende Inhalte. YouTube prüft seit 2025/2026 ganze Kanäle auf „inauthentic content“. Ein offener Satz, dass Geschichten und Figuren selbst gemacht sind und KI nur das Werkzeug ist, zeigt die menschliche Handschrift und schafft Vertrauen bei Eltern. Wenn du ihn nicht willst, streich den letzten Absatz; Pflicht ist er für gezeichnete Figuren nicht.
## Elternhinweis für jede Videobeschreibung ## Elternhinweis für jede Videobeschreibung

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 747 KiB