[object Object]

← back to Wallco Ai

LA Toile: Nano Banana generator (literal scenic toile, 30/30 settlement-clean)

07dab7dac5b6e717f69bf61f99f32cda4d9302df · 2026-06-03 07:53:06 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit 07dab7dac5b6e717f69bf61f99f32cda4d9302df
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Wed Jun 3 07:53:06 2026 -0700

    LA Toile: Nano Banana generator (literal scenic toile, 30/30 settlement-clean)
---
 scripts/generate-la-toile-nb.js | 115 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 115 insertions(+)

diff --git a/scripts/generate-la-toile-nb.js b/scripts/generate-la-toile-nb.js
new file mode 100644
index 0000000..b812baf
--- /dev/null
+++ b/scripts/generate-la-toile-nb.js
@@ -0,0 +1,115 @@
+// LA Toile de Jouy via Gemini Nano Banana (gemini-2.5-flash-image) — LITERAL,
+// recognizable engraved-toile scenes (the SDXL/ComfyUI lane produced abstract blobs;
+// Steve redirected to the Gemini path already wired in this codebase, 2026-06-03).
+// Generates 30 scenes to /tmp/nb-toile-NN.png with a settlement gate (no bird/
+// butterfly/banana/grape — Steve's legal rule). No DB writes (proof set for review).
+// Usage: node scripts/generate-la-toile-nb.js [startIdx] [count]
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+const ROOT = path.join(__dirname, '..');
+const PY = path.join(ROOT, 'scripts/vectorize/.venv/bin/python3');
+
+function readKey() {
+  if (process.env.GEMINI_API_KEY) return process.env.GEMINI_API_KEY;
+  const m = fs.readFileSync(path.join(ROOT, '.env'), 'utf8').match(/^GEMINI_API_KEY=(.+)$/m);
+  if (!m) throw new Error('GEMINI_API_KEY missing'); return m[1].trim();
+}
+async function geminiImage(prompt) {
+  const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent?key=${readKey()}`;
+  const r = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }], generationConfig: { responseModalities: ['IMAGE'] } }) });
+  if (!r.ok) throw new Error(`gemini ${r.status}: ${(await r.text()).slice(0, 160)}`);
+  const j = await r.json();
+  const part = j?.candidates?.[0]?.content?.parts?.find(p => p.inline_data || p.inlineData);
+  const b64 = part?.inline_data?.data || part?.inlineData?.data;
+  if (!b64) throw new Error('no image'); return b64;
+}
+const SETTLEMENT_Q = "Does this image contain any bird, butterfly, banana, banana plant or leaf, or bunch of grapes? Answer yes only if one of those is clearly visible.";
+function settlementViolation(pngPath) {
+  try { return execSync(`${PY} scripts/vision-contains.py ${JSON.stringify(pngPath)} ${JSON.stringify(SETTLEMENT_Q)}`,
+    { cwd: ROOT, encoding: 'utf8' }).trim() === 'yes'; } catch { return false; }
+}
+
+const INKS = [
+  { name: 'garden',          palette: 'hedge green #3E4A38, olive #8C9472, sunset terracotta #B0512F, dusty rose #9F5664 and brass #B08D3F', ground: 'natural flax linen ivory' },
+  { name: 'jacaranda spring',palette: 'jacaranda violet #7C6FA6, hedge green #3E4A38, olive #8C9472, blush stucco #E8B7AC and sand #C9B79C', ground: 'warm ivory linen' },
+  { name: 'bougainvillea',   palette: 'bougainvillea magenta #C2417B, cypress #2C3A30, olive #8C9472 and brass #B08D3F', ground: 'natural flax linen' },
+  { name: 'pacific',         palette: 'pacific blue #4E7C93, sky #A9C6D4, sage #8C9472, sand #E4D6BF and charcoal #3A352E', ground: 'pale flax linen' },
+  { name: 'sunset',          palette: 'sunset coral #E3795F, terracotta #B0512F, apricot #E8A06A, olive #8C9472 and brass #B08D3F', ground: 'peach-dyed linen' },
+  { name: 'blush sunshine',  palette: 'dusty rose #9F5664, blush #E8B7AC, apricot #E8A06A, soft sage #A9B6A0 and champagne gold #CBB179', ground: 'blush-dyed linen' },
+  { name: 'brass & green',   palette: 'brass #B08D3F, champagne gold #CBB179, hedge green #3E4A38, olive #8C9472 and noir #15140F', ground: 'natural flax linen' },
+  { name: 'noir & rose',     palette: 'black-tie noir #15140F, charcoal #3A352E, dusty rose #9F5664, sage #8C9472 and brass #B08D3F', ground: 'marble-tone linen ivory' },
+];
+const SCENES = [
+  'the Hollywood Sign on a chaparral hillside with Griffith Observatory domes below',
+  'the Venice Beach boardwalk with the fishing pier, beach cruisers and rolling surf',
+  'the downtown Los Angeles skyline with City Hall tower and Art-Deco high-rises',
+  'a jacaranda-lined Sunset Boulevard with vintage streetlamps and stucco storefronts',
+  'Beverly Hills Spanish-revival bungalows with tiled fountains and clipped hedges',
+  'the Santa Monica Pier Ferris wheel, arcade and ocean waves',
+  'the Chinatown paifang gate with hanging lanterns and pagoda rooflines',
+  'Echo Park Lake with paddle boats, lotus blossoms and the downtown skyline beyond',
+  'mid-century Case Study glass houses cantilevered over the canyon hills',
+  'vintage 1960s convertibles cruising the curves of Mulholland Drive',
+  'the Watts Towers mosaic spires rising over small craftsman rooftops',
+  'the Los Angeles Aqueduct cascade against the San Gabriel mountains',
+  'Olvera Street market stalls with woven baskets, pottery and string-light arcades',
+  'Griffith Park trails winding through oak groves with a grazing mule deer',
+  'the Malibu coastline with longboard surfers, sea cliffs and a lighthouse',
+  'Art-Deco movie palaces with marquees, ticket booths and klieg-light beams',
+  'old-Los-Angeles citrus groves with orange crates, packing sheds and wagons',
+  'Union Station archways with a vintage streamliner train and a magnolia courtyard',
+  'a golden-age studio backlot with sound stages, klieg lights and camera cranes',
+  'Pasadena Rose Parade floats with marching bands and ranunculus garlands',
+  'the Angels Flight funicular railway climbing Bunker Hill brownstones',
+  'the La Brea Tar Pits with woolly mammoths and sabertooth silhouettes',
+  'the Bradbury Building wrought-iron atrium stairs and birdcage elevators',
+  'a Mulholland overlook at dusk with a lone coyote and city lights below',
+  'Silver Lake reservoir with joggers, eucalyptus trees and modernist homes',
+  'the Original Farmers Market clock tower with the Grove trolley and produce carts',
+  'Topanga Canyon cabins tucked among coast live oaks and boulders',
+  'the Googie LAX Theme Building arches with finned cars and jet contrails',
+  'Dodger Stadium grandstands ringed by eucalyptus and the Elysian hills',
+  'jacaranda-lined residential streets in full bloom with craftsman porches',
+];
+
+function buildPrompt(scene, ink) {
+  return `A true Toile de Jouy wallpaper depicting ${scene}. ` +
+    `Render as fine copperplate ENGRAVING with delicate line hatching and stippling, in the heritage manner of ` +
+    `Pierre Frey, Zuber and Brunschwig & Fils. The pictorial vignettes must be RECOGNIZABLE and LITERAL — a viewer ` +
+    `should clearly identify each landmark and element exactly as described. Small detailed scenes repeat ALL OVER ` +
+    `the sheet, edge to edge, densely filling the field on a ${ink.ground} ground. ` +
+    `Beverly Hills multi-color colorway: ${ink.palette}. Aged, sun-bleached pigments — never neon. ` +
+    `Seamless repeating wallpaper, flat, no perspective, no border, no frame, no text or lettering except any ` +
+    `landmark sign literally named. Trees are Italian cypress, coast live oak, olive, jacaranda, magnolia and ` +
+    `eucalyptus — NO palm trees. NO birds, NO butterflies, NO bananas, NO grapes.`;
+}
+
+(async () => {
+  const start = parseInt(process.argv[2] || '0', 10);
+  const count = parseInt(process.argv[3] || String(SCENES.length), 10);
+  const ROLLS = 3;
+  const winners = {};
+  for (let i = start; i < Math.min(start + count, SCENES.length); i++) {
+    const ink = INKS[i % INKS.length];
+    const slug = 'nb-toile-' + String(i + 1).padStart(2, '0');
+    const dest = `/tmp/${slug}.png`;
+    let won = false;
+    for (let r = 1; r <= ROLLS && !won; r++) {
+      try {
+        const t0 = Date.now();
+        const b64 = await geminiImage(buildPrompt(SCENES[i], ink));
+        fs.writeFileSync(dest, Buffer.from(b64, 'base64'));
+        const bad = settlementViolation(dest);
+        const dt = ((Date.now() - t0) / 1000).toFixed(1);
+        if (bad) { console.log(`  ${slug} roll ${r}: SETTLEMENT violation (bird/etc) — re-roll (${dt}s)`); continue; }
+        console.log(`[${i + 1}/30] ${slug} OK (${ink.name}) -> ${dest} (${dt}s)`);
+        winners[slug] = dest; won = true;
+      } catch (e) { console.log(`  ${slug} roll ${r}: FAIL ${e.message}`); }
+    }
+    if (!won) console.log(`[${i + 1}/30] ${slug} — no clean roll in ${ROLLS} (kept last)`);
+  }
+  fs.writeFileSync('/tmp/nb-toile-winners.json', JSON.stringify(winners, null, 2));
+  console.log(`\n${Object.keys(winners).length} scenes generated → /tmp/nb-toile-winners.json`);
+})();

← b4692f9 yolo audit: APPLIED+DEPLOYED 3 unpublish + 51 kind-flip + 12  ·  back to Wallco Ai  ·  LA Toile: stage+publish 30 Nano Banana toiles to live (peer- 2ef1803 →