<?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: BY L</title>
    <description>The latest articles on DEV Community by BY L (@by_l_99c0b1b4256760ed5596).</description>
    <link>https://dev.to/by_l_99c0b1b4256760ed5596</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%2F4061070%2Fa65047fa-2137-4195-aa92-cb05b9a66b10.png</url>
      <title>DEV Community: BY L</title>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/by_l_99c0b1b4256760ed5596"/>
    <language>en</language>
    <item>
      <title>Privacy-First Patterns for Browser-Based Self-Reflection Tools</title>
      <dc:creator>BY L</dc:creator>
      <pubDate>Thu, 13 Aug 2026 11:13:06 +0000</pubDate>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596/privacy-first-patterns-for-browser-based-self-reflection-tools-5bh3</link>
      <guid>https://dev.to/by_l_99c0b1b4256760ed5596/privacy-first-patterns-for-browser-based-self-reflection-tools-5bh3</guid>
      <description>&lt;p&gt;Personal questionnaires look like simple front-end projects, but they raise real design questions about data handling and interpretation. A useful implementation should minimize collection, explain its scoring, and avoid presenting a lightweight result as a diagnosis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep raw answers on the device
&lt;/h2&gt;

&lt;p&gt;If the experience does not need cross-device sync, individual responses can stay in browser memory or local storage. Scoring can run entirely in JavaScript. This supports progress indicators, answer review, and category summaries without sending sensitive selections to an analytics or application server.&lt;/p&gt;

&lt;p&gt;The privacy copy should match the implementation. Tell users what remains local, whether anything is persisted, and how they can clear it. Avoid collecting an email merely to reveal a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate scoring from meaning
&lt;/h2&gt;

&lt;p&gt;A numeric score is a compact summary, not proof of personality, morality, health, or compatibility. The interface should make that limit visible near the result instead of hiding it in a footer. It should also allow people to skip prompts that are uncomfortable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://testricepurity.org/" rel="noopener noreferrer"&gt;Test Rice Purity&lt;/a&gt; is one example of this pattern: a free browser-local questionnaire paired with transparent score context and privacy-conscious defaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design the follow-up
&lt;/h2&gt;

&lt;p&gt;The most useful questions come after the number: Which prompts felt relevant? Which lacked context? What should remain private? A thoughtful product gives users room to disagree with the output and avoids fixed labels.&lt;/p&gt;

&lt;p&gt;These choices are technically modest, but they materially improve trust. Local computation, clear boundaries, and restrained interpretation are good defaults for any self-reflection tool built on the web.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Designing a Browser-Local Image Merge Pipeline Without Uploads</title>
      <dc:creator>BY L</dc:creator>
      <pubDate>Wed, 12 Aug 2026 16:24:15 +0000</pubDate>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596/designing-a-browser-local-image-merge-pipeline-without-uploads-18ia</link>
      <guid>https://dev.to/by_l_99c0b1b4256760ed5596/designing-a-browser-local-image-merge-pipeline-without-uploads-18ia</guid>
      <description>&lt;p&gt;Combining several images into one canvas looks like a server-side job, but modern browser APIs make a local-first pipeline practical. That choice matters when the source files are screenshots, receipts, product photos, or reference images that users may not want to upload to an unknown processing queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why keep the merge in the browser?
&lt;/h2&gt;

&lt;p&gt;A browser-local workflow has three useful properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy:&lt;/strong&gt; the selected files can stay on the user's device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast feedback:&lt;/strong&gt; decoding, layout changes, and previews do not wait for a round trip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictable cleanup:&lt;/strong&gt; closing or reloading the tab releases the in-memory working set instead of leaving a cloud project behind.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local processing does not remove every risk, but it removes the ordinary image-upload path from the core task.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical merge pipeline
&lt;/h2&gt;

