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 KhodjaefandCursor committed 2026-06-21 16:54:19 +03:00
1 parent 0972b5df99
commit df29848d89
121 files changed
+4765 -396

No files matched your search

+601
View File
@@ -0,0 +1,601 @@
import type { ArtMovement } from '../types';
import type { SurfaceTextureKind } from '../utils/galleryProceduralTextures';
export type GalleryWindowStyle =
| 'roman-arch'
| 'gothic-lancet'
| 'baroque-pair'
| 'sash'
| 'factory'
| 'skylight'
| 'art-nouveau'
| 'glass-block'
| 'clerestory'
| 'round-oculus';
export interface GalleryWindowSpec {
wall: 'back' | 'left' | 'right' | 'ceiling';
/** Offset along wall axis from center (meters). */
x: number;
/** Vertical center height (meters). */
y: number;
width: number;
height: number;
style: GalleryWindowStyle;
lightColor: string;
lightIntensity: number;
}
export interface MovementInteriorStyle {
id: string;
label: string;
subtitle: string;
surfaces: {
wall: SurfaceTextureKind;
wallSide?: SurfaceTextureKind;
ceiling: SurfaceTextureKind;
floor: SurfaceTextureKind;
};
tints: {
wall: string;
wallSide?: string;
ceiling: string;
floor: string;
trim: string;
};
titleColor: string;
ambient: number;
warmLight: string;
sunLight: string;
fog: string;
background: string;
doorWood: [string, string, string];
windows: GalleryWindowSpec[];
trackLights: number;
details: 'palazzo' | 'baroque' | 'medieval' | 'neoclassical' | 'salon' | 'modern' | 'classical' | 'museum' | 'industrial' | 'atelier';
}
function windows(...specs: GalleryWindowSpec[]): GalleryWindowSpec[] {
return specs;
}
function mk(
id: string,
label: string,
subtitle: string,
surfaces: MovementInteriorStyle['surfaces'],
tints: MovementInteriorStyle['tints'],
opts: Partial<Omit<MovementInteriorStyle, 'id' | 'label' | 'subtitle' | 'surfaces' | 'tints'>> & {
details: MovementInteriorStyle['details'];
windows: GalleryWindowSpec[];
}
): MovementInteriorStyle {
return {
id,
label,
subtitle,
surfaces,
tints,
titleColor: opts.titleColor ?? '#4a3020',
ambient: opts.ambient ?? 0.55,
warmLight: opts.warmLight ?? '#fff4e8',
sunLight: opts.sunLight ?? '#fffaf0',
fog: opts.fog ?? '#1a1814',
background: opts.background ?? '#121010',
doorWood: opts.doorWood ?? ['#3d2818', '#4e3624', '#261a10'],
windows: opts.windows,
trackLights: opts.trackLights ?? 0.8,
details: opts.details,
};
}
/** Unique photo-real interior per movement (keyed by database id). */
const BY_MOVEMENT_ID: Record<number, MovementInteriorStyle> = {
27: mk(
'ancient-classical',
'Roman atrium',
'Marble-clad villa · mosaic floor · clerestory daylight',
{ wall: 'limestone', ceiling: 'plaster-warm', floor: 'mosaic-roman' },
{ wall: '#e8e0d0', ceiling: '#f5f0e8', floor: '#c8b898', trim: '#a89878' },
{
details: 'classical',
titleColor: '#5a4830',
ambient: 0.62,
warmLight: '#fff8e8',
windows: windows(
{ wall: 'back', x: -2.5, y: 3.2, width: 1.4, height: 1.8, style: 'roman-arch', lightColor: '#fff8e0', lightIntensity: 3.2 },
{ wall: 'back', x: 2.5, y: 3.2, width: 1.4, height: 1.8, style: 'roman-arch', lightColor: '#fff8e0', lightIntensity: 3.2 },
{ wall: 'left', x: 0, y: 3.0, width: 2.0, height: 1.6, style: 'clerestory', lightColor: '#fffaf0', lightIntensity: 2.8 },
{ wall: 'right', x: 0, y: 3.0, width: 2.0, height: 1.6, style: 'clerestory', lightColor: '#fffaf0', lightIntensity: 2.8 }
),
trackLights: 0.4,
doorWood: ['#6a5840', '#7a6848', '#5a4830'],
}
),
28: mk(
'byzantine-sanctuary',
'Byzantine chapel',
'Gold mosaic walls · amber light through arched windows',
{ wall: 'mosaic-byzantine', ceiling: 'gilded-stucco', floor: 'marble-checker' },
{ wall: '#d4af37', ceiling: '#c8a840', floor: '#ddd8cc', trim: '#b8860b' },
{
details: 'medieval',
titleColor: '#f0e8c0',
ambient: 0.48,
warmLight: '#ffd898',
sunLight: '#ffe8b0',
fog: '#0a0806',
background: '#060504',
windows: windows(
{ wall: 'back', x: 0, y: 2.8, width: 1.0, height: 2.2, style: 'roman-arch', lightColor: '#ffb860', lightIntensity: 2.4 },
{ wall: 'left', x: -1, y: 2.6, width: 0.7, height: 1.8, style: 'roman-arch', lightColor: '#ffb860', lightIntensity: 1.8 },
{ wall: 'right', x: 1, y: 2.6, width: 0.7, height: 1.8, style: 'roman-arch', lightColor: '#ffb860', lightIntensity: 1.8 }
),
trackLights: 0.5,
doorWood: ['#3a3020', '#4a3830', '#2a2018'],
}
),
29: mk(
'gothic-cathedral',
'Gothic hall',
'Stone vault · tall lancet windows · flagstone floor',
{ wall: 'rough-stone', ceiling: 'basalt', floor: 'flagstone' },
{ wall: '#8a8478', ceiling: '#3a3630', floor: '#6a6458', trim: '#5c5648' },
{
details: 'medieval',
titleColor: '#e8dcc8',
ambient: 0.52,
warmLight: '#e8d8c0',
sunLight: '#d0e8ff',
fog: '#0c0c10',
windows: windows(
{ wall: 'back', x: -2, y: 2.8, width: 0.8, height: 2.8, style: 'gothic-lancet', lightColor: '#c8e0ff', lightIntensity: 3.5 },
{ wall: 'back', x: 2, y: 2.8, width: 0.8, height: 2.8, style: 'gothic-lancet', lightColor: '#c8e0ff', lightIntensity: 3.5 },
{ wall: 'left', x: 0, y: 3.0, width: 0.9, height: 2.6, style: 'gothic-lancet', lightColor: '#c8e0ff', lightIntensity: 2.8 },
{ wall: 'right', x: 0, y: 3.0, width: 0.9, height: 2.6, style: 'gothic-lancet', lightColor: '#c8e0ff', lightIntensity: 2.8 }
),
trackLights: 0.3,
}
),
30: mk(
'early-renaissance-palazzo',
'Florentine palazzo',
'Stucco salone · terracotta accents · arched windows',
{ wall: 'stucco-cream', ceiling: 'fresco-worn', floor: 'terracotta-tiles' },
{ wall: '#f4ebe0', ceiling: '#faf6ee', floor: '#c89070', trim: '#c9a227' },
{
details: 'palazzo',
titleColor: '#6b4423',
windows: windows(
{ wall: 'back', x: -2.2, y: 2.6, width: 1.3, height: 1.7, style: 'roman-arch', lightColor: '#fff4e0', lightIntensity: 3.0 },
{ wall: 'back', x: 2.2, y: 2.6, width: 1.3, height: 1.7, style: 'roman-arch', lightColor: '#fff4e0', lightIntensity: 3.0 },
{ wall: 'left', x: 0, y: 2.8, width: 2.2, height: 1.4, style: 'clerestory', lightColor: '#fffaf0', lightIntensity: 2.6 }
),
}
),
31: mk(
'high-renaissance-palazzo',
'Roman palazzo',
'Marble and stucco · coffered ceiling · checker floor',
{ wall: 'marble-veined-carrara', wallSide: 'stucco-cream', ceiling: 'plaster-warm', floor: 'marble-checker' },
{ wall: '#f0ece4', wallSide: '#efe4d4', ceiling: '#faf6ee', floor: '#ddd8cc', trim: '#c9a227' },
{
details: 'palazzo',
titleColor: '#6b4423',
windows: windows(
{ wall: 'back', x: 0, y: 2.8, width: 2.8, height: 1.8, style: 'baroque-pair', lightColor: '#fff8f0', lightIntensity: 3.8 },
{ wall: 'left', x: 0, y: 3.0, width: 2.0, height: 1.5, style: 'clerestory', lightColor: '#fffaf0', lightIntensity: 2.8 },
{ wall: 'right', x: 0, y: 3.0, width: 2.0, height: 1.5, style: 'clerestory', lightColor: '#fffaf0', lightIntensity: 2.8 },
{ wall: 'ceiling', x: 0, y: 0, width: 3.0, height: 2.0, style: 'round-oculus', lightColor: '#ffffff', lightIntensity: 4.0 }
),
}
),
32: mk(
'northern-renaissance-hall',
'Flemish panel hall',
'Oak wainscoting · leaded glass · herringbone floor',
{ wall: 'oak-panel', wallSide: 'plaster-warm', ceiling: 'dark-wood-panel', floor: 'parquet-herringbone' },
{ wall: '#8a6848', wallSide: '#f0ebe3', ceiling: '#3a2818', floor: '#8a6848', trim: '#5c4030' },
{
details: 'salon',
titleColor: '#f0e8d8',
warmLight: '#ffe8c8',
windows: windows(
{ wall: 'back', x: -2, y: 2.5, width: 1.2, height: 1.5, style: 'sash', lightColor: '#e8f0ff', lightIntensity: 2.8 },
{ wall: 'back', x: 2, y: 2.5, width: 1.2, height: 1.5, style: 'sash', lightColor: '#e8f0ff', lightIntensity: 2.8 },
{ wall: 'left', x: 0, y: 2.6, width: 1.8, height: 1.4, style: 'sash', lightColor: '#e8f0ff', lightIntensity: 2.4 }
),
doorWood: ['#4a3020', '#5c3a28', '#3a2010'],
}
),
33: mk(
'mannerist-villa',
'Mannerist gallery',
'Dramatic stucco · elongated windows · veined marble',
{ wall: 'stucco-terracotta', ceiling: 'gilded-stucco', floor: 'marble-veined-emerald' },
{ wall: '#d8b898', ceiling: '#d8c070', floor: '#dce8e0', trim: '#b8860b' },
{
details: 'baroque',
titleColor: '#4a2818',
windows: windows(
{ wall: 'back', x: -1.8, y: 2.7, width: 1.0, height: 2.2, style: 'roman-arch', lightColor: '#fff0d8', lightIntensity: 2.8 },
{ wall: 'back', x: 1.8, y: 2.7, width: 1.0, height: 2.2, style: 'roman-arch', lightColor: '#fff0d8', lightIntensity: 2.8 },
{ wall: 'right', x: 0, y: 3.0, width: 1.6, height: 1.2, style: 'clerestory', lightColor: '#fffaf0', lightIntensity: 2.2 }
),
}
),
34: mk(
'baroque-palace',
'Baroque state gallery',
'Crimson velvet · gilded stucco · grand windows',
{ wall: 'velvet-crimson', ceiling: 'gilded-stucco', floor: 'marble-veined-carrara' },
{ wall: '#5c1828', ceiling: '#d8c070', floor: '#ece8e0', trim: '#d4af37' },
{
details: 'baroque',
titleColor: '#f0d890',
ambient: 0.58,
warmLight: '#ffd898',
fog: '#100808',
windows: windows(
{ wall: 'back', x: 0, y: 2.6, width: 3.2, height: 2.2, style: 'baroque-pair', lightColor: '#fff8e8', lightIntensity: 4.5 },
{ wall: 'left', x: 0, y: 2.8, width: 1.8, height: 1.8, style: 'baroque-pair', lightColor: '#fff8e8', lightIntensity: 3.2 },
{ wall: 'right', x: 0, y: 2.8, width: 1.8, height: 1.8, style: 'baroque-pair', lightColor: '#fff8e8', lightIntensity: 3.2 },
{ wall: 'ceiling', x: 0, y: 0, width: 2.5, height: 1.8, style: 'skylight', lightColor: '#ffffff', lightIntensity: 3.5 }
),
trackLights: 0.6,
doorWood: ['#1a1008', '#2a1810', '#120c06'],
}
),
35: mk(
'rococo-salon',
'Rococo salon',
'Pastel stucco · gilt trim · chevron parquet',
{ wall: 'pastel-plaster', ceiling: 'silk-pale', floor: 'parquet-chevrons' },
{ wall: '#e8dce8', ceiling: '#faf6f0', floor: '#c8a882', trim: '#d4af37' },
{
details: 'salon',
titleColor: '#6a4858',
warmLight: '#fff0f0',
windows: windows(
{ wall: 'back', x: -2, y: 2.5, width: 1.4, height: 1.8, style: 'baroque-pair', lightColor: '#fff8ff', lightIntensity: 3.4 },
{ wall: 'back', x: 2, y: 2.5, width: 1.4, height: 1.8, style: 'baroque-pair', lightColor: '#fff8ff', lightIntensity: 3.4 },
{ wall: 'ceiling', x: 0, y: 0, width: 2.0, height: 1.5, style: 'skylight', lightColor: '#ffffff', lightIntensity: 3.0 }
),
}
),
36: mk(
'neoclassical-museum',
'Neoclassical museum',
'White marble · coffered ceiling · skylit salon',
{ wall: 'marble-white', ceiling: 'plaster-white', floor: 'marble-veined-carrara' },
{ wall: '#f2f0ec', ceiling: '#fafafa', floor: '#eceae6', trim: '#b8b0a4' },
{
details: 'neoclassical',
titleColor: '#4a4844',
ambient: 0.65,
windows: windows(
{ wall: 'back', x: 0, y: 2.6, width: 2.8, height: 2.0, style: 'baroque-pair', lightColor: '#ffffff', lightIntensity: 4.0 },
{ wall: 'ceiling', x: 0, y: 0, width: 4.0, height: 2.5, style: 'skylight', lightColor: '#ffffff', lightIntensity: 5.0 },
{ wall: 'left', x: 0, y: 3.0, width: 2.0, height: 1.2, style: 'clerestory', lightColor: '#fffaf0', lightIntensity: 2.5 }
),
trackLights: 0.7,
}
),
37: mk(
'romantic-gothic-revival',
'Romantic gallery',
'Dark walnut paneling · pointed windows · Persian carpet tones',
{ wall: 'walnut-panel', ceiling: 'dark-plaster', floor: 'parquet-herringbone' },
{ wall: '#5a4030', ceiling: '#2a2420', floor: '#6a5040', trim: '#8a7050' },
{
details: 'salon',
titleColor: '#e8dcc8',
ambient: 0.5,
warmLight: '#ffe0c0',
windows: windows(
{ wall: 'back', x: -1.5, y: 2.6, width: 0.9, height: 2.2, style: 'gothic-lancet', lightColor: '#c8d8ff', lightIntensity: 2.6 },
{ wall: 'back', x: 1.5, y: 2.6, width: 0.9, height: 2.2, style: 'gothic-lancet', lightColor: '#c8d8ff', lightIntensity: 2.6 },
{ wall: 'right', x: 0, y: 2.5, width: 1.4, height: 1.6, style: 'sash', lightColor: '#e8f0ff', lightIntensity: 2.2 }
),
}
),
38: mk(
'realist-bourgeois',
'Realist picture gallery',
'Warm plaster · bourgeois salon · oak parquet',
{ wall: 'plaster-warm', ceiling: 'plaster-warm', floor: 'parquet-herringbone' },
{ wall: '#e8e2d8', ceiling: '#f5f0e8', floor: '#a08060', trim: '#8a7050' },
{
details: 'salon',
windows: windows(
{ wall: 'back', x: 0, y: 2.5, width: 2.4, height: 1.6, style: 'sash', lightColor: '#f0f4ff', lightIntensity: 3.2 },
{ wall: 'left', x: 0, y: 2.7, width: 1.6, height: 1.3, style: 'sash', lightColor: '#f0f4ff', lightIntensity: 2.4 }
),
}
),
39: mk(
'impressionist-salon',
'Impressionist salon',
'North-light skylight · pale walls · herringbone floor',
{ wall: 'plaster-warm', ceiling: 'plaster-white', floor: 'parquet-herringbone' },
{ wall: '#f0ebe3', ceiling: '#fafafa', floor: '#c8a882', trim: '#c9a96e' },
{
details: 'salon',
ambient: 0.68,
windows: windows(
{ wall: 'ceiling', x: 0, y: 0, width: 5.0, height: 3.0, style: 'skylight', lightColor: '#ffffff', lightIntensity: 6.0 },
{ wall: 'back', x: -2, y: 2.6, width: 1.2, height: 1.4, style: 'sash', lightColor: '#f0f8ff', lightIntensity: 2.5 },
{ wall: 'back', x: 2, y: 2.6, width: 1.2, height: 1.4, style: 'sash', lightColor: '#f0f8ff', lightIntensity: 2.5 }
),
trackLights: 0.5,
}
),
40: mk(
'post-impressionist-atelier',
'Montmartre atelier',
'Studio walls · large north window · worn floorboards',
{ wall: 'plaster-warm', ceiling: 'plaster-warm', floor: 'parquet-herringbone' },
{ wall: '#e8e0d0', ceiling: '#f0ebe0', floor: '#9a7858', trim: '#8a6848' },
{
details: 'atelier',
windows: windows(
{ wall: 'left', x: 0, y: 2.4, width: 2.8, height: 2.0, style: 'factory', lightColor: '#f0f8ff', lightIntensity: 4.5 },
{ wall: 'back', x: 0, y: 2.8, width: 1.0, height: 1.2, style: 'sash', lightColor: '#f0f4ff', lightIntensity: 2.0 }
),
}
),
41: mk(
'symbolist-chamber',
'Symbolist chamber',
'Deep emerald walls · amber window glow · dark parquet',
{ wall: 'velvet-emerald', ceiling: 'dark-plaster', floor: 'parquet-herringbone' },
{ wall: '#1a4030', ceiling: '#1a1814', floor: '#4a3828', trim: '#8a7050' },
{
details: 'salon',
titleColor: '#d8e8c8',
ambient: 0.55,
warmLight: '#ffd898',
windows: windows(
{ wall: 'back', x: 0, y: 2.4, width: 1.0, height: 1.8, style: 'sash', lightColor: '#ffb860', lightIntensity: 2.0 },
{ wall: 'right', x: 0, y: 2.6, width: 0.8, height: 1.4, style: 'gothic-lancet', lightColor: '#ffd080', lightIntensity: 1.6 }
),
trackLights: 0.9,
}
),
42: mk(
'art-nouveau-salon',
'Art Nouveau salon',
'Organic plaster · stained glass · terrazzo floor',
{ wall: 'silk-pale', ceiling: 'silk-gold', floor: 'terrazzo' },
{ wall: '#f0ece4', ceiling: '#d8c890', floor: '#d8d0c4', trim: '#6a9868' },
{
details: 'salon',
titleColor: '#3a5840',
windows: windows(
{ wall: 'back', x: 0, y: 2.5, width: 2.2, height: 2.0, style: 'art-nouveau', lightColor: '#e8ffe8', lightIntensity: 3.2 },
{ wall: 'left', x: 0, y: 2.6, width: 1.4, height: 1.8, style: 'art-nouveau', lightColor: '#ffe8f0', lightIntensity: 2.4 },
{ wall: 'ceiling', x: 0, y: 0, width: 2.0, height: 1.2, style: 'skylight', lightColor: '#ffffff', lightIntensity: 2.8 }
),
}
),
43: mk(
'fauvist-studio',
'Fauvist studio',
'Bold warm plaster · flooded with color and light',
{ wall: 'stucco-terracotta', wallSide: 'silk-gold', ceiling: 'plaster-warm', floor: 'parquet-herringbone' },
{ wall: '#e89060', wallSide: '#e8c860', ceiling: '#f8f0e0', floor: '#a07048', trim: '#c04020' },
{
details: 'atelier',
titleColor: '#402010',
ambient: 0.7,
windows: windows(
{ wall: 'left', x: 0, y: 2.5, width: 3.0, height: 2.2, style: 'factory', lightColor: '#fff8f0', lightIntensity: 5.0 },
{ wall: 'back', x: 0, y: 2.8, width: 1.6, height: 1.2, style: 'sash', lightColor: '#fff0e0', lightIntensity: 2.5 }
),
}
),
44: mk(
'expressionist-room',
'Expressionist room',
'Angular wood panels · dramatic raking light',
{ wall: 'dark-wood-panel', ceiling: 'dark-plaster', floor: 'parquet-herringbone' },
{ wall: '#3a2818', ceiling: '#2a2018', floor: '#5a4030', trim: '#8a6040' },
{
details: 'atelier',
titleColor: '#f0d8b0',
ambient: 0.52,
windows: windows(
{ wall: 'back', x: -1.5, y: 2.6, width: 1.0, height: 1.6, style: 'sash', lightColor: '#ffe8c0', lightIntensity: 2.8 },
{ wall: 'right', x: 0, y: 2.4, width: 2.0, height: 1.8, style: 'factory', lightColor: '#fff0d8', lightIntensity: 3.5 }
),
}
),
45: mk(
'cubist-studio',
'Cubist studio',
'Paris atelier · factory windows · raw plaster',
{ wall: 'plaster-warm', ceiling: 'plaster-white', floor: 'parquet-herringbone' },
{ wall: '#e8e4dc', ceiling: '#f5f5f5', floor: '#9a8870', trim: '#888888' },
{
details: 'atelier',
ambient: 0.65,
windows: windows(
{ wall: 'left', x: 0, y: 2.5, width: 3.5, height: 2.4, style: 'factory', lightColor: '#f0f8ff', lightIntensity: 5.5 },
{ wall: 'back', x: 0, y: 3.0, width: 1.8, height: 1.0, style: 'clerestory', lightColor: '#ffffff', lightIntensity: 2.5 }
),
}
),
46: mk(
'futurist-loft',
'Futurist loft',
'Steel and glass · industrial concrete · sweeping daylight',
{ wall: 'concrete-raw', ceiling: 'concrete-raw', floor: 'industrial-floor' },
{ wall: '#a8a8a8', ceiling: '#989898', floor: '#888880', trim: '#606060' },
{
details: 'industrial',
titleColor: '#303030',
ambient: 0.62,
fog: '#181818',
windows: windows(
{ wall: 'back', x: 0, y: 2.6, width: 4.0, height: 2.4, style: 'factory', lightColor: '#ffffff', lightIntensity: 6.0 },
{ wall: 'left', x: 0, y: 2.8, width: 2.5, height: 1.6, style: 'factory', lightColor: '#f0f8ff', lightIntensity: 3.5 },
{ wall: 'ceiling', x: 0, y: 0, width: 3.0, height: 2.0, style: 'skylight', lightColor: '#ffffff', lightIntensity: 4.0 }
),
doorWood: ['#606060', '#707070', '#505050'],
}
),
47: mk(
'suprematist-gallery',
'Suprematist white cube',
'Pure white volume · geometric light · polished floor',
{ wall: 'white-plaster', ceiling: 'white-plaster', floor: 'concrete-polished' },
{ wall: '#ffffff', ceiling: '#ffffff', floor: '#e0e0e0', trim: '#cccccc' },
{
details: 'modern',
titleColor: '#222222',
ambient: 0.72,
fog: '#1a1a1a',
background: '#111111',
windows: windows(
{ wall: 'ceiling', x: 0, y: 0, width: 4.5, height: 3.0, style: 'skylight', lightColor: '#ffffff', lightIntensity: 6.5 },
{ wall: 'back', x: 0, y: 2.8, width: 2.0, height: 1.2, style: 'clerestory', lightColor: '#ffffff', lightIntensity: 3.0 }
),
trackLights: 0.4,
doorWood: ['#aaaaaa', '#bbbbbb', '#999999'],
}
),
48: mk(
'constructivist-space',
'Constructivist space',
'Glass block · concrete · angular daylight',
{ wall: 'concrete-block', ceiling: 'concrete-raw', floor: 'concrete-polished' },
{ wall: '#b0b0b0', ceiling: '#a0a0a0', floor: '#c0c0c0', trim: '#808080' },
{
details: 'industrial',
titleColor: '#303030',
ambient: 0.65,
windows: windows(
{ wall: 'back', x: 0, y: 2.5, width: 2.8, height: 2.0, style: 'glass-block', lightColor: '#f0f8ff', lightIntensity: 4.0 },
{ wall: 'left', x: 0, y: 2.6, width: 2.0, height: 1.8, style: 'glass-block', lightColor: '#f0f8ff', lightIntensity: 3.2 },
{ wall: 'ceiling', x: 0, y: 0, width: 2.5, height: 1.5, style: 'skylight', lightColor: '#ffffff', lightIntensity: 3.5 }
),
doorWood: ['#707070', '#808080', '#606060'],
}
),
49: mk(
'dada-salon',
'Dada salon',
'Eclectic bourgeois room · mismatched light sources',
{ wall: 'plaster-warm', wallSide: 'brick', ceiling: 'plaster-warm', floor: 'parquet-herringbone' },
{ wall: '#e8dcc8', wallSide: '#8a5040', ceiling: '#f0ebe0', floor: '#8a6848', trim: '#6a4830' },
{
details: 'salon',
windows: windows(
{ wall: 'back', x: -1.5, y: 2.5, width: 1.0, height: 1.4, style: 'sash', lightColor: '#f0f4ff', lightIntensity: 2.5 },
{ wall: 'back', x: 1.5, y: 2.6, width: 0.8, height: 1.8, style: 'gothic-lancet', lightColor: '#ffe8c0', lightIntensity: 2.0 },
{ wall: 'right', x: 0, y: 2.4, width: 1.6, height: 1.6, style: 'factory', lightColor: '#ffffff', lightIntensity: 3.0 }
),
trackLights: 1.0,
}
),
50: mk(
'surrealist-interior',
'Surrealist interior',
'Bourgeois wallpaper tones · uncanny warm light',
{ wall: 'velvet-navy', ceiling: 'dark-plaster', floor: 'parquet-herringbone' },
{ wall: '#1a2848', ceiling: '#2a2428', floor: '#5a4838', trim: '#8a7050' },
{
details: 'salon',
titleColor: '#e8dcc8',
ambient: 0.58,
warmLight: '#ffd898',
windows: windows(
{ wall: 'back', x: 0, y: 2.5, width: 1.4, height: 1.6, style: 'sash', lightColor: '#ffe8c8', lightIntensity: 2.8 },
{ wall: 'left', x: 0, y: 2.7, width: 1.0, height: 1.2, style: 'sash', lightColor: '#c8e0ff', lightIntensity: 2.0 },
{ wall: 'ceiling', x: 0, y: 0, width: 1.5, height: 1.0, style: 'skylight', lightColor: '#ffffff', lightIntensity: 2.5 }
),
trackLights: 0.85,
}
),
51: mk(
'abstract-expressionist-loft',
'NYC loft',
'Raw brick and plaster · north-light factory windows',
{ wall: 'brick', wallSide: 'plaster-white', ceiling: 'concrete-raw', floor: 'industrial-floor' },
{ wall: '#8a5040', wallSide: '#f5f5f5', ceiling: '#989898', floor: '#888880', trim: '#606060' },
{
details: 'industrial',
titleColor: '#303030',
ambient: 0.65,
windows: windows(
{ wall: 'left', x: 0, y: 2.5, width: 4.0, height: 2.6, style: 'factory', lightColor: '#f0f8ff', lightIntensity: 6.5 },
{ wall: 'ceiling', x: 0, y: 0, width: 3.5, height: 2.0, style: 'skylight', lightColor: '#ffffff', lightIntensity: 4.5 }
),
}
),
52: mk(
'pop-art-gallery',
'Pop Art gallery',
'White cube · fluorescent daylight · polished concrete',
{ wall: 'studio-white', ceiling: 'studio-white', floor: 'concrete-polished' },
{ wall: '#ffffff', ceiling: '#ffffff', floor: '#d8d8d8', trim: '#cccccc' },
{
details: 'modern',
titleColor: '#222222',
ambient: 0.75,
warmLight: '#ffffff',
fog: '#1a1a1a',
background: '#101010',
windows: windows(
{ wall: 'back', x: 0, y: 2.6, width: 3.0, height: 2.0, style: 'factory', lightColor: '#ffffff', lightIntensity: 5.5 },
{ wall: 'ceiling', x: 0, y: 0, width: 5.0, height: 3.0, style: 'skylight', lightColor: '#ffffff', lightIntensity: 6.0 }
),
trackLights: 0.6,
doorWood: ['#888888', '#999999', '#777777'],
}
),
};
function blendAccent(style: MovementInteriorStyle, accentHex?: string): MovementInteriorStyle {
if (!accentHex) return style;
const w = 0.08;
const mix = (a: string, b: string) => {
const pa = parseInt(a.replace('#', ''), 16);
const pb = parseInt(b.replace('#', ''), 16);
const r = Math.round(((pa >> 16) & 255) * w + ((pb >> 16) & 255) * (1 - w));
const g = Math.round(((pa >> 8) & 255) * w + ((pb >> 8) & 255) * (1 - w));
const bl = Math.round((pa & 255) * w + (pb & 255) * (1 - w));
return `#${((r << 16) | (g << 8) | bl).toString(16).padStart(6, '0')}`;
};
return {
...style,
tints: {
...style.tints,
wall: mix(accentHex, style.tints.wall),
wallSide: style.tints.wallSide ? mix(accentHex, style.tints.wallSide) : undefined,
trim: mix(accentHex, style.tints.trim),
},
};
}
/** Fallback name-based resolver for movements not in the catalog. */
function fallbackByName(movement: ArtMovement & { era_name?: string }): MovementInteriorStyle {
const n = movement.name.toLowerCase();
const era = (movement.era_name ?? '').toLowerCase();
if (n.includes('renaissance')) return BY_MOVEMENT_ID[31];
if (n.includes('baroque') || n.includes('rococo')) return BY_MOVEMENT_ID[34];
if (era.includes('medieval') || n.includes('gothic')) return BY_MOVEMENT_ID[29];
if (n.includes('impression')) return BY_MOVEMENT_ID[39];
if (era.includes('modern') || era.includes('contemporary')) return BY_MOVEMENT_ID[52];
return BY_MOVEMENT_ID[36];
}
export function resolveMovementInteriorStyle(
movement: ArtMovement & { era_name?: string }
): MovementInteriorStyle {
const base = BY_MOVEMENT_ID[movement.id] ?? fallbackByName(movement);
return blendAccent(base, movement.color);
}
/** @deprecated use surfaces.floor — kept for transitional imports */
export type GalleryFloorKind = string;