[object Object]

← back to Pattern Vault

pipeline: add groundColor mode to gen_briefs.py (front-load flat ground color + dark-bg negative + guidance 9.5) — codifies the chartreuse fix; set on deco brief

5ab1439b1640b4a1c06028e643807270c6039499 · 2026-07-02 14:14:39 -0700 · Steve

Files touched

Diff

commit 5ab1439b1640b4a1c06028e643807270c6039499
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 2 14:14:39 2026 -0700

    pipeline: add groundColor mode to gen_briefs.py (front-load flat ground color + dark-bg negative + guidance 9.5) — codifies the chartreuse fix; set on deco brief
---
 data/trend-board.json                        |   2 +-
 scripts/gen_briefs.py                        |  20 +++-
 whimsical-compare/.port                      |   2 +-
 whimsical-compare/public/faces.html          |  63 ++++++++++++
 whimsical-compare/public/faces/face_01.png   | Bin 0 -> 2249684 bytes
 whimsical-compare/public/faces/face_02.png   | Bin 0 -> 2337934 bytes
 whimsical-compare/public/faces/face_03.png   | Bin 0 -> 2312167 bytes
 whimsical-compare/public/faces/face_04.png   | Bin 0 -> 2308220 bytes
 whimsical-compare/public/faces/face_05.png   | Bin 0 -> 2231480 bytes
 whimsical-compare/public/faces/face_06.png   | Bin 0 -> 2256768 bytes
 whimsical-compare/public/faces/face_07.png   | Bin 0 -> 2404168 bytes
 whimsical-compare/public/faces/face_08.png   | Bin 0 -> 2253159 bytes
 whimsical-compare/public/faces/face_09.png   | Bin 0 -> 2204017 bytes
 whimsical-compare/public/faces/face_10.png   | Bin 0 -> 2308672 bytes
 whimsical-compare/public/faces/face_11.png   | Bin 0 -> 2369006 bytes
 whimsical-compare/public/faces/face_12.png   | Bin 0 -> 2395694 bytes
 whimsical-compare/public/faces/face_13.png   | Bin 0 -> 2325680 bytes
 whimsical-compare/public/faces/face_14.png   | Bin 0 -> 2054004 bytes
 whimsical-compare/public/faces/face_15.png   | Bin 0 -> 2263619 bytes
 whimsical-compare/public/faces/face_16.png   | Bin 0 -> 2360211 bytes
 whimsical-compare/public/faces/face_17.png   | Bin 0 -> 2266646 bytes
 whimsical-compare/public/faces/face_18.png   | Bin 0 -> 2307006 bytes
 whimsical-compare/public/faces/face_19.png   | Bin 0 -> 2243095 bytes
 whimsical-compare/public/faces/face_20.png   | Bin 0 -> 2351661 bytes
 whimsical-compare/public/faces/manifest.json | 147 +++++++++++++++++++++++++++
 whimsical-compare/scripts/gen_faces.py       |  99 ++++++++++++++++++
 whimsical-compare/server.js                  |   8 +-
 27 files changed, 334 insertions(+), 7 deletions(-)

diff --git a/data/trend-board.json b/data/trend-board.json
index 0407186..d4e6f49 100644
--- a/data/trend-board.json
+++ b/data/trend-board.json
@@ -27,7 +27,7 @@
     { "id": "BRIEF-2026-animalprint-01", "lane": "Animal-skin print with a twist", "title": "Rosewood Cheetah", "prompt": "Original seamless repeat of stylised cheetah-rosette markings, hand-painted look, scattered on a warm plaster ground; skin-print abstraction only, no animal figures; rose-clay and cocoa spots on oatmeal.", "colorway": "Rose-clay + cocoa on oatmeal", "targetTier": "commercial_print $495", "settlement": "SAFE" },
     { "id": "BRIEF-2026-animalprint-02", "lane": "Animal-skin print with a twist", "title": "Verdant Tiger Stripe", "prompt": "Original painterly tiger-stripe skin-print abstraction (no animal figure) in forest and moss green on deep ink; brushy irregular hand-inked stripes reading as luxe textile.", "colorway": "Forest + moss on ink", "targetTier": "commercial_print $495", "settlement": "SAFE" },
     { "id": "BRIEF-2026-chinoiserie-sat-01", "lane": "Chinoiserie (saturated, no birds)", "title": "Nocturne Garden Chinoiserie", "prompt": "Original hand-painted-look chinoiserie of flowering branches, peonies and boughs on deep forest-green ground; gold + blush blooms. STRICTLY branches + flowers, NO birds, NO butterflies.", "colorway": "Gold + blush on forest green", "targetTier": "commercial_print $495", "settlement": "SAFE - no birds/butterflies" },
