Cover Prompts

Designing Prompts for Six Book Covers

A series of covers needs one style and six subjects. Write the style once, as Structuring a Prompt taught, and let only the subject and the book's palette color vary. The prompt also reserves space: an empty lower third is where the page, not the model, will set the title, so the art carries no text at all.

covers.mjs: one style, six subjects, six parallel callsJavaScript
// covers.mjs: six BookNest covers in one shared style (Section 8.9.1)
import fs from 'node:fs';
import OpenAI from 'openai';
import { dollars } from './budget.mjs';
const style = 'Book-cover painting, gouache, soft light, palette led by COLOR with cream '
  + '#fff3e0 accents. Subject in the upper two thirds; the lower third is calm, plain and '
  + 'empty for the title. No text, no letters, no border.';
const books = {                                    // palette color -> subject, in book order
  '#1f5f8b': 'A lighthouse keeper\'s daughter on a harbor wall at dusk',
  '#5b3f99': 'Glowing threads linking small servers like a coral reef in deep water',
  '#e09a10': 'A coastal kitchen table with saffron threads, chilies and a clay pot',
  '#3f7d3a': 'A lone trekker on a winding trail toward snowy peaks',
  '#b5452f': 'An open pocket watch with brass gears, its hands blurred mid-turn',
  '#2a9d8f': 'A glass terrarium of ferns and moss on a sunny windowsill',
};
const openai = new OpenAI();
const results = await Promise.all(Object.entries(books).map(([color, subject]) =>
  openai.images.generate({ model: 'gpt-image-2.5-flare', size: '1024x1536', quality: 'low',
    prompt: `${subject}. ${style.replace('COLOR', color)}`, output_format: 'jpeg' })));
let cost = 0;
results.forEach(({ data, usage }, i) => {
  fs.writeFileSync(`cover-${i + 1}.jpg`, Buffer.from(data[0].b64_json, 'base64'));
  cost += dollars({ textIn: usage.input_tokens, out: usage.output_tokens });
});
const tokens = results.map(r => r.usage.output_tokens).join(' + ');
console.log(`${tokens} tokens, $${cost.toFixed(4)}`);
Output
158 + 158 + 158 + 158 + 158 + 158 tokens, $0.0307

The six parallel requests took 11 seconds in all. Every painting kept the shared style and left its lower third plain (Compositing in 3D shows them). Parallel calls count against your images-per-minute (IPM) rate limit, so cap concurrency for large batches, and regenerate an approved cover at high quality for print.

