<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Shaishav Patel</title>
    <description>The latest articles on Forem by Shaishav Patel (@shaishav_patel_271fdcd61a).</description>
    <link>https://forem.com/shaishav_patel_271fdcd61a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3765357%2F4328a95f-6619-4f38-80e1-8b0865eb2470.jpg</url>
      <title>Forem: Shaishav Patel</title>
      <link>https://forem.com/shaishav_patel_271fdcd61a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/shaishav_patel_271fdcd61a"/>
    <language>en</language>
    <item>
      <title>How to Actually Improve Your Typing Speed — 5 Techniques That Work</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Tue, 21 Apr 2026 15:18:27 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/how-to-actually-improve-your-typing-speed-5-techniques-that-work-2p1i</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/how-to-actually-improve-your-typing-speed-5-techniques-that-work-2p1i</guid>
      <description>&lt;p&gt;Most typing advice is the same: "practice more." That's true, but it's also useless without knowing &lt;em&gt;what&lt;/em&gt; to practice and &lt;em&gt;how&lt;/em&gt; to practice it.&lt;/p&gt;

&lt;p&gt;Here are five techniques that produce measurable improvement — with the reasoning behind each.&lt;/p&gt;

&lt;p&gt;Measure your current speed first: &lt;a href="https://ultimatetools.io/tools/fun-tools/typing-speed-test/" rel="noopener noreferrer"&gt;Free Typing Speed Test — WPM + Accuracy&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Fix Your Home Row Position First
&lt;/h2&gt;

&lt;p&gt;The most common reason people plateau at 40–50 WPM: they don't use home row consistently.&lt;/p&gt;

&lt;p&gt;Home row (ASDF for the left hand, JKL; for the right) is the default resting position. Every key on the keyboard is one or two movements from home row. The F and J keys have raised bumps so you can find home row without looking.&lt;/p&gt;

&lt;p&gt;If you're resting your hands differently — or hovering them without a consistent anchor — every keystroke requires more visual and motor effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Before your next practice session, find home row by feel. Type a few sentences without looking down. Return your fingers to home row after every key. This single change improves both accuracy and eventual speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Slow Down to Speed Up
&lt;/h2&gt;

&lt;p&gt;This sounds wrong. It's the most important principle in typing improvement.&lt;/p&gt;

&lt;p&gt;When you type at the edge of your current speed, you make errors. Errors interrupt rhythm, force corrections, and — critically — &lt;strong&gt;reinforce the wrong finger movement.&lt;/strong&gt; Every time you mistype a letter and fix it, you've practiced the wrong motion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The technique:&lt;/strong&gt; drop to 70–80% of your comfortable speed. Type at the pace where you make almost no errors. Hold that speed for entire practice sessions.&lt;/p&gt;

&lt;p&gt;Speed increases as accuracy becomes automatic. You cannot outrun bad habits by going faster — you can only outrun them by practicing the correct motion so many times it becomes default.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Target Your Problem Keys
&lt;/h2&gt;

&lt;p&gt;Every typist has specific keys they consistently mistype. For most people it's the number row, the brackets, or letters typed with the weaker fingers (Q, Z, X on the left hand; P, semicolon on the right).&lt;/p&gt;

&lt;p&gt;Identify yours: take a typing test and notice which letters are causing hesitation or errors. Then practice those specifically — not general text, but focused repetition on the letters that slow you down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to do it:&lt;/strong&gt; Type words that heavily use your problem letters. If Q and P are weak, find passages heavy in those letters. Ten minutes of targeted practice on weak keys outperforms an hour of general text on comfortable keys.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Train Muscle Memory With Common Word Patterns
&lt;/h2&gt;

&lt;p&gt;English text is not random. A small set of words — "the," "and," "that," "this," "with," "have," "from" — appears constantly. When your fingers can type these common words without conscious thought, your overall speed increases significantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The technique:&lt;/strong&gt; practice common words in isolation until each one flows as a single motion, not a sequence of individual keystrokes. "the" typed consciously is three movements. "the" typed from muscle memory is one fluid motion. The difference compounds across thousands of words.&lt;/p&gt;

&lt;p&gt;Typing tests like the one at &lt;a href="https://ultimatetools.io/tools/fun-tools/typing-speed-test/" rel="noopener noreferrer"&gt;Ultimate Tools&lt;/a&gt; use common English word lists — practicing with them trains exactly the patterns that appear in real text.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Measure Accuracy, Not Just WPM
&lt;/h2&gt;

&lt;p&gt;WPM is the metric everyone tracks. It's also the metric most likely to mislead.&lt;/p&gt;

&lt;p&gt;A test result of 70 WPM with 95% accuracy is genuinely fast. A result of 80 WPM with 88% accuracy means roughly one error every 8–9 words — a pace that requires constant backspacing in real writing and actually slows your effective output below 70 WPM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Track both numbers.&lt;/strong&gt; If accuracy is below 95%, it's the constraint — not speed. Slow down until accuracy is consistently above 95%, then let speed increase naturally.&lt;/p&gt;

&lt;p&gt;Most typists who "can't get faster" are actually accuracy-limited: they're making errors that mask their actual potential pace.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Realistic Improvement Timeline Looks Like
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First 2 weeks:&lt;/strong&gt; Fixing home row and slowing down typically moves most people from 45 WPM to 55–60 WPM with better accuracy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1–2 months&lt;/strong&gt; of consistent daily practice (15–20 minutes): 60 WPM → 75–85 WPM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6 months+:&lt;/strong&gt; 90–100+ WPM is achievable for most adults with deliberate practice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key word is deliberate. Typing more emails doesn't improve typing speed — it reinforces existing habits. Structured practice with feedback improves typing speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measure Your Progress
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ultimatetools.io/tools/fun-tools/typing-speed-test/" rel="noopener noreferrer"&gt;Typing Speed Test at Ultimate Tools&lt;/a&gt; — free, browser-based, no account. Measures WPM and accuracy on each test. Run it before and after focused practice sessions to track progress.&lt;/p&gt;

&lt;p&gt;Aim for: accuracy above 95% before chasing higher WPM. Once accuracy is consistent, speed follows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/typing-speed-test/" rel="noopener noreferrer"&gt;What Is WPM and What's a Good Score?&lt;/a&gt; — typing speed benchmarks by age and profession&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/text-tools/word-counter/" rel="noopener noreferrer"&gt;Word Counter&lt;/a&gt; — count words and reading time in any text&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productivity</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Solitaire Strategy Guide — How to Win More Games of Klondike</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Tue, 21 Apr 2026 15:17:14 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/solitaire-strategy-guide-how-to-win-more-games-of-klondike-81d</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/solitaire-strategy-guide-how-to-win-more-games-of-klondike-81d</guid>
      <description>&lt;p&gt;Klondike Solitaire has a deceptively high win rate — 80–82% with perfect play on one-card draw. Most players win far less than that. The gap isn't luck. It's strategy.&lt;/p&gt;

&lt;p&gt;Play free in your browser: &lt;a href="https://ultimatetools.io/tools/fun-tools/solitaire/" rel="noopener noreferrer"&gt;Solitaire Online — Free, Classic Klondike&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What "Winning" Actually Requires
&lt;/h2&gt;

&lt;p&gt;The goal is to move all 52 cards to four foundation piles, built from Ace to King by suit. The tableau (seven columns) is just the workspace. Every decision should be evaluated against one question: does this move help get a card to a foundation sooner?&lt;/p&gt;

&lt;p&gt;Most losing games have a common cause: the player fills the tableau with legal-but-counterproductive moves while foundations stay empty.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hierarchy of Moves
&lt;/h2&gt;

&lt;p&gt;Not all moves are equal. When multiple moves are available, prioritize in this order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Move a card to a foundation.&lt;/strong&gt; Always do this if the card is ready. Never withhold an Ace or a 2 from a foundation — they have no useful role in the tableau.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Flip a face-down card.&lt;/strong&gt; Every face-down card is locked information. Exposing it opens possibilities and eliminates uncertainty. Any move that uncovers a face-down card is almost always worth making.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Move a card from the waste pile to the tableau.&lt;/strong&gt; This clears space in the waste and opens new draws.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Draw from the stock.&lt;/strong&gt; If no tableau move is available that flips a face-down card, draw. Don't move cards around aimlessly just to do something.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Empty Column Rule
&lt;/h2&gt;

&lt;p&gt;An empty column is one of the most valuable resources in Solitaire. It's a free staging area — you can temporarily park any stack there to rearrange the tableau.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only a King (or a stack starting with a King) can fill an empty column.&lt;/strong&gt; This makes Kings more valuable than they look. A King with a long sequence ready to stack beneath it (K-Q-J-10-9-8...) is far more useful than a lone King with nothing to follow.&lt;/p&gt;

&lt;p&gt;The key rule: &lt;strong&gt;don't fill an empty column impulsively.&lt;/strong&gt; Wait for a King that will actually unlock something — a face-down card that becomes accessible, or a sequence that can be rearranged to expose a buried card.&lt;/p&gt;

&lt;p&gt;A wasted empty column is one of the most common causes of losing a winnable game.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Foundation Timing Trap
&lt;/h2&gt;

&lt;p&gt;Counter-intuitive: moving cards to the foundation too early can hurt you.&lt;/p&gt;

&lt;p&gt;A 3♥ on the foundation is a 3♥ that can't sit under a 4♠ or 4♣ in the tableau. Low cards (Aces through 4s) are sometimes more useful as tableau stepping stones than as foundation progress.&lt;/p&gt;

&lt;p&gt;The safe rule: &lt;strong&gt;move a card to the foundation when its partner card (same value, opposite color) is already on the foundation or visible in the tableau.&lt;/strong&gt; A 4♠ is safe to move when both 3♥ and 3♦ are on their foundations (nothing needs a black 4 in the tableau anymore).&lt;/p&gt;

&lt;p&gt;This isn't always possible to calculate perfectly, but thinking about it avoids the worst early-foundation mistakes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stock Cycling Strategy
&lt;/h2&gt;

&lt;p&gt;The stock (the face-down deck) cycles — when it's exhausted, the waste pile becomes the new stock. This means you can plan ahead: every card you've seen in the waste is information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the first cycle to gather information.&lt;/strong&gt; Don't force every draw into a tableau move. Sometimes it's better to pass on a marginal move and continue drawing to see what's coming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;By the second and third cycle&lt;/strong&gt;, you should know which cards are deep in the stock and build toward exposing them. If you know a needed card is coming in five draws, play the tableau conservatively until it arrives rather than making moves that reduce your options.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Two Sequences Problem
&lt;/h2&gt;

&lt;p&gt;One of the hardest scenarios: two sequences that need to be merged but can't because the required card is face-down under one of them.&lt;/p&gt;

&lt;p&gt;Example: you have a red 8 available but need it to go on a black 9 — which is buried under several face-down cards in another column.&lt;/p&gt;

