<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: reelfork</title>
    <description>The latest articles on DEV Community by reelfork (@reelfork_3f49c0b4b1f3ca0c).</description>
    <link>https://dev.to/reelfork_3f49c0b4b1f3ca0c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4058665%2Fb7fe9a48-6a3a-4d21-9129-d7f39af68fdb.png</url>
      <title>DEV Community: reelfork</title>
      <link>https://dev.to/reelfork_3f49c0b4b1f3ca0c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/reelfork_3f49c0b4b1f3ca0c"/>
    <language>en</language>
    <item>
      <title>Visual-novel scenes in an interactive drama: how one text game node carried a 4-ending story</title>
      <dc:creator>reelfork</dc:creator>
      <pubDate>Sun, 16 Aug 2026 09:53:38 +0000</pubDate>
      <link>https://dev.to/reelfork_3f49c0b4b1f3ca0c/visual-novel-scenes-in-an-interactive-drama-how-one-text-game-node-carried-a-4-ending-story-1emm</link>
      <guid>https://dev.to/reelfork_3f49c0b4b1f3ca0c/visual-novel-scenes-in-an-interactive-drama-how-one-text-game-node-carried-a-4-ending-story-1emm</guid>
      <description>&lt;p&gt;Some story beats fight against video: long conversations, inner monologues, the quiet standoff before a hard choice. Generating minutes of video for a dialogue-heavy act is expensive, and lip-synced delivery rarely beats well-paced text. Visual novels solved this decades ago — background art, character sprites, a dialogue box, one click per line.&lt;/p&gt;

&lt;p&gt;ReelFork's text game node brings that form into the interactive drama story tree. It renders everything live in the player — no video file involved — and when the last line is read, the story continues through the node's exit like any other node. Video nodes and text game nodes mix freely in one tree, each doing what it does best.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case: 7 nodes, 4 endings, one text game centerpiece
&lt;/h2&gt;

&lt;p&gt;The published wuxia drama "Three Invitations at the Ferry" is built from just 7 nodes: a 10-second opening video of A-Ning waiting at a ferry dock, then a single text game node — 54 lines in which three old friends arrive one by one to make their case — then a four-way choice, three video endings, and a hidden fourth: a 28-line text game node with no exit at all, ending the story in pure text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5cpbotax1cryk2wt38st.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5cpbotax1cryk2wt38st.png" alt="The ferry dock at sunset — the scene's background art" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The division of labor is the lesson. Video carries what video is good at: the opening atmosphere and the ending payoffs. Text carries the emotional core — three characters each get a full entrance, voice, and argument, at a density no video budget would allow. The entire 54-line act compiles to about 60KB inside the drama's RFD file, less than one second of video.&lt;/p&gt;

&lt;h2&gt;
  
  
  Staging tricks worth stealing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sound before sight.&lt;/strong&gt; The scene opens with an off-screen voice line — hoofbeats written as text — before the rider's sprite enters on the next line. With audio channels not yet in play, sound is staged in the reader's head.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Center stage follows the speaker.&lt;/strong&gt; 52 of the 54 lines carry sprite adjustments — whoever speaks takes the center position while the others step aside, and once all three suitors arrive, three sprites share the screen. Each line inherits the previous line's stage by default, so the author only overrides where the blocking changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rhythm is a setting, not an accident.&lt;/strong&gt; The scene runs a 34ms-per-character typewriter with 130ms punctuation pauses, and every line carries an end-of-line pause. It reads like a person speaking, not subtitles scrolling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lingering last frame.&lt;/strong&gt; When the text game ends and the choice appears, the final frame stays as the backdrop — the choice buttons float over three tokens resting in front of A-Ning. The viewer decides inside the scene's atmosphere, not on a black screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftrhb9ruekpwbm2rz9zco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftrhb9ruekpwbm2rz9zco.png" alt="Character sprite for Gu Yunxiu — sprites take center stage as they speak" width="800" height="1183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A text-only ending
&lt;/h2&gt;

&lt;p&gt;The fourth option, "choose no one", leads to a second text game node that simply has no exit. Twenty-eight lines of monologue at the dock, and the story ends where the text ends. When an ending's weight is inner voice rather than spectacle, a text ending lands harder than a video one — and costs almost nothing to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build one
&lt;/h2&gt;

&lt;p&gt;In ReelFork Studio's story tree, add a node of type "Text Game", open the built-in editor, and write sections and lines — each line takes an optional speaker, its text, and per-line overrides for four channels: background, sprite layers, dialogue box, and music. Assets are referenced from the project canvas; an Agent button can generate backgrounds and sprites in place.&lt;/p&gt;

