<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jungle Lee</title>
    <description>The latest articles on DEV Community by Jungle Lee (@jungle_lee_f55a0925201747).</description>
    <link>https://dev.to/jungle_lee_f55a0925201747</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3619229%2F26592987-45cf-46a4-bb25-38180b0d6173.png</url>
      <title>DEV Community: Jungle Lee</title>
      <link>https://dev.to/jungle_lee_f55a0925201747</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jungle_lee_f55a0925201747"/>
    <language>en</language>
    <item>
      <title>I Vibe-Coded a Full Clone of China's Most Infamous Farm Game on Sanity — Stealing Included</title>
      <dc:creator>Jungle Lee</dc:creator>
      <pubDate>Fri, 02 Oct 2026 08:59:03 +0000</pubDate>
      <link>https://dev.to/jungle_lee_f55a0925201747/i-vibe-coded-a-full-clone-of-chinas-most-infamous-farm-game-on-sanity-stealing-included-4dn1</link>
      <guid>https://dev.to/jungle_lee_f55a0925201747/i-vibe-coded-a-full-clone-of-chinas-most-infamous-farm-game-on-sanity-stealing-included-4dn1</guid>
      <description>&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Happy Farm&lt;/strong&gt; — a Chinese social farming game that had hundreds of millions of people setting 3AM alarms to harvest virtual radishes and steal their colleagues' strawberries. Yes, &lt;em&gt;steal&lt;/em&gt;. It was a whole thing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎮 &lt;strong&gt;Play it:&lt;/strong&gt; &lt;a href="https://sanity-completion.vercel.app" rel="noopener noreferrer"&gt;https://sanity-completion.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💾 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/junglelee0328-cloud/happy-farm" rel="noopener noreferrer"&gt;https://github.com/junglelee0328-cloud/happy-farm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🔑 &lt;strong&gt;Sanity Project ID:&lt;/strong&gt; &lt;code&gt;lha5ip1s&lt;/code&gt; (dataset &lt;code&gt;production&lt;/code&gt;, public)&lt;/li&gt;
&lt;li&gt;🗄️ &lt;strong&gt;Query the live farm data yourself:&lt;/strong&gt; &lt;code&gt;https://lha5ip1s.api.sanity.io/v2024-01-01/data/query/production?query=*[_type=="crop"]&lt;/code&gt;
Sign up with just an email (code verification, no OAuth needed), and you get your own farm: 24 plots, 21 crops, fertilizers, weeds, bugs, and friends to rob.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/PASTE_SCREENSHOT_HERE" alt="Farm screenshot" width="800" height="400"&gt;
## Why Sanity Is the Whole Game Engine
The strange idea: &lt;strong&gt;there is no game server. Sanity Content Lake IS the game engine.&lt;/strong&gt;
Every mechanic is a document:
| Sanity document type | Game concept |
|---|---|
| &lt;code&gt;crop&lt;/code&gt; | 21 crops, Lv.1 radish → Lv.100 "Millennium Ginseng" |
| &lt;code&gt;player&lt;/code&gt; | level, XP, coins, backpack inventory |
| &lt;code&gt;plot&lt;/code&gt; | 24 fields with 3-tier thresholds (normal → red soil → black soil) |
| &lt;code&gt;fertilizer&lt;/code&gt; | 3 fertilizer tiers with speed/yield boosts |
| &lt;code&gt;gameRule&lt;/code&gt; | &lt;strong&gt;the entire game balance as content&lt;/strong&gt; |
The &lt;code&gt;gameRule&lt;/code&gt; document is my favorite part. It holds the full 99-row XP curve (&lt;code&gt;40 × level^2.2&lt;/code&gt;, tuned so max level takes ~a year), watering boost, wither time, weed/bug probability, daily steal limit, steal ratio, and land-tier bonuses. Open Sanity Studio, change &lt;code&gt;stealRatio&lt;/code&gt; from &lt;code&gt;0.2&lt;/code&gt; to &lt;code&gt;0.5&lt;/code&gt;, and every player's stealing economics change &lt;strong&gt;live&lt;/strong&gt;. Game balance is now a content editor's job.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Load a farm with crops AND fertilizers resolved in one round trip
*[_type == "plot" &amp;amp;&amp;amp; owner._ref == $pid] | order(index asc) {
  ..., crop-&amp;gt;, fertilizer-&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writes use Sanity &lt;strong&gt;transactions&lt;/strong&gt; so multi-document game actions stay atomic — e.g. stealing from a friend:&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&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;plotId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;stolenBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;thieves&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;me&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nickname&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;span class="p"&gt;.&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;myId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;coins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stolenToday&lt;/span&gt;&lt;span class="p"&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="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Land upgrades are pure data too: each plot has &lt;code&gt;unlockLevel&lt;/code&gt; / &lt;code&gt;redLevel&lt;/code&gt; / &lt;code&gt;blackLevel&lt;/code&gt;, and the effective tier is derived from the player's level — all 24 plots are normal soil by Lv.25, red by Lv.60, black by Lv.100.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vibe-Coding Story (honest version)
&lt;/h2&gt;

&lt;p&gt;The UI went through a humbling arc. My first three rounds built the farm with DOM boxes, CSS gradients and emoji — and every round the feedback was "it looks bad". The fix that finally worked was switching medium entirely: &lt;strong&gt;the farm scene is hand-drawn SVG&lt;/strong&gt; on a true isometric (2:1 diamond) coordinate system with painter's-algorithm depth sorting, per-plant deterministic jitter, and every animated prop (spinning windmill, pecking chickens, rippling pond) as its own SVG component. HUD and modals stay in DOM/CSS. Lesson logged: illustration belongs in SVG/canvas, chrome belongs in DOM.&lt;br&gt;
Stack: React + Vite frontend, Clerk (free tier, email-code auth) for real accounts, Vercel for hosting with push-to-deploy CI/CD, Sanity Studio as the admin panel / balance-tweaking console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Battle Scars
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CORS&lt;/strong&gt;: first production deploy failed with a cryptic &lt;code&gt;net::ERR_FAILED&lt;/code&gt; — Sanity's CORS origin list only whitelists localhost by default. One &lt;code&gt;sanity cors add&lt;/code&gt; later…&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo shortcut I'm not proud of&lt;/strong&gt;: the write token is currently in the frontend bundle (fine for a hackathon demo on a public dataset, terrible for production). The real fix — moving writes behind serverless functions with session verification — is documented in the README as next step.&lt;/li&gt;
&lt;li&gt;Hover tooltips on isometric SVG plots needed a delayed-close + hoverable-card pattern to stay usable.
## What's Next&lt;/li&gt;
&lt;li&gt;Move all writes to Vercel serverless functions (JWT-verified) and rotate the token&lt;/li&gt;
&lt;li&gt;Daily tasks &amp;amp; leaderboards (both are one GROQ query away)&lt;/li&gt;
&lt;li&gt;Real-time sync via Sanity Listen instead of 5s polling
Built in 4 days with an AI pair programmer, way too much nostalgia, and one very patient Sanity dataset. 🌻
## Picture Show&lt;/li&gt;
&lt;li&gt;MainInterface
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe35r75cmhq4572q80i7a.png" alt=" " width="799" height="435"&gt;
&lt;/li&gt;
&lt;li&gt;Store
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv9mb7e0ml9qmzqq7wq05.png" alt=" " width="800" height="802"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have Fun On this game！&lt;/p&gt;

</description>
      <category>sanitychallenge</category>
      <category>devchallenge</category>
      <category>gamedev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