-    { "id": "BRIEF-2026-deco-01", "lane": "Chartreuse Art-Deco geometric", "title": "Gilded Fan Deco", "prompt": "Original Art-Deco geometric of repeating fan/sunburst arcs and stepped chevrons, metallic-gold linework on chartreuse ground; 1920s revival, crisp symmetry.", "colorway": "Metallic gold on chartreuse", "targetTier": "commercial_print $495", "settlement": "SAFE - abstract geometric" },
+    { "id": "BRIEF-2026-deco-01", "lane": "Chartreuse Art-Deco geometric", "title": "Gilded Fan Deco", "prompt": "Original Art-Deco geometric of repeating fan/sunburst arcs and stepped chevrons, metallic-gold linework, 1920s Gatsby revival, crisp symmetry, no plants no leaves no florals", "colorway": "Metallic gold on chartreuse", "groundColor": "#B0C000 chartreuse yellow-green", "targetTier": "commercial_print $495", "settlement": "SAFE - abstract geometric" },
     { "id": "BRIEF-2026-texture-01", "lane": "Tactile texture-effect", "title": "Linen Weave Plain", "prompt": "Original photoreal woven linen/grasscloth texture as seamless near-plain wallcovering; visible warp/weft slubs; warm greige; illusion-of-tactility.", "colorway": "Warm greige linen", "targetTier": "digital_single_use $149", "settlement": "SAFE - abstract texture" },
     { "id": "BRIEF-2026-ditsy-01", "lane": "Small-scale ditsy micro-print", "title": "Meadow Ditsy", "prompt": "Original tiny-scale ditsy floral micro-print, hand-drawn sprigs and dots evenly scattered on open cream ground; sized for small rooms; multiple colorways.", "colorway": "Sage + terracotta sprigs on bone", "targetTier": "digital_single_use $149", "settlement": "SAFE - non-directional sprigs, open ground" },
     { "id": "BRIEF-2026-mural-01", "lane": "Painterly scenic mural", "title": "Saturated Garden Panorama", "prompt": "Original full-wall painterly botanical garden panorama - layered ferns, hydrangea, foliage receding into mist on deep teal-green ground; fine-art scenery, single colorway. NO birds.", "colorway": "Deep teal-green garden", "targetTier": "mural commercial_print $795", "settlement": "SAFE - no birds/butterflies, non-tropical" },
diff --git a/scripts/gen_briefs.py b/scripts/gen_briefs.py
index 805f9a6..1336d70 100644
--- a/scripts/gen_briefs.py
+++ b/scripts/gen_briefs.py
@@ -20,6 +20,11 @@ NEG = ("birds, bird, butterfly, butterflies, bananas, banana leaves, grapes, gra
        "tropical monstera palm fronds, text, watermark, signature, logo, blurry, low quality, "
        "jpeg artifacts, seams, mismatched edges, frame, border, vignette")
 
+# Learned 2026-07-02: SDXL defaults grounds to muddy-dark and ignores a color named late in the
+# prompt. To force a ground color, front-load it as the FIRST token, negate dark backgrounds, and
+# bump guidance so the color binds. Add a "groundColor" field to a brief to trigger this mode.
+DARK_NEG = "black background, dark background, navy background, charcoal background, gradient background"
+
 def sdxl_prompt(b):
     base = b["prompt"]
     if b["id"] == "BRIEF-2026-mural-01":