&lt;p&gt;Publishing compiles the scene into the drama's RFD automatically as a &lt;code&gt;textgame&lt;/code&gt; payload with an inlined asset table. The payload is a compiled artifact — hand-written payloads are rejected at upload validation — so all authoring happens in the editor. Cinema playback, embeds, and offline exports all render it natively.&lt;/p&gt;

&lt;p&gt;Play the example: &lt;a href="https://www.reelfork.com/drama/dca7d4a0-cb5f-427b-a1de-6d03fbec791a?utm_source=devto&amp;amp;utm_medium=seo-article&amp;amp;utm_campaign=blog-textgame" rel="noopener noreferrer"&gt;Three Invitations at the Ferry&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.reelfork.com/blog/text-game-nodes-case-study?utm_source=devto&amp;amp;utm_medium=seo-article&amp;amp;utm_campaign=blog-textgame" rel="noopener noreferrer"&gt;ReelFork blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>storytelling</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Embedding an interactive, branching video player with one iframe</title>
      <dc:creator>reelfork</dc:creator>
      <pubDate>Mon, 03 Aug 2026 15:38:20 +0000</pubDate>
      <link>https://dev.to/reelfork_3f49c0b4b1f3ca0c/embedding-an-interactive-branching-video-player-with-one-iframe-b9i</link>
      <guid>https://dev.to/reelfork_3f49c0b4b1f3ca0c/embedding-an-interactive-branching-video-player-with-one-iframe-b9i</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.reelfork.com/blog/embed-interactive-drama?utm_source=devto&amp;amp;utm_medium=seo-article&amp;amp;utm_campaign=blog-embed" rel="noopener noreferrer"&gt;ReelFork blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Embedding a video player is a solved problem. Embedding an &lt;em&gt;interactive&lt;/em&gt; one — where the viewer's choices branch the story, and where those choices need to survive fullscreen, resizing, and a paywall — is slightly less solved. Here's how the ReelFork embed works, including the two things that usually go wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The snippet
&lt;/h2&gt;

&lt;p&gt;One iframe. No JavaScript, no SDK, no server-side configuration on your end.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://player.reelfork.com/d/&amp;lt;drama_id&amp;gt;"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt;
  &lt;span class="na"&gt;allow=&lt;/span&gt;&lt;span class="s"&gt;"autoplay; fullscreen"&lt;/span&gt;
  &lt;span class="na"&gt;allowfullscreen&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get the filled-in version from the drama page on ReelFork Cinema: &lt;strong&gt;Share → Embed&lt;/strong&gt;. The drama ID and player URL come pre-populated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx69pr5f4o0vr6g06rr58.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx69pr5f4o0vr6g06rr58.png" alt="Four steps from the drama page to a working embed" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it responsive
&lt;/h2&gt;

&lt;p&gt;The fixed 640×360 works for a desktop column and breaks everywhere else. The reliable fix is the old padding-bottom trick, and it's still the right answer: it reserves the correct box &lt;em&gt;before&lt;/em&gt; the iframe loads, so the page doesn't reflow when the player arrives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.drama-embed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;56.25%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* 9 / 16 */&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.drama-embed&lt;/span&gt; &lt;span class="nt"&gt;iframe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"drama-embed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://player.reelfork.com/d/&amp;lt;drama_id&amp;gt;"&lt;/span&gt; &lt;span class="na"&gt;allowfullscreen&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F82dj003p3m3d0ioazg8h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F82dj003p3m3d0ioazg8h.png" alt="How padding-bottom reserves a 16:9 box before the iframe loads" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you only support modern browsers, &lt;code&gt;aspect-ratio: 16 / 9&lt;/code&gt; on the iframe does the same job in one line. The wrapper version is still worth keeping if you care about older Safari.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thing that goes wrong #1: autoplay
&lt;/h2&gt;

&lt;p&gt;Browsers block autoplay with sound. The usual workaround — mute the video — is exactly wrong for a story-driven format, where the dialogue &lt;em&gt;is&lt;/em&gt; the content.&lt;/p&gt;

&lt;p&gt;So the player doesn't autoplay. It renders a start screen and waits for a click. That click is a user gesture, which unblocks audio for the rest of the session. This is why &lt;code&gt;allow="autoplay"&lt;/code&gt; in the snippet is about permitting later programmatic playback between branches, not about starting on load.&lt;/p&gt;