&lt;p&gt;The core loop can be small and explicit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accept only the image types the interface documents.&lt;/li&gt;
&lt;li&gt;Decode each selected file with &lt;code&gt;createImageBitmap()&lt;/code&gt; or an &lt;code&gt;Image&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;Normalize orientation and calculate a destination rectangle for every item.&lt;/li&gt;
&lt;li&gt;Size one output canvas from the complete layout plan.&lt;/li&gt;
&lt;li&gt;Draw backgrounds, gaps, and images in a deterministic order.&lt;/li&gt;
&lt;li&gt;Export with &lt;code&gt;canvas.toBlob()&lt;/code&gt;, then verify that the blob is non-empty before enabling download.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decodeFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&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;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;for &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;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^image&lt;/span&gt;&lt;span class="se"&gt;\/(&lt;/span&gt;&lt;span class="sr"&gt;jpeg|png|webp&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unsupported image type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createImageBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;});&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;decoded&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 important design decision is to compute the whole layout before drawing. A horizontal merge sums destination widths and uses the tallest destination height. A vertical merge does the inverse. A grid also needs column widths, row heights, gaps, outer padding, and the chosen fit rule. Keeping those values in one plan makes the preview and final export use the same geometry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contain, cover, and alignment
&lt;/h2&gt;

&lt;p&gt;Users usually expect one of two fitting modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Contain&lt;/strong&gt; preserves the entire source image and may leave empty space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cover&lt;/strong&gt; fills the destination cell and crops the excess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alignment then decides where the image or crop sits: start, center, or end on each axis. These controls should change only the planned source and destination rectangles; the render loop can remain boring. Boring render code is easier to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails prevent browser crashes
&lt;/h2&gt;

&lt;p&gt;Canvas dimensions and memory usage can grow surprisingly fast. A 12,000 by 8,000 RGBA canvas needs hundreds of megabytes before export. Useful protections include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a documented file-count limit;&lt;/li&gt;
&lt;li&gt;a per-file byte limit;&lt;/li&gt;
&lt;li&gt;a maximum output edge and total pixel count;&lt;/li&gt;
&lt;li&gt;proportional downscaling when the planned output crosses those limits;&lt;/li&gt;
&lt;li&gt;closing decoded &lt;code&gt;ImageBitmap&lt;/code&gt; objects when they are removed;&lt;/li&gt;
&lt;li&gt;revoking temporary object URLs during cleanup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These limits are part of the product contract, not just implementation details. Show the user when scaling occurs and keep the originals untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification should use real files
&lt;/h2&gt;

&lt;p&gt;A merge tool deserves more than a button-click smoke test. Good fixtures use images with known colors and dimensions so the test can check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the exact output width and height;&lt;/li&gt;
&lt;li&gt;the pixel color at each placement boundary;&lt;/li&gt;
&lt;li&gt;gap and background colors;&lt;/li&gt;
&lt;li&gt;ordering after drag-and-drop;&lt;/li&gt;
&lt;li&gt;PNG, JPEG, and WebP signatures;&lt;/li&gt;
&lt;li&gt;behavior when a proposed canvas exceeds the safety limit;&lt;/li&gt;
&lt;li&gt;the absence of image-upload network requests during the core workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is also worth testing at a narrow viewport. File cards, reorder controls, export settings, and the preview should remain usable without horizontal overflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  A working reference
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://mergeimage.app/" rel="noopener noreferrer"&gt;MergeImage&lt;/a&gt; is a free browser-local implementation of this approach. It supports horizontal, vertical, and grid merging, plus screenshot stitching, comparison, overlay, image splitting, sprite sheets, and printable contact sheets. The source images are processed in the active browser tab, and the interface exposes the limits before export.&lt;/p&gt;

