A self-contained 3D e-book reader β a web application that simulates a physical book
329
A self-contained 3D e-book reader β a web application that simulates a physical book in three-dimensional space. No build step, no Node.js, no npm β just a browser and Python.
π Live demo: 129.151.144.75:8080β β try it before installing
β οΈ The media below is large β click to expand (high traffic, ~9 MB total).



![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() |
sounds/ folder (no repeats in a row)sounds/, they are picked up automatically, no code changesbooks/ folder, metadata in meta.jsonjs/reader.js, _bindVolumeKeys):| Way | How to do it in the wrapper |
|---|---|
| JS bridge | Callwindow.BookHavenNative.volumeUp() / .volumeDown() (e.g. via evaluateJavascript in onKeyDown) |
| Event | Dispatchwindow.dispatchEvent(new Event('volumeup')) / ('volumedown') |
| Key | Forward akeydown with keyCode 175 (louder) / 174 (quieter) to the WebView |
docker run -d --name bookhaven \
-p 8080:8080 -p 8001:8001 \
-v bookhaven-books:/app/books \
-v bookhaven-data:/app/data \
--restart unless-stopped \
hiddenath/bookhaven:latest
Open http://localhost:8080β β that's it. Books and settings live in named volumes and survive container recreation. Add a book via the UI (button / drag & drop) or:
docker cp "My book.fb2" bookhaven:/app/books/
# Start the server (static + API in one file)
python3 server.py
# Open in browser
http://127.0.0.1:8080
The server runs two processes in threads:
Options:
python3 server.py # both servers
python3 server.py --static # static only (8080)
python3 server.py --api # API only (8001)
python3 server.py --quiet # no request logging
book_reader/
βββ index.html # Entry point, UI markup
βββ book.svg # Logo (library header + favicon)
βββ server.py # Single server: static + API
βββ css/
β βββ main.css # Global styles, themes (CSS Custom Properties)
β βββ reader.css # 3D reader scene, bookmarks
β βββ library.css # Library (card grid)
βββ js/
β βββ main.js # Initialization, pagination, state, autosave
β βββ reader.js # Reader: flip animation, drag, keyboard, volume keys
β βββ narrator.js # Narrator: speech engines, pacing, stress marks
β βββ sounds.js # Page-flip sounds (random pick from sounds/)
β βββ library.js # Library: cards, import, deletion
β βββ bookmarks.js # Bookmarks (side panel, marker on the book edge)
β βββ notes.js # Footnotes: tooltip, jump to note, "back" button
β βββ parsers.js # TXT/EPUB parsers (FB2 β server-side)
β βββ position.js # Reading anchors (data-block-id)
β βββ toc.js # Table of contents
β βββ storage.js # localStorage + server-side saving
βββ lib/
β βββ epub.js # epub.js (downloaded, BSD)
β βββ jszip.min.js # JSZip β epub.js dependency
β βββ stress_dict.json.gz # Russian stress dictionary (3.2M word forms, RUAccent)
βββ sounds/ # Page-flip sounds (MP3/OGG/WAV, picked up automatically)
βββ assets/ # Screenshots and GIF animations for the README
βββ books/ # Book catalog (created automatically)
β βββ <name>.fb2 / .txt / .epub # Book file with its original name
β βββ <name>.meta.json # Metadata next to the book file
βββ data/
βββ state.json # Global settings (created automatically)
Text is split into pages in the browser: a hidden measurer computes the height of each paragraph under the current font settings, then paragraphs are laid out into pages. Each paragraph gets a stable identifier (data-block-id) tied to the book and its sequential number β so the reading position does not "drift" when settings change.
Books are stored as plain files in books/ β the original filename is kept, no subfolders. Metadata (progress, bookmarks, covers) is stored in <name>.meta.json next to the book.
| What | Where |
|---|---|
| Books (originals) | books/<name>.fb2 / .txt / .epub |
| Book metadata (progress, bookmarks) | books/<name>.meta.json |
| Global settings | localStorage + data/state.json |
| Method | Path | Description |
|---|---|---|
GET | /books | List of books (metadata without text) |
POST | /books | Save a book (copies intobooks/) |
GET | /books/<id>/text | Book text (FB2 parsed on the fly) |
GET | /books/<id>/meta | Book metadata |
POST | /books/<id>/meta | Update progress/bookmarks |
GET | /books/<id>/cover | Book cover (from FB2<coverpage>) |
GET | /books/<id>/image/<name> | Image from the book body (FB2<binary>) |
DELETE | /books/<id> | Delete a book |
GET | /sounds | List of page-flip sound files |
POST | /tts/stress | Mark up text with stress accents (U+0301) for the narrator |
http.server (no dependencies)/lib/)Dual licensing:
Applies to the repository version dated September 1, 2026 and later.
Third-party libraries: epub.js (BSD-3-Clause), JSZip (MIT/GPLv3).
π About the books in the screenshots: the demo uses free preview versions of books from LitResβ (a Russian e-book store) and a public-domain art book. They are shown for demonstration purposes only and are not distributed with the project.
Content type
Image
Digest
sha256:b9290faddβ¦
Size
81.6 MB
Last updated
about 2 months ago
docker pull hiddenath/bookhaven