&lt;p&gt;The fix isn't to move the red 8 somewhere temporary and hope. It's to systematically expose that black 9 by working down through the face-down cards above it. Identify what's blocking the card you need, and work backwards to expose it.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Restart
&lt;/h2&gt;

&lt;p&gt;Not every Solitaire deal is winnable. Roughly 18–20% of games with one-card draw are mathematically impossible regardless of play.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signs the game is stuck:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same cards have cycled through the stock three times without a useful move appearing&lt;/li&gt;
&lt;li&gt;No face-down cards remain accessible and foundations are incomplete&lt;/li&gt;
&lt;li&gt;Key cards are buried under sequences that can't be moved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, restarting isn't giving up — it's correct. The skill is recognizing an unwinnable deal before wasting ten more minutes on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Expose face-down cards above all else&lt;/li&gt;
&lt;li&gt;Protect empty columns — don't fill them carelessly&lt;/li&gt;
&lt;li&gt;Don't rush Aces and 2s to foundations if they're needed in the tableau&lt;/li&gt;
&lt;li&gt;Use stock cycling as information, not just as draws&lt;/li&gt;
&lt;li&gt;Know when to restart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Play and practice: &lt;a href="https://ultimatetools.io/tools/fun-tools/solitaire/" rel="noopener noreferrer"&gt;Solitaire at Ultimate Tools&lt;/a&gt; — no account, no download, classic Klondike one-card draw.&lt;/p&gt;




&lt;h2&gt;
  
  
  Other Strategy Guides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/2048/" rel="noopener noreferrer"&gt;2048 Strategy — The Corner Method&lt;/a&gt; — tile organization, not just merging&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/minesweeper/" rel="noopener noreferrer"&gt;Minesweeper Strategy — Solve Without Guessing&lt;/a&gt; — constraint elimination&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/sudoku/" rel="noopener noreferrer"&gt;Sudoku Strategy — Elimination over Calculation&lt;/a&gt; — logical deduction techniques&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>gamedev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Brick Breaker Strategy Guide — Aim the Ball, Clear Smart, Score High</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Tue, 21 Apr 2026 01:11:52 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/brick-breaker-strategy-guide-aim-the-ball-clear-smart-score-high-3f9j</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/brick-breaker-strategy-guide-aim-the-ball-clear-smart-score-high-3f9j</guid>
      <description>&lt;p&gt;Brick Breaker looks like a reflex game. Keep the ball in the air, break the bricks, don't let it fall. But the players who score high aren't reacting faster — they're aiming better.&lt;/p&gt;

&lt;p&gt;Play for free in your browser: &lt;a href="https://ultimatetools.io/tools/fun-tools/brick-breaker/" rel="noopener noreferrer"&gt;Brick Breaker Online — Free, No Download&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Most Important Mechanic: Paddle Angle Control
&lt;/h2&gt;

&lt;p&gt;Most players treat the paddle like a flat wall — position it under the ball and it bounces back. That's correct, but incomplete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where the ball hits the paddle determines where it goes next:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Center of paddle → ball returns at roughly the same angle it came from&lt;/li&gt;
&lt;li&gt;Left edge → ball deflects sharply to the left&lt;/li&gt;
&lt;li&gt;Right edge → ball deflects sharply to the right&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means you can steer the ball. Not just keep it alive — actually aim it at specific bricks.&lt;/p&gt;

&lt;p&gt;Once this clicks, the game changes. You stop playing defensively (keep the ball from falling) and start playing offensively (direct the ball toward the section you want to clear next).&lt;/p&gt;




&lt;h2&gt;
  
  
  Clear the Bottom Bricks Early
&lt;/h2&gt;

&lt;p&gt;Counter-intuitive for new players: target the bottom rows of bricks before the top ones.&lt;/p&gt;

&lt;p&gt;Here's why: after the upper bricks are gone, the ball has a clear path to the top wall and comes back down. Any surviving lower bricks are now hard to reach — the ball hits the top, bounces around the ceiling, and falls straight back without touching the stranded lower rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Early in a level, angle the ball downward toward bottom-row bricks.&lt;/strong&gt; Yes, the top bricks are easier to hit. But clearing a path to the bottom first gives you access to the whole board.&lt;/p&gt;




&lt;h2&gt;
  
  
  Create a Roof-Run
&lt;/h2&gt;

&lt;p&gt;One of the most satisfying mechanics in Brick Breaker: when the ball gets trapped between the top wall and the top row of bricks, it bounces rapidly and clears an entire row without you doing anything.&lt;/p&gt;

&lt;p&gt;To set this up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clear the brick directly adjacent to a wall at the top row&lt;/li&gt;
&lt;li&gt;Angle the ball into the gap you've created&lt;/li&gt;
&lt;li&gt;The ball enters the space above the bricks and bounces off the ceiling, clearing brick after brick on its way across&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Deliberately creating a gap to exploit isn't just satisfying — it's the fastest way to clear a dense upper section.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Lives Resource
&lt;/h2&gt;

&lt;p&gt;You start with 3 lives. Losing a life doesn't just hurt emotionally — it limits your options going forward. Every life preserved is a cushion for the harder patterns in later levels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't accept lost lives as inevitable.&lt;/strong&gt; Most ball losses happen in the first three seconds after a ball drops, when the player is repositioning from chasing the ball across the screen. The fix: &lt;strong&gt;watch the ball's angle after it bounces off a brick, not after it passes the midpoint of the screen.&lt;/strong&gt; You have more reaction time if you start moving the paddle earlier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dealing With Isolated Bricks
&lt;/h2&gt;

&lt;p&gt;The hardest scenario: one brick remaining in the upper-left corner of the board after everything else is cleared. The ball can only reach it by deflecting off two walls in sequence.&lt;/p&gt;

&lt;p&gt;To reach a corner brick:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Position the paddle slightly off-center toward the corner the brick is in&lt;/li&gt;
&lt;li&gt;Let the ball angle naturally up toward that corner&lt;/li&gt;
&lt;li&gt;Don't try to force it with hard edge hits — a moderate angle toward the wall sequence is more consistent than a sharp redirect&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Patience here beats force. The ball will reach it — don't sacrifice a life trying to speed it up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Score Optimization
&lt;/h2&gt;

&lt;p&gt;Every brick destroyed adds 10 points. Level completion adds a bonus.&lt;/p&gt;

&lt;p&gt;The math: clearing a level quickly with all lives intact earns more than clearing it slowly after losing two lives. &lt;strong&gt;The bonus multiplier is real — preserve lives.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On harder levels where bricks are dense, the highest-value play is often to slow down the ball angle deliberately — take fewer risks with edge hits, accept some lost positions — in exchange for completing the level without losing a life.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary: The Three Principles
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Aim, don't just bounce.&lt;/strong&gt; Use edge hits to steer the ball. Every shot is steerable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clear bottom first.&lt;/strong&gt; Stranded bricks at the bottom are harder than they look once the top opens up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lives are resources.&lt;/strong&gt; Play conservatively when ahead; be aggressive only when you can afford to lose one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Play and Practice
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ultimatetools.io/tools/fun-tools/brick-breaker/" rel="noopener noreferrer"&gt;Brick Breaker at Ultimate Tools&lt;/a&gt; — no download, no account, works in any desktop browser. The angle control mechanics described above apply directly to this version.&lt;/p&gt;




&lt;h2&gt;
  
  
  Other Games With Strategy Depth
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/snake/" rel="noopener noreferrer"&gt;Snake&lt;/a&gt; — spatial planning and path management&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/minesweeper/" rel="noopener noreferrer"&gt;Minesweeper&lt;/a&gt; — pure logic deduction&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/sudoku/" rel="noopener noreferrer"&gt;Sudoku&lt;/a&gt; — elimination-based puzzle solving&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/solitaire/" rel="noopener noreferrer"&gt;Solitaire&lt;/a&gt; — sequencing and foundation management&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>gamedev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Snake Strategy Guide — How to Score High Without Running Into Yourself</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Tue, 21 Apr 2026 01:10:48 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/snake-strategy-guide-how-to-score-high-without-running-into-yourself-28e3</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/snake-strategy-guide-how-to-score-high-without-running-into-yourself-28e3</guid>
      <description>&lt;p&gt;Most Snake runs end the same way: the snake is long, the space is tight, and one bad turn sends the head into the body. The game over isn't a mystery — you can see exactly what went wrong. The question is how to prevent it.&lt;/p&gt;

&lt;p&gt;Play for free in your browser: &lt;a href="https://ultimatetools.io/tools/fun-tools/snake/" rel="noopener noreferrer"&gt;Snake Online — Free, No Download&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Chasing Food Directly
&lt;/h2&gt;

&lt;p&gt;The instinct in Snake is to move the head toward the food as efficiently as possible. Shortest path, fewest turns. This works at short lengths.&lt;/p&gt;

&lt;p&gt;At medium and long lengths, the shortest path to food is usually the most dangerous path. Cutting directly through the center of the board means your body follows you there — and the next piece of food spawns somewhere you now can't easily reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The core mistake:&lt;/strong&gt; optimizing each food pickup individually, without considering what the board will look like afterward.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Spiral Strategy
&lt;/h2&gt;

&lt;p&gt;The single most reliable high-score approach: &lt;strong&gt;trace the perimeter of the board in a large coil, spiraling inward.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Move along the outer edge of the grid&lt;/li&gt;
&lt;li&gt;When you reach a corner, turn inward by one cell&lt;/li&gt;
&lt;li&gt;Repeat — tracing progressively smaller rectangles toward the center&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The body follows this path in order. Because the snake always moves in organized rows, it never crosses itself.&lt;/p&gt;

&lt;p&gt;The downside: this path doesn't chase food efficiently. The food might be on the opposite side of the spiral when you reach it. But that's the point — you trade short-term efficiency for long-term survival.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Deviate From the Spiral
&lt;/h2&gt;

&lt;p&gt;The spiral isn't rigid. If food spawns directly ahead on your current path, take it. If a short detour picks up food without breaking the organized structure, take it.&lt;/p&gt;

&lt;p&gt;The rule: &lt;strong&gt;any detour you take must be completable.&lt;/strong&gt; Before deviating, visualize whether you can return to the structured path. If the detour would strand you in a section of the board with no exit, don't take it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Don't Back Yourself Into a Corner" Principle
&lt;/h2&gt;

&lt;p&gt;At high lengths, empty space is the most valuable resource on the board.&lt;/p&gt;

&lt;p&gt;Every time you make a turn, you're potentially reducing accessible space. Before any turn, ask: &lt;strong&gt;does this path leave me with enough room to continue?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Specifically:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid the center late game.&lt;/strong&gt; The center seems safe early — lots of space in every direction. But a long snake in the center has nowhere to escape. The edges are safer because you can always turn along a wall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leave buffer space between rows.&lt;/strong&gt; If you're tracing a path along the board, leave at least one cell of gap between passes. This gives you an escape route if the food forces you off your planned path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reversals Kill You — Here's Why
&lt;/h2&gt;