&lt;p&gt;The larger lesson is simple: plan first, render once, measure the actual output, and make privacy claims match the network behavior. Those choices turn a small canvas demo into a tool people can understand and verify.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>privacy</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Resize Images Locally in the Browser: A Privacy-First Workflow</title>
      <dc:creator>BY L</dc:creator>
      <pubDate>Thu, 06 Aug 2026 13:45:06 +0000</pubDate>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596/resize-images-locally-in-the-browser-a-privacy-first-workflow-187p</link>
      <guid>https://dev.to/by_l_99c0b1b4256760ed5596/resize-images-locally-in-the-browser-a-privacy-first-workflow-187p</guid>
      <description>&lt;p&gt;If an application portal asks for a photo at an exact pixel size or inside a narrow KB range, the easiest solution is not always an upload-and-process API. Modern browsers already provide the primitives needed to do the work locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local processing matters
&lt;/h2&gt;

&lt;p&gt;Photos and signatures can contain sensitive information. A client-side workflow keeps the source file on the user's device and removes the need to trust a temporary upload pipeline. It also reduces network latency and makes the tool usable on slower connections.&lt;/p&gt;

&lt;p&gt;The core steps are straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the selected file with createImageBitmap() or an Image element.&lt;/li&gt;
&lt;li&gt;Calculate target dimensions while preserving the aspect ratio.&lt;/li&gt;
&lt;li&gt;Draw the bitmap to a canvas.&lt;/li&gt;
&lt;li&gt;Export with canvas.toBlob().&lt;/li&gt;
&lt;li&gt;Measure the resulting blob size and iterate over quality when a KB target matters.&lt;/li&gt;
&lt;li&gt;Show a real preview before download.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resizeToWidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&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;bitmap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createImageBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&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;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;height&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9&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;h2&gt;
  
  
  Pixels and KB are different constraints
&lt;/h2&gt;

&lt;p&gt;Changing dimensions affects the number of pixels. Changing JPEG or WebP quality affects compression. When both constraints apply, solve dimensions first, then adjust output quality and measure the actual blob after every encode. Avoid claiming success from a quality percentage alone because visually similar files can compress very differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  A useful browser tool
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://increaseimages.org/" rel="noopener noreferrer"&gt;IncreaseImages&lt;/a&gt; follows this local-first model. It can increase image dimensions or work toward a target KB range for JPG, PNG, and WebP files. The selected image stays in the browser, and the user can preview and download the measured result.&lt;/p&gt;

&lt;p&gt;This approach is especially useful for application photos, scanned signatures, and other images where privacy, exact dimensions, and predictable file size matter more than server-side convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final checks before download
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify width and height in pixels.&lt;/li&gt;
&lt;li&gt;Verify the measured byte size, not an estimate.&lt;/li&gt;
&lt;li&gt;Inspect edges and text at 100% zoom.&lt;/li&gt;
&lt;li&gt;Keep the original file as a fallback.&lt;/li&gt;
&lt;li&gt;Recheck the destination portal's latest rules before submission.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Browser-local image processing is a small architectural choice that creates a noticeably calmer user experience: faster feedback, less data exposure, and results that can be checked before anything leaves the device.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>security</category>
    </item>
    <item>
      <title>A Privacy-First Architecture for a Browser-Based Rice Purity Test</title>
      <dc:creator>BY L</dc:creator>
      <pubDate>Thu, 06 Aug 2026 12:03:23 +0000</pubDate>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596/a-privacy-first-architecture-for-a-browser-based-rice-purity-test-4pg4</link>
      <guid>https://dev.to/by_l_99c0b1b4256760ed5596/a-privacy-first-architecture-for-a-browser-based-rice-purity-test-4pg4</guid>
      <description>&lt;p&gt;A sensitive questionnaire does not need a server-side answer store. For a recent browser project, I treated data minimization as a product requirement rather than a policy-page promise.&lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://testricepurity.org/" rel="noopener noreferrer"&gt;Test Rice Purity&lt;/a&gt;, an independent adults-only implementation of the familiar 100-question checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core design choices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local state:&lt;/strong&gt; checked items remain in the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent scoring:&lt;/strong&gt; the score is 100 minus the number of checked items.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No account gate:&lt;/strong&gt; users can complete the flow without creating a profile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear-data control:&lt;/strong&gt; progress can be removed on shared devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe result framing:&lt;/strong&gt; the score is for entertainment and self-reflection, not a clinical or moral judgment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sharing without leaking answers
