youtube/remotion/src/folge08/teile.tsx
2026-11-05 20:07:38 +00:00

225 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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