&lt;p&gt;You cannot reverse direction in Snake. If the snake is moving right, pressing left immediately sends the head into the body and the game ends.&lt;/p&gt;

&lt;p&gt;This catches players when they panic. The snake is heading toward a wall, they realize too late, and they press the opposite key by instinct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; commit to turning [up or down] before attempting to reverse horizontal direction. The 90-degree turn gives you a row of buffer before you'd need to reverse again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Controlling the Game Speed
&lt;/h2&gt;

&lt;p&gt;As the snake grows, the effective speed of the game increases — not because the timer changes, but because each decision matters more. A mistake at length 10 is recoverable. At length 50, a single wrong turn ends the game.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slow your decision-making while the snake moves at its own pace.&lt;/strong&gt; Before each turn, look ahead two moves, not just one. The turn you're making now sets up your options for the next turn.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tips for a High Score
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with the perimeter.&lt;/strong&gt; From the very first move, trace the outer edge of the board. This establishes the organized structure before the snake is long enough to complicate things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't panic near walls.&lt;/strong&gt; Most deaths happen when a player overcorrects near an edge. Commit to your direction, adjust early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat food as a secondary goal.&lt;/strong&gt; Food is the score mechanism, but survival is the actual game. A run that lasts 200 moves without food is impossible — but a run that ends at move 20 scores zero. Prioritize staying alive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch the tail.&lt;/strong&gt; As the snake grows, the tail's position becomes as important as the head's. The space your tail is vacating is space you can move into. Track where it's going.&lt;/p&gt;




&lt;h2&gt;
  
  
  Play and Practice
&lt;/h2&gt;

&lt;p&gt;The strategies above apply to &lt;a href="https://ultimatetools.io/tools/fun-tools/snake/" rel="noopener noreferrer"&gt;Snake at Ultimate Tools&lt;/a&gt; — classic grid Snake, no download, no account. Works in any desktop browser with a keyboard.&lt;/p&gt;

&lt;p&gt;The best way to internalize these patterns is to play a few rounds specifically practicing the spiral — even if it means ignoring nearby food. Once the structure is muscle memory, layering in opportunistic food grabs becomes natural.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related Games
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/minesweeper/" rel="noopener noreferrer"&gt;Minesweeper&lt;/a&gt; — logic-based deduction (no reflex required)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/sudoku/" rel="noopener noreferrer"&gt;Sudoku&lt;/a&gt; — pure logic puzzle, three difficulty levels&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ultimatetools.io/tools/fun-tools/brick-breaker/" rel="noopener noreferrer"&gt;Brick Breaker&lt;/a&gt; — arcade ball physics with angle control&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>gamedev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Create a Wi-Fi QR Code — Share Your Network Password in One Scan</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Mon, 20 Apr 2026 14:27:19 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/how-to-create-a-wi-fi-qr-code-share-your-network-password-in-one-scan-4cej</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/how-to-create-a-wi-fi-qr-code-share-your-network-password-in-one-scan-4cej</guid>
      <description>&lt;p&gt;Instead of reading out your Wi-Fi password letter by letter, you can put a QR code on your wall. Guests scan it, they're connected. No typing. No "is that a capital I or a lowercase l?"&lt;/p&gt;

&lt;p&gt;Here's how Wi-Fi QR codes work and how to create one for free.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Wi-Fi QR Codes Work
&lt;/h2&gt;

&lt;p&gt;A Wi-Fi QR code encodes your network credentials in a specific text format that phones recognize automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WIFI:S:YourNetworkName;T:WPA;P:YourPassword;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;S:&lt;/code&gt; — network name (SSID)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;T:&lt;/code&gt; — security type (&lt;code&gt;WPA&lt;/code&gt;, &lt;code&gt;WEP&lt;/code&gt;, or &lt;code&gt;nopass&lt;/code&gt; for open networks)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;P:&lt;/code&gt; — password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a phone camera scans this format, it detects the &lt;code&gt;WIFI:&lt;/code&gt; prefix and opens a connection prompt automatically — no app needed on modern Android or iOS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Create a Wi-Fi QR Code (Free, No Login)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;strong&gt;&lt;a href="https://ultimatetools.io/tools/misc-tools/qr-code-generator/" rel="noopener noreferrer"&gt;QR Code Generator&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Wi-Fi&lt;/strong&gt; as the content type&lt;/li&gt;
&lt;li&gt;Enter your network name (SSID) and password&lt;/li&gt;
&lt;li&gt;Choose your security type (WPA2 for most home routers)&lt;/li&gt;
&lt;li&gt;Customize the style if you want — colors, dot shape, corner style&lt;/li&gt;
&lt;li&gt;Download as PNG or SVG&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The QR code is generated entirely in your browser. Your Wi-Fi password is never sent to any server.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Type — Which to Choose
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Security&lt;/th&gt;
&lt;th&gt;When to use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WPA / WPA2&lt;/td&gt;
&lt;td&gt;Most home and office routers — choose this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WPA3&lt;/td&gt;
&lt;td&gt;Newer routers with WPA3 support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WEP&lt;/td&gt;
&lt;td&gt;Older, legacy networks (rare)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No password&lt;/td&gt;
&lt;td&gt;Open/guest networks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you're unsure, check your router's admin page or the sticker on the back of the router. Almost all modern home routers use WPA2.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to Put It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Printed and framed&lt;/strong&gt; — the most common use. Print the QR code, put it in a small frame, and place it near your router or at your desk. Guests scan it on arrival.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On your phone screen&lt;/strong&gt; — create the QR code, screenshot it, and show guests your screen. They scan directly from your display.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In a welcome document&lt;/strong&gt; — if you run an Airbnb, short-term rental, or office, put the QR code in the welcome PDF or print it on an information card.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On a desk tent card&lt;/strong&gt; — small folded card, QR code on one side, network name on the other. Common in coworking spaces and hotels.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scanning a Wi-Fi QR Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Android (8+):&lt;/strong&gt; Open the camera app, point at the QR code. A notification appears — tap to connect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS (11+):&lt;/strong&gt; Same — open the native Camera app, point at the code. Tap the banner to join the network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Older devices:&lt;/strong&gt; Use a QR scanner app. Most will detect the &lt;code&gt;WIFI:&lt;/code&gt; format and offer to connect.&lt;/p&gt;

&lt;p&gt;No app download needed on modern phones. The camera handles it natively.&lt;/p&gt;




&lt;h2&gt;
  
  
  Can Someone Steal My Password From the QR Code?
&lt;/h2&gt;

&lt;p&gt;Yes — anyone who scans the code gets your Wi-Fi password. Keep that in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't post your Wi-Fi QR code publicly online (social media, websites)&lt;/li&gt;
&lt;li&gt;For Airbnb or short-term rentals, use a &lt;strong&gt;separate guest network&lt;/strong&gt; with its own password — not your main network&lt;/li&gt;
&lt;li&gt;For offices, create a guest network with limited access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most routers support a separate guest SSID. Create a QR code for the guest network only — that way guests connect without touching your main devices.&lt;/p&gt;




&lt;h2&gt;
  
  
  Updating the QR Code
&lt;/h2&gt;

&lt;p&gt;Wi-Fi QR codes are &lt;strong&gt;static&lt;/strong&gt; — they encode the password directly. If you change your Wi-Fi password, you need to generate a new QR code.&lt;/p&gt;

&lt;p&gt;If you change passwords regularly, consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeping the password simple but memorable (so guests can type it if needed)&lt;/li&gt;
&lt;li&gt;Using a &lt;strong&gt;dynamic QR code&lt;/strong&gt; that redirects to a page where you update the password centrally — though this requires more setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most home and small office use, a static Wi-Fi QR code works fine. Passwords rarely change.&lt;/p&gt;




&lt;p&gt;Generate your Wi-Fi QR code free: &lt;strong&gt;&lt;a href="https://ultimatetools.io/tools/misc-tools/qr-code-generator/" rel="noopener noreferrer"&gt;QR Code Generator — Wi-Fi, URL, vCard, and more&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No login. No upload. Works on any browser.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Resize Images for Social Media — The Right Dimensions for Every Platform</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Mon, 20 Apr 2026 14:26:12 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/how-to-resize-images-for-social-media-the-right-dimensions-for-every-platform-3doe</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/how-to-resize-images-for-social-media-the-right-dimensions-for-every-platform-3doe</guid>
      <description>&lt;p&gt;Every social media platform has its own image dimensions — and using the wrong size means cropped photos, blurry thumbnails, or images that don't fill the frame correctly.&lt;/p&gt;

&lt;p&gt;Here are the correct pixel dimensions for every major platform, and how to resize any image to match.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Dimensions Matter
&lt;/h2&gt;

&lt;p&gt;Uploading an image that's the wrong size causes one of three problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Platform crops it&lt;/strong&gt; — Instagram crops non-square images to fit its feed format. Your subject might be cut off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform stretches it&lt;/strong&gt; — Some platforms upscale small images, making them blurry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform letterboxes it&lt;/strong&gt; — Black bars appear around images that don't match the expected ratio.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using exact pixel dimensions avoids all three.&lt;/p&gt;




&lt;h2&gt;
  
  
  Instagram
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Square post&lt;/td&gt;
&lt;td&gt;1080 × 1080 px&lt;/td&gt;
&lt;td&gt;1:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portrait post&lt;/td&gt;
&lt;td&gt;1080 × 1350 px&lt;/td&gt;
&lt;td&gt;4:5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Landscape post&lt;/td&gt;
&lt;td&gt;1080 × 566 px&lt;/td&gt;
&lt;td&gt;1.91:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Story / Reel&lt;/td&gt;
&lt;td&gt;1080 × 1920 px&lt;/td&gt;
&lt;td&gt;9:16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profile photo&lt;/td&gt;
&lt;td&gt;320 × 320 px&lt;/td&gt;
&lt;td&gt;1:1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Portrait (4:5) takes up the most vertical space in the feed and gets the most visibility. Square is the safest for repurposing one image across formats.&lt;/p&gt;




&lt;h2&gt;
  
  
  Twitter / X
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;In-feed image&lt;/td&gt;
&lt;td&gt;1200 × 675 px&lt;/td&gt;
&lt;td&gt;16:9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-feed portrait&lt;/td&gt;
&lt;td&gt;900 × 1200 px&lt;/td&gt;
&lt;td&gt;3:4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profile photo&lt;/td&gt;
&lt;td&gt;400 × 400 px&lt;/td&gt;
&lt;td&gt;1:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cover/header&lt;/td&gt;
&lt;td&gt;1500 × 500 px&lt;/td&gt;
&lt;td&gt;3:1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Twitter auto-crops images in the feed to a 16:9 preview. If your subject is off-center, it may be cropped out. Use 1200 × 675 px to control exactly what's shown.&lt;/p&gt;




