<?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: VOXEL_DRAFT</title>
    <description>The latest articles on DEV Community by VOXEL_DRAFT (@yamada_tanaka_c941e884150).</description>
    <link>https://dev.to/yamada_tanaka_c941e884150</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4048489%2Ff06d406a-d4df-410e-b202-a66aea73ca1c.png</url>
      <title>DEV Community: VOXEL_DRAFT</title>
      <link>https://dev.to/yamada_tanaka_c941e884150</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yamada_tanaka_c941e884150"/>
    <language>en</language>
    <item>
      <title>Building a Local-First Focus Environment in the Browser</title>
      <dc:creator>VOXEL_DRAFT</dc:creator>
      <pubDate>Fri, 18 Sep 2026 01:48:48 +0000</pubDate>
      <link>https://dev.to/yamada_tanaka_c941e884150/building-a-local-first-focus-environment-in-the-browser-2en2</link>
      <guid>https://dev.to/yamada_tanaka_c941e884150/building-a-local-first-focus-environment-in-the-browser-2en2</guid>
      <description>&lt;p&gt;A conventional Pomodoro timer has a simple job: count down a work interval and announce a break. That is useful, but it leaves out the parts of a session that determine whether someone can stay with the work: sound, context, rhythm, and visibility.&lt;/p&gt;

&lt;p&gt;Pomoria started as an experiment in treating focus as an environment instead of a counter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The session is a small state machine
&lt;/h2&gt;

&lt;p&gt;A flexible focus session can be represented as a sequence of steps. Each step has a duration, a type such as work or break, and optional context such as a note or intention.&lt;/p&gt;

&lt;p&gt;That model supports more realistic routines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a short planning step before a writing block;&lt;/li&gt;
&lt;li&gt;two deep-work blocks separated by a recovery break;&lt;/li&gt;
&lt;li&gt;a longer reading block followed by a short review;&lt;/li&gt;
&lt;li&gt;a custom sequence for a recurring creative workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important design decision is that the sequence belongs to the user. A fixed 25/5 preset is a useful starting point, but it should not be the boundary of the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local audio is useful
&lt;/h2&gt;

&lt;p&gt;Ambient audio can be created and mixed directly in the browser with Web Audio APIs. A focus environment can use oscillators, gain nodes, filters, and scheduled changes to build a sound layer without sending the session to a server.&lt;/p&gt;

&lt;p&gt;There are practical benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no audio file needs to be uploaded for the core experience;&lt;/li&gt;
&lt;li&gt;the sound can respond to the current step;&lt;/li&gt;
&lt;li&gt;users can adjust layers independently;&lt;/li&gt;
&lt;li&gt;the session can remain usable offline after the page is available;&lt;/li&gt;
&lt;li&gt;the design is easier to explain in privacy terms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local does not mean every browser feature works identically everywhere. Autoplay restrictions, device volume, headphones, and browser power-saving behaviour still matter. A good interface should make the first interaction explicit and keep controls understandable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixing layers without making noise
&lt;/h2&gt;

&lt;p&gt;More layers do not automatically make a better environment. Each source should have its own gain control, and the default levels should be conservative. White, pink, and brown noise can be treated as alternative beds rather than three sounds that must all play together.&lt;/p&gt;

&lt;p&gt;Rain can add texture. A binaural tone can be optional. The user should be able to remove any layer immediately and still keep the timer running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the session visible
&lt;/h2&gt;

&lt;p&gt;Picture-in-Picture changes the relationship between the timer and the work surface. Instead of asking the user to keep returning to a full tab, the active session can remain visible in a small window while the user writes, codes, or reads elsewhere.&lt;/p&gt;

&lt;p&gt;This is a small feature with an important product implication: visibility should support attention rather than interrupt it. The timer should be available when needed and quiet when it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What “local-first” does not promise
&lt;/h2&gt;

&lt;p&gt;Local processing is not a promise that the entire product is offline or that every browser stores every setting forever. It is a design direction: keep the core focus experience in the browser, make data movement explicit, and avoid requiring an account just to start a session.&lt;/p&gt;

&lt;p&gt;Being precise about this builds more trust than using “private” as a vague marketing word.&lt;/p&gt;

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

&lt;p&gt;The main lesson from building Pomoria is that a focus product should model the environment around the work. The countdown is one component. A useful session also needs a beginning, a middle, and an end; a rhythm that fits the task; sound that can be tuned; context that can be remembered; and a view that stays available.&lt;/p&gt;

