# MangaCircus A no-frills, self-hosted manga / comic / webtoon reader. - **Zero dependencies.** A Node.js server (`server.js`, `library.js`) and plain HTML/CSS/JS pages. No build step, no framework, no database server. - Reads the manga straight from a folder tree. The same folders stay readable in any normal picture viewer. - Accounts with per-user reading progress, view settings and "new chapter" notifications. - Reader with three view modes (**Flow**, **Page**, **Book**), a page zoom that leaves the controls alone, and tap zones for mobile. ## Run it ```bash node server.js ``` Open http://localhost:8080. The first account you create becomes the admin. Try it with demo content: ```bash node tools/make-dummy.js library ``` ### Docker ```bash docker compose up -d --build ``` `docker-compose.yml` mounts `./library` read-only at `/library` and keeps accounts/progress in the `mangacircus-data` volume (`/data`). ### Settings (environment variables) | Variable | Default | Meaning | |-----------------|-------------|---------| | `PORT` | `8080` | HTTP port | | `LIBRARY` | `./library` | Library folder (read-only access is enough) | | `DATA` | `./data` | Where `db.json` (users, progress, votes, notifications) and `scan-cache.json` live | | `SIGNUP` | `closed` | `open` lets anyone create an account. Otherwise the admin adds users under Settings | | `SCAN_INTERVAL` | `10` | Minutes between automatic library rescans (`0` = only at start and on "Rescan") | | `COOKIE_SECURE` | – | `1` marks the login cookie `Secure` (use it when you serve over HTTPS) | | `SENPAI_URL` | – | Optional: base URL of a [Senpai](#whos-here-senpai-optional) story companion, e.g. `http://senpai-api:8780`. Unset = feature off | | `SENPAI_TOKEN` | – | Service token for `SENPAI_URL` | Serve it behind a reverse proxy with HTTPS if it is reachable from the internet. All pages use relative URLs, so a sub-path like `/manga/` works. --- ## Library format Always three folder levels, **Manga / Book / Chapter**, with plain page images in the chapter folder. Page roles are tags in the file name: ``` library/ Moon Garden/ <- manga folder (its name is the manga's id) manga.json cover.jpg <- optional v01 Spring/ <- book (volume / season) c0001 The Seed/ <- chapter chapter.json <- recommended 0001_cover.jpg 0002.jpg 0003_double.jpg 0004_rot90.jpg 0005_extra.jpg c0002 Roots [MangaDex]/ <- same number twice = two versions of one chapter c0002 Roots [Official]/ c0002.5 Tea Time/ <- in-between chapter x01 Character Sheets/ <- extra chapter, outside the numbering x01 Specials/ <- extra book ``` ## Documentation | Document | For | |----------|-----| | [docs/library-format.md](docs/library-format.md) | The folder format: naming rules, page tags, metadata fields, versions, extras, what survives a rename | | [docs/import.md](docs/import.md) | How the library is scanned and indexed, caching, change detection and notifications, troubleshooting | | [docs/architecture.md](docs/architecture.md) | Components, data model, API, security, the reader's layout engine, deployment, design decisions | | [docs/scraper-guide.md](docs/scraper-guide.md) | Rule book (agent-readable) for scrapers and other tools that write into the library | --- ## Reading | | | |---|---| | **Flow** | All pages below each other at the same width. Double pages keep the same height and are twice as wide, but never wider than the screen. Webtoon slices join without gaps | | **Page** | One page (or one double page) at a time | | **Book** | Two facing pages, in reading order (right page first for `rtl`). Covers and double pages stand alone. Pairing restarts after each of them, so one odd page never shifts the whole chapter. If pairs still look wrong, **⇄ Shift** moves the pairs of the current section by one page (remembered per chapter) | - **Which mode opens:** Settings → *Open chapters in* = a fixed mode or *the mode I used last*. With *Use the fitting mode…* on, content that isn't made for your choice opens in the mode that fits (webtoons always open in Flow). That automatic switch never changes your remembered mode. Only clicking a mode in the reader does. - **Page zoom:** 100 % means the page fits the screen (in Flow: a normal single page fits the screen; for webtoons, the strip is as wide as the screen but never wider than its own width). Pages may end up larger than the screen, exactly fitting, or smaller. Zoom with Ctrl + mouse wheel, touchpad pinch or two-finger pinch on the pages, or with `+` / `-` / `0`. This zooms only the pages; zooming over the toolbar zooms the whole page as usual. When a page is larger than the screen, drag it with the mouse (hand cursor) or pan with a finger. The zoom is remembered per mode for your account. Click the `100%` button to fit again. - **Tap / click zones:** - Tapping **beside the pages** always shows or hides the controls. - On a page, its left and right part turn the page, following the reading direction. For `rtl`, the left part goes forward. - In Flow: the bottom part of the screen goes forward, the top part goes back. - The middle of the page also toggles the controls (useful when a zoomed page fills the screen). - A horizontal swipe also turns pages on touch screens. - Keys: arrows, Space / PageDown, `m` = controls, `f` = fullscreen. - **Controls stay out of the way:** they hide as soon as you turn a page or scroll, and come back only with a tap (middle of the page or beside it). Scrolling up never brings them back. The first time they hide, a short hint says so. - **Controls:** the top bar has a chapter picker with every chapter of the manga, grouped by book, with ✓ for read ones. The bottom bar has the page slider, the mode switch, Shift, zoom, the version picker and previous/next chapter. - **Loading:** the next pages (next 3 in Page mode, next 2 spreads in Book mode) are downloaded and decoded in advance, so page turns don't flash. A page that still isn't there shows a spinner. On phones, "Add to home screen" opens the app without browser bars, and the ⛶ button (where supported) goes fullscreen. - **Progress** is saved per user and chapter version: the last page, whether the chapter is done, and book-mode shifts. The manga page offers *Continue*. Tap a chapter's status to mark it read/unread. - **Notifications:** press *Notify me* on a manga. When a rescan finds new chapter numbers, they show up under the bell. Optionally, the server also POSTs a short text to a webhook URL of your choice (e.g. an ntfy.sh or Gotify topic, set in Settings). ## Who's here (Senpai, optional) With `SENPAI_URL` set, the reader's top bar gets a **Who's here** button (key `x`). It opens a side sheet with *This page* / *This chapter* / *So far*: the characters on the visible page(s) (hover or tap outlines them on the page), the cast of the chapter and of the series with their prominence, and a character card with the names known so far, first appearance and a timeline whose links jump to the page. Everything is limited to what you have read: the server forwards `GET /api/senpai/v1/…` to Senpai with your reading horizon in this manga (the furthest page of the furthest chapter you have opened; paging back to re-read does not lower it). Options in the sheet: *Only up to this page* (for re-reading) and *Show unconfirmed* (machine findings). Without `SENPAI_URL` nothing changes. Running both together: Senpai's `docker-compose.circus.yml` and `docs/runbook.md`. ## Files ``` server.js HTTP server, accounts, progress, votes, notifications (JSON file storage) library.js folder scanner + image size reader public/ the web pages (library, manga, reader, settings, login) tools/ demo library generator (pure Node, draws PNGs itself) ```