<?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: 天知道997 997</title>
    <description>The latest articles on DEV Community by 天知道997 997 (@997_997_e71bbc2a940b54).</description>
    <link>https://dev.to/997_997_e71bbc2a940b54</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%2F4031626%2Fdf7df017-ed34-40d0-9390-1ec1eca3940b.jpg</url>
      <title>DEV Community: 天知道997 997</title>
      <link>https://dev.to/997_997_e71bbc2a940b54</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/997_997_e71bbc2a940b54"/>
    <language>en</language>
    <item>
      <title>Building Purupuru Maker: A Browser-Based Image Wobble Tool with React, Canvas, and Three.js</title>
      <dc:creator>天知道997 997</dc:creator>
      <pubDate>Mon, 20 Jul 2026 09:43:01 +0000</pubDate>
      <link>https://dev.to/997_997_e71bbc2a940b54/building-purupuru-maker-a-browser-based-image-wobble-tool-with-react-canvas-and-threejs-41gp</link>
      <guid>https://dev.to/997_997_e71bbc2a940b54/building-purupuru-maker-a-browser-based-image-wobble-tool-with-react-canvas-and-threejs-41gp</guid>
      <description>&lt;p&gt;&lt;a href="https://www.purupurumaker.pro/" rel="noopener noreferrer"&gt;Purupuru Maker&lt;/a&gt; is a lightweight browser tool for turning still images into soft, jelly-like wobble animations. The core idea is simple for the user: upload an image, paint the area that should move, tune the motion, and export the animation. Under the hood, the project combines React, TypeScript, Canvas 2D, Three.js, and browser-native media APIs to create an animation workflow that runs locally without sending user images to a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Project Is Built as a Client-Side Tool
&lt;/h2&gt;

&lt;p&gt;Image animation tools often feel heavier than they need to be. Many require installing desktop software, uploading artwork to a remote service, or learning a timeline-based editor before producing a simple animated sticker or avatar. Purupuru Maker takes the opposite approach: the entire editing and export pipeline runs in the browser.&lt;/p&gt;

&lt;p&gt;That constraint shapes the technical design. The app needs to decode images, let users paint a motion mask, preview a real-time deformation effect, and export media using APIs that are widely available in modern browsers. It also needs to stay responsive while handling images up to practical web sizes. React manages the application interface, Canvas 2D handles direct mask editing, Three.js renders the deformable image mesh, and MediaRecorder or GIF encoding turns the preview into a downloadable file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The High-Level Architecture
&lt;/h2&gt;

&lt;p&gt;The application is built with Vite, React, and TypeScript. Vite keeps local development fast, while TypeScript is especially useful for the shared data structures that move through the editor: loaded image metadata, motion settings, mask state, export settings, and deformable mesh vertices.&lt;/p&gt;

&lt;p&gt;The main workflow is organized around a few responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Image loading normalizes the uploaded file into a browser-friendly bitmap.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mask painting stores the user's brush strokes in a hidden canvas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mesh generation creates a grid of vertices over the image.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mask sampling converts painted pixels into per-vertex motion weights.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Physics updates move the weighted vertices every animation frame.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Three.js renders the image texture on the deformed grid.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Export modules record or encode the rendered canvas locally.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation keeps the product easy to extend. For example, adding a new motion preset mostly means changing motion settings, while adding a new export format can live in the export layer without rewriting the editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning an Image into a Deformable Mesh
&lt;/h2&gt;

&lt;p&gt;The wobble effect is based on a textured grid. Instead of drawing the uploaded image as a flat rectangle, the renderer maps it onto a plane made of many small triangles. Each vertex in the grid stores its original position, current position, UV coordinates, motion weight, velocity, and offset.&lt;/p&gt;

&lt;p&gt;On desktop, the mesh uses a denser grid than on smaller screens, which helps balance visual quality and performance. The grid is converted into triangle indices, then passed to Three.js as a BufferGeometry. The uploaded image becomes a CanvasTexture, and a simple MeshBasicMaterial displays that texture without lighting complexity.&lt;/p&gt;

