Movement halls routed 24 of the 26 movements through 8 generic detail kinds, four of which (modern, industrial, atelier, museum) rendered nothing at all, and `salon` was a single ceiling ring shared by eight movements. `details` is now a 26-value MovementDetailKind, one per movement, each built from the architecture of the same period as the movement itself. Gothic and Byzantine are unchanged and stay in MovementHallDetails.tsx as the reference implementations; the rest live under components/hall-details/, split by era, over shared wall-flush primitives. hall-details/geometry.ts records the budget the Gothic and Byzantine components established, since frames hang 0.23 m proud with a 0.7 m clear margin at each wall end: relief limits above and below the frame line, corner pockets for freestanding masses, doorway and title-band clearance, and a cap of two added lights per hall. Halls now pass their computed windows down so upper-wall panels, arches and roundels step around the openings. Also holds the pale interiors back from blowing out: gallery wall tints are authored around 0.50-0.56 luminance instead of near-white, and the 19 halls that read too bright take lightScale 0.30, matching the basilica. Verified by rendering all 26 interiors offline at two hall sizes and measuring the scene graph: nothing through walls, floor or ceiling, maximum 0.17 m intrusion into the hang zone, at most two added lights per hall. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
741 lines
30 KiB
TypeScript
741 lines
30 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';
|
||
|
||
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: '#c8b898', 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: '#c89070', 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 · coffered ceiling · checker floor',
|
||
{ wall: 'marble-veined-carrara', wallSide: 'stucco-cream', ceiling: 'plaster-warm', floor: 'marble-checker' },
|
||
{ wall: '#918f8a', wallSide: '#8f8980', ceiling: '#9c9994', floor: '#ddd8cc', 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: 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: '#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: '#dce8e0', 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: '#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 painted walls · gilt trim · chevron parquet',
|
||
{ wall: 'painted-oil-satin', ceiling: 'silk-pale', floor: 'parquet-chevrons' },
|
||
{ wall: '#918a91', ceiling: '#9b9995', floor: '#c8a882', 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',
|
||
'White painted walls · coffered ceiling · skylit salon',
|
||
{ wall: 'painted-flat', ceiling: 'plaster-white', floor: 'marble-veined-carrara' },
|
||
{ wall: '#908f8d', ceiling: '#9e9e9e', floor: '#eceae6', trim: '#b8b0a4' },
|
||
{
|
||
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.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: '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: '#c8a882', 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: '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 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: '#d8d0c4', 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: '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: '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: 'factory', lightColor: '#fff0d8', lightIntensity: 3.5 }
|
||
),
|
||
}
|
||
),
|
||
45: mk(
|
||
'cubist-studio',
|
||
'Cubist studio',
|
||
'Paris atelier · factory 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: '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: '#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.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',
|
||
'Matte white paint · geometric light · polished floor',
|
||
{ wall: 'painted-flat', ceiling: 'painted-flat', floor: 'concrete-polished' },
|
||
{ wall: '#a8a8a8', ceiling: '#b3b3b3', floor: '#e0e0e0', 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: '#c0c0c0', 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 salon',
|
||
'Eclectic bourgeois room · painted walls and exposed brick',
|
||
{ wall: 'painted-emulsion', wallSide: 'brick', ceiling: 'painted-emulsion', floor: 'parquet-herringbone' },
|
||
{ wall: '#8b8478', wallSide: '#8a5040', ceiling: '#97948d', floor: '#8a6848', trim: '#6a4830' },
|
||
{
|
||
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,
|
||
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',
|
||
'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',
|
||
'NYC loft',
|
||
'Raw brick and painted plaster · north-light factory windows',
|
||
{ wall: 'brick', wallSide: 'painted-flat', ceiling: 'concrete-raw', floor: 'industrial-floor' },
|
||
{ wall: '#8a5040', wallSide: '#858585', ceiling: '#989898', floor: '#888880', trim: '#606060' },
|
||
{
|
||
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: '#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 painted walls · fluorescent daylight · polished concrete',
|
||
{ wall: 'painted-flat', ceiling: 'painted-flat', floor: 'concrete-polished' },
|
||
{ wall: '#a8a8a8', ceiling: '#b3b3b3', floor: '#d8d8d8', 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: '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),
|
||
},
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 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;
|