Folge 8 nach Rückmeldung: Fenster ohne Papa-Umriss (warmes Zimmer), Schattenspiel mit Noas Hand vor der Laterne (Hand und Schatten aus derselben Form, Schatten projiziert), alle atmen/wiegen sich an der Wand, Haken nur in Szene 8 mit Schnur

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lara 2026-11-06 08:15:05 +00:00
parent ad02241e3f
commit f303ec8d7a
5 changed files with 128 additions and 60 deletions

View file

@ -9,7 +9,7 @@ import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {Abenddunkel, GartenAbend, Laterne, laterneInNoasHand} from './teile';
// Szene 3: Es wird dunkel. Abendgarten, rechts die Hauswand, Papa winkt im Fenster.
// Szene 3: Es wird dunkel. Abendgarten, rechts die Hauswand mit erleuchtetem Fenster.
// Noa knipst das Lämpchen an (kein Feuer) und singt den Laternenvers; die Laterne pendelt sanft.
// Pino tippelt zu ihr (baukasten/gang.ts, Füße rutschen nicht). Lulu steht schon an der Hauswand –
// sie geht nicht durchs Bild, weil ihr Geh-Clip kein echtes Gehen ist.
@ -62,7 +62,7 @@ export const Szene3: React.FC = () => {
return (
<>
<Kamera {...kam}>
<GartenAbend papaWinkt={f < z.e1.ende ? f * 0.22 : 0} />
<GartenAbend />
<Figur pose={[[0, 'lulu_A5_blick-links'], [z.p1.start, 'lulu_A3_lachen']]} x={LULU_X} y={BODEN - 6} stauchY={atmen(f, 3, 0.006)} />
<Figur
pose={[

View file

@ -1,9 +1,11 @@
import React from 'react';
import {interpolate, useCurrentFrame} from 'remotion';
import {Blende, kamFahrt} from '../baukasten/helfer';
import {Blende, kamFahrt, punkt} from '../baukasten/helfer';
import {BODEN} from '../baukasten/orte';
import {POSEN_SKALA} from '../baukasten/figur';
import {Untertitel} from '../baukasten/untertitel';
import {plane} from './plan';
import {HandSchatten} from './teile';
import {HandSchatten, NoasHand} from './teile';
import {PLATZ, Wandszene} from './wand';
// Szene 7: Schattenspiel. Noa macht mit der Hand vor der Laterne einen Hasen, dann einen Igel.
@ -23,6 +25,12 @@ export const S7 = plane('s7', [
]);
const LATERNE = {x: 1010, y: PLATZ.laterneBoden.y};
const NOA_X = PLATZ.noa + 140;
// Noas rechte Schulter in der Hock-Pose (Bildpunkt 780/382 auf ihrer Leinwand), die Hand über der Laterne
const SCHULTER = punkt('noa', NOA_X, BODEN, 780, 382, 340 * POSEN_SKALA.noa_B_hocken);
const HAND = {x: 955, y: 770};
const HAND_S = 0.3;
const K = 3; // Hand nah am Licht → großer Schatten
export const Szene7: React.FC = () => {
const f = useCurrentFrame();
@ -30,12 +38,16 @@ export const Szene7: React.FC = () => {
const hase = interpolate(f, [z.bild0.start + 6, z.bild0.ende, z.hals.start, z.hals.start + 10], [0, 1, 1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
const igel = interpolate(f, [z.igel.start, z.igel.start + 12, dauer - 20, dauer - 6], [0, 1, 1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
const strecken = interpolate(f, [z.hals.start, z.hals.start + 20, z.hals.ende - 10, z.hals.ende + 6], [1, 1.12, 1.12, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
const wackelnHase = Math.sin(f * 0.25);
const wackelnIgel = Math.sin(f * 0.3);
// die Hand wippt leicht; der Schatten wippt K-mal so weit mit
const hand = {x: HAND.x + Math.sin(f * 0.07) * 4, y: HAND.y + Math.sin(f * 0.09) * 5};
const kam = kamFahrt(f, [
[0, 1000, 520, 1.0],
[z.n1.start, 1000, 470, 1.12],
[z.hals.start, 1100, 500, 0.98],
[z.igel.start, 1000, 480, 1.1],
[dauer, 1000, 500, 1.06],
[0, 920, 600, 1.2],
[z.n1.start, 920, 620, 1.28],
[z.hals.start, 1060, 520, 0.98],
[z.igel.start, 920, 610, 1.25],
[dauer, 920, 620, 1.22],
]);
return (
<>
@ -43,13 +55,19 @@ export const Szene7: React.FC = () => {
f={f}
kam={kam}
laterne={{inHand: false, licht: 1, x: LATERNE.x, y: LATERNE.y}}
noa={{pose: [[0, 'noa_B_hocken'], [z.igel.start, 'noa_B_hocken']], x: PLATZ.noa + 140}}
noa={{pose: 'noa_B_hocken', x: NOA_X}}
pino={{pose: [[0, 'pino_A4_staunen'], [z.p1.start, 'pino_A3_lachen']], x: LATERNE.x - 230}}
lulu={{pose: 'lulu_A3_lachen', stauchY: strecken}}
wandExtra={
<>
<HandSchatten art="hase" x={1060} y={440} s={1.1} deckkraft={0.42 * hase} wackeln={Math.sin(f * 0.25)} />
<HandSchatten art="igel" x={1040} y={450} s={1.1} deckkraft={0.42 * igel} wackeln={Math.sin(f * 0.3)} />
<HandSchatten art="hase" hand={hand} licht={LATERNE} s={HAND_S} k={K} deckkraft={0.42 * hase} wackeln={wackelnHase} />
<HandSchatten art="igel" hand={hand} licht={LATERNE} s={HAND_S} k={K} deckkraft={0.42 * igel} wackeln={wackelnIgel} />
</>
}
vorne={
<>
{hase > 0 && <NoasHand art="hase" schulter={SCHULTER} hand={hand} s={HAND_S} wackeln={wackelnHase} deckkraft={hase} />}
{igel > 0 && <NoasHand art="igel" schulter={SCHULTER} hand={hand} s={HAND_S} wackeln={wackelnIgel} deckkraft={igel} />}
</>
}
/>

View file

@ -42,9 +42,13 @@ export const Szene8: React.FC = () => {
return (
<>
<Kamera {...kam}>
<GartenAbend />
<GartenAbend haken />
{/* warmer Lichtschein der Laterne fällt auf den Laubhaufen */}
<div style={{position: 'absolute', left: HAUFEN_X - 340, top: BODEN - 260, width: 680, height: 360, borderRadius: '50%', background: 'radial-gradient(closest-side, rgba(255,205,120,0.32), rgba(255,205,120,0))'}} />
{/* Schnur vom Haken zur Laterne */}
<svg viewBox="0 0 1920 1080" style={{position: 'absolute', left: 0, top: 0, width: 1920, height: 1080}}>
<line x1={HAKEN.x} y1={HAKEN.y} x2={lat.x} y2={lat.y - 64 * s} stroke="#3f3646" strokeWidth={3} />
</svg>
<Laterne x={lat.x} y={lat.y} s={s} licht={1} schwung={Math.sin(f * 0.04) * 2} stab={false} />
<Figur pose="lulu_B_liegt" x={LULU_X} y={BODEN - 6} stauchY={atmen(f, 3, 0.01)} />
<Laubhaufen x={HAUFEN_X} y={BODEN + 6} s={0.9} />

View file

@ -12,7 +12,7 @@ export const WAND = {x0: 1060, x1: 1980, oben: 170, unten: BODEN - 30};
export const FENSTER = {x: 1180, y: 280, b: 230, h: 210};
// Abendgarten in der „blauen Stunde“: Nachtgarten aufgehellt, damit es freundlich und nicht unheimlich wirkt
export const GartenAbend: React.FC<{wandVoll?: boolean; papaWinkt?: number}> = ({wandVoll = false, papaWinkt = 0}) => {
export const GartenAbend: React.FC<{wandVoll?: boolean; haken?: boolean}> = ({wandVoll = false, haken = false}) => {
const x0 = wandVoll ? -60 : WAND.x0;
const oben = wandVoll ? 70 : WAND.oben;
return (
@ -39,25 +39,22 @@ export const GartenAbend: React.FC<{wandVoll?: boolean; papaWinkt?: number}> = (
{!wandVoll && <rect x={x0 - 8} y={WAND.oben} width={16} height={WAND.unten - WAND.oben} fill="#9690a6" />}
{!wandVoll && (
<>
{/* erleuchtetes Fenster mit Papa als Umriss */}
{/* erleuchtetes Fenster: warmes Zimmer mit Vorhängen und Lampe (keine Figur – wirkte unecht) */}
<ellipse cx={FENSTER.x + FENSTER.b / 2} cy={FENSTER.y + FENSTER.h / 2} rx={FENSTER.b * 1.1} ry={FENSTER.h * 1.1} fill="url(#fensterlicht)" />
<rect x={FENSTER.x} y={FENSTER.y} width={FENSTER.b} height={FENSTER.h} rx={6} fill="#f6cf7a" stroke="#4b3f55" strokeWidth={12} />
<g opacity={0.85}>
<circle cx={FENSTER.x + 130} cy={FENSTER.y + 92} r={28} fill="#6b5560" />
<path d={`M ${FENSTER.x + 82} ${FENSTER.y + FENSTER.h} C ${FENSTER.x + 86} ${FENSTER.y + 140}, ${FENSTER.x + 174} ${FENSTER.y + 140}, ${FENSTER.x + 178} ${FENSTER.y + FENSTER.h} Z`} fill="#6b5560" />
{/* winkender Arm */}
<g transform={`rotate(${-20 + 25 * Math.sin(papaWinkt)} ${FENSTER.x + 168} ${FENSTER.y + 150})`}>
<rect x={FENSTER.x + 162} y={FENSTER.y + 70} width={16} height={84} rx={8} fill="#6b5560" />
<circle cx={FENSTER.x + 170} cy={FENSTER.y + 66} r={13} fill="#6b5560" />
</g>
</g>
<line x1={FENSTER.x + FENSTER.b / 2} y1={FENSTER.y} x2={FENSTER.x + FENSTER.b / 2} y2={FENSTER.y + FENSTER.h} stroke="#4b3f55" strokeWidth={8} />
<line x1={FENSTER.x} y1={FENSTER.y + FENSTER.h / 2} x2={FENSTER.x + FENSTER.b} y2={FENSTER.y + FENSTER.h / 2} stroke="#4b3f55" strokeWidth={8} />
<rect x={FENSTER.x} y={FENSTER.y} width={FENSTER.b} height={FENSTER.h} rx={6} fill="#f6cf7a" />
<circle cx={FENSTER.x + FENSTER.b / 2} cy={FENSTER.y + 58} r={46} fill="#fff1c4" opacity={0.8} />
<path d={`M ${FENSTER.x + FENSTER.b / 2 - 26} ${FENSTER.y + 40} h 52 l -10 26 h -32 z`} fill="#e9a94d" />
<line x1={FENSTER.x + FENSTER.b / 2} y1={FENSTER.y} x2={FENSTER.x + FENSTER.b / 2} y2={FENSTER.y + 40} stroke="#8a6a4a" strokeWidth={3} />
<path d={`M ${FENSTER.x + 6} ${FENSTER.y + 6} q 40 ${FENSTER.h * 0.5} 8 ${FENSTER.h - 12} h -8 z`} fill="#c9573f" opacity={0.9} />
<path d={`M ${FENSTER.x + FENSTER.b - 6} ${FENSTER.y + 6} q -40 ${FENSTER.h * 0.5} -8 ${FENSTER.h - 12} h 8 z`} fill="#c9573f" opacity={0.9} />
<rect x={FENSTER.x} y={FENSTER.y} width={FENSTER.b} height={FENSTER.h} rx={6} fill="none" stroke="#4b3f55" strokeWidth={12} />
<line x1={FENSTER.x + FENSTER.b / 2} y1={FENSTER.y + 70} x2={FENSTER.x + FENSTER.b / 2} y2={FENSTER.y + FENSTER.h} stroke="#4b3f55" strokeWidth={8} />
<line x1={FENSTER.x} y1={FENSTER.y + FENSTER.h / 2 + 20} x2={FENSTER.x + FENSTER.b} y2={FENSTER.y + FENSTER.h / 2 + 20} stroke="#4b3f55" strokeWidth={8} />
<rect x={FENSTER.x - 18} y={FENSTER.y + FENSTER.h} width={FENSTER.b + 36} height={16} rx={4} fill="#6d6278" />
</>
)}
{/* Haken für die Laterne (Szene 8) */}
<path d={`M ${FENSTER.x + FENSTER.b + 40} ${FENSTER.y + 40} l 26 0 l 0 22`} stroke="#3f3646" strokeWidth={7} fill="none" strokeLinecap="round" />
{/* Haken mit Schnur für die Laterne (nur Szene 8) */}
{haken && <path d={`M ${FENSTER.x + FENSTER.b + 40} ${FENSTER.y + 40} l 26 0 l 0 22`} stroke="#3f3646" strokeWidth={7} fill="none" strokeLinecap="round" />}
</svg>
</>
);
@ -178,29 +175,72 @@ export const WandSchatten: React.FC<{
</div>
);
// Hand-Schatten an der Wand: Hase (zwei Ohren) und Igel (Faust mit gespreizten Fingern)
export const HandSchatten: React.FC<{art: 'hase' | 'igel'; x: number; y: number; s?: number; deckkraft?: number; wackeln?: number}> = ({art, x, y, s = 1, deckkraft = 0.45, wackeln = 0}) => (
<svg width={400} height={400} viewBox="-200 -200 400 400" style={{position: 'absolute', left: x - 200, top: y - 200, transform: `scale(${s})`, overflow: 'visible', opacity: deckkraft, filter: 'blur(4px)'}}>
{art === 'hase' ? (
<g fill="#1d1a2e">
<ellipse cx={0} cy={40} rx={95} ry={70} />
<ellipse cx={-40} cy={-60} rx={22} ry={80} transform={`rotate(${-12 + wackeln * 6} -40 0)`} />
<ellipse cx={30} cy={-62} rx={22} ry={82} transform={`rotate(${10 - wackeln * 6} 30 0)`} />
<ellipse cx={-80} cy={50} rx={40} ry={30} />
<rect x={-20} y={90} width={70} height={120} rx={20} />
// Hand-Schatten: Noa formt vor der Laterne mit der Hand einen Hasen oder einen Igel. Hand und Schatten
// entstehen aus derselben Form – wackeln die Ohren, wackelt der Schatten genau mit. Der Schatten ist die
// Hand, vom Licht aus an die Wand projiziert: Mitte = Licht + (Hand − Licht) · k, Größe · k.
const HandForm: React.FC<{art: 'hase' | 'igel'; wackeln: number; farbe: string; rand?: string}> = ({art, wackeln, farbe, rand}) => {
const strich = rand ? {stroke: rand, strokeWidth: 5} : {};
return art === 'hase' ? (
<g fill={farbe} {...strich}>
<rect x={-14} y={70} width={70} height={140} rx={24} />
<ellipse cx={0} cy={40} rx={90} ry={66} />
<ellipse cx={-36} cy={-56} rx={21} ry={78} transform={`rotate(${-12 + wackeln * 7} -36 10)`} />
<ellipse cx={26} cy={-58} rx={21} ry={80} transform={`rotate(${10 - wackeln * 7} 26 10)`} />
<ellipse cx={-76} cy={52} rx={36} ry={26} />
</g>
) : (
<g fill="#1d1a2e">
<ellipse cx={0} cy={40} rx={100} ry={64} />
<g fill={farbe} {...strich}>
<rect x={-10} y={70} width={70} height={140} rx={24} />
{[-70, -35, 0, 35, 70].map((dx, i) => (
<ellipse key={dx} cx={dx * 1.05} cy={-30} rx={16} ry={56} transform={`rotate(${dx * 0.35 + (i % 2 ? wackeln * 4 : -wackeln * 4)} ${dx} 20)`} />
<ellipse key={dx} cx={dx * 1.05} cy={-26} rx={15} ry={54} transform={`rotate(${dx * 0.35 + (i % 2 ? wackeln * 5 : -wackeln * 5)} ${dx} 20)`} />
))}
<ellipse cx={-105} cy={60} rx={34} ry={24} />
<rect x={-10} y={90} width={70} height={120} rx={20} />
<ellipse cx={0} cy={40} rx={96} ry={62} />
<ellipse cx={-100} cy={58} rx={32} ry={22} />
</g>
)}
);
};
const HANDBOX = 400;
export const HandSchatten: React.FC<{art: 'hase' | 'igel'; hand: {x: number; y: number}; licht: {x: number; y: number}; s: number; k: number; deckkraft?: number; wackeln?: number}> = ({
art,
hand,
licht,
s,
k,
deckkraft = 0.45,
wackeln = 0,
}) => {
const x = licht.x + (hand.x - licht.x) * k;
const y = licht.y + (hand.y - licht.y) * k;
return (
<svg width={HANDBOX} height={HANDBOX} viewBox="-200 -200 400 400" style={{position: 'absolute', left: x - 200, top: y - 200, transform: `scale(${s * k})`, overflow: 'visible', opacity: deckkraft, filter: `blur(${(4 / (s * k)).toFixed(2)}px)`}}>
<HandForm art={art} wackeln={wackeln} farbe="#1d1a2e" />
</svg>
);
);
};
// Noas Arm (Jackenärmel) von der Schulter bis zur Hand, die Hand formt Hase oder Igel
export const NoasHand: React.FC<{art: 'hase' | 'igel'; schulter: {x: number; y: number}; hand: {x: number; y: number}; s: number; wackeln?: number; deckkraft?: number}> = ({
art,
schulter,
hand,
s,
wackeln = 0,
deckkraft = 1,
}) => {
const handgelenk = {x: hand.x - 6 * s * 10, y: hand.y + 150 * s};
return (
<div style={{position: 'absolute', left: 0, top: 0, opacity: deckkraft}}>
<svg viewBox="0 0 1920 1080" style={{position: 'absolute', left: 0, top: 0, width: 1920, height: 1080, overflow: 'visible'}}>
<path d={`M ${schulter.x} ${schulter.y} Q ${(schulter.x + handgelenk.x) / 2} ${Math.max(schulter.y, handgelenk.y) + 40} ${handgelenk.x} ${handgelenk.y}`} stroke="#c98d2e" strokeWidth={34} strokeLinecap="round" fill="none" />
<path d={`M ${schulter.x} ${schulter.y} Q ${(schulter.x + handgelenk.x) / 2} ${Math.max(schulter.y, handgelenk.y) + 40} ${handgelenk.x} ${handgelenk.y}`} stroke="#dba443" strokeWidth={26} strokeLinecap="round" fill="none" />
</svg>
<svg width={HANDBOX} height={HANDBOX} viewBox="-200 -200 400 400" style={{position: 'absolute', left: hand.x - 200, top: hand.y - 200, transform: `scale(${s})`, overflow: 'visible'}}>
<HandForm art={art} wackeln={wackeln} farbe="#f3cfaa" rand="#b9825a" />
</svg>
</div>
);
};
// Wo die Laterne in der Pose noa_A_laterne hängt, und wie groß unsere Laterne sein muss, um die orange
// Laterne des Bildes ganz abzudecken (gemessen: 131 × 134 Bildpunkte, Mitte bei 73/423 im Bild, Bild bei 339/156)

View file

@ -1,7 +1,7 @@
import React from 'react';
import {ClipFigur, Figur, Kamera, type Posenfolge} from '../baukasten/figur';
import type {Gang} from '../baukasten/gang';
import {Vignette} from '../baukasten/helfer';
import {Vignette, atmen} from '../baukasten/helfer';
import {BODEN} from '../baukasten/orte';
import {Abenddunkel, GartenAbend, Laterne, WAND_VOLL, WandSchatten, laterneInNoasHand} from './teile';
@ -18,11 +18,12 @@ export type Buehne = {
noa: {pose: string | Posenfolge; x?: number};
pino: {pose: string | Posenfolge; x: number; gang?: Gang; drehung?: number; y?: number; k?: number; versatz?: number};
lulu: {pose: string | Posenfolge; x?: number; hell?: number; drehung?: number; stauchY?: number; k?: number};
wandExtra?: React.ReactNode; // z. B. Hand-Schatten
wandExtra?: React.ReactNode; // z. B. Hand-Schatten (an der Wand, hinter den Figuren)
vorne?: React.ReactNode; // z. B. Noas Hand vor der Laterne
f: number;
};
export const Wandszene: React.FC<Buehne> = ({kam, laterne, noa, pino, lulu, wandExtra, f}) => {
export const Wandszene: React.FC<Buehne> = ({kam, laterne, noa, pino, lulu, wandExtra, vorne, f}) => {
const noaX = noa.x ?? PLATZ.noa;
const luluX = lulu.x ?? PLATZ.lulu;
const hand = laterneInNoasHand(noaX);
@ -32,16 +33,20 @@ export const Wandszene: React.FC<Buehne> = ({kam, laterne, noa, pino, lulu, wand
const kPino = pino.k ?? 1 + 220 / Math.max(80, Math.abs(pino.x - lat.x));
const hell = lulu.hell ?? 1;
const pinoGeht = pino.gang?.geht(f);
// Niemand steht ganz still: alle atmen, Lulu wiegt sich leicht – die Schatten machen es mit
const luluDreh = lulu.drehung ?? Math.sin(f * 0.035) * 1.3;
const luluStauch = (lulu.stauchY ?? 1) * atmen(f, 3, 0.006);
const pinoStauch = atmen(f, 1, 0.015);
return (
<>
<Kamera {...kam}>
<GartenAbend wandVoll />
{/* Schatten an der Wand (hinter den Figuren) */}
<WandSchatten pose={lulu.pose} x={luluX} licht={lat} hoehe={340 * 2.05} k={kLulu} wand={WAND_VOLL} drehung={lulu.drehung} stauchY={lulu.stauchY} />
<WandSchatten pose={pinoGeht ? 'pino_A6_blick-rechts' : pino.pose} x={pino.x} licht={lat} hoehe={340 * 0.4} k={kPino} wand={WAND_VOLL} drehung={pino.drehung} versatz={pino.versatz} />
<WandSchatten pose={lulu.pose} x={luluX} licht={lat} hoehe={340 * 2.05} k={kLulu} wand={WAND_VOLL} drehung={luluDreh} stauchY={luluStauch} />
<WandSchatten pose={pinoGeht ? 'pino_A6_blick-rechts' : pino.pose} x={pino.x} licht={lat} hoehe={340 * 0.4} k={kPino} wand={WAND_VOLL} drehung={pino.drehung} stauchY={pinoStauch} versatz={pino.versatz} />
{wandExtra}
<Figur pose={lulu.pose} x={luluX} y={BODEN - 6} drehung={lulu.drehung} stauchY={lulu.stauchY} filter={hell < 1 ? `brightness(${hell})` : undefined} />
<Figur pose={noa.pose} x={noaX} y={BODEN} />
<Figur pose={lulu.pose} x={luluX} y={BODEN - 6} drehung={luluDreh} stauchY={luluStauch} filter={hell < 1 ? `brightness(${hell})` : undefined} />
<Figur pose={noa.pose} x={noaX} y={BODEN} stauchY={atmen(f, 2)} />
{laterne.inHand ? (
<Laterne x={lat.x} y={lat.y} s={s} licht={laterne.licht} schwung={laterne.schwung ?? 0} stab={false} />
) : (
@ -50,8 +55,9 @@ export const Wandszene: React.FC<Buehne> = ({kam, laterne, noa, pino, lulu, wand
{pinoGeht && pino.gang ? (
<ClipFigur name="pino_C1_gehen-schleife" start={pino.gang.clipStart} tempo={pino.gang.tempo} schleife spiegeln={pino.gang.spiegeln} x={pino.gang.x(f)} y={BODEN} />
) : (
<Figur pose={pino.pose} x={pino.x} y={pino.y ?? BODEN} drehung={pino.drehung} />
<Figur pose={pino.pose} x={pino.x} y={pino.y ?? BODEN} drehung={pino.drehung} stauchY={pinoStauch} />
)}
{vorne}
<Abenddunkel licht={lat} staerke={0.42} radius={640} />
</Kamera>
<Vignette />