Skip to content

Repository files navigation

Creature Team Verhalenmachine

A Dutch-language AI bedtime-story generator for kids. Build a fantasy world with characters, configure a story (chapters, age range, narrative arc, learning goals), and the app generates a multi-chapter illustrated story complete with educational quizzes, hero portraits, chapter illustrations, coloring pages, and a parent-only reading aid.

Built around the Creature Team universe — a pre-loaded cast of ~50 heroes, sidekicks, and villains kids can mix and match.

All UI text and generated stories are in Nederlands (Dutch). The rest of this README is in English so contributors can read it.

Features

  • Conversational world-building — chat with the world editor (Gemini structured output) to add characters, traits, places, items. Updates the world log in place.
  • Story generation — pick chapter count, target age, narrative arc, and learning goals. Outline first, then chapters in parallel batches (3 at a time) with per-chapter retry. Each chapter ships with a self-read section, a parent-aid section (500-600 words), 5 quiz challenges, and a generated image prompt.
  • Image generation pipeline — Imagen 4 powers character profile pictures, story covers, in-chapter illustrations, coloring pages, and per-character pose sheets. Generated assets are persisted to GCS and recovered on demand.
  • Three-state character toggle — every character in the world is Mee (forced in), Thuis (forced out), or Willekeurig (random). Story cast capped at 7 characters for narrative coherence.
  • Parent lock — gates settings, image regeneration, and cost-sensitive surfaces behind a PIN screen.
  • Cloud sync — Firestore-backed sync so worlds, characters, and saved stories survive across devices. Auto-sync runs in the background.
  • Parchment reader — the story renderer styles itself like a story book (Fredoka + Nunito, parchment background). Print mode generates separate parent and child booklets.
  • Story archive — every generated story is saved; the variation tracker feeds past titles + synopses back into the prompt so the next story doesn't repeat itself.

Tech stack

  • Framework: Next.js 16.1.6 (App Router, React 19, server actions for the API routes)
  • Language: TypeScript 5 (strict mode)
  • Styling: Tailwind CSS v4 via @tailwindcss/postcss, dark theme with CSS variables in globals.css
  • State: Zustand v5 with persist middleware → auto-saves to localStorage (key: world-smid-store)
  • AI: Google Gemini (gemini-3.1-pro-preview) via @google/genai, plus Imagen 4 for image generation
  • Storage: Firestore (worlds + archive) + GCS (generated images)
  • Icons: lucide-react
  • Fonts: Fredoka (display) + Nunito (body) via Google Fonts

Repo layout

src/
├── app/
│   ├── layout.tsx                  # Root layout (fonts, metadata)
│   ├── page.tsx                    # Main page (tab navigation)
│   ├── globals.css                 # Theme + Tailwind + print styles
│   └── api/                        # 14 server-side endpoints:
│       ├── chat/                     world-building chat (structured output)
│       ├── generate-story/           outline + chapter generation
│       ├── regenerate-chapter/       single-chapter regen with retry
│       ├── regenerate-prompts/       refresh imagePrompt strings
│       ├── generate-cover/           Imagen 4 cover image
│       ├── generate-illustration/    per-chapter image
│       ├── generate-coloring-page/   line-art for print
│       ├── generate-character-sheet/
│       ├── generate-character-sheet-prompt/
│       ├── generate-profile-picture/
│       ├── generate-single-pose/     per-pose character sheet
│       ├── upload-image/             GCS upload
│       ├── recover-images/           GCS recovery
│       ├── backfill-puzzles/         migration helper
│       └── worlds/{save,load}/       Firestore sync
├── components/
│   ├── ChatInterface.tsx           # World-building chat UI
│   ├── WorldManager.tsx            # World selector tabs
│   ├── WorldLogView.tsx            # Character grid + 3-way toggle
│   ├── StoryConfigurator.tsx       # Story param form
│   ├── StoryPreview.tsx            # Reader (parent/child modes, print)
│   ├── StoryArchive.tsx            # Saved stories
│   ├── CharacterEditor.tsx         # Add/edit characters
│   └── ParentLock.tsx              # PIN gate
└── lib/
    ├── store.ts                    # Zustand store + default Creature Team
    ├── creature-team-data.ts       # Pre-loaded character roster
    ├── story-arcs.ts               # Narrative arc templates
    ├── firestore.ts                # Cloud sync
    ├── auto-sync.ts                # Background sync loop
    ├── storage.ts                  # GCS upload/recovery helpers
    ├── migration.ts                # Store version migration
    └── print-helpers.ts            # Booklet print layouts

Prompt-engineering reference lives at the repo root:

Local setup

npm install
cp .env.example .env.local        # fill in GEMINI_API_KEY
npm run dev                       # http://localhost:3000

Building for production:

npm run build
npm run start

Deploy

Cloud Run, source deploy — no Dockerfile, no cloudbuild.yaml:

gcloud run deploy creature-team-verhalenmachine \
  --source . \
  --region europe-west4 \
  --allow-unauthenticated \
  --set-env-vars GEMINI_API_KEY=$GEMINI_API_KEY

Cloud Build auto-containerizes the Next.js app. The service runs unauthenticated; the Gemini API key lives only in the service config.

Hacking on it — invariants worth knowing

These are non-obvious rules the codebase depends on:

  • The model name is locked. gemini-3.1-pro-preview is the only AI model wired in; switching it requires verifying every prompt + the structured-output schemas still parse. Don't bump it casually.
  • Never JSON.stringify(worldLog, null, 2) into a prompt. The world log holds ~50 characters with long descriptions; pretty-printed JSON blows the token budget. Always use the compact helpers in store.ts (buildWorldSummary()) and the per-character truncation (200 chars in story prompts, 150 chars in chat prompts).
  • Outline is passed as one-line-per-chapter into chapter prompts — not the full JSON. Same token-budget reason.
  • Story cast is hard-capped at 7 characters. Beyond that, narrative coherence collapses; the generator was rewritten in 6d79ad2 to enforce this.
  • All UI text and prompts are in Dutch. The only English string in the prompt pipeline is imagePrompt (since the image model performs better on English prompts).
  • Components are "use client". No SSR — the Zustand store relies on localStorage which doesn't exist server-side.
  • Print is first-class. StoryPreview opens an iframe and calls window.print() with separate parent and child booklet layouts.
  • Image generation is async and best-effort. Story generation uses Promise.allSettled so a single failing chapter doesn't kill the run.

License

This repo is published for portfolio + reference. The Creature Team character roster and the prompt-engineering reference markdown are part of the original creative work — please don't reuse the characters or their descriptions verbatim.

About

Creature Team Verhalenmachine — a Dutch-language AI bedtime-story generator for kids. Build a fantasy world, configure a story, and get a multi-chapter illustrated bedtime story with educational quizzes, hero portraits, coloring pages, and a parent reading aid. Next.js 16 + Gemini 3.1 Pro + Imagen 4 on Cloud Run.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages