Enhance timeline and movement flow UI; add fetched painting images.

Timeline: era and historical-event click-to-zoom, event markers from historical-events.ts. Movement flow: shared zoom/pan, viewport-fit layout, curved lineage streams, hover-to-reveal artist lifespans on stacked lanes. Docs updated. Includes batch-fetched paintings and regenerated thumbs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila KhodjaefandCursor committed 2026-06-20 00:21:31 +03:00
1 parent 35c337253d
commit 0ece1195fa
1301 files changed
+1412 -140

No files matched your search

+1 -1
View File
@@ -54,7 +54,7 @@ Movements are filtered to those with at least one artist active in the requested
## `GET /api/artists`
Artists for timeline portraits and movement bands.
Artists for timeline portraits and the movement flow diagram.
**Query**
+1 -1
View File
@@ -53,7 +53,7 @@ Finer-grained styles (Impressionism, Cubism, Suprematism, …).
| `start_definite`, `end_definite` | BOOLEAN | Same visual semantics as eras |
| `era_id` | FK → `historical_eras` | Optional parent era |
| `description` | TEXT | |
| `color` | VARCHAR(20) | Hex colour for movement band |
| `color` | VARCHAR(20) | Hex colour for movement stream in the flow diagram |
### `artists`
+71 -3
View File
@@ -1,13 +1,13 @@
# 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.
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 bands** — art movements aligned to the same time axis, each showing portrait thumbnails of key artists.
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, 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`).
@@ -31,6 +31,10 @@ Gallery/
├── server/ # Express API, DB pool, image service
├── client/ # React/Vite frontend
│ ├── src/ # Source (components, pages, 3D scene)
│ │ ├── components/MovementBands.tsx # Movement flow (SVG streams + branches)
│ │ ├── data/historical-events.ts # Timeline event markers (UI)
│ │ ├── data/movement-lineage.ts # Curated movement predecessor links (UI)
│ │ └── 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
│ ├── fetch-artist-bios.js
@@ -67,7 +71,7 @@ Use the Vite URL during frontend work for HMR.
```mermaid
flowchart TD
A[Home — timeline + movements] -->|scroll / drag / zoom| A
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]
@@ -84,6 +88,69 @@ flowchart TD
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-to-painting influence edges (`painting_influences`) 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:
@@ -148,6 +215,7 @@ Opened from the 3D hall (click a frame) or from influence thumbnails on another
- **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-to-painting relationships, grouped by movement at the exit.
- **3D gallery images** use locally cached files only; slow remote fetches would break realtime rendering.
+29 -2
View File
@@ -130,6 +130,31 @@ Each entry defines a later `work` influenced by an earlier `influencedBy` painti
Extend the data file to add lineage chains (e.g. Giotto → Masaccio → Michelangelo → Manet → Picasso → Warhol). New bridge artists may include Géricault, Friedrich, Constable, Giorgione, Poussin, de Chirico, and Böcklin.
## Movement lineage (frontend flow diagram)
Separate from the painting influence graph, `client/src/data/movement-lineage.ts` lists **art-movement** predecessor→successor pairs used only by `MovementBands.tsx` to position streams and draw branch connectors (e.g. Impressionism → Post-Impressionism → Fauvism / Cubism / Expressionism).
| Aspect | Detail |
|--------|--------|
| Storage | TypeScript module in the client — **not** a database table |
| Format | `[parentMovementName, childMovementName]` tuples; names must match `art_movements.name` from the seed |
| Multiple parents | Allowed (e.g. Post-Impressionism feeding several modern paths) |
| Sources | Curator notes in the file reference Met essays, TheArtStory, and similar |
To add or fix a branch, edit `MOVEMENT_LINEAGE` in that file and rebuild the client. No migration or API change is required.
## Historical event markers (frontend timeline)
`client/src/data/historical-events.ts` lists **world-history** markers shown on `Timeline.tsx` (French Revolution, World War I/II, etc.).
| Aspect | Detail |
|--------|--------|
| Storage | TypeScript module in the client — **not** a database table |
| Format | `{ id, name, startYear, endYear?, shortLabel? }` — omit `endYear` for a single-year pin |
| Interaction | Click a marker to zoom the shared timeline/movement view to that period |
Edit `HISTORICAL_EVENTS` and rebuild the client to extend the set.
## Batch image fetch
`npm run fetch-images` (alias: `npm run search-missing-paintings`) runs `scripts/fetch-missing-images.js`. It searches multiple sources for paintings without local files:
@@ -152,13 +177,15 @@ Extend the data file to add lineage chains (e.g. Giotto → Masaccio → Michela
```bash
npm run fetch-images # all missing, catalog order (~hours)
npm run fetch-images -- --limit=50 # random sample of 50; 10s max per painting
npm run fetch-images -- --limit=920 # random sample up to N missing works
npm run fetch-images -- --limit=250 # random sample up to N (caps at current missing count)
npm run fetch-images -- --limit=50 --max-wait=120 # slower, more thorough lookup per painting
npm run fetch-images -- --artist="Albrecht Dürer" # one artist, catalog order
npm run fetch-images -- --discover-only --limit=20 # fix wikipedia_title only
npm run fetch-images -- --web-search-only --limit=50 # DuckDuckGo + Commons + multilingual Wikipedia
```
Each run prints **`Missing local files: N`** at startup — that is the current count of catalogued paintings with no full-size or thumbnail file under `data/images/`. A painting counts as present if **either** file exists on disk.
| Flag | Effect |
|------|--------|
| `--limit=N` | Process at most **N** paintings. Queue is a **random sample** of all works missing local files (not alphabetical). |
@@ -198,7 +225,7 @@ The 3D scene uses `galleryImageUrl()`, which never hits the on-demand API (remot
When no image is available:
- `/placeholder-portrait.svg` — timeline / movement band portraits
- `/placeholder-portrait.svg` — timeline and movement-flow portraits
- `/placeholder-art.svg` — paintings in lists and detail view
- **3D gallery** — draped **canvas cover** inside the frame (`CanvasCover` in `VirtualGallery.tsx`); shown when there is no local file, the fetch failed, or the texture has not loaded yet
+2 -1
View File
@@ -70,7 +70,7 @@ npm run fetch-images -- --limit=50 --max-wait=120 # same batch size, longer look
cd client && npm run build && cd ..
```
Image fetch can take hours for the full catalog (~900+ missing works after seed). Use **`npm run fetch-images -- --limit=N`** for random batches (10s per painting by default), **`--artist="…"`** for one artist in catalog order, or on-demand resolution when viewing a painting in the detail view.
Image fetch can take hours if you run it for the entire catalog. The first line of each run reports **`Missing local files: N`**. Use **`npm run fetch-images -- --limit=N`** for random batches (10s per painting by default), **`--artist="…"`** for one artist in catalog order, or on-demand resolution when viewing a painting in the detail view.
## Run
@@ -103,6 +103,7 @@ Open http://localhost:3001 (or your configured `PORT`).
| `npm run search-missing-paintings` | ↑ (alias) | Same as `fetch-images` |
| `npm run fetch-images -- --artist="Name"` | ↑ | Limit to one artist (catalog order) |
| `npm run fetch-images -- --limit=50` | ↑ | Random sample of N missing paintings |
| `npm run fetch-images -- --limit=250` | ↑ | Larger random batch (processes min(N, missing count)) |
| `npm run fetch-images -- --limit=50 --max-wait=120` | ↑ | Random batch; 120s cap per painting |
| `npm run fetch-images -- --discover-only --limit=20` | ↑ | Fix `wikipedia_title` via search only |
| `npm run regenerate-thumbnails` | `scripts/regenerate-thumbnails.js` | Rebuild all thumbs from full local files |