&lt;p&gt;The practical consequence for you: don't try to force playback from the parent page. It will either be blocked or will start muted, and a muted interactive drama is a broken one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thing that goes wrong #2: assuming the iframe is sandboxed away from monetization
&lt;/h2&gt;

&lt;p&gt;It isn't, and that's deliberate. Unlocks made inside an embedded player count toward creator earnings identically to unlocks on Cinema. Embedding moves &lt;em&gt;where&lt;/em&gt; the drama is watched, not whether it's monetized.&lt;/p&gt;

&lt;p&gt;The player is served from &lt;code&gt;player.reelfork.com&lt;/code&gt; with permissive CORS headers, so it works from any origin without you configuring anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting at a specific episode
&lt;/h2&gt;

&lt;p&gt;Append &lt;code&gt;?episode=&amp;lt;episode_id&amp;gt;&lt;/code&gt; to the &lt;code&gt;src&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://player.reelfork.com/d/&amp;lt;drama_id&amp;gt;?episode=&amp;lt;episode_id&amp;gt;"&lt;/span&gt; &lt;span class="na"&gt;allowfullscreen&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Episode IDs are listed in the embed panel when you pick a specific episode from the dropdown. Useful when you're writing a post about one chapter and want readers to land there rather than at the series start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is actually useful
&lt;/h2&gt;

&lt;p&gt;The honest case for embedding isn't "reach" — it's &lt;em&gt;context&lt;/em&gt;. A newsletter archive, a portfolio, a documentation page, a community forum thread: places where the reader already has a reason to care, and where sending them to a new platform loses most of them at the click.&lt;/p&gt;




&lt;p&gt;Full docs: &lt;a href="https://www.reelfork.com/blog/embed-interactive-drama?utm_source=devto&amp;amp;utm_medium=seo-article&amp;amp;utm_campaign=blog-embed" rel="noopener noreferrer"&gt;Embed interactive dramas on any website&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>css</category>
      <category>video</category>
    </item>
    <item>
      <title>RFD: a YAML format for branching video, so the story lives in Git</title>
      <dc:creator>reelfork</dc:creator>
      <pubDate>Mon, 03 Aug 2026 15:34:32 +0000</pubDate>
      <link>https://dev.to/reelfork_3f49c0b4b1f3ca0c/rfd-a-yaml-format-for-branching-video-so-the-story-lives-in-git-4n1l</link>
      <guid>https://dev.to/reelfork_3f49c0b4b1f3ca0c/rfd-a-yaml-format-for-branching-video-so-the-story-lives-in-git-4n1l</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.reelfork.com/blog/rfd-format-introduction?utm_source=devto&amp;amp;utm_medium=seo-article&amp;amp;utm_campaign=blog-rfd" rel="noopener noreferrer"&gt;ReelFork blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most interactive-video tooling keeps the story structure locked inside a proprietary editor. If you want to know what your branching drama actually looks like, you open the app and squint at a canvas. You can't diff it. You can't review it. You certainly can't generate it from a script.&lt;/p&gt;

&lt;p&gt;We went the other way. In ReelFork, the story is a YAML file — readable, diffable, and perfectly happy in Git. It's called &lt;strong&gt;RFD&lt;/strong&gt; (ReelFork Drama), and this post walks through the whole format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a text format at all
&lt;/h2&gt;

&lt;p&gt;Three reasons, all of them boring and all of them the point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Diffable.&lt;/strong&gt; &lt;code&gt;git diff&lt;/code&gt; on a story file shows you exactly which choice changed. Try that with a binary project file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generatable.&lt;/strong&gt; An LLM can emit valid RFD directly. Our story agent does exactly this — it writes YAML, not clicks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portable.&lt;/strong&gt; The canvas is one view of the file, not the source of truth. Author in the editor, in a text file, or from a script, and you end up in the same place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4s44unevha8vve4uame.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4s44unevha8vve4uame.png" alt="An RFD file and the graph it renders as" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The minimum viable file
&lt;/h2&gt;

&lt;p&gt;A valid RFD file needs four things: a format version, a title, a cover, and a list of nodes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;rfd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0"&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Night&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Shift"&lt;/span&gt;
&lt;span class="na"&gt;cover&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cover.jpg"&lt;/span&gt;
&lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scene_01&lt;/span&gt;
    &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clips/01.mp4"&lt;/span&gt;
    &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scene_02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's already playable. It's a one-scene drama that ends after &lt;code&gt;scene_02&lt;/code&gt;, which is not much of a drama — but it validates and it runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nodes are scenes
