Soul in Motion — 6:16 PM | 2026-09-10

javascript dev.to

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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));
  });
});
Enter fullscreen mode Exit fullscreen mode

Deployment Pipeline

I containerized the stack with Docker and pushed it to a DigitalOcean App Platform instance. The pipeline:

  1. docker build -t mybooks:latest .
  2. docker push registry.digitalocean.com/myorg/mybooks:latest
  3. 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";
}
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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.

Source: dev.to

arrow_back Back to Tutorials