<?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: TheXper</title>
    <description>The latest articles on DEV Community by TheXper (@thexper_f46a597a4e23988d2).</description>
    <link>https://dev.to/thexper_f46a597a4e23988d2</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3938134%2Fe4f05dc7-428e-4b23-a806-e7df62f77f01.png</url>
      <title>DEV Community: TheXper</title>
      <link>https://dev.to/thexper_f46a597a4e23988d2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thexper_f46a597a4e23988d2"/>
    <language>en</language>
    <item>
      <title>Dungeon Scrawl vs RPGMapEditor.com: Which Browser Map Tool Fits Your Game?</title>
      <dc:creator>TheXper</dc:creator>
      <pubDate>Tue, 02 Jun 2026 13:22:47 +0000</pubDate>
      <link>https://dev.to/thexper_f46a597a4e23988d2/dungeon-scrawl-vs-rpgmapeditorcom-which-browser-map-tool-fits-your-game-5158</link>
      <guid>https://dev.to/thexper_f46a597a4e23988d2/dungeon-scrawl-vs-rpgmapeditorcom-which-browser-map-tool-fits-your-game-5158</guid>
      <description>&lt;p&gt;When you are preparing a tabletop RPG session, the real question is not &lt;em&gt;which map tool is objectively better&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which tool gets this specific session map finished with the least friction?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dungeon Scrawl and &lt;a href="https://www.rpgmapeditor.com/" rel="noopener noreferrer"&gt;RPGMapEditor.com&lt;/a&gt; can both fit browser-based map prep, but they are optimized for different jobs.&lt;/p&gt;

&lt;p&gt;Dungeon Scrawl is strong when you want fast dungeon layouts, old-school map readability, and a low-friction sketching workflow.&lt;/p&gt;

&lt;p&gt;RPGMapEditor.com is aimed at browser-based visual encounter maps: terrain painting, stamps, props, grids, saved projects, and PNG export for VTT use.&lt;/p&gt;

&lt;p&gt;This comparison is not a takedown. It is a workflow distinction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick verdict
&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;Dungeon Scrawl&lt;/strong&gt; if your main job is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast room-and-corridor dungeon layouts&lt;/li&gt;
&lt;li&gt;old-school or black-and-white dungeon maps&lt;/li&gt;
&lt;li&gt;quick sketching before a session&lt;/li&gt;
&lt;li&gt;minimal setup&lt;/li&gt;
&lt;li&gt;clean floor plans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;strong&gt;RPGMapEditor.com&lt;/strong&gt; if your main job is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;visual fantasy encounter maps&lt;/li&gt;
&lt;li&gt;terrain painting&lt;/li&gt;
&lt;li&gt;prop and stamp placement&lt;/li&gt;
&lt;li&gt;tactical grid setup&lt;/li&gt;
&lt;li&gt;saving maps to an account&lt;/li&gt;
&lt;li&gt;exporting a PNG for Roll20, Foundry, Owlbear Rodeo, or another VTT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither tool has to win every use case.&lt;/p&gt;

&lt;p&gt;The practical test is whether you can make a playable map faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  The map-prep difference
&lt;/h2&gt;

&lt;p&gt;Dungeon Scrawl feels like a fast dungeon sketching tool.&lt;/p&gt;

&lt;p&gt;That is useful when the output you need is a readable dungeon structure: rooms, corridors, doors, shapes, and a clean tabletop layout.&lt;/p&gt;

&lt;p&gt;RPGMapEditor.com is trying to solve a different workflow.&lt;/p&gt;

&lt;p&gt;It is more focused on building a visual battle map directly in the browser. The goal is to paint readable terrain, place objects, tune the grid, save the project, and export a usable table image.&lt;/p&gt;

&lt;p&gt;That makes it more relevant when the map is not just a floor plan, but a scene: a forest ambush, a ruined shrine, a cave entrance, a tavern basement, or a tactical encounter location.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Dungeon Scrawl&lt;/th&gt;
&lt;th&gt;RPGMapEditor.com&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fast dungeon sketching&lt;/td&gt;
&lt;td&gt;Strong fit&lt;/td&gt;
&lt;td&gt;Possible, but not the main focus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Old-school dungeon style&lt;/td&gt;
&lt;td&gt;Strong fit&lt;/td&gt;
&lt;td&gt;Partial fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual encounter prep&lt;/td&gt;
&lt;td&gt;Less direct&lt;/td&gt;
&lt;td&gt;Main focus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terrain painting&lt;/td&gt;
&lt;td&gt;Not the primary workflow&lt;/td&gt;
&lt;td&gt;Core workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stamps / props&lt;/td&gt;
&lt;td&gt;Useful depending on workflow&lt;/td&gt;
&lt;td&gt;Core workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser access&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Account-backed saved maps&lt;/td&gt;
&lt;td&gt;Check current product state&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNG export&lt;/td&gt;
&lt;td&gt;Check current export options&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Roll20 / Foundry workflow&lt;/td&gt;
&lt;td&gt;Check current integrations&lt;/td&gt;
&lt;td&gt;Export PNG, upload to VTT, align grid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Walls / doors / lighting export&lt;/td&gt;
&lt;td&gt;Verify current support&lt;/td&gt;
&lt;td&gt;Not currently shipped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Critical note: export features change over time. Before switching your campaign workflow, verify the current export, pricing, and licensing details on each product page.&lt;/p&gt;




&lt;h2&gt;
  
  
  VTT workflow
&lt;/h2&gt;

&lt;p&gt;For RPGMapEditor.com, the reliable workflow is image-based:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the map in the browser.&lt;/li&gt;
&lt;li&gt;Export it as a PNG.&lt;/li&gt;
&lt;li&gt;Upload the image to your VTT.&lt;/li&gt;
&lt;li&gt;Align the grid.&lt;/li&gt;
&lt;li&gt;Add walls, doors, fog, and lighting inside the VTT if needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That means RPGMapEditor.com is usable for Roll20 or Foundry through PNG export, but it should not be treated as a direct Foundry scene exporter or Roll20 automation tool unless those features are explicitly shipped.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;A PNG export is enough for many tables. But if your campaign depends on automated walls, doors, lighting, or scene JSON, you should verify that before committing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 10-minute test
&lt;/h2&gt;

&lt;p&gt;The fastest way to choose between these tools is to test the same real map in both.&lt;/p&gt;

&lt;p&gt;Use this brief:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a small dungeon entrance map:
- one entrance area
- three connected rooms
- one hazard
- one hidden or reveal area
- tactical grid
- export for VTT use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time from blank canvas to playable map&lt;/li&gt;
&lt;li&gt;How many UI decisions slowed you down&lt;/li&gt;
&lt;li&gt;How readable the exported map is&lt;/li&gt;
&lt;li&gt;How easy the VTT import is&lt;/li&gt;
&lt;li&gt;Whether you would reuse the project later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This test is better than comparing feature lists.&lt;/p&gt;

&lt;p&gt;A feature list tells you what a tool can theoretically do. A session-prep test tells you whether it helps you run the game.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choose Dungeon Scrawl if...
&lt;/h2&gt;

&lt;p&gt;Choose Dungeon Scrawl if you mainly need a fast dungeon floor plan.&lt;/p&gt;

&lt;p&gt;It is the more natural choice when your output is a clean old-school dungeon sketch and you do not need a more visual terrain-and-prop editing workflow.&lt;/p&gt;

&lt;p&gt;This is especially true if your prep style is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;draw the layout quickly&lt;/li&gt;
&lt;li&gt;export it&lt;/li&gt;
&lt;li&gt;run the session&lt;/li&gt;
&lt;li&gt;move on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many GMs, that is exactly enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choose RPGMapEditor.com if...
&lt;/h2&gt;

&lt;p&gt;Choose &lt;a href="https://www.rpgmapeditor.com/" rel="noopener noreferrer"&gt;RPGMapEditor.com&lt;/a&gt; if you want a browser-based RPG map editor for visual encounter prep.&lt;/p&gt;

&lt;p&gt;It is a better fit when you care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;terrain readability&lt;/li&gt;
&lt;li&gt;fantasy battle map scenes&lt;/li&gt;
&lt;li&gt;tactical grids&lt;/li&gt;
&lt;li&gt;props and stamps&lt;/li&gt;
&lt;li&gt;saved map projects&lt;/li&gt;
&lt;li&gt;PNG export&lt;/li&gt;
&lt;li&gt;building maps directly in the browser without installing a native app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not a one-to-one Dungeon Scrawl clone.&lt;/p&gt;

&lt;p&gt;The better framing is: RPGMapEditor.com is for GMs who want a visual browser-based battle map workflow, not just a fast dungeon sketch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Does RPGMapEditor.com replace Dungeon Scrawl?
&lt;/h2&gt;

&lt;p&gt;Not one-to-one.&lt;/p&gt;

&lt;p&gt;Dungeon Scrawl is strong for quick dungeon scrawls and old-school layouts.&lt;/p&gt;

&lt;p&gt;RPGMapEditor.com is more useful when you want browser-based visual map editing for fantasy encounters, saved projects, and PNG handoff to a VTT.&lt;/p&gt;

&lt;p&gt;A GM could reasonably use both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dungeon Scrawl for fast dungeon structure&lt;/li&gt;
&lt;li&gt;RPGMapEditor.com for visual encounter scenes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right tool depends on the map job.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is RPGMapEditor.com a Dungeon Scrawl alternative?
&lt;/h3&gt;

&lt;p&gt;Yes, for some workflows. It is an alternative if you want browser-based RPG map editing with terrain, stamps, tactical grids, saved maps, and PNG export. It is not a perfect replacement for Dungeon Scrawl's fast dungeon-sketching feel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use RPGMapEditor.com with Roll20 or Foundry?
&lt;/h3&gt;

&lt;p&gt;Yes, through PNG export. Export the image, upload it to your VTT, align the grid, then configure platform-specific walls, doors, fog, or lighting inside the VTT.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is RPGMapEditor.com an old-school map maker?
&lt;/h3&gt;

&lt;p&gt;Only partly. It can be used for dungeon and fantasy encounter layouts, but it is not positioned as a pure black-and-white old-school dungeon sketching tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which one should I try first?
&lt;/h3&gt;

&lt;p&gt;Try the tool that matches your next session map.&lt;/p&gt;

&lt;p&gt;If you need a clean dungeon floor plan quickly, start with Dungeon Scrawl.&lt;/p&gt;

&lt;p&gt;If you need a visual battle map with terrain, props, a grid, saves, and PNG export, try RPGMapEditor.com.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final recommendation
&lt;/h2&gt;

&lt;p&gt;Do not choose based on the longest feature list.&lt;/p&gt;

&lt;p&gt;Choose based on the shortest path from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;blank canvas → playable map → exported file → table-ready VTT scene
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the workflow that matters on game night.&lt;/p&gt;

&lt;p&gt;Try the same small dungeon brief in both tools and compare the result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rpgmapeditor.com/" rel="noopener noreferrer"&gt;RPGMapEditor.com&lt;/a&gt; is independent and is not affiliated with, endorsed by, or sponsored by Dungeon Scrawl, Roll20, or Foundry.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>tools</category>
      <category>webdev</category>
      <category>dnd</category>
    </item>
    <item>
      <title>I Shipped a Rust + WebAssembly Battle Map Editor to the Microsoft Store</title>
      <dc:creator>TheXper</dc:creator>
      <pubDate>Fri, 29 May 2026 09:17:01 +0000</pubDate>
      <link>https://dev.to/thexper_f46a597a4e23988d2/i-shipped-a-rust-webassembly-battle-map-editor-to-the-microsoft-store-36gl</link>
      <guid>https://dev.to/thexper_f46a597a4e23988d2/i-shipped-a-rust-webassembly-battle-map-editor-to-the-microsoft-store-36gl</guid>
      <description>&lt;h1&gt;
  
  
  I Shipped a Rust + WebAssembly Battle Map Editor to the Microsoft Store
&lt;/h1&gt;

&lt;p&gt;I recently shipped &lt;strong&gt;RPG Map Editor&lt;/strong&gt; to the &lt;strong&gt;Microsoft Store&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is a battle map editor for D&amp;amp;D, Pathfinder, and other tabletop RPGs. The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Help a game master create a usable encounter map faster, without needing Photoshop, a heavy VTT workflow, or a complex desktop-only toolchain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The project is available here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://www.rpgmapeditor.com" rel="noopener noreferrer"&gt;https://www.rpgmapeditor.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Microsoft Store: &lt;a href="https://apps.microsoft.com/detail/9mzc3cfjkrs2" rel="noopener noreferrer"&gt;https://apps.microsoft.com/detail/9mzc3cfjkrs2&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is not a victory lap. It is a build log about what changed, what I learned from early feedback, and what I am trying to validate next.&lt;/p&gt;




&lt;h2&gt;
  
  
  What RPG Map Editor Is
&lt;/h2&gt;

&lt;p&gt;RPG Map Editor is a map-making tool focused on fast tabletop preparation.&lt;/p&gt;

&lt;p&gt;The current workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the editor&lt;/li&gt;
&lt;li&gt;Paint terrain&lt;/li&gt;
&lt;li&gt;Place props and objects&lt;/li&gt;
&lt;li&gt;Align the tactical grid&lt;/li&gt;
&lt;li&gt;Export a PNG map&lt;/li&gt;
&lt;li&gt;Use it in a VTT, print it, or share it with players&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The product is browser-first, but I also wanted a Windows version for users who prefer installing tools from a trusted store.&lt;/p&gt;

&lt;p&gt;That is why I packaged and released it through the Microsoft Store.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Build Another RPG Map Editor?
&lt;/h2&gt;

&lt;p&gt;There are already strong tools in this space:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inkarnate&lt;/li&gt;
&lt;li&gt;Dungeondraft&lt;/li&gt;
&lt;li&gt;DungeonFog&lt;/li&gt;
&lt;li&gt;Dungeon Alchemist&lt;/li&gt;
&lt;li&gt;Dungeon Scrawl&lt;/li&gt;
&lt;li&gt;Campaign Cartographer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the question is obvious:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does this offer that those tools do not?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The current answer is not “more features.”&lt;/p&gt;