&lt;/h2&gt;

&lt;p&gt;Every node is one video clip plus instructions for what happens when it ends.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scene_01&lt;/span&gt;          &lt;span class="c1"&gt;# unique within the file&lt;/span&gt;
  &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clips/01.mp4"&lt;/span&gt; &lt;span class="c1"&gt;# path relative to the package root&lt;/span&gt;
  &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scene_02&lt;/span&gt;        &lt;span class="c1"&gt;# linear continuation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;next&lt;/code&gt; is the linear case. Replace it with &lt;code&gt;choices&lt;/code&gt; and the node becomes a fork.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choices are edges
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scene_01&lt;/span&gt;
  &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clips/01.mp4"&lt;/span&gt;
  &lt;span class="na"&gt;choices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Help&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stranger"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;scene_help&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Keep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;walking"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;scene_ignore&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each entry is &lt;code&gt;[label, target_node_id]&lt;/code&gt;. The player freezes on the clip's last frame, renders the labels as buttons, and jumps to the target the viewer picks. Two options is the common case; three is fine; four starts to feel like a form rather than a story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conditions and effects: state across the whole drama
&lt;/h2&gt;

&lt;p&gt;This is where it stops being a flowchart. &lt;code&gt;effects&lt;/code&gt; writes to a variable when a branch is taken. &lt;code&gt;if&lt;/code&gt; gates whether a node or a choice is available at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scene_help&lt;/span&gt;
  &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clips/help.mp4"&lt;/span&gt;
  &lt;span class="na"&gt;effects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;trust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;+1&lt;/span&gt;
  &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;act_two&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;act_two&lt;/span&gt;
  &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clips/act2.mp4"&lt;/span&gt;
  &lt;span class="na"&gt;choices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ask&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;him&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;key"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;scene_key&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trust &amp;gt;= &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Break&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;door"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;scene_force&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables persist for the whole watch session, which is how a decision in episode one can open — or permanently close — a route in episode three.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0tdcfxinoup8kixqtw4f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0tdcfxinoup8kixqtw4f.png" alt="How a variable set in episode one gates a node in episode three" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The practical advice: keep the variable count small. Two or three named flags (&lt;code&gt;trust&lt;/code&gt;, &lt;code&gt;has_key&lt;/code&gt;, &lt;code&gt;told_truth&lt;/code&gt;) cover almost every story you'd actually shoot. A dozen booleans produces a state space you cannot test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text game nodes: visual-novel scenes without video
&lt;/h2&gt;

&lt;p&gt;RFD also supports a &lt;code&gt;textgame&lt;/code&gt; node type for visual-novel style scenes: background art, character sprites, typewriter dialogue, music, and voice — all rendered live by the player, with no video file involved. A text game node declares &lt;code&gt;type: textgame&lt;/code&gt; and carries a compiled &lt;code&gt;textgame&lt;/code&gt; payload (&lt;code&gt;schema: reelfork.textgame&lt;/code&gt;) plus a &lt;code&gt;next&lt;/code&gt; field for its exit.&lt;/p&gt;

&lt;p&gt;A published example shows the economics: in the wuxia drama "Three Invitations at the Ferry", the 54-line centerpiece — three characters arriving one by one, sprites trading center stage, backgrounds cutting between wide and close views — is a single text game node that compiles to about 60KB, less than one second of video. One branch even ends in a second text game node with no exit at all: a text-only ending. The choice buttons render over the scene's lingering final frame.&lt;/p&gt;

&lt;p&gt;Unlike the rest of RFD, the payload is not meant to be hand-written. You author text game scenes in the ReelFork Studio story tree with the built-in Text Game editor; publishing compiles the scene into the RFD automatically. Hand-crafted payloads are rejected at upload validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ships in a package
&lt;/h2&gt;

&lt;p&gt;An RFD package is a plain zip: one YAML file plus every asset it references, with paths resolved relative to the package root.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhh1cprl31owovlwbgudh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhh1cprl31owovlwbgudh.png" alt="The file layout inside an RFD package" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation happens on upload
&lt;/h2&gt;

