[object Object]

← 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

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 →