# Art Gallery — architecture basics Interactive virtual museum spanning art history: zoomable timeline, movement bands, 3D gallery halls, and painting influence graphs. See [README.md](../README.md) for quick start. ## Concept The app is organised as a **drill-down hierarchy**: 1. **Timeline** — historical eras (Ancient → Contemporary) with definite or fuzzy date boundaries. 2. **Movement bands** — art movements aligned to the same time axis, each showing portrait thumbnails of key artists. 3. **3D gallery** — one personal hall per artist; paintings on the walls, open centre, single exit for influence-based navigation. 4. **Painting detail** — full work in the centre, *Influenced By* on the left, *Influenced* on the right, prev/next catalog browsing, optional fullscreen, link to artist biography. 5. **Artist biography** — portrait, lifespan, movement, and Wikipedia-sourced intro text (`bio_short` / `bio_full`). All artwork images are stored locally under `data/images/` — the UI never hot-links to Wikipedia or Commons at runtime (except optional on-demand fetch when a file is missing). ## Stack | Layer | Choice | Role | |--------|--------|------| | API | **Node.js + Express 5** | REST API, static image serving, optional SPA hosting from `client/dist` | | Database | **PostgreSQL** | Eras, movements, artists, paintings, influence edges | | Frontend | **React 19 + Vite 8** | SPA routing and UI | | 3D | **Three.js** via `@react-three/fiber`, `@react-three/drei` | Virtual gallery navigation | | Data ingestion | Node scripts | Wikipedia summaries, Commons images, curated influence data | ## Repository layout ```text Gallery/ ├── server/ # Express API, DB pool, image service ├── client/ # React/Vite frontend │ ├── src/ # Source (components, pages, 3D scene) │ └── dist/ # Production build (served by API when present) ├── scripts/ # Seed, bios, catalog expansion, image fetch │ ├── fetch-artist-bios.js │ ├── expand-paintings.js │ ├── famous-paintings-data.js │ ├── fetch-missing-images.js │ └── image-fetcher.js ├── data/images/ # Local portraits and paintings (+ thumbs/) ├── db/ # SQL schema and migrations (when present) ├── Documentation/ # This folder └── .env # DB and port config (not committed) ``` ## Runtime modes ### Production-style (single process) ```bash npm run server # http://localhost:3001 ``` Serves `/api/*`, `/images/*`, and the built SPA from `client/dist` if it exists. ### Development (two processes) ```bash npm run dev:server # API on :3001 npm run dev:client # Vite on :5173, proxies /api and /images ``` Use the Vite URL during frontend work for HMR. ## User navigation flow ```mermaid flowchart TD A[Home — timeline + movements] -->|scroll / drag / zoom| A A -->|click portrait| B[Artist bio — Wikipedia text] B -->|Enter Gallery| C[Artist hall — 3D] C -->|click painting| D[Painting detail + influences] D -->|prev / next| D D -->|click centre image| F[Fullscreen lightbox] F -->|close| D D -->|Back| C C -->|exit doorway / E key| E[Path picker] E -->|predecessors| C E -->|successors| C D -->|influence thumbnail| D D -->|artist link| B B -->|Back| A C -->|Back| A ``` ## Virtual gallery (3D halls) Each artist has **exactly one hall**. The hall is a rectangular room sized to fit their catalog: | Rule | Implementation | |------|----------------| | One hall per artist | `VirtualGallery.tsx` builds a single room from that artist’s paintings | | Catalog depth | Most artists target **≥ 6** notable works via `npm run expand-catalog` and `famous-paintings-data.js`; some masters have larger museum dumps | | Paintings on walls | Works hang on the **back, left, and right** walls; the room **grows and uses multiple rows** when the catalog is large (e.g. 75+ works) | | Wall order | On each wall, left → right: **later works on the left**, **earlier works on the right**; undated works sort toward the left | | Influence lamps | A golden picture light appears **above frames** whose work has any influence-graph edge (`has_influence_links` from the API) | | 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 columns, pedestals, or other centre objects | | Single exit | One doorway on the **front wall**; walk to it or click it | | Hall-to-hall travel | Exit opens a panel: **Predecessors** (left) and **Successors** (right), each grouped by art movement | | Missing images | Works without a local file show a **draped canvas cover** in the frame (not a blank white rectangle) | | Detail view return | Opening a painting close-up **keeps the 3D hall mounted** in the background so position and view direction are preserved when you go back | **Controls:** | Input | Action | |-------|--------| | `W` / `↑` | Walk forward | | `S` / `↓` | Walk back | | `A` / `←` / `Q` | Turn left | | `D` / `→` | Turn right | | Mouse drag | Look left / right (same direction as keyboard turns) | | Click painting | Open detail view | | Exit doorway / `E` / **Exit →** header button | Open path picker | Predecessors and successors come from the **painting influence graph** (`painting_influences` → other artists). Empty lists mean no influence edges are recorded yet for that artist — run `npm run update-influences` or extend seed data. **3D images** use locally cached files only (`galleryImageUrl` in `client/src/api/client.ts`). Remote fetches are too slow for realtime WebGL textures; call `POST /api/artists/:id/preload-images` before entering a hall to link disk files. While a texture is loading, the frame shows the canvas cover instead of a white placeholder. ## Painting detail view Opened from the 3D hall (click a frame) or from influence thumbnails on another work’s detail page. | Layer | What you see | |-------|----------------| | **Detail** | Centre image, *Influenced By* (left) and *Influenced* (right), position in catalog (e.g. `3 of 12`) | | **Fullscreen** | Click the centre image; `Escape` or click anywhere to return to detail only | **Controls:** | Input | Action | |-------|--------| | `‹` / `›` beside image | Previous / next work by the **same artist** (chronological order) | | `←` / `→` | Same as prev / next (disabled while fullscreen is open) | | Click centre image | Open fullscreen lightbox | | Click influence thumbnail | Open that work’s detail (different artist allowed) | | **← Back to Gallery** | Return to the hall you entered from — **3D camera position is preserved** | | **About {artist}** | Open artist biography | **Navigation rules:** - **Catalog browsing** (‹ › / arrow keys) walks the current artist’s works earliest → latest. It does **not** change the back target: after browsing several works, **Back to Gallery** still returns directly to the hall. - **Influence links** push a new detail layer; **Back** from an influenced work returns to the painting you came from (and from there back to the gallery if applicable). - The 3D hall stays mounted in the background while detail is open so nothing is lost on return. ## Key design decisions - **Timeline bounds** derive from the earliest art movement start year, not ancient-era metadata alone, so the default view opens where catalogued content begins. - **Movement filtering** on zoom only shows movements that have at least one artist active in the visible year range. - **One hall per artist** keeps navigation predictable: enter from the timeline or bio, leave via the single exit or back button. - **Influence-based hall links** connect artists through documented painting-to-painting relationships, grouped by movement at the exit. - **3D gallery images** use locally cached files only; slow remote fetches would break realtime rendering. - **Influence data** is stored as directed edges between paintings, with optional citation fields (source author, quote, URL) for art-historical references. ## Related docs | Document | Contents | |----------|----------| | [setup.md](setup.md) | Install, database, npm scripts | | [DB_structure.md](DB_structure.md) | Tables and relationships | | [API.md](API.md) | REST endpoints | | [data-and-images.md](data-and-images.md) | Image pipeline and seeding |