&lt;p&gt;The current bet is &lt;strong&gt;lower friction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Some game masters do not want to install a large desktop app, manage asset packs, learn a complex workflow, or spend an hour preparing one simple encounter map.&lt;/p&gt;

&lt;p&gt;The product direction is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast startup&lt;/li&gt;
&lt;li&gt;simple terrain painting&lt;/li&gt;
&lt;li&gt;direct prop placement&lt;/li&gt;
&lt;li&gt;clear grid controls&lt;/li&gt;
&lt;li&gt;browser and Windows availability&lt;/li&gt;
&lt;li&gt;quick export&lt;/li&gt;
&lt;li&gt;minimal setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For power users who already have a strong Dungeondraft + Foundry workflow, RPG Map Editor is not trying to replace that yet.&lt;/p&gt;

&lt;p&gt;That matters because advanced VTT workflows need more than a PNG.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Limitation: PNG Export Is Not Enough for Everyone
&lt;/h2&gt;

&lt;p&gt;One of the strongest pieces of early feedback was about Foundry VTT.&lt;/p&gt;

&lt;p&gt;A plain PNG export is useful, but it does not replace a full VTT scene export with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;walls&lt;/li&gt;
&lt;li&gt;doors&lt;/li&gt;
&lt;li&gt;lighting&lt;/li&gt;
&lt;li&gt;line-of-sight data&lt;/li&gt;
&lt;li&gt;scene metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a real limitation.&lt;/p&gt;

&lt;p&gt;Right now, RPG Map Editor is focused on creating and exporting playable map images. Native Foundry scene export and automatic walls/doors are not shipped yet.&lt;/p&gt;

&lt;p&gt;I would rather say that clearly than pretend the product already replaces mature workflows.&lt;/p&gt;

&lt;p&gt;For many users, PNG export is enough.&lt;/p&gt;

&lt;p&gt;For serious Foundry users, it probably is not.&lt;/p&gt;

&lt;p&gt;That gives me a clear product constraint to test next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Rust + WebAssembly?
&lt;/h2&gt;

&lt;p&gt;The editor uses a Rust + WebAssembly core because I wanted the map engine to be structured more like a real editor engine than a typical DOM-heavy web app.&lt;/p&gt;

&lt;p&gt;The browser is the distribution layer.&lt;/p&gt;

&lt;p&gt;Rust/WASM is the performance and state-management layer.&lt;/p&gt;

&lt;p&gt;The long-term direction is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rust owns the map state&lt;/li&gt;
&lt;li&gt;WebAssembly handles editor operations&lt;/li&gt;
&lt;li&gt;WebGL/WebGPU-style rendering handles the canvas&lt;/li&gt;
&lt;li&gt;TypeScript/React handles UI and shell logic&lt;/li&gt;
&lt;li&gt;the product remains usable from the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates more complexity upfront, but it gives better control over editor behavior later.&lt;/p&gt;

&lt;p&gt;For a creative tool, that matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Microsoft Store?
&lt;/h2&gt;

&lt;p&gt;The Microsoft Store release is mainly a distribution experiment.&lt;/p&gt;

&lt;p&gt;A browser app is easy to try, but a Store app gives the project another trust surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;users can find it through Windows search and Store discovery&lt;/li&gt;
&lt;li&gt;installation feels more native&lt;/li&gt;
&lt;li&gt;the product appears more legitimate to non-technical users&lt;/li&gt;
&lt;li&gt;desktop users can keep it like a normal app&lt;/li&gt;
&lt;li&gt;it creates a clearer “released product” milestone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not automatically mean the product is validated.&lt;/p&gt;

&lt;p&gt;A Store listing is not traction.&lt;/p&gt;

&lt;p&gt;A launch is not retention.&lt;/p&gt;

&lt;p&gt;The real question is whether users install it, open it, create a map, export it, and come back.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Am Testing Now
&lt;/h2&gt;

&lt;p&gt;The current validation question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can I build a better editor than Inkarnate?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is too broad.&lt;/p&gt;

&lt;p&gt;The current validation question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can a GM open RPG Map Editor and create/export a usable battle map without getting stuck?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That breaks into measurable steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Did users find the product?&lt;/li&gt;
&lt;li&gt;Did they understand what it does within 5 seconds?&lt;/li&gt;
&lt;li&gt;Did they open the editor?&lt;/li&gt;
&lt;li&gt;Did they create or edit a map?&lt;/li&gt;
&lt;li&gt;Did they export it?&lt;/li&gt;
&lt;li&gt;Did they use it in a real session?&lt;/li&gt;
&lt;li&gt;Did they return?&lt;/li&gt;
&lt;li&gt;Would they pay?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else is secondary.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Changed After Early Feedback
&lt;/h2&gt;

&lt;p&gt;Early community feedback made a few things clear.&lt;/p&gt;

&lt;p&gt;First, the product positioning needed to be more direct.&lt;/p&gt;

&lt;p&gt;Saying “browser-first RPG map editor” is not enough. People need to immediately understand the practical value:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a playable battle map quickly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Second, I needed to be clear about the limits.&lt;/p&gt;

&lt;p&gt;If Foundry users expect walls, doors, and lighting export, I should not hide behind vague “VTT-ready” language. PNG export is useful, but it is not the same as native VTT scene export.&lt;/p&gt;

&lt;p&gt;Third, readability matters.&lt;/p&gt;

&lt;p&gt;A nice-looking page is useless if contrast issues make it hard to read. This is especially important for a creative tool where users need to trust the UI before they even enter the editor.&lt;/p&gt;

&lt;p&gt;Fourth, the fastest path to validation is not more marketing content.&lt;/p&gt;

&lt;p&gt;It is getting users into the editor and watching where they fail.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Current Product Scope
&lt;/h2&gt;

&lt;p&gt;The current version focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;terrain painting&lt;/li&gt;
&lt;li&gt;prop and object placement&lt;/li&gt;
&lt;li&gt;tactical grid alignment&lt;/li&gt;
&lt;li&gt;demo projects&lt;/li&gt;
&lt;li&gt;PNG export&lt;/li&gt;
&lt;li&gt;browser access&lt;/li&gt;
&lt;li&gt;Windows app availability through Microsoft Store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not yet shipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;native Foundry scene export&lt;/li&gt;
&lt;li&gt;automatic walls and doors&lt;/li&gt;
&lt;li&gt;dynamic lighting metadata&lt;/li&gt;
&lt;li&gt;advanced campaign management&lt;/li&gt;
&lt;li&gt;full marketplace ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those may come later.&lt;/p&gt;

&lt;p&gt;Right now, the product needs stronger proof that the core loop works.&lt;/p&gt;

&lt;p&gt;Links&lt;/p&gt;

&lt;p&gt;Website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rpgmapeditor.com" rel="noopener noreferrer"&gt;https://www.rpgmapeditor.com&lt;/a&gt;&lt;br&gt;
Microsoft Store:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apps.microsoft.com/detail/9mzc3cfjkrs2" rel="noopener noreferrer"&gt;https://apps.microsoft.com/detail/9mzc3cfjkrs2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webassembly</category>
      <category>gamedev</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>RPGMapEditor Roadmap: Bringing a Rust-Powered DnD Map Maker to Windows</title>
      <dc:creator>TheXper</dc:creator>
      <pubDate>Mon, 25 May 2026 20:30:18 +0000</pubDate>
      <link>https://dev.to/thexper_f46a597a4e23988d2/rpgmapeditor-roadmap-bringing-a-rust-powered-dnd-map-maker-to-windows-1ddm</link>
      <guid>https://dev.to/thexper_f46a597a4e23988d2/rpgmapeditor-roadmap-bringing-a-rust-powered-dnd-map-maker-to-windows-1ddm</guid>
      <description>&lt;p&gt;RPGMapEditor is being built as a browser-based &lt;strong&gt;RPG map editor&lt;/strong&gt;, &lt;strong&gt;DnD map maker&lt;/strong&gt;, and &lt;strong&gt;battle map maker&lt;/strong&gt; powered by Rust, WebAssembly, and WebGL.&lt;/p&gt;

&lt;p&gt;The main product will stay web-first.&lt;/p&gt;

&lt;p&gt;That matters because tabletop creators should be able to open a browser, create a map, save it, export it, and continue working without installing a heavy desktop tool.&lt;/p&gt;

&lt;p&gt;But there is now a clear roadmap direction worth exploring:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bring RPGMapEditor to Windows as a desktop app without rewriting the editor from scratch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a pivot away from the web app.&lt;/p&gt;

&lt;p&gt;This is a distribution and workflow expansion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The roadmap direction
&lt;/h2&gt;

&lt;p&gt;The plan is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Web-first RPG map editor
+
Windows desktop app
+
Local file workflows
+
Optional cloud sync
+
Microsoft Store distribution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The desktop version would not replace &lt;code&gt;rpgmapeditor.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead, it would become another way to use the same editor.&lt;/p&gt;

&lt;p&gt;Users who want instant access can use the browser version.&lt;/p&gt;

&lt;p&gt;Users who prefer a desktop creative workflow can install the Windows app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a desktop app makes sense
&lt;/h2&gt;

&lt;p&gt;RPG map editors sit between two product categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;browser tools that are fast to access&lt;/li&gt;
&lt;li&gt;desktop creative apps that feel permanent and file-based&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A browser-based RPG map editor is great for speed.&lt;/p&gt;

&lt;p&gt;A desktop RPG map editor is better for users who expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local files&lt;/li&gt;
&lt;li&gt;recent projects&lt;/li&gt;
&lt;li&gt;direct image export&lt;/li&gt;
&lt;li&gt;offline-friendly workflows&lt;/li&gt;
&lt;li&gt;a dedicated app window&lt;/li&gt;
&lt;li&gt;operating system integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the opportunity.&lt;/p&gt;

&lt;p&gt;The goal is not to build a second editor.&lt;/p&gt;

&lt;p&gt;The goal is to make the existing editor available through a Windows desktop workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not rewrite everything natively?
&lt;/h2&gt;

&lt;p&gt;A full native rewrite would be the wrong move.&lt;/p&gt;

&lt;p&gt;RPGMapEditor already has a Rust-powered architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rust backend&lt;/li&gt;
&lt;li&gt;Rust/WebAssembly editor engine&lt;/li&gt;
&lt;li&gt;WebGL rendering&lt;/li&gt;
&lt;li&gt;browser-based UI&lt;/li&gt;
&lt;li&gt;map persistence&lt;/li&gt;
&lt;li&gt;export workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rebuilding the same editor as a native Windows app would create duplicate work.&lt;/p&gt;

&lt;p&gt;Every feature would need to be maintained twice.&lt;/p&gt;

&lt;p&gt;That would slow down the product.&lt;/p&gt;

&lt;p&gt;The better approach is to reuse the existing editor and package it properly for desktop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planned architecture
&lt;/h2&gt;

&lt;p&gt;The intended architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPGMapEditor Web App
├── Rust backend
├── authentication
├── cloud saves
├── account system
├── pricing
└── map persistence

RPGMapEditor Editor
├── Rust → WebAssembly engine
├── WebGL renderer
├── editor UI
├── asset workflow
└── export system

RPGMapEditor for Windows
├── desktop shell
├── bundled editor frontend
├── local .rpgmap files
├── image export
├── recent maps
└── optional cloud sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The desktop app should reuse the existing editor instead of forking the product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That keeps development focused.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first Windows version
&lt;/h2&gt;

&lt;p&gt;The first version of RPGMapEditor for Windows should be intentionally small.&lt;/p&gt;

&lt;p&gt;The minimum useful feature set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Launch the editor from a desktop app
2. Load the Rust/WASM map engine
3. Render maps correctly
4. Create a new map
5. Open a local .rpgmap file
6. Save a local .rpgmap file
7. Export PNG or WebP images
8. Sign in for optional cloud saves
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is enough for a real first version.&lt;/p&gt;

&lt;p&gt;Not perfect.&lt;/p&gt;

&lt;p&gt;Not overloaded.&lt;/p&gt;

&lt;p&gt;Just useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the desktop app should not become
&lt;/h2&gt;

&lt;p&gt;The Windows version should not become:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a separate editor&lt;/li&gt;
&lt;li&gt;a full native rewrite&lt;/li&gt;
&lt;li&gt;a disconnected product&lt;/li&gt;
&lt;li&gt;a new pricing experiment&lt;/li&gt;
&lt;li&gt;a month-long packaging distraction&lt;/li&gt;
&lt;li&gt;a reason to delay the web editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser editor remains the core product.&lt;/p&gt;

&lt;p&gt;The desktop version is a focused distribution layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Microsoft Store is interesting
&lt;/h2&gt;

&lt;p&gt;Publishing RPGMapEditor for Windows through the Microsoft Store could create a new discovery surface for users searching for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RPG map editor for Windows&lt;/li&gt;
&lt;li&gt;DnD map maker for Windows&lt;/li&gt;
&lt;li&gt;battle map maker desktop app&lt;/li&gt;
&lt;li&gt;tabletop map editor&lt;/li&gt;
&lt;li&gt;fantasy map creator&lt;/li&gt;
&lt;li&gt;VTT map maker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Microsoft Store will not magically create growth.&lt;/p&gt;

&lt;p&gt;But it can help with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trust&lt;/li&gt;
&lt;li&gt;installs&lt;/li&gt;
&lt;li&gt;reviews&lt;/li&gt;
&lt;li&gt;Windows discovery&lt;/li&gt;
&lt;li&gt;product credibility&lt;/li&gt;
&lt;li&gt;desktop user acquisition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an early creative tool, that is worth testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roadmap phase 1: Desktop proof of concept
&lt;/h2&gt;

&lt;p&gt;The first roadmap phase is technical validation.&lt;/p&gt;

&lt;p&gt;The desktop app must prove that the existing editor works correctly inside a desktop shell.&lt;/p&gt;

