<?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>Forem: TheXper</title>
    <description>The latest articles on Forem by TheXper (@thexper_f46a597a4e23988d2).</description>
    <link>https://forem.com/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>Forem: TheXper</title>
      <link>https://forem.com/thexper_f46a597a4e23988d2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/thexper_f46a597a4e23988d2"/>
    <language>en</language>
    <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://forem.com/thexper_f46a597a4e23988d2/why-i-rebuilt-my-rpg-map-editor-landing-page-with-css-instead-of-webgl-55gf</link>
      <guid>https://forem.com/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://forem.com/thexper_f46a597a4e23988d2/building-a-browser-based-inkarnate-alternative-for-dd-battle-maps-4lk</link>
      <guid>https://forem.com/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://forem.com/thexper_f46a597a4e23988d2/i-added-real-playable-demo-maps-to-rpgmapeditorcom-jli</link>
      <guid>https://forem.com/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://forem.com/thexper_f46a597a4e23988d2/building-a-browser-based-rpg-map-editor-with-rust-webassembly-webgl2-and-react-1iof</link>
      <guid>https://forem.com/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>