&lt;/h2&gt;

&lt;p&gt;A share action should be explicit and should contain only the information the user chose to publish. The individual checklist answers are not needed for a result card or a friend challenge, so they should never be placed in an analytics event or a readable query string.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this improves
&lt;/h2&gt;

&lt;p&gt;Keeping the calculation local reduces the data surface, makes the privacy promise testable, and lets the interface be useful even without sign-in. It also forces clearer UX: users can see how their score works and can remove local progress at any time.&lt;/p&gt;

&lt;p&gt;You can review the live implementation at &lt;a href="https://testricepurity.org/" rel="noopener noreferrer"&gt;Test Rice Purity&lt;/a&gt;. It is not affiliated with Rice University.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Designing a Fair Browser-Local Word-Grid Practice Loop</title>
      <dc:creator>BY L</dc:creator>
      <pubDate>Thu, 06 Aug 2026 10:54:44 +0000</pubDate>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596/designing-a-fair-browser-local-word-grid-practice-loop-1gg1</link>
      <guid>https://dev.to/by_l_99c0b1b4256760ed5596/designing-a-fair-browser-local-word-grid-practice-loop-1gg1</guid>
      <description>&lt;p&gt;A good word-grid game is not only a dictionary wrapped around a square of letters. The interesting design work happens in the feedback loop: how a player scans the board, tests a path, understands a miss, and returns for another attempt without being pushed into an account or a leaderboard.&lt;/p&gt;

&lt;p&gt;I recently used &lt;a href="https://squaredle2.org/" rel="noopener noreferrer"&gt;Squaredle 2&lt;/a&gt; as a small case study for this kind of browser-first practice experience. It is an independent word-grid project with Daily, Express, and Unlimited modes, and it keeps progress in the browser instead of requiring a sign-up.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Make the path rule visible
&lt;/h2&gt;

&lt;p&gt;Adjacent-letter games usually allow eight directions while preventing one tile from being reused inside the same word. That sounds simple, but the interface has to explain it through behavior.&lt;/p&gt;

&lt;p&gt;A useful implementation keeps the selected path visible, rejects a repeated tile immediately, and gives the same result whether the player drags, taps, or types. The visible path is not decoration: it is the proof that the game understood the player's intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Separate target words from bonus words
&lt;/h2&gt;

&lt;p&gt;If every dictionary word counts equally, a board can feel arbitrary. A clearer model has a curated target list for progress and a broader bonus list for valid discoveries. Players then understand what completion means without being punished for finding a legitimate word that was not part of the main route.&lt;/p&gt;

&lt;p&gt;The important part is to label the distinction. A bonus should feel like recognition, not like a hidden exception.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Keep practice local and reversible
&lt;/h2&gt;

&lt;p&gt;For a lightweight puzzle, an account can create more friction than value. Local storage is enough for daily progress, streaks, mode history, and the current board. It also makes the privacy promise easy to explain: progress stays on the device and can be cleared by the player.&lt;/p&gt;

&lt;p&gt;Local persistence needs a versioned key and defensive parsing. A corrupted value should reset gracefully rather than breaking the game.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Design multiple time horizons
&lt;/h2&gt;

&lt;p&gt;One daily board builds a habit, but it is not the only useful session shape.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Daily&lt;/strong&gt; gives everyone the same board.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express&lt;/strong&gt; lowers the time commitment with a smaller grid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unlimited&lt;/strong&gt; lets a motivated player keep practicing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These modes share the same interaction model while serving different moments. That is more coherent than adding unrelated mini-games just to increase session time.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Treat accessibility as an input model
&lt;/h2&gt;