@@ -28,7 +33,13 @@ def sdxl_prompt(b):
     else:
         tile = "seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering"
         w, h = 1024, 1024
-    return f"{base} {tile}. Colorway: {b['colorway']}.", w, h
+    ground = b.get("groundColor")
+    if ground:
+        # front-load the flat ground color; bump guidance; add dark-bg negative
+        prompt = (f"flat solid {ground} background color filling the entire canvas, "
+                  f"{base} {tile}. Colorway: {b['colorway']}.")
+        return prompt, w, h, 9.5, DARK_NEG
+    return f"{base} {tile}. Colorway: {b['colorway']}.", w, h, 7.5, ""
 
 UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
 def post(url, payload, extra_headers=None):
@@ -51,13 +62,14 @@ total_secs = 0.0
 seeds = [101, 202]  # 2 variants/brief
 
 for b in briefs:
-    prompt, w, h = sdxl_prompt(b)
+    prompt, w, h, guidance, extra_neg = sdxl_prompt(b)
+    neg = NEG + (", " + extra_neg if extra_neg else "")
     for i, seed in enumerate(seeds, 1):
         tag = f"{b['id']}_v{i}"
         payload = {"version": VERSION, "input": {
-            "prompt": prompt, "negative_prompt": NEG,
+            "prompt": prompt, "negative_prompt": neg,
             "width": w, "height": h, "num_inference_steps": 30,
-            "guidance_scale": 7.5, "seed": seed, "refine": "no_refiner"
+            "guidance_scale": guidance, "seed": seed, "refine": "no_refiner"
         }}
         ok = False
         for attempt in range(4):
