[object Object]

← back to Wallco Ai

design page: admin tone-on-tone color-wheel buttons + /api/design/:id/recolor

e52478f4b79e8afc142fa4f7261d645c18bda20c · 2026-05-13 14:23:59 -0700 · SteveStudio2

Steve's 2026-05-13 ask: 'Add buttons to admin to create in color wheel
color. for tone on tone items.' Triggered by Rose Studio No.141 — pattern
right, color wrong.

Backend (admin-only, isAdmin gate):
- POST /api/design/:id/recolor { hue }
- 14 hues: red/orange/yellow/lime/green/teal/cyan/blue/indigo/violet/
  magenta/pink/neutral/charcoal
- Reads source PNG from disk, calls Gemini-2.5-flash-image-edit with a
  strict mono-hue tone-on-tone prompt, saves new PNG, INSERTs new row
  into spoon_all_designs (is_published=true), pushes onto in-memory
  DESIGNS so it appears without restart
- Cost-logged via cost-tracker
- Returns { ok, new_id, new_image_url, hue, elapsed_s }

Frontend (admin-only on /design/:id):
- 14 colored buttons, click each = generate that hue
- 'Spin all 14 hues (parallel)' button fires Promise.all
- Inline result thumbnails as each completes (click → opens new design)
- Status line shows per-hue progress

Cost: ~/bin/zsh.001 × 14 = ~/bin/zsh.014 per 'spin all' click.

Files touched

Diff

commit e52478f4b79e8afc142fa4f7261d645c18bda20c
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 14:23:59 2026 -0700

    design page: admin tone-on-tone color-wheel buttons + /api/design/:id/recolor
    
    Steve's 2026-05-13 ask: 'Add buttons to admin to create in color wheel
    color. for tone on tone items.' Triggered by Rose Studio No.141 — pattern
    right, color wrong.
    
    Backend (admin-only, isAdmin gate):
    - POST /api/design/:id/recolor { hue }
    - 14 hues: red/orange/yellow/lime/green/teal/cyan/blue/indigo/violet/
      magenta/pink/neutral/charcoal
    - Reads source PNG from disk, calls Gemini-2.5-flash-image-edit with a
      strict mono-hue tone-on-tone prompt, saves new PNG, INSERTs new row
      into spoon_all_designs (is_published=true), pushes onto in-memory
      DESIGNS so it appears without restart
    - Cost-logged via cost-tracker
    - Returns { ok, new_id, new_image_url, hue, elapsed_s }
    
    Frontend (admin-only on /design/:id):
    - 14 colored buttons, click each = generate that hue
    - 'Spin all 14 hues (parallel)' button fires Promise.all
    - Inline result thumbnails as each completes (click → opens new design)
    - Status line shows per-hue progress
    
    Cost: ~/bin/zsh.001 × 14 = ~/bin/zsh.014 per 'spin all' click.
---
 data/designs.json | 1012 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 server.js         |  195 +++++++++++
 2 files changed, 1207 insertions(+)

diff --git a/data/designs.json b/data/designs.json
index b61e702..c4c3dd8 100644
--- a/data/designs.json
+++ b/data/designs.json
@@ -83021,5 +83021,1017 @@
     "user_scale_good": null,
     "user_stars": null,
     "room_mockups": []