&lt;p&gt;Checklist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- app opens successfully
- WASM loads
- WebGL rendering works
- editor UI works
- maps can be created
- exports work
- login flow does not break
- cloud save flow remains usable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this fails, the desktop roadmap pauses.&lt;/p&gt;

&lt;p&gt;There is no reason to force a desktop release if the core editor experience is unstable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roadmap phase 2: Local file workflow
&lt;/h2&gt;

&lt;p&gt;A desktop creative tool needs file handling.&lt;/p&gt;

&lt;p&gt;The second phase would add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- New Map
- Open .rpgmap
- Save .rpgmap
- Save As
- Export PNG
- Export WebP
- Recent Maps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the real reason for a desktop app.&lt;/p&gt;

&lt;p&gt;Without local files, the app is just a website in a window.&lt;/p&gt;

&lt;p&gt;With local files, RPGMapEditor becomes closer to a real creative tool for tabletop creators.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roadmap phase 3: Microsoft Store packaging
&lt;/h2&gt;

&lt;p&gt;Once the desktop version works, the next step is packaging.&lt;/p&gt;

&lt;p&gt;The Store-ready version needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- app icon
- app screenshots
- store description
- privacy policy
- support page
- version number
- stable installer
- clean first-run experience
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Microsoft Store listing should be clear, not keyword-stuffed.&lt;/p&gt;

&lt;p&gt;A simple positioning line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RPGMapEditor is a Rust-powered DnD map maker and battle map editor for Windows and the web.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Roadmap phase 4: Product-led conversion
&lt;/h2&gt;

&lt;p&gt;The desktop app should be measured like a product channel.&lt;/p&gt;

&lt;p&gt;Important events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;desktop_install
desktop_editor_open
desktop_map_created
desktop_local_save
desktop_export_clicked
desktop_cloud_sync_used
desktop_signup_started
desktop_pricing_clicked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A desktop release only matters if users actually create maps.&lt;/p&gt;

&lt;p&gt;The goal is not installs.&lt;/p&gt;

&lt;p&gt;The goal is map creation, saving, exporting, returning, and eventually upgrading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roadmap phase 5: Desktop plus cloud
&lt;/h2&gt;

&lt;p&gt;The long-term product direction could become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser app for instant access
+
Windows app for local workflows
+
Cloud saves for persistence
+
Premium asset packs for monetization
+
Export workflows for VTT use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That creates a stronger product system.&lt;/p&gt;

&lt;p&gt;The web app gives speed.&lt;/p&gt;

&lt;p&gt;The desktop app gives permanence.&lt;/p&gt;

&lt;p&gt;Cloud sync connects both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;The Windows version would be useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dungeon Masters creating DnD maps&lt;/li&gt;
&lt;li&gt;tabletop RPG players preparing encounters&lt;/li&gt;
&lt;li&gt;worldbuilders designing fantasy maps&lt;/li&gt;
&lt;li&gt;VTT users exporting battle maps&lt;/li&gt;
&lt;li&gt;creators who prefer desktop tools&lt;/li&gt;
&lt;li&gt;users who want local map files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser version remains the fastest way to start.&lt;/p&gt;

&lt;p&gt;The desktop version becomes the better option for longer creative sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this roadmap matters
&lt;/h2&gt;

&lt;p&gt;Most mapmaking tools fall into one of two categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;heavy desktop software&lt;/li&gt;
&lt;li&gt;lightweight browser tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;RPGMapEditor can sit between both.&lt;/p&gt;

&lt;p&gt;The product direction is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;fast like a browser tool, but structured enough to feel like a real creative app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is why a Windows roadmap is worth exploring.&lt;/p&gt;

&lt;p&gt;Not because every web app needs a desktop version.&lt;/p&gt;

&lt;p&gt;Not because desktop automatically means serious.&lt;/p&gt;

&lt;p&gt;But because map creation is a workflow where local files, exports, and repeat sessions matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current priority
&lt;/h2&gt;

&lt;p&gt;The immediate priority is still the web editor.&lt;/p&gt;

&lt;p&gt;The desktop app should not slow down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;editor stability&lt;/li&gt;
&lt;li&gt;map creation&lt;/li&gt;
&lt;li&gt;export quality&lt;/li&gt;
&lt;li&gt;save/load reliability&lt;/li&gt;
&lt;li&gt;SEO landing pages&lt;/li&gt;
&lt;li&gt;user onboarding&lt;/li&gt;
&lt;li&gt;pricing validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The desktop roadmap only makes sense if it supports the main product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final roadmap summary
&lt;/h2&gt;

&lt;p&gt;The plan is not to rebuild RPGMapEditor.&lt;/p&gt;

&lt;p&gt;The plan is to extend it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: Keep the browser editor as the core product
Step 2: Validate a Windows desktop shell
Step 3: Add local .rpgmap save/load
Step 4: Add export workflows
Step 5: Package for Microsoft Store
Step 6: Measure installs, map creation, exports, and retention
Step 7: Decide whether desktop deserves more investment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can a Rust-powered browser RPG map editor become a useful Windows desktop mapmaking app without becoming a separate product?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the roadmap.&lt;/p&gt;

&lt;p&gt;If the desktop version helps more users create, save, export, and return to their maps, it is worth continuing.&lt;/p&gt;

&lt;p&gt;If it only creates engineering complexity, it should be paused.&lt;/p&gt;

&lt;p&gt;The product goal stays the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a fast, useful, Rust-powered RPG map editor for people who want to create DnD maps, battle maps, and tabletop worlds.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>rust</category>
      <category>webdev</category>
      <category>tauri</category>
      <category>webassembly</category>
    </item>
    <item>
      <title>How I Rebuilt an RPG Map Editor with Rust, React, and WASM</title>
      <dc:creator>TheXper</dc:creator>
      <pubDate>Mon, 25 May 2026 18:55:25 +0000</pubDate>
      <link>https://dev.to/thexper_f46a597a4e23988d2/reworking-the-rpg-map-editor-10g0</link>
      <guid>https://dev.to/thexper_f46a597a4e23988d2/reworking-the-rpg-map-editor-10g0</guid>
      <description>&lt;p&gt;Replacing a map editor sounds like a frontend task.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;A browser-based RPG map editor lives inside a product loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a map.&lt;/li&gt;
&lt;li&gt;Open it in the editor.&lt;/li&gt;
&lt;li&gt;Paint, place assets, rename it, and change layers.&lt;/li&gt;
&lt;li&gt;Save automatically.&lt;/li&gt;
&lt;li&gt;Reopen it later and trust that nothing disappeared.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If that loop is fragile, the editor can have great brushes, nice UI, and a fast canvas, but the product still feels broken.&lt;/p&gt;

&lt;p&gt;This post walks through how I replaced a legacy map editor with a Rust/WebAssembly editor, rebuilt the maps dashboard around the new flow, and made cloud autosave safer.&lt;/p&gt;

&lt;p&gt;The stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust + Rocket&lt;/strong&gt; for authenticated routes and map persistence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; for map records and saved project snapshots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tera templates&lt;/strong&gt; for the product shell&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React + Vite + WebAssembly&lt;/strong&gt; for the editor UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust/WASM + WebGL2&lt;/strong&gt; for the map editing engine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The boring parts mattered most: routing, boot state, create flow, revisions, and autosave correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual problem
&lt;/h2&gt;

&lt;p&gt;The original request was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace the current map editor with a WASM editor.&lt;br&gt;
screenshot here;&lt;/p&gt;
&lt;/blockquote&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.amazonaws.com%2Fuploads%2Farticles%2F8micpgxd2w8kp3xum5ul.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.amazonaws.com%2Fuploads%2Farticles%2F8micpgxd2w8kp3xum5ul.png" alt="the old rpg map editor" width="800" height="377"&gt;&lt;/a&gt;&lt;br&gt;
But replacing the editor route was only the first layer.&lt;/p&gt;

&lt;p&gt;A real D&amp;amp;D map maker or RPG map editor needs more than a canvas. It needs a stable workflow around the canvas. Users do not care that the editor is written in Rust or that the renderer uses WebGL2 if the &lt;code&gt;New map&lt;/code&gt; button fails or their work does not save reliably.&lt;/p&gt;

&lt;p&gt;So the real goal became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make map creation, editor boot, cloud saving, and reopening feel boring and trustworthy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That meant fixing three areas at the same time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the editor route&lt;/li&gt;
&lt;li&gt;the maps dashboard&lt;/li&gt;
&lt;li&gt;the save model&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  1. Replacing the editor route with the WASM editor
&lt;/h2&gt;

&lt;p&gt;The first step was changing the authenticated editor route so that opening a map loaded the new WebAssembly editor instead of the old editor experience.&lt;/p&gt;

&lt;p&gt;The important part was not just serving a new &lt;code&gt;index.html&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The editor needed account-aware boot data from the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"map_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"map_uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dungeon Entrance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"revision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-25T12:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"read_only"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"csrf_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"load"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/maps/map_uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/maps/map_uuid"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That boot payload became the bridge between the product shell and the editor.&lt;/p&gt;

&lt;p&gt;The React/Vite/WASM editor should not guess who the user is, which map is open, whether the map is editable, or where it should save. The server already knows that. The editor should receive it explicitly.&lt;/p&gt;

&lt;p&gt;That keeps the editor focused on editing, while the product shell owns authentication, permissions, persistence, and routing.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The dashboard had to become part of the editor experience
&lt;/h2&gt;

&lt;p&gt;Once the WASM editor route worked, the maps dashboard became the next bottleneck.&lt;/p&gt;

&lt;p&gt;The old dashboard still behaved like a marketing page connected to an older creation wizard. But the user expectation was much simpler:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;New map&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Create a map record.&lt;/li&gt;
&lt;li&gt;Redirect into the editor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the core loop of a browser-based battle map maker. If this flow is not reliable, the product feels unfinished before the editor even loads.&lt;/p&gt;

&lt;p&gt;So the dashboard was rebuilt around the editing workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;map summary cards&lt;/li&gt;
&lt;li&gt;search&lt;/li&gt;
&lt;li&gt;sorting&lt;/li&gt;
&lt;li&gt;recent map cards&lt;/li&gt;
&lt;li&gt;empty state&lt;/li&gt;
&lt;li&gt;one primary create action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dashboard stopped being a passive page and became the starting surface for map work.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why the first &lt;code&gt;New map&lt;/code&gt; fix was not enough
&lt;/h2&gt;

&lt;p&gt;The first implementation used JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/maps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-CSRF-Token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;csrfToken&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Untitled Map&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/editor/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;createdMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That worked in a smoke test.&lt;/p&gt;

&lt;p&gt;Then the user reported that clicking &lt;strong&gt;New map&lt;/strong&gt; did nothing.&lt;/p&gt;

&lt;p&gt;That exposed the design flaw: a core product action depended entirely on a client-side event listener.&lt;/p&gt;

&lt;p&gt;For something as important as creating a map, that is too fragile. JavaScript can fail because of stale assets, a bad bundle, a browser extension, a hydration issue, or a simple selector mismatch.&lt;/p&gt;

&lt;p&gt;The fix was progressive enhancement.&lt;/p&gt;

&lt;p&gt;The create action became a real HTML form:&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;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/maps/new"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"csrf_token"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"{{ csrf_token }}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;New map&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server route does the critical work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /maps/new
→ create map record
→ redirect to /editor/&amp;lt;id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript can still intercept the submit and use the faster API path when everything is healthy. But the fallback path is now server-owned.&lt;/p&gt;

&lt;p&gt;That is the right tradeoff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML handles the critical action.&lt;/li&gt;
&lt;li&gt;JavaScript improves the experience.&lt;/li&gt;
&lt;li&gt;The server remains the source of truth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of boring engineering that makes a SaaS product feel reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Keeping the API contract small
&lt;/h2&gt;

&lt;p&gt;The editor did not need a huge API surface.&lt;/p&gt;

&lt;p&gt;The useful contract was small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/maps/&amp;lt;id&amp;gt;
PUT /api/maps/&amp;lt;id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GET /api/maps/&amp;lt;id&amp;gt;&lt;/code&gt; loads the current map snapshot.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PUT /api/maps/&amp;lt;id&amp;gt;&lt;/code&gt; saves the new title, project JSON, and concurrency metadata.&lt;/p&gt;

&lt;p&gt;The client sends something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dungeon Entrance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"snapshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"camera"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"objects"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expected_revision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expected_updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-25T12:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server can then reject stale writes instead of silently overwriting newer data.&lt;/p&gt;

&lt;p&gt;For a single-user editor, optimistic concurrency may look unnecessary. It is not. Multiple tabs, reloads, slow requests, and old local drafts can all create write conflicts.&lt;/p&gt;

&lt;p&gt;The editor should not pretend those cases do not exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Cloud autosave has to handle real editing
&lt;/h2&gt;

&lt;p&gt;The editor already had local autosave and a cloud save function.&lt;/p&gt;

&lt;p&gt;The naive loop looked reasonable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mark the editor dirty after a change.&lt;/li&gt;
&lt;li&gt;Wait a few seconds.&lt;/li&gt;
&lt;li&gt;Export project JSON.&lt;/li&gt;
&lt;li&gt;Send it to the server.&lt;/li&gt;
&lt;li&gt;Mark the editor saved.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The problem is that users keep editing while saves are in flight.&lt;/p&gt;

&lt;p&gt;If an old save finishes after newer edits were made, the UI must not say everything is saved.&lt;/p&gt;

&lt;p&gt;That is how users lose trust.&lt;/p&gt;

&lt;p&gt;The safer model is a dirty generation counter.&lt;/p&gt;