&lt;p&gt;Uploading runs the file through a validator before anything is published. It checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the YAML parses and &lt;code&gt;rfd&lt;/code&gt; is a supported version&lt;/li&gt;
&lt;li&gt;every &lt;code&gt;video&lt;/code&gt; path resolves to a file that's actually in the package&lt;/li&gt;
&lt;li&gt;every &lt;code&gt;next&lt;/code&gt; and every choice target points at a node that exists&lt;/li&gt;
&lt;li&gt;the start node is reachable and at least one ending is reachable from it&lt;/li&gt;
&lt;li&gt;no node is orphaned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Failures come back as a list with node IDs, not a generic "upload failed". The point is to catch a broken reference at upload time rather than when a viewer hits a dead end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you might care even if you never use ReelFork
&lt;/h2&gt;

&lt;p&gt;The interesting idea here isn't our specific keys. It's that &lt;strong&gt;branching video is a graph, and graphs belong in text&lt;/strong&gt;. Once the story is a file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code review works on narrative structure&lt;/li&gt;
&lt;li&gt;CI can validate every path is reachable on every commit&lt;/li&gt;
&lt;li&gt;a script can generate 40 localized variants from one source&lt;/li&gt;
&lt;li&gt;an LLM can author the structure and a human can edit the diff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building anything with branching media, steal the shape of this even if you don't use the format.&lt;/p&gt;




&lt;p&gt;Full reference and the rest of the spec: &lt;a href="https://www.reelfork.com/blog/rfd-format-introduction?utm_source=devto&amp;amp;utm_medium=seo-article&amp;amp;utm_campaign=blog-rfd" rel="noopener noreferrer"&gt;The RFD format on the ReelFork blog&lt;/a&gt;. If you want to see a finished branching piece before reading a spec, there's a &lt;a href="https://www.reelfork.com/tutorials/interactive-ad?utm_source=devto&amp;amp;utm_medium=seo-article&amp;amp;utm_campaign=blog-rfd-tut" rel="noopener noreferrer"&gt;worked example here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>yaml</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>video</category>
    </item>
    <item>
      <title>I Built a Clickable Video Ad in 30 Minutes — Here's the Exact Workflow</title>
      <dc:creator>reelfork</dc:creator>
      <pubDate>Sun, 02 Aug 2026 07:49:55 +0000</pubDate>
      <link>https://dev.to/reelfork_3f49c0b4b1f3ca0c/i-built-a-clickable-video-ad-in-30-minutes-heres-the-exact-workflow-4eb0</link>
      <guid>https://dev.to/reelfork_3f49c0b4b1f3ca0c/i-built-a-clickable-video-ad-in-30-minutes-heres-the-exact-workflow-4eb0</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.reelfork.com/tutorials/interactive-ad?utm_source=devto&amp;amp;utm_medium=seo-article&amp;amp;utm_campaign=tutorial-interactive-ad" rel="noopener noreferrer"&gt;ReelFork tutorials page&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most video ads are a one-way street: the viewer watches (or skips), and that is the whole relationship. Interactive ads flip that. The viewer makes a choice inside the ad — which flavor, which storyline, which ending — and the ad branches accordingly. Engagement and completion rates go up because the viewer is &lt;em&gt;doing&lt;/em&gt; something, not just watching something.&lt;/p&gt;

&lt;p&gt;The catch used to be production cost: branching video meant custom players and custom code. This walkthrough shows the no-code version. I built a fully clickable beverage ad — one main storyline, two flavor branches — in about 30 minutes using ReelFork Studio. Here is the exact workflow, step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;A vending-machine scene: the intro video plays, then freezes on two glowing bottles. The viewer taps "Fresh Lemon" or "Sweet Peach", and the ad continues into that flavor's branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Finvy78sjjyv7xkbap8d5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Finvy78sjjyv7xkbap8d5.webp" alt="The finished choice screen" width="799" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Open the canvas
&lt;/h2&gt;

&lt;p&gt;From the home page, select &lt;strong&gt;Free Canvas&lt;/strong&gt; to enter the canvas page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ynx4ww3v96yly84om0x.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ynx4ww3v96yly84om0x.webp" alt="Select Free Canvas" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create an interactive story tree
&lt;/h2&gt;

&lt;p&gt;Double-click the canvas to open the node list, then click &lt;strong&gt;Interactive Story Tree&lt;/strong&gt;. Double-click the tree to enter node editing. You can switch between &lt;strong&gt;Interactive Story Tree&lt;/strong&gt; and &lt;strong&gt;Infinite Canvas&lt;/strong&gt; from the top navigation at any time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feo17nkgudn6qjaknrxzc.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feo17nkgudn6qjaknrxzc.webp" alt="Create the story tree" width="800" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Produce the video content
&lt;/h2&gt;

