diff --git a/content/00-front-matter.md b/content/00-front-matter.md index 46691fe..c6ff456 100644 --- a/content/00-front-matter.md +++ b/content/00-front-matter.md @@ -2,6 +2,6 @@ type: front-matter title: "Das Kaleidoskop der Schlummerwelten" subtitle: "Zwölf magische Geschichten für die Nacht" -author: "" -year: "2025" +author: "Sebastian Krüger" +year: "2026" --- diff --git a/fonts/cormorant-garamond-300-italic.woff2 b/fonts/cormorant-garamond-300-italic.woff2 new file mode 100644 index 0000000..11287ba Binary files /dev/null and b/fonts/cormorant-garamond-300-italic.woff2 differ diff --git a/fonts/cormorant-garamond-400-italic.woff2 b/fonts/cormorant-garamond-400-italic.woff2 new file mode 100644 index 0000000..d927264 Binary files /dev/null and b/fonts/cormorant-garamond-400-italic.woff2 differ diff --git a/fonts/cormorant-garamond-400-normal.woff2 b/fonts/cormorant-garamond-400-normal.woff2 new file mode 100644 index 0000000..096df0a Binary files /dev/null and b/fonts/cormorant-garamond-400-normal.woff2 differ diff --git a/fonts/cormorant-garamond-600-italic.woff2 b/fonts/cormorant-garamond-600-italic.woff2 new file mode 100644 index 0000000..070e7bd Binary files /dev/null and b/fonts/cormorant-garamond-600-italic.woff2 differ diff --git a/fonts/lora-400-italic.woff2 b/fonts/lora-400-italic.woff2 new file mode 100644 index 0000000..81e6cf8 Binary files /dev/null and b/fonts/lora-400-italic.woff2 differ diff --git a/fonts/lora-400-normal.woff2 b/fonts/lora-400-normal.woff2 new file mode 100644 index 0000000..2a40f60 Binary files /dev/null and b/fonts/lora-400-normal.woff2 differ diff --git a/fonts/lora-500-normal.woff2 b/fonts/lora-500-normal.woff2 new file mode 100644 index 0000000..b2702af Binary files /dev/null and b/fonts/lora-500-normal.woff2 differ diff --git a/output/cover.pdf b/output/cover.pdf index b968526..5e91d98 100644 Binary files a/output/cover.pdf and b/output/cover.pdf differ diff --git a/package.json b/package.json index 7bca284..ec52728 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,8 @@ "private": true, "type": "module", "scripts": { + "postinstall": "node scripts/setup-fonts.js", + "setup-fonts": "node scripts/setup-fonts.js", "build": "node scripts/build.js", "pdf": "node scripts/pdf.js", "cover": "node scripts/cover.js", @@ -12,9 +14,13 @@ "watch": "node --watch scripts/build.js" }, "pnpm": { - "onlyBuiltDependencies": ["puppeteer"] + "onlyBuiltDependencies": [ + "puppeteer" + ] }, "dependencies": { + "@fontsource/cormorant-garamond": "^5.2.11", + "@fontsource/lora": "^5.2.8", "gray-matter": "^4.0.3", "marked": "^12.0.0", "nunjucks": "^3.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46a6d53..fb84194 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@fontsource/cormorant-garamond': + specifier: ^5.2.11 + version: 5.2.11 + '@fontsource/lora': + specifier: ^5.2.8 + version: 5.2.8 gray-matter: specifier: ^4.0.3 version: 4.0.3 @@ -31,6 +37,12 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@fontsource/cormorant-garamond@5.2.11': + resolution: {integrity: sha512-5JjpN023lhA5soijgVT0BdRGzmlijm402ppjccMd6h+vRE0mX2lJnE+41UPfnlidrkV9/rCo1mf58WZlHnB0CA==} + + '@fontsource/lora@5.2.8': + resolution: {integrity: sha512-AQlfsHw4TP1x/eb2IZ6VjQ70ctKa39m9JN9A4zlvDOeKYLrCs+GaYIEQ86Y6YfSPGHn01bErXkRcyktOW0LOPQ==} + '@puppeteer/browsers@2.13.0': resolution: {integrity: sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==} engines: {node: '>=18'} @@ -498,6 +510,10 @@ snapshots: '@babel/helper-validator-identifier@7.28.5': {} + '@fontsource/cormorant-garamond@5.2.11': {} + + '@fontsource/lora@5.2.8': {} + '@puppeteer/browsers@2.13.0': dependencies: debug: 4.4.3 diff --git a/scripts/build.js b/scripts/build.js index f4f6a1e..6ecefd3 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -56,10 +56,15 @@ async function loadStories() { return { stories, finale: finale || {} }; } -async function build() { - const { stories, finale } = await loadStories(); +async function loadFrontMatter() { + const raw = await readFile(join(root, 'content', '00-front-matter.md'), 'utf-8'); + return matter(raw).data; +} - const html = env.render('book.html', { stories, finale }); +async function build() { + const [{ stories, finale }, frontMatter] = await Promise.all([loadStories(), loadFrontMatter()]); + + const html = env.render('book.html', { stories, finale, frontMatter }); const outPath = join(root, 'output', 'book.html'); await writeFile(outPath, html, 'utf-8'); diff --git a/scripts/setup-fonts.js b/scripts/setup-fonts.js new file mode 100644 index 0000000..1c2372c --- /dev/null +++ b/scripts/setup-fonts.js @@ -0,0 +1,26 @@ +import { copyFile, mkdir } from 'fs/promises'; +import { resolve, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dir = dirname(fileURLToPath(import.meta.url)); +const root = resolve(__dir, '..'); +const dest = resolve(root, 'fonts'); +const nm = resolve(root, 'node_modules'); + +const files = [ + ['@fontsource/cormorant-garamond/files/cormorant-garamond-latin-300-italic.woff2', 'cormorant-garamond-300-italic.woff2'], + ['@fontsource/cormorant-garamond/files/cormorant-garamond-latin-400-normal.woff2', 'cormorant-garamond-400-normal.woff2'], + ['@fontsource/cormorant-garamond/files/cormorant-garamond-latin-400-italic.woff2', 'cormorant-garamond-400-italic.woff2'], + ['@fontsource/cormorant-garamond/files/cormorant-garamond-latin-600-italic.woff2', 'cormorant-garamond-600-italic.woff2'], + ['@fontsource/lora/files/lora-latin-400-normal.woff2', 'lora-400-normal.woff2'], + ['@fontsource/lora/files/lora-latin-400-italic.woff2', 'lora-400-italic.woff2'], + ['@fontsource/lora/files/lora-latin-500-normal.woff2', 'lora-500-normal.woff2'], +]; + +await mkdir(dest, { recursive: true }); + +for (const [src, name] of files) { + await copyFile(resolve(nm, src), resolve(dest, name)); +} + +console.log(`Fonts copied to fonts/ (${files.length} files)`); diff --git a/styles/layout.css b/styles/layout.css index efe4875..749a0ff 100644 --- a/styles/layout.css +++ b/styles/layout.css @@ -10,14 +10,13 @@ display: block; } -/* Placeholder when image not yet available */ +/* Placeholder shown when image not yet available */ .placeholder-image { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; - opacity: 0.8; } .placeholder-label { @@ -25,28 +24,33 @@ flex-direction: column; align-items: center; gap: 0.5rem; - color: rgba(255,255,255,0.9); + color: rgba(255, 255, 255, 0.75); text-align: center; - padding: 2rem; - border: 2px dashed rgba(255,255,255,0.4); - border-radius: 12px; + padding: 1.5rem 2.5rem; + border: 1px dashed rgba(255, 255, 255, 0.25); + border-radius: 4px; } .placeholder-story { - font-size: 0.9rem; + font-size: 0.75rem; text-transform: uppercase; - letter-spacing: 0.1em; - opacity: 0.7; + letter-spacing: 0.15em; + opacity: 0.6; } .placeholder-scene { - font-size: 1.4rem; - font-weight: 600; + font-size: 1.15rem; + font-weight: 500; + line-height: 1.4; } /* ── Text pages ── */ .page--text { - background: var(--color-bg, #fafafa); + background: var(--cream, #faf8f2); + /* Subtle paper texture via SVG noise */ + background-image: + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.025'/%3E%3C/svg%3E"); + background-repeat: repeat; display: flex; align-items: center; } @@ -55,41 +59,40 @@ width: 100%; display: flex; flex-direction: column; - gap: 1.2rem; + gap: 0; } .scene-ornament { - font-size: 1.5rem; - color: var(--color-secondary, #ccc); - text-align: center; margin-top: auto; } -/* ── Front matter pages ── */ +/* ── Title page ── */ .page--title { - background: #1a1a3e; + background: var(--midnight, #0d0d2b); + /* Radial glow toward center */ + background-image: radial-gradient(ellipse at 50% 45%, #1a1a4a 0%, #0d0d2b 65%); display: flex; align-items: center; justify-content: center; text-align: center; } +.title-content { + display: flex; + flex-direction: column; + align-items: center; +} + +/* ── Imprint page ── */ .page--imprint { - background: #fafafa; + background: var(--cream, #faf8f2); display: flex; align-items: flex-end; } -.imprint-content { - width: 100%; - font-size: 0.75rem; - color: #666; - line-height: 1.8; -} - /* ── Table of contents ── */ .page--toc { - background: #fafafa; + background: var(--cream, #faf8f2); display: flex; flex-direction: column; gap: 2rem; @@ -100,28 +103,25 @@ counter-reset: toc; display: flex; flex-direction: column; - gap: 0.8rem; + gap: 0.7rem; } .toc-list li { counter-increment: toc; display: flex; align-items: baseline; - gap: 0.75rem; - font-size: 1rem; - color: #2a2a4a; + gap: 0.8rem; } .toc-list li::before { content: counter(toc, decimal-leading-zero); - font-size: 0.8rem; - color: #aaa; - min-width: 2rem; + min-width: 2.2rem; + flex-shrink: 0; } /* ── Finale page ── */ .page--finale { - background: #1a1a2e; + background: var(--midnight, #0d0d2b); position: relative; } @@ -139,7 +139,6 @@ align-items: center; justify-content: flex-end; padding-bottom: 1in; - color: white; text-align: center; - background: linear-gradient(to top, rgba(0,0,0,0.6) 0%, transparent 60%); + background: linear-gradient(to top, rgba(13, 13, 43, 0.75) 0%, transparent 55%); } diff --git a/styles/print.css b/styles/print.css index 63787af..15c2050 100644 --- a/styles/print.css +++ b/styles/print.css @@ -24,11 +24,10 @@ html, body { break-after: page; } -/* Safe zone: content that must not be cut stays 0.25in from bleed edge */ +/* Safe zone: 0.125in bleed + 0.675in margin = 0.8in from PDF edge */ .page--text .text-page-inner, .page--title .title-content, .page--toc, .page--imprint .imprint-content { - /* 0.125in bleed + 0.25in safety margin = 0.375in from PDF edge */ - padding: 0.75in; + padding: 0.8in; } diff --git a/styles/typography.css b/styles/typography.css index 64570ac..a30d52f 100644 --- a/styles/typography.css +++ b/styles/typography.css @@ -1,58 +1,185 @@ -/* ── Base typography ── */ +/* ── Cormorant Garamond — display, titles, ornaments ── */ +@font-face { + font-family: 'Cormorant Garamond'; + font-style: normal; + font-weight: 400; + src: url('../fonts/cormorant-garamond-400-normal.woff2') format('woff2'); +} +@font-face { + font-family: 'Cormorant Garamond'; + font-style: italic; + font-weight: 300; + src: url('../fonts/cormorant-garamond-300-italic.woff2') format('woff2'); +} +@font-face { + font-family: 'Cormorant Garamond'; + font-style: italic; + font-weight: 400; + src: url('../fonts/cormorant-garamond-400-italic.woff2') format('woff2'); +} +@font-face { + font-family: 'Cormorant Garamond'; + font-style: italic; + font-weight: 600; + src: url('../fonts/cormorant-garamond-600-italic.woff2') format('woff2'); +} + +/* ── Lora — body text ── */ +@font-face { + font-family: 'Lora'; + font-style: normal; + font-weight: 400; + src: url('../fonts/lora-400-normal.woff2') format('woff2'); +} +@font-face { + font-family: 'Lora'; + font-style: italic; + font-weight: 400; + src: url('../fonts/lora-400-italic.woff2') format('woff2'); +} +@font-face { + font-family: 'Lora'; + font-style: normal; + font-weight: 500; + src: url('../fonts/lora-500-normal.woff2') format('woff2'); +} + +/* ── Design tokens ── */ +:root { + --ink: #1e1b18; + --ink-muted: rgba(30, 27, 24, 0.38); + --ink-rule: rgba(30, 27, 24, 0.18); + --cream: #faf8f2; + --midnight: #0d0d2b; + --gold: #b89a4e; + + --font-display: 'Cormorant Garamond', Georgia, serif; + --font-body: 'Lora', Georgia, serif; +} + +/* ── Base ── */ body { - font-family: Georgia, 'Times New Roman', serif; + font-family: var(--font-body); + color: var(--ink); -webkit-print-color-adjust: exact; print-color-adjust: exact; } /* ── Title page ── */ .title-eyebrow { - font-size: 0.85rem; - letter-spacing: 0.2em; + font-family: var(--font-display); + font-style: italic; + font-weight: 300; + font-size: 0.9rem; + letter-spacing: 0.22em; text-transform: uppercase; - color: rgba(255,255,255,0.6); - margin-bottom: 1.5rem; + color: rgba(255, 255, 255, 0.5); + margin-bottom: 1.8rem; } .title-main { - font-size: 3rem; - line-height: 1.2; - color: white; - margin-bottom: 1.5rem; + font-family: var(--font-display); font-style: italic; + font-weight: 300; + font-size: 3.4rem; + line-height: 1.15; + color: #ffffff; + margin-bottom: 1.6rem; + letter-spacing: 0.01em; +} + +.title-author { + font-family: var(--font-display); + font-style: italic; + font-weight: 400; + font-size: 1rem; + color: rgba(255, 255, 255, 0.65); + letter-spacing: 0.12em; + margin-bottom: 0.5rem; } .title-sub { - font-size: 1.1rem; - color: rgba(255,255,255,0.8); - letter-spacing: 0.05em; + font-family: var(--font-display); + font-style: italic; + font-weight: 300; + font-size: 1rem; + color: rgba(255, 255, 255, 0.45); + letter-spacing: 0.08em; +} + +.title-rule { + width: 3rem; + height: 1px; + background: rgba(255, 255, 255, 0.25); + margin: 1.4rem auto; +} + +/* ── Imprint page ── */ +.imprint-content { + font-family: var(--font-body); + font-size: 0.72rem; + color: var(--ink-muted); + line-height: 2; } /* ── Table of contents ── */ .toc-title { - font-size: 1.6rem; - color: #1a1a3e; + font-family: var(--font-display); font-style: italic; - border-bottom: 1px solid #ddd; - padding-bottom: 0.5rem; + font-weight: 300; + font-size: 1.9rem; + color: var(--ink); + padding-bottom: 0.6rem; + border-bottom: 1px solid var(--ink-rule); + margin-bottom: 0.2rem; } -/* ── Story title (first scene right page) ── */ -.story-title { - font-size: 1.6rem; - color: var(--color-primary); +.toc-list li { + font-family: var(--font-body); + font-size: 0.95rem; + color: var(--ink); + line-height: 1.5; +} + +.toc-list li::before { + font-family: var(--font-display); font-style: italic; - line-height: 1.3; - border-bottom: 2px solid var(--color-secondary); - padding-bottom: 0.5rem; - margin-bottom: 0.5rem; + font-size: 0.8rem; + color: var(--ink-muted); +} + +/* ── Story number label ── */ +.story-number { + display: block; + font-family: var(--font-display); + font-style: normal; + font-weight: 400; + font-size: 0.7rem; + letter-spacing: 0.22em; + text-transform: uppercase; + color: var(--ink-muted); + margin-bottom: 0.55rem; +} + +/* ── Story title ── */ +.story-title { + font-family: var(--font-display); + font-style: italic; + font-weight: 300; + font-size: 1.85rem; + line-height: 1.25; + color: var(--ink); + padding-bottom: 0.6rem; + border-bottom: 1px solid var(--ink-rule); + margin-bottom: 1rem; } /* ── Scene text ── */ .scene-text { - font-size: 1.05rem; - line-height: 1.85; - color: var(--color-text, #1a1a2e); + font-family: var(--font-body); + font-size: 1.02rem; + line-height: 1.9; + color: var(--ink); } .scene-text p { @@ -67,31 +194,32 @@ body { font-style: italic; } -/* Story 9 has dark background — invert text page */ -[data-story="9"].page--text { - background: #0a0a2a; +/* ── Scene ornament ── */ +.scene-ornament { + font-family: var(--font-display); + font-size: 1.4rem; + color: var(--ink-muted); + text-align: center; + margin-top: auto; + padding-top: 0.5rem; } -[data-story="9"] .scene-text, -[data-story="9"] .story-title { - color: #ffffff; -} - -[data-story="9"] .story-title { - border-color: #ff1493; -} - -/* ── Finale ── */ +/* ── Finale overlay ── */ .finale-overlay h2 { - font-size: 2.5rem; - color: white; + font-family: var(--font-display); font-style: italic; - text-shadow: 0 2px 12px rgba(0,0,0,0.6); + font-weight: 300; + font-size: 2.6rem; + color: white; + text-shadow: 0 2px 16px rgba(0, 0, 0, 0.7); + letter-spacing: 0.02em; } .finale-overlay p { - font-size: 1rem; - color: rgba(255,255,255,0.8); - letter-spacing: 0.1em; - margin-top: 0.5rem; + font-family: var(--font-display); + font-style: italic; + font-size: 0.95rem; + color: rgba(255, 255, 255, 0.7); + letter-spacing: 0.12em; + margin-top: 0.6rem; } diff --git a/templates/book.html b/templates/book.html index 6792651..fd8f919 100644 --- a/templates/book.html +++ b/templates/book.html @@ -2,7 +2,6 @@
-Eine Sammlung von
+Zwölf magische Geschichten für die Nacht
+ {% if frontMatter.author %} + + {% endif %}Erste Ausgabe
-Alle Rechte vorbehalten.
+{{ frontMatter.title or 'Das Kaleidoskop der Schlummerwelten' }}
+{% if frontMatter.author %}{{ frontMatter.author }}{% endif %}{% if frontMatter.year %}, {{ frontMatter.year }}{% endif %}
+Erste Ausgabe · Alle Rechte vorbehalten.
Illustrationen: KI-generiert (Dreamy Watercolor / Whimsical Storybook)