&lt;p&gt;Pointer dragging alone is fragile. A keyboard route, readable focus states, descriptive board labels, and large touch targets make the core mechanic usable across devices. Rotation and fullscreen controls should preserve the logical board state instead of restarting it.&lt;/p&gt;

&lt;p&gt;The best accessibility work also improves debugging because every state transition becomes explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Give hints that teach scanning
&lt;/h2&gt;

&lt;p&gt;A complete answer reveal ends the search. A better hint can expose the starting letter, word length, or one early path segment. The player still performs the recognition work, so the hint becomes part of the learning loop.&lt;/p&gt;

&lt;p&gt;That principle generalizes beyond games: good assistance reduces uncertainty without removing the meaningful action.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical checklist
&lt;/h2&gt;

&lt;p&gt;Before publishing a word-grid experience, I would test:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;repeated-tile rejection;&lt;/li&gt;
&lt;li&gt;diagonal and edge paths;&lt;/li&gt;
&lt;li&gt;pointer, touch, and keyboard parity;&lt;/li&gt;
&lt;li&gt;persistence after refresh;&lt;/li&gt;
&lt;li&gt;deterministic daily selection;&lt;/li&gt;
&lt;li&gt;small-screen layout;&lt;/li&gt;
&lt;li&gt;clear target-versus-bonus feedback;&lt;/li&gt;
&lt;li&gt;an honest independent-product disclosure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A small browser game can be technically modest and still be thoughtfully designed. The durable value comes from making every rule visible, every saved state reversible, and every next attempt easy to understand.&lt;/p&gt;

</description>
      <category>design</category>
      <category>frontend</category>
      <category>gamedev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Reverse Word Puzzles: Why Preserving Options Beats Maximum Information</title>
      <dc:creator>BY L</dc:creator>
      <pubDate>Thu, 06 Aug 2026 08:56:05 +0000</pubDate>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596/reverse-word-puzzles-why-preserving-options-beats-maximum-information-k0f</link>
      <guid>https://dev.to/by_l_99c0b1b4256760ed5596/reverse-word-puzzles-why-preserving-options-beats-maximum-information-k0f</guid>
      <description>&lt;p&gt;Reverse word puzzles use a familiar five-letter format but invert the objective: the hidden word is the one answer you must avoid. That change turns ordinary deduction into a problem of option preservation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the usual strategy breaks
&lt;/h2&gt;

&lt;p&gt;In a standard word puzzle, a strong guess removes as many candidates as possible. In a reverse game, removing too many candidates can be risky. If only one legal word remains and it is the hidden answer, the run is over.&lt;/p&gt;

&lt;p&gt;A better evaluation asks two questions at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much information does this guess reveal?&lt;/li&gt;
&lt;li&gt;How many safe continuations will still exist afterward?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best move often keeps several branches alive, even when another word would reveal more information immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three useful tactics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Separate similar endings
&lt;/h3&gt;

&lt;p&gt;When several candidates share a suffix, choose a probe that tests their distinguishing letters in new positions. The goal is to split the set without committing to the obvious completion.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Treat repeated letters as information
&lt;/h3&gt;

&lt;p&gt;A repeated letter can look wasteful in an ordinary opener, but it may be valuable when one-versus-two occurrences determine whether multiple escape words remain.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Delay forced patterns
&lt;/h3&gt;

&lt;p&gt;If a guess would lock four positions and leave a single legal completion, it may be strategically worse than a less direct word that preserves two or three paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  A browser tool for practicing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dontwordle.app/" rel="noopener noreferrer"&gt;Dont Wordle&lt;/a&gt; is a free reverse word puzzle with a daily challenge, unlimited games, an archive, a solver, and strategy references. It runs directly in the browser and makes it easy to compare how different guesses reshape the candidate set.&lt;/p&gt;

&lt;p&gt;The solver is useful for analysis because it exposes the decision tree behind a move. You can test whether a guess merely gains information or actually leaves a healthier set of safe next turns.&lt;/p&gt;

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

