<?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: hinezi</title>
    <description>The latest articles on DEV Community by hinezi (@playonemore).</description>
    <link>https://dev.to/playonemore</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%2F4220378%2F4ebbe04b-61b8-4904-9122-26e9ddb9d680.png</url>
      <title>DEV Community: hinezi</title>
      <link>https://dev.to/playonemore</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/playonemore"/>
    <language>en</language>
    <item>
      <title>I built a collaborative pixel art game in a bottle — with 0 npm dependencies</title>
      <dc:creator>hinezi</dc:creator>
      <pubDate>Fri, 06 Nov 2026 05:54:14 +0000</pubDate>
      <link>https://dev.to/playonemore/i-built-a-collaborative-pixel-art-game-in-a-bottle-with-0-npm-dependencies-3hk7</link>
      <guid>https://dev.to/playonemore/i-built-a-collaborative-pixel-art-game-in-a-bottle-with-0-npm-dependencies-3hk7</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyuzw0pbd5eqak2pl9mxb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyuzw0pbd5eqak2pl9mxb.png" alt="Dear Stranger collaborative pixel art card" width="528" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🌊 What is Dear Stranger?
&lt;/h2&gt;

&lt;p&gt;I recently launched &lt;strong&gt;&lt;a href="https://dearst.app" rel="noopener noreferrer"&gt;Dear Stranger (dearst.app)&lt;/a&gt;&lt;/strong&gt; — a cozy, anonymous, collaborative pixel art web game.&lt;/p&gt;