+  },
+  {
+    "id": 4003,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#232e63",
+    "saturation": 0.478,
+    "title": "Sapphire Reverie No.4003",
+    "handle": "wallco-4003",
+    "image_url": "/designs/img/1778706603118_931902937.png",
+    "filename": "1778706603118_931902937.png",
+    "generator": "wallco.ai",
+    "seed": 931902937,
+    "is_published": false,
+    "created_at": "2026-05-13T21:10:18.417978+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4004,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#4a2934",
+    "saturation": 0.287,
+    "title": "Rose Soir\u00e9e No.4004",
+    "handle": "wallco-4004",
+    "image_url": "/designs/img/1778706664563_432189635.png",
+    "filename": "1778706664563_432189635.png",
+    "generator": "wallco.ai",
+    "seed": 432189635,
+    "is_published": false,
+    "created_at": "2026-05-13T21:11:15.194189+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4005,
+    "kind": "seamless_tile",
+    "category": "stoned-animals",
+    "dominant_hex": "#1e1b16",
+    "saturation": 0.154,
+    "title": "Honey Hazefield No.4005",
+    "handle": "wallco-4005",
+    "image_url": "/designs/img/1778706664563_823956541.png",
+    "filename": "1778706664563_823956541.png",
+    "generator": "wallco.ai",
+    "seed": 823956541,
+    "is_published": false,
+    "created_at": "2026-05-13T21:11:15.337315+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4006,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#2e1a14",
+    "saturation": 0.394,
+    "title": "Rose Damask No.4006",
+    "handle": "wallco-4006",
+    "image_url": "/designs/img/1778706674188_1540004423.png",
+    "filename": "1778706674188_1540004423.png",
+    "generator": "wallco.ai",
+    "seed": 1540004423,
+    "is_published": false,
+    "created_at": "2026-05-13T21:11:24.944242+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4007,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#2e1a14",
+    "saturation": 0.394,
+    "title": "Rose Heirloom No.4007",
+    "handle": "wallco-4007",
+    "image_url": "/designs/img/var_4006_hue+60_1778706685126.png",
+    "filename": "var_4006_hue+60_1778706685126.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:11:27.413931+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4008,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#2e1a14",
+    "saturation": 0.394,
+    "title": "Rose Atelier No.4008",
+    "handle": "wallco-4008",
+    "image_url": "/designs/img/var_4006_hue+120_1778706687420.png",
+    "filename": "var_4006_hue+120_1778706687420.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:11:29.893879+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4009,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#2e1a14",
+    "saturation": 0.394,
+    "title": "Rose Salon No.4009",
+    "handle": "wallco-4009",
+    "image_url": "/designs/img/var_4006_hue+180_1778706689898.png",
+    "filename": "var_4006_hue+180_1778706689898.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:11:32.521329+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4010,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#2e1a14",
+    "saturation": 0.394,
+    "title": "Rose Baroque No.4010",
+    "handle": "wallco-4010",
+    "image_url": "/designs/img/var_4006_hue-60_1778706692525.png",
+    "filename": "var_4006_hue-60_1778706692525.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:11:36.039211+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4011,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#2e1a14",
+    "saturation": 0.394,
+    "title": "Rose Damask No.4011",
+    "handle": "wallco-4011",
+    "image_url": "/designs/img/var_4006_mono_1778706696064.png",
+    "filename": "var_4006_mono_1778706696064.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:11:38.592631+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4012,
+    "kind": "seamless_tile",
+    "category": "stoned-animals",
+    "dominant_hex": "#6c6a48",
+    "saturation": 0.2,
+    "title": "Olive Stardust No.4012",
+    "handle": "wallco-4012",
+    "image_url": "/designs/img/1778706753833_2003210320.png",
+    "filename": "1778706753833_2003210320.png",
+    "generator": "wallco.ai",
+    "seed": 2003210320,
+    "is_published": false,
+    "created_at": "2026-05-13T21:12:45.26105+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4013,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#7c5b7e",
+    "saturation": 0.161,
+    "title": "Plum Reverie No.4013",
+    "handle": "wallco-4013",
+    "image_url": "/designs/img/1778706753833_2064658976.png",
+    "filename": "1778706753833_2064658976.png",
+    "generator": "wallco.ai",
+    "seed": 2064658976,
+    "is_published": false,
+    "created_at": "2026-05-13T21:13:06.644332+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4014,
+    "kind": "seamless_tile",
+    "category": "stoned-animals",
+    "dominant_hex": "#706d57",
+    "saturation": 0.126,
+    "title": "Honey Vespers No.4014",
+    "handle": "wallco-4014",
+    "image_url": "/designs/img/1778706842077_1030913310.png",
+    "filename": "1778706842077_1030913310.png",
+    "generator": "wallco.ai",
+    "seed": 1030913310,
+    "is_published": false,
+    "created_at": "2026-05-13T21:14:12.25242+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4015,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#974d6b",
+    "saturation": 0.325,
+    "title": "Rose Vignette No.4015",
+    "handle": "wallco-4015",
+    "image_url": "/designs/img/1778706842150_999812104.png",
+    "filename": "1778706842150_999812104.png",
+    "generator": "wallco.ai",
+    "seed": 999812104,
+    "is_published": false,
+    "created_at": "2026-05-13T21:14:13.648134+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4016,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#b49566",
+    "saturation": 0.342,
+    "title": "Honey Carouse No.4016",
+    "handle": "wallco-4016",
+    "image_url": "/designs/img/1778706858703_779017205.png",
+    "filename": "1778706858703_779017205.png",
+    "generator": "wallco.ai",
+    "seed": 779017205,
+    "is_published": false,
+    "created_at": "2026-05-13T21:14:27.647323+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4017,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#384548",
+    "saturation": 0.125,
+    "title": "Marine Heirloom No.4017",
+    "handle": "wallco-4017",
+    "image_url": "/designs/img/1778706865250_1096877413.png",
+    "filename": "1778706865250_1096877413.png",
+    "generator": "wallco.ai",
+    "seed": 1096877413,
+    "is_published": false,
+    "created_at": "2026-05-13T21:14:33.818944+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4018,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#384548",
+    "saturation": 0.125,
+    "title": "Marine Atelier No.4018",
+    "handle": "wallco-4018",
+    "image_url": "/designs/img/var_4017_hue+60_1778706873924.png",
+    "filename": "var_4017_hue+60_1778706873924.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:14:36.260768+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4019,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#384548",
+    "saturation": 0.125,
+    "title": "Marine Salon No.4019",
+    "handle": "wallco-4019",
+    "image_url": "/designs/img/var_4017_hue+120_1778706876266.png",
+    "filename": "var_4017_hue+120_1778706876266.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:14:38.964883+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4020,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#384548",
+    "saturation": 0.125,
+    "title": "Marine Baroque No.4020",
+    "handle": "wallco-4020",
+    "image_url": "/designs/img/var_4017_hue+180_1778706878968.png",
+    "filename": "var_4017_hue+180_1778706878968.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:14:41.220572+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4021,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#384548",
+    "saturation": 0.125,
+    "title": "Marine Damask No.4021",
+    "handle": "wallco-4021",
+    "image_url": "/designs/img/var_4017_hue-60_1778706881224.png",
+    "filename": "var_4017_hue-60_1778706881224.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:14:43.447131+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4022,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#384548",
+    "saturation": 0.125,
+    "title": "Marine Heirloom No.4022",
+    "handle": "wallco-4022",
+    "image_url": "/designs/img/var_4017_mono_1778706883451.png",
+    "filename": "var_4017_mono_1778706883451.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:14:45.963053+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4023,
+    "kind": "seamless_tile",
+    "category": "stoned-animals",
+    "dominant_hex": "#746339",
+    "saturation": 0.341,
+    "title": "Honey Daydream No.4023",
+    "handle": "wallco-4023",
+    "image_url": "/designs/img/1778706931899_1240422610.png",
+    "filename": "1778706931899_1240422610.png",
+    "generator": "wallco.ai",
+    "seed": 1240422610,
+    "is_published": false,
+    "created_at": "2026-05-13T21:15:40.134452+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4024,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#442e4c",
+    "saturation": 0.246,
+    "title": "Mauve Soir\u00e9e No.4024",
+    "handle": "wallco-4024",
+    "image_url": "/designs/img/1778706932150_318453292.png",
+    "filename": "1778706932150_318453292.png",
+    "generator": "wallco.ai",
+    "seed": 318453292,
+    "is_published": false,
+    "created_at": "2026-05-13T21:15:43.699397+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4025,
+    "kind": "seamless_tile",
+    "category": "stoned-animals",
+    "dominant_hex": "#26502f",
+    "saturation": 0.356,
+    "title": "Sage Hazefield No.4025",
+    "handle": "wallco-4025",
+    "image_url": "/designs/img/1778707021881_1498887935.png",
+    "filename": "1778707021881_1498887935.png",
+    "generator": "wallco.ai",
+    "seed": 1498887935,
+    "is_published": false,
+    "created_at": "2026-05-13T21:17:10.242386+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4026,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#ac4d29",
+    "saturation": 0.615,
+    "title": "Amber Carouse No.4026",
+    "handle": "wallco-4026",
+    "image_url": "/designs/img/1778707022139_1755041480.png",
+    "filename": "1778707022139_1755041480.png",
+    "generator": "wallco.ai",
+    "seed": 1755041480,
+    "is_published": false,
+    "created_at": "2026-05-13T21:17:13.790438+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4027,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#251f18",
+    "saturation": 0.213,
+    "title": "Amber Heirloom No.4027",
+    "handle": "wallco-4027",
+    "image_url": "/designs/img/1778707054021_395249161.png",
+    "filename": "1778707054021_395249161.png",
+    "generator": "wallco.ai",
+    "seed": 395249161,
+    "is_published": false,
+    "created_at": "2026-05-13T21:17:42.349758+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4028,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#251f18",
+    "saturation": 0.213,
+    "title": "Amber Atelier No.4028",
+    "handle": "wallco-4028",
+    "image_url": "/designs/img/var_4027_hue+60_1778707062478.png",
+    "filename": "var_4027_hue+60_1778707062478.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:17:44.658081+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4029,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#251f18",
+    "saturation": 0.213,
+    "title": "Amber Salon No.4029",
+    "handle": "wallco-4029",
+    "image_url": "/designs/img/var_4027_hue+120_1778707064662.png",
+    "filename": "var_4027_hue+120_1778707064662.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:17:46.855415+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4030,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#251f18",
+    "saturation": 0.213,
+    "title": "Amber Baroque No.4030",
+    "handle": "wallco-4030",
+    "image_url": "/designs/img/var_4027_hue+180_1778707066859.png",
+    "filename": "var_4027_hue+180_1778707066859.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:17:49.049984+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4031,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#251f18",
+    "saturation": 0.213,
+    "title": "Amber Damask No.4031",
+    "handle": "wallco-4031",
+    "image_url": "/designs/img/var_4027_hue-60_1778707069054.png",
+    "filename": "var_4027_hue-60_1778707069054.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:17:51.244457+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4032,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#251f18",
+    "saturation": 0.213,
+    "title": "Amber Heirloom No.4032",
+    "handle": "wallco-4032",
+    "image_url": "/designs/img/var_4027_mono_1778707071248.png",
+    "filename": "var_4027_mono_1778707071248.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:17:53.492102+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4033,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#67b178",
+    "saturation": 0.322,
+    "title": "Sage Reverie No.4033",
+    "handle": "wallco-4033",
+    "image_url": "/designs/img/1778707107972_850447460.png",
+    "filename": "1778707107972_850447460.png",
+    "generator": "wallco.ai",
+    "seed": 850447460,
+    "is_published": false,
+    "created_at": "2026-05-13T21:18:36.878781+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4034,
+    "kind": "seamless_tile",
+    "category": "stoned-animals",
+    "dominant_hex": "#b24968",
+    "saturation": 0.418,
+    "title": "Rose Vespers No.4034",
+    "handle": "wallco-4034",
+    "image_url": "/designs/img/1778707111859_1500738078.png",
+    "filename": "1778707111859_1500738078.png",
+    "generator": "wallco.ai",
+    "seed": 1500738078,
+    "is_published": false,
+    "created_at": "2026-05-13T21:18:40.294915+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4035,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#1a172d",
+    "saturation": 0.324,
+    "title": "Sapphire Vignette No.4035",
+    "handle": "wallco-4035",
+    "image_url": "/designs/img/1778707112133_845983612.png",
+    "filename": "1778707112133_845983612.png",
+    "generator": "wallco.ai",
+    "seed": 845983612,
+    "is_published": false,
+    "created_at": "2026-05-13T21:18:57.539181+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4036,
+    "kind": "seamless_tile",
+    "category": "stoned-animals",
+    "dominant_hex": "#61483b",
+    "saturation": 0.244,
+    "title": "Amber Sundrift No.4036",
+    "handle": "wallco-4036",
+    "image_url": "/designs/img/1778707201864_1932138581.png",
+    "filename": "1778707201864_1932138581.png",
+    "generator": "wallco.ai",
+    "seed": 1932138581,
+    "is_published": false,
+    "created_at": "2026-05-13T21:20:11.743373+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4037,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#36233e",
+    "saturation": 0.278,
+    "title": "Mauve Caprice No.4037",
+    "handle": "wallco-4037",
+    "image_url": "/designs/img/1778707202195_1158633588.png",
+    "filename": "1778707202195_1158633588.png",
+    "generator": "wallco.ai",
+    "seed": 1158633588,
+    "is_published": false,
+    "created_at": "2026-05-13T21:20:13.872884+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4038,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#e8ddc1",
+    "saturation": 0.459,
+    "title": "Honey Atelier No.4038",
+    "handle": "wallco-4038",
+    "image_url": "/designs/img/1778707242808_44366930.png",
+    "filename": "1778707242808_44366930.png",
+    "generator": "wallco.ai",
+    "seed": 44366930,
+    "is_published": false,
+    "created_at": "2026-05-13T21:20:53.753137+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4039,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#e8ddc1",
+    "saturation": 0.459,
+    "title": "Honey Salon No.4039",
+    "handle": "wallco-4039",
+    "image_url": "/designs/img/var_4038_hue+60_1778707253875.png",
+    "filename": "var_4038_hue+60_1778707253875.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:20:56.27259+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4040,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#e8ddc1",
+    "saturation": 0.459,
+    "title": "Honey Baroque No.4040",
+    "handle": "wallco-4040",
+    "image_url": "/designs/img/var_4038_hue+120_1778707256277.png",
+    "filename": "var_4038_hue+120_1778707256277.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:20:58.946651+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4041,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#e8ddc1",
+    "saturation": 0.459,
+    "title": "Honey Damask No.4041",
+    "handle": "wallco-4041",
+    "image_url": "/designs/img/var_4038_hue+180_1778707258950.png",
+    "filename": "var_4038_hue+180_1778707258950.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:21:01.647414+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4042,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#e8ddc1",
+    "saturation": 0.459,
+    "title": "Honey Heirloom No.4042",
+    "handle": "wallco-4042",
+    "image_url": "/designs/img/var_4038_hue-60_1778707261652.png",
+    "filename": "var_4038_hue-60_1778707261652.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:21:04.296741+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4043,
+    "kind": "seamless_tile",
+    "category": "damask",
+    "dominant_hex": "#e8ddc1",
+    "saturation": 0.459,
+    "title": "Honey Atelier No.4043",
+    "handle": "wallco-4043",
+    "image_url": "/designs/img/var_4038_mono_1778707264309.png",
+    "filename": "var_4038_mono_1778707264309.png",
+    "generator": "wallco.ai",
+    "seed": 0,
+    "is_published": false,
+    "created_at": "2026-05-13T21:21:06.824423+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4044,
+    "kind": "seamless_tile",
+    "category": "stoned-animals",
+    "dominant_hex": "#6b744e",
+    "saturation": 0.196,
+    "title": "Olive Vespers No.4044",
+    "handle": "wallco-4044",
+    "image_url": "/designs/img/1778707292124_886719300.png",
+    "filename": "1778707292124_886719300.png",
+    "generator": "wallco.ai",
+    "seed": 886719300,
+    "is_published": false,
+    "created_at": "2026-05-13T21:21:41.738628+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4045,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#c2716f",
+    "saturation": 0.405,
+    "title": "Rose Vignette No.4045",
+    "handle": "wallco-4045",
+    "image_url": "/designs/img/1778707292169_925311146.png",
+    "filename": "1778707292169_925311146.png",
+    "generator": "wallco.ai",
+    "seed": 925311146,
+    "is_published": false,
+    "created_at": "2026-05-13T21:21:43.787542+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4046,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#927270",
+    "saturation": 0.135,
+    "title": "Rose Carouse No.4046",
+    "handle": "wallco-4046",
+    "image_url": "/designs/img/1778707357341_1090054798.png",
+    "filename": "1778707357341_1090054798.png",
+    "generator": "wallco.ai",
+    "seed": 1090054798,
+    "is_published": false,
+    "created_at": "2026-05-13T21:22:46.441918+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4047,
+    "kind": "seamless_tile",
+    "category": "stoned-animals",
+    "dominant_hex": "#34342d",
+    "saturation": 0.072,
+    "title": "Noir Spore No.4047",
+    "handle": "wallco-4047",
+    "image_url": "/designs/img/1778707381861_975933313.png",
+    "filename": "1778707381861_975933313.png",
+    "generator": "wallco.ai",
+    "seed": 975933313,
+    "is_published": false,
+    "created_at": "2026-05-13T21:23:10.091516+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
+  },
+  {
+    "id": 4048,
+    "kind": "seamless_tile",
+    "category": "drunk-animals",
+    "dominant_hex": "#ba2b78",
+    "saturation": 0.624,
+    "title": "Plum Saunter No.4048",
+    "handle": "wallco-4048",
+    "image_url": "/designs/img/1778707382146_663843369.png",
+    "filename": "1778707382146_663843369.png",
+    "generator": "wallco.ai",
+    "seed": 663843369,
+    "is_published": false,
+    "created_at": "2026-05-13T21:23:17.130509+00:00",
+    "motifs": [],
+    "user_removed": false,
+    "user_color_good": null,
+    "user_style_good": null,
+    "user_scale_good": null,
+    "user_stars": null,
+    "room_mockups": []
   }
 ]