&lt;p&gt;Reverse word games are small planning systems. They reward players who resist the most obvious answer, track branching possibilities, and think one move beyond the current clue pattern. The key habit is simple: do not optimize only for certainty; optimize for the quality of the choices that remain.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>gamedev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>A privacy-first way to explore love language results in the browser</title>
      <dc:creator>BY L</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:53:38 +0000</pubDate>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596/a-privacy-first-way-to-explore-love-language-results-in-the-browser-4bhl</link>
      <guid>https://dev.to/by_l_99c0b1b4256760ed5596/a-privacy-first-way-to-explore-love-language-results-in-the-browser-4bhl</guid>
      <description>&lt;h1&gt;
  
  
  A privacy-first way to explore love language results in the browser
&lt;/h1&gt;

&lt;p&gt;Reflection tools can be useful, but the questions and results may feel personal. That makes the storage model part of the product experience, not just an implementation detail.&lt;/p&gt;

&lt;p&gt;I built KindredCue as a small, browser-local love language test. It is free to use and does not require an account or email address. The questions help people notice how they tend to express and receive care, while the result is kept on the device instead of being turned into another online profile.&lt;/p&gt;

&lt;p&gt;The local-first approach also changes the conversation around sharing. Someone can take the test privately, decide what the result means to them, and then choose whether to discuss it with a partner, friend, or family member. The tool is a prompt for reflection, not a diagnosis or a label.&lt;/p&gt;

&lt;p&gt;For a simple example of this pattern, try the &lt;a href="https://lovelanguagetest.app/" rel="noopener noreferrer"&gt;KindredCue love language test&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>privacy</category>
    </item>
    <item>
      <title>Building a Browser-Local Reverse Wordle Game</title>
      <dc:creator>BY L</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:57:20 +0000</pubDate>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596/building-a-browser-local-reverse-wordle-game-9de</link>
      <guid>https://dev.to/by_l_99c0b1b4256760ed5596/building-a-browser-local-reverse-wordle-game-9de</guid>
      <description>&lt;p&gt;Most word games reward a guess that gets closer to the answer. I wanted to explore the opposite constraint: what if solving too early is the mistake?&lt;/p&gt;

&lt;p&gt;That idea became &lt;strong&gt;Dont Wordle&lt;/strong&gt;, a small browser-local reverse Wordle puzzle. The goal is not to guess the hidden word as quickly as possible. Each row must remain a legal guess while the player keeps enough options open to finish the board.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting constraint
&lt;/h2&gt;

&lt;p&gt;The game has three pieces of state that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the current board and its legal guesses&lt;/li&gt;
&lt;li&gt;the remaining candidate paths&lt;/li&gt;
&lt;li&gt;the player’s saved progress for the daily challenge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping that state in the browser makes the core loop fast and private. A player can open the page, play without an account, undo a move, and return later without a server session.&lt;/p&gt;

&lt;p&gt;The design also changes the feedback loop. In a normal Wordle-style game, green letters feel like progress. In a reverse puzzle, too much information can close the remaining paths. The useful question after every row is: “Did this guess preserve a route to the end?”&lt;/p&gt;

&lt;h2&gt;
  
  
  Small UX details carry the game
&lt;/h2&gt;

&lt;p&gt;A reverse rule is easy to explain but easy to misunderstand in play, so the interface needs to make the constraint visible. I added a legal-guess counter, an undo action, a daily challenge state, and clear feedback when a move would make the board unsolvable.&lt;/p&gt;

&lt;p&gt;Those details are more important than adding a large feature list. The player should be able to understand why a guess failed and try another line immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why browser-local was a good fit
&lt;/h2&gt;

&lt;p&gt;This kind of game does not need an account system for its first version. Local persistence keeps the product lightweight, avoids collecting unnecessary player data, and makes the daily loop work on a phone or a desktop browser.&lt;/p&gt;