&lt;p&gt;The concept is inspired by the old tradition of casting a message in a bottle into the ocean:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cast a bottle&lt;/strong&gt;: You start by drawing a few pixel strokes on a small canvas (or choose from 8 classic masterpieces like Van Gogh's &lt;em&gt;Starry Night&lt;/em&gt; or Monet).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drift across the sea&lt;/strong&gt;: Your bottle travels across the world to strangers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborate&lt;/strong&gt;: 2 to 4 strangers take turns painting their fair share.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Returns home&lt;/strong&gt;: Once complete, the bottle journeys back to everyone who participated, complete with a celebratory replay of each painter's strokes and a travel map showing the countries it sailed through!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Play it directly in your browser (no sign-up required):&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://dearst.app" rel="noopener noreferrer"&gt;https://dearst.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ The Tech Stack: Exactly 0 npm dependencies
&lt;/h2&gt;

&lt;p&gt;As web developers in 2026, our &lt;code&gt;node_modules&lt;/code&gt; folders often weigh hundreds of megabytes just to render a button. For &lt;em&gt;Dear Stranger&lt;/em&gt;, I set a personal challenge:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Can we build a full-featured, persistent, real-time multiplayer collaborative web app with ZERO external npm dependencies?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is &lt;strong&gt;yes&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Pure Vanilla JS on the Frontend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero build tools&lt;/strong&gt;: No Webpack, Vite, or Babel. Browsers have supported native ES modules for years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTML5 Canvas &amp;amp; SVG&lt;/strong&gt;: Custom pixel manipulation, touch-enabled zooming/panning, and color palette sampling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Voyage Map&lt;/strong&gt;: Natural Earth 1:50m geographic landmass rasterized into an ultra-compact dot grid rendered with pure SVG paths and beacons.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Node.js Built-in Standard Library on the Backend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Built on Node's native HTTP server and &lt;code&gt;node:crypto&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pure Web Push&lt;/strong&gt;: RFC 8291 payload encryption and RFC 8292 VAPID signatures implemented purely with &lt;code&gt;node:crypto&lt;/code&gt; — no &lt;code&gt;web-push&lt;/code&gt; npm package required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite with WAL mode&lt;/strong&gt;: Fast, zero-config persistent storage with foreign keys and immediate transactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Internationalization (i18n) from Day One
&lt;/h3&gt;

&lt;p&gt;The game natively supports &lt;strong&gt;5 languages&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🇰🇷 Korean&lt;/li&gt;
&lt;li&gt;🇺🇸 English&lt;/li&gt;
&lt;li&gt;🇫🇷 French&lt;/li&gt;
&lt;li&gt;🇯🇵 Japanese&lt;/li&gt;
&lt;li&gt;🇨🇳 Chinese&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even the system NPC (&lt;strong&gt;Lighthouse · Keeper&lt;/strong&gt;) and world country labels dynamically localize using the native &lt;code&gt;Intl.DisplayNames&lt;/code&gt; API!&lt;/p&gt;




&lt;h2&gt;
  
  
  🎨 Game Design: Solving the "Trolling &amp;amp; Slacking" Problem
&lt;/h2&gt;

&lt;p&gt;Collaborative art on the internet is notorious for two problems: &lt;strong&gt;trolls overwriting others' work&lt;/strong&gt; and &lt;strong&gt;slackers drawing 1 pixel and dumping the rest on the next person&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is how Dear Stranger solves it through game mechanics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The 75% Fair Share Quota&lt;/strong&gt;:
Each painter receives an equal allocation of the remaining blank cells. You &lt;em&gt;must&lt;/em&gt; fill at least 75% of your quota before the game lets you send the bottle onward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Overwriting&lt;/strong&gt;:
Pixels already laid down by earlier strangers are immutable and locked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report &amp;amp; Restoration (Kintsugi for pixels)&lt;/strong&gt;:
If someone draws something offensive, any player can report that layer. The offender's layer is immediately hidden, and a blank space is created so &lt;em&gt;another stranger can step in to restore the painting&lt;/em&gt;!&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🖼️ What's next?
&lt;/h2&gt;

&lt;p&gt;We launched with &lt;strong&gt;8 classic museum masterpieces&lt;/strong&gt; in both guided tracing mode and 64-pixel challenge mode. We plan to continuously add more classical artworks and community features.&lt;/p&gt;

&lt;p&gt;Feel free to pick up a bottle, paint a few pixels, and see where your art travels:&lt;/p&gt;

&lt;p&gt;🌊 &lt;strong&gt;Play the game&lt;/strong&gt;: &lt;a href="https://dearst.app" rel="noopener noreferrer"&gt;https://dearst.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’d love to hear your thoughts, feedback, and questions in the comments below!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Building a K-pop ticketing game — and fixing a one-point scoring bug</title>
      <dc:creator>hinezi</dc:creator>
      <pubDate>Tue, 03 Nov 2026 03:02:02 +0000</pubDate>
      <link>https://dev.to/playonemore/building-a-k-pop-ticketing-game-and-fixing-a-one-point-scoring-bug-32h9</link>
      <guid>https://dev.to/playonemore/building-a-k-pop-ticketing-game-and-fixing-a-one-point-scoring-bug-32h9</guid>
      <description>&lt;p&gt;I built TicketKST, a browser game that recreates the pressure of booking K-pop concert tickets.&lt;/p&gt;

&lt;p&gt;Players wait for the sale to open, click into a queue, type a captcha, and then have 30 seconds to select seats. Requests include adjacent seats, aisle seats, and seats within a budget. Other simulated fans keep buying seats while you choose.&lt;/p&gt;

&lt;p&gt;The game uses a fictional venue and does not book real tickets.&lt;/p&gt;

&lt;p&gt;You can try it here: &lt;a href="https://ticketkst.com" rel="noopener noreferrer"&gt;https://ticketkst.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The frontend is vanilla JavaScript, HTML, and CSS. The backend uses Flask and SQLite.&lt;/p&gt;

&lt;p&gt;One surprisingly small implementation detail caused the score shown in the browser to disagree with the leaderboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript and Python don't always round the same way
&lt;/h2&gt;

&lt;p&gt;Each completed seat request earns points based on seat quality and a combo multiplier.&lt;/p&gt;

&lt;p&gt;Consider a request worth 645 base points with a 2.5× multiplier:&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="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="mi"&gt;645&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// 1613&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original Python calculation used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;645&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1612
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python's built-in &lt;code&gt;round()&lt;/code&gt; uses ties-to-even rounding, while JavaScript's &lt;code&gt;Math.round()&lt;/code&gt; resolves this positive halfway value upward.&lt;/p&gt;

&lt;p&gt;Both calculations looked reasonable on their own. Together, they produced a one-point discrepancy.&lt;/p&gt;

&lt;p&gt;For the game's nonnegative point values, the server now uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;round_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result screen also uses the server's saved score once the response arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recalculating a score isn't enough
&lt;/h2&gt;

&lt;p&gt;Another issue was what the server trusted.&lt;/p&gt;

&lt;p&gt;Recalculating a total from client-reported success counts and seat-quality values still lets the client invent those inputs.&lt;/p&gt;

&lt;p&gt;The updated version sends seat actions and their timestamps instead. The server reconstructs the daily hall and checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether the selected seats were available&lt;/li&gt;
&lt;li&gt;Whether they matched the current request&lt;/li&gt;
&lt;li&gt;Whether adjacent seats were actually together&lt;/li&gt;
&lt;li&gt;How scheduled sell-outs affected availability&lt;/li&gt;
&lt;li&gt;What score and combo followed from those actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This validates the game state. It does not prove that a human made every move or eliminate automated play.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing both implementations together
&lt;/h2&gt;

&lt;p&gt;The daily challenge gives everyone the same initial hall and requests for the same Korean date.&lt;/p&gt;

&lt;p&gt;That makes consistency between the JavaScript simulation and Python replay important.&lt;/p&gt;

&lt;p&gt;An offline test harness runs the actual JavaScript controller with a lightweight DOM and clock test double. It generates action transcripts, which are then replayed by Python.&lt;/p&gt;

&lt;p&gt;Across 45 test rounds, the checks compare the initial hall, request sequence, outcomes, score, seat count, and best combo. The scenarios include delayed frames and the game's three surprise events.&lt;/p&gt;

&lt;p&gt;These tests helped check the boundary between the two implementations, rather than testing each scoring function in isolation.&lt;/p&gt;

&lt;p&gt;I'd be interested to hear how other developers handle this: do you replay browser-game actions on the server, share simulation code between environments, or make the server authoritative for every move?&lt;/p&gt;

&lt;p&gt;Disclosure: AI tools assisted with implementation and drafting this article.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>javascript</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a daily puzzle from Wikipedia's pageview data (and rebuilt it when the results took too long)</title>
      <dc:creator>hinezi</dc:creator>
      <pubDate>Sun, 01 Nov 2026 10:17:00 +0000</pubDate>
      <link>https://dev.to/playonemore/i-built-a-daily-puzzle-from-wikipedias-pageview-data-and-rebuilt-it-when-the-results-took-too-13f4</link>
      <guid>https://dev.to/playonemore/i-built-a-daily-puzzle-from-wikipedias-pageview-data-and-rebuilt-it-when-the-results-took-too-13f4</guid>
      <description>&lt;p&gt;Every day, millions of people read English Wikipedia, and Wikimedia publishes how many times each article was viewed. I turned that into a small daily game called &lt;strong&gt;&lt;a href="https://fomorrow.app" rel="noopener noreferrer"&gt;Fomorrow&lt;/a&gt;&lt;/strong&gt;: five quick questions about what the internet read yesterday.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which of these four articles had the most readers yesterday?&lt;br&gt;
Which one rose the most against its own 7-day average?&lt;br&gt;
How many people read "Halloween" yesterday?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You answer, and the real numbers show up right away. A round takes about two minutes. There's also one daily "call": a prediction about how an article does &lt;em&gt;today&lt;/em&gt;, revealed with the next day's puzzle.&lt;/p&gt;

&lt;p&gt;This post covers how it works, the mistake that forced me to rebuild the whole game, and a few things I learned about generating fair questions from real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data
&lt;/h2&gt;

&lt;p&gt;Everything comes from the &lt;a href="https://doc.wikimedia.org/generated-data-platform/aqs/analytics-api/" rel="noopener noreferrer"&gt;Wikimedia Analytics API&lt;/a&gt;. Two endpoints do almost all the work.&lt;/p&gt;

&lt;p&gt;Daily views for one article:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia.org/all-access/user/Halloween/daily/20261024/20261031
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most-read articles for a day:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://wikimedia.org/api/rest_v1/metrics/pageviews/top/en.wikipedia.org/all-access/2026/10/31
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;user&lt;/code&gt; agent filter leaves out automated traffic, so the counts are (mostly) real readers.&lt;/p&gt;

&lt;p&gt;The detail that ended up shaping the whole game is &lt;strong&gt;timing&lt;/strong&gt;. A day's per-article numbers appear a few hours after that UTC day ends. When I checked, Oct 31 was available at about 05:05 UTC on Nov 1. The top list shows up later than the per-article data. There is no hourly per-article endpoint in the REST API. Hourly data exists only as raw dump files of about 40 MB per hour, published roughly three hours late.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 1: predict tomorrow (and wait)
&lt;/h2&gt;

&lt;p&gt;The first version was a forecasting game. Every day you got three questions about &lt;strong&gt;tomorrow's&lt;/strong&gt; pageviews: which topic will rise the most, which of two will get more readers, will this article beat its average.&lt;/p&gt;

&lt;p&gt;It worked technically, but it had a fatal flaw: &lt;strong&gt;you waited 1.5 to 2 days for every result.&lt;/strong&gt; A pick made on day D could only be scored after day D+1 ended &lt;em&gt;and&lt;/em&gt; Wikimedia published it. First-time visitors saw no result at all. People forgot what they had picked. The share button could only say "my picks are locked in", which nobody wants to click.&lt;/p&gt;

&lt;p&gt;I tried to fix it inside the forecasting model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Predict today, close picks at 12:00 UTC.&lt;/strong&gt; Results the next morning, but half the world would find the game closed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use hourly data for a 6-hour window.&lt;/strong&gt; No hourly per-article API, so it means parsing 40 MB dumps every hour, and you still wait most of a day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A personal 6-hour window from each pick.&lt;/strong&gt; Fast results, but every player gets a different answer to the same question, which breaks the "same puzzle for everyone" feel that daily games depend on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I looked at what the daily games people actually play have in common: &lt;strong&gt;an instant result, one shared puzzle per day, a spoiler-free share grid, and a streak.&lt;/strong&gt; My game had none of the first one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 2: yesterday's puzzle, today's call
&lt;/h2&gt;

&lt;p&gt;So I flipped it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The main game is about yesterday.&lt;/strong&gt; The data is already published, so every answer is instant. A new puzzle opens at 06:00 UTC, after Wikimedia publishes the previous day, and stays the same for everyone for 24 hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The prediction survives as one daily "call" about today.&lt;/strong&gt; It is always open, closes at midnight UTC, and its answer arrives with the next puzzle. That gives you a reason to come back tomorrow without making you wait to play today.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The previous version still settles its old picks, and its code lives on a git tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating questions that are actually fair
&lt;/h2&gt;

&lt;p&gt;No LLM is involved. A rule-based generator builds the puzzle each day from two sources: articles from the previous day's most-read list (timely and often surprising) and a curated catalog of well-known articles (so estimation questions are about names people recognize).&lt;/p&gt;

&lt;p&gt;The hard part is not building &lt;em&gt;a&lt;/em&gt; question. It is building one that isn't trivially obvious or completely random. Some rules that made the biggest difference:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep head-to-heads close.&lt;/strong&gt; A random pair is usually a blowout: one article has 10x the views of the other. I only pair articles whose counts are 1.02 to 1.3x apart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Most read" needs a real rival.&lt;/strong&gt; One of the three alternatives must have at least 55% of the winner's views, so it's never "obviously the famous one".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trend battles compare growth, not size.&lt;/strong&gt; "Which rose the most against its own 7-day average?" lets a small article beat a giant. I limit the size spread between the four candidates and prefer races where the runner-up was close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ranges use human numbers.&lt;/strong&gt; Answer bands are cut at roughly 80%, 100% and 125% of the article's weekly average, rounded to two significant figures:&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="c1"&gt;// 12,345 → 12,000; 8,764 → 8,800&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&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;max&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&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;log10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&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;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;p&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;Bands where the real number sits right on a cut are skipped, so nobody loses on a rounding hair.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filter what shouldn't be a game.&lt;/strong&gt; The most-read list is full of things that don't belong in a casual puzzle: adult sites, "Deaths in 2026", shootings, disasters. A title filter drops those, along with lists, non-article namespaces and very long titles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't repeat yourself.&lt;/strong&gt; An article used in a puzzle or call can't come back for a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same puzzle for everyone.&lt;/strong&gt; The random generator is seeded with the date, so the puzzle is deterministic. It can be rebuilt identically, tested against real fixture data, and is the same no matter which request triggers the build.&lt;/p&gt;

&lt;p&gt;Some real answers from Oct 31: &lt;em&gt;Halloween&lt;/em&gt; drew 202,666 views (+485% against its usual week), &lt;em&gt;Trick-or-treating&lt;/em&gt; jumped +584%, and &lt;em&gt;Day of the Dead&lt;/em&gt; reached 38,440.&lt;/p&gt;

&lt;h2&gt;
  
  
  The daily call has its own timing problem
&lt;/h2&gt;

&lt;p&gt;The call about day D opens at 00:00 UTC on D. At that moment, D-1 isn't published yet, so the call can only use data through D-2. Its "7-day average" is D-8 to D-2. Calls rotate through four formats (beat the average, head-to-head, range, trend battle) so consecutive days feel different, and they fall back to the simplest format if the day's data can't support the planned one.&lt;/p&gt;

&lt;p&gt;There's also an honest weakness worth admitting: since the puzzle is about yesterday, you &lt;em&gt;could&lt;/em&gt; look the answers up. For a casual game that's fine. It's also exactly why any future leaderboard will be built on the calls, which are about a day that hasn't finished yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;Deliberately boring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt; (App Router) + React&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; via Node's built-in &lt;code&gt;node:sqlite&lt;/code&gt;, on one small Google Cloud VM behind Caddy&lt;/li&gt;
&lt;li&gt;An hourly collector job that fetches data, settles calls and builds the next puzzle as soon as the data appears&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt;: typecheck, tests, build, then a locked-down SSH deploy&lt;/li&gt;
&lt;li&gt;Verified daily backups copied to Cloud Storage&lt;/li&gt;
&lt;li&gt;No account needed. Optional Google sign-in syncs your streak across devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tests include building a full puzzle from a real day's data saved as a fixture, which caught more generator bugs than anything else.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://fomorrow.app" rel="noopener noreferrer"&gt;fomorrow.app&lt;/a&gt;&lt;/strong&gt;: free, no sign-up, new puzzle every day at 06:00 UTC, and every past puzzle is in the archive.&lt;/p&gt;

&lt;p&gt;It's brand new, so I'd love feedback: which questions felt too easy or too random, what was confusing in your first minute, and whether you'd come back tomorrow.&lt;/p&gt;

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