&lt;p&gt;Ignore the node editor's buttons for now — go back to the canvas and produce your clips there. This example needs three: one intro (the main line) and two branch clips (lemon and peach).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk5huc21djm9d4b1ve639.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk5huc21djm9d4b1ve639.webp" alt="Videos on the canvas" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Bind the main video
&lt;/h2&gt;

&lt;p&gt;Back in the story tree, open &lt;strong&gt;Final Video&lt;/strong&gt; in the right-hand menu and click &lt;strong&gt;Bind Existing Content&lt;/strong&gt; — you'll see every video on the canvas. Bind the intro to the &lt;strong&gt;Start&lt;/strong&gt; node. A &lt;strong&gt;READY&lt;/strong&gt; badge confirms it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgy7avgf17pfdipqxqkf4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgy7avgf17pfdipqxqkf4.webp" alt="Start node READY" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Create the branch choice
&lt;/h2&gt;

&lt;p&gt;Drag the &lt;strong&gt;+&lt;/strong&gt; next to the Start tab and click &lt;strong&gt;Choice&lt;/strong&gt; to create a choice interaction panel. Pick a style that matches your content — mine is comic-styled, so Comic Popup fits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzeqamgj74si95txfbd2e.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzeqamgj74si95txfbd2e.webp" alt="Create the choice" width="799" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Extract the last frame
&lt;/h2&gt;

&lt;p&gt;In the workbench, click &lt;strong&gt;Canvas Video&lt;/strong&gt; → &lt;strong&gt;Select&lt;/strong&gt;, choose the intro, then &lt;strong&gt;Use Video&lt;/strong&gt; → &lt;strong&gt;Extract Last Frame&lt;/strong&gt;. That frozen frame is what viewers see while deciding.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fasl3klhy3acbmrmfsjl9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fasl3klhy3acbmrmfsjl9.webp" alt="Extract last frame" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Bind the branches
&lt;/h2&gt;

&lt;p&gt;Close the workbench, create two narrative nodes after the choice, and bind each branch video the same way as Step 4.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh2l3hlst77a4jtliheah.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh2l3hlst77a4jtliheah.webp" alt="Two branches bound" width="799" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Create hotspots
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Hotspot&lt;/strong&gt; in the workbench. Drag each hotspot region to overlap a bottle's button in the frame, then link each one to its narrative node in the &lt;strong&gt;Action&lt;/strong&gt; section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fetvpuai4idj650ouguqq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fetvpuai4idj650ouguqq.webp" alt="Hotspot aligned with the lemon button" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: Label the options
&lt;/h2&gt;

&lt;p&gt;On the Choice node, rename the branch options to match the on-screen choices ("Fresh Lemon", "Sweet Peach").&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50ll5g0lspf9tb8amktu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50ll5g0lspf9tb8amktu.webp" alt="Rename options" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 10: Preview and assemble
&lt;/h2&gt;

&lt;p&gt;Click &lt;strong&gt;Preview&lt;/strong&gt; to play through every path, then &lt;strong&gt;Assemble&lt;/strong&gt; to produce the final interactive video.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi3tagpk6i7bk96m4k051.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi3tagpk6i7bk96m4k051.webp" alt="Preview and assemble" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this structure scales
&lt;/h2&gt;

&lt;p&gt;The pipeline — canvas assets → story tree → bind videos → choice + hotspots → assemble — works for a lot more than one ad:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-SKU product ads (one branch per variant)&lt;/li&gt;
&lt;li&gt;Story-driven ads where viewers decide for the protagonist&lt;/li&gt;
&lt;li&gt;Chapter selection in interactive short dramas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full tutorial with all 20 screenshots lives here: &lt;a href="https://www.reelfork.com/tutorials/interactive-ad?utm_source=devto&amp;amp;utm_medium=seo-article&amp;amp;utm_campaign=tutorial-interactive-ad" rel="noopener noreferrer"&gt;Interactive Ads Quickstart&lt;/a&gt;. If you want the deep reference on the canvas, AI generation, and story trees, that's the &lt;a href="https://www.reelfork.com/tutorials/guide?utm_source=devto&amp;amp;utm_medium=seo-article&amp;amp;utm_campaign=tutorial-interactive-ad" rel="noopener noreferrer"&gt;ReelFork Studio Complete Guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>ai</category>
      <category>video</category>
      <category>nocode</category>
    </item>
  </channel>
</rss>