&lt;h2&gt;
  
  
  LinkedIn
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Post image&lt;/td&gt;
&lt;td&gt;1200 × 627 px&lt;/td&gt;
&lt;td&gt;1.91:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Article cover&lt;/td&gt;
&lt;td&gt;1920 × 1080 px&lt;/td&gt;
&lt;td&gt;16:9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profile photo&lt;/td&gt;
&lt;td&gt;400 × 400 px&lt;/td&gt;
&lt;td&gt;1:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cover photo&lt;/td&gt;
&lt;td&gt;1584 × 396 px&lt;/td&gt;
&lt;td&gt;4:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Company logo&lt;/td&gt;
&lt;td&gt;300 × 300 px&lt;/td&gt;
&lt;td&gt;1:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Company cover&lt;/td&gt;
&lt;td&gt;1128 × 191 px&lt;/td&gt;
&lt;td&gt;~6:1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; LinkedIn post images at 1200 × 627 px display well on both desktop and mobile feed without cropping.&lt;/p&gt;




&lt;h2&gt;
  
  
  Facebook
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Post image&lt;/td&gt;
&lt;td&gt;1200 × 630 px&lt;/td&gt;
&lt;td&gt;~1.91:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Story&lt;/td&gt;
&lt;td&gt;1080 × 1920 px&lt;/td&gt;
&lt;td&gt;9:16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profile photo&lt;/td&gt;
&lt;td&gt;170 × 170 px (display)&lt;/td&gt;
&lt;td&gt;1:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cover photo&lt;/td&gt;
&lt;td&gt;820 × 312 px&lt;/td&gt;
&lt;td&gt;~2.63:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event cover&lt;/td&gt;
&lt;td&gt;1920 × 1005 px&lt;/td&gt;
&lt;td&gt;~1.91:1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  YouTube
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Thumbnail&lt;/td&gt;
&lt;td&gt;1280 × 720 px&lt;/td&gt;
&lt;td&gt;16:9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel art&lt;/td&gt;
&lt;td&gt;2560 × 1440 px&lt;/td&gt;
&lt;td&gt;16:9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profile photo&lt;/td&gt;
&lt;td&gt;800 × 800 px&lt;/td&gt;
&lt;td&gt;1:1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; YouTube thumbnails display at 1280 × 720 px but are shown small in search results. Use large text and high contrast so they're readable at thumbnail size.&lt;/p&gt;




&lt;h2&gt;
  
  
  WhatsApp
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Status image&lt;/td&gt;
&lt;td&gt;1080 × 1920 px&lt;/td&gt;
&lt;td&gt;9:16, same as Stories&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profile photo&lt;/td&gt;
&lt;td&gt;500 × 500 px&lt;/td&gt;
&lt;td&gt;1:1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How to Resize to Exact Dimensions
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://ultimatetools.io/tools/image-tools/image-resizer/" rel="noopener noreferrer"&gt;Image Resizer&lt;/a&gt;&lt;/strong&gt; lets you set exact pixel width and height:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload your image (JPG, PNG, WebP, GIF)&lt;/li&gt;
&lt;li&gt;Enter the target width and height in pixels&lt;/li&gt;
&lt;li&gt;Toggle &lt;strong&gt;Lock Aspect Ratio&lt;/strong&gt; — on to scale proportionally, off to set exact dimensions&lt;/li&gt;
&lt;li&gt;Download the resized image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything runs in your browser — nothing is uploaded to a server.&lt;/p&gt;




&lt;h2&gt;
  
  
  Aspect Ratio vs. Exact Dimensions
&lt;/h2&gt;

&lt;p&gt;There's an important difference:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lock aspect ratio ON:&lt;/strong&gt; the image scales proportionally. If you set width to 1080px, the height adjusts automatically to preserve the original ratio. You won't distort the image, but you might not hit the exact target height.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lock aspect ratio OFF:&lt;/strong&gt; you set both width and height independently. The image will match exactly, but may appear stretched if the original ratio doesn't match the target.&lt;/p&gt;

&lt;p&gt;For most social media use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;ratio ON&lt;/strong&gt; when the platform accepts a range of sizes&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;ratio OFF&lt;/strong&gt; when the platform requires exact dimensions (profile photos, covers)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Batch Strategy
&lt;/h2&gt;

&lt;p&gt;If you post the same image across multiple platforms, start with the largest dimension:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Resize to &lt;strong&gt;1920 × 1080 px&lt;/strong&gt; (YouTube cover, LinkedIn article)&lt;/li&gt;
&lt;li&gt;Crop/resize to &lt;strong&gt;1200 × 675 px&lt;/strong&gt; (Twitter, Facebook, LinkedIn post)&lt;/li&gt;
&lt;li&gt;Resize to &lt;strong&gt;1080 × 1080 px&lt;/strong&gt; (Instagram square)&lt;/li&gt;
&lt;li&gt;Resize to &lt;strong&gt;1080 × 1920 px&lt;/strong&gt; (Stories, Reels)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Starting large preserves quality. Downscaling is always better than upscaling.&lt;/p&gt;




&lt;p&gt;Resize any image to exact social media dimensions — free, no upload: &lt;strong&gt;&lt;a href="https://ultimatetools.io/tools/image-tools/image-resizer/" rel="noopener noreferrer"&gt;Image Resizer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building 2048, Wordle, Flappy Bird, Hangman, and Whack-a-Mole in React — Architecture Notes</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Mon, 20 Apr 2026 14:24:49 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/building-2048-wordle-flappy-bird-hangman-and-whack-a-mole-in-react-architecture-notes-41ig</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/building-2048-wordle-flappy-bird-hangman-and-whack-a-mole-in-react-architecture-notes-41ig</guid>
      <description>&lt;p&gt;We recently added five more browser games to &lt;a href="https://ultimatetools.io/tools/fun-tools/" rel="noopener noreferrer"&gt;ultimatetools.io&lt;/a&gt; — 2048, Wordle, Flappy Bird, Hangman, and Whack-a-Mole. All built in React, no game libraries, running in the browser.&lt;/p&gt;

&lt;p&gt;Here's what was interesting or non-obvious about each one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shared Architecture
&lt;/h2&gt;

&lt;p&gt;Each game follows the same two-file pattern used across the site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/tools/fun-tools/[game]/page.tsx   ← server component: metadata + SEO schemas
components/tools/[GameName].tsx        ← "use client": all game logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;State approach varies by game:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOM-based games (2048, Wordle, Hangman): &lt;code&gt;useState&lt;/code&gt; is fine — updates are infrequent&lt;/li&gt;
&lt;li&gt;Canvas + RAF games (Flappy Bird, Whack-a-Mole): all game state in &lt;code&gt;useRef&lt;/code&gt; — avoids stale closures inside the animation loop&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2048 — The Direction Rotation Trick
&lt;/h2&gt;

&lt;p&gt;2048 is a 4×4 grid where tiles slide and merge when you swipe/press a direction.&lt;/p&gt;

&lt;p&gt;The naive approach: write four separate merge functions for left, right, up, and down. That's a lot of duplicated logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The better approach:&lt;/strong&gt; write one &lt;code&gt;mergeLeft&lt;/code&gt; function, then rotate the grid before and after calling it.&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;function&lt;/span&gt; &lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&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;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;grid&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="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&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="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Direction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;grid&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;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&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;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&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;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mergeLeft&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// always merge left&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;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&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;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&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;direction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rotateGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&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;g&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;&lt;code&gt;mergeLeft&lt;/code&gt; slides all tiles left, merges adjacent equal tiles, and fills with zeros. One function handles all four directions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slide animations:&lt;/strong&gt; CSS transitions on tile positions. Tiles are keyed by a stable ID (not value or position) so React doesn't remount them on each move — the key stays the same, the &lt;code&gt;transform: translate()&lt;/code&gt; changes, and CSS handles the animation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wordle — Two-Pass Duplicate Letter Evaluation
&lt;/h2&gt;

&lt;p&gt;Wordle has a subtle edge case: &lt;strong&gt;duplicate letters&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;If the secret word is &lt;code&gt;ABBEY&lt;/code&gt; and you guess &lt;code&gt;EERIE&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first &lt;code&gt;E&lt;/code&gt; should be yellow (E is in the word, wrong position)&lt;/li&gt;
&lt;li&gt;The second &lt;code&gt;E&lt;/code&gt; should be gray (the word only has one E, already accounted for)&lt;/li&gt;
&lt;li&gt;The third &lt;code&gt;E&lt;/code&gt; should be gray (same reason)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single-pass evaluation gets this wrong. The correct approach is two passes:&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;function&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;guess&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;span class="nx"&gt;target&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;span class="nx"&gt;LetterState&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;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LetterState&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;absent&lt;/span&gt;&lt;span class="dl"&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;targetChars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&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;guessChars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;guess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Pass 1: mark exact matches (green)&lt;/span&gt;
  &lt;span class="nx"&gt;guessChars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;targetChars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;correct&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;targetChars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// consume this target letter&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Pass 2: mark wrong-position matches (yellow)&lt;/span&gt;
  &lt;span class="nx"&gt;guessChars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;correct&lt;/span&gt;&lt;span class="dl"&gt;"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetChars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;char&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;idx&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;present&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;targetChars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// consume so it's not matched again&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;result&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;&lt;strong&gt;Daily mode seeding:&lt;/strong&gt; The daily word is selected by taking the day offset from a fixed epoch and indexing into the word list. Same result for every user on the same day, no server needed.&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;EPOCH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-01-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&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;dayIndex&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;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;EPOCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;86400000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;wordList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;dailyWord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;wordList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dayIndex&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Flappy Bird — Physics in a RAF Loop
&lt;/h2&gt;

&lt;p&gt;Flappy Bird is Canvas + &lt;code&gt;requestAnimationFrame&lt;/code&gt;. All state lives in refs to avoid stale closures:&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;birdYRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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;velocityRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipesRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Pipe&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scoreRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statusRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dead&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Physics per frame:&lt;/strong&gt;&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="nx"&gt;velocityRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;GRAVITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// ~0.5 per frame&lt;/span&gt;
&lt;span class="nx"&gt;birdYRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;velocityRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Jump:&lt;/strong&gt;&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="nx"&gt;velocityRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JUMP_VELOCITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// ~-9, negative = up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pipe generation:&lt;/strong&gt; a new pipe spawns every N frames. Pipe gap position is randomized within a constrained range so the game stays playable. Speed increases every 10 points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collision detection:&lt;/strong&gt; axis-aligned bounding box (AABB) — simple rectangle overlap between bird bounds and each pipe rect. Checked every frame.&lt;/p&gt;

&lt;p&gt;The bird is drawn as a rounded rectangle with a wing, not a sprite — keeps the asset count at zero.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hangman — SVG Progressive Drawing
&lt;/h2&gt;

