diff --git a/.env.example b/.env.example index 69b2f37..09199cb 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,13 @@ DB_PORT=5432 DB_USER=gallery DB_PASSWORD= DB_NAME=Gallery -PORT=3001 + +# Production: LAN http://192.168.10.70:3520 and reverse-proxy at gallery.mysuperlab.netcraze.pro +PORT=3520 +HOST=0.0.0.0 +PUBLIC_URL=http://gallery.mysuperlab.netcraze.pro +TRUST_PROXY=true + IMAGE_DIR=./data/images # Optional — enable extra museum search in fetch-missing-images / search-missing-paintings diff --git a/Documentation/API.md b/Documentation/API.md index 9d0107c..1927681 100644 --- a/Documentation/API.md +++ b/Documentation/API.md @@ -1,9 +1,11 @@ # Art Gallery — REST API -Base URL in development: +Base URL: -- **Direct:** `http://localhost:3001` -- **Via Vite proxy:** `http://localhost:5173` (same paths) +- **Production (public):** `http://gallery.mysuperlab.netcraze.pro` +- **Production (LAN):** `http://192.168.10.70:3520` +- **Development (direct):** `http://localhost:3520` or `http://localhost:3001` depending on `PORT` in `.env` +- **Development (Vite proxy):** `http://localhost:5173` (same paths) All JSON responses use `Content-Type: application/json`. Errors return `{ "error": "message" }` with an appropriate HTTP status. @@ -262,12 +264,12 @@ Max decoded size 15 MB (JSON body limit 20 MB on the server). **Response** — s ## `GET /api/artists/:id/navigation` -Related artists for hall-to-hall navigation at the exit doorway. Derived from `painting_influences`: +Related artists for hall-to-hall navigation at the exit doorway. Derived from **`painting_influence_sources`**: -- **Predecessors** — artists whose works influenced this artist’s paintings. -- **Successors** — artists whose works were influenced by this artist’s paintings. +- **Predecessors** — artists whose works (or artist records) influenced this artist’s paintings. +- **Successors** — artists whose paintings were influenced by this artist’s works (via painting sources), or whose paintings cite this artist as a source (via artist sources). -Both lists are grouped by art movement and exclude the current artist. +Both lists are grouped by art movement and exclude the current artist. Each artist appears once even when multiple influence edges exist. **Response** @@ -357,6 +359,8 @@ Painting detail with influence graph neighbours. `painting.has_influence_links` matches the flag on `GET /api/artists/:id` paintings (see above). +Both **`influencedBy`** and **`influenced`** are read from **`painting_influence_sources`** only (one row per edge). Painting-type sources appear in *Influenced By* when they are predecessors; in *Influenced* when this painting is the source of a later work. Artist and movement sources appear only in *Influenced By*. The legacy `painting_influences` table is not merged into these responses — it mirrors painting edges for scripts only. + --- ## `GET /api/paintings/:id/image` diff --git a/Documentation/DB_structure.md b/Documentation/DB_structure.md index 74db399..3739b11 100644 --- a/Documentation/DB_structure.md +++ b/Documentation/DB_structure.md @@ -147,7 +147,7 @@ Directed edges: *this painting* was influenced by *that painting*. Unique constraint on `(painting_id, influenced_by_painting_id)`. -Used by the 3D gallery exit panel: predecessors are artists of `influenced_by_painting_id` works; successors are artists of paintings influenced by this artist’s works (see [API.md](API.md#get-apiartistsidnavigation)). Legacy table; new polymorphic links live in `painting_influence_sources`. +**Legacy mirror table.** `npm run update-influences` still inserts painting-to-painting rows here when curating data. The same edges are copied into `painting_influence_sources`. The **REST API does not read this table** for painting detail or hall navigation — use `painting_influence_sources` as the source of truth for display. ### `painting_influence_sources` @@ -175,7 +175,7 @@ Polymorphic influence links: *this painting* was influenced by a **painting**, * Unique index on `(painting_id, source_type, source_painting_id, source_artist_id, source_movement_id)` with COALESCE for null FKs. -Used by painting detail API (`influencedBy`). Painting-type rows also feed `has_influence_links` and hall navigation (with legacy `painting_influences`). +**Canonical influence store.** Used by all API influence queries: painting detail (`influencedBy`, `influenced`), `has_influence_links`, and artist hall navigation (predecessors / successors). Legacy `painting_influences` rows are backfilled here on migration; new curated painting edges are written to both tables by `update-influences`. ## Indexes diff --git a/Documentation/basics.md b/Documentation/basics.md index 0f5f018..701db81 100644 --- a/Documentation/basics.md +++ b/Documentation/basics.md @@ -58,9 +58,11 @@ Gallery/ │ ├── famous-paintings-data.js │ ├── fetch-missing-images.js │ ├── find-duplicate-paintings.js +│ ├── audit-influence-duplicates.js │ └── image-fetcher.js ├── data/images/ # Local portraits and paintings (+ thumbs/) ├── db/ # SQL schema and migrations (when present) +├── deploy/ # Production nginx + systemd examples ├── Documentation/ # This folder └── .env # DB and port config (not committed) ``` @@ -70,16 +72,19 @@ Gallery/ ### Production-style (single process) ```bash -npm run server # http://localhost:3001 +npm run start:prod # build client + serve on PORT (default 3520) +# or: npm run build && npm run start ``` Serves `/api/*`, `/images/*`, and the built SPA from `client/dist` if it exists. +**Deployed URLs:** public http://gallery.mysuperlab.netcraze.pro · LAN http://192.168.10.70:3520 — see [setup.md](setup.md#production-deployment). + ### Development (two processes) ```bash -npm run dev:server # API on :3001 -npm run dev:client # Vite on :5173, proxies /api and /images +npm run dev:server # API on PORT from .env (3520 production, 3001 typical dev) +npm run dev:client # Vite on :5173, proxies /api and /images to PORT ``` Use the Vite URL during frontend work for HMR. @@ -181,7 +186,7 @@ Each artist appears as a **portrait circle** on their movement’s stream row: Artist portraits stop wheel/drag propagation so zooming over a face does not fight portrait clicks. Hovering a portrait highlights the artist’s lifespan on the era bar and brightens their segment on the movement stream. -**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. +**Note:** Movement lineage is **frontend curation** for layout and labels — it is not stored in PostgreSQL. Painting influence links live in **`painting_influence_sources`** (paintings, artists, or movements as sources). The API reads that table for detail panels, hall navigation, and `has_influence_links`. The legacy **`painting_influences`** table is still written in parallel when curators add painting-to-painting edges but is not queried for display. ## Virtual gallery (3D halls) @@ -222,7 +227,7 @@ Each artist has **exactly one hall**. The hall is a rectangular room sized to fi | 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. +Predecessors and successors come from **`painting_influence_sources`** (painting and artist sources). Empty lists mean no influence edges are recorded yet for that artist — run `npm run update-influences` or extend `art-influences-data.js`. ### Movement galleries @@ -236,7 +241,8 @@ Enter from the home page by clicking a **movement name** on the movement flow (` | Wall order | Along each side wall: **later works on the left**, **earlier on the right** (same convention as artist halls) | | Frame captions | **Year · artist** label below each frame | | Period interior | Each of the 26 seeded movements maps to a unique style in `movement-interior-styles.ts` (Italian palazzo, Baroque palace, NYC loft, white cube, etc.) | -| Textures | Hi-res procedural wall/floor/ceiling maps with normal maps (`galleryProceduralTextures.ts`) | +| Wall materials | Hi-res **procedural textures** with normal maps (`galleryProceduralTextures.ts`): real-world stone, marble, wood panelling, brick, velvet, stucco — plus **single-colour painted walls** (`painted-lime`, `painted-oil-matte`, `painted-oil-satin`, `painted-emulsion`, `painted-flat`) tinted per movement for Renaissance salons through modern white cubes | +| Textures | Wall/floor/ceiling maps applied via `useTexturedMaterial`; movement **tints** drive painted-wall hue | | Windows | **Side walls only** — placed in gaps between frames (high on the wall, no overlap with paintings); style matches the movement era | | Lighting | Daylight from windows + ceiling track lights + ambient/sun fill | | Back wall | **Exit double doors** → **Wing navigator** (jump to any wing) or **Exit to Timeline** | @@ -301,7 +307,7 @@ When **Debug mode** is enabled from the home header, painting detail and artist - **Movement galleries** complement artist halls: full movement corpus in period-themed wings, entered from the flow diagram. - **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). +- **Influence data** is stored in **`painting_influence_sources`** (directed links from paintings to source paintings, artists, or movements), with optional period fields and citation metadata. Legacy `painting_influences` mirrors painting-to-painting edges for scripts only. ## Developer tools (image audit) diff --git a/Documentation/data-and-images.md b/Documentation/data-and-images.md index 8366870..8a59060 100644 --- a/Documentation/data-and-images.md +++ b/Documentation/data-and-images.md @@ -58,9 +58,9 @@ migrate → seed → fetch-artist-bios → expand-catalog → update-influences 2. For each curated **artist**: - Creates **artist periods** and **paintings**. - May download portraits and painting images (depending on seed script version). -3. Writes **painting_influences** edges from curated scholarship references. +3. Writes **painting_influences** edges from curated scholarship references (mirrored into `painting_influence_sources` when you run `npm run migrate:influence-sources` and `npm run update-influences`). -Those influence edges power **3D hall navigation**: predecessors and successors at each artist’s exit doorway are computed from this table (see `GET /api/artists/:id/navigation` in [API.md](API.md)). +Those influence edges power **3D hall navigation** and painting detail panels via **`painting_influence_sources`** (see `GET /api/artists/:id/navigation` and `GET /api/paintings/:id` in [API.md](API.md)). Artists are grouped by movement and century; the seed list targets at most ~100 artists per century. @@ -117,14 +117,24 @@ Renaissance and medieval masters with large museum catalog dumps (e.g. Raphael, Directed influence links are stored in **`painting_influence_sources`**. Each row connects a painting to a **source** of type `painting`, `artist`, or `movement`, with optional period context (e.g. influence during the work’s creation year). -The legacy **`painting_influences`** table (painting-to-painting only) is still written for hall navigation compatibility and is backfilled into `painting_influence_sources` on migration. +The legacy **`painting_influences`** table (painting-to-painting only) is still written alongside sources when running `npm run update-influences` — it keeps script compatibility and matches the backfill migration. **The API reads only `painting_influence_sources`**, so each edge appears once in the UI. Influence data drives: - **Painting detail** — *Influenced By* (left) and *Influenced* (right) panels: painting thumbnails, artist portraits, or movement colour swatches, plus notes, aspects, period labels, and citations -- **3D hall exit** — predecessor and successor artists grouped by movement (painting edges only) +- **3D hall exit** — predecessor and successor artists grouped by movement (from painting and artist sources) - **3D gallery lamps** — golden picture light above frames with any influence edge (`has_influence_links` on painting API responses) +### Audit duplicate influence links + +Painting-to-painting edges exist in both tables by design. To confirm the database has no stray duplicates and that the API model is clean: + +```bash +npm run audit-influence-duplicates +``` + +Reports: edges present in both tables, duplicate rows within either table (should be 0), and legacy-only / sources-only mismatches. If *Influenced* ever shows the same successor twice, restart the server after pulling API fixes — responses must not union legacy and sources tables. + ### One-time migration ```bash @@ -310,7 +320,7 @@ These live in `client/public/` (and `client/dist/` after build). 3. Run `npm run fetch-artist-bios` for the new artist’s biography. 4. Add entries to `famous-paintings-data.js` and run `npm run expand-catalog` if needed. 5. Run `npm run fetch-images -- --artist="…"` or rely on preload / on-demand sync. -6. Add influence rows to `painting_influences` with citation fields where possible. +6. Add influence rows via `npm run update-influences` / `art-influences-data.js` (writes both `painting_influence_sources` and legacy painting edges). ## Image fetcher overrides diff --git a/Documentation/setup.md b/Documentation/setup.md index 74528c9..6e193c8 100644 --- a/Documentation/setup.md +++ b/Documentation/setup.md @@ -21,7 +21,10 @@ cp .env.example .env | `DB_USER` | Database user | | `DB_PASSWORD` | Database password | | `DB_NAME` | Database name (`Gallery`) | -| `PORT` | API listen port (default `3001`) | +| `PORT` | API listen port (default `3001`; production uses `3520`) | +| `HOST` | Bind address (default `0.0.0.0` — required for LAN access) | +| `PUBLIC_URL` | Optional public URL shown at startup (e.g. `http://gallery.mysuperlab.netcraze.pro`) | +| `TRUST_PROXY` | Set to `true` when behind nginx/reverse proxy (honours `X-Forwarded-*`) | | `IMAGE_DIR` | Root for cached images (default `./data/images`) | Optional script tuning: @@ -80,19 +83,65 @@ Image fetch can take hours if you run it for the entire catalog. The first line | Command | Purpose | |---------|---------| -| `npm run server` | API + static SPA on `PORT` | +| `npm run build` | Build production SPA into `client/dist` | +| `npm run start` | API + static SPA on `HOST`:`PORT` | +| `npm run start:prod` | Build client, then start server | +| `npm run server` | Alias for `start` | | `npm run dev:server` | API with nodemon reload | | `npm run dev:client` | Vite dev server on :5173 | -| `npm run dev` | Alias for `server` | +| `npm run dev` | Alias for `start` | **Production frontend:** build the client, then start the server: ```bash -cd client && npm run build && cd .. -npm run server +npm run build + # or: cd client && npm run build && cd .. +npm run start ``` -Open http://localhost:3001 (or your configured `PORT`). +Open http://localhost:3520 (or your configured `HOST`/`PORT`). + +## Production deployment + +This install is intended to run at: + +| Access | URL | +|--------|-----| +| Public (reverse proxy) | http://gallery.mysuperlab.netcraze.pro | +| LAN direct | http://192.168.10.70:3520 | + +### 1. Configure `.env` + +Copy `.env.example` → `.env` and set at least: + +```env +PORT=3520 +HOST=0.0.0.0 +PUBLIC_URL=http://gallery.mysuperlab.netcraze.pro +TRUST_PROXY=true +``` + +`HOST=0.0.0.0` lets the app accept connections on the machine’s LAN IP (`192.168.10.70`). The client uses relative `/api` and `/images` paths, so no frontend URL changes are needed. + +### 2. Build and start + +```bash +npm install +cd client && npm install && cd .. +npm run start:prod +``` + +Or use the systemd unit in [`deploy/gallery.service`](../deploy/gallery.service) (adjust `User`, `WorkingDirectory`, and `EnvironmentFile`). + +### 3. Reverse proxy (public domain) + +Point `gallery.mysuperlab.netcraze.pro` at the host running the app. Example nginx config: [`deploy/nginx-gallery.conf`](../deploy/nginx-gallery.conf). + +The proxy forwards to `127.0.0.1:3520`. Keep `TRUST_PROXY=true` in `.env` so Express sees the correct client IP and scheme. + +### 4. Firewall + +Allow inbound **TCP 3520** on the gallery host if clients reach it directly on the LAN (`192.168.10.70:3520`). The public hostname only needs **80/443** on the reverse-proxy host. ## Maintenance scripts @@ -123,6 +172,7 @@ Open http://localhost:3001 (or your configured `PORT`). | `npm run update-painting-annotations -- --wikipedia` | ↑ | Add intro sentences from each work’s Wikipedia page | | `npm run update-painting-annotations -- --wikipedia --wiki-delay=3000` | ↑ | Slower Wikipedia pass when rate-limited (429) | | `npm run find-duplicates` | `scripts/find-duplicate-paintings.js` | Report duplicate and near-duplicate painting rows | +| `npm run audit-influence-duplicates` | `scripts/audit-influence-duplicates.js` | Report mirrored legacy/sources edges and duplicate influence rows | | `npm run update-influences` | `scripts/update-influences.js` | Insert influence links (painting / artist / movement) from `art-influences-data.js` | | `npm run update-influences -- --fetch-images` | ↑ | Also download images for newly created works | | `npm run update-influences -- --discover` | ↑ | Curated pass + web discovery (Wikipedia, Wikidata, Met, art-history sites) | @@ -139,6 +189,7 @@ These are checked in and maintained: - `influence-discovery.js` + `influence-resolver.js` — web discovery and polymorphic source resolution - `fetch-missing-images.js` — batch image backfill - `find-duplicate-paintings.js` — duplicate catalog audit +- `audit-influence-duplicates.js` — influence graph duplicate / mirror audit - `migrate-checkup-flags.js` — checkup workflow columns (paintings) - `migrate-artist-checkup-flags.js` — checkup workflow columns (artist portraits) - `migrate-painting-annotations.js`, `update-painting-annotations.js`, `painting-annotations-data.js` — art-history notes on painting detail @@ -171,8 +222,9 @@ After clone: copy `.env.example` → `.env`, install dependencies, run `npm run | White/grey flicker on frames | Texture loading or z-fighting with wall | Rebuild client (`cd client && npm run build`); ensure latest `VirtualGallery.tsx` | | Wrong painting in 3D gallery frame | Stale or mismatched thumbnail file | `npm run regenerate-thumbnails`; gallery prefers full `image_path` | | Wrong painting image (fetch) | Bad museum / search match on first download | Add entry to `DIRECT_IMAGE_OVERRIDES` in `scripts/image-fetcher.js`, re-fetch file | -| Empty exit navigation lists | No `painting_influences` edges for artist | `npm run update-influences` | +| Empty exit navigation lists | No influence edges for artist | `npm run update-influences`; check `painting_influence_sources` | | Empty *Influenced By* / *Influenced* on painting detail | No edges for that work | `npm run migrate:influence-sources` then `npm run update-influences`; extend `art-influences-data.js` or run `--discover` | +| Same work listed twice under *Influenced* | Stale server merging legacy + sources tables | Restart server; API reads `painting_influence_sources` only — run `npm run audit-influence-duplicates` to verify DB | | No golden lamps above frames in 3D hall | Stale API process or no influence edges | Restart server after API changes; run `npm run update-influences` | | Default Vite page instead of gallery | `client/dist` missing or stale | `cd client && npm run build` | | Permission denied creating tables | `gallery` user lacks CREATE | Run admin grants, then migrate | diff --git a/README.md b/README.md index b96feee..0cabb6a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Art Gallery -Interactive virtual art gallery: zoomable historical timeline with era click-to-zoom, major event markers (vertical guides into the movement flow), branching art-movement streams (click a movement name to enter its **3D movement gallery** — period-themed wings with up to ~55 works each, side-wall windows, wing navigator), one 3D hall per artist (parquet floor, movement-tinted walls, black/gold frames by review status, corridor layout for large catalogs, museum-style exit doors, golden influence lamps, canvas placeholders for missing works, influence-linked exits), painting detail with art-history annotations, prev/next catalog browsing and fullscreen lightbox, debug-mode image audit on painting detail and artist bio (**Checked** / **Fix it** / **More** / **Clear** / **Upload**), Checkup page, preserved gallery camera on return, and Wikipedia-sourced artist biographies. +Interactive virtual art gallery: zoomable historical timeline with era click-to-zoom, major event markers (vertical guides into the movement flow), branching art-movement streams (click a movement name to enter its **3D movement gallery** — photorealistic period interiors with painted walls, stone, and wood textures; chronological wings with up to ~55 works each, side-wall windows, wing navigator), one 3D hall per artist (parquet floor, movement-tinted walls, black/gold frames by review status, corridor layout for large catalogs, museum-style exit doors, golden influence lamps, canvas placeholders for missing works, influence-linked exits), painting detail with art-history annotations, prev/next catalog browsing and fullscreen lightbox, debug-mode image audit on painting detail and artist bio (**Checked** / **Fix it** / **More** / **Clear** / **Upload**), Checkup page, preserved gallery camera on return, and Wikipedia-sourced artist biographies. ## Documentation @@ -51,17 +51,20 @@ Interactive virtual art gallery: zoomable historical timeline with era click-to- 5. Build the client and start the server: ```bash - cd client && npm run build && cd .. - npm run server + npm run start:prod ``` - Open http://localhost:3001 + **Production URLs:** + - Public: http://gallery.mysuperlab.netcraze.pro (via reverse proxy) + - LAN: http://192.168.10.70:3520 + + See [Documentation/setup.md](Documentation/setup.md#production-deployment) for nginx/systemd configs in `deploy/`. ## Development Run API and Vite dev server separately: ```bash -npm run dev:server # http://localhost:3001 +npm run dev:server # API — PORT from .env (3520 or 3001) npm run dev:client # http://localhost:5173 (proxies /api and /images) ``` diff --git a/client/src/components/VirtualGallery.tsx b/client/src/components/VirtualGallery.tsx index e319ad0..e703e8e 100644 --- a/client/src/components/VirtualGallery.tsx +++ b/client/src/components/VirtualGallery.tsx @@ -1165,6 +1165,7 @@ function GalleryFloor({ = { 30: mk( 'early-renaissance-palazzo', 'Florentine palazzo', - 'Stucco salone · terracotta accents · arched windows', - { wall: 'stucco-cream', ceiling: 'fresco-worn', floor: 'terracotta-tiles' }, + 'Lime-washed walls · terracotta accents · arched windows', + { wall: 'painted-lime', wallSide: 'stucco-cream', ceiling: 'fresco-worn', floor: 'terracotta-tiles' }, { wall: '#f4ebe0', ceiling: '#faf6ee', floor: '#c89070', trim: '#c9a227' }, { details: 'palazzo', @@ -249,8 +249,8 @@ const BY_MOVEMENT_ID: Record = { 35: mk( 'rococo-salon', 'Rococo salon', - 'Pastel stucco · gilt trim · chevron parquet', - { wall: 'pastel-plaster', ceiling: 'silk-pale', floor: 'parquet-chevrons' }, + 'Pastel painted walls · gilt trim · chevron parquet', + { wall: 'painted-oil-satin', ceiling: 'silk-pale', floor: 'parquet-chevrons' }, { wall: '#e8dce8', ceiling: '#faf6f0', floor: '#c8a882', trim: '#d4af37' }, { details: 'salon', @@ -266,8 +266,8 @@ const BY_MOVEMENT_ID: Record = { 36: mk( 'neoclassical-museum', 'Neoclassical museum', - 'White marble · coffered ceiling · skylit salon', - { wall: 'marble-white', ceiling: 'plaster-white', floor: 'marble-veined-carrara' }, + 'White painted walls · coffered ceiling · skylit salon', + { wall: 'painted-flat', ceiling: 'plaster-white', floor: 'marble-veined-carrara' }, { wall: '#f2f0ec', ceiling: '#fafafa', floor: '#eceae6', trim: '#b8b0a4' }, { details: 'neoclassical', @@ -302,8 +302,8 @@ const BY_MOVEMENT_ID: Record = { 38: mk( 'realist-bourgeois', 'Realist picture gallery', - 'Warm plaster · bourgeois salon · oak parquet', - { wall: 'plaster-warm', ceiling: 'plaster-warm', floor: 'parquet-herringbone' }, + 'Warm painted walls · bourgeois salon · oak parquet', + { wall: 'painted-emulsion', ceiling: 'painted-emulsion', floor: 'parquet-herringbone' }, { wall: '#e8e2d8', ceiling: '#f5f0e8', floor: '#a08060', trim: '#8a7050' }, { details: 'salon', @@ -316,8 +316,8 @@ const BY_MOVEMENT_ID: Record = { 39: mk( 'impressionist-salon', 'Impressionist salon', - 'North-light skylight · pale walls · herringbone floor', - { wall: 'plaster-warm', ceiling: 'plaster-white', floor: 'parquet-herringbone' }, + 'North-light skylight · pale painted walls · herringbone floor', + { wall: 'painted-emulsion', ceiling: 'painted-flat', floor: 'parquet-herringbone' }, { wall: '#f0ebe3', ceiling: '#fafafa', floor: '#c8a882', trim: '#c9a96e' }, { details: 'salon', @@ -333,8 +333,8 @@ const BY_MOVEMENT_ID: Record = { 40: mk( 'post-impressionist-atelier', 'Montmartre atelier', - 'Studio walls · large north window · worn floorboards', - { wall: 'plaster-warm', ceiling: 'plaster-warm', floor: 'parquet-herringbone' }, + 'Painted studio walls · large north window · worn floorboards', + { wall: 'painted-emulsion', ceiling: 'painted-emulsion', floor: 'parquet-herringbone' }, { wall: '#e8e0d0', ceiling: '#f0ebe0', floor: '#9a7858', trim: '#8a6848' }, { details: 'atelier', @@ -347,8 +347,8 @@ const BY_MOVEMENT_ID: Record = { 41: mk( 'symbolist-chamber', 'Symbolist chamber', - 'Deep emerald walls · amber window glow · dark parquet', - { wall: 'velvet-emerald', ceiling: 'dark-plaster', floor: 'parquet-herringbone' }, + '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: 'salon', @@ -365,8 +365,8 @@ const BY_MOVEMENT_ID: Record = { 42: mk( 'art-nouveau-salon', 'Art Nouveau salon', - 'Organic plaster · stained glass · terrazzo floor', - { wall: 'silk-pale', ceiling: 'silk-gold', floor: 'terrazzo' }, + 'Painted plaster · stained glass · terrazzo floor', + { wall: 'painted-oil-satin', ceiling: 'silk-gold', floor: 'terrazzo' }, { wall: '#f0ece4', ceiling: '#d8c890', floor: '#d8d0c4', trim: '#6a9868' }, { details: 'salon', @@ -381,8 +381,8 @@ const BY_MOVEMENT_ID: Record = { 43: mk( 'fauvist-studio', 'Fauvist studio', - 'Bold warm plaster · flooded with color and light', - { wall: 'stucco-terracotta', wallSide: 'silk-gold', ceiling: 'plaster-warm', floor: 'parquet-herringbone' }, + '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: '#f8f0e0', floor: '#a07048', trim: '#c04020' }, { details: 'atelier', @@ -413,8 +413,8 @@ const BY_MOVEMENT_ID: Record = { 45: mk( 'cubist-studio', 'Cubist studio', - 'Paris atelier · factory windows · raw plaster', - { wall: 'plaster-warm', ceiling: 'plaster-white', floor: 'parquet-herringbone' }, + 'Paris atelier · factory windows · painted plaster', + { wall: 'painted-emulsion', ceiling: 'painted-flat', floor: 'parquet-herringbone' }, { wall: '#e8e4dc', ceiling: '#f5f5f5', floor: '#9a8870', trim: '#888888' }, { details: 'atelier', @@ -447,8 +447,8 @@ const BY_MOVEMENT_ID: Record = { 47: mk( 'suprematist-gallery', 'Suprematist white cube', - 'Pure white volume · geometric light · polished floor', - { wall: 'white-plaster', ceiling: 'white-plaster', floor: 'concrete-polished' }, + 'Matte white paint · geometric light · polished floor', + { wall: 'painted-flat', ceiling: 'painted-flat', floor: 'concrete-polished' }, { wall: '#ffffff', ceiling: '#ffffff', floor: '#e0e0e0', trim: '#cccccc' }, { details: 'modern', @@ -485,8 +485,8 @@ const BY_MOVEMENT_ID: Record = { 49: mk( 'dada-salon', 'Dada salon', - 'Eclectic bourgeois room · mismatched light sources', - { wall: 'plaster-warm', wallSide: 'brick', ceiling: 'plaster-warm', floor: 'parquet-herringbone' }, + 'Eclectic bourgeois room · painted walls and exposed brick', + { wall: 'painted-emulsion', wallSide: 'brick', ceiling: 'painted-emulsion', floor: 'parquet-herringbone' }, { wall: '#e8dcc8', wallSide: '#8a5040', ceiling: '#f0ebe0', floor: '#8a6848', trim: '#6a4830' }, { details: 'salon', @@ -501,8 +501,8 @@ const BY_MOVEMENT_ID: Record = { 50: mk( 'surrealist-interior', 'Surrealist interior', - 'Bourgeois wallpaper tones · uncanny warm light', - { wall: 'velvet-navy', ceiling: 'dark-plaster', floor: 'parquet-herringbone' }, + '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: 'salon', @@ -520,8 +520,8 @@ const BY_MOVEMENT_ID: Record = { 51: mk( 'abstract-expressionist-loft', 'NYC loft', - 'Raw brick and plaster · north-light factory windows', - { wall: 'brick', wallSide: 'plaster-white', ceiling: 'concrete-raw', floor: 'industrial-floor' }, + 'Raw brick and painted plaster · north-light factory windows', + { wall: 'brick', wallSide: 'painted-flat', ceiling: 'concrete-raw', floor: 'industrial-floor' }, { wall: '#8a5040', wallSide: '#f5f5f5', ceiling: '#989898', floor: '#888880', trim: '#606060' }, { details: 'industrial', @@ -536,8 +536,8 @@ const BY_MOVEMENT_ID: Record = { 52: mk( 'pop-art-gallery', 'Pop Art gallery', - 'White cube · fluorescent daylight · polished concrete', - { wall: 'studio-white', ceiling: 'studio-white', floor: 'concrete-polished' }, + 'White painted walls · fluorescent daylight · polished concrete', + { wall: 'painted-flat', ceiling: 'painted-flat', floor: 'concrete-polished' }, { wall: '#ffffff', ceiling: '#ffffff', floor: '#d8d8d8', trim: '#cccccc' }, { details: 'modern', diff --git a/client/src/hooks/useTexturedMaterial.ts b/client/src/hooks/useTexturedMaterial.ts index 581d382..f6349af 100644 --- a/client/src/hooks/useTexturedMaterial.ts +++ b/client/src/hooks/useTexturedMaterial.ts @@ -18,10 +18,11 @@ export function useTexturedMaterial( return new THREE.MeshStandardMaterial({ map: surf.map, normalMap: surf.normalMap, + normalScale: new THREE.Vector2(surf.normalScale, surf.normalScale), color: tint, roughness: surf.roughness, metalness: surf.metalness, - envMapIntensity: 0.65, + envMapIntensity: kind.startsWith('painted-') ? 0.45 : 0.65, }); }, [kind, tint, spanW, spanH]); diff --git a/client/src/utils/galleryProceduralTextures.ts b/client/src/utils/galleryProceduralTextures.ts index 6a40418..62d50d8 100644 --- a/client/src/utils/galleryProceduralTextures.ts +++ b/client/src/utils/galleryProceduralTextures.ts @@ -41,7 +41,12 @@ export type SurfaceTextureKind = | 'dark-plaster' | 'pastel-plaster' | 'white-plaster' - | 'studio-white'; + | 'studio-white' + | 'painted-lime' + | 'painted-oil-matte' + | 'painted-oil-satin' + | 'painted-emulsion' + | 'painted-flat'; export interface SurfaceTextureSet { map: THREE.CanvasTexture; @@ -49,6 +54,7 @@ export interface SurfaceTextureSet { roughness: number; metalness: number; metersPerRepeat: number; + normalScale: number; } const cache = new Map(); @@ -94,28 +100,207 @@ function noiseOverlay(ctx: CanvasRenderingContext2D, size: number, alpha: number ctx.putImageData(img, 0, 0); } +/** Fractal value noise for organic surface variation. */ +function fbm(x: number, y: number, seed: number, octaves = 5): number { + let value = 0; + let amp = 0.5; + let freq = 1; + for (let i = 0; i < octaves; i++) { + value += + amp * + (Math.sin(x * freq * 12.9898 + seed * 0.137 + i * 1.7) * + Math.cos(y * freq * 78.233 + seed * 0.311 + i * 2.3) * + 0.5 + + 0.5); + amp *= 0.52; + freq *= 2.05; + } + return value; +} + +function paintNeutralBase(ctx: CanvasRenderingContext2D, size: number) { + fill(ctx, size, '#f8f8f6'); +} + +type PaintFinish = 'lime' | 'oil-matte' | 'oil-satin' | 'emulsion' | 'flat'; + +/** Single-colour painted wall — neutral base so movement tint drives the hue. */ +function paintedWallSurface( + ctx: CanvasRenderingContext2D, + size: number, + finish: PaintFinish, + seed: number +) { + paintNeutralBase(ctx, size); + const img = ctx.getImageData(0, 0, size, size); + const d = img.data; + + for (let y = 0; y < size; y++) { + for (let x = 0; x < size; x++) { + const i = (y * size + x) * 4; + const u = x / size; + const v = y / size; + let lum = 0; + + if (finish === 'lime') { + lum += (fbm(u * 3.2, v * 3.2, seed) - 0.5) * 0.14; + lum += (fbm(u * 8, v * 8, seed + 17) - 0.5) * 0.06; + lum += Math.sin(v * 40 + fbm(u, 0, seed + 3) * 8) * 0.012; + } else if (finish === 'oil-matte') { + lum += Math.sin(v * 55 + fbm(u * 2, 0, seed) * 4) * 0.018; + lum += (fbm(u * 18, v * 18, seed + 5) - 0.5) * 0.04; + lum += (fbm(u * 60, v * 60, seed + 9) - 0.5) * 0.025; + } else if (finish === 'oil-satin') { + lum += Math.sin(v * 48 + fbm(u, 0, seed) * 3) * 0.014; + const sheen = Math.pow(Math.max(0, Math.sin(u * 28 + v * 22 + seed)), 3) * 0.06; + lum += sheen; + } else if (finish === 'emulsion') { + lum += Math.sin(v * (Math.PI * 2 * size) / 105) * 0.022; + lum += (fbm(u * 22, v * 22, seed + 11) - 0.5) * 0.035; + lum += (fbm(u * 70, v * 70, seed + 13) - 0.5) * 0.018; + } else { + lum += (fbm(u * 35, v * 35, seed + 19) - 0.5) * 0.028; + lum += (fbm(u * 90, v * 90, seed + 21) - 0.5) * 0.012; + } + + const delta = lum * 255; + d[i] = Math.min(255, Math.max(0, d[i] + delta)); + d[i + 1] = Math.min(255, Math.max(0, d[i + 1] + delta)); + d[i + 2] = Math.min(255, Math.max(0, d[i + 2] + delta)); + } + } + ctx.putImageData(img, 0, 0); + noiseOverlay(ctx, size, finish === 'flat' ? 0.012 : 0.022, seed + 99); +} + +function plasterSurface(ctx: CanvasRenderingContext2D, size: number, base: string, seed: number) { + fill(ctx, size, base); + const rand = seeded(seed); + for (let pass = 0; pass < 55; pass++) { + const angle = rand() * Math.PI; + const len = size * (0.08 + rand() * 0.22); + const x0 = rand() * size; + const y0 = rand() * size; + ctx.strokeStyle = `rgba(0,0,0,${0.015 + rand() * 0.025})`; + ctx.lineWidth = 6 + rand() * 18; + ctx.beginPath(); + ctx.moveTo(x0, y0); + ctx.lineTo(x0 + Math.cos(angle) * len, y0 + Math.sin(angle) * len); + ctx.stroke(); + } + noiseOverlay(ctx, size, 0.045, seed + 1); +} + +function stuccoSurface(ctx: CanvasRenderingContext2D, size: number, base: string, seed: number) { + fill(ctx, size, base); + const rand = seeded(seed); + for (let i = 0; i < 80; i++) { + const cx = rand() * size; + const cy = rand() * size; + const r = 20 + rand() * 60; + ctx.strokeStyle = `rgba(0,0,0,${0.02 + rand() * 0.04})`; + ctx.lineWidth = 2 + rand() * 4; + ctx.beginPath(); + ctx.arc(cx, cy, r, rand() * Math.PI, rand() * Math.PI + Math.PI * 0.8); + ctx.stroke(); + } + noiseOverlay(ctx, size, 0.07, seed + 2); +} + function marbleVeined(ctx: CanvasRenderingContext2D, size: number, base: string, vein: string, seed: number) { fill(ctx, size, base); const rand = seeded(seed); - for (let i = 0; i < 28; i++) { - ctx.strokeStyle = vein.replace(')', `,${0.06 + rand() * 0.14})`).replace('rgb', 'rgba'); - if (vein.startsWith('#')) { - const [r, g, b] = hexToRgb(vein); - ctx.strokeStyle = `rgba(${r},${g},${b},${0.05 + rand() * 0.12})`; - } - ctx.lineWidth = 1 + rand() * 5; + for (let i = 0; i < 42; i++) { + const [r, g, b] = hexToRgb(vein.startsWith('#') ? vein : '#a8a098'); + ctx.strokeStyle = `rgba(${r},${g},${b},${0.04 + rand() * 0.16})`; + ctx.lineWidth = 0.8 + rand() * 6; ctx.beginPath(); let x = rand() * size; let y = rand() * size; ctx.moveTo(x, y); - for (let s = 0; s < 10; s++) { - x += (rand() - 0.5) * size * 0.18; - y += (rand() - 0.5) * size * 0.12; - ctx.lineTo(x, y); + for (let s = 0; s < 14; s++) { + x += (rand() - 0.5) * size * 0.14; + y += (rand() - 0.5) * size * 0.1; + const cx = (x + rand() * size * 0.05) / 2; + const cy = (y + rand() * size * 0.05) / 2; + ctx.quadraticCurveTo(cx, cy, x, y); } ctx.stroke(); } - noiseOverlay(ctx, size, 0.04, seed + 1); + { + const img = ctx.getImageData(0, 0, size, size); + const d = img.data; + for (let y = 0; y < size; y++) { + for (let x = 0; x < size; x++) { + const i = (y * size + x) * 4; + const n = (fbm(x / 180, y / 180, seed + 50) - 0.5) * 12; + d[i] += n; + d[i + 1] += n; + d[i + 2] += n; + } + } + ctx.putImageData(img, 0, 0); + } + noiseOverlay(ctx, size, 0.035, seed + 1); +} + +function woodPanelSurface( + ctx: CanvasRenderingContext2D, + size: number, + base: string, + seed: number, + plankCount = 14 +) { + const plankH = size / plankCount; + for (let i = 0; i < plankCount; i++) { + const grad = ctx.createLinearGradient(0, i * plankH, size, i * plankH + plankH); + grad.addColorStop(0, shadeHex(base, -18)); + grad.addColorStop(0.35, base); + grad.addColorStop(0.65, shadeHex(base, 8)); + grad.addColorStop(1, shadeHex(base, -12)); + ctx.fillStyle = grad; + ctx.fillRect(0, i * plankH, size, plankH - 2); + + for (let g = 0; g < 16; g++) { + const gx = g * (size / 16); + ctx.strokeStyle = 'rgba(0,0,0,0.06)'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(gx, i * plankH); + const wave = Math.sin(g * 0.8 + i + seed * 0.01) * size * 0.04; + ctx.bezierCurveTo(gx + size * 0.03, i * plankH + plankH * 0.3, gx - size * 0.02 + wave, i * plankH + plankH * 0.7, gx + wave, i * plankH + plankH); + ctx.stroke(); + } + + ctx.strokeStyle = 'rgba(0,0,0,0.28)'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(0, i * plankH); + ctx.lineTo(size, i * plankH); + ctx.stroke(); + } + noiseOverlay(ctx, size, 0.035, seed); +} + +function velvetSurface(ctx: CanvasRenderingContext2D, size: number, base: string, seed: number) { + fill(ctx, size, base); + const img = ctx.getImageData(0, 0, size, size); + const d = img.data; + for (let y = 0; y < size; y++) { + for (let x = 0; x < size; x++) { + const i = (y * size + x) * 4; + const weave = + Math.sin(x * 0.35) * Math.sin(y * 0.35) * 0.04 + + Math.sin(x * 0.12 + y * 0.08 + seed) * 0.025; + const pile = fbm(x / 40, y / 40, seed) * 0.08; + const delta = (weave + pile) * 255; + d[i] += delta; + d[i + 1] += delta; + d[i + 2] += delta; + } + } + ctx.putImageData(img, 0, 0); + noiseOverlay(ctx, size, 0.12, seed + 1); } function paintSurface(kind: SurfaceTextureKind, size: number): ImageData { @@ -125,6 +310,21 @@ function paintSurface(kind: SurfaceTextureKind, size: number): ImageData { const ctx = canvas.getContext('2d')!; switch (kind) { + case 'painted-lime': + paintedWallSurface(ctx, size, 'lime', 501); + break; + case 'painted-oil-matte': + paintedWallSurface(ctx, size, 'oil-matte', 502); + break; + case 'painted-oil-satin': + paintedWallSurface(ctx, size, 'oil-satin', 503); + break; + case 'painted-emulsion': + paintedWallSurface(ctx, size, 'emulsion', 504); + break; + case 'painted-flat': + paintedWallSurface(ctx, size, 'flat', 505); + break; case 'marble-white': case 'marble-veined-carrara': marbleVeined(ctx, size, '#ece8e0', '#a8a098', 101); @@ -148,7 +348,14 @@ function paintSurface(kind: SurfaceTextureKind, size: number): ImageData { } case 'limestone': fill(ctx, size, '#ddd4c4'); - noiseOverlay(ctx, size, 0.08, 104); + for (let y = 0; y < size; y += 4) { + for (let x = 0; x < size; x += 4) { + const n = (fbm(x / 120, y / 120, 104) - 0.5) * 18; + ctx.fillStyle = rgb(221 + n, 212 + n, 196 + n); + ctx.fillRect(x, y, 4, 4); + } + } + noiseOverlay(ctx, size, 0.06, 104); break; case 'sandstone': fill(ctx, size, '#c8b090'); @@ -171,42 +378,31 @@ function paintSurface(kind: SurfaceTextureKind, size: number): ImageData { noiseOverlay(ctx, size, 0.15, 107); break; case 'stucco-cream': - fill(ctx, size, '#f0e8d8'); - noiseOverlay(ctx, size, 0.06, 108); + stuccoSurface(ctx, size, '#f0e8d8', 108); break; case 'stucco-terracotta': - fill(ctx, size, '#c89070'); - noiseOverlay(ctx, size, 0.08, 109); + stuccoSurface(ctx, size, '#c89070', 109); break; case 'plaster-white': case 'white-plaster': case 'studio-white': - fill(ctx, size, '#f8f8f6'); - noiseOverlay(ctx, size, 0.035, 110); + plasterSurface(ctx, size, '#f8f8f6', 110); break; case 'plaster-warm': - fill(ctx, size, '#f2ebe0'); - noiseOverlay(ctx, size, 0.045, 111); + case 'pastel-plaster': + plasterSurface(ctx, size, kind === 'pastel-plaster' ? '#ece4ec' : '#f2ebe0', 111); break; case 'dark-plaster': - fill(ctx, size, '#3a3230'); - noiseOverlay(ctx, size, 0.07, 112); - break; - case 'pastel-plaster': - fill(ctx, size, '#e8dce8'); - noiseOverlay(ctx, size, 0.05, 113); + plasterSurface(ctx, size, '#3a3230', 112); break; case 'velvet-crimson': - fill(ctx, size, '#5c1828'); - noiseOverlay(ctx, size, 0.18, 114); + velvetSurface(ctx, size, '#5c1828', 114); break; case 'velvet-navy': - fill(ctx, size, '#1a2848'); - noiseOverlay(ctx, size, 0.18, 115); + velvetSurface(ctx, size, '#1a2848', 115); break; case 'velvet-emerald': - fill(ctx, size, '#1a4030'); - noiseOverlay(ctx, size, 0.18, 116); + velvetSurface(ctx, size, '#1a4030', 116); break; case 'silk-gold': fill(ctx, size, '#d8c890'); @@ -217,34 +413,14 @@ function paintSurface(kind: SurfaceTextureKind, size: number): ImageData { noiseOverlay(ctx, size, 0.05, 118); break; case 'oak-panel': + woodPanelSurface(ctx, size, '#8a6848', 119); + break; case 'walnut-panel': - case 'dark-wood-panel': { - const base = kind === 'oak-panel' ? '#8a6848' : kind === 'walnut-panel' ? '#5a4030' : '#3a2818'; - const plankH = size / 14; - for (let i = 0; i < 14; i++) { - const grad = ctx.createLinearGradient(0, i * plankH, size, i * plankH); - grad.addColorStop(0, shadeHex(base, -15)); - grad.addColorStop(0.5, base); - grad.addColorStop(1, shadeHex(base, -10)); - ctx.fillStyle = grad; - ctx.fillRect(0, i * plankH, size, plankH - 2); - ctx.strokeStyle = 'rgba(0,0,0,0.25)'; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(0, i * plankH); - ctx.lineTo(size, i * plankH); - ctx.stroke(); - for (let g = 0; g < 12; g++) { - ctx.strokeStyle = 'rgba(0,0,0,0.08)'; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(g * (size / 12), i * plankH); - ctx.lineTo(g * (size / 12) + size * 0.08, i * plankH + plankH); - ctx.stroke(); - } - } + woodPanelSurface(ctx, size, '#5a4030', 120); + break; + case 'dark-wood-panel': + woodPanelSurface(ctx, size, '#3a2818', 121); break; - } case 'parquet-herringbone': case 'parquet-chevrons': { const tones = ['#6b4a2e', '#735234', '#624428', '#7a5638']; @@ -267,7 +443,7 @@ function paintSurface(kind: SurfaceTextureKind, size: number): ImageData { const tile = size / 8; for (let row = 0; row < 8; row++) { for (let col = 0; col < 8; col++) { - ctx.fillStyle = ['#b87050', '#c88058', '#a86048'][ (row + col) % 3]; + ctx.fillStyle = ['#b87050', '#c88058', '#a86048'][(row + col) % 3]; ctx.fillRect(col * tile + 2, row * tile + 2, tile - 4, tile - 4); } } @@ -289,7 +465,7 @@ function paintSurface(kind: SurfaceTextureKind, size: number): ImageData { const colors = ['#d4af37', '#8a3020', '#2060a0', '#f0ece0', '#408040']; for (let y = 0; y < size; y += cell) { for (let x = 0; x < size; x += cell) { - ctx.fillStyle = colors[((x / cell) + (y / cell)) % colors.length]; + ctx.fillStyle = colors[(x / cell + y / cell) % colors.length]; ctx.fillRect(x + 1, y + 1, cell - 2, cell - 2); } } @@ -301,7 +477,7 @@ function paintSurface(kind: SurfaceTextureKind, size: number): ImageData { const colors = ['#8a7060', '#a88870', '#706050', '#d0c0a0']; for (let y = 0; y < size; y += cell) { for (let x = 0; x < size; x += cell) { - ctx.fillStyle = colors[((x / cell) + (y / cell)) % colors.length]; + ctx.fillStyle = colors[(x / cell + y / cell) % colors.length]; ctx.fillRect(x + 1, y + 1, cell - 2, cell - 2); } } @@ -347,22 +523,23 @@ function paintSurface(kind: SurfaceTextureKind, size: number): ImageData { noiseOverlay(ctx, size, 0.12, 124); break; case 'gilded-stucco': - fill(ctx, size, '#d8c070'); - noiseOverlay(ctx, size, 0.05, 125); + stuccoSurface(ctx, size, '#d8c070', 125); break; case 'fresco-worn': fill(ctx, size, '#d0c8b8'); - noiseOverlay(ctx, size, 0.09, 126); + plasterSurface(ctx, size, '#d0c8b8', 126); break; case 'brick': { const bh = size / 16; const bw = size / 8; - fill(ctx, size, '#6a4030'); + fill(ctx, size, '#5a4030'); for (let row = 0; row < 16; row++) { const offset = row % 2 ? bw / 2 : 0; for (let col = -1; col < 9; col++) { + const rand = seeded(130 + row * 9 + col); ctx.fillStyle = ['#8a5040', '#7a4838', '#9a5848'][(row + col) % 3]; - ctx.fillRect(col * bw + offset, row * bh, bw - 3, bh - 3); + const inset = 2 + rand() * 2; + ctx.fillRect(col * bw + offset + inset, row * bh + inset, bw - inset * 2, bh - inset * 2); } } break; @@ -379,7 +556,7 @@ function imageDataToNormalMap(data: ImageData, size: number, strength = 2.5): Im const out = new ImageData(size, size); const src = data.data; const dst = out.data; - const idx = (x: number, y: number) => ((y * size + x) * 4); + const idx = (x: number, y: number) => (y * size + x) * 4; for (let y = 0; y < size; y++) { for (let x = 0; x < size; x++) { @@ -419,6 +596,16 @@ function imageDataToTexture(data: ImageData): THREE.CanvasTexture { return tex; } +function normalStrengthFor(kind: SurfaceTextureKind): number { + if (kind.startsWith('painted-')) return 2.2; + if (kind.includes('velvet')) return 1.6; + if (kind.includes('marble')) return 3.4; + if (kind.includes('stucco') || kind.includes('plaster')) return 2.6; + if (kind.includes('panel') || kind.includes('oak') || kind.includes('walnut')) return 2.8; + if (kind === 'brick' || kind === 'rough-stone') return 3.2; + return 2.5; +} + const ROUGHNESS: Partial> = { 'marble-white': 0.18, 'marble-veined-carrara': 0.16, @@ -431,7 +618,21 @@ const ROUGHNESS: Partial> = { 'concrete-polished': 0.28, 'concrete-raw': 0.72, 'oak-panel': 0.55, + 'walnut-panel': 0.58, + 'dark-wood-panel': 0.62, 'parquet-herringbone': 0.42, + 'painted-lime': 0.88, + 'painted-oil-matte': 0.82, + 'painted-oil-satin': 0.68, + 'painted-emulsion': 0.86, + 'painted-flat': 0.94, + 'limestone': 0.78, + 'stucco-cream': 0.8, + 'stucco-terracotta': 0.78, + 'plaster-white': 0.9, + 'plaster-warm': 0.88, + 'white-plaster': 0.92, + 'studio-white': 0.94, }; const METALNESS: Partial> = { @@ -440,17 +641,41 @@ const METALNESS: Partial> = { 'gilded-stucco': 0.65, 'concrete-polished': 0.12, 'terrazzo': 0.15, + 'painted-oil-satin': 0.06, + 'painted-flat': 0.02, }; const METERS: Partial> = { 'marble-checker': 2.4, - 'flagstone': 3.0, + flagstone: 3.0, 'mosaic-byzantine': 1.8, 'mosaic-roman': 2.0, 'parquet-herringbone': 1.8, 'parquet-chevrons': 1.8, 'concrete-raw': 4.0, - 'brick': 2.5, + brick: 2.5, + 'painted-lime': 3.2, + 'painted-oil-matte': 3.0, + 'painted-oil-satin': 3.0, + 'painted-emulsion': 3.5, + 'painted-flat': 4.0, +}; + +const NORMAL_SCALE: Partial> = { + 'painted-flat': 0.32, + 'painted-emulsion': 0.48, + 'painted-oil-satin': 0.42, + 'painted-oil-matte': 0.55, + 'painted-lime': 0.62, + 'marble-white': 0.75, + 'marble-veined-carrara': 0.8, + 'velvet-crimson': 0.5, + 'velvet-navy': 0.5, + 'velvet-emerald': 0.5, + 'oak-panel': 0.7, + 'brick': 0.85, + 'stucco-cream': 0.65, + 'plaster-warm': 0.58, }; export function getSurfaceTexture(kind: SurfaceTextureKind): SurfaceTextureSet { @@ -458,7 +683,7 @@ export function getSurfaceTexture(kind: SurfaceTextureKind): SurfaceTextureSet { if (cached) return cached; const colorData = paintSurface(kind, TEXTURE_SIZE); - const normalData = imageDataToNormalMap(colorData, TEXTURE_SIZE, kind.includes('velvet') ? 1.2 : 2.8); + const normalData = imageDataToNormalMap(colorData, TEXTURE_SIZE, normalStrengthFor(kind)); const set: SurfaceTextureSet = { map: imageDataToTexture(colorData), @@ -466,6 +691,7 @@ export function getSurfaceTexture(kind: SurfaceTextureKind): SurfaceTextureSet { roughness: ROUGHNESS[kind] ?? 0.75, metalness: METALNESS[kind] ?? 0.04, metersPerRepeat: METERS[kind] ?? 2.8, + normalScale: NORMAL_SCALE[kind] ?? 0.6, }; cache.set(kind, set); return set; diff --git a/client/vite.config.ts b/client/vite.config.ts index 280ffd3..1cdb4c6 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,13 +1,19 @@ -import { defineConfig } from 'vite' +import path from 'node:path' +import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' // https://vite.dev/config/ -export default defineConfig({ - plugins: [react()], - server: { - proxy: { - '/api': 'http://localhost:3001', - '/images': 'http://localhost:3001', +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, path.resolve(__dirname, '..'), '') + const apiTarget = `http://localhost:${env.PORT || '3001'}` + + return { + plugins: [react()], + server: { + proxy: { + '/api': apiTarget, + '/images': apiTarget, + }, }, - }, -}) \ No newline at end of file + } +}) diff --git a/data/images/paintings/Alphonse_Mucha_Job_Cigarette_Papers.jpeg b/data/images/paintings/Alphonse_Mucha_Job_Cigarette_Papers.jpeg new file mode 100644 index 0000000..21fe695 Binary files /dev/null and b/data/images/paintings/Alphonse_Mucha_Job_Cigarette_Papers.jpeg differ diff --git a/data/images/paintings/Alphonse_Mucha_Job_Cigarette_Papers.jpg b/data/images/paintings/Alphonse_Mucha_Job_Cigarette_Papers.jpg deleted file mode 100644 index e443ced..0000000 Binary files a/data/images/paintings/Alphonse_Mucha_Job_Cigarette_Papers.jpg and /dev/null differ diff --git a/data/images/paintings/Bronzino_Venus__Cupid__Folly_and_Time.jpg b/data/images/paintings/Bronzino_Venus__Cupid__Folly_and_Time.jpg index 2381089..f99a87e 100644 Binary files a/data/images/paintings/Bronzino_Venus__Cupid__Folly_and_Time.jpg and b/data/images/paintings/Bronzino_Venus__Cupid__Folly_and_Time.jpg differ diff --git a/data/images/paintings/Claude_Monet_Water_Lilies.jpg b/data/images/paintings/Claude_Monet_Water_Lilies.jpg index 94768d3..1d362f0 100644 Binary files a/data/images/paintings/Claude_Monet_Water_Lilies.jpg and b/data/images/paintings/Claude_Monet_Water_Lilies.jpg differ diff --git a/data/images/paintings/Diego_Vel_zquez_Portrait_du_Conde-Duque_de_Olivares.jpg b/data/images/paintings/Diego_Vel_zquez_Portrait_du_Conde-Duque_de_Olivares.jpg index 2b01595..52f56bc 100644 Binary files a/data/images/paintings/Diego_Vel_zquez_Portrait_du_Conde-Duque_de_Olivares.jpg and b/data/images/paintings/Diego_Vel_zquez_Portrait_du_Conde-Duque_de_Olivares.jpg differ diff --git a/data/images/paintings/Diego_Vel_zquez_Portrait_of_Philip_IV__of_Spain.jpg b/data/images/paintings/Diego_Vel_zquez_Portrait_of_Philip_IV__of_Spain.jpg index b740bef..91dee5d 100644 Binary files a/data/images/paintings/Diego_Vel_zquez_Portrait_of_Philip_IV__of_Spain.jpg and b/data/images/paintings/Diego_Vel_zquez_Portrait_of_Philip_IV__of_Spain.jpg differ diff --git a/data/images/paintings/Diego_Vel_zquez_Queen_Mariana_of_Spain__1634_1696_.jpg b/data/images/paintings/Diego_Vel_zquez_Queen_Mariana_of_Spain__1634_1696_.jpg index 2b01595..a1a6ac8 100644 Binary files a/data/images/paintings/Diego_Vel_zquez_Queen_Mariana_of_Spain__1634_1696_.jpg and b/data/images/paintings/Diego_Vel_zquez_Queen_Mariana_of_Spain__1634_1696_.jpg differ diff --git a/data/images/paintings/Diego_Vel_zquez_Still_Life.jpg b/data/images/paintings/Diego_Vel_zquez_Still_Life.jpg index d6b334d..4ccb13e 100644 Binary files a/data/images/paintings/Diego_Vel_zquez_Still_Life.jpg and b/data/images/paintings/Diego_Vel_zquez_Still_Life.jpg differ diff --git a/data/images/paintings/Diego_Vel_zquez_The_Buffoon__Pablo_de_Valladolid.jpg b/data/images/paintings/Diego_Vel_zquez_The_Buffoon__Pablo_de_Valladolid.jpg index 2b01595..214f6f5 100644 Binary files a/data/images/paintings/Diego_Vel_zquez_The_Buffoon__Pablo_de_Valladolid.jpg and b/data/images/paintings/Diego_Vel_zquez_The_Buffoon__Pablo_de_Valladolid.jpg differ diff --git a/data/images/paintings/Diego_Vel_zquez_The_Dwarf_Francisco_Lezcano.jpg b/data/images/paintings/Diego_Vel_zquez_The_Dwarf_Francisco_Lezcano.jpg index b740bef..1a15773 100644 Binary files a/data/images/paintings/Diego_Vel_zquez_The_Dwarf_Francisco_Lezcano.jpg and b/data/images/paintings/Diego_Vel_zquez_The_Dwarf_Francisco_Lezcano.jpg differ diff --git a/data/images/paintings/Domenico_Ghirlandaio_Virgin_and_Child.jpg b/data/images/paintings/Domenico_Ghirlandaio_Virgin_and_Child.jpg index d36a157..ac91c08 100644 Binary files a/data/images/paintings/Domenico_Ghirlandaio_Virgin_and_Child.jpg and b/data/images/paintings/Domenico_Ghirlandaio_Virgin_and_Child.jpg differ diff --git a/data/images/paintings/Edgar_Degas_L_Absinthe.jpg b/data/images/paintings/Edgar_Degas_L_Absinthe.jpg index d23bf67..f30baec 100644 Binary files a/data/images/paintings/Edgar_Degas_L_Absinthe.jpg and b/data/images/paintings/Edgar_Degas_L_Absinthe.jpg differ diff --git a/data/images/paintings/Edgar_Degas_The_Ballet_Class.jpg b/data/images/paintings/Edgar_Degas_The_Ballet_Class.jpg index 3a82ed2..85a9c8c 100644 Binary files a/data/images/paintings/Edgar_Degas_The_Ballet_Class.jpg and b/data/images/paintings/Edgar_Degas_The_Ballet_Class.jpg differ diff --git a/data/images/paintings/Edvard_Munch_The_Scream.jpg b/data/images/paintings/Edvard_Munch_The_Scream.jpg index bbe8798..65f8b09 100644 Binary files a/data/images/paintings/Edvard_Munch_The_Scream.jpg and b/data/images/paintings/Edvard_Munch_The_Scream.jpg differ diff --git a/data/images/paintings/Francis_Picabia_Amorous_Parade.jpg b/data/images/paintings/Francis_Picabia_Amorous_Parade.jpg index b5c22d3..3b4536d 100644 Binary files a/data/images/paintings/Francis_Picabia_Amorous_Parade.jpg and b/data/images/paintings/Francis_Picabia_Amorous_Parade.jpg differ diff --git a/data/images/paintings/Francis_Picabia_Transparence.jpg b/data/images/paintings/Francis_Picabia_Transparence.jpg index f385747..8999ebb 100644 Binary files a/data/images/paintings/Francis_Picabia_Transparence.jpg and b/data/images/paintings/Francis_Picabia_Transparence.jpg differ diff --git a/data/images/paintings/Georges_Braque_Violin_and_Candlestick.jpg b/data/images/paintings/Georges_Braque_Violin_and_Candlestick.jpg index 7bbf5f9..0da1c96 100644 Binary files a/data/images/paintings/Georges_Braque_Violin_and_Candlestick.jpg and b/data/images/paintings/Georges_Braque_Violin_and_Candlestick.jpg differ diff --git a/data/images/paintings/Georges_Seurat_A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte.jpg b/data/images/paintings/Georges_Seurat_A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte.jpg index 38cc460..813a3c5 100644 Binary files a/data/images/paintings/Georges_Seurat_A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte.jpg and b/data/images/paintings/Georges_Seurat_A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte.jpg differ diff --git a/data/images/paintings/Giorgio_de_Chirico_The_Disquieting_Muses.jpg b/data/images/paintings/Giorgio_de_Chirico_The_Disquieting_Muses.jpg index 27f7d5c..68c6b88 100644 Binary files a/data/images/paintings/Giorgio_de_Chirico_The_Disquieting_Muses.jpg and b/data/images/paintings/Giorgio_de_Chirico_The_Disquieting_Muses.jpg differ diff --git a/data/images/paintings/Giotto_St_Francis_before_the_Sultan.jpg b/data/images/paintings/Giotto_St_Francis_before_the_Sultan.jpg index fedfa35..a257883 100644 Binary files a/data/images/paintings/Giotto_St_Francis_before_the_Sultan.jpg and b/data/images/paintings/Giotto_St_Francis_before_the_Sultan.jpg differ diff --git a/data/images/paintings/Gustav_Klimt_The_Kiss.jpg b/data/images/paintings/Gustav_Klimt_The_Kiss.jpg index 601fabd..9b0f353 100644 Binary files a/data/images/paintings/Gustav_Klimt_The_Kiss.jpg and b/data/images/paintings/Gustav_Klimt_The_Kiss.jpg differ diff --git a/data/images/paintings/Hannah_H_ch_Cut_with_the_Dada_Kitchen_Knife.jpg b/data/images/paintings/Hannah_H_ch_Cut_with_the_Dada_Kitchen_Knife.jpg index ac40123..227aafe 100644 Binary files a/data/images/paintings/Hannah_H_ch_Cut_with_the_Dada_Kitchen_Knife.jpg and b/data/images/paintings/Hannah_H_ch_Cut_with_the_Dada_Kitchen_Knife.jpg differ diff --git a/data/images/paintings/Henri_Matisse_The_Dance.jpg b/data/images/paintings/Henri_Matisse_The_Dance.jpg index dd1cd4e..d47e611 100644 Binary files a/data/images/paintings/Henri_Matisse_The_Dance.jpg and b/data/images/paintings/Henri_Matisse_The_Dance.jpg differ diff --git a/data/images/paintings/Henri_Matisse_The_Red_Room.Jpeg b/data/images/paintings/Henri_Matisse_The_Red_Room.Jpeg new file mode 100644 index 0000000..3285be5 Binary files /dev/null and b/data/images/paintings/Henri_Matisse_The_Red_Room.Jpeg differ diff --git a/data/images/paintings/Henri_Matisse_The_Red_Room.jpg b/data/images/paintings/Henri_Matisse_The_Red_Room.jpg deleted file mode 100644 index dd1cd4e..0000000 Binary files a/data/images/paintings/Henri_Matisse_The_Red_Room.jpg and /dev/null differ diff --git a/data/images/paintings/Jackson_Pollock_Autumn_Rhythm.jpg b/data/images/paintings/Jackson_Pollock_Autumn_Rhythm.jpg index f730f49..f898efd 100644 Binary files a/data/images/paintings/Jackson_Pollock_Autumn_Rhythm.jpg and b/data/images/paintings/Jackson_Pollock_Autumn_Rhythm.jpg differ diff --git a/data/images/paintings/Jackson_Pollock_Number_1__1950__Lavender_Mist_.jpg b/data/images/paintings/Jackson_Pollock_Number_1__1950__Lavender_Mist_.jpg index 49f30c8..4316060 100644 Binary files a/data/images/paintings/Jackson_Pollock_Number_1__1950__Lavender_Mist_.jpg and b/data/images/paintings/Jackson_Pollock_Number_1__1950__Lavender_Mist_.jpg differ diff --git a/data/images/paintings/Jacques-Louis_David_The_Oath_of_the_Horatii.jpg b/data/images/paintings/Jacques-Louis_David_The_Oath_of_the_Horatii.jpg new file mode 100644 index 0000000..30c1915 Binary files /dev/null and b/data/images/paintings/Jacques-Louis_David_The_Oath_of_the_Horatii.jpg differ diff --git a/data/images/paintings/Jean-Baptiste-Camille_Corot_Forest_of_Fontainebleau.jpg b/data/images/paintings/Jean-Baptiste-Camille_Corot_Forest_of_Fontainebleau.jpg new file mode 100644 index 0000000..0d2bd7a Binary files /dev/null and b/data/images/paintings/Jean-Baptiste-Camille_Corot_Forest_of_Fontainebleau.jpg differ diff --git a/data/images/paintings/Kazimir_Malevich_Black_Square.jpg b/data/images/paintings/Kazimir_Malevich_Black_Square.jpg deleted file mode 100644 index 2e1a7de..0000000 Binary files a/data/images/paintings/Kazimir_Malevich_Black_Square.jpg and /dev/null differ diff --git a/data/images/paintings/Kazimir_Malevich_Black_Square.png b/data/images/paintings/Kazimir_Malevich_Black_Square.png new file mode 100644 index 0000000..04975f8 Binary files /dev/null and b/data/images/paintings/Kazimir_Malevich_Black_Square.png differ diff --git a/data/images/paintings/Marcel_Duchamp_Bicycle_Wheel.jpg b/data/images/paintings/Marcel_Duchamp_Bicycle_Wheel.jpg index 4d8e8b6..d4aacea 100644 Binary files a/data/images/paintings/Marcel_Duchamp_Bicycle_Wheel.jpg and b/data/images/paintings/Marcel_Duchamp_Bicycle_Wheel.jpg differ diff --git a/data/images/paintings/Marcel_Duchamp_Fountain.jpg b/data/images/paintings/Marcel_Duchamp_Fountain.jpg index 4d8e8b6..79028ac 100644 Binary files a/data/images/paintings/Marcel_Duchamp_Fountain.jpg and b/data/images/paintings/Marcel_Duchamp_Fountain.jpg differ diff --git a/data/images/paintings/Marcel_Duchamp_L_H_O_O_Q_.jpg b/data/images/paintings/Marcel_Duchamp_L_H_O_O_Q_.jpg index 4d8e8b6..57a7307 100644 Binary files a/data/images/paintings/Marcel_Duchamp_L_H_O_O_Q_.jpg and b/data/images/paintings/Marcel_Duchamp_L_H_O_O_Q_.jpg differ diff --git a/data/images/paintings/Marcel_Duchamp_Nude_Descending_a_Staircase__No__2.jpg b/data/images/paintings/Marcel_Duchamp_Nude_Descending_a_Staircase__No__2.jpg index 0d10ef8..55a85cb 100644 Binary files a/data/images/paintings/Marcel_Duchamp_Nude_Descending_a_Staircase__No__2.jpg and b/data/images/paintings/Marcel_Duchamp_Nude_Descending_a_Staircase__No__2.jpg differ diff --git a/data/images/paintings/Marcel_Duchamp_The_Bride_Stripped_Bare_by_Her_Bachelors__Even.jpg b/data/images/paintings/Marcel_Duchamp_The_Bride_Stripped_Bare_by_Her_Bachelors__Even.jpg index 4d8e8b6..ff257a4 100644 Binary files a/data/images/paintings/Marcel_Duchamp_The_Bride_Stripped_Bare_by_Her_Bachelors__Even.jpg and b/data/images/paintings/Marcel_Duchamp_The_Bride_Stripped_Bare_by_Her_Bachelors__Even.jpg differ diff --git a/data/images/paintings/Mark_Rothko_No__61__Rust_and_Blue_.jpg b/data/images/paintings/Mark_Rothko_No__61__Rust_and_Blue_.jpg new file mode 100644 index 0000000..9d9a68e Binary files /dev/null and b/data/images/paintings/Mark_Rothko_No__61__Rust_and_Blue_.jpg differ diff --git a/data/images/paintings/Max_Ernst_Europe_After_the_Rain_II.jpg b/data/images/paintings/Max_Ernst_Europe_After_the_Rain_II.jpg deleted file mode 100644 index d47583b..0000000 Binary files a/data/images/paintings/Max_Ernst_Europe_After_the_Rain_II.jpg and /dev/null differ diff --git a/data/images/paintings/Max_Ernst_Europe_After_the_Rain_II.png b/data/images/paintings/Max_Ernst_Europe_After_the_Rain_II.png new file mode 100644 index 0000000..7f40d2c Binary files /dev/null and b/data/images/paintings/Max_Ernst_Europe_After_the_Rain_II.png differ diff --git a/data/images/paintings/Max_Ernst_The_Elephant_Celebes.jpg b/data/images/paintings/Max_Ernst_The_Elephant_Celebes.jpg index d47583b..7a57487 100644 Binary files a/data/images/paintings/Max_Ernst_The_Elephant_Celebes.jpg and b/data/images/paintings/Max_Ernst_The_Elephant_Celebes.jpg differ diff --git a/data/images/paintings/Nicolas_Poussin_Rape_of_the_Sabine_Women.jpg b/data/images/paintings/Nicolas_Poussin_Rape_of_the_Sabine_Women.jpg index 864bbe0..742415e 100644 Binary files a/data/images/paintings/Nicolas_Poussin_Rape_of_the_Sabine_Women.jpg and b/data/images/paintings/Nicolas_Poussin_Rape_of_the_Sabine_Women.jpg differ diff --git a/data/images/paintings/Parmigianino_Antea.jpg b/data/images/paintings/Parmigianino_Antea.jpg index 7ff16b7..4a15df0 100644 Binary files a/data/images/paintings/Parmigianino_Antea.jpg and b/data/images/paintings/Parmigianino_Antea.jpg differ diff --git a/data/images/paintings/Parmigianino_Cupid_Carving_his_Bow.jpg b/data/images/paintings/Parmigianino_Cupid_Carving_his_Bow.jpg new file mode 100644 index 0000000..ee7bc4b Binary files /dev/null and b/data/images/paintings/Parmigianino_Cupid_Carving_his_Bow.jpg differ diff --git a/data/images/paintings/Parmigianino_Self-Portrait_in_a_Convex_Mirror.jpg b/data/images/paintings/Parmigianino_Self-Portrait_in_a_Convex_Mirror.jpg index d3e6fb2..d27dc0c 100644 Binary files a/data/images/paintings/Parmigianino_Self-Portrait_in_a_Convex_Mirror.jpg and b/data/images/paintings/Parmigianino_Self-Portrait_in_a_Convex_Mirror.jpg differ diff --git a/data/images/paintings/Parmigianino_Turkish_Slave.jpg b/data/images/paintings/Parmigianino_Turkish_Slave.jpg new file mode 100644 index 0000000..dde196a Binary files /dev/null and b/data/images/paintings/Parmigianino_Turkish_Slave.jpg differ diff --git a/data/images/paintings/Paul_C_zanne_The_Bathers.jpg b/data/images/paintings/Paul_C_zanne_The_Bathers.jpg index 1ae31c7..afb8301 100644 Binary files a/data/images/paintings/Paul_C_zanne_The_Bathers.jpg and b/data/images/paintings/Paul_C_zanne_The_Bathers.jpg differ diff --git a/data/images/paintings/Paul_C_zanne_The_Card_Players.jpg b/data/images/paintings/Paul_C_zanne_The_Card_Players.jpg index 2e1a7de..12c7914 100644 Binary files a/data/images/paintings/Paul_C_zanne_The_Card_Players.jpg and b/data/images/paintings/Paul_C_zanne_The_Card_Players.jpg differ diff --git a/data/images/paintings/Paul_Gauguin_Where_Do_We_Come_From_.jpg b/data/images/paintings/Paul_Gauguin_Where_Do_We_Come_From_.jpg index f90b7ac..5a86a5d 100644 Binary files a/data/images/paintings/Paul_Gauguin_Where_Do_We_Come_From_.jpg and b/data/images/paintings/Paul_Gauguin_Where_Do_We_Come_From_.jpg differ diff --git a/data/images/paintings/Pierre-Auguste_Renoir_Dance_at_Le_Moulin_de_la_Galette.jpg b/data/images/paintings/Pierre-Auguste_Renoir_Dance_at_Le_Moulin_de_la_Galette.jpg index 436dbca..0f08987 100644 Binary files a/data/images/paintings/Pierre-Auguste_Renoir_Dance_at_Le_Moulin_de_la_Galette.jpg and b/data/images/paintings/Pierre-Auguste_Renoir_Dance_at_Le_Moulin_de_la_Galette.jpg differ diff --git a/data/images/paintings/Ren__Magritte_The_Treachery_of_Images.jpg b/data/images/paintings/Ren__Magritte_The_Treachery_of_Images.jpg index c94034e..ee35d85 100644 Binary files a/data/images/paintings/Ren__Magritte_The_Treachery_of_Images.jpg and b/data/images/paintings/Ren__Magritte_The_Treachery_of_Images.jpg differ diff --git a/data/images/paintings/Sandro_Botticelli_Saint_John_the_Baptist.jpg b/data/images/paintings/Sandro_Botticelli_Saint_John_the_Baptist.jpg index 4320268..1ccbec5 100644 Binary files a/data/images/paintings/Sandro_Botticelli_Saint_John_the_Baptist.jpg and b/data/images/paintings/Sandro_Botticelli_Saint_John_the_Baptist.jpg differ diff --git a/data/images/paintings/Sandro_Botticelli_Two_angels.jpg b/data/images/paintings/Sandro_Botticelli_Two_angels.jpg index 6175748..1b0d2a5 100644 Binary files a/data/images/paintings/Sandro_Botticelli_Two_angels.jpg and b/data/images/paintings/Sandro_Botticelli_Two_angels.jpg differ diff --git a/data/images/paintings/Titian_Adoration_of_the_Christ_Child.jpg b/data/images/paintings/Titian_Adoration_of_the_Christ_Child.jpg index 3097b64..8873e17 100644 Binary files a/data/images/paintings/Titian_Adoration_of_the_Christ_Child.jpg and b/data/images/paintings/Titian_Adoration_of_the_Christ_Child.jpg differ diff --git a/data/images/paintings/Titian_Bache_Madonna.jpg b/data/images/paintings/Titian_Bache_Madonna.jpg index f759ce2..2f3bd42 100644 Binary files a/data/images/paintings/Titian_Bache_Madonna.jpg and b/data/images/paintings/Titian_Bache_Madonna.jpg differ diff --git a/data/images/paintings/Titian_Christ_Carrying_the_Cross.jpg b/data/images/paintings/Titian_Christ_Carrying_the_Cross.jpg index d9089ed..5865b57 100644 Binary files a/data/images/paintings/Titian_Christ_Carrying_the_Cross.jpg and b/data/images/paintings/Titian_Christ_Carrying_the_Cross.jpg differ diff --git a/data/images/paintings/Titian_Christ_and_the_Woman_Taken_in_Adultery.jpg b/data/images/paintings/Titian_Christ_and_the_Woman_Taken_in_Adultery.jpg index 36e544a..aff09a0 100644 Binary files a/data/images/paintings/Titian_Christ_and_the_Woman_Taken_in_Adultery.jpg and b/data/images/paintings/Titian_Christ_and_the_Woman_Taken_in_Adultery.jpg differ diff --git a/data/images/paintings/Titian_Endymion_and_his_Flock.jpg b/data/images/paintings/Titian_Endymion_and_his_Flock.jpg index f759ce2..21cb7da 100644 Binary files a/data/images/paintings/Titian_Endymion_and_his_Flock.jpg and b/data/images/paintings/Titian_Endymion_and_his_Flock.jpg differ diff --git a/data/images/paintings/Titian_Flight_into_Egypt.jpg b/data/images/paintings/Titian_Flight_into_Egypt.jpg index 23f66ec..5788bb6 100644 Binary files a/data/images/paintings/Titian_Flight_into_Egypt.jpg and b/data/images/paintings/Titian_Flight_into_Egypt.jpg differ diff --git a/data/images/paintings/Titian_Giustiniani_Portrait.jpg b/data/images/paintings/Titian_Giustiniani_Portrait.jpg index f759ce2..958ca13 100644 Binary files a/data/images/paintings/Titian_Giustiniani_Portrait.jpg and b/data/images/paintings/Titian_Giustiniani_Portrait.jpg differ diff --git a/data/images/paintings/Titian_Head_of_a_Man.jpg b/data/images/paintings/Titian_Head_of_a_Man.jpg index ea5a023..7b6d5f7 100644 Binary files a/data/images/paintings/Titian_Head_of_a_Man.jpg and b/data/images/paintings/Titian_Head_of_a_Man.jpg differ diff --git a/data/images/paintings/Titian_Jacopo_Pesaro__Bishop_of_Paphos__being_presented_by_Pope_Alexander_VI_to_Saint_Peter.jpg b/data/images/paintings/Titian_Jacopo_Pesaro__Bishop_of_Paphos__being_presented_by_Pope_Alexander_VI_to_Saint_Peter.jpg index e774f24..f68459c 100644 Binary files a/data/images/paintings/Titian_Jacopo_Pesaro__Bishop_of_Paphos__being_presented_by_Pope_Alexander_VI_to_Saint_Peter.jpg and b/data/images/paintings/Titian_Jacopo_Pesaro__Bishop_of_Paphos__being_presented_by_Pope_Alexander_VI_to_Saint_Peter.jpg differ diff --git a/data/images/paintings/Titian_Justice.jpg b/data/images/paintings/Titian_Justice.jpg index f759ce2..4ce5ab2 100644 Binary files a/data/images/paintings/Titian_Justice.jpg and b/data/images/paintings/Titian_Justice.jpg differ diff --git a/data/images/paintings/Titian_Lochis_Madonna.jpg b/data/images/paintings/Titian_Lochis_Madonna.jpg index f759ce2..2e401e8 100644 Binary files a/data/images/paintings/Titian_Lochis_Madonna.jpg and b/data/images/paintings/Titian_Lochis_Madonna.jpg differ diff --git a/data/images/paintings/Titian_Member_of_the_Calza.jpg b/data/images/paintings/Titian_Member_of_the_Calza.jpg deleted file mode 100644 index f759ce2..0000000 Binary files a/data/images/paintings/Titian_Member_of_the_Calza.jpg and /dev/null differ diff --git a/data/images/paintings/Titian_Orpheus_and_Eurydice.jpg b/data/images/paintings/Titian_Orpheus_and_Eurydice.jpg index 64f790a..6e74ed0 100644 Binary files a/data/images/paintings/Titian_Orpheus_and_Eurydice.jpg and b/data/images/paintings/Titian_Orpheus_and_Eurydice.jpg differ diff --git a/data/images/paintings/Titian_Portrait_of_a_Man.jpg b/data/images/paintings/Titian_Portrait_of_a_Man.jpg index 463fc0d..0b1c119 100644 Binary files a/data/images/paintings/Titian_Portrait_of_a_Man.jpg and b/data/images/paintings/Titian_Portrait_of_a_Man.jpg differ diff --git a/data/images/paintings/Titian_The_Birth_of_Adonis.jpg b/data/images/paintings/Titian_The_Birth_of_Adonis.jpg index e774f24..4261060 100644 Binary files a/data/images/paintings/Titian_The_Birth_of_Adonis.jpg and b/data/images/paintings/Titian_The_Birth_of_Adonis.jpg differ diff --git a/data/images/paintings/Titian_The_Circumcision_of_Christ.jpg b/data/images/paintings/Titian_The_Circumcision_of_Christ.jpg index 2314a2a..a43a9b4 100644 Binary files a/data/images/paintings/Titian_The_Circumcision_of_Christ.jpg and b/data/images/paintings/Titian_The_Circumcision_of_Christ.jpg differ diff --git a/data/images/paintings/Vincent_van_Gogh_Sunflowers.jpg b/data/images/paintings/Vincent_van_Gogh_Sunflowers.jpg index 85c7c4d..18ab57a 100644 Binary files a/data/images/paintings/Vincent_van_Gogh_Sunflowers.jpg and b/data/images/paintings/Vincent_van_Gogh_Sunflowers.jpg differ diff --git a/data/images/paintings/Vincent_van_Gogh_The_Starry_Night.jpg b/data/images/paintings/Vincent_van_Gogh_The_Starry_Night.jpg index 85c7c4d..c95a79c 100644 Binary files a/data/images/paintings/Vincent_van_Gogh_The_Starry_Night.jpg and b/data/images/paintings/Vincent_van_Gogh_The_Starry_Night.jpg differ diff --git a/data/images/paintings/Wassily_Kandinsky_Composition_VII.jpg b/data/images/paintings/Wassily_Kandinsky_Composition_VII.jpg index 0b1e5f8..2afe326 100644 Binary files a/data/images/paintings/Wassily_Kandinsky_Composition_VII.jpg and b/data/images/paintings/Wassily_Kandinsky_Composition_VII.jpg differ diff --git a/data/images/paintings/Willem_de_Kooning_Woman_I.jpg b/data/images/paintings/Willem_de_Kooning_Woman_I.jpg deleted file mode 100644 index 56021cb..0000000 Binary files a/data/images/paintings/Willem_de_Kooning_Woman_I.jpg and /dev/null differ diff --git a/data/images/paintings/Willem_de_Kooning_Woman_I.png b/data/images/paintings/Willem_de_Kooning_Woman_I.png new file mode 100644 index 0000000..a809f23 Binary files /dev/null and b/data/images/paintings/Willem_de_Kooning_Woman_I.png differ diff --git a/data/images/paintings/_douard_Manet_A_Bar_at_the_Folies-Berg_re.jpg b/data/images/paintings/_douard_Manet_A_Bar_at_the_Folies-Berg_re.jpg new file mode 100644 index 0000000..25bd07a Binary files /dev/null and b/data/images/paintings/_douard_Manet_A_Bar_at_the_Folies-Berg_re.jpg differ diff --git a/data/images/paintings/thumbs/Alphonse_Mucha_Job_Cigarette_Papers_thumb.jpg b/data/images/paintings/thumbs/Alphonse_Mucha_Job_Cigarette_Papers_thumb.jpg index 615b965..1a57a17 100644 Binary files a/data/images/paintings/thumbs/Alphonse_Mucha_Job_Cigarette_Papers_thumb.jpg and b/data/images/paintings/thumbs/Alphonse_Mucha_Job_Cigarette_Papers_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Bronzino_Venus__Cupid__Folly_and_Time_thumb.jpg b/data/images/paintings/thumbs/Bronzino_Venus__Cupid__Folly_and_Time_thumb.jpg index b855746..8f6918b 100644 Binary files a/data/images/paintings/thumbs/Bronzino_Venus__Cupid__Folly_and_Time_thumb.jpg and b/data/images/paintings/thumbs/Bronzino_Venus__Cupid__Folly_and_Time_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Claude_Monet_Water_Lilies_thumb.jpg b/data/images/paintings/thumbs/Claude_Monet_Water_Lilies_thumb.jpg index 817c5d9..4541fb6 100644 Binary files a/data/images/paintings/thumbs/Claude_Monet_Water_Lilies_thumb.jpg and b/data/images/paintings/thumbs/Claude_Monet_Water_Lilies_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Diego_Vel_zquez_Portrait_du_Conde-Duque_de_Olivares_thumb.jpg b/data/images/paintings/thumbs/Diego_Vel_zquez_Portrait_du_Conde-Duque_de_Olivares_thumb.jpg index 2987329..c1517c6 100644 Binary files a/data/images/paintings/thumbs/Diego_Vel_zquez_Portrait_du_Conde-Duque_de_Olivares_thumb.jpg and b/data/images/paintings/thumbs/Diego_Vel_zquez_Portrait_du_Conde-Duque_de_Olivares_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Diego_Vel_zquez_Portrait_of_Philip_IV__of_Spain_thumb.jpg b/data/images/paintings/thumbs/Diego_Vel_zquez_Portrait_of_Philip_IV__of_Spain_thumb.jpg index b464fec..0de9b13 100644 Binary files a/data/images/paintings/thumbs/Diego_Vel_zquez_Portrait_of_Philip_IV__of_Spain_thumb.jpg and b/data/images/paintings/thumbs/Diego_Vel_zquez_Portrait_of_Philip_IV__of_Spain_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Diego_Vel_zquez_Queen_Mariana_of_Spain__1634_1696__thumb.jpg b/data/images/paintings/thumbs/Diego_Vel_zquez_Queen_Mariana_of_Spain__1634_1696__thumb.jpg index 2987329..3db1569 100644 Binary files a/data/images/paintings/thumbs/Diego_Vel_zquez_Queen_Mariana_of_Spain__1634_1696__thumb.jpg and b/data/images/paintings/thumbs/Diego_Vel_zquez_Queen_Mariana_of_Spain__1634_1696__thumb.jpg differ diff --git a/data/images/paintings/thumbs/Diego_Vel_zquez_Still_Life_thumb.jpg b/data/images/paintings/thumbs/Diego_Vel_zquez_Still_Life_thumb.jpg index 2e099ea..035f903 100644 Binary files a/data/images/paintings/thumbs/Diego_Vel_zquez_Still_Life_thumb.jpg and b/data/images/paintings/thumbs/Diego_Vel_zquez_Still_Life_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Diego_Vel_zquez_The_Buffoon__Pablo_de_Valladolid_thumb.jpg b/data/images/paintings/thumbs/Diego_Vel_zquez_The_Buffoon__Pablo_de_Valladolid_thumb.jpg index 2987329..e3ad24a 100644 Binary files a/data/images/paintings/thumbs/Diego_Vel_zquez_The_Buffoon__Pablo_de_Valladolid_thumb.jpg and b/data/images/paintings/thumbs/Diego_Vel_zquez_The_Buffoon__Pablo_de_Valladolid_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Diego_Vel_zquez_The_Dwarf_Francisco_Lezcano_thumb.jpg b/data/images/paintings/thumbs/Diego_Vel_zquez_The_Dwarf_Francisco_Lezcano_thumb.jpg index b464fec..e0034a0 100644 Binary files a/data/images/paintings/thumbs/Diego_Vel_zquez_The_Dwarf_Francisco_Lezcano_thumb.jpg and b/data/images/paintings/thumbs/Diego_Vel_zquez_The_Dwarf_Francisco_Lezcano_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Domenico_Ghirlandaio_Virgin_and_Child_thumb.jpg b/data/images/paintings/thumbs/Domenico_Ghirlandaio_Virgin_and_Child_thumb.jpg index 6c757d5..7d4bf42 100644 Binary files a/data/images/paintings/thumbs/Domenico_Ghirlandaio_Virgin_and_Child_thumb.jpg and b/data/images/paintings/thumbs/Domenico_Ghirlandaio_Virgin_and_Child_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Edgar_Degas_L_Absinthe_thumb.jpg b/data/images/paintings/thumbs/Edgar_Degas_L_Absinthe_thumb.jpg index 44bc808..94ccfaa 100644 Binary files a/data/images/paintings/thumbs/Edgar_Degas_L_Absinthe_thumb.jpg and b/data/images/paintings/thumbs/Edgar_Degas_L_Absinthe_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Edgar_Degas_The_Ballet_Class_thumb.jpg b/data/images/paintings/thumbs/Edgar_Degas_The_Ballet_Class_thumb.jpg index 74e15ce..6a38ab9 100644 Binary files a/data/images/paintings/thumbs/Edgar_Degas_The_Ballet_Class_thumb.jpg and b/data/images/paintings/thumbs/Edgar_Degas_The_Ballet_Class_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Edvard_Munch_The_Scream_thumb.jpg b/data/images/paintings/thumbs/Edvard_Munch_The_Scream_thumb.jpg index 3427010..5de70c5 100644 Binary files a/data/images/paintings/thumbs/Edvard_Munch_The_Scream_thumb.jpg and b/data/images/paintings/thumbs/Edvard_Munch_The_Scream_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Francis_Picabia_Amorous_Parade_thumb.jpg b/data/images/paintings/thumbs/Francis_Picabia_Amorous_Parade_thumb.jpg index 9f86b7f..fc607f0 100644 Binary files a/data/images/paintings/thumbs/Francis_Picabia_Amorous_Parade_thumb.jpg and b/data/images/paintings/thumbs/Francis_Picabia_Amorous_Parade_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Francis_Picabia_Transparence_thumb.jpg b/data/images/paintings/thumbs/Francis_Picabia_Transparence_thumb.jpg index f32ba7b..92fb1f6 100644 Binary files a/data/images/paintings/thumbs/Francis_Picabia_Transparence_thumb.jpg and b/data/images/paintings/thumbs/Francis_Picabia_Transparence_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Georges_Braque_Violin_and_Candlestick_thumb.jpg b/data/images/paintings/thumbs/Georges_Braque_Violin_and_Candlestick_thumb.jpg index 2aaea55..14cdc3e 100644 Binary files a/data/images/paintings/thumbs/Georges_Braque_Violin_and_Candlestick_thumb.jpg and b/data/images/paintings/thumbs/Georges_Braque_Violin_and_Candlestick_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Georges_Seurat_A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte_thumb.jpg b/data/images/paintings/thumbs/Georges_Seurat_A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte_thumb.jpg index 621fd98..bb29d23 100644 Binary files a/data/images/paintings/thumbs/Georges_Seurat_A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte_thumb.jpg and b/data/images/paintings/thumbs/Georges_Seurat_A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Giorgio_de_Chirico_The_Disquieting_Muses_thumb.jpg b/data/images/paintings/thumbs/Giorgio_de_Chirico_The_Disquieting_Muses_thumb.jpg index 3a52a0e..d42ce31 100644 Binary files a/data/images/paintings/thumbs/Giorgio_de_Chirico_The_Disquieting_Muses_thumb.jpg and b/data/images/paintings/thumbs/Giorgio_de_Chirico_The_Disquieting_Muses_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Giotto_St_Francis_before_the_Sultan_thumb.jpg b/data/images/paintings/thumbs/Giotto_St_Francis_before_the_Sultan_thumb.jpg index 3056b35..94fca3c 100644 Binary files a/data/images/paintings/thumbs/Giotto_St_Francis_before_the_Sultan_thumb.jpg and b/data/images/paintings/thumbs/Giotto_St_Francis_before_the_Sultan_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Gustav_Klimt_The_Kiss_thumb.jpg b/data/images/paintings/thumbs/Gustav_Klimt_The_Kiss_thumb.jpg index 1657106..7179502 100644 Binary files a/data/images/paintings/thumbs/Gustav_Klimt_The_Kiss_thumb.jpg and b/data/images/paintings/thumbs/Gustav_Klimt_The_Kiss_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Hannah_H_ch_Cut_with_the_Dada_Kitchen_Knife_thumb.jpg b/data/images/paintings/thumbs/Hannah_H_ch_Cut_with_the_Dada_Kitchen_Knife_thumb.jpg index 77c6c62..ef81cbf 100644 Binary files a/data/images/paintings/thumbs/Hannah_H_ch_Cut_with_the_Dada_Kitchen_Knife_thumb.jpg and b/data/images/paintings/thumbs/Hannah_H_ch_Cut_with_the_Dada_Kitchen_Knife_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Henri_Matisse_The_Dance_thumb.jpg b/data/images/paintings/thumbs/Henri_Matisse_The_Dance_thumb.jpg index f62060a..a304f20 100644 Binary files a/data/images/paintings/thumbs/Henri_Matisse_The_Dance_thumb.jpg and b/data/images/paintings/thumbs/Henri_Matisse_The_Dance_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Henri_Matisse_The_Red_Room_thumb.jpg b/data/images/paintings/thumbs/Henri_Matisse_The_Red_Room_thumb.jpg index f62060a..73d24d6 100644 Binary files a/data/images/paintings/thumbs/Henri_Matisse_The_Red_Room_thumb.jpg and b/data/images/paintings/thumbs/Henri_Matisse_The_Red_Room_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Jackson_Pollock_Autumn_Rhythm_thumb.jpg b/data/images/paintings/thumbs/Jackson_Pollock_Autumn_Rhythm_thumb.jpg index 44e9299..e090e1f 100644 Binary files a/data/images/paintings/thumbs/Jackson_Pollock_Autumn_Rhythm_thumb.jpg and b/data/images/paintings/thumbs/Jackson_Pollock_Autumn_Rhythm_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Jackson_Pollock_Number_1__1950__Lavender_Mist__thumb.jpg b/data/images/paintings/thumbs/Jackson_Pollock_Number_1__1950__Lavender_Mist__thumb.jpg index d33f25b..8835da2 100644 Binary files a/data/images/paintings/thumbs/Jackson_Pollock_Number_1__1950__Lavender_Mist__thumb.jpg and b/data/images/paintings/thumbs/Jackson_Pollock_Number_1__1950__Lavender_Mist__thumb.jpg differ diff --git a/data/images/paintings/thumbs/Jacques-Louis_David_The_Oath_of_the_Horatii_thumb.jpg b/data/images/paintings/thumbs/Jacques-Louis_David_The_Oath_of_the_Horatii_thumb.jpg index a280ab9..8c8effe 100644 Binary files a/data/images/paintings/thumbs/Jacques-Louis_David_The_Oath_of_the_Horatii_thumb.jpg and b/data/images/paintings/thumbs/Jacques-Louis_David_The_Oath_of_the_Horatii_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Jean-Baptiste-Camille_Corot_Forest_of_Fontainebleau_thumb.jpg b/data/images/paintings/thumbs/Jean-Baptiste-Camille_Corot_Forest_of_Fontainebleau_thumb.jpg index 4ad5223..af54d58 100644 Binary files a/data/images/paintings/thumbs/Jean-Baptiste-Camille_Corot_Forest_of_Fontainebleau_thumb.jpg and b/data/images/paintings/thumbs/Jean-Baptiste-Camille_Corot_Forest_of_Fontainebleau_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Kazimir_Malevich_Black_Square_thumb.jpg b/data/images/paintings/thumbs/Kazimir_Malevich_Black_Square_thumb.jpg index 6773d3c..083d635 100644 Binary files a/data/images/paintings/thumbs/Kazimir_Malevich_Black_Square_thumb.jpg and b/data/images/paintings/thumbs/Kazimir_Malevich_Black_Square_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Marcel_Duchamp_Bicycle_Wheel_thumb.jpg b/data/images/paintings/thumbs/Marcel_Duchamp_Bicycle_Wheel_thumb.jpg index b658bbf..c457142 100644 Binary files a/data/images/paintings/thumbs/Marcel_Duchamp_Bicycle_Wheel_thumb.jpg and b/data/images/paintings/thumbs/Marcel_Duchamp_Bicycle_Wheel_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Marcel_Duchamp_Fountain_thumb.jpg b/data/images/paintings/thumbs/Marcel_Duchamp_Fountain_thumb.jpg index b658bbf..f198088 100644 Binary files a/data/images/paintings/thumbs/Marcel_Duchamp_Fountain_thumb.jpg and b/data/images/paintings/thumbs/Marcel_Duchamp_Fountain_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Marcel_Duchamp_L_H_O_O_Q__thumb.jpg b/data/images/paintings/thumbs/Marcel_Duchamp_L_H_O_O_Q__thumb.jpg index b658bbf..4ecf11d 100644 Binary files a/data/images/paintings/thumbs/Marcel_Duchamp_L_H_O_O_Q__thumb.jpg and b/data/images/paintings/thumbs/Marcel_Duchamp_L_H_O_O_Q__thumb.jpg differ diff --git a/data/images/paintings/thumbs/Marcel_Duchamp_Nude_Descending_a_Staircase__No__2_thumb.jpg b/data/images/paintings/thumbs/Marcel_Duchamp_Nude_Descending_a_Staircase__No__2_thumb.jpg index 1d76d8a..8314ecd 100644 Binary files a/data/images/paintings/thumbs/Marcel_Duchamp_Nude_Descending_a_Staircase__No__2_thumb.jpg and b/data/images/paintings/thumbs/Marcel_Duchamp_Nude_Descending_a_Staircase__No__2_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Marcel_Duchamp_The_Bride_Stripped_Bare_by_Her_Bachelors__Even_thumb.jpg b/data/images/paintings/thumbs/Marcel_Duchamp_The_Bride_Stripped_Bare_by_Her_Bachelors__Even_thumb.jpg index b658bbf..e968295 100644 Binary files a/data/images/paintings/thumbs/Marcel_Duchamp_The_Bride_Stripped_Bare_by_Her_Bachelors__Even_thumb.jpg and b/data/images/paintings/thumbs/Marcel_Duchamp_The_Bride_Stripped_Bare_by_Her_Bachelors__Even_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Mark_Rothko_No__61__Rust_and_Blue__thumb.jpg b/data/images/paintings/thumbs/Mark_Rothko_No__61__Rust_and_Blue__thumb.jpg new file mode 100644 index 0000000..df69774 Binary files /dev/null and b/data/images/paintings/thumbs/Mark_Rothko_No__61__Rust_and_Blue__thumb.jpg differ diff --git a/data/images/paintings/thumbs/Max_Ernst_Europe_After_the_Rain_II_thumb.jpg b/data/images/paintings/thumbs/Max_Ernst_Europe_After_the_Rain_II_thumb.jpg index 21ae76a..8a1462f 100644 Binary files a/data/images/paintings/thumbs/Max_Ernst_Europe_After_the_Rain_II_thumb.jpg and b/data/images/paintings/thumbs/Max_Ernst_Europe_After_the_Rain_II_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Max_Ernst_The_Elephant_Celebes_thumb.jpg b/data/images/paintings/thumbs/Max_Ernst_The_Elephant_Celebes_thumb.jpg index 21ae76a..dbba994 100644 Binary files a/data/images/paintings/thumbs/Max_Ernst_The_Elephant_Celebes_thumb.jpg and b/data/images/paintings/thumbs/Max_Ernst_The_Elephant_Celebes_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Nicolas_Poussin_Rape_of_the_Sabine_Women_thumb.jpg b/data/images/paintings/thumbs/Nicolas_Poussin_Rape_of_the_Sabine_Women_thumb.jpg index 96f327b..d00ce43 100644 Binary files a/data/images/paintings/thumbs/Nicolas_Poussin_Rape_of_the_Sabine_Women_thumb.jpg and b/data/images/paintings/thumbs/Nicolas_Poussin_Rape_of_the_Sabine_Women_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Parmigianino_Antea_thumb.jpg b/data/images/paintings/thumbs/Parmigianino_Antea_thumb.jpg index 911f49b..812c51a 100644 Binary files a/data/images/paintings/thumbs/Parmigianino_Antea_thumb.jpg and b/data/images/paintings/thumbs/Parmigianino_Antea_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Parmigianino_Cupid_Carving_his_Bow_thumb.jpg b/data/images/paintings/thumbs/Parmigianino_Cupid_Carving_his_Bow_thumb.jpg index 617c048..184fc87 100644 Binary files a/data/images/paintings/thumbs/Parmigianino_Cupid_Carving_his_Bow_thumb.jpg and b/data/images/paintings/thumbs/Parmigianino_Cupid_Carving_his_Bow_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Parmigianino_Self-Portrait_in_a_Convex_Mirror_thumb.jpg b/data/images/paintings/thumbs/Parmigianino_Self-Portrait_in_a_Convex_Mirror_thumb.jpg index a5da2b3..f0821e5 100644 Binary files a/data/images/paintings/thumbs/Parmigianino_Self-Portrait_in_a_Convex_Mirror_thumb.jpg and b/data/images/paintings/thumbs/Parmigianino_Self-Portrait_in_a_Convex_Mirror_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Parmigianino_Turkish_Slave_thumb.jpg b/data/images/paintings/thumbs/Parmigianino_Turkish_Slave_thumb.jpg index 617c048..d9dfcba 100644 Binary files a/data/images/paintings/thumbs/Parmigianino_Turkish_Slave_thumb.jpg and b/data/images/paintings/thumbs/Parmigianino_Turkish_Slave_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Paul_C_zanne_The_Bathers_thumb.jpg b/data/images/paintings/thumbs/Paul_C_zanne_The_Bathers_thumb.jpg index d072381..22ffd12 100644 Binary files a/data/images/paintings/thumbs/Paul_C_zanne_The_Bathers_thumb.jpg and b/data/images/paintings/thumbs/Paul_C_zanne_The_Bathers_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Paul_C_zanne_The_Card_Players_thumb.jpg b/data/images/paintings/thumbs/Paul_C_zanne_The_Card_Players_thumb.jpg index 6773d3c..02112b3 100644 Binary files a/data/images/paintings/thumbs/Paul_C_zanne_The_Card_Players_thumb.jpg and b/data/images/paintings/thumbs/Paul_C_zanne_The_Card_Players_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Paul_Gauguin_Where_Do_We_Come_From__thumb.jpg b/data/images/paintings/thumbs/Paul_Gauguin_Where_Do_We_Come_From__thumb.jpg index 7e29376..77f38cc 100644 Binary files a/data/images/paintings/thumbs/Paul_Gauguin_Where_Do_We_Come_From__thumb.jpg and b/data/images/paintings/thumbs/Paul_Gauguin_Where_Do_We_Come_From__thumb.jpg differ diff --git a/data/images/paintings/thumbs/Pierre-Auguste_Renoir_Dance_at_Le_Moulin_de_la_Galette_thumb.jpg b/data/images/paintings/thumbs/Pierre-Auguste_Renoir_Dance_at_Le_Moulin_de_la_Galette_thumb.jpg index 73113d5..022746c 100644 Binary files a/data/images/paintings/thumbs/Pierre-Auguste_Renoir_Dance_at_Le_Moulin_de_la_Galette_thumb.jpg and b/data/images/paintings/thumbs/Pierre-Auguste_Renoir_Dance_at_Le_Moulin_de_la_Galette_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Ren__Magritte_The_Treachery_of_Images_thumb.jpg b/data/images/paintings/thumbs/Ren__Magritte_The_Treachery_of_Images_thumb.jpg new file mode 100644 index 0000000..0cb50f8 Binary files /dev/null and b/data/images/paintings/thumbs/Ren__Magritte_The_Treachery_of_Images_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Sandro_Botticelli_Saint_John_the_Baptist_thumb.jpg b/data/images/paintings/thumbs/Sandro_Botticelli_Saint_John_the_Baptist_thumb.jpg index f84061e..46f4cdd 100644 Binary files a/data/images/paintings/thumbs/Sandro_Botticelli_Saint_John_the_Baptist_thumb.jpg and b/data/images/paintings/thumbs/Sandro_Botticelli_Saint_John_the_Baptist_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Sandro_Botticelli_Two_angels_thumb.jpg b/data/images/paintings/thumbs/Sandro_Botticelli_Two_angels_thumb.jpg index d58f77c..d29aba8 100644 Binary files a/data/images/paintings/thumbs/Sandro_Botticelli_Two_angels_thumb.jpg and b/data/images/paintings/thumbs/Sandro_Botticelli_Two_angels_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Adoration_of_the_Christ_Child_thumb.jpg b/data/images/paintings/thumbs/Titian_Adoration_of_the_Christ_Child_thumb.jpg index 638f927..664ff7d 100644 Binary files a/data/images/paintings/thumbs/Titian_Adoration_of_the_Christ_Child_thumb.jpg and b/data/images/paintings/thumbs/Titian_Adoration_of_the_Christ_Child_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Bache_Madonna_thumb.jpg b/data/images/paintings/thumbs/Titian_Bache_Madonna_thumb.jpg index d205465..553922d 100644 Binary files a/data/images/paintings/thumbs/Titian_Bache_Madonna_thumb.jpg and b/data/images/paintings/thumbs/Titian_Bache_Madonna_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Christ_Carrying_the_Cross_thumb.jpg b/data/images/paintings/thumbs/Titian_Christ_Carrying_the_Cross_thumb.jpg index 5bce782..2d71d61 100644 Binary files a/data/images/paintings/thumbs/Titian_Christ_Carrying_the_Cross_thumb.jpg and b/data/images/paintings/thumbs/Titian_Christ_Carrying_the_Cross_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Christ_and_the_Woman_Taken_in_Adultery_thumb.jpg b/data/images/paintings/thumbs/Titian_Christ_and_the_Woman_Taken_in_Adultery_thumb.jpg index a3279e4..eb33f83 100644 Binary files a/data/images/paintings/thumbs/Titian_Christ_and_the_Woman_Taken_in_Adultery_thumb.jpg and b/data/images/paintings/thumbs/Titian_Christ_and_the_Woman_Taken_in_Adultery_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Endymion_and_his_Flock_thumb.jpg b/data/images/paintings/thumbs/Titian_Endymion_and_his_Flock_thumb.jpg index d205465..90ab9f6 100644 Binary files a/data/images/paintings/thumbs/Titian_Endymion_and_his_Flock_thumb.jpg and b/data/images/paintings/thumbs/Titian_Endymion_and_his_Flock_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Flight_into_Egypt_thumb.jpg b/data/images/paintings/thumbs/Titian_Flight_into_Egypt_thumb.jpg index c72f8eb..c7b8bf2 100644 Binary files a/data/images/paintings/thumbs/Titian_Flight_into_Egypt_thumb.jpg and b/data/images/paintings/thumbs/Titian_Flight_into_Egypt_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Giustiniani_Portrait_thumb.jpg b/data/images/paintings/thumbs/Titian_Giustiniani_Portrait_thumb.jpg index d205465..67ca3e1 100644 Binary files a/data/images/paintings/thumbs/Titian_Giustiniani_Portrait_thumb.jpg and b/data/images/paintings/thumbs/Titian_Giustiniani_Portrait_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Head_of_a_Man_thumb.jpg b/data/images/paintings/thumbs/Titian_Head_of_a_Man_thumb.jpg index 266a6a7..f47d894 100644 Binary files a/data/images/paintings/thumbs/Titian_Head_of_a_Man_thumb.jpg and b/data/images/paintings/thumbs/Titian_Head_of_a_Man_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Jacopo_Pesaro__Bishop_of_Paphos__being_presented_by_Pope_Alexander_VI_to_Saint_Peter_thumb.jpg b/data/images/paintings/thumbs/Titian_Jacopo_Pesaro__Bishop_of_Paphos__being_presented_by_Pope_Alexander_VI_to_Saint_Peter_thumb.jpg index 7d78cb0..e7de455 100644 Binary files a/data/images/paintings/thumbs/Titian_Jacopo_Pesaro__Bishop_of_Paphos__being_presented_by_Pope_Alexander_VI_to_Saint_Peter_thumb.jpg and b/data/images/paintings/thumbs/Titian_Jacopo_Pesaro__Bishop_of_Paphos__being_presented_by_Pope_Alexander_VI_to_Saint_Peter_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Justice_thumb.jpg b/data/images/paintings/thumbs/Titian_Justice_thumb.jpg index d205465..16f5b7f 100644 Binary files a/data/images/paintings/thumbs/Titian_Justice_thumb.jpg and b/data/images/paintings/thumbs/Titian_Justice_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Lochis_Madonna_thumb.jpg b/data/images/paintings/thumbs/Titian_Lochis_Madonna_thumb.jpg index d205465..aa102ef 100644 Binary files a/data/images/paintings/thumbs/Titian_Lochis_Madonna_thumb.jpg and b/data/images/paintings/thumbs/Titian_Lochis_Madonna_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Member_of_the_Calza_thumb.jpg b/data/images/paintings/thumbs/Titian_Member_of_the_Calza_thumb.jpg deleted file mode 100644 index d205465..0000000 Binary files a/data/images/paintings/thumbs/Titian_Member_of_the_Calza_thumb.jpg and /dev/null differ diff --git a/data/images/paintings/thumbs/Titian_Orpheus_and_Eurydice_thumb.jpg b/data/images/paintings/thumbs/Titian_Orpheus_and_Eurydice_thumb.jpg index dec3245..9a58486 100644 Binary files a/data/images/paintings/thumbs/Titian_Orpheus_and_Eurydice_thumb.jpg and b/data/images/paintings/thumbs/Titian_Orpheus_and_Eurydice_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_Portrait_of_a_Man_thumb.jpg b/data/images/paintings/thumbs/Titian_Portrait_of_a_Man_thumb.jpg index 719af23..eee6066 100644 Binary files a/data/images/paintings/thumbs/Titian_Portrait_of_a_Man_thumb.jpg and b/data/images/paintings/thumbs/Titian_Portrait_of_a_Man_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_The_Birth_of_Adonis_thumb.jpg b/data/images/paintings/thumbs/Titian_The_Birth_of_Adonis_thumb.jpg index 7d78cb0..1e21d42 100644 Binary files a/data/images/paintings/thumbs/Titian_The_Birth_of_Adonis_thumb.jpg and b/data/images/paintings/thumbs/Titian_The_Birth_of_Adonis_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Titian_The_Circumcision_of_Christ_thumb.jpg b/data/images/paintings/thumbs/Titian_The_Circumcision_of_Christ_thumb.jpg index 7f4b089..c8533f1 100644 Binary files a/data/images/paintings/thumbs/Titian_The_Circumcision_of_Christ_thumb.jpg and b/data/images/paintings/thumbs/Titian_The_Circumcision_of_Christ_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Vincent_van_Gogh_Sunflowers_thumb.jpg b/data/images/paintings/thumbs/Vincent_van_Gogh_Sunflowers_thumb.jpg index 9afe95a..3109907 100644 Binary files a/data/images/paintings/thumbs/Vincent_van_Gogh_Sunflowers_thumb.jpg and b/data/images/paintings/thumbs/Vincent_van_Gogh_Sunflowers_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Vincent_van_Gogh_The_Starry_Night_thumb.jpg b/data/images/paintings/thumbs/Vincent_van_Gogh_The_Starry_Night_thumb.jpg index 9afe95a..b8652ab 100644 Binary files a/data/images/paintings/thumbs/Vincent_van_Gogh_The_Starry_Night_thumb.jpg and b/data/images/paintings/thumbs/Vincent_van_Gogh_The_Starry_Night_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Wassily_Kandinsky_Composition_VII_thumb.jpg b/data/images/paintings/thumbs/Wassily_Kandinsky_Composition_VII_thumb.jpg index a3b98ce..e065e75 100644 Binary files a/data/images/paintings/thumbs/Wassily_Kandinsky_Composition_VII_thumb.jpg and b/data/images/paintings/thumbs/Wassily_Kandinsky_Composition_VII_thumb.jpg differ diff --git a/data/images/paintings/thumbs/Willem_de_Kooning_Woman_I_thumb.jpg b/data/images/paintings/thumbs/Willem_de_Kooning_Woman_I_thumb.jpg index 227c670..ea7618b 100644 Binary files a/data/images/paintings/thumbs/Willem_de_Kooning_Woman_I_thumb.jpg and b/data/images/paintings/thumbs/Willem_de_Kooning_Woman_I_thumb.jpg differ diff --git a/data/images/paintings/thumbs/_douard_Manet_A_Bar_at_the_Folies-Berg_re_thumb.jpg b/data/images/paintings/thumbs/_douard_Manet_A_Bar_at_the_Folies-Berg_re_thumb.jpg index 5a0c24b..6d79d43 100644 Binary files a/data/images/paintings/thumbs/_douard_Manet_A_Bar_at_the_Folies-Berg_re_thumb.jpg and b/data/images/paintings/thumbs/_douard_Manet_A_Bar_at_the_Folies-Berg_re_thumb.jpg differ diff --git a/data/images/portraits/Titian.jpg b/data/images/portraits/Titian.jpg index 942a1e1..417eed2 100644 Binary files a/data/images/portraits/Titian.jpg and b/data/images/portraits/Titian.jpg differ diff --git a/deploy/gallery.service b/deploy/gallery.service new file mode 100644 index 0000000..05618f8 --- /dev/null +++ b/deploy/gallery.service @@ -0,0 +1,22 @@ +# systemd unit — adjust User, WorkingDirectory, and EnvironmentFile paths. +# Install: +# sudo cp deploy/gallery.service /etc/systemd/system/gallery.service +# sudo systemctl daemon-reload +# sudo systemctl enable --now gallery + +[Unit] +Description=Art Gallery (Node.js) +After=network.target + +[Service] +Type=simple +User=gallery +WorkingDirectory=/opt/Art-gallery +Environment=NODE_ENV=production +EnvironmentFile=/opt/Art-gallery/.env +ExecStart=/usr/bin/node server/index.js +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/deploy/nginx-gallery.conf b/deploy/nginx-gallery.conf new file mode 100644 index 0000000..47000da --- /dev/null +++ b/deploy/nginx-gallery.conf @@ -0,0 +1,19 @@ +# Reverse proxy for gallery.mysuperlab.netcraze.pro → Node on port 3520. +# Install: copy to /etc/nginx/sites-available/gallery, symlink sites-enabled, nginx -t && systemctl reload nginx + +server { + listen 80; + server_name gallery.mysuperlab.netcraze.pro; + + client_max_body_size 20M; + + location / { + proxy_pass http://127.0.0.1:3520; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 120s; + } +} diff --git a/package.json b/package.json index ee865db..4a17638 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,11 @@ "migrate:painting-annotations": "node scripts/migrate-painting-annotations.js", "update-painting-annotations": "node scripts/update-painting-annotations.js", "find-duplicates": "node scripts/find-duplicate-paintings.js", + "audit-influence-duplicates": "node scripts/audit-influence-duplicates.js", "discover-influences": "node scripts/update-influences.js --discover-only", + "build": "npm run build --prefix client", + "start": "node server/index.js", + "start:prod": "npm run build && node server/index.js", "server": "node server/index.js", "dev:server": "nodemon server/index.js", "dev:client": "npm run dev --prefix client", diff --git a/scripts/audit-influence-duplicates.js b/scripts/audit-influence-duplicates.js new file mode 100644 index 0000000..500fb9d --- /dev/null +++ b/scripts/audit-influence-duplicates.js @@ -0,0 +1,125 @@ +require('dotenv').config(); +const pool = require('../server/db'); + +async function main() { + const overlap = await pool.query(` + WITH legacy AS ( + SELECT influenced_by_painting_id AS src, painting_id AS dst + FROM painting_influences + ), + sources AS ( + SELECT source_painting_id AS src, painting_id AS dst + FROM painting_influence_sources + WHERE source_type = 'painting' AND source_painting_id IS NOT NULL + ) + SELECT COUNT(*)::int AS doubled_edges + FROM legacy l + INNER JOIN sources s ON l.src = s.src AND l.dst = s.dst + `); + console.log('Doubled painting edges (legacy + sources):', overlap.rows[0].doubled_edges); + + const dupSources = await pool.query(` + SELECT painting_id, source_type, + COALESCE(source_painting_id, 0) AS sp, + COALESCE(source_artist_id, 0) AS sa, + COALESCE(source_movement_id, 0) AS sm, + COUNT(*)::int AS cnt + FROM painting_influence_sources + GROUP BY painting_id, source_type, sp, sa, sm + HAVING COUNT(*) > 1 + ORDER BY cnt DESC + LIMIT 20 + `); + console.log('Duplicate rows in painting_influence_sources:', dupSources.rows.length); + if (dupSources.rows.length) console.table(dupSources.rows); + + const dupLegacy = await pool.query(` + SELECT painting_id, influenced_by_painting_id, COUNT(*)::int AS cnt + FROM painting_influences + GROUP BY painting_id, influenced_by_painting_id + HAVING COUNT(*) > 1 + `); + console.log('Duplicate rows in painting_influences:', dupLegacy.rows.length); + + const sample = await pool.query(` + SELECT pi.influenced_by_painting_id AS src_id, pi.painting_id AS dst_id, + p_src.title AS src_title, a_src.name AS src_artist, + p_dst.title AS dst_title, a_dst.name AS dst_artist + FROM painting_influences pi + JOIN paintings p_dst ON pi.painting_id = p_dst.id + JOIN artists a_dst ON p_dst.artist_id = a_dst.id + JOIN paintings p_src ON pi.influenced_by_painting_id = p_src.id + JOIN artists a_src ON p_src.artist_id = a_src.id + WHERE EXISTS ( + SELECT 1 FROM painting_influence_sources pis + WHERE pis.painting_id = pi.painting_id + AND pis.source_painting_id = pi.influenced_by_painting_id + AND pis.source_type = 'painting' + ) + ORDER BY a_dst.name, p_dst.title + LIMIT 15 + `); + console.log('\nSample doubled links (shown twice in API "influenced" query):'); + for (const r of sample.rows) { + console.log(` ${r.src_artist} — ${r.src_title} → ${r.dst_artist} — ${r.dst_title}`); + } + + const apiDupCount = await pool.query(` + WITH influenced AS ( + SELECT pi.influenced_by_painting_id AS painting_id, pi.painting_id AS linked_id + FROM painting_influences pi + UNION ALL + SELECT pis.source_painting_id, pis.painting_id + FROM painting_influence_sources pis + WHERE pis.source_painting_id IS NOT NULL AND pis.source_type = 'painting' + ) + SELECT painting_id, linked_id, COUNT(*)::int AS cnt + FROM influenced + GROUP BY painting_id, linked_id + HAVING COUNT(*) > 1 + ORDER BY cnt DESC + LIMIT 20 + `); + console.log('\nAPI "influenced" duplicates (UNION ALL):', apiDupCount.rows.length); + if (apiDupCount.rows.length) console.table(apiDupCount.rows); + + const legacyOnly = await pool.query(` + SELECT COUNT(*)::int AS n + FROM painting_influences pi + WHERE NOT EXISTS ( + SELECT 1 FROM painting_influence_sources pis + WHERE pis.painting_id = pi.painting_id + AND pis.source_painting_id = pi.influenced_by_painting_id + AND pis.source_type = 'painting' + ) + `); + console.log('\nLegacy-only edges (not in sources):', legacyOnly.rows[0].n); + + const sourcesOnly = await pool.query(` + SELECT COUNT(*)::int AS n + FROM painting_influence_sources pis + WHERE pis.source_type = 'painting' AND pis.source_painting_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM painting_influences pi + WHERE pi.painting_id = pis.painting_id + AND pi.influenced_by_painting_id = pis.source_painting_id + ) + `); + console.log('Sources-only painting edges (not in legacy):', sourcesOnly.rows[0].n); + + const fixedApiDup = await pool.query(` + SELECT source_painting_id AS painting_id, painting_id AS linked_id, COUNT(*)::int AS cnt + FROM painting_influence_sources + WHERE source_type = 'painting' AND source_painting_id IS NOT NULL + GROUP BY source_painting_id, painting_id + HAVING COUNT(*) > 1 + `); + console.log('\nDuplicate "influenced" after fix (should be 0):', fixedApiDup.rows.length); + + await pool.end(); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/server/index.js b/server/index.js index c9a3653..c0ab856 100644 --- a/server/index.js +++ b/server/index.js @@ -9,7 +9,13 @@ const { ensurePaintingImages, preloadArtistImagesLocal, replacePaintingImageFrom const { searchGoogleImagesFirst, searchArtistPortraitFirst, searchPaintingImagesMany, searchArtistPortraitMany, fetchImageBuffer, friendlyImageFetchError, pickExt } = require('../scripts/image-fetcher'); const app = express(); -const PORT = process.env.PORT || 3001; +const HOST = process.env.HOST || '0.0.0.0'; +const PORT = Number(process.env.PORT) || 3001; +const PUBLIC_URL = process.env.PUBLIC_URL || ''; + +if (process.env.TRUST_PROXY === '1' || process.env.TRUST_PROXY === 'true') { + app.set('trust proxy', 1); +} app.use(cors()); app.use(express.json({ limit: '20mb' })); @@ -19,9 +25,6 @@ const INFLUENCE_LINKS_EXISTS = ` EXISTS ( SELECT 1 FROM painting_influence_sources pis WHERE pis.painting_id = p.id OR pis.source_painting_id = p.id - ) OR EXISTS ( - SELECT 1 FROM painting_influences pi - WHERE pi.painting_id = p.id OR pi.influenced_by_painting_id = p.id )`; const INFLUENCED_BY_SQL = ` @@ -59,6 +62,28 @@ const INFLUENCED_BY_SQL = ` ORDER BY CASE pis.source_type WHEN 'painting' THEN 1 WHEN 'artist' THEN 2 ELSE 3 END, COALESCE(p.year, pis.period_start_year) NULLS LAST`; + +const INFLUENCED_SQL = ` + SELECT + pis.notes, + pis.source, + pis.aspects, + pis.quote, + pis.source_author, + pis.source_url, + 'painting' AS source_type, + p.id, + p.title, + p.year, + p.image_path, + a.name AS artist_name, + a.id AS artist_id + FROM painting_influence_sources pis + JOIN paintings p ON pis.painting_id = p.id + JOIN artists a ON p.artist_id = a.id + WHERE pis.source_painting_id = $1 AND pis.source_type = 'painting' + ORDER BY p.year NULLS LAST, p.title`; + app.get('/api/timeline', async (req, res) => { try { const { start, end } = req.query; @@ -221,16 +246,7 @@ app.get('/api/artists/:id/navigation', async (req, res) => { LEFT JOIN paintings p_pred ON pis.source_painting_id = p_pred.id LEFT JOIN artists a ON a.id = COALESCE(p_pred.artist_id, pis.source_artist_id) LEFT JOIN art_movements m ON a.movement_id = m.id - WHERE p_work.artist_id = $1 AND a.id IS NOT NULL AND a.id <> $1 - UNION - SELECT DISTINCT a.id, a.name, a.birth_year, a.death_year, a.portrait_path, - m.id AS movement_id, m.name AS movement_name, m.color AS movement_color - FROM painting_influences pi - JOIN paintings p_work ON pi.painting_id = p_work.id - JOIN paintings p_pred ON pi.influenced_by_painting_id = p_pred.id - JOIN artists a ON p_pred.artist_id = a.id - LEFT JOIN art_movements m ON a.movement_id = m.id - WHERE p_work.artist_id = $1 AND a.id <> $1`; + WHERE p_work.artist_id = $1 AND a.id IS NOT NULL AND a.id <> $1`; const successorSql = ` SELECT DISTINCT a.id, a.name, a.birth_year, a.death_year, a.portrait_path, @@ -244,12 +260,11 @@ app.get('/api/artists/:id/navigation', async (req, res) => { UNION SELECT DISTINCT a.id, a.name, a.birth_year, a.death_year, a.portrait_path, m.id AS movement_id, m.name AS movement_name, m.color AS movement_color - FROM painting_influences pi - JOIN paintings p_src ON pi.influenced_by_painting_id = p_src.id - JOIN paintings p_work ON pi.painting_id = p_work.id + FROM painting_influence_sources pis + JOIN paintings p_work ON pis.painting_id = p_work.id JOIN artists a ON p_work.artist_id = a.id LEFT JOIN art_movements m ON a.movement_id = m.id - WHERE p_src.artist_id = $1 AND a.id <> $1`; + WHERE pis.source_artist_id = $1 AND a.id <> $1`; const [predecessors, successors] = await Promise.all([ pool.query( @@ -610,27 +625,7 @@ app.get('/api/paintings/:id', async (req, res) => { const [influencedBy, influenced, annotations] = await Promise.all([ pool.query(INFLUENCED_BY_SQL, [id]), - pool.query( - `SELECT * FROM ( - SELECT pi.notes, pi.source, pi.aspects, pi.quote, pi.source_author, pi.source_url, - 'painting' AS source_type, - p.id, p.title, p.year, p.image_path, a.name as artist_name, a.id as artist_id - FROM painting_influences pi - JOIN paintings p ON pi.painting_id = p.id - JOIN artists a ON p.artist_id = a.id - WHERE pi.influenced_by_painting_id = $1 - UNION ALL - SELECT pis.notes, pis.source, pis.aspects, pis.quote, pis.source_author, pis.source_url, - 'painting' AS source_type, - p.id, p.title, p.year, p.image_path, a.name as artist_name, a.id as artist_id - FROM painting_influence_sources pis - JOIN paintings p ON pis.painting_id = p.id - JOIN artists a ON p.artist_id = a.id - WHERE pis.source_painting_id = $1 AND pis.source_type = 'painting' - ) influenced_works - ORDER BY year NULLS LAST`, - [id] - ), + pool.query(INFLUENCED_SQL, [id]), pool.query( `SELECT id, label, body, category, pos_x, pos_y, source_author, source, source_url, sort_order, confidence FROM painting_annotations @@ -861,6 +856,7 @@ if (fs.existsSync(CLIENT_DIST)) { }); } -app.listen(PORT, () => { - console.log(`Gallery running on http://localhost:${PORT}`); +app.listen(PORT, HOST, () => { + console.log(`Gallery listening on http://${HOST}:${PORT}`); + if (PUBLIC_URL) console.log(`Public URL: ${PUBLIC_URL}`); });