\ No newline at end of file
diff --git a/server.js b/server.js
index 6273943..ddba2af 100644
--- a/server.js
+++ b/server.js
@@ -6206,6 +6206,77 @@ ${htmlHeader('/designs')}
             </div>
           </div>
 
+          ${_isAdmin ? `
+          <!-- Admin: tone-on-tone color-wheel recoloring. Per design click,
+               spins up a Gemini image-edit in the target hue. Steve's
+               2026-05-13 ask: "Add buttons to admin to create in color
+               wheel color. for tone on tone items." -->
+          <div class="recolor-wheel" style="margin-top:22px;padding-top:18px;border-top:1px solid var(--line)">
+            <label style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);display:block;margin-bottom:8px">Admin · tone-on-tone color wheel</label>
+            <p style="font:11px var(--sans);color:var(--ink-faint);margin:0 0 10px">Click a hue to generate a tone-on-tone variant. Each takes ~5-15s &amp; costs ~$0.001 (gemini-image-edit).</p>
+            <div id="recolor-buttons" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px">
+              <button data-hue="red"      style="padding:6px 14px;background:#dc2626;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Red</button>
+              <button data-hue="orange"   style="padding:6px 14px;background:#ea580c;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Orange</button>
+              <button data-hue="yellow"   style="padding:6px 14px;background:#ca8a04;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Yellow</button>
+              <button data-hue="lime"     style="padding:6px 14px;background:#65a30d;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Lime</button>
+              <button data-hue="green"    style="padding:6px 14px;background:#16a34a;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Green</button>
+              <button data-hue="teal"     style="padding:6px 14px;background:#0d9488;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Teal</button>
+              <button data-hue="cyan"     style="padding:6px 14px;background:#0891b2;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Cyan</button>
+              <button data-hue="blue"     style="padding:6px 14px;background:#2563eb;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Blue</button>
+              <button data-hue="indigo"   style="padding:6px 14px;background:#4338ca;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Indigo</button>
+              <button data-hue="violet"   style="padding:6px 14px;background:#7c3aed;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Violet</button>
+              <button data-hue="magenta"  style="padding:6px 14px;background:#c026d3;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Magenta</button>
+              <button data-hue="pink"     style="padding:6px 14px;background:#db2777;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Pink</button>
+              <button data-hue="neutral"  style="padding:6px 14px;background:#a16207;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Neutral</button>
+              <button data-hue="charcoal" style="padding:6px 14px;background:#262626;color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em">Charcoal</button>
+              <button id="recolor-all" style="padding:6px 14px;background:linear-gradient(90deg,#dc2626,#ea580c,#ca8a04,#16a34a,#0891b2,#2563eb,#7c3aed,#db2777);color:#fff;border:0;border-radius:6px;cursor:pointer;font:600 11px var(--sans);text-transform:uppercase;letter-spacing:.04em;flex-basis:100%;margin-top:4px">✦ Spin all 14 hues (parallel)</button>
+            </div>
+            <div id="recolor-status" style="font:12px var(--sans);color:var(--ink-soft,#555);min-height:18px"></div>
+            <div id="recolor-results" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin-top:10px"></div>
+          </div>
+          <script>
+          (function(){
+            const stat = document.getElementById('recolor-status');
+            const results = document.getElementById('recolor-results');
+            const fireOne = async (hue, btn) => {
+              if (btn) { btn.disabled = true; btn.style.opacity = '0.5'; }
+              const startTxt = '→ ' + hue + '…';
+              stat.textContent = (stat.textContent ? stat.textContent + '  ' : '') + startTxt;
+              try {
+                const r = await fetch('/api/design/${design.id}/recolor', {
+                  method: 'POST', headers: { 'Content-Type': 'application/json' },
+                  body: JSON.stringify({ hue }),
+                });
+                const j = await r.json();
+                if (!j.ok) throw new Error(j.error || 'failed');
+                const card = document.createElement('div');
+                card.innerHTML = '<a href="/design/' + j.new_id + '" target="_blank" style="display:block;text-decoration:none;color:inherit">'
+                  + '<img src="' + j.new_image_url + '" style="width:100%;aspect-ratio:1;object-fit:cover;border-radius:6px;border:1px solid var(--line)">'
+                  + '<div style="font:10px var(--sans);text-align:center;margin-top:4px;color:var(--ink-soft,#555)">' + hue + ' · #' + j.new_id + '</div>'
+                  + '</a>';
+                results.appendChild(card);
+                stat.textContent = stat.textContent.replace(startTxt, '✓ ' + hue);
+              } catch (e) {
+                stat.textContent = stat.textContent.replace(startTxt, '✗ ' + hue + ':' + e.message);
+              } finally {
+                if (btn) { btn.disabled = false; btn.style.opacity = '1'; }
+              }
+            };
+            document.querySelectorAll('#recolor-buttons button[data-hue]').forEach(b => {
+              b.addEventListener('click', () => fireOne(b.dataset.hue, b));
+            });
+            const allBtn = document.getElementById('recolor-all');
+            if (allBtn) allBtn.addEventListener('click', async () => {
+              allBtn.disabled = true; allBtn.style.opacity = '0.5';
+              const hues = Array.from(document.querySelectorAll('#recolor-buttons button[data-hue]')).map(b => b.dataset.hue);
+              stat.textContent = 'firing ' + hues.length + ' in parallel…';
+              await Promise.all(hues.map(h => fireOne(h, null)));
+              allBtn.disabled = false; allBtn.style.opacity = '1';
+            });
+          })();
+          </script>
+          ` : ''}
+
           <!-- Share this design — Pinterest (one-click Pin) + Instagram
                (download image + open IG). Per Steve's 2026-05-13 ask. -->
           <div class="share-block" style="margin-top:22px;padding-top:18px;border-top:1px solid var(--line)">
@@ -12891,6 +12962,130 @@ app.post('/api/design/:id/furnish', async (req, res) => {
   }
 });
 
+// ── /api/design/:id/recolor — tone-on-tone color-wheel recoloring ──
+// Admin-only. Takes a source design + hue family, calls Gemini image-edit
+// with a strict mono-hue prompt, inserts a new spoon_all_designs row,
+// returns the new id + image_url.
+//
+// Steve's 2026-05-13 ask: "Add buttons to admin to create in color wheel
+// color. for tone on tone items." This endpoint backs those buttons.
+const RECOLOR_HUES = {
+  red:     { label: 'Red',      tail: 'crimson red and burgundy ground; deeper near-black-red motif; rich saturation' },
+  orange:  { label: 'Orange',   tail: 'warm terracotta-orange ground; deeper burnt-orange motif; earthy saturation' },
+  yellow:  { label: 'Yellow',   tail: 'soft butter-yellow ground; deeper gold-ochre motif; vintage feel' },
+  lime:    { label: 'Lime',     tail: 'pale lime-chartreuse ground; deeper olive-lime motif; fresh herbaceous' },
+  green:   { label: 'Green',    tail: 'sage to forest green ground; deeper hunter motif; tonal jewel tones' },
+  teal:    { label: 'Teal',     tail: 'teal-aqua ground; deeper near-black teal motif; tonal lacquer feel' },
+  cyan:    { label: 'Cyan',     tail: 'pale cyan-aqua ground; deeper peacock motif; subtle marine' },
+  blue:    { label: 'Blue',     tail: 'powder to navy blue ground; near-black-navy motif; tonal jewel' },
+  indigo:  { label: 'Indigo',   tail: 'rich indigo ground; near-black indigo motif; deep tonal' },
+  violet:  { label: 'Violet',   tail: 'lavender to deep violet ground; near-black violet motif; tonal' },
+  magenta: { label: 'Magenta',  tail: 'fuchsia-magenta ground; deeper burgundy-magenta motif; saturated tonal' },
+  pink:    { label: 'Pink',     tail: 'blush to dusty rose ground; deeper rose motif; tonal romantic' },
+  neutral: { label: 'Neutral',  tail: 'warm cream/taupe ground; deeper sepia-bronze motif; tonal vintage' },
+  charcoal:{ label: 'Charcoal', tail: 'pale grey ground; deep charcoal motif; tonal monochrome' },
+};
+
+app.post('/api/design/:id/recolor', express.json(), async (req, res) => {
+  if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
+  try {
+    const id = parseInt(req.params.id, 10);
+    if (!Number.isFinite(id) || id < 1) return res.status(400).json({ ok: false, error: 'bad id' });
+    const hueKey = String((req.body && req.body.hue) || '').toLowerCase();
+    const hue = RECOLOR_HUES[hueKey];
+    if (!hue) return res.status(400).json({ ok: false, error: 'bad hue', valid: Object.keys(RECOLOR_HUES) });
+
+    const KEY = process.env.GEMINI_API_KEY;
+    if (!KEY) return res.status(500).json({ ok: false, error: 'GEMINI_API_KEY missing' });
+
+    // Load source image bytes from local_path or by reading the file behind image_url
+    const d = DESIGNS.find(x => x.id === id);
+    if (!d) return res.status(404).json({ ok: false, error: 'design not found' });
+    const srcPath = d.local_path || (d.filename ? path.join(IMG_DIR, d.filename) : null)
+      || (d.image_url && d.image_url.startsWith('/designs/img/') ? path.join(IMG_DIR, d.image_url.replace('/designs/img/', '')) : null);
+    if (!srcPath || !fs.existsSync(srcPath)) return res.status(404).json({ ok: false, error: 'source file missing on disk', srcPath });
+    const srcB64 = fs.readFileSync(srcPath).toString('base64');
+
+    const prompt = `Recolor everything into the ${hue.label.toUpperCase()} family ONLY. ${hue.tail}. `
+      + 'Tone-on-tone: strictly one hue family throughout — no contrasting hues anywhere. '
+      + 'Preserve the motif, composition, and pattern repeat exactly. '
+      + 'Seamless tile, no edge artifacts, no signature, no watermark, no text. '
+      + 'High detail, archival quality, fabric-pattern aesthetic.';
+
+    const t0 = Date.now();
+    const gr = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent?key=${KEY}`, {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        contents: [{ parts: [{ inline_data: { mime_type: 'image/png', data: srcB64 } }, { text: prompt }] }],
+        generationConfig: { responseModalities: ['IMAGE'] },
+      }),
+    });
+    if (!gr.ok) return res.status(502).json({ ok: false, error: 'gemini ' + gr.status, body: (await gr.text()).slice(0, 200) });
+    const gj = await gr.json();
+    try {
+      const { logGemini } = require(require('os').homedir() + '/.claude/skills/cost-tracker/scripts/log-gemini.js');
+      logGemini(gj, { app: 'wallco-ai', note: 'recolor-' + hueKey + '-from-' + id, model: 'gemini-2.5-flash-image' });
+    } catch {}
+    const part = gj.candidates?.[0]?.content?.parts?.find(p => p.inline_data || p.inlineData);
+    const data = part?.inline_data?.data || part?.inlineData?.data;
+    if (!data) return res.status(502).json({ ok: false, error: 'no image returned', finish: gj.candidates?.[0]?.finishReason });
+
+    // Save PNG
+    const crypto2 = require('crypto');
+    const seed = crypto2.randomInt(1, 2 ** 31 - 1);
+    const filename = `recolor_${Date.now()}_p${id}_${hueKey}_${seed}.png`;
+    const outPath = path.join(IMG_DIR, filename);
+    fs.writeFileSync(outPath, Buffer.from(data, 'base64'));
+    const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
+
+    // Insert into PG
+    const parentTitle = (d.title || ('Design ' + id)).replace(/\s+No\.\d+$/, '');
+    const newTitle = `${parentTitle} (${hue.label} tonal)`;
+    const baseTags = ['recolor', 'tone-on-tone', hueKey];
+    if (Array.isArray(d.motifs)) baseTags.push(...d.motifs.slice(0, 3));
+    const tagSql = 'ARRAY[' + baseTags.map(t => "'" + String(t).replace(/'/g, "''") + "'").join(',') + ']::text[]';
+    const motifsSql = Array.isArray(d.motifs) && d.motifs.length
+      ? 'ARRAY[' + d.motifs.map(t => "'" + String(t).replace(/'/g, "''") + "'").join(',') + ']::text[]'
+      : "ARRAY[]::text[]";
+    const promptDesc = (newTitle + ' — ' + prompt).slice(0, 1800).replace(/'/g, "''");
+    const insertSql = `INSERT INTO spoon_all_designs
+      (kind, width_in, height_in, generator, prompt, seed, dominant_hex,
+       local_path, image_url, category, motifs, tags, is_published, request_text)
+      VALUES ('seamless_tile', 24, 24, 'gemini-2.5-flash-image-edit',
+        '${promptDesc}', ${seed}, NULL,
+        '${outPath.replace(/'/g, "''")}', '/designs/img/${filename}',
+        ${"'" + (d.category || 'recolor') + "'"},
+        ${motifsSql}, ${tagSql}, TRUE,
+        '${("Tone-on-tone " + hue.label + " recolor of design " + id).replace(/'/g, "''")}')
+      RETURNING id;`;
+    const newIdStr = psqlQuery(insertSql);
+    const newId = parseInt(newIdStr, 10);
+
+    // Push into in-memory DESIGNS so it shows up without a server restart
+    DESIGNS.unshift({
+      id: newId,
+      kind: 'seamless_tile',
+      category: d.category || 'recolor',
+      title: newTitle,
+      handle: 'wallco-' + newId,
+      image_url: '/designs/img/' + filename,
+      filename,
+      local_path: outPath,
+      generator: 'gemini-2.5-flash-image-edit',
+      seed,
+      is_published: true,
+      created_at: new Date().toISOString(),
+      motifs: d.motifs || [],
+      tags: baseTags,
+    });
+
+    res.json({ ok: true, parent_id: id, new_id: newId, new_image_url: '/designs/img/' + filename, hue: hueKey, elapsed_s: elapsed });
+  } catch (e) {
+    res.status(500).json({ ok: false, error: e.message });
+  }
+});
+
 app.post('/api/design/:id/bake',
   express.json({ limit: '20mb' }),
   (req, res) => {

← f4031b4 furnish: disk-backed cache (data/furnish-cache/<id>-<ctx>.js  ·  back to Wallco Ai  ·  design coordinate: collapse paint chips into <details> table 029a6cf →