Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4088d7d57b | ||
|
|
f20f811f21 |
@@ -494,7 +494,7 @@ Each painting includes:
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `has_influence_links` | `true` when the work appears in any influence row — 3D gallery shows a golden lamp above the frame |
|
||||
| `has_influence_links` | `true` when the work appears in any influence row — 3D gallery shows a golden lamp on a shared rail (40 cm above the tallest frame in the hall) |
|
||||
| `checkup_checked` | Reviewed in checkup / debug workflow (gold frame in 3D when true) |
|
||||
| `checkup_fixed` | Image replaced via **Fix it** |
|
||||
|
||||
|
||||
@@ -301,7 +301,7 @@ Each artist has **exactly one hall**. The hall is a rectangular room sized to fi
|
||||
| Floor | Herringbone **parquet** procedural texture (`parquetFloorTexture.ts`) |
|
||||
| Wall tint | Gallery walls blend the artist’s **movement colour** into cream plaster tones |
|
||||
| Frame finish | **Unchecked** works: black moulding; **Reviewed** (`checkup_checked`): bright gold moulding at **double width** |
|
||||
| Influence lamps | A golden picture light appears **above frames** whose work has any influence-graph edge (`has_influence_links` from the API); fixture is mounted **upside down** and is **emissive-only** (no per-frame lights — see light budget below) |
|
||||
| Influence lamps | A golden picture light marks works with any influence-graph edge (`has_influence_links` from the API). All lamps in a hall share one rail height: **40 cm above the tallest allocated frame** in that hall (so they line up regardless of canvas size). Fixture is mounted **upside down** and is **emissive-only** (no per-frame lights — see light budget below) |
|
||||
| Curator-note plates | A small brass plate hangs **beneath frames** that have non-empty `curator_notes` |
|
||||
| Eye-level viewing | Frame centres sit at **eye height (~1.65 m)**; the camera stays **level with the floor** (no pitch up/down) |
|
||||
| Open centre | Floor and ceiling only — no freestanding columns or pedestals in the walkway |
|
||||
|
||||
@@ -191,8 +191,8 @@ function galleryWallColors(movementColor?: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/** Height above frame top edge — keeps fixture out of the viewer's line of sight. */
|
||||
const INFLUENCE_LAMP_ABOVE_FRAME = 0.46;
|
||||
/** Clearance above the tallest frame in the hall for influence lamps (metres). */
|
||||
const INFLUENCE_LAMP_ABOVE_HIGHEST_M = 0.4;
|
||||
|
||||
type WallSide = 'back' | 'left' | 'right';
|
||||
|
||||
@@ -304,6 +304,20 @@ function frameOuterH(canvasH: number, reviewed: boolean): number {
|
||||
return canvasH + matBorder * 2 + rail;
|
||||
}
|
||||
|
||||
/** World Y of the top edge of the tallest allocated frame in the hall. */
|
||||
function highestPaintingTopY(segments: WallSegment[]): number {
|
||||
let maxTop = EYE_HEIGHT;
|
||||
for (const seg of segments) {
|
||||
for (let i = 0; i < seg.paintings.length; i++) {
|
||||
const slot = seg.slots[i];
|
||||
if (!slot) continue;
|
||||
const top = slot.position[1] + frameOuterH(slot.maxH, paintingIsReviewed(seg.paintings[i])) / 2;
|
||||
if (top > maxTop) maxTop = top;
|
||||
}
|
||||
}
|
||||
return maxTop;
|
||||
}
|
||||
|
||||
function layoutRow(paintings: Painting[], span: number) {
|
||||
const count = paintings.length;
|
||||
if (count === 0) return { slots: [], spanNeeded: span };
|
||||
@@ -803,17 +817,19 @@ function usePaintingTexture(urls: string[] | string | null) {
|
||||
}
|
||||
|
||||
function InfluencePictureLamp({
|
||||
frameHeight,
|
||||
matBorder,
|
||||
frameWorldY,
|
||||
lampWorldY,
|
||||
frameDepth,
|
||||
highlighted,
|
||||
}: {
|
||||
frameHeight: number;
|
||||
matBorder: number;
|
||||
/** Painting group world Y — local lamp offset = lampWorldY − frameWorldY. */
|
||||
frameWorldY: number;
|
||||
/** Shared hall rail: 40 cm above the tallest frame. */
|
||||
lampWorldY: number;
|
||||
frameDepth: number;
|
||||
highlighted: boolean;
|
||||
}) {
|
||||
const mountY = frameHeight / 2 + matBorder + INFLUENCE_LAMP_ABOVE_FRAME;
|
||||
const mountY = lampWorldY - frameWorldY;
|
||||
const glow = highlighted ? 1.6 : 1.15;
|
||||
const scale = 1.35;
|
||||
|
||||
@@ -918,6 +934,7 @@ function PaintingFrame({
|
||||
wallSide,
|
||||
imageRevision,
|
||||
caption,
|
||||
influenceLampWorldY,
|
||||
onClick,
|
||||
}: {
|
||||
painting: Painting;
|
||||
@@ -928,6 +945,7 @@ function PaintingFrame({
|
||||
wallSide: WallSide;
|
||||
imageRevision?: number;
|
||||
caption?: string;
|
||||
influenceLampWorldY: number;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
@@ -1018,8 +1036,8 @@ function PaintingFrame({
|
||||
|
||||
{hasInfluenceLinks && (
|
||||
<InfluencePictureLamp
|
||||
frameHeight={height}
|
||||
matBorder={matBorder}
|
||||
frameWorldY={position[1]}
|
||||
lampWorldY={influenceLampWorldY}
|
||||
frameDepth={frameDepth}
|
||||
highlighted={hovered}
|
||||
/>
|
||||
@@ -1505,6 +1523,10 @@ function ArtistHall({
|
||||
const hallLightScale = interiorStyle?.lightScale ?? 1;
|
||||
const wallRoughness = interiorStyle ? 0.75 : 0.92;
|
||||
const wallMetalness = interiorStyle ? 0.08 : 0.06;
|
||||
const influenceLampWorldY = useMemo(
|
||||
() => highestPaintingTopY(segments) + INFLUENCE_LAMP_ABOVE_HIGHEST_M,
|
||||
[segments]
|
||||
);
|
||||
|
||||
const wallMaterial = (color: string) => (
|
||||
<meshStandardMaterial color={color} roughness={wallRoughness} metalness={wallMetalness} />
|
||||
@@ -1752,6 +1774,7 @@ function ArtistHall({
|
||||
wallSide={seg.slots[i].side}
|
||||
imageRevision={imageRevisions?.[painting.id]}
|
||||
caption={showCaptions ? paintingWallCaption(painting) : undefined}
|
||||
influenceLampWorldY={influenceLampWorldY}
|
||||
onClick={() => onPaintingClick(painting.id)}
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user