Add parquet floor, museum-style exit, movement-tinted walls, and gold/black frames with gallery sync after Fix it. Debug panel gets Checked and Fix it buttons; documentation and image-fetch reliability updates included. Co-authored-by: Cursor <cursoragent@cursor.com>
279 lines
16 KiB
Markdown
279 lines
16 KiB
Markdown
# Art Gallery — architecture basics
|
||
|
||
Interactive virtual museum spanning art history: zoomable timeline, branching movement flow, 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 flow** — art movements as curved SVG streams on the same year axis; documented predecessor→successor branches; portrait thumbnails placed along each stream.
|
||
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 (paintings, artists, or movements), 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)
|
||
│ │ ├── components/VirtualGallery.tsx # 3D hall (parquet, frames, museum exit)
|
||
│ │ ├── components/PaintingDetail.tsx # Detail view + debug panel
|
||
│ │ ├── components/MovementBands.tsx # Movement flow (SVG streams + branches)
|
||
│ │ ├── pages/CheckupPage.tsx # Image audit table
|
||
│ │ ├── data/historical-events.ts # Timeline event markers (UI)
|
||
│ │ ├── data/movement-lineage.ts # Curated movement predecessor links (UI)
|
||
│ │ ├── utils/parquetFloorTexture.ts # Procedural parquet floor
|
||
│ │ ├── utils/debugMode.ts # Debug mode localStorage toggle
|
||
│ │ └── utils/timelineView.ts # Shared zoom/pan math for timeline + movements
|
||
│ └── dist/ # Production build (served by API when present)
|
||
├── scripts/ # Seed, bios, catalog expansion, image fetch, checkup tools
|
||
│ ├── fetch-artist-bios.js
|
||
│ ├── expand-paintings.js
|
||
│ ├── famous-paintings-data.js
|
||
│ ├── fetch-missing-images.js
|
||
│ ├── find-duplicate-paintings.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 + movement flow] -->|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
|
||
```
|
||
|
||
## Timeline and movement flow
|
||
|
||
The home page shows two linked views over the **same year window** (`viewStart` / `viewEnd` in `HomePage.tsx`):
|
||
|
||
| View | Component | Purpose |
|
||
|------|-----------|---------|
|
||
| Era bar | `Timeline.tsx` | Historical eras, major event markers, click-to-zoom |
|
||
| Movement flow | `MovementBands.tsx` | Curved streams per movement, lineage branches, artist portraits |
|
||
|
||
Both views share zoom/pan behaviour via `client/src/utils/timelineView.ts`. The home page uses a **fixed viewport** (`100vh`): the movement flow compresses vertically so all movements in the visible year range fit without page scrolling.
|
||
|
||
### Timeline controls
|
||
|
||
| Input | Action |
|
||
|-------|--------|
|
||
| Click an **era band** | Zoom to that historical period |
|
||
| Click an **event marker** | Zoom to that event (or war span) |
|
||
| Scroll wheel | Zoom toward cursor |
|
||
| Drag centre | Pan the year range |
|
||
| Drag left / right handle | Trim view start / end |
|
||
| **+** / **−** / **⟲** buttons | Zoom in, zoom out, reset to full range |
|
||
|
||
### Historical event markers
|
||
|
||
Major world events appear on the era bar as pin markers (single years) or shaded spans (e.g. World War I, World War II). Data lives in `client/src/data/historical-events.ts` — not in PostgreSQL. Labels appear when zoomed in enough; tooltips always show name and dates. Edit `HISTORICAL_EVENTS` and rebuild the client to add or change markers.
|
||
|
||
### Movement flow
|
||
|
||
Each visible movement is drawn as a **portrait-width curved stream** (~54 px stroke, compressed when many rows are visible) from its start year to its end year.
|
||
|
||
| Feature | Implementation |
|
||
|---------|----------------|
|
||
| Lineage layout | `client/src/data/movement-lineage.ts` — curated predecessor→successor pairs (Met / ArtStory / museum essays); multiple parents allowed |
|
||
| Vertical depth | Successor movements sit on rows below their deepest parent; sibling movements at the same depth are spread into lanes to limit overlap |
|
||
| Branch connectors | Smooth curves from the **centre** of a parent stream to the **centre** of each child stream (siblings fan out along the parent’s length) |
|
||
| Visual blending | Path-aligned SVG gradients with transparent fades at stream ends and branch junctions; streams draw on top of branches so overlap brightness stays uniform |
|
||
| Filtering | Same rule as the API: only movements with at least one artist active in the visible year range |
|
||
| Viewport layout | Row height and stream width scale from measured canvas size so every visible movement row fits in the remaining screen space |
|
||
|
||
### Artists on movement streams
|
||
|
||
Each artist appears as a **portrait circle** on their movement’s stream row:
|
||
|
||
| Feature | Behaviour |
|
||
|---------|-----------|
|
||
| Placement | Portrait at the **midpoint** of birth–death years |
|
||
| Lifespan line | Hidden by default; **hover the portrait** to show a horizontal line from birth year to death year |
|
||
| Timeline span | The lifespan line follows the **full timeline axis** (clamped to the current zoom), even when it extends beyond the movement band |
|
||
| Stacking | Artists in the same movement who would overlap are placed on **separate lanes** within the stream band |
|
||
| Colour | Each lane gets a slightly shifted tint derived from the movement colour; portrait border matches its line |
|
||
|
||
### Movement flow controls
|
||
|
||
| Input | Action |
|
||
|-------|--------|
|
||
| Scroll wheel on flow canvas | Zoom (same range as timeline) |
|
||
| Drag on flow canvas | Pan |
|
||
| Click portrait | Open artist biography |
|
||
|
||
Artist portraits stop wheel/drag propagation so zooming over a face does not fight portrait clicks.
|
||
|
||
**Note:** Movement lineage is **frontend curation** for layout and labels — it is not stored in PostgreSQL. Painting influence links (`painting_influence_sources`, plus legacy `painting_influences` for hall navigation) are separate and drive the 3D exit picker and detail panels.
|
||
|
||
## 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 in **one row per wall**; room **depth grows** when the catalog is large |
|
||
| Corridor layout | **15+ paintings:** short back wall (up to 8 works), remaining works on extended **left/right** side walls — a long gallery corridor |
|
||
| Wall order | On each wall, left → right: **later works on the left**, **earlier works on the right**; undated works sort toward the left |
|
||
| 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) |
|
||
| 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 |
|
||
| Museum exit | Front-wall **double doors** with transom, brass hardware, sconces, marble threshold, and warm vestibule glow |
|
||
| 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 |
|
||
| After image fix | Debug **Fix it** updates the gallery session, busts texture cache (`?v=N`), and returns to the hall with the new image and gold frame |
|
||
|
||
**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) — painting thumbnails, artist portraits, or movement swatches — 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) |
|
||
| Click influence artist portrait | Open that artist’s 3D gallery hall |
|
||
| **← 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.
|
||
|
||
### Debug mode (developer)
|
||
|
||
When **Debug mode** is enabled from the home header, painting detail shows a bottom-left panel with image search preview and **Checked** / **Fix it** buttons. See [Developer tools (image audit)](#developer-tools-image-audit).
|
||
|
||
## 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.
|
||
- **Movement lineage** (`movement-lineage.ts`) documents art-historical predecessor→successor links for the flow diagram; extend that file to add or correct branches.
|
||
- **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 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 links from paintings to sources (another painting, an artist, or a movement), with optional period fields and citation metadata (source author, quote, URL).
|
||
|
||
## Developer tools (image audit)
|
||
|
||
Optional workflow for curating local image files — not part of the public visitor experience.
|
||
|
||
| Feature | Where | Purpose |
|
||
|---------|--------|---------|
|
||
| **Debug mode** | Home header toggle (`client/src/utils/debugMode.ts`) | Persists in `localStorage`; enables debug panel on painting detail |
|
||
| **Checkup page** | Home header → **Checkup** (`CheckupPage.tsx`) | Full-catalog table: gallery vs detail thumbnails, search, fix, review flags |
|
||
| **Debug panel** | Painting detail (bottom-left, when debug mode on) | Search preview + two action buttons |
|
||
|
||
### Debug panel (painting detail)
|
||
|
||
When debug mode is on, a panel at the bottom-left shows the image search query, a preview when a result is found, and **two buttons**:
|
||
|
||
| Button | Action |
|
||
|--------|--------|
|
||
| **Checked** | Sets `checkup_checked` via `PATCH /api/paintings/:id/checkup-flags` (disabled once already reviewed) |
|
||
| **Fix it** | Replaces local full + thumbnail from the search result via `POST /api/paintings/:id/fix-image`; sets **Fixed** and **Reviewed** |
|
||
|
||
After **Fix it**, the detail image, gallery textures, and frame colour (gold if reviewed) update without a full page reload. **Back to Gallery** returns to the live hall session, not a stale snapshot.
|
||
|
||
### Checkup page
|
||
|
||
**Columns:** Gallery and Detail thumbnails, Search (reference image), Fix (replace local file), **Reviewed** (`checkup_checked`), **Fixed** (`checkup_fixed`).
|
||
|
||
**Search visible** runs image search only for rows currently shown after text/filter — not automatically on page load. Fixing an image sets **Fixed** and **Reviewed**.
|
||
|
||
Run `npm run migrate:checkup-flags` once on existing databases. After server code changes, restart `npm run dev` so new routes (e.g. `PATCH …/checkup-flags`) are registered.
|
||
|
||
See [API.md](API.md#developer-image-audit) and [data-and-images.md](data-and-images.md#duplicate-paintings).
|
||
|
||
## 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 |
|