Interactive micro-drama · Flutter + Node

Every episode
ends on a cliff.

Ninety-second vertical episodes. The screen cracks in two and the viewer decides which way it falls — one path costs coins, the other is free, and they lead to genuinely different endings.

Flutter app · Node + PostgreSQL backend · branch-graph editor · runs with no server at all in demo mode

See what happens
if she opens it
EMBER
Open the letter
◆ 40
TIDE
Burn it
FREE
63% burned it.

The app

Thirty-two screens, all built and wired — not a gallery of mockups. This is the loop a viewer actually walks.

9:41◆ 120
FOR YOU
ASH
HARBOR
She came back for the funeral. She stayed for the fire.

Feed
Ordered by the tropes you picked

9:41EP 12
ASH HARBOR EP 12

Player
Vertical video, seek and resume

9:41◆ 120
EMBER
OPEN THE
LETTER
◆ 40
TIDE
BURN IT
FREE
THE CLIFF
SEE WHAT HAPPENS
IF SHE OPENS IT
63% burned it.

The cliff
The screen cracks along the seam

9:411/4
BRANCH MAP
1 of 4 endings reached

Branch map
Solid = walked. Dim = still sealed.

The panel

Where the operator writes the story. The branch editor is the part no competing item has — and the validator below it is why a broken story cannot go live.

cliff.webkodingtheme.com/app/admin/series/1
ASH HARBOR
ash-harbor · 9 episodes · 4 endings
The graph holds together. Every episode is reachable and every cliff has two sides.
EP 12 · THE LETTER
Prompt — See what happens if she opens it
EMBER
Open the letter
40
EP 13 · Ashes ▾
TIDE
Go the long way
0
EP 13 · The Long Way Round ▾

Sign in to the live demo — credentials are on the page

The loop

Watch, hit the cliff, choose, watch the consequence. The choice is the product — not a feature bolted onto a player.

01 · Watch

Ninety seconds

Full-bleed vertical video with a progress bar that is really a countdown to a decision. Range-request seeking, resume where you left off.

02 · Choose

The fault line

The screen cracks along a seam. EMBER is the risky path and usually the paid one; TIDE is safe and free. The prompt is about the story, never the transaction.

03 · Diverge

Different endings

Each side leads to a different episode. A series ends in two, four or more places, and the branch map shows how much of it you have not seen.

Episodes are a graph,
not a list

This is the whole difference, and it goes all the way down to the schema. An episode is a node; a branch option is an edge to another episode. Nothing in the product assumes episode 12 is followed by episode 13.

  Typical short-drama app CLIFF
Episode orderA list. Next is next. A directed graph with validated edges
What the viewer buysThe next episode A branch — and the episode stays theirs
EndingsOne As many as the graph produces, counted automatically
The path not taken— Openable later and rewatchable
AdminEpisode list Branch editor that refuses to publish a broken story
Rewarded adsCredited by the phone Credited from AdMob's signed server callback
PurchasesOften trusted from the client Verified against Apple and Google server-side

What you get

The app and the backend are developed against each other. The app is not a mockup and the backend is not a stub.

Backend
5,092
lines, 23 tables
Flutter
3,749
lines, 32 screens
Backend tests
16
all passing
Dependencies
3
on the server
Flutter application

iOS and Android

Age gate, onboarding, guest-first auth, feed, search, library, series detail, player, the branch moment, branch map, wallet, streak, VIP, rewarded ads, and the compliance screens stores look for — data export and real account deletion.

Node + PostgreSQL

Backend and panel

Node 20, Express, PostgreSQL. No build step, no framework, no bundler — you can read every file. An installer sets up nginx, a certificate, a service and a daily job on Ubuntu in about ten minutes.

The differentiator

Branch editor

Lay out cliffs, point each side at an episode, mark your endings. A validator blocks publication while an episode is unreachable, a cliff has one side, or a choice leads nowhere — every one of which reads to a viewer as a broken app.

Money, done honestly

A ledger that balances

Every coin movement is idempotent and written in one transaction behind a row lock. The wallet always equals the sum of the ledger, and the test suite proves it under concurrent spends.

What is in the box

One zip. Both halves of the product, the design system that produced the look, and documentation that covers the whole thing.

cliff/
├── flutter_app/        iOS + Android, full source
│   ├── lib/data/        repository, models, state
│   ├── lib/features/    32 screens in 7 areas
│   ├── lib/core/        theme, router, widgets
│   └── test/            unit + live integration
├── backend/            Node 20 + PostgreSQL
│   ├── src/core/        ledger, access, branching
│   ├── src/api/         the mobile API
│   ├── src/panel/       operator panel
│   ├── src/providers/   video, IAP, ads
│   ├── src/db/schema.sql 23 tables, commented
│   ├── scripts/         install, seed, purge
│   └── test/            16 tests
├── design_system/      tokens, components, UI kit
├── documentation/      one file, opens in a browser
├── README.md
└── CHANGELOG.md
Source, not a binary

Everything readable

No build step on the server, no bundler, no framework. No AWS SDK, no Google API client, no RevenueCat — the request signing each of those would provide is written against the standard library, so you can read the sixty lines that do it.

PieceSize
Backend source~5,100 lines · 32 files
Flutter source~3,750 lines · 32 screens
Database23 tables, one commented file
Tests16 backend + 7 app
Server dependencies3
Documentationinstall → API → troubleshooting

Everything it does

Not a feature list of intentions. Each of these is built, wired to the backend and walkable in the live demo.

Watching
  • Vertical video player, seek and resume
  • Progress saved and restored per episode
  • Personalised feed from chosen tropes
  • Search across titles and synopses
  • Series detail with episode rail
  • Library and continue-watching
Branching
  • Cliff moment with the fault-line split
  • EMBER / TIDE two-temperature choice
  • Real choice statistics, withheld until meaningful
  • Branch map drawn from the live graph
  • Endings counted from the graph, never typed
  • Unchosen paths openable later
Money
  • Coin wallet with a full ledger
  • Coin packages, store-verified
  • VIP subscription, weekly or yearly
  • Rewarded ads via signed server callback
  • Daily streak that forgives a missed day
  • Idempotent spending — a retry never double-charges
Operator panel
  • Branch-graph editor with validation
  • Series, episodes, tropes, publishing
  • Viewers, balances and audited adjustments
  • Coin packages and VIP plans
  • Moderation queue
  • Remote config — change the economy without a release
Store compliance
  • Age gate enforced by the router
  • Data export on request
  • Real deletion with a recoverable grace period
  • Push priming before the system dialogue
  • Report and block with a queue behind it
  • Subscription management from settings
Running it
  • One-command installer for Ubuntu 22.04 / 24.04
  • nginx, certificate, service and timers set up
  • Local disk or S3-compatible storage
  • Demo seed with a real four-ending story
  • Demo mode — the app runs with no server
  • Rebrand from two files

Run it in five minutes

With no server, no database and no account. Demo mode holds the same branching story the backend seeds — four endings, three priced cliffs, coins that actually get spent.

No backend
cd flutter_app
flutter pub get
flutter run
Against your server
cd backend
npm install && npm run migrate
npm run seed && npm start

flutter run \
  --dart-define=CLIFF_API=https://your-domain/app/api/v1

Launch it as yours

Two files carry the whole look — swap the two accent colours and three Google fonts, and it is your brand. The full design system ships with it.

Requires a VPS and a domain. Apple and Google developer accounts are needed to publish. None of these are included in the item price — see the documentation for the full list.