&lt;p&gt;Pomoria is an early implementation of that idea: a browser-based focus environment with custom sequences, local ambient sound, and Picture-in-Picture. It is freemium and can be started without installing anything or creating an account: &lt;a href="https://pomoria.app/" rel="noopener noreferrer"&gt;https://pomoria.app/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions for other builders
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Which browser audio constraints have affected your product design?&lt;/li&gt;
&lt;li&gt;When does a persistent timer help rather than distract?&lt;/li&gt;
&lt;li&gt;Which settings should belong to a session template instead of a user account?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>startup</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building a Browser-Based Voxel Editor with React Three Fiber</title>
      <dc:creator>VOXEL_DRAFT</dc:creator>
      <pubDate>Tue, 28 Jul 2026 06:37:06 +0000</pubDate>
      <link>https://dev.to/yamada_tanaka_c941e884150/building-a-browser-based-voxel-editor-with-react-three-fiber-20af</link>
      <guid>https://dev.to/yamada_tanaka_c941e884150/building-a-browser-based-voxel-editor-with-react-three-fiber-20af</guid>
      <description>&lt;p&gt;I have been building &lt;a href="https://voxeldraft.com/en/edit/" rel="noopener noreferrer"&gt;VoxelDraft&lt;/a&gt;, a voxel editor that runs entirely in the browser without an account or installation.&lt;/p&gt;

&lt;p&gt;The editor supports block painting, layers, keyframe animation, GIF recording, local projects, and exports for OBJ/MTL, GLB, VOX, Minecraft Schematic, and Roblox RBXL. This post covers the architecture choices that kept those features manageable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep edit data serializable
&lt;/h2&gt;

&lt;p&gt;The editable model is an array of plain voxel records rather than a collection of Three.js objects:&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;type&lt;/span&gt; &lt;span class="nx"&gt;VoxelData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;layerId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That decision makes JSON backups, local persistence, undo/redo snapshots, sharing, and format conversion much simpler. Three.js objects are derived render state, not the source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render repeated cubes with InstancedMesh
&lt;/h2&gt;

&lt;p&gt;Creating one mesh and one React component per cube becomes expensive as a model grows. VoxelDraft uses &lt;code&gt;THREE.InstancedMesh&lt;/code&gt; where geometry and material can be shared.&lt;/p&gt;

&lt;p&gt;Each voxel contributes a transform matrix. Pointer intersections return the instanced mesh and instance ID, which can be mapped back to the editable voxel record.&lt;/p&gt;

&lt;p&gt;There are tradeoffs. Per-voxel colors need instance colors or grouping by material, and changing a single block still requires carefully updating the instance buffers. The reduction in draw calls is worth that complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make exporters independent from UI
&lt;/h2&gt;

&lt;p&gt;The format exporters accept voxel records and produce a &lt;code&gt;Blob&lt;/code&gt;. The UI is only responsible for validation and triggering a download.&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;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exportToVOX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;voxels&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VOX, Minecraft Schematic, and RBXL are generated directly. For GLB, the app builds a temporary Three.js scene and sends it to &lt;code&gt;GLTFExporter&lt;/code&gt; from &lt;code&gt;three-stdlib&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Keeping binary generation separate from React event handlers makes exporters easier to test and reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move GIF encoding off the main thread
&lt;/h2&gt;

&lt;p&gt;VoxelDraft records both animation output and modeling timelapses. GIF encoding can easily freeze the editor, so gif.js runs its encoder in a Web Worker.&lt;/p&gt;

&lt;p&gt;With Vite, a tiny module resolves the worker asset:&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;import&lt;/span&gt; &lt;span class="nx"&gt;workerUrl&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gif.js/dist/gif.worker.js?url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;workerUrl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The editor captures canvas frames, sends them to the encoder, and displays progress. Resolution, frame count, and FPS need sensible limits because all three affect memory use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start local-first, but provide backups
&lt;/h2&gt;

&lt;p&gt;Projects are stored in &lt;code&gt;localStorage&lt;/code&gt; so the first editing session requires no backend or signup. This is convenient, but browser data can be cleared and storage is limited. The UI therefore offers JSON export for durable backups.&lt;/p&gt;

&lt;p&gt;IndexedDB is the natural next step if projects and thumbnails outgrow localStorage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-render the pages around the editor
&lt;/h2&gt;

&lt;p&gt;An interactive editor is not a great search landing page by itself. The surrounding English, Japanese, Chinese, and Spanish pages are pre-rendered with Vike. They include canonical URLs, &lt;code&gt;hreflang&lt;/code&gt;, Open Graph metadata, and JSON-LD.&lt;/p&gt;

&lt;p&gt;The broader lesson is that a browser tool has two surfaces: the client-heavy workspace and the indexable pages that explain why someone should open it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would keep if I started again
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Plain serializable data as the source of truth.&lt;/li&gt;
&lt;li&gt;Instanced rendering for repeated geometry.&lt;/li&gt;
&lt;li&gt;Exporters isolated from the UI.&lt;/li&gt;
&lt;li&gt;Workers for CPU-heavy media encoding.&lt;/li&gt;
&lt;li&gt;A no-account path plus explicit backup tools.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can try the current build at &lt;a href="https://voxeldraft.com/en/edit/" rel="noopener noreferrer"&gt;voxeldraft.com&lt;/a&gt;. I would especially appreciate feedback on the editor flow, animation tools, and export compatibility.&lt;/p&gt;

</description>
      <category>react</category>
      <category>threejs</category>
      <category>webdev</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
