← back to Wallco Ai
night-builder: fix PLACEHOLDER image_url bug in colorway INSERT (CTE)
f67ea3a96d653f21c80b03ad55e3f309bf17cf77 · 2026-05-20 07:37:38 -0700 · Steve Abrams
The colorway-variant INSERT was hardcoding image_url='/designs/img/by-id/
PLACEHOLDER' because at INSERT time the new row's id isn't known. The
returning id from RETURNING was used only for logging, never for an
UPDATE — so 9,217 colorway children accumulated in PG with PLACEHOLDER
URLs, all hidden from /designs because designHasImage's regex requires
/by-id/\d+.
Fix: single-round-trip CTE that INSERTs the child, captures the id via
RETURNING, then UPDATEs that same row to set image_url = '/designs/img/
by-id/' || id::text. Atomic, no second round trip.
Run-time UPDATE (2026-05-20 14:00) corrected the existing 9,585 rows;
this commit prevents new ones from accumulating. /designs visible count
jumped 176 → 8,903 (50×) after the bulk UPDATE; will hold steady going
forward as the worker generates new colorways with proper URLs.
Workers restarted (pkill + respawn) so they pick up this code.
Files touched
A scripts/night-builder.js
Diff
commit f67ea3a96d653f21c80b03ad55e3f309bf17cf77
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 20 07:37:38 2026 -0700
night-builder: fix PLACEHOLDER image_url bug in colorway INSERT (CTE)
The colorway-variant INSERT was hardcoding image_url='/designs/img/by-id/
PLACEHOLDER' because at INSERT time the new row's id isn't known. The
returning id from RETURNING was used only for logging, never for an
UPDATE — so 9,217 colorway children accumulated in PG with PLACEHOLDER
URLs, all hidden from /designs because designHasImage's regex requires
/by-id/\d+.
Fix: single-round-trip CTE that INSERTs the child, captures the id via
RETURNING, then UPDATEs that same row to set image_url = '/designs/img/
by-id/' || id::text. Atomic, no second round trip.
Run-time UPDATE (2026-05-20 14:00) corrected the existing 9,585 rows;
this commit prevents new ones from accumulating. /designs visible count
jumped 176 → 8,903 (50×) after the bulk UPDATE; will hold steady going
forward as the worker generates new colorways with proper URLs.
Workers restarted (pkill + respawn) so they pick up this code.
---
scripts/night-builder.js | 863 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 863 insertions(+)
diff --git a/scripts/night-builder.js b/scripts/night-builder.js
new file mode 100755
index 0000000..c1ceaf8
--- /dev/null
+++ b/scripts/night-builder.js
@@ -0,0 +1,863 @@
+#!/usr/bin/env node
+// All-night Replicate generator.
+//
+// Two tracks, interleaved:
+// A) Muted Gucci-palette CACTUS variants (extends the existing 10 colorways)
+// B) Drunk-animals → designer-elevated reinterpretation (Schumacher/Cole&Son
+// register, muted Gucci palette, single-ink, archival gouache)
+//
+// Steve's brief: "continue creating patterns all night with the subdued hues
+// of grey, taupe, cream, ivory, gucci browns, recreate the drunk animals with
+// much more designer like designs."
+//
+// Hard rules:
+// - Budget cap: $20 (≈2000 Replicate SDXL images at ~$0.01 ea)
+// - Halt: touch /tmp/halt-night-builder.flag to stop after current image
+// - Stop time: 07:30 local (Steve wakes ~8 AM)
+// - Settlement gate inline (single-ink + no Part B elements = OK)
+// - is_published=TRUE so Steve sees them in /designs immediately
+// - Category: 'drunk-animals-designer' for the B-track (NOT 'drunk-animals')
+// so Steve can sort + dedupe vs the kitsch originals
+// - Cooldown: 5s between calls to be polite to Replicate
+//
+// Cost tracking: each Replicate image hits the cost-tracker via log.js
+// (already wired in generate_designs.js).
+
+const { spawnSync } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+
+const ROOT = path.join(__dirname, '..');
+const HALT_FLAG = '/tmp/halt-night-builder.flag';
+const LOG = '/tmp/night-builder.log';
+const STOP_HOUR = 7; // local: stop at 07:30 next-morning
+const STOP_MIN = 30;
+const BUDGET_CAP_USD = 20.0;
+const PER_IMAGE_USD = 0.011; // Replicate SDXL ~$0.01 (incl. 10% safety margin)
+const COOLDOWN_MS = 5000;
+
+// Compute the actual next-7:30-AM deadline relative to script start.
+function computeStopDeadline() {
+ const now = new Date();
+ const d = new Date(now);
+ d.setHours(STOP_HOUR, STOP_MIN, 0, 0);
+ // If 7:30 today is already in the past, target 7:30 tomorrow.
+ if (d.getTime() <= now.getTime()) d.setDate(d.getDate() + 1);
+ return d;
+}
+const STOP_DEADLINE = computeStopDeadline();
+
+const MUTED_PALETTE = [
+ { slug: 'smoke-ash', name: 'Smoke Ash', hex: '#6B6660', tone: 'warm-mid-grey' },
+ { slug: 'mushroom-taupe', name: 'Mushroom Taupe', hex: '#8A7E70', tone: 'designer-taupe' },
+ { slug: 'linen-cream', name: 'Linen Cream', hex: '#EDE4D3', tone: 'soft-cream' },
+ { slug: 'bone-ivory', name: 'Bone Ivory', hex: '#F2EADB', tone: 'warm-ivory' },
+ { slug: 'gucci-cocoa', name: 'Gucci Cocoa', hex: '#5C4033', tone: 'deep-cocoa' },
+ { slug: 'antique-tan', name: 'Antique Tan', hex: '#A78863', tone: 'warm-camel' },
+ { slug: 'stone-pewter', name: 'Stone Pewter', hex: '#7A736A', tone: 'cool-pewter' },
+ { slug: 'saddle-mocha', name: 'Saddle Mocha', hex: '#4A3528', tone: 'saddle-leather-brown' },
+ { slug: 'champagne-linen', name: 'Champagne Linen', hex: '#D7C9A8', tone: 'champagne-neutral' },
+ { slug: 'charcoal-smoke', name: 'Charcoal Smoke', hex: '#3D3833', tone: 'near-black-charcoal' },
+];
+
+const DB = 'dw_unified';
+const PSQL = `psql ${DB} -At -q`;
+function psql(sql) {
+ return execSync(PSQL, { input: sql, encoding: 'utf8' }).trim();
+}
+
+function nowIso() { return new Date().toISOString(); }
+function logLine(s) {
+ const line = `[${nowIso()}] ${s}`;
+ console.log(line);
+ fs.appendFileSync(LOG, line + '\n');
+}
+
+function pickColorway() {
+ return MUTED_PALETTE[Math.floor(Math.random() * MUTED_PALETTE.length)];
+}
+
+// ── Track A: cactus variants in muted palette ────────────────────────
+const CACTUS_PATTERNS = [
+ 'tall saguaro forms with raised arms, scattered prickly-pear paddles',
+ 'dense agave rosettes with whip-stem ocotillo verticals',
+ 'barrel cactus rosettes and cholla branches in tight tessellation',
+ 'sprawling prickly-pear paddle clusters as a vertical wallpaper repeat',
+ 'ocotillo whip-stem fountain pattern with small barrel-cactus accents',
+];
+
+function composeCactusPrompt(cw) {
+ const pattern = CACTUS_PATTERNS[Math.floor(Math.random() * CACTUS_PATTERNS.length)];
+ return (
+ `Cactus Repeat — ${cw.name} colorway. ` +
+ `Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) — ALL cactus silhouettes in this exact single ink. ` +
+ `Sonoran desert cacti as FLAT STENCIL SILHOUETTES: ${pattern}, arranged in a tessellated seamless wallpaper repeat. ` +
+ `Each cactus is one solid uniform shape with NO interior detail, NO ribbing lines, NO shading, NO spines drawn — pure silhouette only. ` +
+ `All cacti upright, oriented vertically, symmetric placement on the vertical axis. ` +
+ `Edge-to-edge tessellation, generous breathing room of clean ground between motifs. ` +
+ `Avoid: banana leaves, palm fronds, monstera, philodendron, tropical jungle foliage. ` +
+ SHARED_ARTDIR
+ );
+}
+
+// ── Track B: DEBAUCHED ANIMALS (rewritten 2026-05-20 per Steve directive) ──
+// References:
+// 1. Asterix drunk-Roman-legionnaire pose (Uderzo): staggering gait, leg
+// kicking sideways, helmet askew, X-pupil eyes, wobble-line motion arc.
+// 2. Eadweard Muybridge "Animal Locomotion" sequential-motion plates: each
+// animal captured at multiple frozen poses showing the full gait cycle,
+// tiled side-by-side. The original "movement sneak" before cinema existed.
+// Tone: HEDONISTIC, DEBAUCHED, BACCHANALIAN. Cocktail party gone fully off
+// the rails. Animals are mid-stagger, mid-collapse, mid-tumble — never in
+// dignified repose.
+const ANIMAL_DESCRIPTORS = [
+ // STAGGERING / FALLING
+ 'staggering bear mid-stumble with one paw airborne, Asterix-drunk-legionnaire pose',
+ 'tumbling fox falling backward limbs splayed',
+ 'reticulated giraffe slumped against an invisible wall, legs splayed Bambi-on-ice',
+ 'red panda hanging upside down from a phantom branch, eyes spiraled',
+ 'lemur mid-cartwheel, limbs flung in four directions, Uderzo motion arcs around it',
+ 'sloth lying flat on its back, all four limbs in the air, classic dead-drunk pose',
+ 'capybara mid-belly-laugh slumped sideways, hiccup bubble above',
+ 'orangutan kicking one leg high in a Charleston-stagger, the other arm windmilling',
+ 'raccoon in a top hat falling face-first toward the viewer',
+ 'elephant calf doing a full pratfall on its back legs, trunk waving wildly',
+ 'tree frog mid-leap and missing, arms grasping at empty air',
+ 'Bornean orangutan blowing a kiss with one eye crossed, drink mid-spill',
+ 'koala wrapped around a bottle larger than itself, swaying',
+ 'wallaby boxer mid-haymaker against nothing, swinging at phantoms',
+ 'tasmanian devil mid-spin, dust-cloud lines around it',
+ 'fennec fox flopped on its side, tongue lolling out',
+ // DANCING / CABARET POSES
+ 'flamingo in a high-kick cancan pose, feathers flying',
+ 'okapi in a tango dip, partner is an invisible silhouette',
+ 'pangolin doing a one-legged drunken pirouette, scales spread out',
+ 'capybara in a top hat doing the Charleston',
+ 'meerkat trio in a chorus-line kick',
+ 'aardvark mid-Lindy-hop swing-out, partner missing',
+ 'lemur ballroom-glide with one paw extended, the other holding a cocktail',
+ 'red panda in disco pose, one arm up Saturday-Night-Fever style',
+ // PASSED OUT / OBLIVION
+ 'walrus snoring with three Zs above it, X-pupils',
+ 'turtle on its back legs in the air, hiccup bubble',
+ 'manatee floating belly-up with bubbles around its mouth',
+ 'wombat sprawled on a velvet ottoman, paws curled',
+ 'lazy panther draped over a bar-stool silhouette',
+ 'sleepy hedgehog curled inside an empty cocktail glass',
+ // MID-PRATFALL
+ 'chimpanzee mid-banana-peel slip (peel falling, not held)',
+ 'baboon doing a forward roll into a stack of empty glasses',
+ 'mandrill mid-faceplant, legs in the air',
+ 'gorilla doing a dignified slow-motion topple, one finger raised',
+ // INTOXICATED VIBE
+ 'platypus floating in zero-gravity with a snifter, monocle askew',
+ 'armadillo curled mid-tumble, motion-arc behind it',
+ 'tapir wobbling on two legs holding a martini glass overhead',
+ 'binturong (bearcat) dangling from a velvet rope by its tail, drink in mouth',
+ 'ocelot lounging on a chaise with smoking pipe and one eye closed',
+ 'civet stretched out on a Persian rug, paws to the ceiling',
+ // BACCHANALIAN HARVEST
+ 'wild boar reclined against a barrel, snout sniffing the air',
+ 'bison crowned with a wreath of curlicue smoke, head tipped back',
+ 'ibex doing a goat-headstand on a stack of bottles',
+ 'fallow deer mid-leap with hooves crossed, antlers tangled with ribbons',
+ // CLASSIC CARTOON-DRUNK
+ 'cat with five-o\'clock-shadow holding a cocktail like Tom of Tom-and-Jerry',
+ 'penguin in a bowtie tipping a top hat with one flipper',
+ 'walrus playing a tiny piano with one tusk',
+ 'badger in a smoking jacket striking a Sherlock Holmes pose',
+ 'opossum hanging by its tail clutching a martini upside-down',
+ // SMOKE-CLOUD / VAPOR POSES
+ 'fox blowing a perfect smoke-ring, eye squinted',
+ 'rabbit reclining in a hookah-style pose, pipe in paw',
+ 'mongoose with three smoke-curls rising from a pipe',
+ 'lizard breathing out a single curling vapor-trail',
+ // CRYPTID DEBAUCHERY (real animals only, no fantasy)
+ 'aye-aye gesturing wildly with a long finger raised',
+ 'star-nosed mole sniffing a cocktail with thirteen tentacles',
+ 'pangolin doing a slow Charleston in scaly tuxedo',
+ 'kinkajou dangling from a chandelier with a coupe',
+ // MISC SURREAL POSES
+ 'sloth doing a slow-motion crowd-surf on raised paws',
+ 'porcupine quill-up flexing arms strongman pose',
+ 'capybara DJ-ing on a turntable made of orange slices',
+ 'badger conducting a phantom orchestra, baton in paw',
+];
+
+const DRINK_DESCRIPTORS = [
+ 'half-empty long-stemmed coupe tilted at 45° with champagne sloshing out',
+ 'cut-crystal whiskey tumbler frozen mid-toss, amber liquid arcing through the air',
+ 'martini glass tipped over, two olives rolling away',
+ 'bottle of pilsner held inverted, foam splashing onto the ground',
+ 'red wine balloon mid-spill, dark liquid streaming sideways',
+ 'frosted vodka decanter being chugged straight from the neck',
+ 'tequila shot raised in toast, lime wedge between teeth',
+ 'cognac snifter cradled like a baby',
+ 'Aperol spritz in one hand, second drink in the other',
+ 'magnum of champagne held aloft and shaken, cork popping with a comic burst',
+ 'rolled joint trailing a wispy smoke-curl ribbon',
+ 'oversize pipe with three smoke-curls forming a clover',
+ 'cocktail with three umbrellas crammed into it',
+ 'pint glass overflowing with foam pouring over the sides',
+];
+
+// Muybridge-style: sometimes show the animal in a sequence of 3 frozen motion
+// poses (instead of one), tiled side-by-side. Used 30% of the time.
+const MOTION_FORMATS = [
+ 'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
+ 'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
+ 'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
+ 'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
+ 'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
+ 'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
+ 'single motion-blurred pose with Asterix-style wobble-arc lines around the figure showing the sway',
+ // 3 out of 10 → Muybridge sequence:
+ 'Muybridge-style sequence of three frozen poses of the SAME animal mid-stagger, tiled side-by-side left-to-right showing the full stumble cycle',
+ 'Muybridge-style sequence of three frozen poses of the SAME animal collapsing forward, tiled side-by-side left-to-right',
+ 'Muybridge-style sequence of four frozen poses of the SAME animal mid-pratfall, tiled in a 2x2 grid',
+];
+
+const BOTANICAL_SETTINGS = [
+ 'set among silhouetted Ghost Orchids',
+ 'against a damask-trellis backdrop',
+ 'framed by Botticelli-style arabesques',
+ 'surrounded by hand-painted Magnolia branches',
+ 'within a Schumacher-Audubon herbarium frame',
+ 'set in a Cole&Son chinoiserie panel',
+ 'flanked by ogival damask medallions',
+ 'centered on a wunderkammer specimen plate',
+];
+
+function composeDrunkAnimalPrompt(cw) {
+ const animal = ANIMAL_DESCRIPTORS[Math.floor(Math.random() * ANIMAL_DESCRIPTORS.length)];
+ const drink = DRINK_DESCRIPTORS[Math.floor(Math.random() * DRINK_DESCRIPTORS.length)];
+ return (
+ `Cocktail-hour wallpaper — ${animal} cradling a ${drink}. ` +
+ `Colorway: ${cw.name} (${cw.hex}) ground with bone-ivory (#F2EADB) figure ink. ` +
+ `Each animal-and-drink vignette is one FLAT STENCIL SILHOUETTE in bone-ivory — no interior shading, no anatomical detail, no fur texture. The drink glass is part of the same silhouette, also flat ink. ` +
+ `Vignettes arranged in a symmetric tessellated repeat, generous breathing room of clean ground between them. ` +
+ `Strictly no botanical background, no foliage filler, no decorative arabesques behind the figures — the ground is one uniform flat color with nothing on it except the silhouette vignettes. ` +
+ `No bananas, no grapes, no birds, no butterflies. ` +
+ SHARED_ARTDIR
+ );
+}
+
+// ── SHARED art direction — rewritten 2026-05-20 to ELIMINATE the bleed-through
+// layered-background problem. Anchor refs swapped from natural-history plates
+// (Haeckel/Merian — those DO have atmospheric depth + layered washes) to pure
+// 2-tone serigraph references (Warhol/Lichtenstein/Toulouse-Lautrec/WPA poster
+// /Saul Bass/pochoir stencil — those are GUARANTEED flat 2-tone with NO depth).
+// Rewritten 2026-05-20 — Steve directive: "way too busy for most designs..
+// keep it simple". Old SHARED_ARTDIR encouraged tessellated edge-to-edge
+// filling. New version forces SPARSE motifs + GENEROUS NEGATIVE SPACE.
+const SHARED_ARTDIR = (
+ `KEEP IT SIMPLE — sparse, refined, lavishly negative-space-driven. At LEAST 75-85% of the tile area is solid clean ground tone with NOTHING in it (often 85%+, like Zuber and Maya Romanoff archives). Motifs are FEW (2-4 per tile maximum), well-spaced, never edge-to-edge, never tessellated, never cluttered. The empty ground IS the design — motifs are punctuation, not pattern-fill. ` +
+ `Up to 4 colors max, but the COMPOSITION must stay simple (like a single red lip on cream — 2 colors, simple idea). Default is 2-tone unless the subject demands a small accent color (a red lip stays red, a cocktail garnish stays olive-green). Never more than 4 distinct ink tones. ` +
+ `ABSOLUTELY NO bleed between layers — bleed looks cheap and is FORBIDDEN. Single-layer screen-print only. ` +
+ `NO ghost layer, NO faded background, NO atmospheric depth, NO secondary motifs behind the main figures. The ground is one completely uniform flat solid color from edge to edge. ` +
+ `Reference aesthetic: high-end designer wallcovering register, anchored to these brand archives — Zuber & Cie (hand-blocked scenic), Designers Guild (refined modern romantic), Thibaut (archival classic), Arte International (natural-fiber luxe), Romo (painterly British), Maya Romanoff (handcrafted luxe surface). Subtle. Sparse. Refined. Negative-space-driven. Single-pass silkscreen with two ink screens. ` +
+ `Hard clean razor-sharp edges. No half-tones, no gradient, no watercolor, no crosshatch shading. Every figure region is one flat color. ` +
+ `Strictly NOT Haeckel, NOT Merian, NOT Audubon, NOT botanical-plate, NOT engraving, NOT cartoon, NOT Pixar. ` +
+ `Square aspect ratio, seamless tile repeat (edges meet cleanly), flat-paper rendering, no watermark, no signature, no text, no logos.`
+);
+
+// Universal negative prompt — passed to Replicate as --negative-prompt. Catches
+// the model's instinct to add depth/layers even when text anti-prompts fail.
+const SHARED_NEGATIVE = (
+ `gradient, ombre, fade, faded, ghosted, ghost layer, atmospheric perspective, depth, layered background, ` +
+ `secondary motifs behind, lighter background copies, halftone, half-tone, chromatic gradient, watercolor wash, ` +
+ `airbrush, soft edges, blurry edges, anti-aliased blur, crosshatching, engraving lines, stippling, ` +
+ `three colors, four colors, multi-color, textured ground, mottled background, shadow, drop shadow, ` +
+ `Haeckel, Merian, Audubon, natural-history plate, botanical plate, engraving, cartoon, Pixar, 3D render`
+);
+
+const CHINOISERIE_MOTIFS = [
+ 'pagodas with curved-eave silhouettes flanked by symmetric flowering plum branches',
+ 'kylin mythical-beast pair on a rocky outcrop with stylized cloud-bands above',
+ 'lotus blossom roundels in vertical stacks separated by bamboo verticals',
+ 'mountain-and-temple silhouette repeats with stylized scholar-cloud forms',
+];
+function composeChinoiseriePrompt(cw) {
+ const motif = CHINOISERIE_MOTIFS[Math.floor(Math.random() * CHINOISERIE_MOTIFS.length)];
+ return (
+ `Chinoiserie wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
+ `Motif: ${motif}, arranged in a tessellated seamless repeat. ` +
+ `Cole & Son Mandarin / Ming-dynasty woodblock register. ` + SHARED_ARTDIR
+ );
+}
+
+const TOILE_VIGNETTES = [
+ 'a pair of mirror-image country-cottage scenes flanking a central fountain',
+ 'symmetric pastoral courtship scene under arching elm-tree silhouettes',
+ 'paired equestrian-rider vignettes facing each other across a ribbon-tied bouquet',
+ 'symmetric harvest-basket scene with sheaves of wheat and hand-tools',
+];
+function composeToilePrompt(cw) {
+ const vig = TOILE_VIGNETTES[Math.floor(Math.random() * TOILE_VIGNETTES.length)];
+ return (
+ `Toile de Jouy wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink line-engraving. ` +
+ `Vignette: ${vig}, repeated as a seamless symmetric tile, every figure rendered in fine-line copperplate-engraving style with cross-hatched shading replaced by solid-flat figure tone. ` +
+ `Pierre-Frey / Manuel Canovas archival toile register. ` + SHARED_ARTDIR
+ );
+}
+
+const STRIPE_STYLES = [
+ 'wide-narrow alternating pinstripe (2cm thick / 4cm gap), perfectly vertical, hard edges',
+ 'awning stripe — uniform 6cm vertical bands alternating ground and figure tones',
+ 'beaded vertical lines with small ornamental dot-clusters between stripes',
+ 'fine triple-pinstripe groups separated by wider blank channels',
+];
+function composeStripePrompt(cw) {
+ const style = STRIPE_STYLES[Math.floor(Math.random() * STRIPE_STYLES.length)];
+ return (
+ `Vertical stripe wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure stripes: bone-ivory (#F2EADB) single-ink. ` +
+ `Pattern: ${style}. ` +
+ `Robert Kime / Colefax & Fowler archival stripe register. ` + SHARED_ARTDIR
+ );
+}
+
+const QUATREFOIL_FIELDS = [
+ 'classical quatrefoil tessellation, each quatrefoil oriented identically, edge-to-edge tight grid',
+ 'gothic-window quatrefoil-plus-circle interlock, symmetric 4-fold',
+ 'moorish-tile quatrefoil with negative-space cross channels',
+ 'ogee-quatrefoil hybrid — pointed-arch ogee crossed with classical quatrefoil',
+];
+function composeQuatrefoilPrompt(cw) {
+ const field = QUATREFOIL_FIELDS[Math.floor(Math.random() * QUATREFOIL_FIELDS.length)];
+ return (
+ `Quatrefoil / ogee geometric wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
+ `Field: ${field}. ` +
+ `Owen Jones Grammar of Ornament register, strict mathematical tessellation. ` + SHARED_ARTDIR
+ );
+}
+
+const DIAMOND_TRELLIS_FIELDS = [
+ 'narrow ogival diamond lattice with empty centers (just the lattice lines)',
+ 'diamond trellis with a single tiny center-medallion in each diamond',
+ 'crossed-ribbon trellis forming diamond cells with ornamental ribbon-knot intersections',
+ 'fine-line diamond lattice with classical tassels at each lattice node',
+];
+function composeDiamondTrellisPrompt(cw) {
+ const field = DIAMOND_TRELLIS_FIELDS[Math.floor(Math.random() * DIAMOND_TRELLIS_FIELDS.length)];
+ return (
+ `Diamond trellis wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink line-trellis. ` +
+ `Field: ${field}. NOT a damask, NOT a heavy medallion field — light open trellis only. ` +
+ `Soane Britain / De Gournay archival trellis register. ` + SHARED_ARTDIR
+ );
+}
+
+// ── Track H: DRUNK ZOO 36" (Steve-approved 2026-05-20) ──────────────
+// Replaces the giraffe-only track with a broader symmetric-zoo track.
+// 36" repeat — wall-scale large pattern. Each design = one symmetric
+// paired-animal vignette in 19c-engraving aesthetic.
+const ZOO_VIGNETTES = [
+ 'two reticulated giraffes facing each other in symmetric profile, necks crossing in a heart-shape, both sipping champagne coupes',
+ 'pair of mirror-image giraffes flanking a central acacia tree, both holding cocktail glasses',
+ 'mother-and-calf giraffe pair, mother arching over the calf, both with bottles',
+ 'pair of orangutans in symmetric profile facing each other across a magnum bottle of champagne, hand-engraved',
+ 'two sloths hanging in mirror-image from a central acacia branch, each with a cocktail glass',
+ 'pair of red pandas in symmetric grooming pose, central bottle between them',
+ 'two lemurs in symmetric profile leaping toward each other over a central palm-frond-free fern',
+ 'pair of capybaras in symmetric resting pose, central mint julep glass between them',
+ 'two African elephant calves in symmetric trunk-raised pose, central wine glass',
+ 'pair of macaws in symmetric perched pose (no flight), central long-stemmed coupe between them',
+ 'two tipsy frogs in symmetric leaping pose mirrored, central decanter',
+];
+function composeDrunkZoo36Prompt(cw) {
+ const vig = ZOO_VIGNETTES[Math.floor(Math.random() * ZOO_VIGNETTES.length)];
+ return (
+ `Drunk Zoo 36" repeat wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
+ `Vignette: ${vig}. Renders at wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, generous breathing room of clean ground around each motif. ` +
+ `Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink — no interior shading, no anatomical line-detail, no spots, no stripes, no fur texture. Spots/stripes appear ONLY as cut-out negative shapes in the silhouette (revealing ground tone), never as a second ink color. ` +
+ SHARED_ARTDIR
+ );
+}
+
+// ── Track I: FACE-SKULL DAMASK (Steve directive 2026-05-20) ──────────
+// Damask is purged unless it has faces or skulls. This track generates new
+// damask designs where the medallion FILL is a hand-engraved face or skull
+// rendered in single-ink screen-print style.
+const FACE_SKULL_MOTIFS = [
+ 'memento-mori skull at the center of each ogival damask medallion, crossed bones forming the lattice spines',
+ 'classical-bust face profile centered in each damask medallion, hair flowing into the medallion borders',
+ 'death\'s-head moth skull pattern at each damask cell, wings forming the connecting flourishes',
+ 'Venetian carnival mask face filling each damask medallion, ribbon-ties forming the lattice',
+ 'Day-of-the-Dead calavera skull centered in each damask medallion with marigold-laurel surrounds',
+ 'Greek-tragedy theatre-mask face (one comedy / one tragedy alternating) inside each medallion',
+ 'anatomical-engraving skull profile at each medallion, scientific-illustration crosshatch replaced by solid flat ink',
+ 'phantom-of-the-opera half-mask face at each medallion, opera-cape ribbons forming the lattice',
+];
+function composeFaceSkullDamaskPrompt(cw) {
+ const motif = FACE_SKULL_MOTIFS[Math.floor(Math.random() * FACE_SKULL_MOTIFS.length)];
+ return (
+ `Face-Skull Damask — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
+ `Composition: classic ogival damask lattice, but the medallion FILL is ${motif}. ` +
+ `Strict bilateral symmetry — every medallion is symmetric, lattice is symmetric. ` +
+ `Each face/skull/lattice element is one FLAT STENCIL SILHOUETTE in bone-ivory ink. NO interior crosshatching, NO engraved line shading, NO 3D modeling of the skull/face. Pure cut-out silhouette only. ` +
+ SHARED_ARTDIR
+ );
+}
+
+// ── Settlement check (inline — same logic as src/fliepaper-bugs.js) ──
+function settlementOK(prompt) {
+ let p = String(prompt || '').toLowerCase();
+ // Strip negatives so anti-prompts don't false-trigger
+ p = p
+ .replace(/\b(?:no|not|without|excluding|never|avoid)\b[^.,;]*(?:banana|bird|butterfl|grape|leaf|leaves|frond|palm|foliage|tropical|fluoresc|neon|rainbow)[^.,;]*[.,;]?/g, ' ')
+ .replace(/\bwithout\b[^.,;]*[.,;]/g, ' ')
+ .replace(/\bexcluding\b[^.,;]*[.,;]/g, ' ');
+ // Part B specific elements (affirmative only)
+ const partB = [
+ /\bbanana(?:s|\s+pods?)?\b/,
+ /\bplantains?\b/,
+ /\bgrapes?\b/,
+ /\bbirds?\b/,
+ /\bbutterfl(?:y|ies)\b/,
+ ];
+ for (const rx of partB) if (rx.test(p)) return false;
+ // Foliage = needs review (we still allow, the figure is single-ink)
+ return true;
+}
+
+// ── Track mix — Steve flagged "needs alot more variety in patterns, way
+// too much damasks" 2026-05-20. Six tracks, equal weight:
+// cactus — desert botanical (Sonoran luxury)
+// chinoiserie — Cole&Son Mandarin/Ming-style
+// toile-de-jouy — classic French pastoral one-color
+// stripe-block — vertical stripe / pinstripe / awning stripe
+// quatrefoil-ogee — geometric tessellation (non-damask)
+// diamond-trellis — narrow ogival lattice (lighter than damask)
+// drunk-animals-designer paused — see feedback_no_modern_bending_designs.
+// 2026-05-20 — top-selling interior-designer styles, ALL at 36" repeat:
+// chinoiserie — Cole & Son / de Gournay perennial best-seller
+// stripe — Colefax & Fowler ticking stripe (designer classic)
+// cactus — Steve's house collection (Sonoran luxury)
+// drunk-zoo-36 — symmetric paired animals (giraffes lead, sloths/lemurs/
+// panda/capybara/elephant added), 19c-engraving register
+// 2026-05-20 Steve directives:
+// - Add fromental-scenic (emulate Fromental.com elegance, NOT copy)
+// - Add throttled-plaster-roses ("throwled plaster look with roses")
+// - Thibaut is the high-end LLM crisp reference (NEVER York/Brewster/Marburg)
+const FROMENTAL_SCENES = [
+ 'flowering magnolia branches reaching up the panel with peony blossoms at branch nodes',
+ 'cascading wisteria vines hanging in symmetric clusters with stylized vine leaves',
+ 'cherry-blossom branches arching across the panel with falling petal motifs',
+ 'climbing rose vines on bamboo-trellis armature with full open rose heads at intersections',
+ 'flowering plum branches in winter-Asian-painting register with sparse blossoms',
+];
+function composeFromentalScenicPrompt(cw) {
+ const scene = FROMENTAL_SCENES[Math.floor(Math.random() * FROMENTAL_SCENES.length)];
+ return (
+ `Hand-painted scenic chinoiserie wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}) silk register. Figure: bone-ivory (#F2EADB) single-ink. ` +
+ `Scene: ${scene}, arranged as a seamless symmetric tile — each branch repeats with bilateral mirror symmetry. ` +
+ `Thibaut-grade archival register, hand-painted silk-panel aesthetic in the spirit of de Gournay or Fromental atelier work (emulate the elegance, do not copy any specific design). ` +
+ `Strictly no birds, no insects, no human figures. Branches and flowers only. ` + SHARED_ARTDIR
+ );
+}
+
+const PLASTER_ROSE_STYLES = [
+ 'full-open garden roses in symmetric pairs with their stems crossing at the panel center',
+ 'climbing-rose vine in vertical repeats with rose heads at every node and serrated leaves',
+ 'damask-style rose medallions — single large rose head encircled by a wreath of smaller buds',
+ 'rose-and-thorn-branch tessellation, each rose head identical, edge-to-edge tight grid',
+];
+function composeThrottledPlasterRosesPrompt(cw) {
+ const style = PLASTER_ROSE_STYLES[Math.floor(Math.random() * PLASTER_ROSE_STYLES.length)];
+ return (
+ `Trowelled-plaster-wall wallpaper with roses — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}) with a subtle warm-cream (#F2EADB) secondary plaster-mottle TONE — rendered as flat zones with hard edges, NOT a gradient. ` +
+ `Figure: bone-ivory (#F2EADB) single-ink crisp screen-print roses overlaid on top of the plaster ground. ` +
+ `Rose style: ${style}. ` +
+ `Venetian trowelled-plaster register, hand-troweled artisanal wall finish with crisp painted roses — Carlisle & Company / Thibaut high-end archival baseline. ` + SHARED_ARTDIR
+ );
+}
+
+// ── MUYBRIDGE — sequential motion-study plates as wallpaper ──────────
+// Eadweard Muybridge "Animal Locomotion" (1887, public domain) — ~780 plates,
+// each one a tiled grid of frozen gait poses of a single subject. The plate
+// FORMAT is already a perfect wallpaper repeat: 12-24 small images tiled in
+// a grid showing the full motion cycle. Steve directive 2026-05-20.
+const MUYBRIDGE_SUBJECTS = [
+ 'horse galloping — sequence of 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full gait cycle, tiled left-to-right in three rows of four',
+ 'horse trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule tiled in a 4x4 grid',
+ 'racehorse with jockey — 12 frozen pose sequence showing the full stride',
+ 'leaping greyhound — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule, dog mid-leap, tiled side-by-side',
+ 'galloping dog — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule in a 4x4 grid',
+ 'mastiff trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in 3 rows',
+ 'cat walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle, each panel separated by a thin hairline rule showing the saunter, tiled 4x4',
+ 'cat leaping from rock to rock — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule',
+ 'lion pacing — 12 frozen pose sequence tiled in a strip',
+ 'tiger walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
+ 'elephant walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the heavy gait, tiled 3x4',
+ 'ostrich running — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
+ 'kangaroo hopping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full bound, tiled 3x4',
+ 'pig trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 4x3 grid',
+ 'goat walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
+ 'donkey trotting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
+ 'camel walking — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the long stride, tiled 3x4',
+ 'rabbit hopping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full bound',
+ 'deer leaping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in a strip',
+ 'bison charging — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
+ 'rooster strutting — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
+ 'cockatoo flapping its wings — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
+ 'pigeon taking flight — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full wingbeat',
+ 'human walking — Muybridge male figure in a loincloth, 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
+ 'human descending stairs — woman in a Victorian dress, 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled left-to-right',
+ 'human running — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
+ 'human jumping — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full leap',
+ 'human throwing a discus — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled 3x4',
+ 'human ascending stairs — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule left-to-right',
+ 'human carrying a heavy bucket — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the strained walk',
+ 'human dancing a waltz — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule in a 3x4 grid',
+ 'human waving a flag — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled side-by-side',
+ 'human boxing — two figures in 12 frozen pose sequence, tiled 3x4',
+ 'human wrestling — two figures locked together in 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule',
+ 'baby crawling — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule tiled in a 3x4 grid',
+ 'human hammering a nail — 4 distinct frozen poses arranged as a 4-panel triptych — horizontal 1x4 strip composition showing the full motion cycle progression left-to-right, each panel separated by a thin hairline rule showing the full swing',
+];
+function composeMuybridgePlatePrompt(cw) {
+ const subj = MUYBRIDGE_SUBJECTS[Math.floor(Math.random() * MUYBRIDGE_SUBJECTS.length)];
+ return (
+ `Eadweard Muybridge motion-study plate as wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
+ `Subject: ${subj}. ` +
+ `Each pose is a flat stencil silhouette in single ink on the solid ground — no chiaroscuro, no shading, no anatomical detail inside. ` +
+ `Render as a SEAMLESS REPEAT — the full plate is one tile, edge-to-edge. ` +
+ `Reference: Muybridge "Animal Locomotion" 1887, plate-format, single-color blueprint-style serigraph treatment. NOT photographic, NOT engraved — flat stencil only. ` +
+ SHARED_ARTDIR
+ );
+}
+
+// ── LIPS — Steve directive 2026-05-20. 4-color max, sparse iconic lips on
+// generous ground. Inspired by Warhol Marilyn, Lichtenstein, Patrick Nagel,
+// matchbook covers.
+const LIPS_SUBJECTS = [
+ 'one large parted red-velvet lips silhouette centered on the tile, no face, no teeth — just the lips. 80% empty cream ground around it',
+ 'a single perfect lipstick-kiss imprint (blot mark) on the upper-third of the tile in scarlet, otherwise empty cream ground',
+ 'two lips facing each other almost touching (mid-kiss silhouette), one in deep red and one in cream-outline, lots of empty ground around them',
+ 'pursed pouting lips in profile silhouette, scarlet red, no other elements, vast clean ground',
+ 'lips biting a single stem of a tiny rose — minimalist Patrick-Nagel silhouette, two-tone (red lips, black rose silhouette, cream ground)',
+ 'classic Andy Warhol style pop-art lips — one giant pair of half-open red lips with a thin black outline, 4 colors max (red, black, cream, optional cream highlight on lower lip)',
+];
+
+function composeLipsPrompt(subject, cw) {
+ // For lips: ground=cream/ivory, figure=scarlet red. Override colorway.
+ return (
+ `Lips wallpaper — minimalist pop-art register. ` +
+ `Ground: solid cream-ivory (#F2EADB). Figure: deep scarlet red (#B0282A). ` +
+ `Subject: ${subject}. ` +
+ `Up to 4 colors maximum (cream + scarlet + optional black outline + optional cream highlight on lower lip). NO bleed. NO halftone. NO gradient. ` +
+ `Reference: Andy Warhol pop-art lips, Patrick Nagel, vintage matchbook cover, single-pass screen-print. ` +
+ SHARED_ARTDIR
+ );
+}
+
+// ── MONTEREY WEST-COAST MURAL — Steve directive 2026-05-20.
+// 6 scene subjects × 4 seasonal colorways = 24 base designs. Each is a single
+// 36"-wide × 132"-tall (11ft) panel intended to be the FIRST in a multi-panel
+// mural set. Admin can later "+next panel" to extend the scene rightward
+// across the wall (via /api/design/:id/add-next-panel).
+const MONTEREY_SUBJECTS = [
+ 'Lone Cypress on Pebble Beach 17-Mile Drive — a single windswept Monterey cypress silhouetted on a granite headland with the Pacific behind, hand-painted scenic register, de Gournay aesthetic',
+ 'Cannery Row at low tide — silhouettes of the historic canneries on pilings over kelp-strewn rocks, fog ribbons in the upper third',
+ 'Point Pinos Lighthouse at dusk — the oldest continuously-operating lighthouse on the West Coast with cypress hedge and bluff in foreground',
+ 'Carmel Mission Basilica — adobe bell tower silhouette with rose-garden hedges and ancient olive trees',
+ 'Big Sur Bixby Creek Bridge silhouette — arched span over the canyon mouth with the Pacific opening to the west, redwoods on the inland slope',
+ 'Monterey Bay kelp-forest above-and-below cross-section — sea-otters on the surface, kelp fronds rising from the seabed, single-layer silhouette only',
+];
+
+const SEASONAL_COLORWAYS = [
+ { season: 'spring', name: 'Spring Bloom', ground: '#A8B79A', figure: '#F2EADB', tone: 'sage-mint' },
+ { season: 'summer', name: 'Summer Surf', ground: '#3E6B82', figure: '#F2EADB', tone: 'bay-blue' },
+ { season: 'fall', name: 'Autumn Saddle', ground: '#7A4A28', figure: '#F2EADB', tone: 'cypress-burnt-umber' },
+ { season: 'winter', name: 'Winter Fog', ground: '#9CA09B', figure: '#3A2A1C', tone: 'pacific-mist' },
+];
+
+function composeMontereyMuralPrompt(subject, cw) {
+ return (
+ `Monterey, California west-coast mural panel — ${subject}. ` +
+ `${cw.season.charAt(0).toUpperCase() + cw.season.slice(1)} colorway: ${cw.name}. Ground: solid ${cw.tone} (${cw.ground}). Figure: ${cw.figure} single-ink. ` +
+ `Composition: panoramic vertical panel, motif anchored center-bottom, sky/atmosphere extending up the panel (still in solid ground tone, NOT a sky gradient), generous breathing room. ` +
+ `This is panel 1 of a multi-panel mural set — leave the right edge open and scene-continuous so additional panels can extend the scene to the right. ` +
+ `Reference: de Gournay scenic panel, hand-painted silk register, NOT Haeckel, NOT Audubon. Single-layer stencil silhouette only. ` +
+ SHARED_ARTDIR
+ );
+}
+
+// Tall panel kind — 36" wide × 132" tall (11ft). Different from the seamless_tile
+// kind which is square. Panel kind is meant to hang as-is, not tile-repeat.
+const PANEL_W = 36;
+const PANEL_H = 132;
+
+// ── DESIGNER-ZOO-CALM — Steve directive 2026-05-20 ("love this theme")
+// Inspired by accepted design 10492. Forest-green ground + bone-ivory animal
+// silhouettes, dignified standing/sitting poses (not staggering), sparse
+// fern/grass botanical fillers between animals, Cole-Son/Sister-Parish energy.
+const ZOO_CALM_ANIMALS = [
+ 'pair of red pandas in symmetric grooming pose',
+ 'bear seated upright in a meditative pose, head slightly tilted',
+ 'a tall standing fox in profile, ears alert',
+ 'pair of foxes mirrored facing each other, tails curving outward',
+ 'family of three lemurs perched in a vertical stack',
+ 'pair of standing wolves facing forward, dignified',
+ 'mountain lion seated in regal posture, tail curled',
+ 'pair of badgers facing each other in symmetric pose',
+ 'standing pangolin in profile, scales catching highlight as cut-outs',
+ 'pair of capybaras seated side-by-side',
+ 'tall standing crane with neck curved (no flying — standing pose only)',
+ 'pair of forest deer with antlers, mirrored facing inward',
+ 'standing fennec fox with oversized ears, alert pose',
+ 'pair of stoats standing upright, paws raised',
+ 'standing armadillo in profile, shell silhouette clean',
+ 'lounging snow leopard with paws extended',
+];
+const ZOO_CALM_FILLERS = [
+ 'sparse fern fronds rising between the animals',
+ 'tall slender grass clusters in negative space between motifs',
+ 'small wildflower clusters between motifs',
+ 'pinecone-and-needle sprigs',
+ 'wild-rose stems with single open blossoms',
+ 'tall cattails between figures',
+ 'sparse oak-leaf clusters in negative space',
+ 'olive branches between motifs',
+];
+const ZOO_CALM_PALETTE = [
+ { name: 'Forest Loden', tone: 'deep forest green', hex: '#3C4844' },
+ { name: 'Hunter Olive', tone: 'hunter olive', hex: '#48543A' },
+ { name: 'Bottle Green', tone: 'bottle-green', hex: '#2E4538' },
+ { name: 'Pine Charcoal', tone: 'pine charcoal', hex: '#3A4A45' },
+ { name: 'Moss Slate', tone: 'moss slate', hex: '#566554' },
+ { name: 'Ink Navy', tone: 'ink navy', hex: '#1F2B3A' },
+];
+function composeDesignerZooCalmPrompt() {
+ const animal = ZOO_CALM_ANIMALS[Math.floor(Math.random() * ZOO_CALM_ANIMALS.length)];
+ const filler = ZOO_CALM_FILLERS[Math.floor(Math.random() * ZOO_CALM_FILLERS.length)];
+ const cw = ZOO_CALM_PALETTE[Math.floor(Math.random() * ZOO_CALM_PALETTE.length)];
+ return (
+ `Designer Wallcoverings zoo wallpaper — ${cw.name} colorway. Ground: solid ${cw.tone} (${cw.hex}). Figure: bone-ivory (#F2EADB) single-ink. ` +
+ `Vignette: ${animal}. Renders at wall-scale 36-inch seamless tile. Animals are LARGE, anchored on the central vertical axis of symmetry, dignified standing or seated pose (NOT staggering, NOT falling, NOT mid-motion). ` +
+ `Between the animals: ${filler}, kept sparse — generous breathing room of clean ground tone visible throughout. ` +
+ `Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink. NO interior shading, NO anatomical detail, NO fur texture. Spots/stripes/markings appear ONLY as cut-out negative shapes in the silhouette (revealing ground tone), never as a second ink color. ` +
+ `Reference: Cole & Son archival, Sister Parish, Designer Wallcoverings house style. Refined, restrained, designer-grade. ` +
+ SHARED_ARTDIR
+ );
+}
+
+const TRACKS = [
+ // Weighted — designer-zoo-calm appears MORE often per Steve "do more" of 10492
+ 'designer-zoo-calm','designer-zoo-calm','designer-zoo-calm','designer-zoo-calm',
+ 'chinoiserie','stripe','cactus','drunk-zoo-36','face-skull-damask',
+ 'fromental-scenic','throttled-plaster-roses','muybridge-plate','monterey-mural',
+];
+
+// Variable repeat size per Steve directive 2026-05-20: vary 24"/36"/54" so
+// designs render at different motif scales (small/medium/large wall coverage).
+const REPEAT_SIZES = [24, 36, 36, 36, 54];
+function pickRepeatSize() {
+ return REPEAT_SIZES[Math.floor(Math.random() * REPEAT_SIZES.length)];
+}
+const REPEAT_INCHES = 36; // wall-scale repeat — Steve directive 2026-05-20
+function nextTrack() {
+ return TRACKS[Math.floor(Math.random() * TRACKS.length)];
+}
+
+// ── Stop conditions ──────────────────────────────────────────────────
+function shouldStop(state) {
+ if (fs.existsSync(HALT_FLAG)) {
+ logLine('HALT — flag file present at ' + HALT_FLAG);
+ return true;
+ }
+ if (state.spentUsd >= BUDGET_CAP_USD) {
+ logLine(`HALT — budget cap reached ($${state.spentUsd.toFixed(2)} >= $${BUDGET_CAP_USD})`);
+ return true;
+ }
+ if (Date.now() >= STOP_DEADLINE.getTime()) {
+ logLine(`HALT — stop deadline reached (${new Date().toLocaleString()} past ${STOP_DEADLINE.toLocaleString()})`);
+ return true;
+ }
+ return false;
+}
+
+// ── Generate one design ──────────────────────────────────────────────
+function generateOne(track, cw) {
+ let prompt;
+ switch (track) {
+ case 'cactus': prompt = composeCactusPrompt(cw); break;
+ case 'chinoiserie': prompt = composeChinoiseriePrompt(cw); break;
+ case 'toile': prompt = composeToilePrompt(cw); break;
+ case 'stripe': prompt = composeStripePrompt(cw); break;
+ case 'quatrefoil': prompt = composeQuatrefoilPrompt(cw); break;
+ case 'diamond-trellis': prompt = composeDiamondTrellisPrompt(cw); break;
+ case 'drunk-zoo-36': prompt = composeDrunkZoo36Prompt(cw); break;
+ case 'face-skull-damask': prompt = composeFaceSkullDamaskPrompt(cw); break;
+ case 'fromental-scenic': prompt = composeFromentalScenicPrompt(cw); break;
+ case 'throttled-plaster-roses': prompt = composeThrottledPlasterRosesPrompt(cw); break;
+ case 'muybridge-plate': prompt = composeMuybridgePlatePrompt(cw); break;
+ case 'designer-zoo-calm': prompt = composeDesignerZooCalmPrompt(); break;
+ case 'monterey-mural': {
+ const subj = MONTEREY_SUBJECTS[Math.floor(Math.random() * MONTEREY_SUBJECTS.length)];
+ const season = SEASONAL_COLORWAYS[Math.floor(Math.random() * SEASONAL_COLORWAYS.length)];
+ // Override the colorway with the seasonal one for this track
+ cw = { name: season.name, hex: season.ground, tone: season.tone, slug: season.season };
+ prompt = composeMontereyMuralPrompt(subj, season);
+ break;
+ }
+ default: prompt = composeCactusPrompt(cw);
+ }
+ if (!settlementOK(prompt)) {
+ logLine(`SKIP settlement — track=${track} cw=${cw.slug}`);
+ return { ok: false, settlementBlock: true };
+ }
+ // Each track inserts into its own category for sortability + variety.
+ const category = track === 'cactus' ? 'cactus'
+ : track === 'chinoiserie' ? 'chinoiserie'
+ : track === 'toile' ? 'toile-de-jouy'
+ : track === 'stripe' ? 'stripe'
+ : track === 'quatrefoil' ? 'quatrefoil-ogee'
+ : track === 'diamond-trellis' ? 'diamond-trellis'
+ : track === 'drunk-zoo-36' ? 'drunk-zoo-36'
+ : track === 'face-skull-damask' ? 'face-skull-damask'
+ : track === 'fromental-scenic' ? 'fromental-scenic'
+ : track === 'throttled-plaster-roses' ? 'throttled-plaster-roses'
+ : track === 'muybridge-plate' ? 'muybridge-plate'
+ : track === 'monterey-mural' ? 'monterey-mural'
+ : track === 'designer-zoo-calm' ? 'designer-zoo-calm'
+ : 'drunk-animals-designer';
+ // Mural panels render at 36"×132" (11ft); everything else at square tiles
+ // with variable size per Steve 2026-05-20 (24/36/54).
+ const isPanel = (track === 'monterey-mural');
+ const w = isPanel ? PANEL_W : pickRepeatSize();
+ const h = isPanel ? PANEL_H : w;
+ const kind = isPanel ? 'mural_panel' : 'seamless_tile';
+ const r = spawnSync('node', [
+ path.join(__dirname, 'generate_designs.js'),
+ '--n', '1',
+ '--kind', kind,
+ '--width', String(w),
+ '--height', String(h),
+ '--category', category,
+ '--prompts', prompt,
+ ], { cwd: ROOT, encoding: 'utf8', env: process.env });
+
+ if (r.status === 0) {
+ // generate_designs.js prints " ✓ #<id> ·" per-design and "IDs: <id>,..." summary
+ const summary = (r.stdout || '').match(/IDs:\s*(\d+(?:,\s*\d+)*)/);
+ const perLine = (r.stdout || '').match(/✓\s*#(\d+)/);
+ const idStr = summary ? summary[1].split(/,\s*/)[0] : (perLine ? perLine[1] : null);
+ const id = idStr ? parseInt(idStr, 10) : null;
+ // POST-PROCESS: force 2-tone quantize on the output PNG. Eliminates bleed
+ // between layers (Steve directive 2026-05-20 — looks cheap). The script
+ // backs up the original to <file>.original.png before overwriting.
+ if (id) {
+ try {
+ // psqlAt returns tuples-only, no header/footer — clean single-value reads
+ const lp = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "SELECT local_path FROM spoon_all_designs WHERE id=${id};"`, { encoding: 'utf8' }).trim();
+ if (lp && fs.existsSync(lp)) {
+ const q = spawnSync('python3', [path.join(__dirname, 'quantize-2tone.py'), lp], { encoding: 'utf8' });
+ if (q.status === 0) {
+ logLine(` 2tone OK id=${id}: ${(q.stdout || '').trim().split('->')[1] || '?'}`);
+ // COLORWAY-VARIANTS — for every 2-toned design, generate 3 free
+ // recolors. Steve directive 2026-05-20. Each variant becomes a
+ // child row in spoon_all_designs with parent_design_id=id.
+ // Skip for mural panels (they're meant to be a multi-panel set,
+ // colorway variation happens at the source-prompt level there).
+ if (!isPanel) {
+ try {
+ const cwOut = spawnSync('python3', [path.join(__dirname, 'colorway-variants.py'), lp], { encoding: 'utf8' });
+ if (cwOut.status === 0) {
+ const parsed = JSON.parse((cwOut.stdout || '').trim().split('\n').pop() || '{}');
+ const variants = parsed.variants || [];
+ for (const v of variants) {
+ if (!fs.existsSync(v.path)) continue;
+ const cwTitle = `${category} · ${v.slug}`;
+ // Single-round-trip CTE — INSERT the colorway child with
+ // PLACEHOLDER image_url (we don't know its id yet), then
+ // immediately UPDATE that same row to use its assigned id.
+ // Prevents the recurring "9k rows stuck at PLACEHOLDER"
+ // bug that left those designs invisible on /designs because
+ // designHasImage's regex requires /by-id/\d+ (Steve fix,
+ // 2026-05-20 — see git log for the bulk PG correction).
+ const cwSql = `
+ WITH inserted AS (
+ INSERT INTO spoon_all_designs (brand, kind, width_in, height_in, panels, generator, prompt, image_url, local_path, dominant_hex, category, is_published, notes, parent_design_id, created_at)
+ SELECT brand, kind, width_in, height_in, panels, generator, prompt || E'\\n[colorway-variant of #${id}]', '/designs/img/by-id/PLACEHOLDER', '${v.path.replace(/'/g, "''")}', '${v.ground.replace(/'/g, "''")}', '${cwTitle.replace(/'/g, "''")}', TRUE, E'colorway variant: ${v.slug} (ground=${v.ground}, figure=${v.figure}) of parent #${id}', ${id}, NOW()
+ FROM spoon_all_designs WHERE id=${id}
+ RETURNING id
+ ),
+ fixed AS (
+ UPDATE spoon_all_designs s
+ SET image_url = '/designs/img/by-id/' || s.id::text
+ FROM inserted WHERE s.id = inserted.id
+ RETURNING s.id
+ )
+ SELECT id FROM fixed;`;
+ try {
+ const cwId = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "${cwSql.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { encoding: 'utf8' }).trim();
+ logLine(` cw child id=${cwId} (${v.slug}) of parent ${id}`);
+ } catch (e2) {
+ logLine(` cw INSERT FAIL ${v.slug}: ${e2.message.slice(0, 200)}`);
+ }
+ }
+ } else {
+ logLine(` cw FAIL id=${id}: ${(cwOut.stderr || cwOut.stdout || '').slice(0, 200)}`);
+ }
+ } catch (e3) {
+ logLine(` cw EXC id=${id}: ${e3.message.slice(0, 200)}`);
+ }
+ }
+ } else {
+ logLine(` 2tone FAIL id=${id}: ${(q.stderr || q.stdout || '').slice(0, 200)}`);
+ }
+ } else {
+ logLine(` 2tone SKIP id=${id}: local_path not found (${lp})`);
+ }
+ } catch (e) {
+ logLine(` 2tone EXC id=${id}: ${e.message.slice(0, 200)}`);
+ }
+ }
+ // Auto-publish so Steve sees it in /designs
+ if (id) {
+ try {
+ psql(`UPDATE spoon_all_designs SET is_published=TRUE WHERE id=${id};`);
+ } catch (e) {
+ logLine(`publish-flip FAIL id=${id}: ${e.message.slice(0, 120)}`);
+ }
+ }
+ return { ok: true, id, category, colorway: cw.slug };
+ } else {
+ return { ok: false, stderr: (r.stderr || r.stdout || '').slice(0, 200) };
+ }
+}
+
+// ── Main loop ────────────────────────────────────────────────────────
+(async () => {
+ if (fs.existsSync(HALT_FLAG)) {
+ logLine('ABORT — halt flag already exists at startup. Remove it first.');
+ process.exit(1);
+ }
+ const state = { iterations: 0, ok: 0, fail: 0, settlementSkipped: 0, spentUsd: 0, startedAt: nowIso() };
+ logLine(`START night-builder · budget=$${BUDGET_CAP_USD} stop=${STOP_DEADLINE.toLocaleString()} cooldown=${COOLDOWN_MS}ms`);
+ logLine(`Track mix: 30% cactus / 70% drunk-animals-designer · ${MUTED_PALETTE.length} muted colorways`);
+
+ while (!shouldStop(state)) {
+ state.iterations++;
+ const track = nextTrack();
+ const cw = pickColorway();
+ const r = generateOne(track, cw);
+ state.spentUsd += PER_IMAGE_USD;
+ if (r.settlementBlock) {
+ state.settlementSkipped++;
+ } else if (r.ok) {
+ state.ok++;
+ logLine(`OK #${state.iterations} ${track}/${cw.slug} id=${r.id} (cumul: ok=${state.ok} fail=${state.fail} spent=$${state.spentUsd.toFixed(2)})`);
+ } else {
+ state.fail++;
+ logLine(`FAIL #${state.iterations} ${track}/${cw.slug}: ${r.stderr || 'unknown'}`);
+ // brief backoff on failure (rate-limit or transient)
+ await new Promise(rs => setTimeout(rs, 15000));
+ }
+ await new Promise(rs => setTimeout(rs, COOLDOWN_MS));
+ }
+
+ logLine(`STOP iterations=${state.iterations} ok=${state.ok} fail=${state.fail} settlementSkip=${state.settlementSkipped} spent=$${state.spentUsd.toFixed(2)}`);
+ process.exit(0);
+})();
← 1e6fc9a wallco: generate-lips.js — 10 designer lipstick-brand colorw
·
back to Wallco Ai
·
Bulk 2-color fix + full-room murals + hue-sync 4-color quart 2279beb →