&lt;p&gt;This approach is efficient because the browser's GPU handles most of the textured rendering work. Each frame only needs to update vertex positions, mark the position buffer as dirty, and let Three.js draw the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Painting Motion with a Canvas Mask
&lt;/h2&gt;

&lt;p&gt;The user does not directly edit the image. Instead, Purupuru Maker keeps the original artwork intact and stores brush strokes in a separate motion mask. Canvas 2D is a strong fit for this job because it supports fast pointer-based drawing, erasing, alpha blending, and pixel reads.&lt;/p&gt;

&lt;p&gt;When the user paints, the mask records where motion should happen and how strong that motion should be. A visible overlay can show the painted area during editing, but the underlying image remains unchanged. This makes the editor non-destructive and keeps the mental model simple: paint the parts that should wobble, erase the parts that should stay still.&lt;/p&gt;

&lt;p&gt;Before previewing, the app samples the mask around every mesh vertex. The sampled mask value becomes the vertex's motion weight. A small edge fade reduces movement near image borders, and several smoothing passes blend neighboring weights so the deformation feels organic instead of jagged.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wobble Physics Model
&lt;/h2&gt;

&lt;p&gt;The animation is not a single sine wave applied uniformly to the image. Each weighted vertex moves toward a procedural target, then a spring-damping model decides how it follows that target over time.&lt;/p&gt;

&lt;p&gt;The target motion is generated from a mix of sine and cosine functions using the vertex's normalized position, the current time, motion speed, strength, direction, and randomness. That creates subtle spatial variation, so the image feels soft and lively rather than mechanically sliding as one flat piece.&lt;/p&gt;

&lt;p&gt;The spring system gives the motion its character. A higher spring value makes vertices chase their target more eagerly. Damping controls how quickly motion loses energy. Strength determines the scale of the wobble, while clamping keeps offsets bounded so extreme settings do not tear the image apart.&lt;/p&gt;

&lt;p&gt;The app also supports motion envelopes, including looping motion and settling motion. A settling envelope can start strong and decay over a short duration, which is useful for bounce-style effects that feel like a quick reaction instead of an endless loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering with Three.js
&lt;/h2&gt;

&lt;p&gt;Three.js is used as a focused rendering layer rather than a full 3D scene engine. The scene contains an orthographic camera and a single textured mesh. The orthographic camera maps the image coordinate system directly into the renderer, which makes it easier to align pointer input, mask sampling, and export dimensions.&lt;/p&gt;

&lt;p&gt;Every animation frame follows a compact loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Update mesh physics based on elapsed time and motion settings.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write the new vertex positions into the geometry buffer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Render the scene to the WebGL canvas.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the renderer preserves the drawing buffer, the same canvas can be used for preview and export capture. That keeps exported media visually close to what the user sees in the editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Export: MP4 and GIF
&lt;/h2&gt;

&lt;p&gt;Export is intentionally local. For MP4-capable browsers, the app uses &lt;code&gt;HTMLCanvasElement.captureStream()&lt;/code&gt; with &lt;code&gt;MediaRecorder&lt;/code&gt;. The renderer's canvas becomes a media stream, MediaRecorder collects video chunks, and the final Blob is downloaded directly by the browser.&lt;/p&gt;

&lt;p&gt;GIF export follows a different path. The app captures frames from the rendered canvas, reads pixel data, quantizes colors with &lt;code&gt;gifenc&lt;/code&gt;, writes frames into a GIF encoder, and returns a downloadable GIF Blob. This is more CPU-intensive than MediaRecorder, but it gives users a format that remains useful for stickers, posts, and lightweight sharing.&lt;/p&gt;

&lt;p&gt;Both export paths report progress and support cancellation. That matters because media generation can take a few seconds, especially for longer animations or larger images.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy as a Technical Feature
&lt;/h2&gt;

&lt;p&gt;One of the most important design choices is that uploaded images stay local. The app does not need a server-side rendering pipeline to create the effect. Image decoding, mask editing, mesh deformation, preview rendering, and export all happen in the user's browser.&lt;/p&gt;

