+ );
+};
+
+// Untertitel der Folgen, nach oben versetzt (unten liegen im Shorts-Player Titel und Knöpfe)
+export const HochUntertitel: React.FC<{zeilen: Geplant[]}> = ({zeilen}) => (
+
+
+
+);
+
+// Runde Bildkarte, die bei `ab` aufploppt (Bildschirm-Koordinaten des Hochformats)
+export const BildKarte: React.FC<{x: number; y: number; ab: number; r?: number; aus?: number; children: React.ReactNode}> = ({x, y, ab, r = 150, aus = 1e9, children}) => {
+ const f = useCurrentFrame();
+ const {fps} = useVideoConfig();
+ const s = spring({frame: f - ab, fps, config: {damping: 11}});
+ const weg = interpolate(f, [aus, aus + 10], [1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
+ if (f < ab) return null;
+ return (
+
+ {children}
+
+ );
+};
+
+// Großes Wort zum Mitsagen (klein – mittel – groß)
+// `puls`: Zeitpunkte, an denen das Wort noch einmal kurz groß wird (beim Mitsagen)
+export const Wort: React.FC<{text: string; x: number; y: number; ab: number; puls?: number[]; groesse?: number; farbe?: string}> = ({text, x, y, ab, puls = [], groesse = 90, farbe = '#c8502c'}) => {
+ const f = useCurrentFrame();
+ const {fps} = useVideoConfig();
+ if (f < ab) return null;
+ const pulsen = puls.reduce((m, p) => m + 0.35 * Math.max(0, Math.sin(Math.PI * Math.min(1, Math.max(0, (f - p) / 18)))), 0);
+ const s = spring({frame: f - ab, fps, config: {damping: 10}}) + pulsen;
+ return (
+
+ {text}
+
+ );
+};
+
+// `hintergrund`: füllt oben und unten, wenn die Bühne das Hochformat nicht ganz ausfüllt
+export const Hochformat: React.FC<{hintergrund?: string; children: React.ReactNode}> = ({hintergrund = '#f4ead8', children}) => (
+ {children}
+);
diff --git a/remotion/src/folge90/igel.tsx b/remotion/src/folge90/igel.tsx
new file mode 100644
index 0000000..0b8448f
--- /dev/null
+++ b/remotion/src/folge90/igel.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import {useCurrentFrame} from 'remotion';
+import {Figur} from '../baukasten/figur';
+import {Blende, atmen, huepfer} from '../baukasten/helfer';
+import {Blaetterwind} from '../baukasten/natur';
+import {Ort} from '../baukasten/orte';
+import {ApfelBild, Kaefer, Milch, Nein, Schnecke, Wurm} from './bilder';
+import {BildKarte, Buehne, FrageKarte, HochUntertitel, Hochformat} from './hochformat';
+import {TEXTE, plane} from './plan';
+
+// Lern-Short „Was frisst ein Igel?“: Pino groß in der Mitte, darüber ploppen Käfer, Wurm und Schnecke
+// auf; Milch und Obst sind nichts für Igel (sanft durchgestrichen). Passt zum Elternhinweis aus Folge 1.
+
+export const S_IGEL = plane('igel', [
+ {id: 'bild0', dauer: 1.0},
+ {id: 'e1', wer: 'ERZ', pause: 3},
+ {id: 'p1', wer: 'PINO'},
+ {id: 'e2', wer: 'ERZ', pause: 0.3},
+ {id: 'e3', wer: 'ERZ', pause: 0.3},
+ {id: 'e4', wer: 'ERZ'},
+ {id: 'p2', wer: 'PINO'},
+ {id: 'e5', wer: 'ERZ'},
+ {id: 'p3', wer: 'PINO'},
+ {id: 'bild1', dauer: 1.2},
+]);
+
+const PINO = {x: 960, y: 790, hoehe: 300};
+
+export const Igel: React.FC = () => {
+ const f = useCurrentFrame();
+ const {z, dauer} = S_IGEL;
+ const hops = huepfer(f, z.p1.start, 16, 10) + huepfer(f, z.p2.start, 16, 10) + huepfer(f, z.p3.start, 16, 10);
+ return (
+
+
+
+
+
+
+
+ {/* Das fressen Igel */}
+
+
+
+
+
+
+
+
+
+ {/* Das lieber nicht */}
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/remotion/src/folge90/plan.ts b/remotion/src/folge90/plan.ts
new file mode 100644
index 0000000..b77fb8e
--- /dev/null
+++ b/remotion/src/folge90/plan.ts
@@ -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/.json (Schlüssel „szene/zeile“),
+// die Szenen enthalten nur Ablauf und Bewegung. Aufnahmen je Sprache in
+// stimmen/.json bzw. public/stimmen/folge90// (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/folge90/${SPRACHE}`},
+ );
diff --git a/remotion/src/folge90/stimmen/de.json b/remotion/src/folge90/stimmen/de.json
new file mode 100644
index 0000000..78e453d
--- /dev/null
+++ b/remotion/src/folge90/stimmen/de.json
@@ -0,0 +1,77 @@
+{
+ "igel/e1": {
+ "datei": "igel-e1.mp3",
+ "dauer": 3,
+ "text": "Was frisst ein Igel? Was meinst du?"
+ },
+ "igel/p1": {
+ "datei": "igel-p1.mp3",
+ "dauer": 1.65,
+ "text": "Ich verrate es dir!"
+ },
+ "igel/e2": {
+ "datei": "igel-e2.mp3",
+ "dauer": 2.04,
+ "text": "Igel fressen Käfer,"
+ },
+ "igel/e3": {
+ "datei": "igel-e3.mp3",
+ "dauer": 1.07,
+ "text": "Würmer"
+ },
+ "igel/e4": {
+ "datei": "igel-e4.mp3",
+ "dauer": 1.49,
+ "text": "und Schnecken."
+ },
+ "igel/p2": {
+ "datei": "igel-p2.mp3",
+ "dauer": 2.12,
+ "text": "Mmmh, lecker!"
+ },
+ "igel/e5": {
+ "datei": "igel-e5.mp3",
+ "dauer": 4.91,
+ "text": "Milch und Obst sind nichts für Igel. Davon bekommen sie Bauchweh."
+ },
+ "igel/p3": {
+ "datei": "igel-p3.mp3",
+ "dauer": 2.27,
+ "text": "Den Apfel schenke ich Noa!"
+ },
+ "giraffe/e1": {
+ "datei": "giraffe-e1.mp3",
+ "dauer": 4.36,
+ "text": "Wer ist am größten? Pino, Noa oder Lulu?"
+ },
+ "giraffe/e2": {
+ "datei": "giraffe-e2.mp3",
+ "dauer": 1.72,
+ "text": "Pino ist klein."
+ },
+ "giraffe/e3": {
+ "datei": "giraffe-e3.mp3",
+ "dauer": 1.8,
+ "text": "Noa ist mittel."
+ },
+ "giraffe/e4": {
+ "datei": "giraffe-e4.mp3",
+ "dauer": 2.69,
+ "text": "Und Lulu ist ganz groß!"
+ },
+ "giraffe/l1": {
+ "datei": "giraffe-l1.mp3",
+ "dauer": 1.96,
+ "text": "Ich bin eine Giraffe!"
+ },
+ "giraffe/e5": {
+ "datei": "giraffe-e5.mp3",
+ "dauer": 5.88,
+ "text": "Kein Tier auf der Welt ist so hoch wie eine Giraffe. So hoch wie ein Haus!"
+ },
+ "giraffe/e6": {
+ "datei": "giraffe-e6.mp3",
+ "dauer": 3.47,
+ "text": "Sag mit: klein, mittel, groß!"
+ }
+}
diff --git a/remotion/src/folge90/stimmen/es.json b/remotion/src/folge90/stimmen/es.json
new file mode 100644
index 0000000..5445a57
--- /dev/null
+++ b/remotion/src/folge90/stimmen/es.json
@@ -0,0 +1,77 @@
+{
+ "igel/e1": {
+ "datei": "igel-e1.mp3",
+ "dauer": 2.85,
+ "text": "¿Qué come un erizo? ¿Tú qué crees?"
+ },
+ "igel/p1": {
+ "datei": "igel-p1.mp3",
+ "dauer": 1.15,
+ "text": "¡Te lo cuento!"
+ },
+ "igel/e2": {
+ "datei": "igel-e2.mp3",
+ "dauer": 2.77,
+ "text": "Los erizos comen escarabajos,"
+ },
+ "igel/e3": {
+ "datei": "igel-e3.mp3",
+ "dauer": 1.23,
+ "text": "gusanos"
+ },
+ "igel/e4": {
+ "datei": "igel-e4.mp3",
+ "dauer": 1.41,
+ "text": "y caracoles."
+ },
+ "igel/p2": {
+ "datei": "igel-p2.mp3",
+ "dauer": 2.12,
+ "text": "¡Mmm, qué rico!"
+ },
+ "igel/e5": {
+ "datei": "igel-e5.mp3",
+ "dauer": 4.91,
+ "text": "La leche y la fruta no son para erizos. Les duele la tripa."
+ },
+ "igel/p3": {
+ "datei": "igel-p3.mp3",
+ "dauer": 2.51,
+ "text": "¡La manzana se la regalo a Noa!"
+ },
+ "giraffe/e1": {
+ "datei": "giraffe-e1.mp3",
+ "dauer": 3.97,
+ "text": "¿Quién es el más alto? ¿Pino, Noa o Lulu?"
+ },
+ "giraffe/e2": {
+ "datei": "giraffe-e2.mp3",
+ "dauer": 1.72,
+ "text": "Pino es pequeño."
+ },
+ "giraffe/e3": {
+ "datei": "giraffe-e3.mp3",
+ "dauer": 1.8,
+ "text": "Noa es mediana."
+ },
+ "giraffe/e4": {
+ "datei": "giraffe-e4.mp3",
+ "dauer": 2.51,
+ "text": "¡Y Lulu es muy alta!"
+ },
+ "giraffe/l1": {
+ "datei": "giraffe-l1.mp3",
+ "dauer": 1.96,
+ "text": "¡Soy una jirafa!"
+ },
+ "giraffe/e5": {
+ "datei": "giraffe-e5.mp3",
+ "dauer": 5.88,
+ "text": "Ningún animal del mundo es tan alto como una jirafa. ¡Tan alto como una casa!"
+ },
+ "giraffe/e6": {
+ "datei": "giraffe-e6.mp3",
+ "dauer": 4.36,
+ "text": "Dilo conmigo: pequeño, mediano, grande."
+ }
+}
diff --git a/remotion/src/folge90/stimmen/fr.json b/remotion/src/folge90/stimmen/fr.json
new file mode 100644
index 0000000..1843b00
--- /dev/null
+++ b/remotion/src/folge90/stimmen/fr.json
@@ -0,0 +1,77 @@
+{
+ "igel/e1": {
+ "datei": "igel-e1.mp3",
+ "dauer": 2.59,
+ "text": "Que mange un hérisson ? Qu'est-ce que tu crois ?"
+ },
+ "igel/p1": {
+ "datei": "igel-p1.mp3",
+ "dauer": 1.57,
+ "text": "Je vais te le dire !"
+ },
+ "igel/e2": {
+ "datei": "igel-e2.mp3",
+ "dauer": 2.59,
+ "text": "Les hérissons mangent des scarabées,"
+ },
+ "igel/e3": {
+ "datei": "igel-e3.mp3",
+ "dauer": 1.8,
+ "text": "des vers de terre"
+ },
+ "igel/e4": {
+ "datei": "igel-e4.mp3",
+ "dauer": 1.49,
+ "text": "et des escargots."
+ },
+ "igel/p2": {
+ "datei": "igel-p2.mp3",
+ "dauer": 1.96,
+ "text": "Miam, c'est bon !"
+ },
+ "igel/e5": {
+ "datei": "igel-e5.mp3",
+ "dauer": 4.83,
+ "text": "Le lait et les fruits, ce n'est pas pour les hérissons. Ça leur donne mal au ventre."
+ },
+ "igel/p3": {
+ "datei": "igel-p3.mp3",
+ "dauer": 2.27,
+ "text": "La pomme, je la donne à Noa !"
+ },
+ "giraffe/e1": {
+ "datei": "giraffe-e1.mp3",
+ "dauer": 3.79,
+ "text": "Qui est le plus grand ? Pino, Noa ou Lulu ?"
+ },
+ "giraffe/e2": {
+ "datei": "giraffe-e2.mp3",
+ "dauer": 1.65,
+ "text": "Pino est petit."
+ },
+ "giraffe/e3": {
+ "datei": "giraffe-e3.mp3",
+ "dauer": 1.72,
+ "text": "Noa est moyenne."
+ },
+ "giraffe/e4": {
+ "datei": "giraffe-e4.mp3",
+ "dauer": 2.43,
+ "text": "Et Lulu est très grande !"
+ },
+ "giraffe/l1": {
+ "datei": "giraffe-l1.mp3",
+ "dauer": 1.8,
+ "text": "Je suis une girafe !"
+ },
+ "giraffe/e5": {
+ "datei": "giraffe-e5.mp3",
+ "dauer": 5.25,
+ "text": "Aucun animal au monde n'est aussi haut qu'une girafe. Aussi haut qu'une maison !"
+ },
+ "giraffe/e6": {
+ "datei": "giraffe-e6.mp3",
+ "dauer": 3.63,
+ "text": "Dis avec moi : petit, moyen, grand !"
+ }
+}
diff --git a/remotion/src/folge90/texte/de.json b/remotion/src/folge90/texte/de.json
new file mode 100644
index 0000000..4c56bd3
--- /dev/null
+++ b/remotion/src/folge90/texte/de.json
@@ -0,0 +1,26 @@
+{
+ "titel": "Pinos Wissen",
+ "zeilen": {
+ "igel/e1": "Was frisst ein Igel? Was meinst du?",
+ "igel/p1": "Ich verrate es dir!",
+ "igel/e2": "Igel fressen Käfer,",
+ "igel/e3": "Würmer",
+ "igel/e4": "und Schnecken.",
+ "igel/p2": "Mmmh, lecker!",
+ "igel/e5": "Milch und Obst sind nichts für Igel. Davon bekommen sie Bauchweh.",
+ "igel/p3": "Den Apfel schenke ich Noa!",
+ "giraffe/e1": "Wer ist am größten? Pino, Noa oder Lulu?",
+ "giraffe/e2": "Pino ist klein.",
+ "giraffe/e3": "Noa ist mittel.",
+ "giraffe/e4": "Und Lulu ist ganz groß!",
+ "giraffe/l1": "Ich bin eine Giraffe!",
+ "giraffe/e5": "Kein Tier auf der Welt ist so hoch wie eine Giraffe. So hoch wie ein Haus!",
+ "giraffe/e6": "Sag mit: klein, mittel, groß!",
+ "igel/frage": "Was frisst ein Igel?",
+ "giraffe/frage": "Wer ist am größten?",
+ "giraffe/w1": "klein",
+ "giraffe/w2": "mittel",
+ "giraffe/w3": "groß",
+ "kanal": "Pinos kleine Welt"
+ }
+}
\ No newline at end of file
diff --git a/remotion/src/folge90/texte/es.json b/remotion/src/folge90/texte/es.json
new file mode 100644
index 0000000..d0eaa57
--- /dev/null
+++ b/remotion/src/folge90/texte/es.json
@@ -0,0 +1,26 @@
+{
+ "titel": "Pino sabe",
+ "zeilen": {
+ "igel/e1": "¿Qué come un erizo? ¿Tú qué crees?",
+ "igel/p1": "¡Te lo cuento!",
+ "igel/e2": "Los erizos comen escarabajos,",
+ "igel/e3": "gusanos",
+ "igel/e4": "y caracoles.",
+ "igel/p2": "¡Mmm, qué rico!",
+ "igel/e5": "La leche y la fruta no son para erizos. Les duele la tripa.",
+ "igel/p3": "¡La manzana se la regalo a Noa!",
+ "giraffe/e1": "¿Quién es el más alto? ¿Pino, Noa o Lulu?",
+ "giraffe/e2": "Pino es pequeño.",
+ "giraffe/e3": "Noa es mediana.",
+ "giraffe/e4": "¡Y Lulu es muy alta!",
+ "giraffe/l1": "¡Soy una jirafa!",
+ "giraffe/e5": "Ningún animal del mundo es tan alto como una jirafa. ¡Tan alto como una casa!",
+ "giraffe/e6": "Dilo conmigo: pequeño, mediano, grande.",
+ "igel/frage": "¿Qué come un erizo?",
+ "giraffe/frage": "¿Quién es el más alto?",
+ "giraffe/w1": "pequeño",
+ "giraffe/w2": "mediano",
+ "giraffe/w3": "grande",
+ "kanal": "El pequeño mundo de Pino"
+ }
+}
\ No newline at end of file
diff --git a/remotion/src/folge90/texte/fr.json b/remotion/src/folge90/texte/fr.json
new file mode 100644
index 0000000..52232bf
--- /dev/null
+++ b/remotion/src/folge90/texte/fr.json
@@ -0,0 +1,26 @@
+{
+ "titel": "Pino sait tout",
+ "zeilen": {
+ "igel/e1": "Que mange un hérisson ? Qu'est-ce que tu crois ?",
+ "igel/p1": "Je vais te le dire !",
+ "igel/e2": "Les hérissons mangent des scarabées,",
+ "igel/e3": "des vers de terre",
+ "igel/e4": "et des escargots.",
+ "igel/p2": "Miam, c'est bon !",
+ "igel/e5": "Le lait et les fruits, ce n'est pas pour les hérissons. Ça leur donne mal au ventre.",
+ "igel/p3": "La pomme, je la donne à Noa !",
+ "giraffe/e1": "Qui est le plus grand ? Pino, Noa ou Lulu ?",
+ "giraffe/e2": "Pino est petit.",
+ "giraffe/e3": "Noa est moyenne.",
+ "giraffe/e4": "Et Lulu est très grande !",
+ "giraffe/l1": "Je suis une girafe !",
+ "giraffe/e5": "Aucun animal au monde n'est aussi haut qu'une girafe. Aussi haut qu'une maison !",
+ "giraffe/e6": "Dis avec moi : petit, moyen, grand !",
+ "igel/frage": "Que mange un hérisson ?",
+ "giraffe/frage": "Qui est le plus grand ?",
+ "giraffe/w1": "petit",
+ "giraffe/w2": "moyen",
+ "giraffe/w3": "grand",
+ "kanal": "Le petit monde de Pino"
+ }
+}
\ No newline at end of file