Lied 4: Noa tippt mit den Higgsfield-Posen auf die Körperteile statt Leuchtkreisen; Karte zeigt die Pose klein

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lara 2026-11-09 15:08:04 +00:00
parent 1a6e76e346
commit a705b32b1e
2 changed files with 36 additions and 37 deletions

View file

@ -1,7 +1,7 @@
import React from 'react';
import {AbsoluteFill, Audio, Sequence, interpolate, staticFile, useCurrentFrame} from 'remotion';
import {INTRO_OPTIONEN, Intro, OUTRO, Outro} from '../../baukasten/buch';
import {Figur, Kamera} from '../../baukasten/figur';
import {Figur, Kamera, type Posenfolge} from '../../baukasten/figur';
import {Blende, Vignette, kamFahrt} from '../../baukasten/helfer';
import {BODEN, Ort} from '../../baukasten/orte';
import {SCHRIFT} from '../../baukasten/untertitel';
@ -21,7 +21,8 @@ export type Bild = {
kamera?: {x: number; y: number; z: number};
leuchten?: (Punkt & {farbe: string; r?: number; ab?: number})[];
karte?: React.ReactNode;
noa?: string;
noa?: string | Posenfolge; // Posenfolge mit Frames ab Liedbeginn (z. B. Pose je Wort)
tipps?: number[]; // Sekunden, an denen Noa kurz im Takt nachfedert (Tippen)
pino?: string;
lulu?: string;
luluStreckt?: boolean;
@ -152,7 +153,7 @@ const Garten: React.FC<{daten: LiedDaten; szene: (i: number, z: Zeile) => Bild}>
<Kamera {...kam}>
<Ort name="garten" />
<Figur pose={b.lulu ?? 'lulu_A3_lachen'} x={PLATZ.lulu} y={BODEN} drehung={schwank(0.5, 2)} stauchY={strecken} blende={8} />
<Figur pose={b.noa ?? 'noa_A3_lachen'} x={PLATZ.noa} y={BODEN + wipp(0.3, 10)} drehung={schwank(0, 2.5)} blende={6} />
<Figur pose={b.noa ?? 'noa_A3_lachen'} x={PLATZ.noa} y={BODEN + wipp(0.3, 10)} drehung={schwank(0, 2.5)} stauchY={1 - (b.tipps ?? []).reduce((m, tp) => m + 0.03 * Math.max(0, 1 - Math.abs(f - s(tp)) / 4), 0)} blende={4} />
<Figur pose={b.pino ?? 'pino_A3_lachen'} x={PLATZ.pino} y={BODEN + wipp(0, 14)} drehung={schwank(1, 3)} blende={8} />
{bilder.map((bb, k) =>
(bb.leuchten ?? []).map((l, j) => <Leuchtkreis key={`${k}-${j}`} {...l} ab={s(l.ab ?? zeilen[k].start - 0.1)} bis={zeilenEnde(k)} />),

View file

@ -1,29 +1,14 @@
import React from 'react';
import {Figur} from '../../baukasten/figur';
import {punkt} from '../../baukasten/helfer';
import {BODEN} from '../../baukasten/orte';
import {Figur, type Posenfolge} from '../../baukasten/figur';
import {SPRACHE} from '../../baukasten/sprache';
import {type Bild, EinfachesLied, type LiedDaten, PLATZ, type Punkt, type Zeile, wortZeit} from '../einfach/EinfachesLied';
import {type Bild, EinfachesLied, type LiedDaten, PLATZ, type Zeile, wortZeit} from '../einfach/EinfachesLied';
import {DE, ES, FR} from '../einfach/daten/lied04';
// Liedvideo „Wer hat den längsten Hals?“ (Pinos Liederbuch, Lied 4: Körperteile) in DE/ES/FR.
// Refrain: Lulu streckt den Hals. Strophen (Ohren, Hände, Beine, Arme): nah bei Noa, die Stellen leuchten,
// die Karte zeigt eine kleine Noa mit der leuchtenden Stelle. Turbo-Teil (3×): jedes genannte Körperteil
// leuchtet zu seinem Wort, im dritten Durchgang langsam. Drehbuch drehbuecher/lieder/lied04-koerperteile/.
// Refrain: Lulu streckt den Hals, Noa zeigt auf ihn. Strophen (Ohren, Hände, Beine, Arme): nah bei Noa, sie tippt
// auf das Körperteil (Higgsfield-Posen noa_B_tippt-*), die Karte zeigt die Pose klein. Turbo-Teil (3×): zu jedem
// gesungenen Körperteil die passende Pose, im dritten Durchgang langsam. Keine Leuchtkreise (Nutzerin 09.11.). Drehbuch drehbuecher/lieder/lied04-koerperteile/.
// Körperteile in noa_A3_lachen (Leinwand = Bild + 339/156, gemessen)
const TEIL: Record<string, [number, number][]> = {
ohren: [[607, 306], [809, 306]],
haende: [[629, 576], [809, 551]],
beine: [[684, 616], [744, 616]],
arme: [[629, 476], [799, 466]],
nase: [[734, 296]],
bauch: [[709, 486]],
hals: [[709, 361]],
fuesse: [[684, 691], [754, 691]],
zeh: [[759, 721]],
};
const FARBE = '#d9482b'; // kräftiges Rot-Orange: hebt sich von Jacke und Gesicht ab
const WOERTER: [RegExp, string][] = [
[/^(ohren|orejas|oreilles)/i, 'ohren'],
[/^(h[aä]nde|manos|mains)/i, 'haende'],
@ -36,16 +21,25 @@ const WOERTER: [RegExp, string][] = [
[/^(zeh|deditos|orteils)/i, 'zeh'],
];
const teilVon = (wort: string) => WOERTER.find(([re]) => re.test(wort.replace(/[^\p{L}]/gu, '')))?.[1];
const anNoa = (teil: string): Punkt[] => TEIL[teil].map(([cx, cy]) => punkt('noa', PLATZ.noa, BODEN, cx, cy));
// Karte: kleine Noa, die genannte Stelle leuchtet
// Higgsfield-Posen (09.11.2026): Noa tippt wirklich auf das Körperteil. Hände = klatschen, Hals = sie zeigt
// auf Lulus langen Hals, Zeh = wie Füße.
const POSE: Record<string, string> = {
ohren: 'noa_B_tippt-ohren',
nase: 'noa_B_tippt-nase',
bauch: 'noa_B_tippt-bauch',
beine: 'noa_B_tippt-beine',
arme: 'noa_B_tippt-arme',
fuesse: 'noa_B_tippt-fuesse',
zeh: 'noa_B_tippt-fuesse',
haende: 'noa_A_klatschen',
hals: 'noa_B_zeigen',
};
// Karte: kleine Noa in derselben Pose (Bild statt Text)
const NoaKarte: React.FC<{teil: string}> = ({teil}) => (
<div style={{position: 'relative', width: 204, height: 204}}>
<Figur pose="noa_A3_lachen" x={102} y={198} hoehe={180} schatten={false} />
{TEIL[teil].map(([cx, cy], k) => {
const p = punkt('noa', 102, 198, cx, cy, 180);
return <div key={k} style={{position: 'absolute', left: p.x - 16, top: p.y - 16, width: 32, height: 32, borderRadius: '50%', border: `5px solid ${FARBE}`, background: `${FARBE}66`}} />;
})}
<Figur pose={POSE[teil]} x={102} y={198} hoehe={180} schatten={false} />
</div>
);
@ -64,28 +58,32 @@ const lage = (i: number) => {
return i - k;
};
const NOA_NAH = {x: PLATZ.noa, y: 765, z: 1.75}; // ganze Noa über dem Mitlesetext
const LULU_HALS = punkt('lulu', PLATZ.lulu, BODEN, 322 + 320, 23 + 230);
const istTipp = (w: string) => /^(tipp|toc)/i.test(w.replace(/[^\p{L}]/gu, ''));
const szene = (i: number, z: Zeile): Bild => {
const a = z.abschnitt.toLowerCase();
const n = lage(i);
const woerter = z.zeile.split(' ').map((w) => w.split('@')[0]);
const tipps = woerter.flatMap((w, k) => (istTipp(w) ? [wortZeit(z, k)] : []));
if (a.startsWith('chorus')) {
if (n === 0) return {kamera: {x: PLATZ.lulu - 60, y: 420, z: 1.5}, luluStreckt: true, lulu: 'lulu_A3_lachen'};
if (n === 1) return {leuchten: [{...LULU_HALS, farbe: FARBE, r: 90}]};
return {};
if (n === 1) return {noa: 'noa_B_zeigen', luluStreckt: true, tipps};
return {tipps};
}
if (a.startsWith('verse')) {
const teil = woerter.map(teilVon).find(Boolean) ?? 'ohren';
if (n === 0) return {kamera: NOA_NAH, noa: i % 2 ? 'noa_A5_blick-links' : 'noa_A6_blick-rechts', ruhig: true, karte: <NoaKarte teil={teil} />};
return {kamera: NOA_NAH, leuchten: anNoa(teil).map((p) => ({...p, farbe: FARBE, r: 46})), karte: <NoaKarte teil={teil} />};
return {kamera: NOA_NAH, noa: POSE[teil], tipps, karte: <NoaKarte teil={teil} />};
}
if (a.startsWith('bridge')) {
const leuchten = woerter.flatMap((w, k) => {
// zu jedem gesungenen Körperteil die passende Pose (knapp vor dem Wort, damit sie beim Wort steht)
const folge: Posenfolge = [[0, 'noa_A3_lachen']];
woerter.forEach((w, k) => {
const teil = teilVon(w);
return teil ? anNoa(teil).map((p) => ({...p, farbe: FARBE, r: 40, ab: wortZeit(z, k) - 0.05})) : [];
if (teil) folge.push([Math.round(wortZeit(z, k) * 30) - 4, POSE[teil]]);
});
return {kamera: {x: PLATZ.noa - 40, y: 720, z: 1.5}, leuchten, ruhig: a.includes('slow')};
return {kamera: {x: PLATZ.noa - 40, y: 720, z: 1.5}, noa: folge.length > 1 ? folge : 'noa_A3_lachen', luluStreckt: woerter.some((w) => teilVon(w) === 'hals'), ruhig: a.includes('slow')};
}
return {ruhig: true};
};