&lt;p&gt;Every edit increments a number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dirtyGeneration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;savedGeneration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;saveInFlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;markDirty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;dirtyGeneration&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;scheduleAutosave&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;p&gt;When a save starts, the editor captures the current generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveToCloud&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saveInFlight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirtyGeneration&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;savedGeneration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;saveInFlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generationAtStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dirtyGeneration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exportProjectJson&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;putMapSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirtyGeneration&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;generationAtStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;savedGeneration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generationAtStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;updateRevision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;markCloudSaved&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;scheduleAutosaveSoon&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;keepLocalDraft&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;scheduleRetry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;saveInFlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&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;p&gt;This protects against a common editor bug:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Save request A starts. The user makes edit B. Save request A finishes. The UI incorrectly says B is saved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With generation tracking, the editor only clears the dirty state if no newer edits happened during the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Local drafts are a safety net, not the source of truth
&lt;/h2&gt;

&lt;p&gt;Local autosave still matters.&lt;/p&gt;

&lt;p&gt;But local storage should not become the final persistence model for an authenticated cloud editor.&lt;/p&gt;

&lt;p&gt;The better model is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local draft protects the user from crashes, reloads, and network failures&lt;/li&gt;
&lt;li&gt;cloud save becomes the source of truth after a successful server write&lt;/li&gt;
&lt;li&gt;server revision prevents stale overwrites&lt;/li&gt;
&lt;li&gt;conflict handling decides what happens when the client is behind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives the user two layers of protection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Immediate local safety&lt;/strong&gt; while editing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable cloud persistence&lt;/strong&gt; once the network catches up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a creative tool, this matters more than it sounds. Losing a map is not a small bug. It is a trust-breaking event.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What changed in the product loop
&lt;/h2&gt;

&lt;p&gt;Before this pass, the product had editor functionality, but the loop around it was fragile.&lt;/p&gt;

&lt;p&gt;After the pass, the workflow became much tighter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The dashboard opens as the user's map workspace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New map&lt;/strong&gt; always has a server-backed path.&lt;/li&gt;
&lt;li&gt;The server creates a real map record.&lt;/li&gt;
&lt;li&gt;The user lands in the Rust/WASM editor.&lt;/li&gt;
&lt;li&gt;The editor boots with map ID, API URLs, title, revision, permissions, and CSRF token.&lt;/li&gt;
&lt;li&gt;Changes save locally first.&lt;/li&gt;
&lt;li&gt;Cloud autosave persists the project snapshot with optimistic concurrency.&lt;/li&gt;
&lt;li&gt;Newer edits made during an in-flight save are not accidentally marked as saved.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the product loop a browser RPG map editor needs before deeper features matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would not overbuild yet
&lt;/h2&gt;

&lt;p&gt;It is tempting to jump into advanced editor features immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiplayer editing&lt;/li&gt;
&lt;li&gt;complex asset marketplaces&lt;/li&gt;
&lt;li&gt;AI-generated maps&lt;/li&gt;
&lt;li&gt;procedural dungeons&lt;/li&gt;
&lt;li&gt;advanced sharing permissions&lt;/li&gt;
&lt;li&gt;plugin systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But those are not the first bottleneck.&lt;/p&gt;

&lt;p&gt;The first bottleneck is trust.&lt;/p&gt;

&lt;p&gt;For an RPG map editor, users need to believe that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creating a map works&lt;/li&gt;
&lt;li&gt;opening a map works&lt;/li&gt;
&lt;li&gt;saving works&lt;/li&gt;
&lt;li&gt;reopening works&lt;/li&gt;
&lt;li&gt;their work will not disappear&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until that is true, advanced features are just decoration on top of a weak product loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical lessons
&lt;/h2&gt;

&lt;p&gt;The useful lessons from this rebuild were simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make critical actions work without JavaScript.&lt;/li&gt;
&lt;li&gt;Pass explicit boot state into the editor.&lt;/li&gt;
&lt;li&gt;Keep the editor API small.&lt;/li&gt;
&lt;li&gt;Save title and project snapshot together.&lt;/li&gt;
&lt;li&gt;Use server revisions for optimistic concurrency.&lt;/li&gt;
&lt;li&gt;Track dirty generations on the client.&lt;/li&gt;
&lt;li&gt;Treat local drafts as a fallback, not the final persistence layer.&lt;/li&gt;
&lt;li&gt;Make saves boring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best editor infrastructure disappears when it works.&lt;/p&gt;

&lt;p&gt;A user should not think about revisions, snapshots, CSRF tokens, local drafts, or save generations. They should create a dungeon map, close the tab, reopen it later, and see their work still there.&lt;/p&gt;

&lt;p&gt;That is the real feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why use Rust and WebAssembly for a browser map editor?
&lt;/h3&gt;

&lt;p&gt;Rust and WebAssembly make sense when the editor has engine-like requirements: canvas rendering, map state management, undo/redo, geometry, asset placement, and performance-sensitive operations. React can own the UI shell, while Rust/WASM owns the editing core.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not make the whole editor a React app?
&lt;/h3&gt;

&lt;p&gt;React is good for panels, buttons, modals, inspectors, and dashboard UI. It is not the best place to put the entire editing engine. For a map editor, the canvas state, rendering model, command history, and project serialization benefit from a stricter engine layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why use progressive enhancement for the New Map button?
&lt;/h3&gt;

&lt;p&gt;Because creating a map is a critical action. If JavaScript fails, users should still be able to create a map and enter the editor. The enhanced JavaScript path can improve speed, but the server form path should remain reliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does autosave need a dirty generation counter?
&lt;/h3&gt;

&lt;p&gt;Because users can continue editing while a save request is still running. A generation counter prevents an older save response from incorrectly marking newer edits as saved.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the most important part of cloud autosave?
&lt;/h3&gt;

&lt;p&gt;The most important part is not the timer. It is correctness. The editor must know which edits were included in a save, which edits happened later, and whether the server accepted the write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Replacing the editor was the visible task.&lt;/p&gt;

&lt;p&gt;Fixing the product loop was the real task.&lt;/p&gt;

&lt;p&gt;For a browser-based D&amp;amp;D map maker, the canvas is only one part of the experience. The user also needs reliable creation, clear routing, safe autosave, and boring persistence.&lt;/p&gt;

&lt;p&gt;That is what makes the editor feel like a product instead of a demo.&lt;/p&gt;

&lt;p&gt;And the Result is here:&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.amazonaws.com%2Fuploads%2Farticles%2F66ejj5nt4haspvokb026.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.amazonaws.com%2Fuploads%2Farticles%2F66ejj5nt4haspvokb026.png" alt="RPG Map Editor v2" width="800" height="381"&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Ffpzyyvzcbk0013lwn734.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.amazonaws.com%2Fuploads%2Farticles%2Ffpzyyvzcbk0013lwn734.png" alt="RPG Map Editor v2" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>rust</category>
      <category>webassembly</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Why I Rebuilt My RPG Map Editor Landing Page with CSS Instead of WebGL</title>
      <dc:creator>TheXper</dc:creator>
      <pubDate>Fri, 22 May 2026 17:58:31 +0000</pubDate>
      <link>https://dev.to/thexper_f46a597a4e23988d2/why-i-rebuilt-my-rpg-map-editor-landing-page-with-css-instead-of-webgl-55gf</link>
      <guid>https://dev.to/thexper_f46a597a4e23988d2/why-i-rebuilt-my-rpg-map-editor-landing-page-with-css-instead-of-webgl-55gf</guid>
      <description>&lt;h1&gt;
  
  
  Why I Rebuilt My RPG Map Editor Landing Page with CSS Instead of WebGL
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; I removed the Three.js/WebGL hero animation from my RPG map editor landing page because it was slower, less reliable, and less useful than a simple CSS-based pixel-art hero.&lt;/p&gt;

&lt;p&gt;The product is &lt;a href="https://rpgmapeditor.com" rel="noopener noreferrer"&gt;RPG Map Editor&lt;/a&gt; — a browser-based &lt;strong&gt;battle map maker&lt;/strong&gt; for D&amp;amp;D, TTRPGs, dungeon maps, and fantasy map design.&lt;/p&gt;

&lt;p&gt;The landing page originally tried to look technically impressive: particles, magical dust, animated cave lighting, shader effects, and a real-time WebGL background.&lt;/p&gt;

&lt;p&gt;It worked.&lt;/p&gt;

&lt;p&gt;Then I deleted it.&lt;/p&gt;

&lt;p&gt;And the page became better.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mistake: I built the landing page like a tech demo
&lt;/h2&gt;

&lt;p&gt;When I first built the landing page for RPG Map Editor, I wanted the hero section to feel alive.&lt;/p&gt;

&lt;p&gt;The idea was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a fantasy cave background&lt;/li&gt;
&lt;li&gt;Three.js particle effects&lt;/li&gt;
&lt;li&gt;magical lighting&lt;/li&gt;
&lt;li&gt;real-time atmosphere&lt;/li&gt;
&lt;li&gt;a premium “serious software” feeling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my head, this made sense. RPG Map Editor is not just a static website. It is a real browser-based editor for creating DnD battle maps, fantasy maps, dungeon maps, and tabletop RPG layouts.&lt;/p&gt;

&lt;p&gt;So I thought the landing page should prove the engineering quality immediately.&lt;/p&gt;

&lt;p&gt;That was the wrong priority.&lt;/p&gt;

&lt;p&gt;A landing page is not supposed to prove that you can write shaders.&lt;/p&gt;

&lt;p&gt;A landing page is supposed to help the right user understand:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Can this tool help me make a battle map faster?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That user might be a Dungeon Master preparing a session tonight. They might be looking for an &lt;strong&gt;Inkarnate alternative&lt;/strong&gt;, a fast &lt;strong&gt;DnD map maker&lt;/strong&gt;, or a simple &lt;strong&gt;RPG map editor&lt;/strong&gt; that runs in the browser.&lt;/p&gt;

&lt;p&gt;They do not care how clever the hero animation is.&lt;/p&gt;

&lt;p&gt;They care if the page loads, looks trustworthy, and gets them to the editor quickly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The WebGL problem: decoration was competing with the product
&lt;/h2&gt;

&lt;p&gt;The actual RPG Map Editor uses WebGL2 for the editor canvas.&lt;/p&gt;

&lt;p&gt;That makes sense.&lt;/p&gt;

&lt;p&gt;The editor needs GPU rendering because users are painting terrain, placing tiles, moving around maps, and exporting battle maps.&lt;/p&gt;

&lt;p&gt;But the landing page also had a WebGL hero animation.&lt;/p&gt;

&lt;p&gt;That was the mistake.&lt;/p&gt;

&lt;p&gt;The decorative WebGL scene was competing with the product’s real WebGL context. On some machines, especially mid-range laptops and browsers with many tabs open, the landing page could create reliability issues before the user even opened the map editor.&lt;/p&gt;

&lt;p&gt;At one point, users could land on the site and see a WebGL-related failure message.&lt;/p&gt;

&lt;p&gt;That is a terrible first impression for a browser-based map editor.&lt;/p&gt;

&lt;p&gt;The irony was obvious:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The landing page was breaking trust in the exact technology the product depends on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your actual product needs WebGL, do not waste WebGL on decoration unless the payoff is massive.&lt;/p&gt;

&lt;p&gt;For this page, it was not.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance also mattered more than visual flexing
&lt;/h2&gt;

&lt;p&gt;The old version looked good on my machine.&lt;/p&gt;

&lt;p&gt;That is not enough.&lt;/p&gt;

&lt;p&gt;A lot of people who need a DnD map maker are not using a high-end development setup. They may be on older Windows laptops, integrated graphics, school machines, cheap tablets, or browsers already under load.&lt;/p&gt;

&lt;p&gt;The WebGL hero added unnecessary weight and GPU usage before the user had done anything valuable.&lt;/p&gt;

&lt;p&gt;That is bad product strategy.&lt;/p&gt;

&lt;p&gt;The landing page’s job is not to impress other developers.&lt;/p&gt;

&lt;p&gt;The landing page’s job is to convert people who searched for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RPG map editor&lt;/li&gt;
&lt;li&gt;DnD map maker&lt;/li&gt;
&lt;li&gt;battle map maker&lt;/li&gt;
&lt;li&gt;fantasy map editor&lt;/li&gt;
&lt;li&gt;browser-based map maker&lt;/li&gt;
&lt;li&gt;Inkarnate alternative&lt;/li&gt;
&lt;li&gt;tabletop RPG map tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those users need clarity, speed, and a reason to click.&lt;/p&gt;

&lt;p&gt;They do not need a particle system.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pivot: CSS, pixel art, and brutal simplicity
&lt;/h2&gt;

&lt;p&gt;I removed the Three.js hero scene.&lt;/p&gt;

&lt;p&gt;All of it.&lt;/p&gt;

&lt;p&gt;The particle system, shaders, animated background logic, and extra JavaScript were gone.&lt;/p&gt;

&lt;p&gt;I replaced the hero with a simple image:&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;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hero-bg"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/static/photos/heroBg.webp"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Pixel-art fantasy cave background for RPG Map Editor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And CSS:&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;.hero-bg&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;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;object-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt; &lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;image-rendering&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pixelated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;user-select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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;p&gt;Then I added atmosphere with CSS gradients:&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;.hero-overlay&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;ellipse&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt; &lt;span class="m"&gt;55%&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;42%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.0&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="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.35&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="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="m"&gt;90deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.45&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="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;40%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.45&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="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="m"&gt;180deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.2&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="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;45%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.55&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No canvas.&lt;/p&gt;

&lt;p&gt;No Three.js.&lt;/p&gt;

&lt;p&gt;No extra rendering context.&lt;/p&gt;

&lt;p&gt;No shader debugging.&lt;/p&gt;

&lt;p&gt;Just HTML, CSS, and a fantasy pixel-art image.&lt;/p&gt;




&lt;h2&gt;
  
  
  The surprising part: the simpler version looked more like an RPG tool
&lt;/h2&gt;

&lt;p&gt;This was the part I did not expect.&lt;/p&gt;

&lt;p&gt;The CSS version did not just perform better.&lt;/p&gt;

&lt;p&gt;It communicated the product better.&lt;/p&gt;

