Document hang keep-outs and period-module fixes; leave columns fixed while packing frames into gaps. Co-authored-by: Cursor <cursoragent@cursor.com>
755 lines
31 KiB
TypeScript
755 lines
31 KiB
TypeScript
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'
|
||
/** Thin frame, open glass — no mullion grid (modern / Dada cabaret). */
|
||
| 'picture'
|
||
/** Thin steel ribbon frame, open glass — Futurist / modern loft daylight. */
|
||
| 'ribbon';
|
||
|
||
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;
|
||
/** Scales the shared hall/scene lights. <1 for deliberately dim period interiors. */
|
||
lightScale: number;
|
||
/**
|
||
* Which period architecture the hall is built from. Every movement has its
|
||
* own — the interiors are not shared between movements — and each kind maps
|
||
* to one component under `components/hall-details/`.
|
||
*/
|
||
details: MovementDetailKind;
|
||
}
|
||
|
||
export type MovementDetailKind =
|
||
| 'roman'
|
||
| 'byzantine'
|
||
| 'gothic'
|
||
| 'quattrocento'
|
||
| 'cinquecento'
|
||
| 'flemish'
|
||
| 'mannerist'
|
||
| 'baroque'
|
||
| 'rococo'
|
||
| 'neoclassical'
|
||
| 'gothic-revival'
|
||
| 'bourgeois'
|
||
| 'north-light'
|
||
| 'paris-atelier'
|
||
| 'symbolist'
|
||
| 'art-nouveau'
|
||
| 'fauvist'
|
||
| 'expressionist'
|
||
| 'cubist-atelier'
|
||
| 'futurist'
|
||
| 'suprematist'
|
||
| 'constructivist'
|
||
| 'dada'
|
||
| 'surrealist'
|
||
| 'loft'
|
||
| 'white-cube';
|
||
|
||
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,
|
||
lightScale: opts.lightScale ?? 1,
|
||
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: '#8e8980', ceiling: '#9c9994', floor: '#a08868', trim: '#a89878' },
|
||
{
|
||
details: 'roman',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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 basilica',
|
||
'Marble revetment · coffered timber ceiling · Cosmatesque stone floor',
|
||
{ wall: 'marble-revetment', ceiling: 'coffered-wood', floor: 'marble-opus-sectile' },
|
||
{ wall: '#a89880', ceiling: '#6a4526', floor: '#a09079', trim: '#8a6440' },
|
||
{
|
||
details: 'byzantine',
|
||
titleColor: '#e8d8b0',
|
||
ambient: 0.44,
|
||
warmLight: '#ffdca8',
|
||
sunLight: '#ffdca0',
|
||
fog: '#100b07',
|
||
background: '#0a0705',
|
||
windows: windows(
|
||
{ wall: 'back', x: 0, y: 3.1, width: 0.9, height: 1.9, style: 'roman-arch', lightColor: '#ffd28a', lightIntensity: 3.4 },
|
||
{ wall: 'left', x: -3.5, y: 3.2, width: 0.7, height: 1.6, style: 'roman-arch', lightColor: '#ffd28a', lightIntensity: 3.0 },
|
||
{ wall: 'left', x: 3.5, y: 3.2, width: 0.7, height: 1.6, style: 'roman-arch', lightColor: '#ffd28a', lightIntensity: 3.0 },
|
||
{ wall: 'right', x: -3.5, y: 3.2, width: 0.7, height: 1.6, style: 'roman-arch', lightColor: '#ffd28a', lightIntensity: 3.0 },
|
||
{ wall: 'right', x: 3.5, y: 3.2, width: 0.7, height: 1.6, style: 'roman-arch', lightColor: '#ffd28a', lightIntensity: 3.0 }
|
||
),
|
||
trackLights: 0.35,
|
||
// Basilica interiors are lit by shafts from high windows, not evenly flooded.
|
||
lightScale: 0.3,
|
||
doorWood: ['#3a2416', '#4c3220', '#241608'],
|
||
}
|
||
),
|
||
29: mk(
|
||
'gothic-cathedral',
|
||
'Gothic nave',
|
||
'Blind-arcaded ashlar · ribbed stone vault · worn flagstones',
|
||
{ wall: 'gothic-ashlar', ceiling: 'gothic-vault', floor: 'gothic-stone-floor' },
|
||
{ wall: '#8d8471', ceiling: '#6d6555', floor: '#6f6759', trim: '#7d7159' },
|
||
{
|
||
details: 'gothic',
|
||
titleColor: '#efe4cc',
|
||
ambient: 0.6,
|
||
warmLight: '#ffe6bc',
|
||
sunLight: '#dbe8ff',
|
||
// Cool shadowed stone, but never a pure void — walls must stay readable.
|
||
fog: '#1b1a18',
|
||
background: '#131211',
|
||
windows: windows(
|
||
{ wall: 'back', x: -2, y: 2.8, width: 0.8, height: 2.8, style: 'gothic-lancet', lightColor: '#c8e0ff', lightIntensity: 3.8 },
|
||
{ wall: 'back', x: 2, y: 2.8, width: 0.8, height: 2.8, style: 'gothic-lancet', lightColor: '#c8e0ff', lightIntensity: 3.8 },
|
||
{ wall: 'left', x: -4.5, y: 3.0, width: 0.9, height: 2.6, style: 'gothic-lancet', lightColor: '#c8e0ff', lightIntensity: 3.2 },
|
||
{ wall: 'left', x: 4.5, y: 3.0, width: 0.9, height: 2.6, style: 'gothic-lancet', lightColor: '#c8e0ff', lightIntensity: 3.2 },
|
||
{ wall: 'right', x: -4.5, y: 3.0, width: 0.9, height: 2.6, style: 'gothic-lancet', lightColor: '#c8e0ff', lightIntensity: 3.2 },
|
||
{ wall: 'right', x: 4.5, y: 3.0, width: 0.9, height: 2.6, style: 'gothic-lancet', lightColor: '#c8e0ff', lightIntensity: 3.2 }
|
||
),
|
||
trackLights: 0.5,
|
||
// Naves are lit by window shafts against shadowed stone, not flooded.
|
||
lightScale: 0.26,
|
||
}
|
||
),
|
||
30: mk(
|
||
'early-renaissance-palazzo',
|
||
'Florentine palazzo',
|
||
'Lime-washed walls · terracotta accents · arched windows',
|
||
{ wall: 'painted-lime', wallSide: 'stucco-cream', ceiling: 'fresco-worn', floor: 'terracotta-tiles' },
|
||
{ wall: '#89847e', ceiling: '#96948f', floor: '#a07050', trim: '#c9a227' },
|
||
{
|
||
details: 'quattrocento',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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 · calm coffered ceiling · veined stone floor',
|
||
{ wall: 'marble-veined-carrara', wallSide: 'stucco-cream', ceiling: 'plaster-warm', floor: 'marble-veined-carrara' },
|
||
{ wall: '#918f8a', wallSide: '#8f8980', ceiling: '#9c9994', floor: '#9a9080', trim: '#c9a227' },
|
||
{
|
||
details: 'cinquecento',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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: 3.2 }
|
||
),
|
||
}
|
||
),
|
||
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: '#8c8985', ceiling: '#3a2818', floor: '#8a6848', trim: '#5c4030' },
|
||
{
|
||
details: 'flemish',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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: '#98816b', ceiling: '#a79456', floor: '#7a8a82', trim: '#b8860b' },
|
||
{
|
||
details: 'mannerist',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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: '#8a8078', 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 painted walls · gilt trim · chevron parquet',
|
||
{ wall: 'painted-oil-satin', ceiling: 'silk-pale', floor: 'parquet-chevrons' },
|
||
{ wall: '#918a91', ceiling: '#9b9995', floor: '#9a7858', trim: '#d4af37' },
|
||
{
|
||
details: 'rococo',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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',
|
||
'Cool painted walls · coffered ceiling · subdued stone floor',
|
||
{ wall: 'painted-flat', ceiling: 'plaster-white', floor: 'marble-veined-carrara' },
|
||
{ wall: '#908f8d', ceiling: '#9e9e9e', floor: '#8a8680', trim: '#a0988c' },
|
||
{
|
||
details: 'neoclassical',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
titleColor: '#4a4844',
|
||
ambient: 0.58,
|
||
windows: windows(
|
||
{ wall: 'back', x: 0, y: 2.6, width: 2.8, height: 2.0, style: 'baroque-pair', lightColor: '#f4f2ec', lightIntensity: 3.2 },
|
||
{ wall: 'ceiling', x: 0, y: 0, width: 4.0, height: 2.5, style: 'skylight', lightColor: '#f0eee8', lightIntensity: 3.2 },
|
||
{ wall: 'left', x: 0, y: 3.0, width: 2.0, height: 1.2, style: 'clerestory', lightColor: '#fffaf0', lightIntensity: 2.2 }
|
||
),
|
||
trackLights: 0.65,
|
||
}
|
||
),
|
||
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: 'gothic-revival',
|
||
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 painted walls · bourgeois salon · oak parquet',
|
||
{ wall: 'painted-emulsion', ceiling: 'painted-emulsion', floor: 'parquet-herringbone' },
|
||
{ wall: '#88847e', ceiling: '#97948f', floor: '#a08060', trim: '#8a7050' },
|
||
{
|
||
details: 'bourgeois',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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 painted walls · herringbone floor',
|
||
{ wall: 'painted-emulsion', ceiling: 'painted-flat', floor: 'parquet-herringbone' },
|
||
{ wall: '#8f8c87', ceiling: '#9e9e9e', floor: '#9a7858', trim: '#c9a96e' },
|
||
{
|
||
details: 'north-light',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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',
|
||
'Painted studio walls · large north window · worn floorboards',
|
||
{ wall: 'painted-emulsion', ceiling: 'painted-emulsion', floor: 'parquet-herringbone' },
|
||
{ wall: '#847f76', ceiling: '#918f88', floor: '#9a7858', trim: '#8a6848' },
|
||
{
|
||
details: 'paris-atelier',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
windows: windows(
|
||
{ wall: 'left', x: 0, y: 2.4, width: 2.8, height: 2.0, style: 'ribbon', 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 green painted walls · amber window glow · dark parquet',
|
||
{ wall: 'painted-oil-matte', ceiling: 'dark-plaster', floor: 'parquet-herringbone' },
|
||
{ wall: '#1a4030', ceiling: '#1a1814', floor: '#4a3828', trim: '#8a7050' },
|
||
{
|
||
details: 'symbolist',
|
||
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',
|
||
'Painted plaster · stained glass · terrazzo floor',
|
||
{ wall: 'painted-oil-satin', ceiling: 'silk-gold', floor: 'terrazzo' },
|
||
{ wall: '#8f8c87', ceiling: '#a0946b', floor: '#9a9084', trim: '#6a9868' },
|
||
{
|
||
details: 'art-nouveau',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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 painted walls · flooded with color and light',
|
||
{ wall: 'painted-oil-matte', wallSide: 'painted-oil-matte', ceiling: 'painted-emulsion', floor: 'parquet-herringbone' },
|
||
{ wall: '#e89060', wallSide: '#e8c860', ceiling: '#9e998e', floor: '#a07048', trim: '#c04020' },
|
||
{
|
||
details: 'fauvist',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
titleColor: '#402010',
|
||
ambient: 0.7,
|
||
windows: windows(
|
||
{ wall: 'left', x: 0, y: 2.5, width: 3.0, height: 2.2, style: 'ribbon', 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: 'expressionist',
|
||
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: 'ribbon', lightColor: '#fff0d8', lightIntensity: 3.5 }
|
||
),
|
||
}
|
||
),
|
||
45: mk(
|
||
'cubist-studio',
|
||
'Cubist studio',
|
||
'Paris atelier · open ribbon windows · painted plaster',
|
||
{ wall: 'painted-emulsion', ceiling: 'painted-flat', floor: 'parquet-herringbone' },
|
||
{ wall: '#8c8a85', ceiling: '#999999', floor: '#9a8870', trim: '#888888' },
|
||
{
|
||
details: 'cubist-atelier',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
ambient: 0.65,
|
||
windows: windows(
|
||
{ wall: 'left', x: 0, y: 2.5, width: 3.5, height: 2.4, style: 'ribbon', 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 open glass · ribbon windows · industrial concrete',
|
||
{ wall: 'concrete-raw', ceiling: 'concrete-raw', floor: 'industrial-floor' },
|
||
{ wall: '#858585', ceiling: '#7a7a7a', floor: '#888880', trim: '#606060' },
|
||
{
|
||
details: 'futurist',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
titleColor: '#303030',
|
||
ambient: 0.62,
|
||
fog: '#181818',
|
||
windows: windows(
|
||
{ wall: 'back', x: 0, y: 2.65, width: 4.2, height: 2.2, style: 'ribbon', lightColor: '#ffffff', lightIntensity: 5.8 },
|
||
{ wall: 'left', x: -1.15, y: 2.75, width: 1.9, height: 1.55, style: 'ribbon', lightColor: '#f0f8ff', lightIntensity: 3.4 },
|
||
{ wall: 'left', x: 1.15, y: 2.75, width: 1.9, height: 1.55, style: 'ribbon', lightColor: '#f0f8ff', lightIntensity: 3.4 },
|
||
{ 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',
|
||
'Matte white paint · geometric light · polished floor',
|
||
{ wall: 'painted-flat', ceiling: 'painted-flat', floor: 'concrete-polished' },
|
||
{ wall: '#a8a8a8', ceiling: '#b3b3b3', floor: '#8e8e8e', trim: '#cccccc' },
|
||
{
|
||
details: 'suprematist',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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: '#858585', ceiling: '#7a7a7a', floor: '#888888', trim: '#808080' },
|
||
{
|
||
details: 'constructivist',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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 cabaret',
|
||
'Modern cabaret · collage panels · open picture windows',
|
||
{ wall: 'painted-flat', wallSide: 'painted-emulsion', ceiling: 'painted-flat', floor: 'concrete-polished' },
|
||
{ wall: '#f0ebe3', wallSide: '#e4d8c4', ceiling: '#f5f1ea', floor: '#949088', trim: '#1a1a1a' },
|
||
{
|
||
details: 'dada',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
titleColor: '#1a1a1a',
|
||
ambient: 0.68,
|
||
warmLight: '#fff6ea',
|
||
sunLight: '#f4f8ff',
|
||
windows: windows(
|
||
{ wall: 'back', x: -1.6, y: 2.55, width: 1.5, height: 1.7, style: 'picture', lightColor: '#eef4ff', lightIntensity: 3.4 },
|
||
{ wall: 'back', x: 1.7, y: 2.7, width: 1.1, height: 1.35, style: 'picture', lightColor: '#fff0d8', lightIntensity: 2.8 },
|
||
{ wall: 'right', x: 0, y: 2.5, width: 2.2, height: 1.9, style: 'picture', lightColor: '#f5f9ff', lightIntensity: 3.8 }
|
||
),
|
||
trackLights: 1.0,
|
||
doorWood: ['#2a2a2a', '#3a3a3a', '#1a1a1a'],
|
||
}
|
||
),
|
||
50: mk(
|
||
'surrealist-interior',
|
||
'Surrealist interior',
|
||
'Dark painted walls · uncanny warm light',
|
||
{ wall: 'painted-oil-matte', ceiling: 'dark-plaster', floor: 'parquet-herringbone' },
|
||
{ wall: '#1a2848', ceiling: '#2a2428', floor: '#5a4838', trim: '#8a7050' },
|
||
{
|
||
details: 'surrealist',
|
||
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',
|
||
'Painter loft',
|
||
'Whitewashed plaster · warm timber · open north-light picture windows',
|
||
{ wall: 'painted-flat', wallSide: 'painted-emulsion', ceiling: 'plaster-warm', floor: 'industrial-floor' },
|
||
{ wall: '#e8e2d6', wallSide: '#ddd5c6', ceiling: '#f0ebe2', floor: '#9a9288', trim: '#6a5848' },
|
||
{
|
||
details: 'loft',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
titleColor: '#3a3028',
|
||
ambient: 0.72,
|
||
warmLight: '#fff6e8',
|
||
sunLight: '#f2f6ff',
|
||
windows: windows(
|
||
{ wall: 'left', x: -1.2, y: 2.55, width: 2.4, height: 2.3, style: 'picture', lightColor: '#f0f6ff', lightIntensity: 5.5 },
|
||
{ wall: 'left', x: 1.4, y: 2.55, width: 2.2, height: 2.3, style: 'picture', lightColor: '#f0f6ff', lightIntensity: 5.2 },
|
||
{ wall: 'ceiling', x: 0, y: 0, width: 3.2, height: 1.8, style: 'skylight', lightColor: '#ffffff', lightIntensity: 4.2 }
|
||
),
|
||
doorWood: ['#5a4838', '#6a5848', '#4a3828'],
|
||
}
|
||
),
|
||
52: mk(
|
||
'pop-art-gallery',
|
||
'Pop Art gallery',
|
||
'White painted walls · fluorescent daylight · polished concrete',
|
||
{ wall: 'painted-flat', ceiling: 'painted-flat', floor: 'concrete-polished' },
|
||
{ wall: '#a8a8a8', ceiling: '#b3b3b3', floor: '#909090', trim: '#cccccc' },
|
||
{
|
||
details: 'white-cube',
|
||
// Lit like the basilica: shared lights held back so the walls read as
|
||
// surfaces rather than blowing out. Window shafts are not scaled.
|
||
lightScale: 0.3,
|
||
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: 'ribbon', 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),
|
||
},
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Styles were authored with legacy ids 27–52; gallery_dev uses 1–26.
|
||
* Northern (5) / High Renaissance (6) are swapped vs the legacy sequence.
|
||
*/
|
||
const DB_ID_TO_STYLE_KEY: Record<number, number> = {
|
||
1: 27,
|
||
2: 28,
|
||
3: 29,
|
||
4: 30,
|
||
5: 32,
|
||
6: 31,
|
||
7: 33,
|
||
8: 34,
|
||
9: 35,
|
||
10: 36,
|
||
11: 37,
|
||
12: 38,
|
||
13: 39,
|
||
14: 40,
|
||
15: 41,
|
||
16: 42,
|
||
17: 43,
|
||
18: 44,
|
||
19: 45,
|
||
20: 46,
|
||
21: 47,
|
||
22: 48,
|
||
23: 49,
|
||
24: 50,
|
||
25: 51,
|
||
26: 52,
|
||
};
|
||
|
||
/** 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('byzantine')) return BY_MOVEMENT_ID[28];
|
||
if (n.includes('gothic')) return BY_MOVEMENT_ID[29];
|
||
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')) 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 styleKey = DB_ID_TO_STYLE_KEY[movement.id] ?? movement.id;
|
||
const base = BY_MOVEMENT_ID[styleKey] ?? fallbackByName(movement);
|
||
return blendAccent(base, movement.color);
|
||
}
|
||
|
||
/** @deprecated use surfaces.floor — kept for transitional imports */
|
||
export type GalleryFloorKind = string;
|