TL;DR
- Built an interactive Frankenstein demo that animates scenes based on the text, including subtle air‑visualization.
- Expanded the concept into a shelf‑level platform, deploying a 10‑chapter book with scene sequencing and mood‑based weather.
- Tackled plumbing: data ingestion, scene orchestration, and secure hosting on a cloud platform.
- Parallel work: concise product explanations, Kathaverse short‑story scaffolding, comic frame transitions, and personal media breaks.
- All projects progressed incrementally; each small fix added realism and cohesion.
From Page to Space: Making Frankenstein Breathable
The core idea was simple: turn a static text into a living environment.
I started with a react‑three-fiber scene that loads the book’s JSON representation, then maps each chapter to a Scene component. The component adjusts lighting, background audio, and particle effects based on metadata extracted from the text (e.g., “dark, stormy night”).
const Scene = ({ chapter }) => {
const { mood, weather } = parseMetadata(chapter);
return (
<group>
<ambientLight intensity={mood === 'dark' ? 0.2 : 0.8} />
<audio src={weatherAudio[weather]} loop />
<AirParticles visible={chapter.includes('air')} />
</group>
);
};
The Invisible Air Effect
The “air becomes visible” trick was a debugging win. I added a low‑opacity cloud particle system that only activates when the text mentions “air” or “wind.” It’s almost imperceptible, but it grounds the reader in the environment.
function AirParticles({ visible }) {
if (!visible) return null;
return (
<points>
<bufferGeometry attach="geometry" {...cloudGeometry} />
<pointsMaterial attach="material" color="#fff" size={0.5} opacity={0.05} transparent />
</points>
);
}
The challenge was keeping the particles from over‑rendering. I throttled updates to once per second and used GPU instancing to keep the frame rate above 60 fps.
Scaling Up: From One Book to a Shelf
After polishing the single‑book demo, I built a shelf abstraction. Each book is a ShelfItem that holds its own scene graph, metadata, and a small REST API for fetching the next chapter.
# shelf.yaml
books:
- id: frankenstein
title: "Frankenstein"
chapters: 12
entry: /books/frankenstein/chapters/1
- id: great_expectations
title: "GreatExpectations"
chapters: 9
entry: /books/great_expectations/chapters/1
The API is a lightweight Node/Express server that serves chapter JSON and a WebSocket for real‑time updates (e.g., when a user marks a bookmark).
npm install express ws
const app = require('express')();
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server: app });
app.get('/books/:id/chapters/:num', (req, res) => {
res.json(require(`./data/${req.params.id}/chapter${req.params.num}.json`));
});
wss.on('connection', ws => {
ws.on('message', msg => {
// broadcast bookmark updates
wss.clients.forEach(client => client.send(msg));
});
});
Deployment Pipeline
I containerized the stack with Docker and pushed it to a DigitalOcean App Platform instance. The pipeline:
docker build -t mybooks:latest .docker push registry.digitalocean.com/myorg/mybooks:latest- Deploy via the DO dashboard, exposing port 80.
The only real plumbing hurdle was ensuring the WebSocket endpoint survived the platform’s HTTP load balancer. I added a proxy_pass rule in the Nginx config:
location /ws/ {
proxy_pass http://backend:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Translating Complexity into Clarity
Parallel to the technical work, I spent time crafting a one‑pager that explains the platform to non‑technical stakeholders. The goal was to distill the “interactive reading” concept into a single slide:
- Problem: Static books lack immersion.
- Solution: A web‑based environment that reacts to narrative cues.
- Benefits: Higher engagement, new monetization via premium scenes.
I used a simple diagram built in Figma, then exported it as an SVG and embedded it directly:
<img src="assets/interactive-reading.svg" alt="Interactive Reading Flow" />
Kathaverse: Minimalist Short Stories
In the Kathaverse project, I experimented with ultra‑short stories composed of a handful of scenes. Each scene is a JSON snippet:
{"scene":1,"description":"A lone lantern flickers in the dark alley.","audio":"dark_alley.mp3"}
The renderer stitches them together on the fly, using a simple state machine to transition between scenes. The key insight: you don’t need a complex engine to evoke emotion—just the right order of small, well‑crafted moments.
Comic Strip Smoothness
I revisited an old comic strip project to tighten frame transitions. Using GSAP I added a subtle easing function that guides the eye:
gsap.fromTo(".frame", { opacity: 0 }, {
opacity: 1,
duration: 0.8,
ease: "power2.out",
stagger: 0.1
});
The result is a fluid flow that feels “right” without being obvious. Small tweaks like this accumulate into a polished experience.
Personal Recharge
Between code and design, I let myself unwind with Scorpion episodes and a playlist of Bad Bunny & Jhay Cortez. The music helped clear mental clutter, making the next debugging session feel fresh.
Day in Review
- Frankenstein: Scene orchestration, air‑visualization, particle throttling.
- Shelf Platform: API, WebSocket, Docker, DO deployment.
- Communication: One‑pager, Figma diagram.
- Kathaverse: Minimalist story engine.
- Comic: GSAP frame easing.
- Self‑care: Media breaks.
Each task, though distinct, fed into a common goal: making narrative feel alive. The day felt cohesive because every small improvement added a layer of realism.
Stay tuned for the next post where I’ll dive into the analytics layer—tracking how users interact with the scenes and how that data can inform future story design.