&lt;p&gt;Hangman's "drawing" is an SVG with 7 stages. Each stage is a path or element that becomes visible as wrong guesses accumulate:&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;PARTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="nx"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;60&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;20&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;x2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;60&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;120&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// body&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;circle&lt;/span&gt; &lt;span class="nx"&gt;cx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;60&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// head&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="nx"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;60&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;50&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;x2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;40&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;80&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// left arm&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="nx"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;60&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;50&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;x2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;80&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;80&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// right arm&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="nx"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;60&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;120&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;x2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;40&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;150&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// left leg&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="nx"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;60&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;120&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;x2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;80&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;150&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// right leg&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="nx"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;20&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;180&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;x2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;y2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;180&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// ground&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Render parts[0..wrongCount]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Keyboard handling:&lt;/strong&gt; both on-screen letter buttons and physical keyboard events (&lt;code&gt;keydown&lt;/code&gt; listener). The physical keyboard handler checks that the key is a letter, hasn't been guessed, and the game is in progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Word categories:&lt;/strong&gt; General, Animals, Countries, Tech — each a pre-defined array of words. Category selector shown at game start. Selecting a new category resets the game.&lt;/p&gt;




&lt;h2&gt;
  
  
  Whack-a-Mole — Recursive setTimeout Spawning
&lt;/h2&gt;

&lt;p&gt;Whack-a-Mole has 9 holes. Moles pop up randomly, stay for a fixed duration, then retract.&lt;/p&gt;

&lt;p&gt;The approach that works well: &lt;strong&gt;per-hole recursive setTimeout&lt;/strong&gt;, not a single game tick.&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;function&lt;/span&gt; &lt;span class="nf"&gt;spawnMole&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;hole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pickRandomEmptyHole&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;setMoles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hole&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="nf"&gt;setTimeout&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="nf"&gt;setMoles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hole&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;false&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;gameRunning&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spawnMole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;getRandomDelay&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// recursive — next mole&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;MOLE_VISIBLE_MS&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;&lt;strong&gt;Speed progression:&lt;/strong&gt; &lt;code&gt;MOLE_VISIBLE_MS&lt;/code&gt; decreases every 15 seconds. More moles appear faster as the game progresses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hit detection:&lt;/strong&gt; clicking a mole calls &lt;code&gt;whack(hole)&lt;/code&gt; which checks &lt;code&gt;moles[hole] === true&lt;/code&gt;, increments score, and immediately sets &lt;code&gt;moles[hole] = false&lt;/code&gt;. The mole visually retracts on the next render.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;60-second timer:&lt;/strong&gt; a single &lt;code&gt;setInterval&lt;/code&gt; ticks every second, decrements time, and ends the game at 0. On game end, all active mole timeouts are effectively abandoned — they fire but the &lt;code&gt;gameRunning&lt;/code&gt; check prevents new spawns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Patterns Across All Five
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;localStorage for scores/streaks:&lt;/strong&gt;&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="nf"&gt;useEffect&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="o"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;best-score&lt;/span&gt;&lt;span class="dl"&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;saved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setBestScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saved&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;&lt;strong&gt;Touch support:&lt;/strong&gt; all Canvas games listen for &lt;code&gt;touchstart&lt;/code&gt;/&lt;code&gt;touchend&lt;/code&gt; in addition to &lt;code&gt;mousedown&lt;/code&gt;/&lt;code&gt;keydown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ShareResultCard:&lt;/strong&gt; the existing share component is reused for end-of-game share images — same canvas generation pattern as every other tool on the site.&lt;/p&gt;




&lt;p&gt;All five games are live at &lt;strong&gt;&lt;a href="https://ultimatetools.io/tools/fun-tools/" rel="noopener noreferrer"&gt;ultimatetools fun tools&lt;/a&gt;&lt;/strong&gt; — no login, no download, works on any browser.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>How to Rotate a Photo Online Without Losing Quality</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Mon, 20 Apr 2026 02:09:06 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/how-to-rotate-a-photo-online-without-losing-quality-56h9</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/how-to-rotate-a-photo-online-without-losing-quality-56h9</guid>
      <description>&lt;p&gt;Rotating a photo sounds trivial — click a button, done. But depending on how you do it, you can degrade image quality every single time. Here's why that happens and how to avoid it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Rotation Can Lose Quality
&lt;/h2&gt;

&lt;p&gt;Most photos are JPEGs. JPEG is a &lt;strong&gt;lossy format&lt;/strong&gt; — every time a JPEG is saved, it re-encodes the image using compression that permanently discards some data.&lt;/p&gt;

&lt;p&gt;When a typical image editor rotates a JPEG, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decodes the compressed image to raw pixel data&lt;/li&gt;
&lt;li&gt;Rotates the pixels&lt;/li&gt;
&lt;li&gt;Re-encodes the result to JPEG — applying another full round of compression&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That re-encoding step is the problem. Each cycle makes the image slightly softer, adds compression artifacts, and you can never recover the original data.&lt;/p&gt;

&lt;p&gt;For a single rotation the difference is subtle. After 3–4 save cycles, it becomes visible in fine details and edges.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lossless JPEG Rotation
&lt;/h2&gt;

&lt;p&gt;The solution is &lt;strong&gt;lossless JPEG rotation&lt;/strong&gt; — rotating the image at the compressed-block level without decoding and re-encoding the whole image.&lt;/p&gt;

&lt;p&gt;JPEG compresses data in &lt;strong&gt;8×8 pixel blocks&lt;/strong&gt;. Rotating a JPEG by exactly 90°, 180°, or 270° means transposing those blocks mathematically — no decode/re-encode pass required.&lt;/p&gt;

&lt;p&gt;Lossless rotation only works when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The rotation angle is 90°, 180°, or 270° (not arbitrary angles like 15°)&lt;/li&gt;
&lt;li&gt;The image dimensions are multiples of 8 — most camera photos qualify&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For arbitrary angle rotation, some re-encoding is unavoidable.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About PNG and WebP?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PNG&lt;/strong&gt; is lossless by design. Rotating and saving a PNG never degrades quality regardless of how many times you do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebP&lt;/strong&gt; supports both lossy and lossless modes. Rotating a lossless WebP has no quality loss.&lt;/p&gt;

&lt;p&gt;If quality is critical and you're doing multiple edits, the safest workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert your JPEG to PNG&lt;/li&gt;
&lt;li&gt;Do all your edits (rotate, crop, resize)&lt;/li&gt;
&lt;li&gt;Save as PNG throughout&lt;/li&gt;
&lt;li&gt;Only convert back to JPEG at the very final step&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The EXIF Rotation Problem
&lt;/h2&gt;

&lt;p&gt;Your phone camera often doesn't physically rotate photos. Instead, it writes a rotation tag in the image's &lt;strong&gt;EXIF metadata&lt;/strong&gt;. Most modern apps read this tag and display the photo correctly.&lt;/p&gt;

&lt;p&gt;But some tools — web upload forms, older apps, certain browsers — ignore the EXIF tag and display the photo sideways or upside down.&lt;/p&gt;

&lt;p&gt;The fix: use a tool that applies the EXIF rotation to the actual pixel data, then clears the tag. This makes the rotation permanent and visible everywhere, regardless of whether the app reads EXIF.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Rotate Photos Online
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://ultimatetools.io/tools/image-tools/rotate-image/" rel="noopener noreferrer"&gt;Rotate Image tool&lt;/a&gt; handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;90° clockwise / counter-clockwise&lt;/strong&gt; — standard quarter turns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;180° rotation&lt;/strong&gt; — full flip&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal flip&lt;/strong&gt; — mirror left-to-right&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vertical flip&lt;/strong&gt; — mirror top-to-bottom&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output format&lt;/strong&gt; — export as JPG, PNG, or WebP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs in your browser using the Canvas API. Your file is read locally, rotated in memory, and downloaded directly — nothing is sent to a server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;strong&gt;&lt;a href="https://ultimatetools.io/tools/image-tools/rotate-image/" rel="noopener noreferrer"&gt;Rotate Image tool&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Upload your photo — JPG, PNG, WebP, or GIF&lt;/li&gt;
&lt;li&gt;Click the rotation direction&lt;/li&gt;
&lt;li&gt;Choose output format&lt;/li&gt;
&lt;li&gt;Download&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Tips for Best Results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use PNG for screenshots.&lt;/strong&gt; Screenshots have hard pixel edges that JPEG compression softens. Rotating a screenshot as PNG preserves every pixel exactly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do all rotations in one session.&lt;/strong&gt; If you need to rotate 90° twice to get 180°, do both clicks before downloading — don't save between steps. Each intermediate save is a quality hit for JPEGs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check EXIF before rotating.&lt;/strong&gt; If the photo looks correct on your phone but sideways everywhere else, the issue is the EXIF tag. A rotation tool that applies EXIF correction will fix this properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid re-uploading the same file repeatedly.&lt;/strong&gt; If you rotate, download, then upload the downloaded file to rotate again — that's two encode cycles instead of one. Do everything in one upload session.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Best approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JPEG, 90°/180°/270° rotation&lt;/td&gt;
&lt;td&gt;Lossless rotation (block transpose)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNG rotation&lt;/td&gt;
&lt;td&gt;Always lossless, any angle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple edits on a JPEG&lt;/td&gt;
&lt;td&gt;Convert to PNG → edit → convert back at the end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Photo sideways on some apps&lt;/td&gt;
&lt;td&gt;EXIF tag issue — apply pixel-level rotation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://ultimatetools.io/tools/image-tools/rotate-image/" rel="noopener noreferrer"&gt;Rotate Image tool&lt;/a&gt;&lt;/strong&gt; is free, works on any device, and supports all common image formats. No download, no signup.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What Is a QR Code and How Does It Work?</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Mon, 20 Apr 2026 02:07:49 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/what-is-a-qr-code-and-how-does-it-work-48dl</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/what-is-a-qr-code-and-how-does-it-work-48dl</guid>
      <description>&lt;p&gt;QR codes are everywhere — restaurant menus, packaging, event tickets, business cards. But most people have no idea how they actually store and transmit information.&lt;/p&gt;

&lt;p&gt;Here's exactly how they work, from the grid of dots to the decoded URL.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "QR" Stands For
&lt;/h2&gt;

&lt;p&gt;QR stands for &lt;strong&gt;Quick Response&lt;/strong&gt;. The format was invented in 1994 by Denso Wave (a Toyota subsidiary) for tracking automotive parts through manufacturing. The goal: a code that could be scanned faster than a barcode and store far more data.&lt;/p&gt;

&lt;p&gt;It worked. QR codes can hold up to 4,296 alphanumeric characters. A standard barcode holds around 20.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a QR Code Actually Is
&lt;/h2&gt;

&lt;p&gt;A QR code is a &lt;strong&gt;two-dimensional matrix of black and white squares&lt;/strong&gt; called modules. Unlike a barcode (which only encodes data horizontally), a QR code encodes data in both directions — which is why it stores so much more.&lt;/p&gt;