&lt;p&gt;This is not only a privacy benefit; it also simplifies infrastructure. There is no upload queue, no storage lifecycle, no server-side image processing cost, and no need to retain user-generated artwork. For a creative tool where users may test personal drawings, avatars, fan art, or unreleased assets, that local-first model is part of the product experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Lessons
&lt;/h2&gt;

&lt;p&gt;The project highlights a few practical browser graphics lessons:&lt;/p&gt;

&lt;p&gt;Dense meshes look smoother, but every extra vertex adds per-frame work. Mask sampling should happen when the mask changes, not every frame. Physics should use a stable delta time so animation does not explode after a tab pause or frame drop. Large images should be downscaled before editing so the tool remains fast on ordinary laptops.&lt;/p&gt;

&lt;p&gt;The architecture also benefits from using the right rendering mode for each task. Canvas 2D is excellent for brush input and pixel masks. WebGL, through Three.js, is better for repeatedly drawing a textured deformable mesh. Keeping those jobs separate makes the app simpler and faster than forcing one API to do everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;Purupuru Maker's current architecture leaves room for richer creative controls. Future improvements could include more motion presets, undo and redo for mask edits, project save and restore, finer pinning or freeze masks, better mobile gestures, and additional export options through newer browser APIs such as WebCodecs.&lt;/p&gt;

&lt;p&gt;The underlying pattern is flexible: a user-authored mask becomes weights, weights drive mesh motion, and the browser renders the result in real time. That pattern can support more than a jelly wobble. With different physics, presets, and masks, the same foundation could power breathing animations, subtle idle loops, heartbeat effects, shake reactions, and playful sticker motion.&lt;/p&gt;

&lt;p&gt;Purupuru Maker shows how far modern browser APIs can go for small creative tools. With a focused architecture and a local-first pipeline, a static image can become an animated, shareable moment without leaving the page.&lt;/p&gt;

</description>
      <category>react</category>
      <category>showdev</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Game Guide Site Without a Framework: The Quiet Power of Static HTML</title>
      <dc:creator>天知道997 997</dc:creator>
      <pubDate>Thu, 16 Jul 2026 07:51:37 +0000</pubDate>
      <link>https://dev.to/997_997_e71bbc2a940b54/building-a-game-guide-site-without-a-framework-the-quiet-power-of-static-html-55ni</link>
      <guid>https://dev.to/997_997_e71bbc2a940b54/building-a-game-guide-site-without-a-framework-the-quiet-power-of-static-html-55ni</guid>
      <description>&lt;p&gt;I love a good framework as much as the next developer. Give me routing, components, hot reload, a tidy build pipeline, and I am happy.&lt;/p&gt;

&lt;p&gt;But for this project, I deliberately went the other way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.sootseer.org" rel="noopener noreferrer"&gt;Sootseer&lt;/a&gt; is a small, focused Palworld guide site. It answers a very specific player intent: where to find Sootseer, what it drops, how breeding works, and what changed in newer game data. It is not a dashboard. It is not a SaaS product. It is not a social app.&lt;/p&gt;

&lt;p&gt;It is a content-first reference page.&lt;/p&gt;

&lt;p&gt;So the architecture is almost aggressively simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static HTML&lt;/li&gt;
&lt;li&gt;One CSS file&lt;/li&gt;
&lt;li&gt;One small JavaScript file&lt;/li&gt;
&lt;li&gt;Local image assets&lt;/li&gt;
&lt;li&gt;A privacy page&lt;/li&gt;
&lt;li&gt;&lt;code&gt;robots.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sitemap.xml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Structured data in the page head&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No bundler. No hydration. No client-side router. No dependency graph that needs a weather report before deployment.&lt;/p&gt;

&lt;p&gt;And honestly? It works really well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Main Technical Decision: Let HTML Do the Heavy Lifting
&lt;/h2&gt;

