Set up book project: Markdown→CSS→PDF pipeline for KDP

Adds the full authoring and build toolchain for "Das Kaleidoskop der
Schlummerwelten" — all 12 story content files in Markdown, Nunjucks
HTML templates, CSS print layout, and Puppeteer-based PDF generation
targeting Amazon KDP (8.5×8.5 in, 0.125in bleed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-03 15:38:07 +02:00
parent b5582a65d6
commit a8ade90ffb
26 changed files with 2219 additions and 1 deletions
+4
View File
@@ -1 +1,5 @@
.claude/
node_modules/
output/book.html
# Keep the PDF in git if you want to track versions, otherwise add:
# output/kaleidoskop.pdf
+293
View File
@@ -0,0 +1,293 @@
# Das Kaleidoskop der Schlummerwelten
A German children's bedtime anthology — 12 independent stories with AI-generated watercolor illustrations — built for [Amazon KDP](https://kdp.amazon.com) (8.5 × 8.5 in, premium color print).
The entire book is written in Markdown and styled with CSS. Running `pnpm book` produces a print-ready PDF.
---
## Project structure
```
kaleidoskop/
├── content/ # One Markdown file per story (+ front matter & finale)
│ ├── 00-front-matter.md
│ ├── 01-der-glaeserne-sternenwald.md
│ ├── ...
│ └── 13-finale-kaleidoskop.md
├── images/ # AI-generated illustrations (300 DPI PNG/JPG)
│ ├── cover/
│ │ └── cover.png
│ ├── 01/
│ │ ├── scene-1.png # One image per scene
│ │ ├── scene-2.png
│ │ ├── scene-3.png
│ │ └── scene-4.png
│ └── ... (02/ through 12/)
├── templates/
│ ├── book.html # Master Nunjucks template (title page, TOC, stories)
│ └── story-spread.html # Per-story layout (image page + text page per scene)
├── styles/
│ ├── print.css # @page rules, bleed, page dimensions
│ ├── layout.css # Page structure, image/text positioning
│ └── typography.css # Fonts, colors, story-specific theming
├── scripts/
│ ├── build.js # Markdown → HTML (via Nunjucks + marked)
│ └── pdf.js # HTML → PDF (via Puppeteer)
└── output/
├── book.html # Intermediate build artifact (gitignored)
└── kaleidoskop.pdf # Final print-ready PDF
```
---
## Prerequisites
- **Node.js** ≥ 18
- **pnpm** — `npm install -g pnpm`
- **Chromium** (ARM64 / WSL2 only — Puppeteer's bundled Chrome is x86-64)
```bash
sudo apt-get install -y chromium
```
---
## Setup
```bash
pnpm install
```
---
## Build commands
| Command | What it does |
|---|---|
| `pnpm build` | Converts Markdown → `output/book.html` |
| `pnpm pdf` | Renders `output/book.html` → `output/kaleidoskop.pdf` |
| `pnpm book` | Both steps in sequence |
| `pnpm watch` | Auto-rebuilds HTML on file changes (no PDF) |
---
## Writing a story
Each story lives in `content/XX-story-name.md`. The file has two parts:
### 1. Frontmatter (YAML)
```yaml
---
number: 1
title: "Der gläserne Sternenwald"
character: Leo
palette:
primary: "#1a3a6b" # Main accent color (used for headings, borders)
secondary: "#c0c0c0" # Secondary accent (ornaments, dividers)
text: "#1a3a6b" # Body text color
background: "#f0f4ff" # Text page background color
scenes:
- image: images/01/scene-1.png
alt: "Das Glühwürmchen am Fenster"
- image: images/01/scene-2.png
alt: "Der Mondlichtpfad"
- image: images/01/scene-3.png
alt: "Der gläserne Sternenwald"
- image: images/01/scene-4.png
alt: "Das schlafende Kind"
---
```
### 2. Body text
Four scene paragraphs separated by `---`. They map to the four scenes in order:
```markdown
Leo lag in seinem gemütlichen Bett... ← Scene 1 text
---
Leo schlüpfte in seine Hausschuhe... ← Scene 2 text
---
Am Ende des Pfades blieben sie stehen... ← Scene 3 text
---
„Es ist Zeit zum Träumen", raunte die Eule. ← Scene 4 text
```
---
## Adding illustrations
Place AI-generated images at the paths listed in the story's frontmatter:
```
images/01/scene-1.png
images/01/scene-2.png
images/01/scene-3.png
images/01/scene-4.png
```
**Before you have images:** The build automatically shows a colored placeholder box (using the story's primary palette color) so you can work on text and layout without waiting for all 48 illustrations.
**Image requirements for KDP:**
- **Resolution:** 300 DPI minimum — upscale AI output with [Upscayl](https://upscayl.org) (free) or Topaz Gigapixel AI
- **Dimensions:** At least 2550 × 2550 px (= 8.5 in × 300 DPI)
- **Format:** PNG or JPG
- **Color space:** RGB (KDP accepts RGB — no CMYK conversion needed)
> See `DOSSIER.md` for the complete AI image prompts for all 48 scenes.
---
## Page layout
Each scene produces two facing pages:
```
┌──────────────┬──────────────┐
│ │ │
│ Full-bleed │ Story text │
│ illustration │ (right pg) │
│ (left pg) │ │
└──────────────┴──────────────┘
```
- 4 scenes × 2 pages = **8 pages per story**
- 12 stories = **96 interior pages**
- Plus title page, copyright, table of contents, and finale = ~**101 pages total**
The story title appears at the top of the first text page. The story-specific color palette is applied via CSS custom properties injected from the frontmatter.
---
## Customizing the design
### Changing fonts
Add a `@font-face` declaration to `styles/typography.css` and update the `font-family` on `body`. Puppeteer will embed the font in the PDF automatically.
```css
@font-face {
font-family: 'MyFont';
src: url('../fonts/MyFont-Regular.woff2') format('woff2');
}
body {
font-family: 'MyFont', Georgia, serif;
}
```
### Changing per-story colors
Edit the `palette` block in the story's frontmatter. Available properties:
| Key | Applied to |
|---|---|
| `primary` | Story title heading, text color, accent borders |
| `secondary` | Ornament ``, dividers |
| `text` | Scene body text |
| `background` | Text page background |
### Dark text pages (e.g. story 9)
Story 9 (*Das Ballett der Nachtfalter*) uses a near-black background. Its text page is overridden in `styles/typography.css`:
```css
[data-story="9"].page--text { background: #0a0a2a; }
[data-story="9"] .scene-text { color: #ffffff; }
```
Add similar overrides for any story that needs special treatment.
### Page size / bleed
Defined in `styles/print.css`:
```css
@page {
size: 8.75in 8.75in; /* 8.5in trim + 0.125in bleed on each side */
margin: 0;
}
.page {
width: 8.75in;
height: 8.75in;
}
```
To change trim size, update both values here and the matching `width`/`height` in `scripts/pdf.js`.
---
## KDP submission checklist
1. **Run `pnpm book`** to generate `output/kaleidoskop.pdf`
2. **Verify page count** is correct in your PDF viewer
3. **Check fonts are embedded:**
```bash
pdfinfo output/kaleidoskop.pdf | grep -i font
# or: pdffonts output/kaleidoskop.pdf
```
4. **Upload to KDP:**
- Log in → Your Books → Create → Paperback
- Trim size: **8.5 × 8.5 inches**
- Interior: **Premium Color**, 100 gsm
- Bleed: **Yes (with bleed)**
- Upload `output/kaleidoskop.pdf` as the interior file
5. **Use the KDP online previewer** to check layout before ordering a proof
6. **Order a proof copy** before publishing — real print color may differ from screen
---
## AI image prompts
All prompts are in [`DOSSIER.md`](./DOSSIER.md) under each story's `KI-Bild-Prompts` section. The global style guide (paste at the start of every prompt):
```
whimsical storybook illustration, dreamy watercolor style, soft cinematic lighting,
magical atmosphere, high resolution, 300 DPI
```
Recommended tools: **Midjourney**, **DALL-E 3**, **Ideogram**, or **Adobe Firefly**.
---
## Git & large files
Images are tracked in git as binary files. If the repo grows large (each 300 DPI PNG can be 520 MB × 48 scenes), consider enabling [Git LFS](https://git-lfs.com):
```bash
git lfs install
git lfs track "images/**/*.png" "images/**/*.jpg"
git add .gitattributes
```
---
## Troubleshooting
**`pnpm pdf` fails with "Syntax error: redirection unexpected"**
Puppeteer's bundled Chrome is x86-64 but your system is ARM64. Install system Chromium:
```bash
sudo apt-get install -y chromium
```
The script auto-detects `/usr/bin/chromium` and uses it instead.
**Images are not showing in the PDF**
Check that the image path in the story's frontmatter matches the actual file location exactly (case-sensitive).
**Text is cut off on a page**
Increase the font size or reduce padding in `styles/typography.css` and `styles/layout.css`. You can also split a long scene text into shorter paragraphs.
**Colors look different in print vs. screen**
KDP calibrates for print. Order a proof copy to see the real result before publishing. Dark tones (story 8 — bronze/chocolate) benefit from premium color paper (100 gsm).
+7
View File
@@ -0,0 +1,7 @@
---
type: front-matter
title: "Das Kaleidoskop der Schlummerwelten"
subtitle: "Zwölf magische Geschichten für die Nacht"
author: ""
year: "2025"
---
+33
View File
@@ -0,0 +1,33 @@
---
number: 1
title: "Der gläserne Sternenwald"
character: Leo
palette:
primary: "#1a3a6b"
secondary: "#c0c0c0"
text: "#1a3a6b"
background: "#f0f4ff"
scenes:
- image: images/01/scene-1.png
alt: "Das Glühwürmchen am Fenster"
- image: images/01/scene-2.png
alt: "Der Mondlichtpfad"
- image: images/01/scene-3.png
alt: "Der gläserne Sternenwald"
- image: images/01/scene-4.png
alt: "Das schlafende Kind"
---
Leo lag in seinem gemütlichen Bett und beobachtete die Schatten der Blätter an seiner Wand. Plötzlich hörte er ein leises *Pling-Pling* am Fenster. Dort saß ein winziges Glühwürmchen, das so hell leuchtete wie ein kleiner Diamant. Es zwinkerte Leo zu und tippte mit seinen Beinchen gegen die Scheibe, als wollte es sagen: „Komm mit, das Abenteuer wartet!"
---
Leo schlüpfte in seine Hausschuhe und öffnete das Fenster. Vor ihm erstreckte sich ein schimmernder Pfad aus purem Mondlicht, der direkt in den Garten und darüber hinaus führte. Jeder Schritt auf diesem Pfad fühlte sich an, als würde er über weiche Wolken gehen. Das Glühwürmchen tanzte voraus und wies ihm den Weg in das geheimnisvolle Dunkel der Nacht.
---
Am Ende des Pfades blieben sie stehen. Leo staunte: Vor ihm erstreckte sich ein Wald, in dem die Bäume nicht aus Holz, sondern aus buntem, durchsichtigem Glas bestanden. Wenn der Wind sanft durch die Zweige wehte, klangen sie wie tausend kleine Windspiele. In der Krone einer gläsernen Buche saß eine große, weise Eule mit Federn, die wie Silber glänzten. Sie erzählte Leo mit ruhiger Stimme die Geschichten der Sterne, während der ganze Wald im Rhythmus des Mondes funkelte.
---
„Es ist Zeit zum Träumen", raunte die Eule. Das Glühwürmchen begleitete Leo zurück über den Lichtpfad bis zu seinem Fenster. Leo kuschelte sich tief in seine Decke, die noch ein wenig nach Sternenstaub duftete. Die sanfte Melodie der Glasbäume klang noch leise in seinen Ohren nach, während er glücklich und müde die Augen schloss.
+33
View File
@@ -0,0 +1,33 @@
---
number: 2
title: "Das Wolkenschiff zum Mondschatten"
character: Mia
palette:
primary: "#2d1b69"
secondary: "#b8c4e8"
text: "#2d1b69"
background: "#f0f2ff"
scenes:
- image: images/02/scene-1.png
alt: "Das Bett wird zum Schiff"
- image: images/02/scene-2.png
alt: "Flug über die Stadt"
- image: images/02/scene-3.png
alt: "Die Wolken-Tiere"
- image: images/02/scene-4.png
alt: "Zurück im Zimmer"
---
Als Mia sich heute Abend in ihre Kissen kuschelte, fühlten sie sich plötzlich viel weicher an als sonst. Ihr Bett begann ganz sacht zu schaukeln, wie eine Wiege im Wind. Mit einem Mal bemerkte sie, dass die Beine ihres Bettes verschwunden waren und stattdessen dicke, weiße Wattewölkchen unter ihr hervorguckten.
---
„Leinen los!", flüsterte der Wind. Mias Bett erhob sich lautlos vom Boden und schwebte durch das offene Fenster hinaus in die Nacht. Sie segelte höher und höher, vorbei an den Dächern der schlafenden Stadt, bis sie mitten in einem Meer aus Wolken war, die im Mondlicht silbern glänzten. Ihr Wolkenschiff glitt so ruhig dahin, dass Mia fast vergessen hätte, dass sie eigentlich im Schlafzimmer war.
---
Das Schiff steuerte auf eine geheime Insel zu, die mitten am Himmel schwebte. Hier waren die Wolken nicht einfach nur Nebel sie hatten Formen! Mia sah riesige Wolkenelefanten, die mit ihren Rüsseln Sternenstaub pusteten, und Wolkenhasen, die über den Mondschatten hüpften. Es war die Insel der sanften Riesen, wo alles leise und weich war. Ein kleiner Wolkenhund kuschelte sich an Mias Seite und schenkte ihr ein wohliges Gefühl der Geborgenheit.
---
Das Wolkenschiff drehte langsam bei und trug Mia zurück in ihr Zimmer. Ganz sacht setzte das Bett wieder auf dem Teppich auf, und die Wolken verwandelten sich zurück in Mias gewohnte Bettdecke. Sie war nun so schwer und schläfrig wie ein kleiner Bär im Winterschlaf. Mit dem Bild der tanzenden Wolkenelefanten im Kopf glitt sie in einen tiefen, traumlosen Schlaf.
@@ -0,0 +1,33 @@
---
number: 3
title: "Die Werkstatt der Traumweber"
character: Elias
palette:
primary: "#7a4e00"
secondary: "#d4a017"
text: "#7a4e00"
background: "#fff8e8"
scenes:
- image: images/03/scene-1.png
alt: "Der goldene Faden"
- image: images/03/scene-2.png
alt: "Die Samttreppe"
- image: images/03/scene-3.png
alt: "Die Traumweber-Werkstatt"
- image: images/03/scene-4.png
alt: "Eingekuschelt im Lichttuch"
---
Elias konnte nicht einschlafen. Er starrte auf einen winzigen, goldenen Faden, der unter seiner Bettdecke hervorguckte und im Dunkeln leuchtete. Neugierig zog er daran, doch der Faden wurde immer länger und führte ihn wie eine glitzernde Spur direkt unter sein Bett.
---
Elias krabbelte dem Faden hinterher und stellte fest, dass sein Teppich plötzlich zu einer Treppe aus weichem Samt geworden war. Stufe um Stufe stieg er hinab, tiefer als es eigentlich möglich war, bis er das sanfte Surren von Spinnrädern hörte. Die Luft roch hier unten nach warmer Vanille und frisch gewaschener Wolle.
---
Er betrat eine riesige, warme Halle: die Werkstatt der Traumweber. Überall arbeiteten freundliche kleine Wesen mit flinken Fingern an großen Webstühlen. Sie fingen Mondstrahlen mit ihren Händen ein und verwebten sie mit buntem Sternenstaub zu leuchtenden Tüchern. Ein Weber lächelte Elias zu: „Wähle eine Farbe für deinen Traum, kleiner Gast." Elias entschied sich für ein tiefes Saphirblau mit tanzenden Goldpünktchen.
---
Die Weber wickelten Elias sanft in ein Tuch aus purem Sternenlicht ein. Er fühlte sich augenblicklich schwer und unendlich geborgen. Der goldene Faden führte ihn sanft zurück nach oben in sein Zimmer. Er kuschelte sich in sein Bett, und noch bevor er die Augen ganz schließen konnte, war er bereits auf dem Weg in seinen saphirblauen Traum.
@@ -0,0 +1,33 @@
---
number: 4
title: "Der Drache, der Seifenblasen atmet"
character: Sophie
palette:
primary: "#8b0000"
secondary: "#e8a0b0"
text: "#8b0000"
background: "#fff5f5"
scenes:
- image: images/04/scene-1.png
alt: "Der Drache am Fenster"
- image: images/04/scene-2.png
alt: "Flug über die Gärten"
- image: images/04/scene-3.png
alt: "Die Seifenblasen"
- image: images/04/scene-4.png
alt: "Die schützende Blase"
---
Ein leises Scharren auf dem Sims ließ Sophie aufhorchen. Als sie vorsichtig aus dem Fenster spähte, traute sie ihren Augen nicht: Ein kleiner Drache mit Schuppen so rot wie reife Erdbeeren saß dort und hielt sich mit seinen dicken Tatzen fest. Er sah Sophie aus großen, freundlichen Augen an und öffnete das Maul doch statt Feuer kam nur ein sanftes Glucksen heraus.
---
Der kleine Drache breitete seine Flügel aus, die wie bunte Kirchenfenster schimmerten. Sophie kletterte behutsam auf seinen Rücken, der sich warm und fest anfühlte. Mit einem kräftigen Satz schwangen sie sich in die kühle Nachtluft. Sie flogen über die schlafenden Gärten, während der Drache bei jedem Atemzug kleine, schillernde Funken hinterließ.
---
Sie landeten auf einem flachen Dach, von dem aus man den ganzen Sternenhimmel sehen konnte. Der Drache begann nun, riesige Seifenblasen zu atmen. Jede Blase war ein kleines Fenster in ein anderes Abenteuer: Sophie sah tanzende Blumen, lachende Monde und Wälder aus Zuckerwatte. Sie musste nur eine Blase berühren, und schon erfüllte sie ein Gefühl von purem Glück und tiefer Ruhe.
---
Müde vom Staunen brachte der Drache Sophie zurück in ihr Zimmer. Er pustete eine letzte, besonders große und warme Seifenblase direkt über ihr Bett, die sanft wie eine Schutzhülle über ihr schwebte. Sophie kuschelte sich ein, während der rote Drache zum Abschied leise schnurrte. Mit dem Schimmern der Blasen in den Augen schlief sie friedlich ein.
+33
View File
@@ -0,0 +1,33 @@
---
number: 5
title: "Das Echo der Sternenmelodie"
character: Lukas
palette:
primary: "#4a0080"
secondary: "#d4af37"
text: "#4a0080"
background: "#f8f0ff"
scenes:
- image: images/05/scene-1.png
alt: "Der gefallene Stern"
- image: images/05/scene-2.png
alt: "Die Pusteblumen-Reise"
- image: images/05/scene-3.png
alt: "Das Sternen-Echo"
- image: images/05/scene-4.png
alt: "Schlaf unter Samt"
---
Lukas schaute aus dem Fenster, als plötzlich ein Lichtstrahl so hell wie flüssiges Silber in seinen Garten fiel. Es war eine kleine Sternschnuppe, die nicht einfach verglühte, sondern sanft wie eine Feder auf dem Rasen landete. Als Lukas barfuß nach draußen schlich, hörte er es: Der kleine Stern summte eine Melodie, die so rein und klar klang wie eine Glasharfe.
---
„Ich habe meinen Weg verloren", flüsterte der Stern leise. Lukas nahm ihn vorsichtig in seine Hände er war warm und kribbelte wie Brausepulver. Gemeinsam stiegen sie auf eine riesige Pusteblume, die vom Nachtwind erfasst wurde. Sie schwebten höher und höher, vorbei an Lavendelwolken, die ihren beruhigenden Duft verströmten, bis sie den Rand des großen Sternenzeltes erreichten.
---
Oben angekommen, stimmte der kleine Stern sein Lied wieder an. Plötzlich begannen alle anderen Sterne am Himmel zu antworten. Es entstand ein gewaltiges Echo aus Licht und Klang, das den ganzen Himmel zum Pulsieren brachte. Lukas durfte mitsummen und spürte, wie die Musik jede kleine Sorge aus seinem Kopf vertrieb und durch funkelnde Ruhe ersetzte.
---
Der Nachthimmel breitete eine schwere Decke aus dunklem Samt über Lukas aus. Sanft trug ihn der Wind zurück in sein Zimmer, während das Echo der Sternenmelodie immer leiser und sanfter wurde. Lukas kuschelte sich tief in seine Kissen. Er wusste nun, dass die Sterne jede Nacht für ihn sangen, und mit diesem tröstlichen Gedanken schlief er sofort ein.
@@ -0,0 +1,33 @@
---
number: 6
title: "Die Bibliothek der ungeschriebenen Märchen"
character: Clara
palette:
primary: "#3b1a08"
secondary: "#2e7d32"
text: "#3b1a08"
background: "#f5f0e8"
scenes:
- image: images/06/scene-1.png
alt: "Der gelehrte Kater"
- image: images/06/scene-2.png
alt: "Der geheime Gang"
- image: images/06/scene-3.png
alt: "Die fliegenden Bücher"
- image: images/06/scene-4.png
alt: "Der Ohrensessel"
---
Clara hörte ein leises *Miau* unter ihrem Schreibtisch. Dort saß ein Kater mit einem Fell so schwarz wie die Nacht und Augen, die wie zwei goldene Knöpfe funkelten. Er trug eine winzige Brille auf der Nase und hielt eine silberne Einladungskarte im Maul, auf der in geschwungenen Buchstaben stand: „Nur für Träumer".
---
Der Kater tippte gegen eine lose Tapetenecke, die sich sogleich wie eine schwere Holztür zur Seite schwingen ließ. Clara folgte ihm durch einen schmalen Gang, der nach altem Papier und getrockneten Rosen duftete. Mit jedem Schritt wurden die Wände um sie herum höher, bis sie in einer Halle standen, deren Decke so weit entfernt war wie der Himmel selbst.
---
Sie waren in der Bibliothek der ungeschriebenen Märchen. Hier standen die Bücher nicht starr in den Regalen sie flatterten wie bunte Vögel durch die Luft. Wenn Clara die Hand ausstreckte, landete ein Buch sanft auf ihrer Palme und begann, ihr mit leiser Stimme eine Geschichte zu erzählen, die noch nie zuvor jemand gehört hatte. Es war ein Märchen, das nur für sie in diesem Moment erfunden wurde.
---
Der Kater führte Clara zu einer Leseecke mit riesigen, weichen Ohrensesseln. „Jede Geschichte braucht ein Ende, damit ein Traum beginnen kann", flüsterte er. Clara kuschelte sich in den Samt des Sessels und spürte, wie ihre Augenlider schwer wurden. Als sie das nächste Mal blinzelte, lag sie wieder in ihrem eigenen Bett, doch das leise Flüstern der Märchen begleitete sie bis in den tiefsten Schlaf.
@@ -0,0 +1,33 @@
---
number: 7
title: "Das Picknick auf dem Silberberg"
character: Ben
palette:
primary: "#4a4a6a"
secondary: "#e8e8f0"
text: "#4a4a6a"
background: "#f8f8ff"
scenes:
- image: images/07/scene-1.png
alt: "Die silbernen Einhörner"
- image: images/07/scene-2.png
alt: "Die Regenbogenbrücke"
- image: images/07/scene-3.png
alt: "Das Picknick auf dem Silberberg"
- image: images/07/scene-4.png
alt: "Eingehüllt in Silbermähnen"
---
Ben wunderte sich über den sanften Duft von frisch gebackenen Keksen, der durch sein Zimmer zog. Als er aus dem Fenster blickte, standen dort zwei Einhörner im Mondschein, deren Fell wie flüssiges Silber glänzte. Sie neigten ihre Köpfe und luden ihn mit einem sanften Schnauben ein, ihnen zu folgen.
---
Ben kletterte auf den Rücken des größeren Einhorns, das sich so weich wie Seide anfühlte. Mit federleichten Sprüngen galoppierten sie über eine Brücke aus Regenbogenfarben, die im Dunkeln leuchtete. Sie stiegen immer höher, bis sie den Gipfel des Silberbergs erreichten, der über den Wolken thronte und das Licht der Sterne wie ein riesiger Spiegel einfing.
---
Auf dem Gipfel war eine silberne Picknickdecke ausgebreitet. Es gab Sternenkekse, die nach Honig schmeckten und beim ersten Biss leise wie kleine Glocken knisterten. Dazu tranken sie schäumende Mondmilch aus Bechern, die aus reinem Bergkristall geformt waren. Während sie aßen, erzählten die Einhörner Ben, dass jeder Krümel, der herunterfiel, am Himmel zu einem neuen kleinen Stern wurde.
---
Nach dem Picknick fühlte sich Ben wunderbar satt und schläfrig. Die Einhörner hüllten ihn in ihre langen, silbrigen Mähnen ein, die ihn wie eine warme Decke wärmten. Sanft trugen sie ihn den Regenbogen hinunter zurück in sein Zimmer. Er schlüpfte unter seine Decke und hatte noch immer den süßen Geschmack von Honig auf den Lippen, während er friedlich einschlummerte.
@@ -0,0 +1,33 @@
---
number: 8
title: "Der Uhrmacher der Zeitlosen Stunde"
character: Lina
palette:
primary: "#5c3a1e"
secondary: "#b87333"
text: "#5c3a1e"
background: "#fdf5e8"
scenes:
- image: images/08/scene-1.png
alt: "Die Schokoladenuhr"
- image: images/08/scene-2.png
alt: "Die Kupfertreppe"
- image: images/08/scene-3.png
alt: "Die Uhrmacher-Werkstatt"
- image: images/08/scene-4.png
alt: "Die goldene Sanduhr"
---
Lina wunderte sich, warum ihr Wecker plötzlich nicht mehr tickte, sondern leise wie eine schmelzende Praline knackte. Als sie das Licht anmachte, sah sie, dass die Zeiger aus feinster Zartbitterschokolade bestanden. Eine kleine, hölzerne Tür öffnete sich in der Wand ihres Zimmers, aus der ein warmer Duft nach Kakao und Zimt strömte.
---
Hinter der Tür wartete eine Treppe aus polierten Kupfermünzen. Lina stieg hinab und merkte, dass sich alles um sie herum verlangsamte. Die Luft fühlte sich dick und weich an, als würde sie durch flüssigen Honig spazieren. Am Ende der Treppe gelangte sie in eine Werkstatt, in der tausende Uhren hingen, die alle keine Ziffern, sondern kleine schlafende Gesichter hatten.
---
Dort traf sie den Uhrmacher, einen freundlichen alten Mann mit einer Schürze aus Marzipan. „In der Nacht halten wir die Zeit an", erklärte er und reichte Lina ein Zahnrad aus Vollmilchschokolade. „Hier gibt es keine Eile, nur Ruhe." Gemeinsam drehten sie an einer großen, goldenen Kurbel, bis die ganze Welt draußen in einem zeitlosen, friedlichen Schlummer versank. Lina durfte auf den großen, runden Zahnrädern Karussell fahren, die sich so langsam drehten, dass sie dabei fast einschlief.
---
Der Uhrmacher schenkte Lina eine kleine Sanduhr, die mit goldenem Sternenstaub gefüllt war. „Wenn der letzte Krümel fällt, bist du im Land der Träume", flüsterte er. Lina kehrte in ihr Bett zurück und beobachtete, wie der goldene Staub leise rieselte. Ihr ganzes Zimmer fühlte sich nun so warm und zeitlos an wie die Werkstatt, und mit einem zufriedenen Lächeln schloss sie die Augen.
+33
View File
@@ -0,0 +1,33 @@
---
number: 9
title: "Das Ballett der Nachtfalter"
character: Paul
palette:
primary: "#0a0a1a"
secondary: "#ff1493"
text: "#ffffff"
background: "#0a0a2a"
scenes:
- image: images/09/scene-1.png
alt: "Das Flattern am Fenster"
- image: images/09/scene-2.png
alt: "Die Reise über die Laternenwiese"
- image: images/09/scene-3.png
alt: "Das Ballett der Falter"
- image: images/09/scene-4.png
alt: "Der leuchtende Kissenstaub"
---
Ein sanftes Flattern, so leise wie fallender Schnee, weckte Paul aus seinem Halbschlaf. Vor seinem Fenster tanzten hunderte kleine Lichter, die wie lebendige Edelsteine aussahen. Es waren Nachtfalter, deren Flügel im Dunkeln leuchteten und bunte Muster in die Luft malten, die sofort wieder verblassten.
---
Einer der Falter, so groß wie Pauls Hand und in den Farben eines Pfaus schimmernd, setzte sich auf seine Schulter. Wie von Zauberhand wurde Paul ganz leicht und schwebte gemeinsam mit den Faltern aus dem Fenster. Sie flogen tief über eine Wiese, auf der Laternenblumen wuchsen, die bei jeder Berührung ein sanftes *Ding* von sich gaben.
---
In der Mitte der Wiese begannen die Falter ihr Ballett. Sie wirbelten im Kreis und bildeten leuchtende Spiralen und Sternbilder, die so nah waren, dass Paul sie fast anfassen konnte. Der Tanz war so beruhigend, dass sich Pauls Herzschlag dem Rhythmus der Flügel anpasste. Jede Bewegung der Falter schien zu flüstern: „Alles ist gut, die Welt ruht."
---
Als die Laternenblumen begannen, ihre Kelche für die Nacht zu schließen, trugen die Falter Paul sanft zurück in sein Zimmer. Sie ließen ein wenig von ihrem schimmernden Flügelstaub auf seinem Kopfkissen zurück, der wie ein Nachtlicht leuchtete. Paul kuschelte sich ein, schloss die Augen und sah den Tanz der Falter noch lange vor seinem inneren Auge weitergehen.
@@ -0,0 +1,33 @@
---
number: 10
title: "Die Stadt der schlafenden Riesen"
character: Jona
palette:
primary: "#2d4a1e"
secondary: "#8b6914"
text: "#2d4a1e"
background: "#f5f0e8"
scenes:
- image: images/10/scene-1.png
alt: "Die atmenden Hügel"
- image: images/10/scene-2.png
alt: "Der moosige Riesenarm"
- image: images/10/scene-3.png
alt: "Die Kissen-Stadt"
- image: images/10/scene-4.png
alt: "Der sanfte Rückzug"
---
Jona wunderte sich über ein tiefes, rhythmisches Brummen, das leise durch seine Zimmerwände vibrierte. Es klang wie das Schnurren einer riesigen Katze, nur viel tiefer und ruhiger. Als er aus dem Fenster sah, bemerkte er, dass die Hügel hinter dem Haus sanft auf und ab stiegen, als würden sie atmen.
---
Ein großer, weicher Arm so dick wie ein hundertjähriger Baumstamm und bedeckt mit Moos senkte sich vor Jonas Fenster. Vorsichtig kletterte er darauf und fühlte sich sofort geborgen, als wäre er in eine riesige Wolldecke gewickelt. Der Riese hob ihn behutsam hoch und trug ihn über die schlafende Welt zu einem Ort, den man auf keiner Landkarte findet.
---
Sie erreichten die Stadt der schlafenden Riesen. Hier bestanden die Häuser aus riesigen Kissen, und die Straßen waren mit weichem Samt ausgelegt. Überall lagen freundliche Riesen im Gras, die so tief schliefen, dass auf ihren Bäuchen kleine Wälder gewachsen waren. Jona durfte auf einer Wolken-Hängematte schaukeln, die zwischen den Fingern eines schlafenden Riesen gespannt war, während die ganze Stadt im Takt ihres friedlichen Atems wippte.
---
„Schlaf ist das größte Abenteuer", schien der Wind zu flüstern. Der Riese brachte Jona zurück und legte ihn so sacht in sein Bett, dass nicht einmal sein Teddybär aufwachte. Jona fühlte sich nun selbst wie ein kleiner Riese, stark und unendlich müde. Er schloss die Augen und ließ sich vom fernen Brummen der Stadt direkt in den Schlaf wiegen.
+33
View File
@@ -0,0 +1,33 @@
---
number: 11
title: "Die Reise im Teetassen-Boot"
character: Mila
palette:
primary: "#006666"
secondary: "#40e0d0"
text: "#006666"
background: "#f0fffe"
scenes:
- image: images/11/scene-1.png
alt: "Der Sternenlicht-Fluss"
- image: images/11/scene-2.png
alt: "Die Reise im Porzellanboot"
- image: images/11/scene-3.png
alt: "Die singenden Opalfische"
- image: images/11/scene-4.png
alt: "Das Ufer aus Traumstaub"
---
Mila bemerkte ein leises Glucksen unter ihrem Bett, als würde dort ein kleiner Bach fließen. Als sie nachsah, war ihr Teppich verschwunden. Stattdessen schimmerte dort ein breiter Fluss aus flüssigem Sternenlicht, der sanft gegen die Wände ihres Zimmers plätscherte. In der Mitte des Flusses trieb eine riesige, wunderschön verzierte Teetasse aus feinstem Porzellan.
---
Vorsichtig kletterte Mila in die Teetasse, die innen mit weichen Samtkissen ausgepolstert war. Mit einem silbernen Löffel als Ruder stieß sie sich ab. Das Teetassen-Boot glitt lautlos aus dem Zimmer und hinaus in eine Welt, in der die Bäume wie Korallen aussahen und die Luft nach frischer Meeresbrise duftete. Die Strömung trug sie sanft dahin, während die Sterne sich im Wasser spiegelten.
---
Plötzlich tauchten neben ihrem Boot singende Fische auf, deren Schuppen wie Opale leuchteten. Sie erzählten Mila Geschichten von versunkenen Zauberreichen, in denen die Zeit rückwärts läuft und Spielzeuge niemals kaputtgehen. Mila musste nur ihre Hand in das warme Sternenlicht-Wasser halten, und schon fühlte sie sich so leicht und sorgenfrei wie eine kleine Blase.
---
Die Teetasse steuerte langsam auf ein Ufer zu, das aus feinstem, weißem Traumstaub bestand. Ganz sacht setzte das Boot auf, und Mila merkte, wie sie in die weichen Kissen der Tasse zurücksank. Das leise Glucksen des Flusses wurde zu einem fernen Wiegenlied. Mila schloss die Augen und ließ sich von der sanften Strömung direkt in das Land der Träume tragen.
@@ -0,0 +1,33 @@
---
number: 12
title: "Der Garten der flüsternden Laternen"
character: Anton
palette:
primary: "#2a1a00"
secondary: "#ffd700"
text: "#2a1a00"
background: "#fffbf0"
scenes:
- image: images/12/scene-1.png
alt: "Das Licht unter der Tür"
- image: images/12/scene-2.png
alt: "Der Garten der Laternen"
- image: images/12/scene-3.png
alt: "Der Kaleidoskop-Baum"
- image: images/12/scene-4.png
alt: "Schlaf mit der goldenen Erinnerung"
---
Als Anton das letzte Mal für heute gähnte, bemerkte er ein sanftes, buntes Licht, das unter seiner Zimmertür hindurchschimmerte. Er öffnete sie einen Spaltbreit und sah keinen Flur, sondern einen Pfad aus weichem Moos, der von schwebenden, bunten Lichtern gesäumt war.
---
Anton folgte dem Pfad und betrat einen verborgenen Garten. Hier wuchsen keine gewöhnlichen Äpfel an den Bäumen, sondern leuchtende Laternen in allen Formen und Farben. Sie schwangen sanft im Wind und gaben ein leises, beruhigendes Flüstern von sich. Jede Laterne, an der er vorbeiging, erzählte ihm im Vorbeigehen eine besonders schöne Erinnerung an den vergangenen Tag.
---
In der Mitte des Gartens stand der größte Baum von allen. Seine Laternen leuchteten in allen Farben des Regenbogens genau wie ein Kaleidoskop. Anton durfte sich eine Laterne aussuchen, die er mit in seine Träume nehmen wollte. Er wählte eine goldene, die nach Geborgenheit duftete. Als er sie berührte, verwandelte sich der ganze Garten in ein Meer aus sanftem Sternenstaub, das ihn wie eine warme Decke einhüllte.
---
Anton fühlte sich nun so leicht und glücklich wie noch nie. Der Garten der Laternen begleitete ihn zurück bis an sein Bett. Er legte die goldene Erinnerung unter sein Kissen und spürte, wie die Ruhe des Gartens in sein Herz floss. Mit einem tiefen Seufzer schloss er die Augen, bereit für die schönsten Träume der Welt.
+6
View File
@@ -0,0 +1,6 @@
---
type: finale
title: "Das Kaleidoskop"
subtitle: "Alle zwölf Welten auf einen Blick"
image: images/cover/finale.png
---
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
{
"name": "kaleidoskop-der-schlummerwelten",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "node scripts/build.js",
"pdf": "node scripts/pdf.js",
"book": "pnpm build && pnpm pdf",
"watch": "node --watch scripts/build.js"
},
"pnpm": {
"onlyBuiltDependencies": ["puppeteer"]
},
"dependencies": {
"gray-matter": "^4.0.3",
"marked": "^12.0.0",
"nunjucks": "^3.2.4",
"puppeteer": "^24.42.0"
}
}
+985
View File
@@ -0,0 +1,985 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
gray-matter:
specifier: ^4.0.3
version: 4.0.3
marked:
specifier: ^12.0.0
version: 12.0.2
nunjucks:
specifier: ^3.2.4
version: 3.2.4
puppeteer:
specifier: ^24.42.0
version: 24.42.0
packages:
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.28.5':
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
'@puppeteer/browsers@2.13.0':
resolution: {integrity: sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==}
engines: {node: '>=18'}
hasBin: true
'@tootallnate/quickjs-emscripten@0.23.0':
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
'@types/node@25.6.0':
resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
'@types/yauzl@2.10.3':
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
a-sync-waterfall@1.0.1:
resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==}
agent-base@7.1.4:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
ast-types@0.13.4:
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
engines: {node: '>=4'}
b4a@1.8.1:
resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==}
peerDependencies:
react-native-b4a: '*'
peerDependenciesMeta:
react-native-b4a:
optional: true
bare-events@2.8.2:
resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==}
peerDependencies:
bare-abort-controller: '*'
peerDependenciesMeta:
bare-abort-controller:
optional: true
bare-fs@4.7.1:
resolution: {integrity: sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==}
engines: {bare: '>=1.16.0'}
peerDependencies:
bare-buffer: '*'
peerDependenciesMeta:
bare-buffer:
optional: true
bare-os@3.9.1:
resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==}
engines: {bare: '>=1.14.0'}
bare-path@3.0.0:
resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==}
bare-stream@2.13.1:
resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==}
peerDependencies:
bare-abort-controller: '*'
bare-buffer: '*'
bare-events: '*'
peerDependenciesMeta:
bare-abort-controller:
optional: true
bare-buffer:
optional: true
bare-events:
optional: true
bare-url@2.4.2:
resolution: {integrity: sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==}
basic-ftp@5.3.1:
resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==}
engines: {node: '>=10.0.0'}
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
chromium-bidi@14.0.0:
resolution: {integrity: sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==}
peerDependencies:
devtools-protocol: '*'
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
commander@5.1.0:
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
engines: {node: '>= 6'}
cosmiconfig@9.0.1:
resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==}
engines: {node: '>=14'}
peerDependencies:
typescript: '>=4.9.5'
peerDependenciesMeta:
typescript:
optional: true
data-uri-to-buffer@6.0.2:
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
engines: {node: '>= 14'}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
degenerator@5.0.1:
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
engines: {node: '>= 14'}
devtools-protocol@0.0.1595872:
resolution: {integrity: sha512-kRfgp8vWVjBu/fbYCiVFiOqsCk3CrMKEo3WbgGT2NXK2dG7vawWPBljixajVgGK9II8rDO9G0oD0zLt3I1daRg==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
end-of-stream@1.4.5:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
escodegen@2.1.0:
resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
engines: {node: '>=6.0'}
hasBin: true
esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}
hasBin: true
estraverse@5.3.0:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
events-universal@1.0.1:
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
extend-shallow@2.0.1:
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
engines: {node: '>=0.10.0'}
extract-zip@2.0.1:
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
engines: {node: '>= 10.17.0'}
hasBin: true
fast-fifo@1.3.2:
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
fd-slicer@1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-stream@5.2.0:
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
engines: {node: '>=8'}
get-uri@6.0.5:
resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==}
engines: {node: '>= 14'}
gray-matter@4.0.3:
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
engines: {node: '>=6.0'}
http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
ip-address@10.2.0:
resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
engines: {node: '>= 12'}
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
is-extendable@0.1.1:
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
engines: {node: '>=0.10.0'}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@3.14.2:
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
hasBin: true
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
kind-of@6.0.3:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
lru-cache@7.18.3:
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
engines: {node: '>=12'}
marked@12.0.2:
resolution: {integrity: sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==}
engines: {node: '>= 18'}
hasBin: true
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
netmask@2.1.1:
resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==}
engines: {node: '>= 0.4.0'}
nunjucks@3.2.4:
resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==}
engines: {node: '>= 6.9.0'}
hasBin: true
peerDependencies:
chokidar: ^3.3.0
peerDependenciesMeta:
chokidar:
optional: true
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
pac-proxy-agent@7.2.0:
resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==}
engines: {node: '>= 14'}
pac-resolver@7.0.1:
resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==}
engines: {node: '>= 14'}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
parse-json@5.2.0:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
progress@2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}
proxy-agent@6.5.0:
resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==}
engines: {node: '>= 14'}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
pump@3.0.4:
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
puppeteer-core@24.42.0:
resolution: {integrity: sha512-T4zXokk/izH01fYPhyyev1A4piWiOKrYq7CUFpdoYQxmOnXoV6YjUabmfIjCYkNspSoAXIxRid3Tw+Vg0fthYg==}
engines: {node: '>=18'}
puppeteer@24.42.0:
resolution: {integrity: sha512-94MoPfFp2eY3eYIMdINkez4IOP5TMHntlZbVx06fHlQTtiQiYgaY0L2Zzfod8PVUkPqP7m3Qlre2v8YS8cudPA==}
engines: {node: '>=18'}
hasBin: true
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
section-matter@1.0.0:
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
engines: {node: '>=4'}
semver@7.7.4:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
hasBin: true
smart-buffer@4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
socks-proxy-agent@8.0.5:
resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
engines: {node: '>= 14'}
socks@2.8.8:
resolution: {integrity: sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==}
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
streamx@2.25.0:
resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-bom-string@1.0.0:
resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
engines: {node: '>=0.10.0'}
tar-fs@3.1.2:
resolution: {integrity: sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==}
tar-stream@3.2.0:
resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==}
teex@1.0.1:
resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==}
text-decoder@1.2.7:
resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typed-query-selector@2.12.2:
resolution: {integrity: sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==}
undici-types@7.19.2:
resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
webdriver-bidi-protocol@0.4.1:
resolution: {integrity: sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@8.20.0:
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
snapshots:
'@babel/code-frame@7.29.0':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
js-tokens: 4.0.0
picocolors: 1.1.1
'@babel/helper-validator-identifier@7.28.5': {}
'@puppeteer/browsers@2.13.0':
dependencies:
debug: 4.4.3
extract-zip: 2.0.1
progress: 2.0.3
proxy-agent: 6.5.0
semver: 7.7.4
tar-fs: 3.1.2
yargs: 17.7.2
transitivePeerDependencies:
- bare-abort-controller
- bare-buffer
- react-native-b4a
- supports-color
'@tootallnate/quickjs-emscripten@0.23.0': {}
'@types/node@25.6.0':
dependencies:
undici-types: 7.19.2
optional: true
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 25.6.0
optional: true
a-sync-waterfall@1.0.1: {}
agent-base@7.1.4: {}
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
argparse@1.0.10:
dependencies:
sprintf-js: 1.0.3
argparse@2.0.1: {}
asap@2.0.6: {}
ast-types@0.13.4:
dependencies:
tslib: 2.8.1
b4a@1.8.1: {}
bare-events@2.8.2: {}
bare-fs@4.7.1:
dependencies:
bare-events: 2.8.2
bare-path: 3.0.0
bare-stream: 2.13.1(bare-events@2.8.2)
bare-url: 2.4.2
fast-fifo: 1.3.2
transitivePeerDependencies:
- bare-abort-controller
- react-native-b4a
bare-os@3.9.1: {}
bare-path@3.0.0:
dependencies:
bare-os: 3.9.1
bare-stream@2.13.1(bare-events@2.8.2):
dependencies:
streamx: 2.25.0
teex: 1.0.1
optionalDependencies:
bare-events: 2.8.2
transitivePeerDependencies:
- react-native-b4a
bare-url@2.4.2:
dependencies:
bare-path: 3.0.0
basic-ftp@5.3.1: {}
buffer-crc32@0.2.13: {}
callsites@3.1.0: {}
chromium-bidi@14.0.0(devtools-protocol@0.0.1595872):
dependencies:
devtools-protocol: 0.0.1595872
mitt: 3.0.1
zod: 3.25.76
cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
commander@5.1.0: {}
cosmiconfig@9.0.1:
dependencies:
env-paths: 2.2.1
import-fresh: 3.3.1
js-yaml: 4.1.1
parse-json: 5.2.0
data-uri-to-buffer@6.0.2: {}
debug@4.4.3:
dependencies:
ms: 2.1.3
degenerator@5.0.1:
dependencies:
ast-types: 0.13.4
escodegen: 2.1.0
esprima: 4.0.1
devtools-protocol@0.0.1595872: {}
emoji-regex@8.0.0: {}
end-of-stream@1.4.5:
dependencies:
once: 1.4.0
env-paths@2.2.1: {}
error-ex@1.3.4:
dependencies:
is-arrayish: 0.2.1
escalade@3.2.0: {}
escodegen@2.1.0:
dependencies:
esprima: 4.0.1
estraverse: 5.3.0
esutils: 2.0.3
optionalDependencies:
source-map: 0.6.1
esprima@4.0.1: {}
estraverse@5.3.0: {}
esutils@2.0.3: {}
events-universal@1.0.1:
dependencies:
bare-events: 2.8.2
transitivePeerDependencies:
- bare-abort-controller
extend-shallow@2.0.1:
dependencies:
is-extendable: 0.1.1
extract-zip@2.0.1:
dependencies:
debug: 4.4.3
get-stream: 5.2.0
yauzl: 2.10.0
optionalDependencies:
'@types/yauzl': 2.10.3
transitivePeerDependencies:
- supports-color
fast-fifo@1.3.2: {}
fd-slicer@1.1.0:
dependencies:
pend: 1.2.0
get-caller-file@2.0.5: {}
get-stream@5.2.0:
dependencies:
pump: 3.0.4
get-uri@6.0.5:
dependencies:
basic-ftp: 5.3.1
data-uri-to-buffer: 6.0.2
debug: 4.4.3
transitivePeerDependencies:
- supports-color
gray-matter@4.0.3:
dependencies:
js-yaml: 3.14.2
kind-of: 6.0.3
section-matter: 1.0.0
strip-bom-string: 1.0.0
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
debug: 4.4.3
transitivePeerDependencies:
- supports-color
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.4
debug: 4.4.3
transitivePeerDependencies:
- supports-color
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
ip-address@10.2.0: {}
is-arrayish@0.2.1: {}
is-extendable@0.1.1: {}
is-fullwidth-code-point@3.0.0: {}
js-tokens@4.0.0: {}
js-yaml@3.14.2:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
json-parse-even-better-errors@2.3.1: {}
kind-of@6.0.3: {}
lines-and-columns@1.2.4: {}
lru-cache@7.18.3: {}
marked@12.0.2: {}
mitt@3.0.1: {}
ms@2.1.3: {}
netmask@2.1.1: {}
nunjucks@3.2.4:
dependencies:
a-sync-waterfall: 1.0.1
asap: 2.0.6
commander: 5.1.0
once@1.4.0:
dependencies:
wrappy: 1.0.2
pac-proxy-agent@7.2.0:
dependencies:
'@tootallnate/quickjs-emscripten': 0.23.0
agent-base: 7.1.4
debug: 4.4.3
get-uri: 6.0.5
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
pac-resolver: 7.0.1
socks-proxy-agent: 8.0.5
transitivePeerDependencies:
- supports-color
pac-resolver@7.0.1:
dependencies:
degenerator: 5.0.1
netmask: 2.1.1
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
parse-json@5.2.0:
dependencies:
'@babel/code-frame': 7.29.0
error-ex: 1.3.4
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
pend@1.2.0: {}
picocolors@1.1.1: {}
progress@2.0.3: {}
proxy-agent@6.5.0:
dependencies:
agent-base: 7.1.4
debug: 4.4.3
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
lru-cache: 7.18.3
pac-proxy-agent: 7.2.0
proxy-from-env: 1.1.0
socks-proxy-agent: 8.0.5
transitivePeerDependencies:
- supports-color
proxy-from-env@1.1.0: {}
pump@3.0.4:
dependencies:
end-of-stream: 1.4.5
once: 1.4.0
puppeteer-core@24.42.0:
dependencies:
'@puppeteer/browsers': 2.13.0
chromium-bidi: 14.0.0(devtools-protocol@0.0.1595872)
debug: 4.4.3
devtools-protocol: 0.0.1595872
typed-query-selector: 2.12.2
webdriver-bidi-protocol: 0.4.1
ws: 8.20.0
transitivePeerDependencies:
- bare-abort-controller
- bare-buffer
- bufferutil
- react-native-b4a
- supports-color
- utf-8-validate
puppeteer@24.42.0:
dependencies:
'@puppeteer/browsers': 2.13.0
chromium-bidi: 14.0.0(devtools-protocol@0.0.1595872)
cosmiconfig: 9.0.1
devtools-protocol: 0.0.1595872
puppeteer-core: 24.42.0
typed-query-selector: 2.12.2
transitivePeerDependencies:
- bare-abort-controller
- bare-buffer
- bufferutil
- react-native-b4a
- supports-color
- typescript
- utf-8-validate
require-directory@2.1.1: {}
resolve-from@4.0.0: {}
section-matter@1.0.0:
dependencies:
extend-shallow: 2.0.1
kind-of: 6.0.3
semver@7.7.4: {}
smart-buffer@4.2.0: {}
socks-proxy-agent@8.0.5:
dependencies:
agent-base: 7.1.4
debug: 4.4.3
socks: 2.8.8
transitivePeerDependencies:
- supports-color
socks@2.8.8:
dependencies:
ip-address: 10.2.0
smart-buffer: 4.2.0
source-map@0.6.1:
optional: true
sprintf-js@1.0.3: {}
streamx@2.25.0:
dependencies:
events-universal: 1.0.1
fast-fifo: 1.3.2
text-decoder: 1.2.7
transitivePeerDependencies:
- bare-abort-controller
- react-native-b4a
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-bom-string@1.0.0: {}
tar-fs@3.1.2:
dependencies:
pump: 3.0.4
tar-stream: 3.2.0
optionalDependencies:
bare-fs: 4.7.1
bare-path: 3.0.0
transitivePeerDependencies:
- bare-abort-controller
- bare-buffer
- react-native-b4a
tar-stream@3.2.0:
dependencies:
b4a: 1.8.1
bare-fs: 4.7.1
fast-fifo: 1.3.2
streamx: 2.25.0
transitivePeerDependencies:
- bare-abort-controller
- bare-buffer
- react-native-b4a
teex@1.0.1:
dependencies:
streamx: 2.25.0
transitivePeerDependencies:
- bare-abort-controller
- react-native-b4a
text-decoder@1.2.7:
dependencies:
b4a: 1.8.1
transitivePeerDependencies:
- react-native-b4a
tslib@2.8.1: {}
typed-query-selector@2.12.2: {}
undici-types@7.19.2:
optional: true
webdriver-bidi-protocol@0.4.1: {}
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrappy@1.0.2: {}
ws@8.20.0: {}
y18n@5.0.8: {}
yargs-parser@21.1.1: {}
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
yauzl@2.10.0:
dependencies:
buffer-crc32: 0.2.13
fd-slicer: 1.1.0
zod@3.25.76: {}
+71
View File
@@ -0,0 +1,71 @@
import { readdir, readFile, writeFile, access } from 'fs/promises';
import { join, resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import matter from 'gray-matter';
import { marked } from 'marked';
import nunjucks from 'nunjucks';
const __dir = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dir, '..');
// Nunjucks: load templates relative to project root
const env = nunjucks.configure(join(root, 'templates'), { autoescape: true });
async function fileExists(path) {
try { await access(path); return true; } catch { return false; }
}
async function loadStories() {
const contentDir = join(root, 'content');
const files = (await readdir(contentDir)).filter(f => f.endsWith('.md')).sort();
const stories = [];
let finale = null;
for (const file of files) {
const raw = await readFile(join(contentDir, file), 'utf-8');
const { data, content } = matter(raw);
if (data.type === 'front-matter') continue;
if (data.type === 'finale') {
finale = data;
continue;
}
// Split body by --- into individual scene texts
const sceneTexts = content.split(/\n---\n/).map(t => t.trim()).filter(Boolean);
const scenes = await Promise.all(
(data.scenes || []).map(async (scene, i) => {
const imagePath = join(root, scene.image);
const imageExists = await fileExists(imagePath);
return {
...scene,
imageExists,
// Relative path from output/book.html back to project root
image: `../${scene.image}`,
html: marked.parse(sceneTexts[i] || ''),
};
})
);
stories.push({ ...data, scenes });
}
return { stories, finale: finale || {} };
}
async function build() {
const { stories, finale } = await loadStories();
const html = env.render('book.html', { stories, finale });
const outPath = join(root, 'output', 'book.html');
await writeFile(outPath, html, 'utf-8');
const pageCount = stories.reduce((acc, s) => acc + s.scenes.length * 2, 0) + 5;
console.log(`Built output/book.html — ${stories.length} stories, ~${pageCount} pages`);
}
build().catch(err => { console.error(err); process.exit(1); });
+51
View File
@@ -0,0 +1,51 @@
import puppeteer from 'puppeteer';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { access } from 'fs/promises';
const __dir = dirname(fileURLToPath(import.meta.url));
async function fileExists(path) {
try { await access(path); return true; } catch { return false; }
}
const root = resolve(__dir, '..');
const inputPath = resolve(root, 'output', 'book.html');
const outputPath = resolve(root, 'output', 'kaleidoskop.pdf');
async function generate() {
try {
await access(inputPath);
} catch {
console.error('output/book.html not found — run `pnpm build` first');
process.exit(1);
}
// On ARM64 (e.g. WSL2 on Apple Silicon / Raspberry Pi), Puppeteer's bundled
// Chrome is x86-64 and won't run. Use the system Chromium instead:
// sudo apt-get install -y chromium
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://${inputPath}`, { waitUntil: 'networkidle0' });
// 8.75 × 8.75 inches = trim (8.5×8.5) + 0.125in bleed on each side
await page.pdf({
path: outputPath,
width: '8.75in',
height: '8.75in',
printBackground: true,
margin: { top: 0, bottom: 0, left: 0, right: 0 },
});
await browser.close();
console.log(`PDF written to output/kaleidoskop.pdf`);
}
generate().catch(err => { console.error(err); process.exit(1); });
+145
View File
@@ -0,0 +1,145 @@
/* ── Image pages ── */
.page--image {
background: var(--color-primary, #1a1a2e);
}
.scene-image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* Placeholder when image not yet available */
.placeholder-image {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.8;
}
.placeholder-label {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
color: rgba(255,255,255,0.9);
text-align: center;
padding: 2rem;
border: 2px dashed rgba(255,255,255,0.4);
border-radius: 12px;
}
.placeholder-story {
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.1em;
opacity: 0.7;
}
.placeholder-scene {
font-size: 1.4rem;
font-weight: 600;
}
/* ── Text pages ── */
.page--text {
background: var(--color-bg, #fafafa);
display: flex;
align-items: center;
}
.text-page-inner {
width: 100%;
display: flex;
flex-direction: column;
gap: 1.2rem;
}
.scene-ornament {
font-size: 1.5rem;
color: var(--color-secondary, #ccc);
text-align: center;
margin-top: auto;
}
/* ── Front matter pages ── */
.page--title {
background: #1a1a3e;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.page--imprint {
background: #fafafa;
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;
display: flex;
flex-direction: column;
gap: 2rem;
}
.toc-list {
list-style: none;
counter-reset: toc;
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.toc-list li {
counter-increment: toc;
display: flex;
align-items: baseline;
gap: 0.75rem;
font-size: 1rem;
color: #2a2a4a;
}
.toc-list li::before {
content: counter(toc, decimal-leading-zero);
font-size: 0.8rem;
color: #aaa;
min-width: 2rem;
}
/* ── Finale page ── */
.page--finale {
background: #1a1a2e;
position: relative;
}
.finale-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.finale-overlay {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
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%);
}
+34
View File
@@ -0,0 +1,34 @@
/* KDP: 8.5×8.5 in trim, 0.125in bleed on all sides */
@page {
size: 8.75in 8.75in;
margin: 0;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
width: 8.75in;
background: white;
}
.page {
width: 8.75in;
height: 8.75in;
position: relative;
overflow: hidden;
page-break-after: always;
break-after: page;
}
/* Safe zone: content that must not be cut stays 0.25in from bleed 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;
}
+97
View File
@@ -0,0 +1,97 @@
/* ── Base typography ── */
body {
font-family: Georgia, 'Times New Roman', serif;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* ── Title page ── */
.title-eyebrow {
font-size: 0.85rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: rgba(255,255,255,0.6);
margin-bottom: 1.5rem;
}
.title-main {
font-size: 3rem;
line-height: 1.2;
color: white;
margin-bottom: 1.5rem;
font-style: italic;
}
.title-sub {
font-size: 1.1rem;
color: rgba(255,255,255,0.8);
letter-spacing: 0.05em;
}
/* ── Table of contents ── */
.toc-title {
font-size: 1.6rem;
color: #1a1a3e;
font-style: italic;
border-bottom: 1px solid #ddd;
padding-bottom: 0.5rem;
}
/* ── Story title (first scene right page) ── */
.story-title {
font-size: 1.6rem;
color: var(--color-primary);
font-style: italic;
line-height: 1.3;
border-bottom: 2px solid var(--color-secondary);
padding-bottom: 0.5rem;
margin-bottom: 0.5rem;
}
/* ── Scene text ── */
.scene-text {
font-size: 1.05rem;
line-height: 1.85;
color: var(--color-text, #1a1a2e);
}
.scene-text p {
margin-bottom: 1em;
}
.scene-text p:last-child {
margin-bottom: 0;
}
.scene-text em {
font-style: italic;
}
/* Story 9 has dark background — invert text page */
[data-story="9"].page--text {
background: #0a0a2a;
}
[data-story="9"] .scene-text,
[data-story="9"] .story-title {
color: #ffffff;
}
[data-story="9"] .story-title {
border-color: #ff1493;
}
/* ── Finale ── */
.finale-overlay h2 {
font-size: 2.5rem;
color: white;
font-style: italic;
text-shadow: 0 2px 12px rgba(0,0,0,0.6);
}
.finale-overlay p {
font-size: 1rem;
color: rgba(255,255,255,0.8);
letter-spacing: 0.1em;
margin-top: 0.5rem;
}
+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Das Kaleidoskop der Schlummerwelten</title>
<link rel="stylesheet" href="../styles/print.css">
<link rel="stylesheet" href="../styles/layout.css">
<link rel="stylesheet" href="../styles/typography.css">
</head>
<body>
{# ── Title page ── #}
<div class="page page--title">
<div class="title-content">
<p class="title-eyebrow">Eine Sammlung von</p>
<h1 class="title-main">Das Kaleidoskop<br>der Schlummerwelten</h1>
<p class="title-sub">Zwölf magische Geschichten für die Nacht</p>
</div>
</div>
{# ── Copyright / imprint page ── #}
<div class="page page--imprint">
<div class="imprint-content">
<p>Erste Ausgabe</p>
<p>Alle Rechte vorbehalten.</p>
<p>Illustrationen: KI-generiert (Dreamy Watercolor / Whimsical Storybook)</p>
</div>
</div>
{# ── Table of contents ── #}
<div class="page page--toc">
<h2 class="toc-title">Die zwölf Welten</h2>
<ol class="toc-list">
{% for story in stories %}
{% if story.type != 'front-matter' and story.type != 'finale' %}
<li>{{ story.title }}</li>
{% endif %}
{% endfor %}
</ol>
</div>
{# ── Stories ── #}
{% for story in stories %}
{% if story.type == 'front-matter' or story.type == 'finale' %}
{# skip — handled separately #}
{% else %}
{% include "story-spread.html" %}
{% endif %}
{% endfor %}
{# ── Finale page ── #}
<div class="page page--finale">
{% if finale.image %}
<img class="finale-image" src="{{ finale.image }}" alt="Das Kaleidoskop alle zwölf Welten">
{% else %}
<div class="placeholder-image placeholder-image--finale">
<span>Finale-Illustration (alle 12 Welten)</span>
</div>
{% endif %}
<div class="finale-overlay">
<h2>Das Kaleidoskop</h2>
<p>Alle zwölf Welten auf einen Blick</p>
</div>
</div>
</body>
</html>
+40
View File
@@ -0,0 +1,40 @@
{% for scene in story.scenes %}
{# ── Left page: full-bleed illustration ── #}
<div class="page page--image" data-story="{{ story.number }}" style="
--color-primary: {{ story.palette.primary }};
--color-secondary: {{ story.palette.secondary }};
--color-text: {{ story.palette.text }};
--color-bg: {{ story.palette.background }};
">
{% if scene.imageExists %}
<img
class="scene-image"
src="{{ scene.image }}"
alt="{{ scene.alt }}"
>
{% else %}
<div class="placeholder-image" style="background: {{ story.palette.primary }};">
<div class="placeholder-label">
<span class="placeholder-story">Geschichte {{ story.number }}</span>
<span class="placeholder-scene">{{ scene.alt }}</span>
</div>
</div>
{% endif %}
</div>
{# ── Right page: story text ── #}
<div class="page page--text" data-story="{{ story.number }}" style="
--color-primary: {{ story.palette.primary }};
--color-secondary: {{ story.palette.secondary }};
--color-text: {{ story.palette.text }};
--color-bg: {{ story.palette.background }};
">
<div class="text-page-inner">
{% if loop.first %}
<h2 class="story-title">{{ story.title }}</h2>
{% endif %}
<div class="scene-text">{{ scene.html | safe }}</div>
<div class="scene-ornament"></div>
</div>
</div>
{% endfor %}