&lt;p&gt;The pattern is not random. Every region has a specific role:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finder patterns&lt;/strong&gt; — the three square-within-square symbols in three corners. They tell the scanner "this is a QR code" and establish orientation. This is why QR codes always have those three identical corner squares.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing patterns&lt;/strong&gt; — alternating black/white lines along the top and left edges. They let the scanner determine the size of each module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alignment patterns&lt;/strong&gt; — smaller position markers used in larger QR codes to correct for distortion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data modules&lt;/strong&gt; — the actual encoded content, spread across the remaining area.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error correction modules&lt;/strong&gt; — redundant data that lets scanners read the code even when part of it is damaged, dirty, or covered.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Data Is Encoded
&lt;/h2&gt;

&lt;p&gt;QR codes don't store a URL as plain readable text embedded in a grid. The encoding process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The input (URL, text, etc.) is converted to a binary sequence&lt;/li&gt;
&lt;li&gt;Error correction bytes are calculated using Reed-Solomon codes and added&lt;/li&gt;
&lt;li&gt;The full binary sequence is mapped to module positions on the grid&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;mask pattern&lt;/strong&gt; is applied — one of eight patterns that XOR with the data to prevent large solid areas of the same color (which confuse scanners)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is the distinctive black-and-white grid.&lt;/p&gt;




&lt;h2&gt;
  
  
  Error Correction Levels
&lt;/h2&gt;

&lt;p&gt;QR codes have four error correction levels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Recovery capacity&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L (Low)&lt;/td&gt;
&lt;td&gt;~7%&lt;/td&gt;
&lt;td&gt;Clean, undamaged print&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M (Medium)&lt;/td&gt;
&lt;td&gt;~15%&lt;/td&gt;
&lt;td&gt;Most everyday use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q (Quartile)&lt;/td&gt;
&lt;td&gt;~25%&lt;/td&gt;
&lt;td&gt;Industrial, outdoor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H (High)&lt;/td&gt;
&lt;td&gt;~30%&lt;/td&gt;
&lt;td&gt;Logo embedded in QR code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Higher error correction = more modules needed = larger, denser QR code. This is why QR codes with logos in the center still scan — the logo sits in the error correction zone, and the missing data is reconstructed.&lt;/p&gt;




&lt;h2&gt;
  
  
  What QR Codes Can Store
&lt;/h2&gt;

&lt;p&gt;QR codes encode more than just URLs. Common content types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;URL&lt;/strong&gt; — opens a website (most common)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain text&lt;/strong&gt; — a message or instructions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wi-Fi credentials&lt;/strong&gt; — SSID + password, for one-tap network connection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vCard&lt;/strong&gt; — contact details (name, phone, email, address)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email&lt;/strong&gt; — opens a pre-addressed compose window&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phone number&lt;/strong&gt; — opens the dialler&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location&lt;/strong&gt; — coordinates or a maps link&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendar event&lt;/strong&gt; — adds an event to the device calendar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The content type is determined by a prefix in the encoded data. For example, Wi-Fi QR codes start with &lt;code&gt;WIFI:S:NetworkName;T:WPA;P:password;;&lt;/code&gt;. The phone's QR scanner reads the prefix and opens the appropriate app automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Static vs Dynamic QR Codes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Static QR codes&lt;/strong&gt; encode the destination directly. If you want to change where the code points, you generate a new code and reprint it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic QR codes&lt;/strong&gt; encode a short redirect URL. The redirect target can be updated at any time without changing or reprinting the physical code. Most business and marketing QR codes are dynamic.&lt;/p&gt;

&lt;p&gt;Dynamic codes also support scan tracking — you can see how many times a code was scanned, from where, and on what device.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Generate and Scan QR Codes
&lt;/h2&gt;

&lt;p&gt;To generate a QR code with custom style, error correction level, and content type:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ultimatetools.io/tools/misc-tools/qr-code-generator/" rel="noopener noreferrer"&gt;QR Code Generator&lt;/a&gt;&lt;/strong&gt; — supports 10 content types (URL, Wi-Fi, vCard, text, and more), custom colors, dot shapes, and logo embedding.&lt;/p&gt;

&lt;p&gt;To scan a QR code from a screenshot, image file, or your camera:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ultimatetools.io/tools/misc-tools/qr-code-scanner/" rel="noopener noreferrer"&gt;QR Code Scanner&lt;/a&gt;&lt;/strong&gt; — upload any image or use your device camera. Decodes instantly in the browser, nothing uploaded to a server.&lt;/p&gt;

&lt;p&gt;Both are free, no login required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;A QR code is a 2D matrix where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three corner squares establish orientation&lt;/li&gt;
&lt;li&gt;Binary data is mapped to module positions&lt;/li&gt;
&lt;li&gt;Error correction allows partial damage recovery&lt;/li&gt;
&lt;li&gt;A mask pattern prevents scanning failures from solid regions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What looks like a random grid of dots is actually a precisely structured data format — one that your phone decodes in under a second.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Snake Game Tips — How to Beat Your High Score Without Hitting the Wall</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Mon, 20 Apr 2026 02:06:34 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/snake-game-tips-how-to-beat-your-high-score-without-hitting-the-wall-2n6k</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/snake-game-tips-how-to-beat-your-high-score-without-hitting-the-wall-2n6k</guid>
      <description>&lt;p&gt;Snake is one of the oldest browser games — and one of the hardest to master at high scores. Fast reflexes help, but strategy is what separates a score of 50 from a score of 500.&lt;/p&gt;

&lt;p&gt;After a few hundred games on the &lt;a href="https://ultimatetools.io/tools/fun-tools/snake/" rel="noopener noreferrer"&gt;free browser Snake game&lt;/a&gt;, here are the strategies that actually work.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Never Chase the Apple Directly
&lt;/h2&gt;

&lt;p&gt;The biggest beginner mistake: always heading straight for the apple.&lt;/p&gt;

&lt;p&gt;If the apple is in the top-right corner and you're in the bottom-left, going directly there means cutting across the middle of the board — right where your tail will be in 20 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think two moves ahead:&lt;/strong&gt; where will your tail be by the time you reach the apple?&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Hug the Edges Early
&lt;/h2&gt;

&lt;p&gt;When your snake is short (under 10 segments), the safest movement pattern is to circle the perimeter of the board. This keeps your tail behind you and gives you time to plan your next move.&lt;/p&gt;

&lt;p&gt;Edges are safe — there are no surprises from your own body when you're tracing the border.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The S-Pattern for Long Snakes
&lt;/h2&gt;

&lt;p&gt;Once your snake is more than half the board length, perimeter loops stop working — your tail catches up with your head. Switch to an &lt;strong&gt;S-pattern&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Move horizontally across one full row&lt;/li&gt;
&lt;li&gt;Drop down one row&lt;/li&gt;
&lt;li&gt;Move back across in the opposite direction&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This systematic sweep covers the entire board without crossing your own tail. It's slower, but it's the only reliable way to survive past a certain length.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Don't Panic-React — Commit to One Move
&lt;/h2&gt;

&lt;p&gt;When you notice you're heading toward your tail, the instinct is to react immediately. That usually kills you faster.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You &lt;strong&gt;can't reverse&lt;/strong&gt; — snake games don't allow 180° turns&lt;/li&gt;
&lt;li&gt;Three panicked inputs in a row almost always causes a collision&lt;/li&gt;
&lt;li&gt;One calm decision is better than rapid direction changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When something goes wrong: pause mentally, identify the nearest open path, and commit.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Count Your Available Space
&lt;/h2&gt;

&lt;p&gt;As the snake grows, mentally divide the board into zones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open zone&lt;/strong&gt; — clear space ahead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Danger zone&lt;/strong&gt; — areas close to your tail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe zone&lt;/strong&gt; — space you can reach without crossing your body&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always move toward open space, even if it means temporarily moving &lt;em&gt;away&lt;/em&gt; from the apple. Staying alive matters more than the next apple.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The Trap Technique
&lt;/h2&gt;

&lt;p&gt;Advanced players intentionally route their snake &lt;em&gt;around&lt;/em&gt; the apple without eating it, creating a clean approach path. This sounds counterintuitive, but it prevents you from boxing yourself in.&lt;/p&gt;

&lt;p&gt;If eating the apple now puts you in a bad position, loop around it once and approach from a better angle.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Avoid Corners
&lt;/h2&gt;

&lt;p&gt;Corners are where most snakes die. A corner means only one exit — and if your body is near that exit, there's no way out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; never commit to a corner unless you can clearly see the exit is open and will stay open.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Scan Before Moving
&lt;/h2&gt;

&lt;p&gt;Most snake games give you a brief moment before the snake moves. Use that time to scan the full board, not just the tile immediately ahead.&lt;/p&gt;

&lt;p&gt;One extra second of scanning per direction change prevents most avoidable deaths.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;These strategies work best with consistent practice in short sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ultimatetools.io/tools/fun-tools/snake/" rel="noopener noreferrer"&gt;Play Snake free online — no download, no login&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No install. No account. Works on any browser. High score saved locally so you can track improvement over sessions.&lt;/p&gt;

&lt;p&gt;Good luck beating 500.&lt;/p&gt;

</description>
      <category>gamechallenge</category>
      <category>beginners</category>
      <category>gamedev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Real-Time Cricket Scorer with Next.js 15, Prisma, and Canvas Scorecards</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Sun, 19 Apr 2026 15:40:04 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/building-a-real-time-cricket-scorer-with-nextjs-15-prisma-and-canvas-scorecards-354a</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/building-a-real-time-cricket-scorer-with-nextjs-15-prisma-and-canvas-scorecards-354a</guid>
      <description>&lt;p&gt;I play box cricket every other Friday. Six overs. Ten players. One scorer typing scores into a group chat while trying to watch the match.&lt;/p&gt;

&lt;p&gt;The tooling for this is terrible. So I built a proper one: a real-time scoring app where the scorer scores on their phone, everyone else watches live on a shared link, and the scorecard gets WhatsApp-shared as a generated image at the end.&lt;/p&gt;

