Add KDP cover PDF generator
scripts/cover.js computes the exact cover canvas dimensions from the page count in book-meta.json (written by build.js), using the KDP Premium Color spine formula (0.002347 in/page), and renders a Nunjucks template to a single PDF containing back cover, spine, and front cover with bleed (0.125 in) and safe-zone overlay guides. - `pnpm cover` — generate output/cover.pdf - `pnpm all` — build interior + both PDFs in one command - Cover artwork slots: images/cover/front.png, images/cover/back.png Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+6
-1
@@ -1,5 +1,10 @@
|
|||||||
.claude/
|
.claude/
|
||||||
node_modules/
|
node_modules/
|
||||||
output/book.html
|
output/book.html
|
||||||
# Keep the PDF in git if you want to track versions, otherwise add:
|
output/cover.html
|
||||||
|
output/book-meta.json
|
||||||
|
# Interior PDF is large (~100 MB with placeholder pages) — excluded from git.
|
||||||
|
# Re-add images and re-run `pnpm all` to regenerate.
|
||||||
output/kaleidoskop.pdf
|
output/kaleidoskop.pdf
|
||||||
|
# Cover PDF is small (~140 KB) — tracked in git.
|
||||||
|
# output/cover.pdf
|
||||||
|
|||||||
Binary file not shown.
@@ -6,7 +6,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node scripts/build.js",
|
"build": "node scripts/build.js",
|
||||||
"pdf": "node scripts/pdf.js",
|
"pdf": "node scripts/pdf.js",
|
||||||
|
"cover": "node scripts/cover.js",
|
||||||
"book": "pnpm build && pnpm pdf",
|
"book": "pnpm build && pnpm pdf",
|
||||||
|
"all": "pnpm build && pnpm pdf && pnpm cover",
|
||||||
"watch": "node --watch scripts/build.js"
|
"watch": "node --watch scripts/build.js"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|||||||
+8
-2
@@ -64,8 +64,14 @@ async function build() {
|
|||||||
const outPath = join(root, 'output', 'book.html');
|
const outPath = join(root, 'output', 'book.html');
|
||||||
await writeFile(outPath, html, 'utf-8');
|
await writeFile(outPath, html, 'utf-8');
|
||||||
|
|
||||||
const pageCount = stories.reduce((acc, s) => acc + s.scenes.length * 2, 0) + 5;
|
// title page + copyright + TOC + (4 scenes × 2 pages per story) + finale
|
||||||
console.log(`Built output/book.html — ${stories.length} stories, ~${pageCount} pages`);
|
const pageCount = 3 + stories.reduce((acc, s) => acc + s.scenes.length * 2, 0) + 1;
|
||||||
|
await writeFile(
|
||||||
|
join(root, 'output', 'book-meta.json'),
|
||||||
|
JSON.stringify({ pageCount, storyCount: stories.length, builtAt: new Date().toISOString() }, null, 2),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
console.log(`Built output/book.html — ${stories.length} stories, ${pageCount} pages`);
|
||||||
}
|
}
|
||||||
|
|
||||||
build().catch(err => { console.error(err); process.exit(1); });
|
build().catch(err => { console.error(err); process.exit(1); });
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import puppeteer from 'puppeteer';
|
||||||
|
import { readFile, writeFile, access } from 'fs/promises';
|
||||||
|
import { resolve, dirname } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import nunjucks from 'nunjucks';
|
||||||
|
|
||||||
|
const __dir = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const root = resolve(__dir, '..');
|
||||||
|
|
||||||
|
// KDP constants — Premium Color paper, 8.5×8.5 in trim
|
||||||
|
const BLEED_IN = 0.125;
|
||||||
|
const TRIM_W_IN = 8.5;
|
||||||
|
const TRIM_H_IN = 8.5;
|
||||||
|
const SPINE_PER_PAGE = 0.002347; // inches per page, Premium Color
|
||||||
|
const SAFE_IN = 0.125; // safe zone inset from trim edge (front/back)
|
||||||
|
const SPINE_SAFE_IN = 0.0625; // safe zone inset from spine fold (each side)
|
||||||
|
const MIN_SPINE_TEXT = 79; // KDP minimum pages for spine text
|
||||||
|
|
||||||
|
async function fileExists(p) {
|
||||||
|
try { await access(p); return true; } catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generate() {
|
||||||
|
// Read page count produced by build.js
|
||||||
|
const metaPath = resolve(root, 'output', 'book-meta.json');
|
||||||
|
let pageCount = 100;
|
||||||
|
|
||||||
|
if (await fileExists(metaPath)) {
|
||||||
|
const meta = JSON.parse(await readFile(metaPath, 'utf-8'));
|
||||||
|
pageCount = meta.pageCount;
|
||||||
|
console.log(`Page count: ${pageCount} (from output/book-meta.json)`);
|
||||||
|
} else {
|
||||||
|
console.warn(`book-meta.json not found — using default page count ${pageCount}`);
|
||||||
|
console.warn('Run `pnpm build` first for an accurate spine width.\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute dimensions
|
||||||
|
const spineWidth = pageCount * SPINE_PER_PAGE;
|
||||||
|
const totalWidth = BLEED_IN + TRIM_W_IN + spineWidth + TRIM_W_IN + BLEED_IN;
|
||||||
|
const totalHeight = BLEED_IN + TRIM_H_IN + BLEED_IN;
|
||||||
|
|
||||||
|
console.log(`Spine width : ${spineWidth.toFixed(4)} in`);
|
||||||
|
console.log(`Cover canvas: ${totalWidth.toFixed(4)} × ${totalHeight.toFixed(4)} in\n`);
|
||||||
|
|
||||||
|
// Check for optional artwork files
|
||||||
|
const frontImage = await fileExists(resolve(root, 'images/cover/front.png'))
|
||||||
|
? '../images/cover/front.png' : null;
|
||||||
|
const backImage = await fileExists(resolve(root, 'images/cover/back.png'))
|
||||||
|
? '../images/cover/back.png' : null;
|
||||||
|
|
||||||
|
// Render HTML from template
|
||||||
|
const env = nunjucks.configure(resolve(root, 'templates'), { autoescape: false });
|
||||||
|
const html = env.render('cover.html', {
|
||||||
|
pageCount,
|
||||||
|
spineWidth: spineWidth.toFixed(4),
|
||||||
|
totalWidth: totalWidth.toFixed(4),
|
||||||
|
totalHeight: totalHeight.toFixed(4),
|
||||||
|
bleed: BLEED_IN,
|
||||||
|
trimW: TRIM_W_IN,
|
||||||
|
trimH: TRIM_H_IN,
|
||||||
|
safe: SAFE_IN,
|
||||||
|
spineSafe: SPINE_SAFE_IN,
|
||||||
|
hasSpineText: pageCount >= MIN_SPINE_TEXT,
|
||||||
|
frontImage,
|
||||||
|
backImage,
|
||||||
|
});
|
||||||
|
|
||||||
|
const htmlOut = resolve(root, 'output', 'cover.html');
|
||||||
|
await writeFile(htmlOut, html, 'utf-8');
|
||||||
|
|
||||||
|
// Generate PDF
|
||||||
|
const systemChromium = '/usr/bin/chromium';
|
||||||
|
const useSystem = await fileExists(systemChromium);
|
||||||
|
console.log(`Launching Puppeteer (${useSystem ? 'system Chromium' : 'bundled Chrome'})…`);
|
||||||
|
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
headless: true,
|
||||||
|
...(useSystem ? { executablePath: systemChromium } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await page.goto(`file://${htmlOut}`, { waitUntil: 'networkidle0' });
|
||||||
|
|
||||||
|
const pdfOut = resolve(root, 'output', 'cover.pdf');
|
||||||
|
await page.pdf({
|
||||||
|
path: pdfOut,
|
||||||
|
width: `${totalWidth}in`,
|
||||||
|
height: `${totalHeight}in`,
|
||||||
|
printBackground: true,
|
||||||
|
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
console.log('Cover PDF written to output/cover.pdf');
|
||||||
|
console.log('\nNote: KDP recommends CMYK for covers. This PDF is RGB.');
|
||||||
|
console.log('Convert with Adobe Acrobat or Ghostscript before upload if needed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
generate().catch(err => { console.error(err); process.exit(1); });
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Cover – Das Kaleidoskop der Schlummerwelten</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bleed: {{ bleed }}in;
|
||||||
|
--trim-w: {{ trimW }}in;
|
||||||
|
--trim-h: {{ trimH }}in;
|
||||||
|
--spine-w: {{ spineWidth }}in;
|
||||||
|
--total-w: {{ totalWidth }}in;
|
||||||
|
--total-h: {{ totalHeight }}in;
|
||||||
|
--safe: {{ safe }}in;
|
||||||
|
--spine-safe: {{ spineSafe }}in;
|
||||||
|
|
||||||
|
/* Derived positions (left edge of each zone) */
|
||||||
|
--back-left: 0in;
|
||||||
|
--spine-left: calc(var(--bleed) + var(--trim-w));
|
||||||
|
--front-left: calc(var(--bleed) + var(--trim-w) + var(--spine-w));
|
||||||
|
|
||||||
|
/* Design tokens */
|
||||||
|
--color-bg: #080820;
|
||||||
|
--color-mid: #111138;
|
||||||
|
--color-gold: #c8a84b;
|
||||||
|
--color-gold2: #f0d080;
|
||||||
|
--color-text: #e8e4d8;
|
||||||
|
--color-muted: rgba(200,168,75,0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: var(--total-w);
|
||||||
|
height: var(--total-h);
|
||||||
|
background: var(--color-bg);
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Canvas ─────────────────────────────────────────────── */
|
||||||
|
.canvas {
|
||||||
|
position: relative;
|
||||||
|
width: var(--total-w);
|
||||||
|
height: var(--total-h);
|
||||||
|
background: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Star-field background (pure CSS, no images required) */
|
||||||
|
.canvas::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(1px 1px at 12% 18%, rgba(255,255,255,0.7) 0%, transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 28% 42%, rgba(255,255,255,0.5) 0%, transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 44% 11%, rgba(255,255,255,0.6) 0%, transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 60% 73%, rgba(255,255,255,0.4) 0%, transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 76% 29%, rgba(255,255,255,0.7) 0%, transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 88% 55%, rgba(255,255,255,0.5) 0%, transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 7% 66%, rgba(255,255,255,0.4) 0%, transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 51% 88%, rgba(255,255,255,0.6) 0%, transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 33% 77%, rgba(255,255,255,0.3) 0%, transparent 100%),
|
||||||
|
radial-gradient(1px 1px at 93% 14%, rgba(255,255,255,0.5) 0%, transparent 100%),
|
||||||
|
radial-gradient(2px 2px at 20% 5%, rgba(200,168,75,0.5) 0%, transparent 100%),
|
||||||
|
radial-gradient(2px 2px at 68% 90%, rgba(200,168,75,0.4) 0%, transparent 100%),
|
||||||
|
radial-gradient(2px 2px at 85% 38%, rgba(200,168,75,0.5) 0%, transparent 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Zones ──────────────────────────────────────────────── */
|
||||||
|
.zone {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: var(--total-h);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone--back {
|
||||||
|
left: var(--back-left);
|
||||||
|
width: calc(var(--bleed) + var(--trim-w));
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone--spine {
|
||||||
|
left: var(--spine-left);
|
||||||
|
width: var(--spine-w);
|
||||||
|
background: var(--color-mid);
|
||||||
|
border-left: 0.5px solid rgba(200,168,75,0.2);
|
||||||
|
border-right: 0.5px solid rgba(200,168,75,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone--front {
|
||||||
|
left: var(--front-left);
|
||||||
|
width: calc(var(--trim-w) + var(--bleed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Back cover content ─────────────────────────────────── */
|
||||||
|
.back-content {
|
||||||
|
position: absolute;
|
||||||
|
/* safe zone: bleed + safe from left, safe from right, bleed+safe from top/bottom */
|
||||||
|
top: calc(var(--bleed) + var(--safe));
|
||||||
|
left: calc(var(--bleed) + var(--safe));
|
||||||
|
right: var(--safe);
|
||||||
|
bottom: calc(var(--bleed) + var(--safe));
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-headline {
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.22in;
|
||||||
|
color: var(--color-gold);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
margin-bottom: 0.12in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-synopsis {
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-size: 0.145in;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--color-text);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-synopsis p { margin-bottom: 0.1in; }
|
||||||
|
|
||||||
|
.back-bottom {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-tagline {
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.115in;
|
||||||
|
color: var(--color-muted);
|
||||||
|
max-width: 60%;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Barcode placeholder — KDP places the barcode here if left blank */
|
||||||
|
.barcode-placeholder {
|
||||||
|
width: 1in;
|
||||||
|
height: 0.6in;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid rgba(200,168,75,0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-placeholder span {
|
||||||
|
font-size: 0.07in;
|
||||||
|
color: #aaa;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional back cover artwork */
|
||||||
|
.back-artwork {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
object-fit: cover;
|
||||||
|
opacity: 0.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Spine content ──────────────────────────────────────── */
|
||||||
|
.spine-content {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spine-text {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.15in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spine-title {
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.13in;
|
||||||
|
color: var(--color-gold);
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spine-divider {
|
||||||
|
font-size: 0.1in;
|
||||||
|
color: var(--color-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spine-author {
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-size: 0.1in;
|
||||||
|
color: var(--color-text);
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spine-too-narrow {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
font-size: 0.08in;
|
||||||
|
color: rgba(255,80,80,0.8);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Front cover content ────────────────────────────────── */
|
||||||
|
.front-artwork {
|
||||||
|
position: absolute;
|
||||||
|
/* art fills from left trim edge (skip the right bleed) */
|
||||||
|
top: 0; left: 0;
|
||||||
|
width: var(--trim-w);
|
||||||
|
height: var(--total-h);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.front-content {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(var(--bleed) + var(--safe));
|
||||||
|
left: var(--safe);
|
||||||
|
right: calc(var(--safe) + var(--bleed));
|
||||||
|
bottom: calc(var(--bleed) + var(--safe));
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: 0.15in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.front-ornament-top {
|
||||||
|
font-size: 0.18in;
|
||||||
|
color: var(--color-gold);
|
||||||
|
letter-spacing: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.front-title {
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.52in;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: var(--color-gold2);
|
||||||
|
text-shadow: 0 0 0.15in rgba(200,168,75,0.4), 0 2px 8px rgba(0,0,0,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.front-subtitle {
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-size: 0.18in;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--color-text);
|
||||||
|
text-shadow: 0 1px 4px rgba(0,0,0,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder box shown when no front artwork is provided */
|
||||||
|
.front-art-placeholder {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--bleed);
|
||||||
|
left: 0;
|
||||||
|
width: var(--trim-w);
|
||||||
|
height: var(--trim-h);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: radial-gradient(ellipse at center, #1a1a4a 0%, #080820 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.front-art-placeholder span {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.12in;
|
||||||
|
color: rgba(200,168,75,0.4);
|
||||||
|
border: 1px dashed rgba(200,168,75,0.2);
|
||||||
|
padding: 0.15in 0.3in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.front-author {
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-size: 0.16in;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: rgba(200,168,75,0.7);
|
||||||
|
text-shadow: 0 1px 4px rgba(0,0,0,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.front-ornament-bottom {
|
||||||
|
font-size: 0.14in;
|
||||||
|
color: var(--color-muted);
|
||||||
|
letter-spacing: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Safe-zone & bleed guides overlay ───────────────────── */
|
||||||
|
/*
|
||||||
|
* These dashed lines are visual guides only — they mark the trim lines
|
||||||
|
* and safe zones. Remove or comment out this section before final upload
|
||||||
|
* if you prefer a clean proof. KDP will print nothing outside the trim.
|
||||||
|
*/
|
||||||
|
.guides {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Outer bleed boundary = trim line */
|
||||||
|
.guide--trim-top { position: absolute; top: var(--bleed); left: 0; right: 0; border-top: 0.5px dashed rgba(255,80,80,0.5); }
|
||||||
|
.guide--trim-bottom { position: absolute; bottom: var(--bleed); left: 0; right: 0; border-top: 0.5px dashed rgba(255,80,80,0.5); }
|
||||||
|
.guide--trim-left { position: absolute; left: var(--bleed); top: 0; bottom: 0; border-left: 0.5px dashed rgba(255,80,80,0.5); }
|
||||||
|
.guide--trim-right { position: absolute; right: var(--bleed); top: 0; bottom: 0; border-left: 0.5px dashed rgba(255,80,80,0.5); }
|
||||||
|
|
||||||
|
/* Safe zone (bleed + safe from edge) */
|
||||||
|
.guide--safe-top { position: absolute; top: calc(var(--bleed) + var(--safe)); left: 0; right: 0; border-top: 0.5px dashed rgba(255,220,50,0.4); }
|
||||||
|
.guide--safe-bottom { position: absolute; bottom: calc(var(--bleed) + var(--safe)); left: 0; right: 0; border-top: 0.5px dashed rgba(255,220,50,0.4); }
|
||||||
|
.guide--safe-left { position: absolute; left: calc(var(--bleed) + var(--safe)); top: 0; bottom: 0; border-left: 0.5px dashed rgba(255,220,50,0.4); }
|
||||||
|
.guide--safe-right { position: absolute; right: calc(var(--bleed) + var(--safe)); top: 0; bottom: 0; border-left: 0.5px dashed rgba(255,220,50,0.4); }
|
||||||
|
|
||||||
|
/* Spine boundaries */
|
||||||
|
.guide--spine-left { position: absolute; left: var(--spine-left); top: 0; bottom: 0; border-left: 0.5px dashed rgba(100,200,255,0.5); }
|
||||||
|
.guide--spine-right { position: absolute; left: calc(var(--front-left)); top: 0; bottom: 0; border-left: 0.5px dashed rgba(100,200,255,0.5); }
|
||||||
|
|
||||||
|
/* Guide labels */
|
||||||
|
.guide-label {
|
||||||
|
position: absolute;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.07in;
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
padding: 1px 3px;
|
||||||
|
border-radius: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.guide-label--bleed { color: rgba(255,80,80,0.9); top: 2px; left: calc(var(--bleed) + 2px); }
|
||||||
|
.guide-label--safe { color: rgba(255,220,50,0.9); top: calc(var(--bleed) + var(--safe) + 2px); left: calc(var(--bleed) + var(--safe) + 2px); }
|
||||||
|
.guide-label--spine { color: rgba(100,200,255,0.9); top: 2px; left: calc(var(--spine-left) + 2px); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="canvas">
|
||||||
|
|
||||||
|
{# ── Back cover ─────────────────────────────────────────── #}
|
||||||
|
<div class="zone zone--back">
|
||||||
|
|
||||||
|
{% if backImage %}
|
||||||
|
<img class="back-artwork" src="{{ backImage }}" alt="Back cover artwork">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="back-content">
|
||||||
|
<div>
|
||||||
|
<p class="back-headline">Zwölf magische Reisen in die Welt der Träume</p>
|
||||||
|
<div class="back-synopsis">
|
||||||
|
<p>
|
||||||
|
Jede Nacht beginnt eine neue Reise – und dieses Buch hat gleich zwölf davon.
|
||||||
|
Ob gläserne Wälder, schlafende Riesen oder ein Teetassen-Boot auf einem
|
||||||
|
Fluss aus Sternenlicht: Jede Geschichte führt sanft in eine neue Zauberwelt
|
||||||
|
und begleitet dein Kind liebevoll in den Schlaf.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Zwölf unabhängige Einschlafgeschichten · Traumhafte Aquarell-Illustrationen<br>
|
||||||
|
Für Kinder von 3 bis 8 Jahren · Ideal zum Vorlesen
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="back-bottom">
|
||||||
|
<p class="back-tagline">
|
||||||
|
„Die schönsten Welten sind nur<br>mit geschlossenen Augen zu erreichen."
|
||||||
|
</p>
|
||||||
|
{# Leave this box empty — KDP will place the barcode here automatically #}
|
||||||
|
<div class="barcode-placeholder">
|
||||||
|
<span>Barcode<br>(KDP)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>{# end back #}
|
||||||
|
|
||||||
|
{# ── Spine ──────────────────────────────────────────────── #}
|
||||||
|
<div class="zone zone--spine">
|
||||||
|
<div class="spine-content">
|
||||||
|
{% if hasSpineText %}
|
||||||
|
<div class="spine-text">
|
||||||
|
<span class="spine-title">Das Kaleidoskop der Schlummerwelten</span>
|
||||||
|
<span class="spine-divider">✦</span>
|
||||||
|
<span class="spine-author">[AUTOR]</span>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<span class="spine-too-narrow">too narrow ({{ pageCount }}p < 79)</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>{# end spine #}
|
||||||
|
|
||||||
|
{# ── Front cover ─────────────────────────────────────────── #}
|
||||||
|
<div class="zone zone--front">
|
||||||
|
|
||||||
|
{% if frontImage %}
|
||||||
|
<img class="front-artwork" src="{{ frontImage }}" alt="Front cover artwork">
|
||||||
|
{% else %}
|
||||||
|
<div class="front-art-placeholder">
|
||||||
|
<span>Cover-Illustration<br>→ images/cover/front.png</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="front-content">
|
||||||
|
<p class="front-ornament-top">✦ ✦ ✦</p>
|
||||||
|
<h1 class="front-title">Das Kaleidoskop<br>der Schlummerwelten</h1>
|
||||||
|
<p class="front-subtitle">Zwölf magische Einschlafgeschichten</p>
|
||||||
|
<p class="front-author">[AUTOR]</p>
|
||||||
|
<p class="front-ornament-bottom">· · ·</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>{# end front #}
|
||||||
|
|
||||||
|
{# ── Guides overlay (trim lines, safe zones, spine) ──────── #}
|
||||||
|
{# Remove this block before final upload to KDP #}
|
||||||
|
<div class="guides">
|
||||||
|
<div class="guide--trim-top"></div>
|
||||||
|
<div class="guide--trim-bottom"></div>
|
||||||
|
<div class="guide--trim-left"></div>
|
||||||
|
<div class="guide--trim-right"></div>
|
||||||
|
<div class="guide--safe-top"></div>
|
||||||
|
<div class="guide--safe-bottom"></div>
|
||||||
|
<div class="guide--safe-left"></div>
|
||||||
|
<div class="guide--safe-right"></div>
|
||||||
|
<div class="guide--spine-left"></div>
|
||||||
|
<div class="guide--spine-right"></div>
|
||||||
|
|
||||||
|
<span class="guide-label guide-label--bleed">← Beschnitt (3,2 mm)</span>
|
||||||
|
<span class="guide-label guide-label--safe">← Sicherheitsabstand (3,2 mm)</span>
|
||||||
|
<span class="guide-label guide-label--spine">← Rücken ({{ spineWidth }} Zoll / {{ pageCount }} S.)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>{# end canvas #}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user