One shared style and six subjects: stand-in cover paintings with an empty lower third, and the prompt for eachHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; background: #fafaf7; font: 12px system-ui, sans-serif; color: #263238; max-width: 600px; }
  canvas { display: block; max-width: 100%; cursor: pointer; }
  pre { background: #fff; border: 1px solid #e0d8c8; padding: 6px; font-size: 11px; white-space: pre-wrap; margin: 6px 0 0; }
</style>
<canvas id="c" width="600" height="160"></canvas>
<p><label><input type="checkbox" id="guide" checked> show the reserved lower third</label> · click a cover for its prompt</p>
<pre id="prompt"></pre>
<script>
  const style = 'Book-cover painting, gouache, soft light, palette led by COLOR with cream #fff3e0 accents. ' +
    'Subject in the upper two thirds; the lower third is calm, plain and empty for the title. No text, no letters, no border.';
  const books = [
    ['#1f5f8b', "A lighthouse keeper's daughter on a harbor wall at dusk"],
    ['#5b3f99', 'Glowing threads linking small servers like a coral reef in deep water'],
    ['#e09a10', 'A coastal kitchen table with saffron threads, chilies and a clay pot'],
    ['#3f7d3a', 'A lone trekker on a winding trail toward snowy peaks'],
    ['#b5452f', 'An open pocket watch with brass gears, its hands blurred mid-turn'],
    ['#2a9d8f', 'A glass terrarium of ferns and moss on a sunny windowsill'],
  ];
  const ctx = document.getElementById('c').getContext('2d'), W = 92, H = 138;
  // Stand-in paintings: each subject drawn in the book's color, lower third left calm
  const subjects = [
    (c) => { ctx.fillStyle = '#e09a10'; ctx.fillRect(0, 50, W, 10); ctx.fillStyle = '#fff3e0'; ctx.fillRect(60, 14, 8, 36);
             ctx.fillStyle = '#b5452f'; ctx.fillRect(60, 20, 8, 5); ctx.fillStyle = '#263238'; ctx.fillRect(28, 38, 5, 14); ctx.fillRect(0, 60, W, 6); },
    (c) => { ctx.strokeStyle = '#fff3e0'; ctx.lineWidth = 1.5; for (let i = 0; i < 7; i++) { ctx.beginPath(); ctx.moveTo(10 + i * 11, 80);
             ctx.bezierCurveTo(20 + i * 5, 40, 60 - i * 3, 50, 46, 14 + i * 4); ctx.stroke(); }
             ctx.fillStyle = '#e09a10'; [[20, 30], [60, 22], [46, 50], [70, 60]].forEach(([x, y]) => ctx.fillRect(x, y, 8, 6)); },
    (c) => { ctx.fillStyle = '#8d6e63'; ctx.fillRect(0, 62, W, 26); ctx.fillStyle = '#b5452f'; ctx.beginPath(); ctx.ellipse(46, 52, 20, 14, 0, 0, 7); ctx.fill();
             ctx.fillStyle = '#b5452f'; [[16, 60], [72, 58], [24, 70]].forEach(([x, y]) => { ctx.beginPath(); ctx.ellipse(x, y, 7, 3, 0.4, 0, 7); ctx.fill(); });
             ctx.strokeStyle = '#fff3e0'; for (let i = 0; i < 5; i++) { ctx.beginPath(); ctx.moveTo(50 + i * 3, 64); ctx.lineTo(56 + i * 4, 72); ctx.stroke(); } },
    (c) => { ctx.fillStyle = '#fff3e0'; ctx.beginPath(); ctx.moveTo(0, 60); ctx.lineTo(30, 18); ctx.lineTo(50, 44); ctx.lineTo(70, 10); ctx.lineTo(W, 40); ctx.lineTo(W, 60); ctx.fill();
             ctx.strokeStyle = '#e09a10'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(20, 90); ctx.quadraticCurveTo(70, 80, 40, 62); ctx.stroke();
             ctx.fillStyle = '#263238'; ctx.fillRect(38, 56, 3, 7); },
    (c) => { ctx.fillStyle = '#e09a10'; ctx.beginPath(); ctx.arc(46, 46, 28, 0, 7); ctx.fill(); ctx.fillStyle = '#fff3e0'; ctx.beginPath(); ctx.arc(46, 46, 22, 0, 7); ctx.fill();
             ctx.strokeStyle = '#8d6e63'; ctx.lineWidth = 2; for (let i = 0; i < 12; i++) { const a = i * Math.PI / 6; ctx.beginPath(); ctx.moveTo(46 + Math.cos(a) * 18, 46 + Math.sin(a) * 18); ctx.lineTo(46 + Math.cos(a) * 21, 46 + Math.sin(a) * 21); ctx.stroke(); }
             ctx.strokeStyle = 'rgba(38,50,56,0.5)'; ctx.lineWidth = 3; for (let k = 0; k < 3; k++) { ctx.beginPath(); ctx.moveTo(46, 46); ctx.lineTo(46 + Math.cos(-1 + k * 0.25) * 16, 46 + Math.sin(-1 + k * 0.25) * 16); ctx.stroke(); } },
    (c) => { ctx.fillStyle = '#fff3e0'; ctx.fillRect(10, 10, 72, 50); ctx.strokeStyle = 'rgba(255,255,255,0.8)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.ellipse(46, 62, 26, 24, 0, Math.PI, 2 * Math.PI); ctx.stroke();
             ctx.fillStyle = '#3f7d3a'; ctx.beginPath(); ctx.ellipse(46, 74, 24, 10, 0, 0, 7); ctx.fill(); ctx.fillStyle = '#6fbf73'; [[36, 60], [50, 54], [58, 64]].forEach(([x, y]) => { ctx.beginPath(); ctx.ellipse(x, y, 4, 10, 0.3, 0, 7); ctx.fill(); }); },
  ];
  let selected = 0;
  function draw() {
    ctx.clearRect(0, 0, 600, 160);
    books.forEach(([color, subject], i) => {
      ctx.save();  ctx.translate(8 + i * 98, 10);
      const g = ctx.createLinearGradient(0, 0, 0, H);
      g.addColorStop(0, color);  g.addColorStop(1, '#fff3e0');
      ctx.fillStyle = g;  ctx.fillRect(0, 0, W, H);
      subjects[i](color);
      ctx.fillStyle = 'rgba(255,243,224,0.55)';  ctx.fillRect(0, H * 2 / 3, W, H / 3);    // calm, plain lower third
      if (document.getElementById('guide').checked) { ctx.setLineDash([4, 3]);  ctx.strokeStyle = '#263238';  ctx.lineWidth = 1;  ctx.strokeRect(2, H * 2 / 3, W - 4, H / 3 - 2);  ctx.setLineDash([]); }
      if (i === selected) { ctx.strokeStyle = '#263238';  ctx.lineWidth = 3;  ctx.strokeRect(-3, -3, W + 6, H + 6); }
      ctx.restore();
    });
    const [color, subject] = books[selected];
    document.getElementById('prompt').textContent = `${subject}. ${style.replace('COLOR', color)}\n\n` +
      `gpt-image-2.5-flare · 1024x1536 · low · jpeg · six requests in parallel (mind the images-per-minute limit)`;
  }
  document.getElementById('c').addEventListener('click', e => {
    const r = e.target.getBoundingClientRect(), x = (e.clientX - r.left) * 600 / r.width;
    selected = Math.max(0, Math.min(5, Math.floor((x - 8) / 98)));  draw();
  });
  document.getElementById('guide').onchange = draw;
  draw();
</script>