&lt;p&gt;Here's how it's built — the architecture, the tricky parts, and the specific choices I made for a Hostinger VPS deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://ultimatetools.io/cricket/" rel="noopener noreferrer"&gt;Box Cricket Scorer — free, no login&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Creator phone           → POST /api/cricket/          → creates match row
Scorer phone            → PATCH /api/cricket/[id]/    → updates innings JSON
Viewers (everyone else) → GET /api/cricket/[id]/      → polls every 5 seconds
End of match            → Canvas API generates scorecard PNG → navigator.share()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key choices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Polling, not WebSockets&lt;/strong&gt; — score updates are infrequent (one ball every ~30 seconds). WebSockets add server complexity for no real gain here. 5-second polling is perfectly adequate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single JSON column per innings&lt;/strong&gt; — the entire innings state (runs, wickets, overs, balls, batters, bowlers) is one JSON blob. No relational joins needed per request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No auth&lt;/strong&gt; — creator gets a &lt;code&gt;shareId&lt;/code&gt; (6-char public ID) + &lt;code&gt;editToken&lt;/code&gt; (UUID in localStorage). Edit access is verified by sending &lt;code&gt;editToken&lt;/code&gt; in the request header.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Data Model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model CricketMatch {
  id          String    @id @default(cuid())
  shareId     String    @unique   // 6-char public ID
  editToken   String               // UUID — grants edit access
  lockToken   String?              // current active editor (handover)
  lockExpiry  DateTime?
  handoverPin String?              // 4-digit PIN for "pass the phone"
  pinExpiry   DateTime?

  team1Name   String
  team2Name   String
  totalOvers  Int
  status      String    @default("innings1")

  innings1    Json?
  innings2    Json?

  createdAt   DateTime  @default(now())
  expiresAt   DateTime  // auto-expire after 24h

  @@index([shareId])
  @@index([expiresAt])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The innings JSON structure:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;InningsData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;runs&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="nl"&gt;wickets&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="nl"&gt;currentOver&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="nl"&gt;currentBalls&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="c1"&gt;// legal balls in current over&lt;/span&gt;
  &lt;span class="nl"&gt;currentOverBalls&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;span class="c1"&gt;// ["0","1","W","nb","nb4","wd"]&lt;/span&gt;
  &lt;span class="nl"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;wides&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="nl"&gt;noBalls&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="nl"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BatterStats&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;currentBowler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BowlerStats&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OverSummary&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;       &lt;span class="c1"&gt;// completed overs archive&lt;/span&gt;
  &lt;span class="nl"&gt;dismissals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dismissal&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;bowlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BowlerStats&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;     &lt;span class="c1"&gt;// completed bowler figures&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The &lt;code&gt;processBall()&lt;/code&gt; Pure Function
&lt;/h2&gt;

&lt;p&gt;All scoring logic lives in one pure function. No side effects — it takes the current innings state and a ball string, returns the new state.&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processBall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;innings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InningsData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ball&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;span class="c1"&gt;// "0"|"1"|"2"|"3"|"4"|"6"|"wd"|"nb"|"W"&lt;/span&gt;
  &lt;span class="nx"&gt;totalOvers&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="nx"&gt;wicketType&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;span class="c1"&gt;// "Caught"|"Bowled"|"Run Out"|...&lt;/span&gt;
  &lt;span class="nx"&gt;nbExtraRuns&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="c1"&gt;// runs scored off the no-ball&lt;/span&gt;
  &lt;span class="nx"&gt;dismissedBatterIdx&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="c1"&gt;// for run-outs (striker or non-striker)&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;InningsData&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;inns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InningsData&lt;/span&gt; &lt;span class="o"&gt;=&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innings&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// deep clone&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;strikerIdx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onStrike&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;bowler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentBowler&lt;/span&gt;&lt;span class="o"&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;ball&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nb&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;nbRuns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nbExtraRuns&lt;/span&gt; &lt;span class="o"&gt;??&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;totalExtra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;nbRuns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;totalExtra&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noBalls&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;totalExtra&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;totalExtra&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noBalls&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="c1"&gt;// Store as "nb", "nb1", "nb4" etc. for display&lt;/span&gt;
    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentOverBalls&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;nbRuns&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`nb&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nbRuns&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// NOT a legal ball — don't increment currentBalls&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;ball&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;W&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="c1"&gt;// Run Out can dismiss either batter&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;si&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wicketType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Run Out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;dismissedBatterIdx&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;dismissedBatterIdx&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;strikerIdx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wickets&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="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentBalls&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="nx"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;balls&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wicketType&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Run Out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wickets&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dismissals&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;balls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;balls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sixes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sixes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;how&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wicketType&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Dismissed slot inherits the onStrike flag&lt;/span&gt;
    &lt;span class="c1"&gt;// so the incoming batter starts at the same end&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wasOnStrike&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onStrike&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;si&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;runs&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="na"&gt;balls&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="na"&gt;fours&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="na"&gt;sixes&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="na"&gt;onStrike&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wasOnStrike&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;runs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ball&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentBalls&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="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;strikerIdx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;runs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;strikerIdx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;balls&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runs&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;strikerIdx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;fours&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runs&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;strikerIdx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;sixes&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="nx"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;balls&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="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentOverBalls&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;ball&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Odd runs = strike rotates&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;runs&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&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="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;onStrike&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onStrike&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Over complete (6 legal balls)?&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;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentBalls&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&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;overRuns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentOverBalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;W&lt;/span&gt;&lt;span class="dl"&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;s&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;b&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wd&lt;/span&gt;&lt;span class="dl"&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;s&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nb&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;extra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&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="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;extra&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;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&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;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overs&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="na"&gt;over&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentOver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;overRuns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;wickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentOverBalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;W&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;balls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentOverBalls&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Archive bowler figures&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finishedBowler&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="nx"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;overs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overs&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="na"&gt;balls&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bowlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;bowler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;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;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bowlers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;finishedBowler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bowlers&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;finishedBowler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentOver&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="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentBalls&lt;/span&gt; &lt;span class="o"&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;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentOverBalls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentBowler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Strike rotates at end of over&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;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wickets&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentOver&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;totalOvers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;onStrike&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onStrike&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;inns&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;This pure function makes the scoring logic fully testable without any DB or React involved.&lt;/p&gt;




&lt;h2&gt;
  
  
  The API Routes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create match — &lt;code&gt;POST /api/cricket/&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;team1Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;team2Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalOvers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;shareId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nanoid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// e.g. "abc123"&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&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;expiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 24h&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cricketMatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;shareId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;team1Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;team2Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;totalOvers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;innings1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;innings1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;emptyInnings&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;innings2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;shareId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shareId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;editToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editToken&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 client stores &lt;code&gt;editToken&lt;/code&gt; in &lt;code&gt;localStorage&lt;/code&gt;. It's never in the URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update score — &lt;code&gt;PATCH /api/cricket/[shareId]/&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PATCH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shareId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;params&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;editToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-edit-token&lt;/span&gt;&lt;span class="dl"&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;lockToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-lock-token&lt;/span&gt;&lt;span class="dl"&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;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cricketMatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shareId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Verify edit access&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasAccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editToken&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;editToken&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lockToken&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;lockToken&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
     &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lockExpiry&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lockExpiry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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="nx"&gt;hasAccess&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cricketMatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shareId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updated&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;h3&gt;
  
  
  Poll for updates — &lt;code&gt;GET /api/cricket/[shareId]/&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shareId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;params&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;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cricketMatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shareId&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="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not found&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&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;Expired matches naturally 404. No cleanup cron needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pass the Phone — PIN Handover
&lt;/h2&gt;

&lt;p&gt;The "Hand Over Scoring" flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Editor taps "Hand Over" → &lt;code&gt;POST /api/cricket/[id]/handover/&lt;/code&gt; with their &lt;code&gt;editToken&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Server generates a 4-digit PIN, sets &lt;code&gt;pinExpiry = now + 2 minutes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;New scorer opens the view link, taps "Take Over", enters the PIN&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PUT /api/cricket/[id]/handover/&lt;/code&gt; validates the PIN, issues a new &lt;code&gt;lockToken&lt;/code&gt;, sets &lt;code&gt;lockExpiry = now + 90 seconds&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Previous editor's token is still valid (editToken never expires), but the lockToken is now in the new scorer's localStorage
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Claim the PIN&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PUT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cricketMatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;shareId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shareId&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="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handoverPin&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handoverPin&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;pin&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pinExpiry&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pinExpiry&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid or expired PIN&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&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;lockToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&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;lockExpiry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cricketMatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;shareId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shareId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lockToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lockExpiry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;handoverPin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pinExpiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;lockToken&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;
  
  
  Canvas Scorecard Generation
&lt;/h2&gt;

&lt;p&gt;The end-of-match scorecard is a 700×~700px PNG generated entirely in the browser using the Canvas API. No server-side image generation needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two-column layout:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Left: batting (name + dismissal inline, R(B), 4s, 6s)&lt;/li&gt;
&lt;li&gt;Right: bowling (name, O, R, W, ECO)&lt;/li&gt;
&lt;li&gt;Vertical divider at x=378&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key technique — dynamic height pre-calculation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before creating the canvas, calculate the height based on the data:&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;function&lt;/span&gt; &lt;span class="nf"&gt;blockHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InningsData&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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="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="nx"&gt;innings&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;batters&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dismissals&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&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="nx"&gt;innings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeBatters&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&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;12&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;bowlers&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bowlers&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&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;8&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;TEAM_H&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;SECT_H&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="nx"&gt;batters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bowlers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ROW_H&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;EXTRA_H&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;H&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;HEADER_H&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;blockHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innings1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;INS_GAP&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;blockHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innings2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;FOOTER_H&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="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="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Text truncation using measureText:&lt;/strong&gt;&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;function&lt;/span&gt; &lt;span class="nf"&gt;trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;span class="nx"&gt;maxPx&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;string&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;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measureText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;maxPx&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;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;measureText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;…&lt;/span&gt;&lt;span class="dl"&gt;"&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;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxPx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="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="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;…&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;QR code in the footer:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;qr-code-styling&lt;/code&gt; (already a project dependency) to generate a scannable QR pointing to &lt;code&gt;ultimatetools.io/cricket&lt;/code&gt;. The QR has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;White background + 8px white quiet zone (crucial for scanners to detect boundaries)&lt;/li&gt;
&lt;li&gt;Square dots (sharper edges after WhatsApp JPEG compression)&lt;/li&gt;
&lt;li&gt;Error correction "M" (keeps QR density low for better scannability on a 160px canvas element)
&lt;/li&gt;
&lt;/ul&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;qr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QRCodeStyling&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;QR_SIZE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&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;QR_SIZE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// render at 4x, draw at QR_SIZE&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://ultimatetools.io/cricket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dotsOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;square&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#1e293b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;backgroundOptions&lt;/span&gt;&lt;span class="p"&gt;:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#ffffff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cornersSquareOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;extra-rounded&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#4f46e5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;qrOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;errorCorrectionLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M&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;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;qr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRawData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Draw white quiet zone first, then QR on top&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#ffffff&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;fillRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;QR_X&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QR_Y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QR_SIZE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QR_SIZE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;16&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;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QR_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QR_Y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QR_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QR_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sharing the image:&lt;/strong&gt;&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shareToWhatsApp&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;blob&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataUrl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;File&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;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cricket-scorecard.png&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="na"&gt;type&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/png&lt;/span&gt;&lt;span class="dl"&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;isMobile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/Android|iPhone|iPad|iPod/i&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&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;isMobile&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;share&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;share&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cricket Scorecard&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Desktop fallback: download&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&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;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dataUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cricket-scorecard.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&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;
  
  
  Hiding Nav/Footer on the Match Page
&lt;/h2&gt;

&lt;p&gt;The match page is a full-screen experience — no top nav, no footer, no padding container. But the root layout in Next.js always renders.&lt;/p&gt;

&lt;p&gt;Solution: a client component that reads the current path and conditionally renders:&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;// components/ConditionalNav.tsx&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;usePathname&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&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;MATCH_PAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;cricket&lt;/span&gt;&lt;span class="se"&gt;\/[&lt;/span&gt;&lt;span class="sr"&gt;a-z0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\/?&lt;/span&gt;&lt;span class="sr"&gt;$/i&lt;/span&gt;&lt;span class="p"&gt;;&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;ConditionalNav&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;usePathname&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;MATCH_PAGE&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;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&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;ConditionalWrapper&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;usePathname&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;MATCH_PAGE&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;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mx-auto max-w-screen-2xl px-6 py-6 lg:py-8&lt;/span&gt;&lt;span class="dl"&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;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;Used in &lt;code&gt;app/layout.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConditionalNav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TopNav&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ConditionalNav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex-1"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConditionalWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ConditionalWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConditionalNav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Footer&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ConditionalNav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Deployment Notes (Hostinger VPS)
&lt;/h2&gt;

&lt;p&gt;This app runs on a Hostinger VPS Node.js hosting. A few constraints worth knowing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prisma with MariaDB adapter:&lt;/strong&gt;&lt;br&gt;
The project uses Prisma 7.7.0 with &lt;code&gt;@prisma/adapter-mariadb&lt;/code&gt; instead of the default MySQL connector. Standard Prisma MySQL connector has issues on Hostinger's MariaDB version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No WebSockets:&lt;/strong&gt;&lt;br&gt;
Hostinger VPS doesn't support persistent WebSocket connections well. Polling was the right call architecturally anyway — cricket scores update infrequently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;24-hour auto-expiry without a cron:&lt;/strong&gt;&lt;br&gt;
Expired matches return 404 on the next GET. Rows are never explicitly deleted — they just become permanently 404. The DB stays small because match data is tiny (~2–5KB per match JSON).&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Change
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If this grew to 1000 concurrent matches:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace polling with Server-Sent Events (SSE) — much lower overhead than polling, no WebSocket complexity&lt;/li&gt;
&lt;li&gt;Add a cleanup cron to actually delete expired rows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If I wanted player persistence:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a simple "save my team" feature using localStorage with a QR-shareable team JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For now, for a Friday box cricket match with 22 players, polling + one JSON row per match is exactly right.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ultimatetools.io/cricket/" rel="noopener noreferrer"&gt;Box Cricket Scorer — build your own live scorecard&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full code for the scoring logic, API routes, canvas generation, and handover PIN flow is all in the Next.js app. If you're building something similar — a live poll, a shared game score, any multi-device real-time-ish feature without WebSockets — the polling + single JSON column pattern is worth considering.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>cricket</category>
    </item>
    <item>
      <title>How to Score Box Cricket Live and Share the Scorecard on WhatsApp (Free, No App)</title>
      <dc:creator>Shaishav Patel</dc:creator>
      <pubDate>Sun, 19 Apr 2026 15:38:21 +0000</pubDate>
      <link>https://forem.com/shaishav_patel_271fdcd61a/how-to-score-box-cricket-live-and-share-the-scorecard-on-whatsapp-free-no-app-2a7a</link>
      <guid>https://forem.com/shaishav_patel_271fdcd61a/how-to-score-box-cricket-live-and-share-the-scorecard-on-whatsapp-free-no-app-2a7a</guid>
      <description>&lt;p&gt;Box cricket is fast. Six overs. Ten players. One scorer desperately typing in a group chat while also trying to watch the match.&lt;/p&gt;

&lt;p&gt;If you've played box cricket anywhere, you know the pain. The scorer misses balls. The chat fills with "what's the score??" every 30 seconds. And the final scorecard? A photo of someone's screen or a rough total at best.&lt;/p&gt;

&lt;p&gt;We built a free tool that fixes this: &lt;strong&gt;&lt;a href="https://ultimatetools.io/cricket/" rel="noopener noreferrer"&gt;Box Cricket Scorer&lt;/a&gt;&lt;/strong&gt; — live score sharing, ball-by-ball tracking, and a proper WhatsApp scorecard at the end. No app. No login. Works on any phone.&lt;/p&gt;

&lt;p&gt;Here's exactly how to use it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Create Your Match
&lt;/h2&gt;

&lt;p&gt;Go to &lt;strong&gt;&lt;a href="https://ultimatetools.io/cricket/" rel="noopener noreferrer"&gt;Box Cricket Scorer&lt;/a&gt;&lt;/strong&gt; and tap "Create Match."&lt;/p&gt;

&lt;p&gt;Fill in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Team 1 name&lt;/strong&gt; (e.g., "Office XI" or just "PJM")&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team 2 name&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total overs&lt;/strong&gt; (drag slider: 1–20 overs)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Who bats first&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tap &lt;strong&gt;Start Match →&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's it. No account. No login. The match page opens immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Share the Live Link
&lt;/h2&gt;

&lt;p&gt;In the top bar of the match page, tap &lt;strong&gt;📋 Share&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On mobile&lt;/strong&gt; → opens the native share sheet (pick WhatsApp, send to your group)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On desktop&lt;/strong&gt; → copies the link to clipboard (paste into WhatsApp Web)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyone who opens that link sees the live scoreboard, &lt;strong&gt;auto-updated every 5 seconds&lt;/strong&gt;. No refresh needed. Works on any browser, any phone.&lt;/p&gt;

&lt;p&gt;The viewers don't need an account. They don't need to download anything. Just open the link.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Score Ball by Ball
&lt;/h2&gt;

&lt;p&gt;Before the first ball, you'll need to enter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Both batter names&lt;/strong&gt; (striker and non-striker)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bowler's name&lt;/strong&gt; (before each over)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then score each ball by tapping:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Button&lt;/th&gt;
&lt;th&gt;What it records&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0, 1, 2, 3&lt;/td&gt;
&lt;td&gt;Dot ball or run(s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Four&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Six&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WD&lt;/td&gt;
&lt;td&gt;Wide (+1 run, no ball counted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NB&lt;/td&gt;
&lt;td&gt;No ball (pick extra runs scored off it)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W&lt;/td&gt;
&lt;td&gt;Wicket&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Wickets
&lt;/h3&gt;

&lt;p&gt;Tap &lt;strong&gt;W → WICKET&lt;/strong&gt;. Select how the batter was dismissed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caught, Bowled, Stumped, LBW, Hit Wicket&lt;/strong&gt; → records against striker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run Out&lt;/strong&gt; → asks which batter was dismissed (striker or non-striker)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  No Balls with Runs
&lt;/h3&gt;

&lt;p&gt;Tap &lt;strong&gt;NB&lt;/strong&gt; → select how many extra runs the batter hit off it (0, 1, 2, 3, 4, 6). The tool correctly records: 1 NB extra + batter runs. So a NB hit for 4 = 5 total runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Corrections
&lt;/h2&gt;

&lt;p&gt;Made a mistake? Two quick fixes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong bowler name?&lt;/strong&gt; Tap the ✏️ button next to the bowler name — but only before the first ball of that over is bowled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong striker?&lt;/strong&gt; Tap the non-striker's row in the batters table — it swaps the strike.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: End of Innings
&lt;/h2&gt;

&lt;p&gt;When all overs are done (or all wickets fall), the second innings starts automatically. The live link shows the target.&lt;/p&gt;

&lt;p&gt;You can also manually end an innings early — tap &lt;strong&gt;End Inning&lt;/strong&gt; in the scoring panel. This records any partial over and moves to the next innings.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Pass the Phone (If Needed)
&lt;/h2&gt;

&lt;p&gt;Tired of scoring? Tap &lt;strong&gt;🤝 Hand Over&lt;/strong&gt; in the top bar.&lt;/p&gt;

&lt;p&gt;A 4-digit PIN appears — valid for 2 minutes. The new scorer opens the match link, taps &lt;strong&gt;✏️ Take Over&lt;/strong&gt;, enters the PIN, and takes over scoring. The previous scorer automatically becomes view-only.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Share the Final Scorecard
&lt;/h2&gt;

&lt;p&gt;When the match completes, a result banner appears. Tap &lt;strong&gt;📤 Share Scorecard&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A scorecard image generates with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both innings: team total, overs faced&lt;/li&gt;
&lt;li&gt;Full batting stats: every batter, runs, balls, fours, sixes&lt;/li&gt;
&lt;li&gt;Full bowling stats: every bowler, overs, runs, wickets, economy&lt;/li&gt;
&lt;li&gt;Match result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On mobile: tap &lt;strong&gt;Share (WhatsApp)&lt;/strong&gt; → native share sheet opens → send directly to the group.&lt;br&gt;
On desktop: tap &lt;strong&gt;Download Scorecard&lt;/strong&gt; → saves as PNG → attach manually.&lt;/p&gt;

&lt;p&gt;The scorecard also has a QR code that links back to the scorer — so when someone in another friend group sees the image and wants to use the tool, they can scan and create their own match.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Example
&lt;/h2&gt;

&lt;p&gt;Here's a real match we scored (PJM vs TPDM, 6 overs):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1st innings — PJM: 29/5&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TPDM3 took 3 wickets in one over&lt;/li&gt;
&lt;li&gt;PJM1 stayed not out on 12(11)&lt;/li&gt;
&lt;li&gt;Over 3 had 2 No Balls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2nd innings — TPDM: 30/0 in 1 over&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TPDM1 hit 30 off 6 balls: 3 fours, 3 sixes&lt;/li&gt;
&lt;li&gt;Match over in the first over of the chase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full scorecard — with all of this recorded correctly, including the No Balls and extras — generated in seconds and went out on WhatsApp before everyone had even finished shaking hands.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Box Cricket Specifically
&lt;/h2&gt;

&lt;p&gt;Box cricket is different from standard gully cricket:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixed overs&lt;/strong&gt; (usually 4–8)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited fielding positions&lt;/strong&gt; (the box boundaries)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Balls count more&lt;/strong&gt; (frequent in box cricket due to pace bowling in tight spaces)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run Outs are common&lt;/strong&gt; (tight run-calling in a small ground)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Standard cricket scoring apps don't handle these well — they're built for 50-over club matches with 11-a-side full rosters. This tool is built specifically for the box cricket format.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ultimatetools.io/cricket/" rel="noopener noreferrer"&gt;Score box cricket live — free, no app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Free. No login. No app. Create a match, share the link, score ball by ball.&lt;/p&gt;

&lt;p&gt;Next Friday, the scorer in your group won't need to type in WhatsApp anymore. They just tap.&lt;/p&gt;

</description>
      <category>cricket</category>
      <category>webdev</category>
      <category>tooling</category>
      <category>sports</category>
    </item>
  </channel>
</rss>