diff --git a/whimsical-compare/.port b/whimsical-compare/.port
index 1b98715..8c9536e 100644
--- a/whimsical-compare/.port
+++ b/whimsical-compare/.port
@@ -1 +1 @@
-60218
\ No newline at end of file
+9793
\ No newline at end of file
diff --git a/whimsical-compare/public/faces.html b/whimsical-compare/public/faces.html
new file mode 100644
index 0000000..fd736d3
--- /dev/null
+++ b/whimsical-compare/public/faces.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Tema e Variazioni — Our Faces</title>
+<style>
+  :root { --cols: 4; }
+  * { box-sizing: border-box; }
+  body { margin: 0; font-family: -apple-system, "Segoe UI", Roboto, sans-serif; background: #f3f1ec; color: #1c1a17; }
+  header { position: sticky; top: 0; z-index: 5; background: #fdfcf9; border-bottom: 1px solid #e2dccf;
+    padding: 16px 24px; display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
+  header h1 { margin: 0; font-size: 20px; font-weight: 600; letter-spacing: .4px; }
+  header .sub { color: #8a8172; font-size: 12px; }
+  .controls { margin-left: auto; display: flex; align-items: center; gap: 14px; }
+  .controls label { font-size: 12px; color: #6b6355; display: flex; align-items: center; gap: 6px; }
+  .note { padding: 10px 24px; background: #fbf4e6; border-bottom: 1px solid #ecdfc4; font-size: 12px; color: #7a6a2f; }
+  .wrap { padding: 24px; }
+  .grid { display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: 20px; }
+  .tile { background: #fff; border: 1px solid #e2dccf; border-radius: 12px; overflow: hidden; }
+  .tile img { width: 100%; aspect-ratio: 1/1; object-fit: cover; display: block; background: #eee; }
+  .tile .cap { padding: 9px 12px; }
+  .tile .cap b { font-size: 13px; } .tile .cap .d { font-size: 11px; color: #8a8172; margin-top: 2px; }
+  .wallprev { margin: 8px 0 0; }
+  .pending { display: flex; align-items: center; justify-content: center; aspect-ratio: 1/1;
+    background: repeating-linear-gradient(45deg,#eee,#eee 10px,#e6e1d6 10px,#e6e1d6 20px); color: #999; font-size: 12px; }
+</style>
+</head>
+<body>
+<header>
+  <div><h1>Tema e Variazioni — <span style="color:#8a8172">Our Faces</span></h1>
+    <div class="sub">20 original b&amp;w portrait-medallion tiles · whimsical variations · our own face</div></div>
+  <div class="controls">
+    <label>Density <input type="range" id="density" min="2" max="6" value="4"></label>
+    <label><input type="checkbox" id="wall"> show as wall repeat</label>
+  </div>
+</header>
+<div class="note">Original homage to the <b>theme-and-variations portrait-plate format</b> — our own invented face and variations. Not a reproduction of Fornasetti's design or their model.</div>
+<div class="wrap"><div class="grid" id="grid"></div></div>
+<script>
+const $ = s => document.querySelector(s);
+let DATA = { designs: [] }, WALL = false;
+function tile(d) {
+  const img = d.file
+    ? (WALL ? `<div class="wallprev" style="aspect-ratio:1/1;background:url('${d.file}');background-size:33%;background-repeat:repeat"></div>`
+            : `<img loading="lazy" src="${d.file}" alt="${d.name}">`)
+    : `<div class="pending">rendering…</div>`;
+  return `<div class="tile">${img}<div class="cap"><b>${d.n}. ${d.name}</b><div class="d">${d.desc}</div></div></div>`;
+}
+function render() { $('#grid').innerHTML = DATA.designs.map(tile).join(''); }
+async function load() {
+  const r = await fetch('/api/faces'); DATA = await r.json();
+  // pad to 20 slots so pending tiles show
+  const have = new Set(DATA.designs.map(d => d.n));
+  for (let i = 1; i <= 20; i++) if (!have.has(i)) DATA.designs.push({ n: i, name: 'variation ' + i, desc: '', file: null });
+  DATA.designs.sort((a, b) => a.n - b.n); render();
+}
+$('#density').oninput = e => document.documentElement.style.setProperty('--cols', e.target.value);
+$('#wall').onchange = e => { WALL = e.target.checked; render(); };
+load(); setInterval(load, 12000);
+</script>
+</body>
+</html>
diff --git a/whimsical-compare/public/faces/face_01.png b/whimsical-compare/public/faces/face_01.png
new file mode 100644
index 0000000..568159e
Binary files /dev/null and b/whimsical-compare/public/faces/face_01.png differ
diff --git a/whimsical-compare/public/faces/face_02.png b/whimsical-compare/public/faces/face_02.png
new file mode 100644
index 0000000..9480903
Binary files /dev/null and b/whimsical-compare/public/faces/face_02.png differ
diff --git a/whimsical-compare/public/faces/face_03.png b/whimsical-compare/public/faces/face_03.png
new file mode 100644
index 0000000..f4dc39e
Binary files /dev/null and b/whimsical-compare/public/faces/face_03.png differ
diff --git a/whimsical-compare/public/faces/face_04.png b/whimsical-compare/public/faces/face_04.png
new file mode 100644
index 0000000..17d313a
Binary files /dev/null and b/whimsical-compare/public/faces/face_04.png differ
diff --git a/whimsical-compare/public/faces/face_05.png b/whimsical-compare/public/faces/face_05.png
new file mode 100644
index 0000000..59469aa
Binary files /dev/null and b/whimsical-compare/public/faces/face_05.png differ
diff --git a/whimsical-compare/public/faces/face_06.png b/whimsical-compare/public/faces/face_06.png
new file mode 100644
index 0000000..b330c3d
Binary files /dev/null and b/whimsical-compare/public/faces/face_06.png differ
diff --git a/whimsical-compare/public/faces/face_07.png b/whimsical-compare/public/faces/face_07.png
new file mode 100644
index 0000000..bd196ba
Binary files /dev/null and b/whimsical-compare/public/faces/face_07.png differ
diff --git a/whimsical-compare/public/faces/face_08.png b/whimsical-compare/public/faces/face_08.png
new file mode 100644
index 0000000..6443a8b
Binary files /dev/null and b/whimsical-compare/public/faces/face_08.png differ
diff --git a/whimsical-compare/public/faces/face_09.png b/whimsical-compare/public/faces/face_09.png
new file mode 100644
index 0000000..d1e14cb
Binary files /dev/null and b/whimsical-compare/public/faces/face_09.png differ
diff --git a/whimsical-compare/public/faces/face_10.png b/whimsical-compare/public/faces/face_10.png
new file mode 100644
index 0000000..49eef43
Binary files /dev/null and b/whimsical-compare/public/faces/face_10.png differ
diff --git a/whimsical-compare/public/faces/face_11.png b/whimsical-compare/public/faces/face_11.png
new file mode 100644
index 0000000..66824a7
Binary files /dev/null and b/whimsical-compare/public/faces/face_11.png differ
diff --git a/whimsical-compare/public/faces/face_12.png b/whimsical-compare/public/faces/face_12.png
new file mode 100644
index 0000000..b19de72
Binary files /dev/null and b/whimsical-compare/public/faces/face_12.png differ
diff --git a/whimsical-compare/public/faces/face_13.png b/whimsical-compare/public/faces/face_13.png
new file mode 100644
index 0000000..e2a8532
Binary files /dev/null and b/whimsical-compare/public/faces/face_13.png differ
diff --git a/whimsical-compare/public/faces/face_14.png b/whimsical-compare/public/faces/face_14.png
new file mode 100644
index 0000000..426fa76
Binary files /dev/null and b/whimsical-compare/public/faces/face_14.png differ
diff --git a/whimsical-compare/public/faces/face_15.png b/whimsical-compare/public/faces/face_15.png
new file mode 100644
index 0000000..7919e3e
Binary files /dev/null and b/whimsical-compare/public/faces/face_15.png differ
diff --git a/whimsical-compare/public/faces/face_16.png b/whimsical-compare/public/faces/face_16.png
new file mode 100644
index 0000000..b4b29be
Binary files /dev/null and b/whimsical-compare/public/faces/face_16.png differ
diff --git a/whimsical-compare/public/faces/face_17.png b/whimsical-compare/public/faces/face_17.png
new file mode 100644
index 0000000..fde43e0
Binary files /dev/null and b/whimsical-compare/public/faces/face_17.png differ
diff --git a/whimsical-compare/public/faces/face_18.png b/whimsical-compare/public/faces/face_18.png
new file mode 100644
index 0000000..937aa96
Binary files /dev/null and b/whimsical-compare/public/faces/face_18.png differ
diff --git a/whimsical-compare/public/faces/face_19.png b/whimsical-compare/public/faces/face_19.png
new file mode 100644
index 0000000..575d920
Binary files /dev/null and b/whimsical-compare/public/faces/face_19.png differ
diff --git a/whimsical-compare/public/faces/face_20.png b/whimsical-compare/public/faces/face_20.png
new file mode 100644
index 0000000..a6900c7
Binary files /dev/null and b/whimsical-compare/public/faces/face_20.png differ
diff --git a/whimsical-compare/public/faces/manifest.json b/whimsical-compare/public/faces/manifest.json
new file mode 100644
index 0000000..4ea5f03
--- /dev/null
+++ b/whimsical-compare/public/faces/manifest.json
@@ -0,0 +1,147 @@
+{
+  "title": "Tema e Variazioni \u2014 our faces",
+  "count": 20,
+  "est_cost_usd": 0.066,
+  "designs": [
+    {
+      "n": 1,
+      "name": "Mona Lisa",
+      "desc": "the face wearing a subtle Mona Lisa smile with a soft renaissance veil",
+      "file": "/faces/face_01.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: the face wearing a subtle Mona Lisa smile with a soft renaissance veil."
+    },
+    {
+      "n": 2,
+      "name": "Pipe Dream",
+      "desc": "a slender pipe held at the lips with a curl of smoke",
+      "file": "/faces/face_02.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: a slender pipe held at the lips with a curl of smoke."
+    },
+    {
+      "n": 3,
+      "name": "Monocle",
+      "desc": "a single monocle over one eye on a fine chain",
+      "file": "/faces/face_03.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: a single monocle over one eye on a fine chain."
+    },
+    {
+      "n": 4,
+      "name": "Crown",
+      "desc": "a delicate line-drawn crown resting on the hair",
+      "file": "/faces/face_04.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: a delicate line-drawn crown resting on the hair."
+    },
+    {
+      "n": 5,
+      "name": "Keyhole",
+      "desc": "a small keyhole motif where one eye would be, surreal",
+      "file": "/faces/face_05.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: a small keyhole motif where one eye would be, surreal."
+    },
+    {
+      "n": 6,
+      "name": "Playing Card",
+      "desc": "the portrait mirrored like a queen playing card, top and bottom",
+      "file": "/faces/face_06.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: the portrait mirrored like a queen playing card, top and bottom."
+    },
+    {
+      "n": 7,
+      "name": "Blindfold",
+      "desc": "a lace blindfold across the eyes, mysterious",
+      "file": "/faces/face_07.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: a lace blindfold across the eyes, mysterious."
+    },
+    {
+      "n": 8,
+      "name": "Teacup",
+      "desc": "balancing a tiny teacup and saucer on the crown of the head",
+      "file": "/faces/face_08.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: balancing a tiny teacup and saucer on the crown of the head."
+    },
+    {
+      "n": 9,
+      "name": "Clockface",
+      "desc": "a small clock face set into the cheek, surreal collage",
+      "file": "/faces/face_09.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: a small clock face set into the cheek, surreal collage."
+    },
+    {
+      "n": 10,
+      "name": "Moon Phase",
+      "desc": "crescent moons for earrings and stars scattered in the hair",
+      "file": "/faces/face_10.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: crescent moons for earrings and stars scattered in the hair."
+    },
+    {
+      "n": 11,
+      "name": "Pince-Nez",
+      "desc": "old-fashioned pince-nez spectacles perched on the nose",
+      "file": "/faces/face_11.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: old-fashioned pince-nez spectacles perched on the nose."
+    },
+    {
+      "n": 12,
+      "name": "Ribbon",
+      "desc": "an oversized bow ribbon tied at the chin",
+      "file": "/faces/face_12.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: an oversized bow ribbon tied at the chin."
+    },
+    {
+      "n": 13,
+      "name": "Birdcage Veil",
+      "desc": "a fine birdcage veil netting over the upper face",
+      "file": "/faces/face_13.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: a fine birdcage veil netting over the upper face."
+    },
+    {
+      "n": 14,
+      "name": "Pearl Choker",
+      "desc": "an ornate pearl-and-jewel choker at the throat",
+      "file": "/faces/face_14.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: an ornate pearl-and-jewel choker at the throat."
+    },
+    {
+      "n": 15,
+      "name": "Winking",
+      "desc": "one eye closed in a playful wink",
+      "file": "/faces/face_15.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: one eye closed in a playful wink."
+    },
+    {
+      "n": 16,
+      "name": "Feather Hat",
+      "desc": "a jaunty plumed hat tilted over the brow",
+      "file": "/faces/face_16.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: a jaunty plumed hat tilted over the brow."
+    },
+    {
+      "n": 17,
+      "name": "Mustache",
+      "desc": "a curled dapper mustache, gender-play whimsy",
+      "file": "/faces/face_17.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: a curled dapper mustache, gender-play whimsy."
+    },
+    {
+      "n": 18,
+      "name": "Veil of Lace",
+      "desc": "half the face dissolving into an intricate lace pattern",
+      "file": "/faces/face_18.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: half the face dissolving into an intricate lace pattern."
+    },
+    {
+      "n": 19,
+      "name": "Sun Rays",
+      "desc": "a halo of fine engraved sun rays behind the head",
+      "file": "/faces/face_19.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: a halo of fine engraved sun rays behind the head."
+    },
+    {
+      "n": 20,
+      "name": "Masquerade",
+      "desc": "an ornate venetian masquerade mask over the eyes",
+      "file": "/faces/face_20.png",
+      "prompt": "black and white vintage engraving wallpaper, a single elegant original woman's face inside a circular medallion plate, fine cross-hatch etching and stipple, monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations portrait-plate wallpaper, our own original face, not a copy of any existing design, seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering; variation: an ornate venetian masquerade mask over the eyes."
+    }
+  ]
+}
\ No newline at end of file
diff --git a/whimsical-compare/scripts/gen_faces.py b/whimsical-compare/scripts/gen_faces.py
new file mode 100644
index 0000000..85833bb
--- /dev/null
+++ b/whimsical-compare/scripts/gen_faces.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+"""
+"Tema e Variazioni" — 20 ORIGINAL face-medallion wallpaper tiles.
+
+Homage to the FORMAT of Fornasetti's black-and-white repeating portrait plates
+(theme-and-variations), rendered with our OWN invented face and our own whimsical
+variations — NOT a copy of Fornasetti's design and NOT their model (Lina Cavalieri).
+Original interpretation only.
+
+Each tile = a seamless repeating wallpaper of a single circular b&w engraved
+portrait medallion with one whimsical twist (pipe, monocle, Mona Lisa framing,
+crown, keyhole, playing-card, etc.).
+
+Replicate SDXL (~1c/img). Usage: python3 gen_faces.py [--n 20] [--seed-base N]
+"""
+import os, re, json, time, sys, urllib.request
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+ROOT = os.path.dirname(HERE)
+OUT = f"{ROOT}/public/faces"
+os.makedirs(OUT, exist_ok=True)
+
+def argv(f, d=None):
+    a = sys.argv; return a[a.index(f)+1] if f in a else d
+N = int(argv("--n", "20"))
+SEED_BASE = int(argv("--seed-base", str(int(time.time()) % 1000000)))
+
+tok = None
+with open(os.path.expanduser("~/Projects/secrets-manager/.env")) as f:
+    m = re.search(r"REPLICATE_API_TOKEN=([A-Za-z0-9_-]+)", f.read()); tok = m and m.group(1)
+assert tok, "no replicate token"
+UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
+
+# 20 whimsical variations — our own "theme e variazioni"
+VARIATIONS = [
+    ("Mona Lisa", "the face wearing a subtle Mona Lisa smile with a soft renaissance veil"),
+    ("Pipe Dream", "a slender pipe held at the lips with a curl of smoke"),
+    ("Monocle", "a single monocle over one eye on a fine chain"),
+    ("Crown", "a delicate line-drawn crown resting on the hair"),
+    ("Keyhole", "a small keyhole motif where one eye would be, surreal"),
+    ("Playing Card", "the portrait mirrored like a queen playing card, top and bottom"),
+    ("Blindfold", "a lace blindfold across the eyes, mysterious"),
+    ("Teacup", "balancing a tiny teacup and saucer on the crown of the head"),
+    ("Clockface", "a small clock face set into the cheek, surreal collage"),
+    ("Moon Phase", "crescent moons for earrings and stars scattered in the hair"),
+    ("Pince-Nez", "old-fashioned pince-nez spectacles perched on the nose"),
+    ("Ribbon", "an oversized bow ribbon tied at the chin"),
+    ("Birdcage Veil", "a fine birdcage veil netting over the upper face"),
+    ("Pearl Choker", "an ornate pearl-and-jewel choker at the throat"),
+    ("Winking", "one eye closed in a playful wink"),
+    ("Feather Hat", "a jaunty plumed hat tilted over the brow"),
+    ("Mustache", "a curled dapper mustache, gender-play whimsy"),
+    ("Veil of Lace", "half the face dissolving into an intricate lace pattern"),
+    ("Sun Rays", "a halo of fine engraved sun rays behind the head"),
+    ("Masquerade", "an ornate venetian masquerade mask over the eyes"),
+]
+
+BASE = ("black and white vintage engraving wallpaper, a single elegant original woman's face "
+        "inside a circular medallion plate, fine cross-hatch etching and stipple, "
+        "monochrome charcoal on ivory, surrealist whimsical homage to theme-and-variations "
+        "portrait-plate wallpaper, our own original face, not a copy of any existing design, "
+        "seamless repeating wallpaper pattern, tileable, flat lay, even lighting, high-end wallcovering")
+NEG = ("color, colour, photograph, photo-realistic, modern, text, watermark, signature, logo, "
+       "blurry, low quality, jpeg artifacts, seams, frame, border, vignette, extra faces, deformed")
+
+def hj(url, payload=None):
+    hdr = {"User-Agent": UA, "Content-Type": "application/json", "Authorization": f"Bearer {tok}"}
+    data = json.dumps(payload).encode() if payload else None
+    req = urllib.request.Request(url, data=data, headers=hdr, method="POST" if data else "GET")
+    with urllib.request.urlopen(req, timeout=180) as r: return json.load(r)
+
+def sdxl(prompt, seed):
+    ver = hj("https://api.replicate.com/v1/models/stability-ai/sdxl")["latest_version"]["id"]
+    pred = hj("https://api.replicate.com/v1/predictions", {"version": ver, "input": {
+        "prompt": prompt, "negative_prompt": NEG, "width": 1024, "height": 1024,
+        "num_inference_steps": 34, "guidance_scale": 8.0, "seed": seed, "refine": "no_refiner"}})
+    for _ in range(90):
+        if pred.get("status") in ("succeeded", "failed", "canceled"): break
+        time.sleep(2); pred = hj(pred["urls"]["get"])
+    return pred
+
+designs = []; total = 0.0
+for i, (name, desc) in enumerate(VARIATIONS[:N], 1):
+    prompt = f"{BASE}; variation: {desc}."
+    pred = sdxl(prompt, SEED_BASE + i * 17)
+    secs = (pred.get("metrics") or {}).get("predict_time", 0) or 0; total += secs
+    if pred.get("status") != "succeeded":
+        print(f"[{i}] {name} FAIL {pred.get('status')}", flush=True); continue
+    out = pred["output"]; url = out[0] if isinstance(out, list) else out
+    fn = f"face_{i:02d}.png"; dest = f"{OUT}/{fn}"
+    urllib.request.urlretrieve(url, dest)
+    designs.append({"n": i, "name": name, "desc": desc, "file": f"/faces/{fn}", "prompt": prompt})
+    print(f"[{i}/{N}] {name} OK {secs:.1f}s", flush=True)
+    time.sleep(0.6)
+
+cost = round(total * 0.000725, 4)
+json.dump({"title": "Tema e Variazioni — our faces", "count": len(designs),
+           "est_cost_usd": cost, "designs": designs}, open(f"{OUT}/manifest.json", "w"), indent=2)
+print(f"\n=== {len(designs)} tiles | est ${cost} ===", flush=True)
diff --git a/whimsical-compare/server.js b/whimsical-compare/server.js
index 36dfb37..936a0b7 100644
--- a/whimsical-compare/server.js
+++ b/whimsical-compare/server.js
@@ -16,7 +16,7 @@ const https = require('https');
 
 const ROOT = __dirname;
 const PG = 'postgresql:///dw_unified?host=/tmp';
-const PORT = process.env.PORT || 0; // OS-assigned free port
+const PORT = process.env.PORT || 9793; // stable port for a durable URL
 const OURS = path.join(ROOT, 'public', 'ours');
 
 // ---- secrets ----
@@ -54,6 +54,12 @@ function psql(sql) {
   });
 }
 
+app.get('/api/faces', (req, res) => {
+  const mf = path.join(ROOT, 'public', 'faces', 'manifest.json');
+  if (!fs.existsSync(mf)) return res.json({ count: 0, designs: [] });
+  res.json(JSON.parse(fs.readFileSync(mf, 'utf8')));
+});
+
 app.get('/api/designs', async (req, res) => {
   try {
     const out = await psql(

← d9bd2b6 fernwick daily drop: 20 settlement-passed originals/day, ran  ·  back to Pattern Vault  ·  Tema e Variazioni: 20 original b&w face-medallion tiles (our 7a0948d →