Add movement gallery wings with period interiors and expand timeline features.

Movement galleries split large catalogs into chronological wings (~55 works), use era-themed 3D interiors with side-wall windows, wing navigator on the back exit, and front archways between wings. Also adds painting annotations, timeline event guides, portrait hover highlights, and documentation/API updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-06-21 16:54:19 +03:00
co-authored by Cursor
parent 0972b5df99
commit df29848d89
121 changed files with 4765 additions and 396 deletions
+106
View File
@@ -0,0 +1,106 @@
import { useState } from 'react';
import * as THREE from 'three';
import { Text } from '@react-three/drei';
const DOOR_WIDTH = 2.4;
const DOOR_HEIGHT = 2.5;
const WALL_THICKNESS = 0.18;
/** Open archway connecting to the next movement wing. */
export default function HallPassage({
position,
active,
label,
onActivate,
wallColor = '#f0ebe3',
trimColor = '#ddd5c8',
}: {
position: [number, number, number];
active: boolean;
label: string;
onActivate: () => void;
wallColor?: string;
trimColor?: string;
}) {
const [hovered, setHovered] = useState(false);
const highlight = active || hovered;
const openingW = DOOR_WIDTH;
const openingH = DOOR_HEIGHT;
const jamb = 0.12;
const faceZ = 0.04;
const handleActivate = (e: THREE.Event & { stopPropagation: () => void }) => {
e.stopPropagation();
onActivate();
};
return (
<group position={position}>
<pointLight position={[0, openingH * 0.5, 0.3]} intensity={highlight ? 0.9 : 0.5} distance={5} color="#fff4e8" />
{/* Depth beyond passage */}
<mesh position={[0, openingH * 0.5, 0.15]}>
<planeGeometry args={[openingW * 0.9, openingH * 0.95]} />
<meshStandardMaterial color="#fff8f0" emissive="#ffe8c8" emissiveIntensity={highlight ? 0.28 : 0.14} />
</mesh>
{/* Side jambs */}
{([-1, 1] as const).map((sign) => (
<mesh key={sign} position={[sign * (openingW / 2 + jamb / 2), openingH / 2, faceZ]} castShadow>
<boxGeometry args={[jamb, openingH + 0.1, 0.1]} />
<meshStandardMaterial color={trimColor} roughness={0.45} metalness={0.25} />
</mesh>
))}
{/* Arch header */}
<mesh position={[0, openingH + 0.06, faceZ]} castShadow>
<boxGeometry args={[openingW + jamb * 2, 0.14, 0.1]} />
<meshStandardMaterial color={trimColor} roughness={0.4} metalness={0.3} />
</mesh>
<mesh position={[0, openingH + 0.22, faceZ]} rotation={[0, 0, Math.PI]}>
<sphereGeometry args={[openingW / 2 + 0.04, 16, 8, 0, Math.PI * 2, 0, Math.PI / 2]} />
<meshStandardMaterial color={wallColor} roughness={0.88} />
</mesh>
<group position={[0, openingH + 0.38, faceZ - 0.01]}>
<mesh onClick={handleActivate} onPointerOver={() => setHovered(true)} onPointerOut={() => setHovered(false)}>
<boxGeometry args={[1.4, 0.18, 0.02]} />
<meshStandardMaterial
color={highlight ? '#d4af37' : trimColor}
roughness={0.3}
metalness={0.5}
emissive={highlight ? '#5a4010' : '#000000'}
emissiveIntensity={highlight ? 0.2 : 0}
/>
</mesh>
<Text
position={[0, 0, -0.012]}
rotation={[0, Math.PI, 0]}
fontSize={0.1}
color={highlight ? '#fff8e8' : '#4a3020'}
anchorX="center"
anchorY="middle"
onClick={handleActivate}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
>
{label.toUpperCase()}
</Text>
</group>
<mesh
position={[0, openingH / 2, faceZ - 0.12]}
rotation={[0, Math.PI, 0]}
onClick={handleActivate}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
>
<planeGeometry args={[openingW * 1.1, openingH * 1.05]} />
<meshBasicMaterial transparent opacity={0} depthWrite={false} side={THREE.DoubleSide} />
</mesh>
</group>
);
}
export { DOOR_WIDTH, DOOR_HEIGHT, WALL_THICKNESS };