Files
kaleidoskop/templates/cover.html
T
valknar 1037b84eaa 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>
2026-05-03 17:29:10 +02:00

438 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 &lt; 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>