A public, no-login web reader for the Someleze's Semantic Meltdown manga series, built on Rayfin with React, Vite and Tailwind CSS.
Chapter pages are served as optimised WebP static assets; reading progress and display preferences live in the browser, so there are no accounts and no tracking.
- Chapter grid — covers, titles, release dates, and per-chapter completion badges.
- Resume where you left off — the hero turns into a "Resume Episode 002 · page 2 / 11" call to action once you've started reading.
- Distraction-free reader — chrome auto-hides after 3 seconds and returns on a centre tap, a hover near the top or bottom edge, or any key press.
- Real page turns — a 3D leaf rotates about the spine, revealing the page beneath. Can be switched off, and is disabled automatically when the OS requests reduced motion.
- A closing page per chapter — turn past the last page to reach the next episode, rather than scrolling past the art to find a link.
- Full screen — from the toolbar or the
Fkey. - Three scaling modes — Page fits the whole sheet on screen (default), Height fills the viewport height and lets wide pages scroll sideways, Width fills the width and scrolls down. When a page overflows, turning it zooms out to the whole page, flips, then zooms back in.
- Keyboard, click and swipe navigation — arrow keys,
Home/End,Space, tap zones, and touch swipes. - Auto-saved progress — stored per device in
localStorage, no account required. - Configurable next-release notice — show a release date with a countdown, or a custom message when the date isn't known.
- Responsive — one layout that works from a 360px phone to a desktop display.
-
Node.js 20 or later
-
poppler and ImageMagick — only needed to re-import comics
brew install poppler imagemagick # macOS sudo apt install -y poppler-utils imagemagick # Debian/Ubuntu
npm install
npm run dev:webOpen http://localhost:5173.
Note
npm run dev:web starts the frontend only, which is all this app needs today.
Use npm run dev when you start using the Rayfin backend — it deploys the
Rayfin services to Microsoft Fabric first and requires npx rayfin login.
-
Drop the new PDF into
import/, namedYYYY-MM-DD-episode-NNN.pdf. The date becomes the release date andNNNthe episode number. (The PDFs aren't committed — only the generated pages are.) -
Run the importer:
npm run comics:import
It renders each page to WebP under
public/comics/<slug>/, generates a cover, and adds the chapter tosrc/data/manga.json. Existing chapters are skipped unless you pass--force, and any titles or summaries you wrote by hand are preserved. -
Edit the new entry's
titleandsummaryinsrc/data/manga.json.
Options: --source <dir> (default import), --width <px> (default 1400), --quality <1-100> (default 82), --force.
Everything editable lives in src/data/manga.json.
The next-release panel falls back to message whenever date is null or already in the past, so a stale date never claims an episode is overdue.
├── rayfin/
│ ├── rayfin.yml # Rayfin service config (auth disabled, data enabled)
│ └── data/schema.ts # Entity registry — empty until you add entities
├── import/ # Source chapter PDFs (not committed)
├── scripts/
│ └── import-comics.mjs # PDF → WebP importer + manifest updater
├── public/comics/ # Generated page images (committed)
├── src/
│ ├── main.css # Design system: palette, type, halftone/grain textures
│ ├── App.tsx # Routes
│ ├── data/
│ │ ├── manga.json # Series info + chapter list (edit this)
│ │ └── manga.ts # Typed accessors and date helpers
│ ├── features/
│ │ ├── persistentStore.ts # Tiny localStorage-backed observable store
│ │ ├── progress.ts # Reading progress + resume logic
│ │ └── preferences.ts # Scale mode, animation, reduced-motion handling
│ ├── components/ # NextReleaseNotice, ReaderSettings
│ ├── pages/ # HomePage, ReaderPage
│ └── services/
│ └── rayfinClient.ts # Lazy Rayfin data client (unused seam for later)
└── package.json
| Command | Description |
|---|---|
npm run dev:web |
Start the frontend dev server (no Fabric deploy) |
npm run dev |
Deploy Rayfin services to Fabric, then start the dev server |
npm run build |
Production build into dist/ |
npm run comics:import |
Convert source PDFs to WebP pages and refresh the manifest |
npm run lint |
Lint with ESLint |
npm run test |
Run unit tests with Vitest |
npm run rayfin:up |
Deploy to Fabric (no dev server) |
The app reads chapters from a JSON file, which means adding a chapter needs a rebuild. To manage chapters without redeploying the frontend, move them into Rayfin's data service:
- Declare an entity in
rayfin/data/with@entity()and@anonymous('read')so the public can read it. Use@text({ max: N })on every string field — unbounded text breaks GraphQL schema generation on MSSQL. - Register it in
rayfin/data/schema.ts(both theAppSchematype and theschemaarray). - Deploy and apply the schema with
npx rayfin up. - Read it from the frontend via
getRayfinClient().data.Chapterinsrc/services/rayfinClient.ts.
Page images should stay in public/comics/ either way — the database is for metadata, not binaries.
The frontend is a static bundle, served by Rayfin's static hosting:
npx rayfin login
npx rayfin up
npx rayfin up status- Rayfin documentation
- Rayfin on GitHub
- Rayfin docs are also version-locked to this project — run
npx -y @microsoft/rayfin-cli docs search '<topic>'from the project root.
{ "series": { "title": "Someleze's Semantic Meltdown", "tagline": "Data everywhere. Meaning nowhere.", "description": "…", "nextRelease": { // Set a date to show it with a countdown… "date": "2026-09-30", // …otherwise this message is shown instead. "message": "Episode 004 is in the inker's hands." } } }