A no-build, iPad-friendly escape game framework in plain HTML/CSS/JS.
Create sophisticated point-and-click adventures with Puzzles 2.0, dialogs, theming system, and an in-browser editor.
- Demo (GitHub Pages):
- https://raven2cz.github.io/escape-game-engine/index.html?game=leeuwenhoek&lang=cs&debug=1&hero=adam&reset=1 (two heroes: "adam" and "eva", adam selected)
- https://raven2cz.github.io/escape-game-engine/index.html?game=leeuwenhoek&lang=cs&debug=1&hero=eva&reset=1 (two heroes: "adam" and "eva", eva selected)
- https://raven2cz.github.io/escape-game-engine/index.html?game=stop-train&lang=cs&debug=1&hero=adam&reset=1
- https://raven2cz.github.io/escape-game-engine/index.html?game=time-factory&lang=cs&debug=1&hero=adam&reset=1
- https://raven2cz.github.io/escape-game-engine/index.html?game=reactor&lang=cs&debug=1&hero=adam&reset=1
- PWA: append
?pwa=1for installable/offline mode.
- Scene Management: Navigate between scenes with hotspots (
goTo,pickup,puzzle,dialog) - Inventory System: Collect items with image/description inspect modals
- State Management: Use flags and
requireItems/requireFlagsto lock doors or reveal secret paths - Event System: Trigger complex action chains on scene enter/exit, item pickup, puzzle completion
- Hero Profiles: Support for multiple playable characters with custom avatars and names
- Internationalization (i18n): Multi-language support with
@key@fallbacksyntax - PWA Support: Install as offline-capable app on mobile devices
Nine built-in puzzle types with unified theming and layout system:
phrase: Text answer (diacritics/case-insensitive)code: Numeric/alphanumeric code (optional password mask)order: Arrange tokens into correct sequencematch: Match pairs (columns or drag-and-drop mode)quiz: Multiple-choice questions (single or multi-select)choice: Select from options or fill editable fieldsgroup: Sort tokens into categoriescloze: Fill-in-the-blank text exerciseslist: Sequential puzzle chains with summary screen
- Universal Theming: Hierarchical CSS variables cascade from engine โ game โ puzzle โ token
- Flexible Layouts: AUTO (responsive vertical/horizontal/grid) or MANUAL (absolute positioning)
- Aggregate Mode: Collect results without immediate feedback for list sequences
- Block Until Solved: Require puzzle completion before proceeding
- Success/Fail Actions: Award items, set flags, display messages, navigate scenes
- Character Profiles: Define characters with multiple poses and expressions
- Hero Alias: Special "hero" character auto-maps to selected player profile
- Choice-Based Dialogs: Interactive branching with conditions (
requireFlags,requireItems) - Voice Lines: Link audio files to dialog steps
- Auto-Advance: Configurable delays for cinematic sequences
- Theme Integration: Dialogs use puzzle theming system for consistent UI
- Hotspot Editor: Draw rectangles, live coordinate labels in percent
- Puzzle Editor: Visual positioning for AUTO (window rect) or MANUAL (component layout)
- JSON Export: Copy complete hotspot/puzzle JSON or just rect coordinates
- Real-time Labels: See
x,y,w,hvalues while dragging - Keyboard Support: Delete/Backspace to remove selected elements
escape-game-engine/
โโโ index.html # Main entry point
โโโ styles/
โ โโโ style.css # Global app styles
โ โโโ puzzles.css # Puzzles 2.0 framework (semi-transparent colors)
โโโ engine/
โ โโโ engine.js # Core runtime (scenes, inventory, flags, dialogs)
โ โโโ editor.js # In-browser editor (draw hotspots, export JSON)
โ โโโ dialogs.js # Dialog system with character management
โ โโโ i18n.js # Engine internationalization strings
โ โโโ puzzles/
โ โโโ index.js # Puzzle runner factory
โ โโโ base.js # Shared puzzle infrastructure
โ โโโ layout.js # AUTO layout algorithm
โ โโโ phrase.js # Text input puzzle
โ โโโ code.js # Code entry puzzle
โ โโโ order.js # Token sequencing puzzle
โ โโโ match.js # Pair matching puzzle
โ โโโ quiz.js # Multiple choice quiz
โ โโโ choice.js # Choice/fill-in puzzle
โ โโโ group.js # Category sorting puzzle
โ โโโ cloze.js # Fill-in-the-blank puzzle
โ โโโ list.js # Puzzle sequence manager
โโโ games/
โ โโโ <game-id>/
โ โโโ scenes.json # Scene definitions, hotspots, items
โ โโโ puzzles.json # Puzzle configurations
โ โโโ dialogs.json # Dialog trees (optional)
โ โโโ i18n/
โ โ โโโ cs.json # Czech translations
โ โ โโโ en.json # English translations
โ โโโ game.css # Per-game theme overrides (optional)
โ โโโ assets/ # Images, audio, backgrounds
โโโ service-worker.js # PWA offline cache
โโโ manifest.webmanifest # PWA manifest
# Serve locally (Python 3)
python3 -m http.server 5500
# Or use any static server
# Open http://localhost:5500/- Toggle โ Edit mode
- Draw a rectangle on the scene
- Click Copy JSON or Rect JSON to get:
{
"type": "pickup",
"itemId": "glass_key",
"rect": { "x": 72, "y": 58, "w": 8, "h": 10 }
}- Paste into
games/<your-game>/scenes.jsonunder the current scene
Define in games/<your-game>/puzzles.json:
{
"my-puzzle": {
"id": "my-puzzle",
"kind": "phrase",
"title": "Enter the secret phrase",
"prompt": "What did Anton say?",
"solution": "microscopy is life",
"options": {
"aggregateOnly": false,
"blockUntilSolved": true
}
}
}Reference in a hotspot:
{
"type": "puzzle",
"ref": "my-puzzle",
"rect": { "x": 40, "y": 30, "w": 20, "h": 15 }
}Define in games/<your-game>/dialogs.json:
{
"characters": [
{
"id": "professor",
"name": "Prof. Leeuwenhoek",
"poses": {
"neutral": "assets/characters/prof-neutral.png",
"happy": "assets/characters/prof-happy.png"
}
}
],
"dialogs": [
{
"id": "intro",
"left": {
"characterId": "hero",
"defaultPose": "neutral"
},
"right": {
"characterId": "professor",
"defaultPose": "neutral"
},
"steps": [
{
"side": "right",
"text": "Welcome to my laboratory!",
"pose": "happy"
},
{
"side": "left",
"text": "Thank you, Professor!"
}
]
}
]
}Trigger from hotspot:
{
"type": "dialog",
"dialogId": "intro",
"rect": { "x": 50, "y": 60, "w": 10, "h": 15 }
}Puzzles 2.0 uses cascading CSS variables:
engine defaults (puzzles.css)
โ override via game.css
โ override via puzzle.theme in JSON
โ override via token.style in JSON
In puzzles.json:
{
"my-puzzle": {
"kind": "phrase",
"options": {
"theme": {
"vars": {
"--pz-token-bg": "rgba(100, 200, 255, 0.15)",
"--pz-token-border": "rgba(100, 200, 255, 0.4)"
},
"title": {
"fontSize": "1.5em",
"color": "rgba(255, 255, 255, 0.95)"
}
}
}
}
}Create games/<your-game>/game.css:
:root {
--app-bg: #1a1a2e;
--app-text: #e0e0e0;
--inventory-item-bg: rgba(255, 255, 255, 0.08);
}
/* Override puzzle defaults */
.pz {
--pz-token-bg: rgba(50, 150, 200, 0.12);
--pz-token-border: rgba(50, 150, 200, 0.35);
}Edit engine/i18n.js for core UI strings (inventory, modals, buttons).
Create translation files in games/<your-game>/i18n/:
cs.json:
{
"lh.pz.lensTitle": "Sestav vฤtu",
"lh.pz.lensPrompt": "Zadej vฤtu, kterou jsi odvodil z indiciรญ."
}en.json:
{
"lh.pz.lensTitle": "Compose the sentence",
"lh.pz.lensPrompt": "Enter the sentence you deduced from the clues."
}Use @key@fallback syntax:
{
"title": "@lh.pz.lensTitle@Assemble the sentence",
"prompt": "@lh.pz.lensPrompt@Enter the deduced sentence."
}The engine loads the translation for current language, falling back to the text after @ if key not found.
Add ?lang=en to URL or modify index.html default.
- All puzzles support touch/pen/mouse input
- Drag-and-drop works on touch devices
- Editor overlay supports touch drawing
- Add
?pwa=1to URL - Open in Safari/Chrome
- Tap "Add to Home Screen"
- App runs offline after first load
When updating code, bump CACHE_NAME in service-worker.js:
const CACHE_NAME = 'escape-game-v2'; // increment versionHotspots and puzzles support rich action chains in onSuccess, onFail, onEnter, onExit:
{
"onSuccess": [
{ "giveItem": "golden_key" },
{ "setFlags": ["lab_unlocked"] },
{ "clearFlags": ["first_visit"] },
{ "message": "You found the key!" },
{ "goTo": "laboratory" },
{ "delay": 1000 },
{ "openDialog": "victory_dialog" },
{ "setSceneImage": { "sceneId": "corridor", "image": "corridor_night.jpg" } },
{ "highlightHotspot": { "rect": { "x": 30, "y": 50, "w": 15, "h": 20 }, "ms": 3000 } },
{ "openPuzzle": { "ref": "bonus_puzzle", "onSuccess": [...] } },
{ "openPuzzleList": { "items": ["puzzle1", "puzzle2"], "aggregateOnly": true } }
]
}Lock hotspots until conditions are met:
{
"type": "goTo",
"scene": "secret_room",
"requireItems": ["brass_key", "cipher_note"],
"requireFlags": ["password_entered"],
"missingMessage": "The door won't open without the key and cipher."
}Set player character dynamically:
// In game code or console
window.__game.setHero({
id: 'eva',
heroId: 'eva',
heroName: 'Eva',
heroBase: 'assets/characters/eva'
});Dialogs will automatically use the hero's avatar when characterId: "hero" is used.
Enable "use item on scene" mode:
- Click inventory item โ enters use mode (cursor changes)
- Click hotspot โ triggers
onUseaction chain - ESC key exits use mode
{
"items": [
{
"id": "screwdriver",
"name": "Screwdriver",
"image": "assets/items/screwdriver.png",
"description": "A flathead screwdriver."
}
],
"hotspots": [
{
"type": "inspect",
"onUse": {
"screwdriver": [
{ "message": "You opened the panel!" },
{ "setFlags": ["panel_open"] },
{ "consumeItem": true }
]
}
}
]
}- Enable: Click โ Edit button
- Draw: Click-drag on scene to create rectangle
- Resize: Drag corner handles (NW, NE, SW, SE) or edge handles
- Move: Drag center area
- Delete: Press Delete/Backspace with rectangle selected
- Export: Click Copy JSON for complete hotspot or Rect JSON for just coordinates
For puzzles with layout: { mode: "auto" }:
- Enable editor while puzzle is open
- Adjust yellow window rectangle (puzzle viewport)
- Components auto-flow inside window
- Copy rect for
options.rectin JSON
For puzzles with layout: { mode: "manual" }:
- Enable editor while puzzle is open
- Each component gets purple overlay with
[data-id] - Position/resize components individually
- Export generates complete positioning JSON
- Paste into puzzle config
tokens[].rect
- Push repository to GitHub
- Settings โ Pages
- Source: Deploy from a branch
- Branch:
main| Folder:/ (root) - Save
- Open
https://<username>.github.io/<repo-name>/
Use ?game=<game-id> parameter:
https://username.github.io/repo/?game=leeuwenhoek
https://username.github.io/repo/?game=mystery-manor
Each game lives in games/<game-id>/ directory.
Run unit tests:
npm install
npm testUses Vitest with JSDOM for DOM testing. Test files mirror source structure.
{
"meta": {
"id": "leeuwenhoek",
"version": "2.0.0",
"title": "The Mystery of Leeuwenhoek",
"authors": ["Your Name"],
"startScene": "entry_hall"
},
"items": [
{
"id": "brass_key",
"name": "Brass Key",
"description": "An old brass key.",
"image": "assets/items/brass_key.png"
}
],
"scenes": [
{
"id": "entry_hall",
"name": "Entry Hall",
"image": "assets/scenes/entry_hall.jpg",
"onEnter": [
{ "message": "You enter the hall..." }
],
"hotspots": [
{
"type": "goTo",
"scene": "library",
"rect": { "x": 70, "y": 40, "w": 15, "h": 30 }
},
{
"type": "pickup",
"itemId": "brass_key",
"rect": { "x": 20, "y": 60, "w": 8, "h": 10 }
},
{
"type": "puzzle",
"ref": "entry_code",
"rect": { "x": 45, "y": 35, "w": 12, "h": 18 },
"onSuccess": [
{ "giveItem": "silver_coin" },
{ "setFlags": ["safe_opened"] }
]
},
{
"type": "dialog",
"dialogId": "guard_chat",
"rect": { "x": 30, "y": 30, "w": 10, "h": 20 }
}
]
}
]
}{
"entry_code": {
"id": "entry_code",
"kind": "code",
"title": "Enter the code",
"prompt": "The safe requires a 4-digit code.",
"solution": "1738",
"options": {
"aggregateOnly": false,
"blockUntilSolved": true,
"layout": {
"mode": "auto",
"direction": "vertical"
},
"theme": {
"vars": {
"--pz-token-bg": "rgba(255, 255, 255, 0.10)"
}
}
},
"background": "assets/puzzles/safe_closeup.jpg"
}
}{
"characters": [
{
"id": "guard",
"name": "Palace Guard",
"poses": {
"neutral": "assets/characters/guard_neutral.png",
"suspicious": "assets/characters/guard_suspicious.png"
}
}
],
"dialogs": [
{
"id": "guard_chat",
"left": { "characterId": "hero" },
"right": { "characterId": "guard", "defaultPose": "neutral" },
"steps": [
{
"side": "right",
"text": "Halt! State your business.",
"pose": "suspicious"
},
{
"side": "left",
"text": "I'm here to see the professor."
},
{
"side": "right",
"text": "Very well. Proceed.",
"pose": "neutral",
"requireFlags": ["has_invitation"],
"choices": [
{
"label": "Thank you",
"action": [{ "goTo": "laboratory" }]
}
]
}
]
}
]
}Explore games/leeuwenhoek/ for a complete example with:
- All puzzle types
- Dialog trees
- Item usage
- Multi-language support
- Custom theming
Check source files for inline documentation:
engine/engine.js- Core game loop and state managementengine/puzzles/base.js- Puzzle framework architectureengine/dialogs.js- Dialog system implementationengine/editor.js- Visual editor tools
Contributions welcome! Areas for improvement:
- New puzzle types (crossword, sliding puzzle, etc.)
- Audio/music system enhancements
- Save/load slot system
- Achievements/statistics
- Accessibility improvements (keyboard navigation, screen readers)
- Create
engine/puzzles/your-puzzle.jsextendingBasePuzzle - Implement
mount(),validate(),destroy() - Register in
engine/puzzles/index.js - Add CSS in
styles/puzzles.cssunder.pz--kind-your-puzzle - Document in README
MIT License
Copyright (c) 2024 Antonรญn Fischer (raven2cz)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Built with inspiration from classic point-and-click adventures and modern web technologies.
Special thanks to the open-source community for tools and libraries that made this possible.
Ready to create your escape game? Start by duplicating the games/leeuwenhoek/ directory and customize it to your story! ๐ฎ