← 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
M data/designs.jsonM server.js
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 & 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 →