&lt;p&gt;The page is built around plain semantic sections:&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;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"route"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"breeding"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"faq"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sounds basic, but for a guide site, basic is a feature.&lt;/p&gt;

&lt;p&gt;Search engines, browsers, screen readers, and impatient users all benefit when the document is readable before JavaScript does anything. The page has a real title, real headings, real links, real FAQ content, and real tables. If the JavaScript never runs, the core content still exists.&lt;/p&gt;

&lt;p&gt;That was the rule I kept coming back to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaScript can improve the experience, but it should not be required to understand the page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a game guide, this matters. A lot of visitors arrive from search with one narrow question. They do not want an app shell. They want the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured Data Is Part of the Architecture
&lt;/h2&gt;

&lt;p&gt;One thing I think developers often treat as “SEO garnish” is structured data. For this kind of site, I consider it part of the core architecture.&lt;/p&gt;

&lt;p&gt;The page includes JSON-LD for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organization&lt;/li&gt;
&lt;li&gt;WebSite&lt;/li&gt;
&lt;li&gt;WebPage&lt;/li&gt;
&lt;li&gt;Article&lt;/li&gt;
&lt;li&gt;BreadcrumbList&lt;/li&gt;
&lt;li&gt;FAQPage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives the content a machine-readable shape.&lt;/p&gt;

&lt;p&gt;The visual page says, “Here is a Sootseer guide.”&lt;/p&gt;

&lt;p&gt;The structured data says, more precisely, “This is an article, published by this entity, about this topic, with these FAQ answers, at this canonical URL.”&lt;/p&gt;

&lt;p&gt;That distinction matters when the site is designed to compete in search results where many pages are saying roughly the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just Enough JavaScript
&lt;/h2&gt;

&lt;p&gt;The JavaScript layer is intentionally tiny. It handles three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tab switching for encounter routes&lt;/li&gt;
&lt;li&gt;Filtering the skill table&lt;/li&gt;
&lt;li&gt;Remembering checklist progress in &lt;code&gt;localStorage&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The checklist is a nice example of progressive enhancement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storageKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sootseer-guide-checklist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;readChecklist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;Nothing here needs a framework. There is no shared global state problem. No server state. No complex synchronization. Just a few DOM nodes and a small piece of browser storage.&lt;/p&gt;

&lt;p&gt;The page feels interactive, but the interaction is not allowed to take over the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS Carries the Product Feel
&lt;/h2&gt;

&lt;p&gt;The CSS does more than make things pretty. It defines the whole reading experience.&lt;/p&gt;

&lt;p&gt;The layout uses responsive grids, compact panels, sticky navigation, visual tags, loot cards, tables, and mobile-specific adjustments. The design goal was not “landing page.” It was “useful guide that feels polished.”&lt;/p&gt;

&lt;p&gt;That distinction changed the UI choices.&lt;/p&gt;

&lt;p&gt;Instead of a giant marketing hero and vague feature blocks, the first screen gives users the practical identity of the page: Sootseer, Palworld version context, type tags, stats, and direct navigation to location, Predator Core, breeding, and ranch information.&lt;/p&gt;

&lt;p&gt;A content site can still have personality. It just has to respect the reader’s intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Boring Files Matter
&lt;/h2&gt;

&lt;p&gt;The project also includes &lt;code&gt;robots.txt&lt;/code&gt;, &lt;code&gt;sitemap.xml&lt;/code&gt;, Open Graph tags, Twitter card tags, canonical links, image preloading, and a privacy page.&lt;/p&gt;

&lt;p&gt;None of that is glamorous.&lt;/p&gt;

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

&lt;p&gt;A small static site does not get to hide behind application complexity. Every file has a job. The page needs to load quickly, preview cleanly when shared, index correctly, and explain its data and privacy posture.&lt;/p&gt;

&lt;p&gt;That is especially true for niche content sites. Trust is built through small signals: clear source policy, current review dates, stable URLs, and no unnecessary friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d Use This Architecture For Again
&lt;/h2&gt;