&lt;p&gt;RPG Map Editor is for tabletop RPG maps, dungeon maps, battle maps, D&amp;amp;D sessions, and fantasy worldbuilding. A pixel-art cave background instantly feels closer to that world than a generic tech-startup WebGL animation.&lt;/p&gt;

&lt;p&gt;The old hero said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I know how to build WebGL effects.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The new hero says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This is a map-making tool for people who love tabletop RPGs.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a better message.&lt;/p&gt;

&lt;p&gt;Especially for users comparing tools like Inkarnate, Dungeon Scrawl, DungeonFog, or other battle map makers. They are not choosing based on which landing page has the most complex animation. They are choosing based on whether the tool looks useful, fast, and relevant to their campaign.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before vs after
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;WebGL Hero&lt;/th&gt;
&lt;th&gt;CSS Hero&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First impression&lt;/td&gt;
&lt;td&gt;Impressive but generic&lt;/td&gt;
&lt;td&gt;Fantasy/RPG-specific&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Heavier&lt;/td&gt;
&lt;td&gt;Lighter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliability&lt;/td&gt;
&lt;td&gt;Could conflict with WebGL usage&lt;/td&gt;
&lt;td&gt;No WebGL context needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;Shaders, browser quirks, Three.js logic&lt;/td&gt;
&lt;td&gt;HTML and CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product fit&lt;/td&gt;
&lt;td&gt;Tech demo feeling&lt;/td&gt;
&lt;td&gt;DnD map maker feeling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO clarity&lt;/td&gt;
&lt;td&gt;Focused on frontend tech&lt;/td&gt;
&lt;td&gt;Focused on RPG map editor users&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest win was not technical.&lt;/p&gt;

&lt;p&gt;The biggest win was positioning.&lt;/p&gt;

&lt;p&gt;The page stopped feeling like a developer portfolio and started feeling like a product page for a battle map maker.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I kept: WebGL where it actually matters
&lt;/h2&gt;

&lt;p&gt;I am not anti-WebGL.&lt;/p&gt;

&lt;p&gt;The editor itself still needs WebGL2.&lt;/p&gt;

&lt;p&gt;That is where GPU rendering belongs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;painting terrain&lt;/li&gt;
&lt;li&gt;rendering map layers&lt;/li&gt;
&lt;li&gt;moving around the canvas&lt;/li&gt;
&lt;li&gt;placing assets&lt;/li&gt;
&lt;li&gt;handling large battle maps&lt;/li&gt;
&lt;li&gt;exporting maps&lt;/li&gt;
&lt;li&gt;keeping the editor responsive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is real product value.&lt;/p&gt;

&lt;p&gt;But using WebGL for a non-interactive hero background was unnecessary complexity.&lt;/p&gt;

&lt;p&gt;A good rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use advanced technology where it improves the user’s outcome, not where it only improves your ego.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For an RPG map editor, WebGL belongs inside the editor.&lt;/p&gt;

&lt;p&gt;The landing page can be simple.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I built instead
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A pixel-art fantasy hero
&lt;/h3&gt;

&lt;p&gt;The hero now uses a static pixel-art background that matches the product category: fantasy, D&amp;amp;D, tabletop RPGs, dungeon maps, and battle maps.&lt;/p&gt;

&lt;p&gt;This helps users understand the product faster.&lt;/p&gt;

&lt;p&gt;It also gives search engines and AI systems clearer context when the page talks about RPG map editing, battle map creation, and browser-based DnD map tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. CSS-based atmosphere
&lt;/h3&gt;

&lt;p&gt;Instead of real-time lights and particles, I use layered CSS gradients for darkness, depth, and contrast.&lt;/p&gt;

&lt;p&gt;It feels atmospheric without becoming fragile.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. CSS button effects
&lt;/h3&gt;

&lt;p&gt;The main call-to-action button uses a small shimmer effect with a pseudo-element:&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;.glass-button&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;""&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;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="m"&gt;105deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;40%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;45%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;55%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;60%&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-120%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;0.65s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.glass-button&lt;/span&gt;&lt;span class="nd"&gt;:hover::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;120%&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;p&gt;It gives the interface a premium feeling without adding a JavaScript animation library.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Clearer copy
&lt;/h3&gt;

&lt;p&gt;The copy now focuses less on the technology and more on the user:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make a battle map&lt;/li&gt;
&lt;li&gt;try a browser-based editor&lt;/li&gt;
&lt;li&gt;create DnD maps without installing software&lt;/li&gt;
&lt;li&gt;use a map-making tool built for tabletop RPGs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That matters more than saying “powered by WebGL” in the first three seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 1: Your landing page is not your portfolio
&lt;/h2&gt;

&lt;p&gt;This was the harshest lesson.&lt;/p&gt;

&lt;p&gt;I was building the old landing page to impress other developers.&lt;/p&gt;

&lt;p&gt;But the buyer is not another frontend engineer.&lt;/p&gt;

&lt;p&gt;The buyer is a DM, GM, solo worldbuilder, indie RPG creator, or tabletop player who needs a map.&lt;/p&gt;

&lt;p&gt;They are asking practical questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can I make a battle map quickly?&lt;/li&gt;
&lt;li&gt;Does it work in my browser?&lt;/li&gt;
&lt;li&gt;Is it free to try?&lt;/li&gt;
&lt;li&gt;Do I need an account?&lt;/li&gt;
&lt;li&gt;Is it easier than learning a complex editor?&lt;/li&gt;
&lt;li&gt;Is this a usable Inkarnate alternative for my workflow?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions should shape the landing page.&lt;/p&gt;

&lt;p&gt;Not my desire to show off shaders.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 2: “Premium” does not require heavy tech
&lt;/h2&gt;

&lt;p&gt;A page can feel premium with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;strong typography&lt;/li&gt;
&lt;li&gt;clear contrast&lt;/li&gt;
&lt;li&gt;good spacing&lt;/li&gt;
&lt;li&gt;fast loading&lt;/li&gt;
&lt;li&gt;intentional visuals&lt;/li&gt;
&lt;li&gt;consistent art direction&lt;/li&gt;
&lt;li&gt;focused copy&lt;/li&gt;
&lt;li&gt;clean CSS animation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not need WebGL for every premium visual.&lt;/p&gt;

&lt;p&gt;You need taste.&lt;/p&gt;

&lt;p&gt;The CSS version feels better because it has stronger product-market alignment. It looks like a fantasy mapping tool instead of a generic SaaS landing page with a cave background.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 3: SEO works better when the product story is clear
&lt;/h2&gt;

&lt;p&gt;The original article was mostly about removing WebGL.&lt;/p&gt;

&lt;p&gt;That is interesting to developers, but it does not fully capture what the product is.&lt;/p&gt;

&lt;p&gt;The better framing is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I simplified the landing page of a browser-based RPG map editor so DnD map maker users could understand the product faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence contains the actual search intent.&lt;/p&gt;

&lt;p&gt;Good SEO is not just keyword insertion.&lt;/p&gt;

&lt;p&gt;It is matching the article to the real problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;people need RPG maps&lt;/li&gt;
&lt;li&gt;people search for map-making tools&lt;/li&gt;
&lt;li&gt;they compare browser-based options&lt;/li&gt;
&lt;li&gt;they know names like Inkarnate&lt;/li&gt;
&lt;li&gt;they want fast battle map creation&lt;/li&gt;
&lt;li&gt;they do not care about decorative engineering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I understood that, the article became stronger.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is RPG Map Editor?
&lt;/h3&gt;

&lt;p&gt;RPG Map Editor is a browser-based map editor for tabletop RPGs. It is designed for creating fantasy maps, DnD battle maps, dungeon maps, and grid-based maps directly in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is RPG Map Editor a DnD map maker?
&lt;/h3&gt;

&lt;p&gt;Yes. RPG Map Editor can be used as a DnD map maker for creating battle maps, dungeon layouts, terrain maps, and tabletop RPG scenes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is RPG Map Editor a battle map maker?
&lt;/h3&gt;

&lt;p&gt;Yes. The goal is to make it easy to create browser-based battle maps for tabletop RPG sessions without needing to install desktop software.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is RPG Map Editor an Inkarnate alternative?
&lt;/h3&gt;

&lt;p&gt;RPG Map Editor is not an Inkarnate clone. Inkarnate is a well-known fantasy map-making tool, while RPG Map Editor is being built as a browser-based RPG and battle map editor with its own workflow, technical stack, and product direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why use CSS instead of WebGL on the landing page?
&lt;/h3&gt;

&lt;p&gt;Because the landing page does not need real-time rendering. CSS is faster to maintain, more reliable, and good enough for atmospheric visuals. WebGL should be reserved for the actual editor canvas where users create maps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should every RPG map editor avoid WebGL on the landing page?
&lt;/h3&gt;

&lt;p&gt;Not always. If the WebGL experience directly demonstrates the product and performs reliably, it can be worth using. But if it is only decorative, it may create more risk than value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The best technology is not the most impressive one.&lt;/p&gt;

&lt;p&gt;The best technology is the one that helps the user succeed without creating new problems.&lt;/p&gt;

&lt;p&gt;For RPG Map Editor, WebGL belongs in the editor.&lt;/p&gt;

&lt;p&gt;The landing page just needs to be fast, clear, thematic, and convincing.&lt;/p&gt;

&lt;p&gt;That meant deleting the clever part.&lt;/p&gt;

&lt;p&gt;And that was the right decision.&lt;/p&gt;

&lt;p&gt;Try it here: &lt;a href="https://rpgmapeditor.com" rel="noopener noreferrer"&gt;RPG Map Editor — browser-based battle map maker for D&amp;amp;D and TTRPGs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>gamedev</category>
      <category>css</category>
      <category>webgl</category>
    </item>
    <item>
      <title>Building a Browser-Based Inkarnate Alternative for D&amp;D Battle Maps</title>
      <dc:creator>TheXper</dc:creator>
      <pubDate>Thu, 21 May 2026 18:50:36 +0000</pubDate>
      <link>https://dev.to/thexper_f46a597a4e23988d2/building-a-browser-based-inkarnate-alternative-for-dd-battle-maps-4lk</link>
      <guid>https://dev.to/thexper_f46a597a4e23988d2/building-a-browser-based-inkarnate-alternative-for-dd-battle-maps-4lk</guid>
      <description>&lt;h1&gt;
  
  
  Building a Browser-Based Inkarnate Alternative for D&amp;amp;D Battle Maps
&lt;/h1&gt;

&lt;p&gt;When people search for an &lt;strong&gt;Inkarnate alternative&lt;/strong&gt;, they are usually not asking for one single thing.&lt;/p&gt;

&lt;p&gt;Some want a beautiful fantasy world map.&lt;/p&gt;

&lt;p&gt;Some want a regional map for a novel.&lt;/p&gt;

&lt;p&gt;Some want a city map.&lt;/p&gt;

&lt;p&gt;But a lot of Dungeon Masters want something much more practical:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I need a playable D&amp;amp;D battle map for this week’s session, and I do not want to spend the whole evening fighting a heavy tool.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the problem I am building around with &lt;a href="https://www.rpgmapeditor.com/" rel="noopener noreferrer"&gt;RPGMapEditor.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is a browser-based RPG map editor focused on encounter-scale battle maps for D&amp;amp;D, TTRPGs, Roll20, Foundry VTT, and printable tabletop sessions.&lt;/p&gt;

&lt;p&gt;Not a full fantasy illustration suite.&lt;/p&gt;

&lt;p&gt;Not a worldbuilding atlas.&lt;/p&gt;

&lt;p&gt;Not a replacement for every cartography tool.&lt;/p&gt;

&lt;p&gt;A faster way to create readable, playable battle maps in the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick answer: is RPGMapEditor.com an Inkarnate alternative?
&lt;/h2&gt;

&lt;p&gt;Yes, but only for a specific workflow.&lt;/p&gt;

&lt;p&gt;RPGMapEditor.com is an Inkarnate alternative if your main goal is to create &lt;strong&gt;grid-based battle maps&lt;/strong&gt; for D&amp;amp;D or other tabletop RPG sessions.&lt;/p&gt;

&lt;p&gt;It is not trying to replace Inkarnate for polished world maps, regional fantasy maps, or large illustrated map galleries.&lt;/p&gt;

&lt;p&gt;The difference is simple:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Better fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fantasy world map&lt;/td&gt;
&lt;td&gt;Inkarnate or a broader fantasy map tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regional campaign map&lt;/td&gt;
&lt;td&gt;Inkarnate-style tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast D&amp;amp;D battle map&lt;/td&gt;
&lt;td&gt;RPGMapEditor.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser-based encounter prep&lt;/td&gt;
&lt;td&gt;RPGMapEditor.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNG export for Roll20 / Foundry background use&lt;/td&gt;
&lt;td&gt;RPGMapEditor.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Highly polished fantasy illustration&lt;/td&gt;
&lt;td&gt;Broader art-first map tools&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your weekly pain is &lt;strong&gt;encounter production&lt;/strong&gt;, not fantasy illustration, a narrower tool can be better.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why another RPG map editor?
&lt;/h2&gt;

&lt;p&gt;Most DMs do not need a perfect map.&lt;/p&gt;

&lt;p&gt;They need a map that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;readable&lt;/li&gt;
&lt;li&gt;fast to make&lt;/li&gt;
&lt;li&gt;aligned to a grid&lt;/li&gt;
&lt;li&gt;easy to revise&lt;/li&gt;
&lt;li&gt;exportable to a VTT&lt;/li&gt;
&lt;li&gt;good enough for actual play&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sounds obvious, but many map tools slowly become asset browsers, art suites, marketplace platforms, or full VTT automation systems.&lt;/p&gt;

&lt;p&gt;Those can be powerful.&lt;/p&gt;

&lt;p&gt;They can also be slow when the party unexpectedly goes into a cave, tavern, forest road, sewer, bridge, or ruined chapel you did not prepare.&lt;/p&gt;

&lt;p&gt;The bet behind RPGMapEditor.com is that a focused editor can win by reducing decisions.&lt;/p&gt;