&lt;p&gt;The result is available as &lt;a href="https://dontwordle.app/" rel="noopener noreferrer"&gt;Dont Wordle&lt;/a&gt;, a free reverse word puzzle you can try directly in the browser.&lt;/p&gt;

&lt;p&gt;The broader lesson for small web games is that a single changed rule can create a different strategy space. The engineering challenge is then to make that rule legible through state, feedback, and a very short path from opening the page to making the first move.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Using HSB sliders to make a playful color-memory game</title>
      <dc:creator>BY L</dc:creator>
      <pubDate>Wed, 05 Aug 2026 06:21:18 +0000</pubDate>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596/using-hsb-sliders-to-make-a-playful-color-memory-game-1mla</link>
      <guid>https://dev.to/by_l_99c0b1b4256760ed5596/using-hsb-sliders-to-make-a-playful-color-memory-game-1mla</guid>
      <description>&lt;p&gt;I built Toon Tone, a free cartoon color guessing game for practicing color memory in a lightweight browser experience. The game uses HSB sliders so hue, saturation, and brightness are easy to reason about while matching a character color.&lt;/p&gt;

&lt;p&gt;The project is intentionally small and shareable: it includes daily prompts, instant feedback, and shareable scores without requiring an account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://toontone.page/" rel="noopener noreferrer"&gt;Play Toon Tone&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would love feedback on the interaction design and on whether HSB controls feel more intuitive than RGB inputs for this kind of warm-up game.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Designing a Browser-Local Eight-Board Word Puzzle</title>
      <dc:creator>BY L</dc:creator>
      <pubDate>Mon, 03 Aug 2026 17:06:47 +0000</pubDate>
      <link>https://dev.to/by_l_99c0b1b4256760ed5596/designing-a-browser-local-eight-board-word-puzzle-aco</link>
      <guid>https://dev.to/by_l_99c0b1b4256760ed5596/designing-a-browser-local-eight-board-word-puzzle-aco</guid>
      <description>&lt;h2&gt;
  
  
  Why an eight-board puzzle needs a different rhythm
&lt;/h2&gt;

&lt;p&gt;Most five-letter word games ask you to optimize one board. An eight-board variant turns every guess into a small information-sharing system: the same word is tested against eight answers, and the feedback arrives in parallel. That changes the best strategy from “find the answer” to “reduce uncertainty across the grid.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://octordlepro.com/" rel="noopener noreferrer"&gt;Octordle Pro&lt;/a&gt; is a free browser-local implementation of that format. It offers Daily and Unlimited modes, keeps progress and statistics locally, and includes shareable challenge links. The product is useful as a small example of how a web game can stay interactive without requiring an account or a paid API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data problem is the game
&lt;/h2&gt;

&lt;p&gt;Each board can be represented as a pattern, a set of excluded letters, and a set of misplaced letters. A strong opening word maximizes coverage across those sets. Once one board becomes constrained, the next guess should be chosen by its expected value for the other seven, not just by how close it looks on the first board.&lt;/p&gt;

&lt;p&gt;That creates three practical rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with distinct, common letters so the first result teaches you something on many boards.&lt;/li&gt;
&lt;li&gt;Keep confirmed and excluded letters separate; a yellow letter is evidence about position, not permission to reuse it anywhere.&lt;/li&gt;
&lt;li&gt;Use a probe guess when it can eliminate several candidate families at once, even if it is unlikely to solve a board immediately.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why browser-local matters
&lt;/h2&gt;

&lt;p&gt;A browser-local game can keep statistics and preferences in local storage, load quickly, and avoid sending a player's guesses to a server. The trade-off is that progress is device-specific, so a clear shareable challenge URL is useful when you want to compare a round with someone else.&lt;/p&gt;

&lt;p&gt;The most satisfying rounds come from treating the eight boards as one system. Broad coverage early, careful bookkeeping in the middle, and focused cleanup at the end make a large grid feel manageable.&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
  </channel>
</rss>
