feat: initial mycro-zine generator toolkit
- Single-page print layout (2x4 grid) for 8-page zines - Prompt templates for AI content/image generation - Example Undernet zine pages - Support for US Letter and A4 paper sizes - CLI and programmatic API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
@ -0,0 +1,27 @@
|
|||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Output directory (generated files)
|
||||
output/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Build artifacts
|
||||
dist/
|
||||
build/
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
# MycroZine
|
||||
|
||||
A toolkit for creating print-ready **mycro-zines** - 8-page mini folded zines that fit on a single 8.5" x 11" sheet.
|
||||
|
||||
## What is a MycroZine?
|
||||
|
||||
A mycro-zine is a tiny, foldable magazine made from a single sheet of paper. When folded correctly, it creates an 8-page booklet perfect for:
|
||||
- Punk zines and manifestos
|
||||
- Educational mini-guides
|
||||
- Event programs
|
||||
- DIY instructions
|
||||
- Art projects
|
||||
|
||||
## Features
|
||||
|
||||
- **Single-page print layout**: All 8 pages arranged on one 8.5" x 11" sheet (2 cols x 4 rows)
|
||||
- **High-resolution output**: 300 DPI for crisp printing
|
||||
- **Prompt templates**: Ready-to-use prompts for AI content/image generation
|
||||
- **Multiple styles**: punk-zine, minimal, collage, retro, academic
|
||||
- **US Letter & A4 support**: Works with common paper sizes
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### CLI - Create Print Layout
|
||||
|
||||
```bash
|
||||
# Using example pages
|
||||
npm run example
|
||||
|
||||
# Or specify your own 8 pages
|
||||
node src/layout.mjs page1.png page2.png page3.png page4.png page5.png page6.png page7.png page8.png
|
||||
|
||||
# With custom output path
|
||||
node src/layout.mjs page1.png ... page8.png --output my_zine_print.png
|
||||
```
|
||||
|
||||
### Programmatic API
|
||||
|
||||
```javascript
|
||||
import { createPrintLayout } from 'mycro-zine';
|
||||
|
||||
// Create print-ready layout from 8 page images
|
||||
await createPrintLayout({
|
||||
pages: [
|
||||
'page1.png', 'page2.png', 'page3.png', 'page4.png',
|
||||
'page5.png', 'page6.png', 'page7.png', 'page8.png'
|
||||
],
|
||||
outputPath: 'my_zine_print.png',
|
||||
background: '#ffffff'
|
||||
});
|
||||
```
|
||||
|
||||
### Prompt Templates (for AI generation)
|
||||
|
||||
```javascript
|
||||
import { getContentOutlinePrompt, getImagePrompt, STYLES, TONES } from 'mycro-zine/prompts';
|
||||
|
||||
// Generate content outline prompt
|
||||
const outlinePrompt = getContentOutlinePrompt({
|
||||
topic: 'The Undernet',
|
||||
style: 'punk-zine',
|
||||
tone: 'rebellious',
|
||||
sourceContent: 'Reference text here...'
|
||||
});
|
||||
|
||||
// Generate image prompt for a page
|
||||
const imagePrompt = getImagePrompt({
|
||||
pageNumber: 1,
|
||||
zineTopic: 'The Undernet',
|
||||
pageOutline: {
|
||||
title: 'THE UNDERNET',
|
||||
keyPoints: ['Own your data', 'Run local servers'],
|
||||
hashtags: ['#DataSovereignty', '#Mycopunk'],
|
||||
imagePrompt: 'Bold cover with mycelial network imagery...'
|
||||
},
|
||||
style: 'punk-zine'
|
||||
});
|
||||
```
|
||||
|
||||
## Print Layout
|
||||
|
||||
The output is a single PNG image with all 8 pages arranged in reading order:
|
||||
|
||||
```
|
||||
┌─────────────┬─────────────┐
|
||||
│ Page 1 │ Page 2 │ Row 1
|
||||
├─────────────┼─────────────┤
|
||||
│ Page 3 │ Page 4 │ Row 2
|
||||
├─────────────┼─────────────┤
|
||||
│ Page 5 │ Page 6 │ Row 3
|
||||
├─────────────┼─────────────┤
|
||||
│ Page 7 │ Page 8 │ Row 4
|
||||
└─────────────┴─────────────┘
|
||||
|
||||
Panel size: 4.25" x 2.75" (~10.8cm x 7cm)
|
||||
Total: 8.5" x 11" at 300 DPI (2550 x 3300 pixels)
|
||||
```
|
||||
|
||||
## Folding Instructions
|
||||
|
||||
After printing, fold your zine:
|
||||
|
||||
1. **Accordion fold**: Fold the paper in half horizontally (hamburger style)
|
||||
2. **Fold again**: Fold in half vertically (hotdog style)
|
||||
3. **One more fold**: Fold in half again
|
||||
4. **Cut center**: Unfold completely, cut a slit along the center fold
|
||||
5. **Push and fold**: Push through the center to create the booklet
|
||||
|
||||
## Examples
|
||||
|
||||
See the `examples/undernet/` directory for a complete 8-page zine about The Undernet project.
|
||||
|
||||
## Styles
|
||||
|
||||
| Style | Description |
|
||||
|-------|-------------|
|
||||
| `punk-zine` | Xerox texture, high contrast B&W, DIY collage, hand-drawn typography |
|
||||
| `minimal` | Clean lines, white space, modern sans-serif, subtle gradients |
|
||||
| `collage` | Layered imagery, mixed media textures, vintage photographs |
|
||||
| `retro` | 1970s aesthetic, earth tones, groovy typography, halftone patterns |
|
||||
| `academic` | Diagram-heavy, annotated illustrations, infographic elements |
|
||||
|
||||
## Integration with Gemini MCP
|
||||
|
||||
This library is designed to work with the [Gemini MCP Server](https://github.com/jeffemmett/gemini-mcp) for AI-powered content and image generation:
|
||||
|
||||
1. Use `getContentOutlinePrompt()` with `gemini_generate` for zine planning
|
||||
2. Use `getImagePrompt()` with `gemini_generate_image` for page creation
|
||||
3. Use `createPrintLayout()` to assemble the final print-ready file
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
|
@ -0,0 +1,566 @@
|
|||
{
|
||||
"name": "mycro-zine",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mycro-zine",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
|
||||
"integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
|
||||
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
|
||||
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
|
||||
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
|
||||
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
|
||||
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
|
||||
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
|
||||
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
|
||||
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
|
||||
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
|
||||
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
|
||||
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
|
||||
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
|
||||
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
|
||||
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
|
||||
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-ppc64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
|
||||
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-ppc64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-riscv64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
|
||||
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-riscv64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
|
||||
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
|
||||
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
|
||||
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
|
||||
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
|
||||
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-arm64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
|
||||
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
|
||||
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
|
||||
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@img/colour": "^1.0.0",
|
||||
"detect-libc": "^2.1.2",
|
||||
"semver": "^7.7.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.34.5",
|
||||
"@img/sharp-darwin-x64": "0.34.5",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.2.4",
|
||||
"@img/sharp-libvips-darwin-x64": "1.2.4",
|
||||
"@img/sharp-libvips-linux-arm": "1.2.4",
|
||||
"@img/sharp-libvips-linux-arm64": "1.2.4",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.2.4",
|
||||
"@img/sharp-libvips-linux-riscv64": "1.2.4",
|
||||
"@img/sharp-libvips-linux-s390x": "1.2.4",
|
||||
"@img/sharp-libvips-linux-x64": "1.2.4",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
|
||||
"@img/sharp-linux-arm": "0.34.5",
|
||||
"@img/sharp-linux-arm64": "0.34.5",
|
||||
"@img/sharp-linux-ppc64": "0.34.5",
|
||||
"@img/sharp-linux-riscv64": "0.34.5",
|
||||
"@img/sharp-linux-s390x": "0.34.5",
|
||||
"@img/sharp-linux-x64": "0.34.5",
|
||||
"@img/sharp-linuxmusl-arm64": "0.34.5",
|
||||
"@img/sharp-linuxmusl-x64": "0.34.5",
|
||||
"@img/sharp-wasm32": "0.34.5",
|
||||
"@img/sharp-win32-arm64": "0.34.5",
|
||||
"@img/sharp-win32-ia32": "0.34.5",
|
||||
"@img/sharp-win32-x64": "0.34.5"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "mycro-zine",
|
||||
"version": "1.0.0",
|
||||
"description": "8-page mini-zine generator utilities for print-ready zine layouts",
|
||||
"type": "module",
|
||||
"main": "src/index.mjs",
|
||||
"exports": {
|
||||
".": "./src/index.mjs",
|
||||
"./layout": "./src/layout.mjs",
|
||||
"./prompts": "./src/prompts.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"layout": "node src/layout.mjs",
|
||||
"example": "node src/layout.mjs examples/undernet/undernet_zine_p1_cover.png examples/undernet/undernet_zine_p2_what.png examples/undernet/undernet_zine_p3_metacelium.png examples/undernet/undernet_zine_p4_privacy.png examples/undernet/undernet_zine_p5_threepunks.png examples/undernet/undernet_zine_p6_techstack.png examples/undernet/undernet_zine_p7_philosophy.png examples/undernet/undernet_zine_p8_cta.png"
|
||||
},
|
||||
"keywords": [
|
||||
"zine",
|
||||
"mycrozine",
|
||||
"mini-zine",
|
||||
"print",
|
||||
"layout",
|
||||
"punk",
|
||||
"diy",
|
||||
"generator"
|
||||
],
|
||||
"author": "Jeff Emmett",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@gitea.jeffemmett.com:jeffemmett/mycro-zine.git"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* MycroZine - 8-page mini-zine generator utilities
|
||||
*
|
||||
* A toolkit for creating print-ready mycro-zines (8-page mini folded zines).
|
||||
*
|
||||
* @module mycro-zine
|
||||
*/
|
||||
|
||||
export { createPrintLayout } from './layout.mjs';
|
||||
export {
|
||||
STYLES,
|
||||
TONES,
|
||||
PAGE_TEMPLATES,
|
||||
ZINE_STRUCTURES,
|
||||
getContentOutlinePrompt,
|
||||
getImagePrompt,
|
||||
getIdeationPrompt
|
||||
} from './prompts.mjs';
|
||||
|
||||
/**
|
||||
* Zine configuration defaults
|
||||
*/
|
||||
export const DEFAULTS = {
|
||||
style: 'punk-zine',
|
||||
tone: 'rebellious',
|
||||
paperFormat: 'letter', // US Letter 8.5" x 11"
|
||||
dpi: 300,
|
||||
pageCount: 8
|
||||
};
|
||||
|
||||
/**
|
||||
* Page dimensions in pixels at 300 DPI
|
||||
*/
|
||||
export const DIMENSIONS = {
|
||||
letter: {
|
||||
width: 2550, // 8.5" x 300
|
||||
height: 3300, // 11" x 300
|
||||
panelWidth: 1275, // width / 2 cols
|
||||
panelHeight: 825, // height / 4 rows
|
||||
panelCols: 2,
|
||||
panelRows: 4
|
||||
},
|
||||
a4: {
|
||||
width: 2480, // 210mm at 300 DPI
|
||||
height: 3508, // 297mm at 300 DPI
|
||||
panelWidth: 1240,
|
||||
panelHeight: 877,
|
||||
panelCols: 2,
|
||||
panelRows: 4
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a zine configuration
|
||||
*
|
||||
* @param {Object} config - Zine configuration to validate
|
||||
* @returns {{ valid: boolean, errors: string[] }}
|
||||
*/
|
||||
export function validateConfig(config) {
|
||||
const errors = [];
|
||||
|
||||
if (!config.topic || typeof config.topic !== 'string') {
|
||||
errors.push('Topic is required and must be a string');
|
||||
}
|
||||
|
||||
if (config.style && !['punk-zine', 'minimal', 'collage', 'retro', 'academic'].includes(config.style)) {
|
||||
errors.push(`Invalid style: ${config.style}`);
|
||||
}
|
||||
|
||||
if (config.tone && !['rebellious', 'playful', 'informative', 'poetic'].includes(config.tone)) {
|
||||
errors.push(`Invalid tone: ${config.tone}`);
|
||||
}
|
||||
|
||||
if (config.paperFormat && !['letter', 'a4'].includes(config.paperFormat)) {
|
||||
errors.push(`Invalid paper format: ${config.paperFormat}`);
|
||||
}
|
||||
|
||||
if (config.pages && (!Array.isArray(config.pages) || config.pages.length !== 8)) {
|
||||
errors.push('Pages must be an array of exactly 8 items');
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new zine configuration
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.topic - Main topic/theme
|
||||
* @param {string} [options.title] - Zine title (generated from topic if not provided)
|
||||
* @param {string} [options.style='punk-zine'] - Visual style
|
||||
* @param {string} [options.tone='rebellious'] - Content tone
|
||||
* @param {string} [options.paperFormat='letter'] - Paper format
|
||||
* @param {string[]} [options.sourceUrls] - Reference URLs
|
||||
* @returns {Object} Zine configuration object
|
||||
*/
|
||||
export function createZineConfig(options) {
|
||||
const {
|
||||
topic,
|
||||
title = topic.toUpperCase(),
|
||||
style = DEFAULTS.style,
|
||||
tone = DEFAULTS.tone,
|
||||
paperFormat = DEFAULTS.paperFormat,
|
||||
sourceUrls = []
|
||||
} = options;
|
||||
|
||||
const config = {
|
||||
id: `zine_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
topic,
|
||||
title,
|
||||
style,
|
||||
tone,
|
||||
paperFormat,
|
||||
sourceUrls,
|
||||
createdAt: Date.now(),
|
||||
pages: [],
|
||||
outline: null,
|
||||
status: 'draft'
|
||||
};
|
||||
|
||||
const validation = validateConfig(config);
|
||||
if (!validation.valid) {
|
||||
throw new Error(`Invalid config: ${validation.errors.join(', ')}`);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* MycroZine Layout Generator
|
||||
*
|
||||
* Creates a print-ready layout with all 8 pages on a single 8.5" x 11" sheet.
|
||||
* Layout: 2 columns x 4 rows (reading order: left-to-right, top-to-bottom)
|
||||
*
|
||||
* Panel size: 4.25" x 2.75" (~10.8cm x 7cm)
|
||||
*/
|
||||
|
||||
import sharp from 'sharp';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs/promises';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// 8.5" x 11" at 300 DPI = 2550 x 3300 pixels
|
||||
const PAGE_WIDTH = 2550;
|
||||
const PAGE_HEIGHT = 3300;
|
||||
|
||||
// Layout configuration: 2 columns x 4 rows
|
||||
const COLS = 2;
|
||||
const ROWS = 4;
|
||||
const PANEL_WIDTH = Math.floor(PAGE_WIDTH / COLS); // 1275 pixels
|
||||
const PANEL_HEIGHT = Math.floor(PAGE_HEIGHT / ROWS); // 825 pixels
|
||||
|
||||
/**
|
||||
* Create a print-ready zine layout from 8 page images
|
||||
*
|
||||
* @param {Object} options - Layout options
|
||||
* @param {string[]} options.pages - Array of 8 page image paths (in order)
|
||||
* @param {string} [options.outputPath] - Output file path (default: output/mycrozine_print.png)
|
||||
* @param {string} [options.background] - Background color (default: '#ffffff')
|
||||
* @returns {Promise<string>} - Path to generated print layout
|
||||
*/
|
||||
export async function createPrintLayout(options) {
|
||||
const {
|
||||
pages,
|
||||
outputPath = path.join(__dirname, '..', 'output', 'mycrozine_print.png'),
|
||||
background = '#ffffff'
|
||||
} = options;
|
||||
|
||||
if (!pages || pages.length !== 8) {
|
||||
throw new Error('Exactly 8 page images are required');
|
||||
}
|
||||
|
||||
// Ensure output directory exists
|
||||
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
||||
|
||||
// Load and resize all pages to panel size
|
||||
const resizedPages = await Promise.all(
|
||||
pages.map(async (pagePath) => {
|
||||
return sharp(pagePath)
|
||||
.resize(PANEL_WIDTH, PANEL_HEIGHT, {
|
||||
fit: 'contain',
|
||||
background
|
||||
})
|
||||
.toBuffer();
|
||||
})
|
||||
);
|
||||
|
||||
// Build composite array for all 8 pages in reading order
|
||||
const compositeImages = [];
|
||||
for (let row = 0; row < ROWS; row++) {
|
||||
for (let col = 0; col < COLS; col++) {
|
||||
const pageIndex = row * COLS + col; // 0-7
|
||||
compositeImages.push({
|
||||
input: resizedPages[pageIndex],
|
||||
left: col * PANEL_WIDTH,
|
||||
top: row * PANEL_HEIGHT
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create the final composite image
|
||||
await sharp({
|
||||
create: {
|
||||
width: PAGE_WIDTH,
|
||||
height: PAGE_HEIGHT,
|
||||
channels: 3,
|
||||
background
|
||||
}
|
||||
})
|
||||
.composite(compositeImages)
|
||||
.png()
|
||||
.toFile(outputPath);
|
||||
|
||||
console.log(`Created print layout: ${outputPath}`);
|
||||
console.log(` Dimensions: ${PAGE_WIDTH}x${PAGE_HEIGHT} pixels (8.5"x11" @ 300 DPI)`);
|
||||
console.log(` Panel size: ${PANEL_WIDTH}x${PANEL_HEIGHT} pixels (${COLS}x${ROWS} grid)`);
|
||||
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* CLI entry point
|
||||
* Usage: node layout.mjs <page1> <page2> ... <page8> [--output <path>]
|
||||
*/
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
// Parse arguments
|
||||
let pages = [];
|
||||
let outputPath = null;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--output' || args[i] === '-o') {
|
||||
outputPath = args[++i];
|
||||
} else if (!args[i].startsWith('-')) {
|
||||
pages.push(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (pages.length === 0) {
|
||||
// Default: look for undernet_zine_p*.png in examples
|
||||
const exampleDir = path.join(__dirname, '..', 'examples', 'undernet');
|
||||
try {
|
||||
const files = await fs.readdir(exampleDir);
|
||||
pages = files
|
||||
.filter(f => f.match(/undernet_zine_p\d\.png/))
|
||||
.sort()
|
||||
.map(f => path.join(exampleDir, f));
|
||||
|
||||
if (pages.length === 0) {
|
||||
console.log('No page images found. Usage:');
|
||||
console.log(' node layout.mjs page1.png page2.png ... page8.png [--output path]');
|
||||
console.log(' Or place 8 pages named undernet_zine_p1.png through p8.png in examples/undernet/');
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Usage: node layout.mjs page1.png page2.png ... page8.png [--output path]');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (pages.length !== 8) {
|
||||
console.error(`Error: Expected 8 pages, got ${pages.length}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const options = { pages };
|
||||
if (outputPath) {
|
||||
options.outputPath = outputPath;
|
||||
}
|
||||
|
||||
try {
|
||||
await createPrintLayout(options);
|
||||
} catch (error) {
|
||||
console.error('Error creating layout:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run CLI if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main();
|
||||
}
|
||||
|
||||
export default createPrintLayout;
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
/**
|
||||
* MycroZine Prompt Templates
|
||||
*
|
||||
* Templates for generating zine content outlines and image prompts.
|
||||
* Designed for use with Gemini MCP tools.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Available zine styles
|
||||
*/
|
||||
export const STYLES = {
|
||||
'punk-zine': 'xerox texture, high contrast black and white with accent color highlights, DIY cut-and-paste collage, hand-drawn typography, rough edges, rebellious feel',
|
||||
'minimal': 'clean lines, lots of white space, simple geometric shapes, modern sans-serif typography, subtle gradients',
|
||||
'collage': 'layered imagery, mixed media textures, overlapping elements, vintage photographs, torn paper edges',
|
||||
'retro': '1970s aesthetic, warm earth tones, groovy typography, halftone patterns, nostalgic imagery',
|
||||
'academic': 'diagram-heavy, annotated illustrations, serif typography, reference-style layout, infographic elements'
|
||||
};
|
||||
|
||||
/**
|
||||
* Available tones
|
||||
*/
|
||||
export const TONES = {
|
||||
'rebellious': 'defiant, anti-establishment, punk attitude, call to action, questioning authority',
|
||||
'playful': 'whimsical, fun, light-hearted, humorous, engaging, accessible',
|
||||
'informative': 'educational, clear explanations, factual, well-structured, easy to understand',
|
||||
'poetic': 'lyrical, metaphorical, evocative imagery, emotional resonance, artistic expression'
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a content outline prompt for an 8-page zine
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.topic - Main topic/theme
|
||||
* @param {string} [options.style='punk-zine'] - Visual style
|
||||
* @param {string} [options.tone='rebellious'] - Tone of content
|
||||
* @param {string} [options.sourceContent] - Optional reference content
|
||||
* @returns {string} Prompt for content outline generation
|
||||
*/
|
||||
export function getContentOutlinePrompt({ topic, style = 'punk-zine', tone = 'rebellious', sourceContent = null }) {
|
||||
return `You are creating an 8-page mycro-zine (mini folded zine) on the topic: ${topic}
|
||||
|
||||
Style: ${style} | Tone: ${tone}
|
||||
|
||||
${sourceContent ? `Reference content:\n${sourceContent}\n` : ''}
|
||||
|
||||
Generate a JSON outline for 8 pages:
|
||||
|
||||
Page 1 (Cover): Bold title, subtitle, visual hook
|
||||
Pages 2-7 (Content): Key concepts with emoji-fied, memetic explanations, hashtags
|
||||
Page 8 (CTA): Call-to-action with QR code placeholders
|
||||
|
||||
For each page provide:
|
||||
- pageNumber (1-8)
|
||||
- type: "cover" | "content" | "cta"
|
||||
- title: Bold headline
|
||||
- subtitle: (optional) Supporting text
|
||||
- keyPoints: Array of 2-4 key points with emojis
|
||||
- hashtags: 2-3 relevant hashtags
|
||||
- imagePrompt: Detailed prompt for ${style} style image generation
|
||||
|
||||
Output as JSON array.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an image prompt for a zine page
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {number} options.pageNumber - Page number (1-8)
|
||||
* @param {string} options.zineTopic - Overall zine topic
|
||||
* @param {Object} options.pageOutline - Page outline from content generation
|
||||
* @param {string} [options.style='punk-zine'] - Visual style
|
||||
* @param {string} [options.feedback] - User feedback to incorporate
|
||||
* @returns {string} Prompt for image generation
|
||||
*/
|
||||
export function getImagePrompt({ pageNumber, zineTopic, pageOutline, style = 'punk-zine', feedback = null }) {
|
||||
const styleDesc = STYLES[style] || STYLES['punk-zine'];
|
||||
|
||||
let prompt = `Punk zine page ${pageNumber}/8 for "${zineTopic}".
|
||||
|
||||
${pageOutline.imagePrompt || ''}
|
||||
|
||||
Style: ${styleDesc}
|
||||
|
||||
Include text elements: ${pageOutline.title}
|
||||
${pageOutline.keyPoints ? pageOutline.keyPoints.join(', ') : ''}
|
||||
${pageOutline.hashtags ? `Hashtags: ${pageOutline.hashtags.join(' ')}` : ''}`;
|
||||
|
||||
if (feedback) {
|
||||
prompt += `\n\nUser feedback to incorporate: ${feedback}`;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an ideation prompt to start zine planning
|
||||
*
|
||||
* @param {string} topic - Topic to brainstorm about
|
||||
* @param {string} [style='punk-zine'] - Visual style preference
|
||||
* @returns {string} Prompt for ideation brainstorming
|
||||
*/
|
||||
export function getIdeationPrompt(topic, style = 'punk-zine') {
|
||||
return `Let's create an 8-page mycro-zine about "${topic}" in ${style} style.
|
||||
|
||||
This is a mini folded zine format - think punk, DIY, memetic, shareable.
|
||||
|
||||
Help me brainstorm:
|
||||
1. What are the key concepts to cover?
|
||||
2. What's the narrative arc (intro → core content → call to action)?
|
||||
3. What visual metaphors or imagery would work well?
|
||||
4. What hashtags and slogans would resonate?
|
||||
5. Any source URLs or references to pull from?
|
||||
|
||||
Let's iterate on this before generating the actual pages.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page templates for common zine structures
|
||||
*/
|
||||
export const PAGE_TEMPLATES = {
|
||||
cover: {
|
||||
type: 'cover',
|
||||
description: 'Bold title, eye-catching visual, sets the tone',
|
||||
elements: ['title', 'subtitle', 'visual hook', 'issue number (optional)']
|
||||
},
|
||||
intro: {
|
||||
type: 'content',
|
||||
description: 'What is this about? Hook the reader',
|
||||
elements: ['question or statement', 'brief explanation', 'why it matters']
|
||||
},
|
||||
concept: {
|
||||
type: 'content',
|
||||
description: 'Explain a key concept',
|
||||
elements: ['concept name', 'visual metaphor', 'key points with emojis', 'hashtag']
|
||||
},
|
||||
comparison: {
|
||||
type: 'content',
|
||||
description: 'Compare/contrast two things',
|
||||
elements: ['side by side layout', 'pros/cons', 'clear distinction']
|
||||
},
|
||||
process: {
|
||||
type: 'content',
|
||||
description: 'Step-by-step or flow',
|
||||
elements: ['numbered steps', 'arrows/flow', 'simple icons']
|
||||
},
|
||||
manifesto: {
|
||||
type: 'content',
|
||||
description: 'Statement of values/beliefs',
|
||||
elements: ['bold statements', 'call to action language', 'emotive']
|
||||
},
|
||||
resources: {
|
||||
type: 'content',
|
||||
description: 'Links, QR codes, further reading',
|
||||
elements: ['QR codes', 'URLs', 'social handles', 'book/article references']
|
||||
},
|
||||
cta: {
|
||||
type: 'cta',
|
||||
description: 'Call to action - what should reader do?',
|
||||
elements: ['action items', 'QR code', 'community links', 'hashtag', 'share prompt']
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Suggested 8-page structures
|
||||
*/
|
||||
export const ZINE_STRUCTURES = {
|
||||
educational: [
|
||||
'cover',
|
||||
'intro',
|
||||
'concept',
|
||||
'concept',
|
||||
'concept',
|
||||
'process',
|
||||
'resources',
|
||||
'cta'
|
||||
],
|
||||
manifesto: [
|
||||
'cover',
|
||||
'manifesto',
|
||||
'concept',
|
||||
'concept',
|
||||
'comparison',
|
||||
'manifesto',
|
||||
'resources',
|
||||
'cta'
|
||||
],
|
||||
howto: [
|
||||
'cover',
|
||||
'intro',
|
||||
'process',
|
||||
'process',
|
||||
'process',
|
||||
'concept',
|
||||
'resources',
|
||||
'cta'
|
||||
]
|
||||
};
|
||||
|
||||
export default {
|
||||
STYLES,
|
||||
TONES,
|
||||
PAGE_TEMPLATES,
|
||||
ZINE_STRUCTURES,
|
||||
getContentOutlinePrompt,
|
||||
getImagePrompt,
|
||||
getIdeationPrompt
|
||||
};
|
||||