&lt;p&gt;Open the browser.&lt;/p&gt;

&lt;p&gt;Start from a blank canvas or demo map.&lt;/p&gt;

&lt;p&gt;Paint terrain.&lt;/p&gt;

&lt;p&gt;Place props.&lt;/p&gt;

&lt;p&gt;Check the grid.&lt;/p&gt;

&lt;p&gt;Export PNG.&lt;/p&gt;

&lt;p&gt;Use it in Roll20, Foundry VTT, or at the table.&lt;/p&gt;

&lt;p&gt;That workflow matters more than having every possible feature on day one.&lt;/p&gt;




&lt;h2&gt;
  
  
  What RPGMapEditor.com supports today
&lt;/h2&gt;

&lt;p&gt;The current shipped workflow focuses on practical battle map creation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser-based map editing&lt;/li&gt;
&lt;li&gt;terrain painting&lt;/li&gt;
&lt;li&gt;props and stamps&lt;/li&gt;
&lt;li&gt;grid-based battle maps&lt;/li&gt;
&lt;li&gt;demo projects&lt;/li&gt;
&lt;li&gt;PNG export&lt;/li&gt;
&lt;li&gt;free accounts with limited saved maps&lt;/li&gt;
&lt;li&gt;optional Studio plan for more saved maps and sharing features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the tool is already useful for a common tabletop workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a tactical map.&lt;/li&gt;
&lt;li&gt;Export it as a PNG.&lt;/li&gt;
&lt;li&gt;Upload it into Roll20 or Foundry VTT.&lt;/li&gt;
&lt;li&gt;Align the grid inside your VTT.&lt;/li&gt;
&lt;li&gt;Run the encounter.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is intentionally simple.&lt;/p&gt;

&lt;p&gt;PNG export is not glamorous, but it is one of the most universal handoff formats for virtual tabletops.&lt;/p&gt;




&lt;h2&gt;
  
  
  What RPGMapEditor.com does not claim yet
&lt;/h2&gt;

&lt;p&gt;This matters because fake comparison articles are useless.&lt;/p&gt;

&lt;p&gt;RPGMapEditor.com does &lt;strong&gt;not&lt;/strong&gt; currently claim to export:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;native Foundry VTT scenes&lt;/li&gt;
&lt;li&gt;Roll20 dynamic lighting&lt;/li&gt;
&lt;li&gt;automatic walls&lt;/li&gt;
&lt;li&gt;automatic doors&lt;/li&gt;
&lt;li&gt;full VTT automation data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For now, the product focuses on the visual battle map.&lt;/p&gt;

&lt;p&gt;Your VTT still handles tokens, walls, lighting, fog, automation, initiative, sheets, and live gameplay.&lt;/p&gt;

&lt;p&gt;That is not a weakness if the user understands the scope.&lt;/p&gt;

&lt;p&gt;It becomes a weakness only if the marketing lies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why browser-based map editing matters
&lt;/h2&gt;

&lt;p&gt;Desktop map tools can be powerful, but they create friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installation&lt;/li&gt;
&lt;li&gt;updates&lt;/li&gt;
&lt;li&gt;local files&lt;/li&gt;
&lt;li&gt;asset folders&lt;/li&gt;
&lt;li&gt;OS compatibility&lt;/li&gt;
&lt;li&gt;device switching&lt;/li&gt;
&lt;li&gt;heavier onboarding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A browser-based RPG map editor removes some of that friction.&lt;/p&gt;

&lt;p&gt;For many DMs, the best tool is not the one with the longest feature list.&lt;/p&gt;

&lt;p&gt;The best tool is the one they actually open on a weeknight when the session is tomorrow.&lt;/p&gt;

&lt;p&gt;That is where browser-based editing has a real advantage.&lt;/p&gt;

&lt;p&gt;You can open a demo project, test the editor, and understand the workflow before committing to an account or paid plan.&lt;/p&gt;




&lt;h2&gt;
  
  
  The real comparison: art suite vs encounter tool
&lt;/h2&gt;

&lt;p&gt;Inkarnate is known for fantasy map creation across multiple styles.&lt;/p&gt;

&lt;p&gt;That is a strength.&lt;/p&gt;

&lt;p&gt;But broad tools often serve broad use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;world maps&lt;/li&gt;
&lt;li&gt;regional maps&lt;/li&gt;
&lt;li&gt;city maps&lt;/li&gt;
&lt;li&gt;fantasy art scenes&lt;/li&gt;
&lt;li&gt;map galleries&lt;/li&gt;
&lt;li&gt;cloneable maps&lt;/li&gt;
&lt;li&gt;large asset libraries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RPGMapEditor.com is narrower.&lt;/p&gt;

&lt;p&gt;It is aimed at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;D&amp;amp;D battle maps&lt;/li&gt;
&lt;li&gt;grid-based tactical scenes&lt;/li&gt;
&lt;li&gt;encounter prep&lt;/li&gt;
&lt;li&gt;Roll20 PNG workflows&lt;/li&gt;
&lt;li&gt;Foundry VTT background map workflows&lt;/li&gt;
&lt;li&gt;browser-first editing&lt;/li&gt;
&lt;li&gt;quick iteration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That narrower positioning is the product strategy.&lt;/p&gt;

&lt;p&gt;Not “more features than Inkarnate.”&lt;/p&gt;

&lt;p&gt;Not “better at everything.”&lt;/p&gt;

&lt;p&gt;Just faster battle map prep for the use case where speed matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  A simple example workflow
&lt;/h2&gt;

&lt;p&gt;Imagine you need a forest ambush map for tomorrow’s session.&lt;/p&gt;

&lt;p&gt;You do not need a masterpiece.&lt;/p&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a road&lt;/li&gt;
&lt;li&gt;trees&lt;/li&gt;
&lt;li&gt;cover&lt;/li&gt;
&lt;li&gt;difficult terrain&lt;/li&gt;
&lt;li&gt;readable grid spacing&lt;/li&gt;
&lt;li&gt;maybe a ruined cart&lt;/li&gt;
&lt;li&gt;maybe rocks or elevation hints&lt;/li&gt;
&lt;li&gt;a clean export&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In RPGMapEditor.com, the intended workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start from a blank map or demo.&lt;/li&gt;
&lt;li&gt;Paint the main playable terrain.&lt;/li&gt;
&lt;li&gt;Add props that affect tactics.&lt;/li&gt;
&lt;li&gt;Keep the grid readable.&lt;/li&gt;
&lt;li&gt;Export a PNG.&lt;/li&gt;
&lt;li&gt;Import it into Roll20 or Foundry VTT.&lt;/li&gt;
&lt;li&gt;Add tokens, walls, lighting, and gameplay details inside the VTT.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the practical DM workflow.&lt;/p&gt;

&lt;p&gt;The map does not need to win an art contest.&lt;/p&gt;

&lt;p&gt;It needs to make combat understandable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who should try RPGMapEditor.com?
&lt;/h2&gt;

&lt;p&gt;RPGMapEditor.com is worth testing if you are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Dungeon Master making regular battle maps&lt;/li&gt;
&lt;li&gt;a GM running grid-based TTRPG encounters&lt;/li&gt;
&lt;li&gt;preparing maps for Roll20&lt;/li&gt;
&lt;li&gt;preparing scene backgrounds for Foundry VTT&lt;/li&gt;
&lt;li&gt;tired of installing desktop tools for simple maps&lt;/li&gt;
&lt;li&gt;looking for a lightweight browser-based RPG map editor&lt;/li&gt;
&lt;li&gt;comparing Inkarnate alternatives specifically for battle maps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is probably not the right first choice if you mainly want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a giant world map&lt;/li&gt;
&lt;li&gt;a polished regional fantasy map&lt;/li&gt;
&lt;li&gt;advanced illustration tools&lt;/li&gt;
&lt;li&gt;a huge established asset marketplace&lt;/li&gt;
&lt;li&gt;automatic VTT walls and doors today&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction is important.&lt;/p&gt;

&lt;p&gt;A focused tool should be judged by the job it is designed to do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I am building it this way
&lt;/h2&gt;

&lt;p&gt;The product philosophy is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A battle map editor should help DMs reach a playable map faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means the important questions are not only technical.&lt;/p&gt;

&lt;p&gt;They are product questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can a new user understand the editor quickly?&lt;/li&gt;
&lt;li&gt;Can they make a usable map in one sitting?&lt;/li&gt;
&lt;li&gt;Can they export without confusion?&lt;/li&gt;
&lt;li&gt;Can they reopen and revise maps later?&lt;/li&gt;
&lt;li&gt;Can the grid stay readable?&lt;/li&gt;
&lt;li&gt;Can the map survive real VTT usage?&lt;/li&gt;
&lt;li&gt;Does the workflow reduce prep stress?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions matter more than adding another shiny feature.&lt;/p&gt;




&lt;h2&gt;
  
  
  RPGMapEditor.com vs Inkarnate: the honest version
&lt;/h2&gt;

&lt;p&gt;Here is the honest comparison.&lt;/p&gt;

&lt;p&gt;Use Inkarnate or a broader fantasy map tool when you want a polished fantasy illustration workflow, world maps, regional maps, city maps, or access to a mature asset ecosystem.&lt;/p&gt;

&lt;p&gt;Use RPGMapEditor.com when you want a focused browser-based battle map editor for encounter prep, terrain painting, props, grid maps, saved projects, and PNG export for VTT use.&lt;/p&gt;

&lt;p&gt;That is the lane.&lt;/p&gt;

&lt;p&gt;And for many DMs, that lane is enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;You can try the browser-based editor here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rpgmapeditor.com/" rel="noopener noreferrer"&gt;RPGMapEditor.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Useful pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rpgmapeditor.com/features" rel="noopener noreferrer"&gt;RPG Map Editor features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rpgmapeditor.com/compare/inkarnate-alternative" rel="noopener noreferrer"&gt;Inkarnate alternative comparison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rpgmapeditor.com/dnd-map-maker" rel="noopener noreferrer"&gt;D&amp;amp;D map maker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rpgmapeditor.com/dungeon-map-maker" rel="noopener noreferrer"&gt;Dungeon map maker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rpgmapeditor.com/roll20-battle-map-export" rel="noopener noreferrer"&gt;Roll20 battle map export&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rpgmapeditor.com/foundry-vtt-battle-map-export" rel="noopener noreferrer"&gt;Foundry VTT battle map export&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are a DM, the best test is simple:&lt;/p&gt;

&lt;p&gt;**Create one encounter map.&lt;/p&gt;

&lt;p&gt;Export it.&lt;/p&gt;

&lt;p&gt;Use it in your actual VTT.&lt;/p&gt;

&lt;p&gt;If it saves prep time, the product is doing its job.**&lt;/p&gt;

</description>
      <category>dnd</category>
      <category>rpg</category>
      <category>rpgmapeditor</category>
      <category>rust</category>
    </item>
    <item>
      <title>I Added Real Playable Demo Maps to RPGMapEditor.com</title>
      <dc:creator>TheXper</dc:creator>
      <pubDate>Tue, 19 May 2026 21:44:24 +0000</pubDate>
      <link>https://dev.to/thexper_f46a597a4e23988d2/i-added-real-playable-demo-maps-to-rpgmapeditorcom-jli</link>
      <guid>https://dev.to/thexper_f46a597a4e23988d2/i-added-real-playable-demo-maps-to-rpgmapeditorcom-jli</guid>
      <description>&lt;p&gt;I’m building &lt;a href="https://www.rpgmapeditor.com" rel="noopener noreferrer"&gt;RPG Map Editor&lt;/a&gt;, a browser-based map editor for tabletop RPGs, D&amp;amp;D sessions, Roll20 maps, and Foundry VTT workflows.&lt;/p&gt;

&lt;p&gt;One of the biggest problems with creative SaaS landing pages is that they usually show fake proof.&lt;/p&gt;

&lt;p&gt;You get a shiny hero section, some screenshots, a few feature cards, and a call-to-action.&lt;/p&gt;

&lt;p&gt;But if you are building a tool for Dungeon Masters, game masters, or worldbuilders, screenshots are not enough.&lt;/p&gt;

&lt;p&gt;People want to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can I open a real map?&lt;/li&gt;
&lt;li&gt;Can I edit it?&lt;/li&gt;
&lt;li&gt;Can I export something useful?&lt;/li&gt;
&lt;li&gt;Is this actually a product, or just a pretty landing page?&lt;/li&gt;
&lt;li&gt;Does it work before I sign up?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I added real playable demo maps directly to the RPGMapEditor.com homepage.&lt;/p&gt;

&lt;p&gt;But there was a catch.&lt;/p&gt;