&lt;p&gt;I would happily reuse this approach for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Game guide pages&lt;/li&gt;
&lt;li&gt;Small documentation hubs&lt;/li&gt;
&lt;li&gt;Local business microsites&lt;/li&gt;
&lt;li&gt;Tool directories&lt;/li&gt;
&lt;li&gt;Comparison pages&lt;/li&gt;
&lt;li&gt;Static editorial projects&lt;/li&gt;
&lt;li&gt;SEO-focused niche sites&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would not use it for everything. Once the site needs user accounts, server-side personalization, large content operations, or shared UI across hundreds of dynamic pages, I would reach for a framework.&lt;/p&gt;

&lt;p&gt;But for a single-purpose guide, static architecture is not a compromise. It is a strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson
&lt;/h2&gt;

&lt;p&gt;Modern web development often nudges us toward more machinery than the problem actually needs.&lt;/p&gt;

&lt;p&gt;This project was a useful reminder that a fast, useful website can still be made from the old ingredients: HTML that explains itself, CSS that respects the content, and JavaScript that knows when to stop.&lt;/p&gt;

&lt;p&gt;Sometimes the best stack is the one that leaves the user alone and lets the page answer the question.&lt;/p&gt;

</description>
      <category>html</category>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built a Daily Browser Game Without a Framework, and I Kind of Loved It</title>
      <dc:creator>天知道997 997</dc:creator>
      <pubDate>Thu, 16 Jul 2026 07:49:50 +0000</pubDate>
      <link>https://dev.to/997_997_e71bbc2a940b54/i-built-a-daily-browser-game-without-a-framework-and-i-kind-of-loved-it-39fb</link>
      <guid>https://dev.to/997_997_e71bbc2a940b54/i-built-a-daily-browser-game-without-a-framework-and-i-kind-of-loved-it-39fb</guid>
      <description>&lt;p&gt;Modern web development can make you feel like every small idea needs a full production stack before it deserves to exist.&lt;/p&gt;

&lt;p&gt;A framework. A router. A bundler. A component system. A styling strategy. A content layer. A deployment adapter. Maybe a database, because apparently we all enjoy having responsibilities.&lt;/p&gt;

&lt;p&gt;For Vexle, a small daily flag-drawing game, I went in the opposite direction.&lt;/p&gt;

&lt;p&gt;The site is built with plain JavaScript, a tiny Node.js static generator, JSON content files, one HTML game fragment, and a browser canvas. The final output is just a &lt;code&gt;dist&lt;/code&gt; folder that can be dropped onto Vercel or any static host.&lt;/p&gt;

&lt;p&gt;And honestly? For this kind of project, it felt great.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Product Shape
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.vexle.pro/" rel="noopener noreferrer"&gt;Vexle&lt;/a&gt; is a daily geography game. The player sees a country name, draws the flag from memory, then reveals the real flag and gets a rough match score.&lt;/p&gt;

&lt;p&gt;That meant the site needed a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A playable first screen&lt;/li&gt;
&lt;li&gt;Fast loading&lt;/li&gt;
&lt;li&gt;Mobile-friendly drawing controls&lt;/li&gt;
&lt;li&gt;Local daily progress&lt;/li&gt;
&lt;li&gt;SEO pages like About, Privacy, and Terms&lt;/li&gt;
&lt;li&gt;Metadata, Open Graph tags, structured data, sitemap, and robots.txt&lt;/li&gt;
&lt;li&gt;A simple deployment story&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it did not need was a client-side app framework.&lt;/p&gt;

&lt;p&gt;There is no complex state shared across routes. There is no authenticated dashboard. There are no deeply nested UI flows. The game is the app, and most of the surrounding site is content.&lt;/p&gt;

&lt;p&gt;So the architecture became very boring in the best possible way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The project is split into a few plain folders:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
text
content/
  home.json
  game.html
  pages/
    about.json
    privacy.json
    terms.json

public/
  app.js
  styles.css
  data/
    game-data.json

scripts/
  build-static.mjs

server.mjs
site.config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>gamedev</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