&lt;p&gt;I wanted users and crawlers to see real product proof, but I did not want Google indexing a bunch of thin &lt;code&gt;/demo/*&lt;/code&gt; editor shell pages.&lt;/p&gt;

&lt;p&gt;So the final architecture became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make the homepage indexable. Make the demos playable. Keep the editor utility routes noindex.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is how I built it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The product problem
&lt;/h2&gt;

&lt;p&gt;RPGMapEditor.com is meant to be a lightweight browser-based alternative for creating RPG and D&amp;amp;D battle maps without installing a desktop app.&lt;/p&gt;

&lt;p&gt;The target user is someone who wants to quickly create a usable session map, export it, and use it in a tabletop workflow.&lt;/p&gt;

&lt;p&gt;That means the landing page cannot only say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Create beautiful RPG maps.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It has to prove it.&lt;/p&gt;

&lt;p&gt;So instead of only showing static marketing screenshots, I wanted the homepage to show real demo projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A top-down encounter map&lt;/li&gt;
&lt;li&gt;A multi-room map&lt;/li&gt;
&lt;li&gt;An entity playground&lt;/li&gt;
&lt;li&gt;Real &lt;code&gt;.rpgmap.json&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;Real editor links&lt;/li&gt;
&lt;li&gt;Real preview images&lt;/li&gt;
&lt;li&gt;Real export workflow copy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The homepage needed to become product proof, not just product description.&lt;/p&gt;




&lt;h2&gt;
  
  
  The SEO problem
&lt;/h2&gt;

&lt;p&gt;The naive version would be simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/demo/typical-topdown
/demo/multi-room-world
/demo/entity-playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each page would load the editor and let users try a map.&lt;/p&gt;

&lt;p&gt;That works for users.&lt;/p&gt;

&lt;p&gt;But it is bad for search.&lt;/p&gt;

&lt;p&gt;Those pages are mostly utility routes. They are not strong landing pages. They are not meant to rank for searches like:&lt;/p&gt;

&lt;p&gt;D&amp;amp;D battle map maker&lt;br&gt;
browser RPG map editor&lt;br&gt;
Roll20 battle map export&lt;br&gt;
Foundry VTT map tool&lt;br&gt;
online tabletop map maker&lt;/p&gt;

&lt;p&gt;The homepage and dedicated landing pages should target those searches.&lt;/p&gt;

&lt;p&gt;The demo routes should support conversion, not compete in search results.&lt;/p&gt;

&lt;p&gt;So the rule became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/                -&amp;gt; indexable product page
/battle-map-maker -&amp;gt; indexable intent page
/roll20-battle-map-export -&amp;gt; indexable workflow page
/foundry-vtt-battle-map-export -&amp;gt; indexable workflow page
/demo/*          -&amp;gt; playable, but noindex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That keeps the site focused.&lt;/p&gt;

&lt;p&gt;The architecture&lt;/p&gt;

&lt;p&gt;The whole demo system is driven by one manifest file:&lt;/p&gt;

&lt;p&gt;`map-editor/public/demo-projects/manifest.json&lt;/p&gt;

&lt;p&gt;That manifest is the source of truth for:&lt;/p&gt;

&lt;p&gt;Homepage demo cards&lt;br&gt;
Editor boot config&lt;br&gt;
Demo route resolution&lt;br&gt;
Preview images&lt;br&gt;
JSON-LD structured data&lt;br&gt;
Internal validation&lt;br&gt;
Product copy for each demo&lt;/p&gt;

&lt;p&gt;The key principle is simple:&lt;/p&gt;

&lt;p&gt;If the marketing page says a demo exists, the editor must actually be able to load it.&lt;/p&gt;

&lt;p&gt;No fake screenshots.&lt;/p&gt;

&lt;p&gt;No stale copy.&lt;/p&gt;

&lt;p&gt;No card that says “open in editor” and then breaks.&lt;/p&gt;

&lt;p&gt;Example manifest entry&lt;/p&gt;

&lt;p&gt;A demo project entry looks roughly like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
  "slug": "typical-topdown",&lt;br&gt;
  "title": "Top-Down Encounter Sample",&lt;br&gt;
  "description": "A small editable battle map designed for a quick tabletop encounter.",&lt;br&gt;
  "intent": "Use this to test terrain, props, grid alignment, and PNG export.",&lt;br&gt;
  "projectUrl": "/demo-projects/typical-topdown.rpgmap.json",&lt;br&gt;
  "previewUrl": "/demo-projects/typical-topdown.webp",&lt;br&gt;
  "demoUrl": "/demo/typical-topdown",&lt;br&gt;
  "width": 32,&lt;br&gt;
  "height": 24,&lt;br&gt;
  "grid": "square"&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This gives the homepage everything it needs:&lt;/p&gt;

&lt;p&gt;A readable title&lt;br&gt;
A useful description&lt;br&gt;
A preview image&lt;br&gt;
A real project file&lt;br&gt;
A playable editor URL&lt;br&gt;
Grid metadata&lt;br&gt;
Search-friendly text&lt;br&gt;
Structured data fields&lt;/p&gt;

&lt;p&gt;The demo card and the editor both come from the same data.&lt;/p&gt;

&lt;p&gt;That matters because marketing drift is real.&lt;/p&gt;

&lt;p&gt;It is very easy for a landing page to say one thing while the actual product does another.&lt;/p&gt;

&lt;p&gt;Server-side validation&lt;/p&gt;

&lt;p&gt;The Rust server validates the demo manifest before rendering the homepage.&lt;/p&gt;

&lt;p&gt;The validation checks that:&lt;/p&gt;

&lt;p&gt;The manifest version is valid&lt;br&gt;
Slugs are unique&lt;br&gt;
Demo URLs are same-origin&lt;br&gt;
Preview images exist&lt;br&gt;
Project files exist&lt;br&gt;
Project JSON parses correctly&lt;br&gt;
Demo projects do not depend on remote http(s) asset URLs&lt;/p&gt;

&lt;p&gt;That last point is important.&lt;/p&gt;

&lt;p&gt;A demo map should not silently depend on a remote image or tileset that can disappear later.&lt;/p&gt;

&lt;p&gt;For &lt;a href="//www.rpgmapeditor.com"&gt;RPGMapEditor.com&lt;/a&gt;, demo projects need to be self-contained so they load quickly and reliably.&lt;/p&gt;

&lt;p&gt;If validation fails, the homepage does not advertise broken demo cards.&lt;/p&gt;

&lt;p&gt;That is better than showing “Open in editor” and sending users into a dead route.&lt;/p&gt;

&lt;p&gt;Server-rendered proof cards&lt;/p&gt;

&lt;p&gt;The homepage renders demo projects as real HTML.&lt;/p&gt;

&lt;p&gt;Not a JavaScript-only carousel.&lt;/p&gt;

&lt;p&gt;Not a hidden JSON blob.&lt;/p&gt;

&lt;p&gt;Not an empty &lt;/p&gt; that only appears after hydration.

&lt;p&gt;Real server-rendered cards.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
`&lt;br&gt;
&lt;br&gt;
  &lt;/p&gt;
&lt;h2&gt;Start from a real encounter&lt;/h2&gt;

&lt;p&gt;&lt;br&gt;
    &lt;a href="" class="article-body-image-wrapper"&gt;&lt;img&gt;&lt;/a&gt;
      src="/demo-projects/typical-topdown.webp"&lt;br&gt;
      alt="Top-Down Encounter Sample editable demo battle map preview"&lt;br&gt;
      width="1200"&lt;br&gt;
      height="800"&lt;br&gt;
      loading="lazy"&lt;br&gt;
      decoding="async"&lt;br&gt;
    &amp;gt;&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h3&amp;gt;Top-Down Encounter Sample&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;A small editable battle map designed for a quick tabletop encounter.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;32 × 24 square grid&amp;lt;/p&amp;gt;

&amp;lt;a href="/demo/typical-topdown"&amp;gt;Open in editor&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;This gives both users and search engines actual proof:&lt;/p&gt;

&lt;p&gt;&lt;a href="//www.rpgmapeditor.com"&gt;RPGMapEditor.com&lt;/a&gt; has real demo maps&lt;br&gt;
The maps are editable&lt;br&gt;
The links are visible&lt;br&gt;
The previews have descriptive alt text&lt;br&gt;
The product is not just a mockup&lt;/p&gt;

&lt;p&gt;That matters for a new SaaS.&lt;/p&gt;

&lt;p&gt;A new product has no trust by default.&lt;/p&gt;

&lt;p&gt;The fastest way to earn trust is to let people try something real.&lt;/p&gt;

&lt;p&gt;The demo routes are playable, but noindex&lt;/p&gt;

&lt;p&gt;Each demo link opens the real editor.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rpgmapeditor.com/demo/typical-topdown" rel="noopener noreferrer"&gt;https://www.rpgmapeditor.com/demo/typical-topdown&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The server resolves the slug against the manifest and injects a boot config:&lt;br&gt;
&lt;code&gt;&lt;br&gt;
window.RPGME_EDITOR_BOOT = {&lt;br&gt;
  mode: "demo",&lt;br&gt;
  projectUrl: "/demo-projects/typical-topdown.rpgmap.json",&lt;br&gt;
  copyApiUrl: "/api/demo-projects/typical-topdown/copy",&lt;br&gt;
  signupNextUrl: "/signup?next=/demo/typical-topdown"&lt;br&gt;
};&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But the demo route also includes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;meta name="robots" content="noindex, nofollow"&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
That is intentional.&lt;/p&gt;

&lt;p&gt;The demo route exists for product experience.&lt;/p&gt;

&lt;p&gt;The homepage exists for search.&lt;/p&gt;

&lt;p&gt;The journey is:&lt;/p&gt;

&lt;p&gt;Google / Dev.to / social post&lt;br&gt;
        ↓&lt;br&gt;
RPGMapEditor.com homepage&lt;br&gt;
        ↓&lt;br&gt;
Demo card&lt;br&gt;
        ↓&lt;br&gt;
Open real editor&lt;br&gt;
        ↓&lt;br&gt;
Try map&lt;br&gt;
        ↓&lt;br&gt;
Export / sign up / copy project&lt;/p&gt;

&lt;p&gt;The demo page helps conversion.&lt;/p&gt;

&lt;p&gt;It does not need to rank.&lt;/p&gt;

&lt;p&gt;Structured data mirrors the visible page&lt;/p&gt;

&lt;p&gt;The homepage also outputs JSON-LD structured data.&lt;/p&gt;

&lt;p&gt;The important part is that the structured data is generated from the same manifest as the visible demo cards.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "&lt;a class="mentioned-user" href="https://dev.to/context"&gt;@context&lt;/a&gt;": "&lt;a href="https://schema.org" rel="noopener noreferrer"&gt;https://schema.org&lt;/a&gt;",&lt;br&gt;
  "@type": "ItemList",&lt;br&gt;
  "itemListElement": [&lt;br&gt;
    {&lt;br&gt;
      "@type": "ListItem",&lt;br&gt;
      "position": 1,&lt;br&gt;
      "name": "Top-Down Encounter Sample",&lt;br&gt;
      "url": "&lt;a href="https://www.rpgmapeditor.com/demo/typical-topdown" rel="noopener noreferrer"&gt;https://www.rpgmapeditor.com/demo/typical-topdown&lt;/a&gt;"&lt;br&gt;
    }&lt;br&gt;
  ]&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;There is also FAQ schema generated from visible FAQ content on the page.&lt;/p&gt;

&lt;p&gt;The rule is:&lt;/p&gt;

&lt;p&gt;Structured data should describe what users can actually see.&lt;/p&gt;

&lt;p&gt;If the manifest fails and demo cards are not rendered, the ItemList schema is not rendered either.&lt;/p&gt;

&lt;p&gt;No fake schema.&lt;/p&gt;

&lt;p&gt;No invisible SEO content.&lt;/p&gt;

&lt;p&gt;No pretending the page contains things it does not.&lt;/p&gt;

&lt;p&gt;Performance choices&lt;/p&gt;

&lt;p&gt;The homepage also has a visual hero section, but I did not want it to destroy performance.&lt;/p&gt;

&lt;p&gt;The hero uses:&lt;/p&gt;

&lt;p&gt;A static WebP poster&lt;br&gt;
fetchpriority="high"&lt;br&gt;
Preload for important visual assets&lt;br&gt;
Optional Three.js overlay&lt;br&gt;
Motion preference checks&lt;br&gt;
Desktop viewport checks&lt;br&gt;
IntersectionObserver before loading heavy JavaScript&lt;/p&gt;

&lt;p&gt;The demo preview images are lazy-loaded below the fold.&lt;/p&gt;

&lt;p&gt;Heavy animation is deferred.&lt;/p&gt;

&lt;p&gt;The homepage should still make sense before JavaScript loads.&lt;/p&gt;

&lt;p&gt;This matters because technical SEO is not only meta tags.&lt;/p&gt;

&lt;p&gt;If the page is slow, unstable, or blank until JavaScript runs, the implementation is weak.&lt;/p&gt;

&lt;p&gt;Why this matters for RPGMapEditor.com&lt;/p&gt;

&lt;p&gt;For a new map editor, the hard part is not only building tools.&lt;/p&gt;

&lt;p&gt;The hard part is proving that the product deserves attention in a crowded space.&lt;/p&gt;

&lt;p&gt;There are already established tools for tabletop maps.&lt;/p&gt;

&lt;p&gt;So RPGMapEditor.com needs to show its positioning clearly:&lt;/p&gt;

&lt;p&gt;Browser-based&lt;br&gt;
No desktop install required&lt;br&gt;
Real editable demo maps&lt;br&gt;
Fast try-before-signup flow&lt;br&gt;
Useful for D&amp;amp;D and tabletop sessions&lt;br&gt;
PNG workflow for Roll20 and Foundry VTT&lt;br&gt;
Honest about what is shipped and what is not&lt;/p&gt;

&lt;p&gt;The demo maps are not just a nice visual section.&lt;/p&gt;

&lt;p&gt;They are part of the product strategy.&lt;/p&gt;

&lt;p&gt;They reduce the gap between:&lt;/p&gt;

&lt;p&gt;“This looks interesting.”&lt;/p&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;p&gt;“I opened a real map and tested it.”&lt;/p&gt;

&lt;p&gt;That gap is where most SaaS landing pages lose users.&lt;/p&gt;

&lt;p&gt;The reusable pattern&lt;/p&gt;

&lt;p&gt;This architecture is not only useful for website.&lt;/p&gt;

&lt;p&gt;You can use the same pattern for any creative SaaS product:&lt;/p&gt;

&lt;p&gt;Browser editors&lt;br&gt;
Diagram tools&lt;br&gt;
Design tools&lt;br&gt;
Code playgrounds&lt;br&gt;
Game tools&lt;br&gt;
Image tools&lt;br&gt;
Website builders&lt;br&gt;
Workflow builders&lt;/p&gt;

&lt;p&gt;The pattern is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create real example projects&lt;/li&gt;
&lt;li&gt;Store metadata in one manifest&lt;/li&gt;
&lt;li&gt;Validate the manifest server-side&lt;/li&gt;
&lt;li&gt;Render example cards as HTML&lt;/li&gt;
&lt;li&gt;Link examples to real playable routes&lt;/li&gt;
&lt;li&gt;Mark utility routes noindex&lt;/li&gt;
&lt;li&gt;Generate structured data from the same manifest&lt;/li&gt;
&lt;li&gt;Track opens, exports, signups, and conversion&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The principle:&lt;/p&gt;

&lt;p&gt;Index the proof. Noindex the playground.&lt;/p&gt;

&lt;p&gt;What I am watching next&lt;/p&gt;

&lt;p&gt;This is still an experiment.&lt;/p&gt;

&lt;p&gt;The implementation is technically cleaner, but the business question is whether it converts.&lt;/p&gt;

&lt;p&gt;The metrics that matter are:&lt;/p&gt;

&lt;p&gt;Homepage click-through rate to demos&lt;br&gt;
Demo open rate&lt;br&gt;
Demo-to-signup conversion&lt;br&gt;
Export attempts&lt;br&gt;
Returning users&lt;br&gt;
Search impressions for map editor queries&lt;br&gt;
Queries mentioning Roll20, Foundry, D&amp;amp;D, and battle maps&lt;br&gt;
Whether users understand what is shipped today&lt;/p&gt;

&lt;p&gt;If people open the demo but do not sign up, the problem may be onboarding.&lt;/p&gt;

&lt;p&gt;If people never open the demo, the homepage copy or card placement is weak.&lt;/p&gt;

&lt;p&gt;If people sign up but do not export, the editor workflow needs work.&lt;/p&gt;

&lt;p&gt;That is the point of building it this way.&lt;/p&gt;

&lt;p&gt;The demos are not only SEO content.&lt;/p&gt;

&lt;p&gt;They are a product validation loop.&lt;/p&gt;

&lt;p&gt;Final thought&lt;/p&gt;

&lt;p&gt;I do not think a new creative SaaS product should hide behind polished screenshots.&lt;/p&gt;

&lt;p&gt;If the product is real, the landing page should prove it.&lt;/p&gt;

&lt;p&gt;For &lt;a href="//www.rpgmapeditor.com"&gt;RPGMapEditor.com&lt;/a&gt;, that meant putting real demo maps on the homepage, rendering them as crawlable HTML, validating them through a manifest, linking them into the real editor, and keeping the utility editor pages out of the search index.&lt;/p&gt;

&lt;p&gt;It is a simple split:&lt;/p&gt;

&lt;p&gt;The homepage ranks.&lt;br&gt;
The demo routes convert.&lt;br&gt;
The manifest keeps everything honest.&lt;/p&gt;

&lt;p&gt;That is the architecture I would use again.&lt;/p&gt;

&lt;p&gt;If you are building a browser-based creative tool, do not just say what it can do.&lt;/p&gt;

&lt;p&gt;Let people open something real.&lt;/p&gt;

</description>
      <category>dnd</category>
      <category>rpg</category>
      <category>rpgmapeditor</category>
      <category>rust</category>
    </item>
    <item>
      <title>Building a Browser-Based RPG Map Editor with Rust, WebAssembly, WebGL2, and React</title>
      <dc:creator>TheXper</dc:creator>
      <pubDate>Mon, 18 May 2026 13:08:33 +0000</pubDate>
      <link>https://dev.to/thexper_f46a597a4e23988d2/building-a-browser-based-rpg-map-editor-with-rust-webassembly-webgl2-and-react-1iof</link>
      <guid>https://dev.to/thexper_f46a597a4e23988d2/building-a-browser-based-rpg-map-editor-with-rust-webassembly-webgl2-and-react-1iof</guid>
      <description>&lt;p&gt;I've been building &lt;a href="https://www.rpgmapeditor.com" rel="noopener noreferrer"&gt;RPGMapEditor.com&lt;/a&gt; — a browser-based fantasy map editor for dungeon masters, worldbuilders, and tabletop RPG players.&lt;/p&gt;

&lt;p&gt;The stack is: &lt;strong&gt;Rust + WebAssembly&lt;/strong&gt; for the editor core, &lt;strong&gt;WebGL2&lt;/strong&gt; for rendering, &lt;strong&gt;React + TypeScript&lt;/strong&gt; for UI, &lt;strong&gt;Rocket&lt;/strong&gt; for the backend, and &lt;strong&gt;SQLite&lt;/strong&gt; for storage.&lt;/p&gt;

&lt;p&gt;This post is not a product pitch. It's about the architecture decisions I made, what broke, what I'd change, and why a map editor is a surprisingly brutal problem domain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick answers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is RPGMapEditor.com?&lt;/strong&gt;&lt;br&gt;
A browser-based fantasy map editor for tabletop RPG creators, dungeon masters, worldbuilders, and virtual tabletop users. No install required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Rust and WebAssembly in a browser app?&lt;/strong&gt;&lt;br&gt;
Rust/WASM keeps editor state, geometry, layer data, and commands outside of React. React owns the UI. Rust owns the map. They don't fight over the source of truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it an Inkarnate alternative?&lt;/strong&gt;&lt;br&gt;
It is in the same category as Inkarnate and Dungeondraft — fantasy maps, battle maps, dungeon maps — but built with a Rust/WASM/WebGL2 browser-native architecture instead of Unity or a pure JS canvas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who is it for?&lt;/strong&gt;&lt;br&gt;
Dungeon masters, tabletop RPG players, worldbuilders, indie game developers, and creators who need fantasy maps and VTT-ready exports.&lt;/p&gt;


&lt;h2&gt;
  
  
  The hard part
&lt;/h2&gt;

&lt;p&gt;A map editor is not a normal web app.&lt;/p&gt;

&lt;p&gt;The login page and dashboard were easy. The hard parts were:&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.amazonaws.com%2Fuploads%2Farticles%2F1621yho7mw18h6qnm79e.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.amazonaws.com%2Fuploads%2Farticles%2F1621yho7mw18h6qnm79e.png" alt=" " width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeping editor state &lt;strong&gt;deterministic&lt;/strong&gt; across undo/redo&lt;/li&gt;
&lt;li&gt;Rendering hundreds of stamps without &lt;strong&gt;one draw call per object&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Preventing React state from becoming the source of truth for the map&lt;/li&gt;
&lt;li&gt;Keeping saves &lt;strong&gt;structured&lt;/strong&gt; rather than flattening everything into a PNG&lt;/li&gt;
&lt;li&gt;Supporting layers, procedural brushes, terrain, fog, and export without turning the codebase into spaghetti&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every time I tried to prototype a map editor in pure React with a canvas, it collapsed. State was in three places. Undo was wrong. Re-renders were killing rendering performance. The map and the UI were constantly fighting over who owned what.&lt;/p&gt;

&lt;p&gt;That's why I moved the editor core to Rust/WASM.&lt;/p&gt;


&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User input (mouse, keyboard, touch)
        ↓
React UI — toolbar, panels, modals
        ↓
TypeScript ↔ WASM bindings (wasm-bindgen)
        ↓
Rust editor engine
        ↓
Command system + immutable map state
        ↓
Renderer pipeline
        ↓
WebGL2 — batched draw calls, atlas, framebuffers
        ↓
&amp;lt;canvas&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;React handles &lt;strong&gt;what the user sees and clicks&lt;/strong&gt;. Rust handles &lt;strong&gt;what the map actually is&lt;/strong&gt;. They communicate through typed WASM bindings. The Rust side is the single source of truth. React is display.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why Rust instead of TypeScript for the core
&lt;/h2&gt;

&lt;p&gt;I want to be honest: Rust has a steep learning curve, and I'm not a graphics programming expert. I picked it anyway because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ownership model forces you to think about state clearly.&lt;/strong&gt; You can't have two mutable references to the same map state. That prevents a whole class of bugs that destroyed my earlier Canvas/JS prototypes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WASM performance is real.&lt;/strong&gt; Geometry operations, tessellation with &lt;code&gt;lyon_tessellation&lt;/code&gt;, and texture atlas packing with &lt;code&gt;guillotiere&lt;/code&gt; are fast and deterministic. No GC pauses, no hidden re-allocation surprises.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The command system is cleaner in Rust.&lt;/strong&gt; Every editor action — stamp placement, eraser stroke, layer reorder, terrain paint — is a typed &lt;code&gt;Command&lt;/code&gt; enum. Undo/redo is just a stack of commands. This is theoretically possible in TypeScript but Rust's type system makes it much harder to cheat.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  The rendering pipeline
&lt;/h2&gt;

&lt;p&gt;WebGL2 was the right call for this kind of editor. The key constraint: &lt;strong&gt;minimizing draw calls&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A naive implementation draws one quad per stamp. Drop 200 stamps on a map and you have 200 draw calls. That doesn't scale.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All stamp textures are packed into a &lt;strong&gt;texture atlas&lt;/strong&gt; at load time using &lt;code&gt;guillotiere&lt;/code&gt; on the Rust side.&lt;/li&gt;
&lt;li&gt;The renderer batches sprites that share the same atlas texture into a &lt;strong&gt;single draw call&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Each layer has its own &lt;strong&gt;framebuffer&lt;/strong&gt;. Compositing layers means blending a small number of textures rather than re-drawing every object.&lt;/li&gt;
&lt;li&gt;The atlas UV coordinates are pre-calculated in Rust and passed to WebGL2 as typed arrays.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: a map with a few hundred stamps and four layers renders comfortably at 60fps on mid-range hardware.&lt;/p&gt;


&lt;h2&gt;
  
  
  What broke (and what I'd redesign)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Biggest mistake: underestimating wasm-bindgen boilerplate.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The boundary between Rust and TypeScript takes serious maintenance. Every time I changed a Rust type that crossed the boundary, I had to update bindings, types, and sometimes the React component that consumed them. I should have designed a stable, narrow API surface between the two sides much earlier. Instead, the boundary grew organically and became a source of bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second mistake: starting the atlas too simple.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first texture atlas was a fixed 2048×2048 texture. That ran out quickly once I added procedural terrain brushes. Moving to a dynamic atlas system (&lt;code&gt;guillotiere&lt;/code&gt;) mid-project was painful. I should have planned for this from the start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third mistake: saving too late.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I spent months on the rendering pipeline before I built the save format. When I finally sat down to serialize a map, I realized my state structure wasn't as clean as I thought. Parts of the render state had leaked into the map state. Redesigning the save format forced a partial refactor of the state model.&lt;/p&gt;

&lt;p&gt;If I started over: design the save format on day one. It forces you to define what the map actually &lt;em&gt;is&lt;/em&gt;, separate from how it &lt;em&gt;renders&lt;/em&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  A real code snippet: the command system
&lt;/h2&gt;

&lt;p&gt;Here's a simplified version of how editor commands work in Rust. Every user action that modifies the map becomes a &lt;code&gt;Command&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Command&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PlaceStamp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stamp_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StampId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LayerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;EraseArea&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LayerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;MoveStamp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stamp_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StampId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Vec2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ReorderLayer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;layer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LayerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;new_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;EditorState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MapState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;undo_stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;redo_stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;EditorState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.map&lt;/span&gt;&lt;span class="nf"&gt;.execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.undo_stack&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.redo_stack&lt;/span&gt;&lt;span class="nf"&gt;.clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;undo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.undo_stack&lt;/span&gt;&lt;span class="nf"&gt;.pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.map&lt;/span&gt;&lt;span class="nf"&gt;.revert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.redo_stack&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&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;p&gt;The key: &lt;code&gt;revert&lt;/code&gt; is the inverse of &lt;code&gt;execute&lt;/code&gt;. Every command knows how to undo itself. No magic. No diff-based snapshots. No full state cloning on every action.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current status
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2Fdqpft42kgmrfvjgoybas.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.amazonaws.com%2Fuploads%2Farticles%2Fdqpft42kgmrfvjgoybas.png" alt=" " width="800" height="377"&gt;&lt;/a&gt;&lt;br&gt;
The editor is in active development. Working right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stamp placement, eraser, selection tools&lt;/li&gt;
&lt;li&gt;Layer system with blend modes&lt;/li&gt;
&lt;li&gt;Procedural terrain brushes (noise-based)&lt;/li&gt;
&lt;li&gt;Texture atlas and batched rendering&lt;/li&gt;
&lt;li&gt;Fog of War&lt;/li&gt;
&lt;li&gt;JSON-based save format&lt;/li&gt;
&lt;li&gt;PNG export&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In progress:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VTT export format (Foundry, Roll20 compatibility)&lt;/li&gt;
&lt;li&gt;Lighting effects with framebuffer compositing&lt;/li&gt;
&lt;li&gt;Collaborative editing (long-term goal)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Is this approach overkill for a side project?
&lt;/h2&gt;

&lt;p&gt;Probably, yes.&lt;/p&gt;

&lt;p&gt;A simpler implementation with Fabric.js or Konva would have gotten me a working editor faster. If the goal was just shipping a demo, I over-engineered this. hah&lt;/p&gt;

&lt;p&gt;But I've used those approaches before. They hit walls. When you want reliable undo/redo, proper layer compositing, atlas batching, and a structured save format, the simple canvas library starts fighting you. You end up bolting architecture onto a foundation that wasn't designed for it.&lt;/p&gt;

&lt;p&gt;Building it as an architecture problem from the start has made it easier to add features correctly, even if it made the first six months slower.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you're building something similar
&lt;/h2&gt;

&lt;p&gt;A few things I'd tell myself earlier:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Design your save format first.&lt;/strong&gt; It forces clarity on what your data model actually is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick one source of truth and protect it.&lt;/strong&gt; If Rust owns the map, don't let React sneak in and mutate it directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design a narrow, stable WASM API surface.&lt;/strong&gt; Fewer crossing points = fewer headaches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't optimize the renderer until you have a correct renderer.&lt;/strong&gt; Get batching right logically before profiling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log your draw call count during development.&lt;/strong&gt; It's the fastest feedback loop for renderer health.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;If you're working on browser-based creative tools, game-adjacent editors, or WebAssembly/WebGL projects, I'd genuinely enjoy discussing the architecture. Drop a comment or find me at &lt;a href="https://www.rpgmapeditor.com" rel="noopener noreferrer"&gt;RPGMapEditor.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webgl</category>
      <category>webassembly</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
