<?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: Ado Daniel Nj</title>
    <description>The latest articles on DEV Community by Ado Daniel Nj (@adodanieln).</description>
    <link>https://dev.to/adodanieln</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%2F3848240%2Fa6b00e2e-3734-4b29-b3db-0f1f5c8a281c.png</url>
      <title>DEV Community: Ado Daniel Nj</title>
      <link>https://dev.to/adodanieln</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adodanieln"/>
    <language>en</language>
    <item>
      <title>Archive — A Narrative Investigation Game About Curating Human History</title>
      <dc:creator>Ado Daniel Nj</dc:creator>
      <pubDate>Mon, 22 Jun 2026 03:23:20 +0000</pubDate>
      <link>https://dev.to/adodanieln/archive-a-narrative-investigation-game-about-curating-human-history-1ca0</link>
      <guid>https://dev.to/adodanieln/archive-a-narrative-investigation-game-about-curating-human-history-1ca0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Archive — The Last Historian of Humanity
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What if history wasn't discovered... but selected?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;History is often treated as something permanent—something waiting to be uncovered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Archive&lt;/strong&gt; asks a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;What happens when humanity loses the ability to tell the difference between truth, memory, and fabrication?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You play as the final Archivist after the collapse of civilization. Humanity's knowledge survives, but it has become fragmented, contradictory, and corrupted. Your responsibility is no longer to preserve everything—you must decide &lt;strong&gt;what deserves to be remembered.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every decision changes the civilization that will inherit your version of history.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Archive is a narrative investigation game where players reconstruct humanity's past by examining historical memories, investigating evidence, resolving contradictions, and deciding what becomes official history.&lt;/p&gt;

&lt;p&gt;Unlike traditional mystery games, there is rarely a perfect answer.&lt;/p&gt;

&lt;p&gt;Instead, every investigation asks questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should conflicting memories be preserved?&lt;/li&gt;
&lt;li&gt;Is stability more important than truth?&lt;/li&gt;
&lt;li&gt;Can compassion justify rewriting history?&lt;/li&gt;
&lt;li&gt;If no one can verify the past, what does "truth" even mean?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each recovered memory is presented as a historical article. Players investigate through classified documents, research papers, witness testimonies, forensic reports, personal journals, and government archives before making irreversible decisions.&lt;/p&gt;

&lt;p&gt;Those decisions reshape the civilization that follows.&lt;/p&gt;

&lt;p&gt;By the end of the game, players don't simply receive a score—they discover the kind of society they created.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why It Fits the Theme
&lt;/h2&gt;

&lt;p&gt;The June Solstice represents a turning point.&lt;/p&gt;

&lt;p&gt;It is the moment when one season gives way to another, when light begins yielding to darkness, or darkness begins yielding to light.&lt;/p&gt;

&lt;p&gt;Archive explores a similar transition.&lt;/p&gt;

&lt;p&gt;Not between seasons...&lt;/p&gt;

&lt;p&gt;but between &lt;strong&gt;certainty and uncertainty.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Human civilization reaches a moment where objective history begins to disappear. Every decision the player makes determines what survives that transition.&lt;/p&gt;

&lt;p&gt;The game is ultimately about how societies evolve through the stories they choose to preserve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Gameplay
&lt;/h2&gt;

&lt;p&gt;Players:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recover fragmented historical memories.&lt;/li&gt;
&lt;li&gt;Explore multiple investigation paths.&lt;/li&gt;
&lt;li&gt;Analyze evidence with varying reliability and bias.&lt;/li&gt;
&lt;li&gt;Resolve contradictions between historical accounts.&lt;/li&gt;
&lt;li&gt;Decide which memories become part of civilization's official record.&lt;/li&gt;
&lt;li&gt;Shape the philosophical identity of humanity's future.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No two playthroughs necessarily create the same civilization.&lt;/p&gt;




&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://the-last-memory-of-earth.vercel.app/demo.mp4" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;the-last-memory-of-earth.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The video demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Archive exploration&lt;/li&gt;
&lt;li&gt;Investigation system&lt;/li&gt;
&lt;li&gt;Evidence paths&lt;/li&gt;
&lt;li&gt;Decision making&lt;/li&gt;
&lt;li&gt;Civilization evaluation&lt;/li&gt;
&lt;li&gt;Multiple possible historical outcomes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sorry about the low vibe during that, i was extremely tired 😅.&lt;/p&gt;




&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;GitHub Repository:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(&lt;a href="https://github.com/acetennyson/The-Last-Memory-of-Earth/" rel="noopener noreferrer"&gt;https://github.com/acetennyson/The-Last-Memory-of-Earth/&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Test Live
&lt;/h2&gt;

&lt;p&gt;Public domain:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(&lt;a href="https://the-last-memory-of-earth.vercel.app/" rel="noopener noreferrer"&gt;https://the-last-memory-of-earth.vercel.app/&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Archive was designed around one core philosophy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;History is not merely remembered, it is curated.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rather than treating lore as optional background reading, every piece of narrative content exists as gameplay.&lt;/p&gt;

&lt;p&gt;The project includes systems for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;branching investigations&lt;/li&gt;
&lt;li&gt;layered evidence&lt;/li&gt;
&lt;li&gt;contradictory historical records&lt;/li&gt;
&lt;li&gt;civilization philosophy&lt;/li&gt;
&lt;li&gt;procedural ending evaluation&lt;/li&gt;
&lt;li&gt;markdown-powered historical articles&lt;/li&gt;
&lt;li&gt;dynamic narrative progression&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The game is built using React, TypeScript, Capacitor, and a fully data-driven content architecture that allows new story arcs, memories, investigations, and evidence to be added without modifying gameplay systems.&lt;/p&gt;

&lt;p&gt;One of the biggest design challenges was avoiding "good" and "bad" endings.&lt;/p&gt;

&lt;p&gt;Instead, the game evaluates the civilization created by the player's accumulated decisions across values such as Truth, Freedom, Compassion, Progress, Power, and Legacy.&lt;/p&gt;

&lt;p&gt;The result is an ending that reflects &lt;strong&gt;who humanity became&lt;/strong&gt;, rather than simply whether the player succeeded.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Best Ode to Alan Turing
&lt;/h3&gt;

&lt;p&gt;Archive is deeply inspired by questions surrounding computation, information, and artificial intelligence.&lt;/p&gt;

&lt;p&gt;As the Archive evolves, it begins connecting information in ways humans never anticipated, raising questions similar to those explored by Alan Turing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can a machine create knowledge?&lt;/li&gt;
&lt;li&gt;Can intelligence emerge from information alone?&lt;/li&gt;
&lt;li&gt;When does computation become interpretation?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than focusing on AI as a tool, the game explores AI as a historical force capable of reshaping civilization itself.&lt;/p&gt;




&lt;h3&gt;
  
  
  Best Google AI Usage
&lt;/h3&gt;

&lt;p&gt;Google AI played an important role throughout development.&lt;/p&gt;

&lt;p&gt;It was used as a creative collaborator for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;worldbuilding&lt;/li&gt;
&lt;li&gt;historical consistency&lt;/li&gt;
&lt;li&gt;narrative iteration&lt;/li&gt;
&lt;li&gt;investigation design&lt;/li&gt;
&lt;li&gt;story arc refinement&lt;/li&gt;
&lt;li&gt;gameplay balancing&lt;/li&gt;
&lt;li&gt;technical architecture discussions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than generating finished content automatically, Google AI became part of the iterative design process, helping refine ideas while preserving a consistent narrative vision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Archive isn't about winning.&lt;/p&gt;

&lt;p&gt;It isn't even about discovering the truth.&lt;/p&gt;

&lt;p&gt;It's about confronting one uncomfortable possibility:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;History never remembers everything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only what someone chose to preserve.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>The Weekend I Finally Finished 17 Projects</title>
      <dc:creator>Ado Daniel Nj</dc:creator>
      <pubDate>Fri, 12 Jun 2026 00:09:11 +0000</pubDate>
      <link>https://dev.to/adodanieln/the-weekend-i-finally-finished-17-projects-3lpl</link>
      <guid>https://dev.to/adodanieln/the-weekend-i-finally-finished-17-projects-3lpl</guid>
      <description>&lt;p&gt;You know that feeling when you open a folder you haven't touched in months and find... yourself?&lt;/p&gt;

&lt;p&gt;Not metaphorically. I mean literally — old projects you don't even remember writing, with code that's somehow &lt;em&gt;good&lt;/em&gt;? And you're sitting there like, "wait, &lt;em&gt;I&lt;/em&gt; wrote this?"&lt;/p&gt;

&lt;p&gt;That was my Saturday.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mess
&lt;/h2&gt;

&lt;p&gt;I decided it was time. My portfolio was a ghost town. My GitHub was full of half-finished dreams. You know the drill — you start a project, get 80% there, then a shinier thing comes along and suddenly it's gathering dust for a year.&lt;/p&gt;

&lt;p&gt;I had 17 of those.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LIS&lt;/strong&gt; — An AI tutoring app for Cameroonian students. Works offline. Has a &lt;em&gt;Learning DNA&lt;/em&gt; that adapts to how you study. I literally submitted this for a social impact prize and then... just left it there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mbende Stay&lt;/strong&gt; — A booking platform with mobile money payments. Fully functional. Zero visitors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"418 I'm a Teapot"&lt;/strong&gt; — A game where you play as a teapot that can never win and an AI &lt;em&gt;roasts you&lt;/em&gt; every time you fail. I'm not kidding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;elementTouch&lt;/strong&gt; — A JavaScript library I built from scratch. Drag and drop. Typewriter effects, and more. A whole code editor. Just... sitting there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I'm looking at all of this thinking: &lt;em&gt;bro, you really just built things and then forgot to tell anyone?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I made a decision: polish them all, put them in my portfolio, and actually show the work.&lt;/p&gt;

&lt;p&gt;First step: build an admin import tool. One click, seed 17 projects into Firestore. Felt like a genius.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then the Universe Laughed
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;ReferenceError: location is not defined
    at v (.next/server/app/admin/blog/new/page.js:1:11128)
    at y (.next/server/app/admin/projects/import/page.js:1:16875)
    at y (.next/server/app/admin/projects/new/page.js:1:8262)
    at j (.next/server/app/admin/resumes/new/page.js:1:4056)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four pages. Dead. And I have &lt;em&gt;no idea&lt;/em&gt; where &lt;code&gt;location&lt;/code&gt; is coming from.&lt;/p&gt;

&lt;p&gt;You ever debug a Firebase + Next.js app and feel like you're chasing a ghost? Because I was definitely chasing a ghost.&lt;/p&gt;

&lt;p&gt;Turns out, Firebase's SDK references &lt;code&gt;location&lt;/code&gt; (as in &lt;code&gt;window.location&lt;/code&gt;) at the module level. Not inside a function you call. At &lt;em&gt;import time&lt;/em&gt;. So when Next.js tries to server-render your admin page, Node.js is like "what's &lt;code&gt;location&lt;/code&gt;? never heard of her."&lt;/p&gt;

&lt;p&gt;The fix?&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dynamic&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/dynamic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;dynamic&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;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./page.client&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;ssr&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wrapped each page in &lt;code&gt;next/dynamic&lt;/code&gt; with &lt;code&gt;ssr: false&lt;/code&gt;. Basically told Next.js: &lt;em&gt;"Don't even look at this page on the server. Not once. Not ever. Just send the JS and let the browser figure it out."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Moved all the actual code to &lt;code&gt;page.client.tsx&lt;/code&gt;. Build passed. I felt like a god for approximately 12 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Betrayal
&lt;/h2&gt;

&lt;p&gt;Then I looked at my blog.&lt;/p&gt;

&lt;p&gt;You see, my blog posts are stored in Firestore as HTML. And somewhere along the way, I'd written them with beautiful Tailwind dark mode classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"not-dark:bg-white dark:bg-gray-900 dark:shadow-lg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the problem: &lt;strong&gt;Tailwind v4's JIT compiler has never heard of your database.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It scans your source files — your &lt;code&gt;app/&lt;/code&gt;, your &lt;code&gt;components/&lt;/code&gt;, your &lt;code&gt;lib/&lt;/code&gt;. It does NOT make a Firebase query to find class names.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;dark:bg-gray-900&lt;/code&gt; in a blog post? Never compiled. It renders as plain text with zero styles. My "beautiful blog content" was just... floating in the void. No background. No shadow. Nothing.&lt;/p&gt;

&lt;p&gt;I sat there staring at the screen feeling personally betrayed by a CSS framework.&lt;/p&gt;

&lt;p&gt;But hey, I fixed it:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;prepareHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&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="kr"&gt;string&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;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(?:&lt;/span&gt;&lt;span class="sr"&gt;not-&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;dark:&lt;/span&gt;&lt;span class="se"&gt;[^\s&lt;/span&gt;&lt;span class="sr"&gt;"'&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/g&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="nf"&gt;trim&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;Strip the prefixes. Then override everything with CSS variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.blog-content&lt;/span&gt; &lt;span class="nc"&gt;.text-gray-900&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.blog-content&lt;/span&gt; &lt;span class="nc"&gt;.bg-white&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.blog-content&lt;/span&gt; &lt;span class="nc"&gt;.bg-gray-900&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.blog-content&lt;/span&gt; &lt;span class="nc"&gt;.shadow-md&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.blog-content&lt;/span&gt; &lt;span class="nc"&gt;.shadow-lg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&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;Now every hardcoded class respects the theme. Dark mode, light mode, it just works. The blog content is finally alive.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then There Were 17
&lt;/h2&gt;

&lt;p&gt;So now I've got 17 projects in my portfolio. On one page. One. Grid. Screen. You see the problem.&lt;/p&gt;

&lt;p&gt;I needed pagination. Not the boring kind — something that actually looks good:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chevrons that bounce when you hover&lt;/li&gt;
&lt;li&gt;Page numbers with the accent color on active&lt;/li&gt;
&lt;li&gt;Ellipsis that only show when there's actually a gap&lt;/li&gt;
&lt;li&gt;A quiet little "Page X of Y · N projects" at the bottom&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The button logic nearly broke me. You try handling "first page, last page, exactly 5 pages, exactly 6 pages, ellipsis-or-no-ellipsis" without your brain melting:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&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="na"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;number&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="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;totalPages&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&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;push&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&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;push&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;safePage&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&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;push&lt;/span&gt;&lt;span class="p"&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;// ... more edge cases&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;p&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;safePage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works. It's clean. I'm proud of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Actually Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. You probably have better code than you think
&lt;/h3&gt;

&lt;p&gt;Open that old project. The one you're embarrassed about. I bet it's not as bad as you remember. We're all way too hard on ourselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Admin pages don't need SSR
&lt;/h3&gt;

&lt;p&gt;If your page is behind a login wall, there's zero SEO value in server-rendering it. Just use &lt;code&gt;ssr: false&lt;/code&gt; and save yourself the headache. Your admin panel will survive without being indexed by Google.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Tailwind v4 JIT is amazing until your content is dynamic
&lt;/h3&gt;

&lt;p&gt;If your HTML lives in a database, plan ahead. CSS variables are your escape hatch. Hardcoded utility classes in data strings will come back to haunt you.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. A portfolio is never done
&lt;/h3&gt;

&lt;p&gt;Every time I thought "okay this is final," I found another project, another bug, another thing to improve. That's not failure. That's just what building in public looks like.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Left
&lt;/h2&gt;

&lt;p&gt;elementTouch still has 2 unfinished tasks out of 7. I'll get to them.&lt;/p&gt;

&lt;p&gt;And there's a missing &lt;code&gt;admin/blog/edit/[id]&lt;/code&gt; page that keeps crashing the build. Just one day.&lt;/p&gt;




&lt;p&gt;Anyway, that's my weekend. What's the most embarrassing bug you've fought with Next.js + Firebase? Drop it below so I don't feel alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://adodanielnj.vercel.app/" rel="noopener noreferrer"&gt;My Portfolio&lt;/a&gt;
&lt;/h2&gt;

</description>
      <category>nextjs</category>
      <category>firebase</category>
      <category>portfolio</category>
      <category>architecture</category>
    </item>
    <item>
      <title>We Built a “Stripe for African Mobile Money” — Then Discovered Why It Couldn’t Exist</title>
      <dc:creator>Ado Daniel Nj</dc:creator>
      <pubDate>Thu, 16 Apr 2026 09:31:42 +0000</pubDate>
      <link>https://dev.to/adodanieln/we-built-a-stripe-for-african-mobile-money-then-discovered-why-it-couldnt-exist-3ica</link>
      <guid>https://dev.to/adodanieln/we-built-a-stripe-for-african-mobile-money-then-discovered-why-it-couldnt-exist-3ica</guid>
      <description>&lt;p&gt;As developers, we love hard problems.&lt;/p&gt;

&lt;p&gt;So we set out to solve one that almost every African developer has faced:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Why do I need a different integration for every mobile money provider in every country?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our answer was a project we called &lt;strong&gt;UAPL (Unified African Payment Layer)&lt;/strong&gt; — a single API that could sit on top of providers like &lt;strong&gt;Campay&lt;/strong&gt;, &lt;strong&gt;Flutterwave&lt;/strong&gt;, MTN MoMo, Orange Money, Airtel Money, and others.&lt;/p&gt;

&lt;p&gt;The goal?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Give developers a Stripe-like experience for mobile money across Africa.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Technically, we nailed it.&lt;/p&gt;

&lt;p&gt;Legally, we walked into a wall we didn’t know existed.&lt;/p&gt;

&lt;p&gt;This post is about the architecture we built, the performance we achieved, the failures we discovered &lt;strong&gt;before launch&lt;/strong&gt;, and why we had to pivot the entire idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 The Technical Vision
&lt;/h2&gt;

&lt;p&gt;UAPL was designed as an orchestration layer with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Adapter pattern&lt;/strong&gt; for each provider&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Unified Payment Object (UPO)&lt;/strong&gt; to normalize all APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis-backed transaction ledger&lt;/strong&gt; for USSD/async flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket events&lt;/strong&gt; for real-time transaction updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic provider failover&lt;/strong&gt; (MTN → Orange if one fails)&lt;/li&gt;
&lt;li&gt;A clean SDK so devs never see provider differences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From a systems design perspective, it was beautiful.&lt;/p&gt;

&lt;p&gt;You could write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;uapl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;XAF&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;677000000&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;And UAPL would figure out the provider, handle retries, normalize responses, and notify your app in real time.&lt;/p&gt;

&lt;p&gt;We load-tested the orchestration layer. It was fast. Stateless. Horizontally scalable. Provider-agnostic. Exactly what African devs need.&lt;/p&gt;




&lt;h2&gt;
  
  
  💥 The Discovery That Changed Everything
&lt;/h2&gt;

&lt;p&gt;While preparing for real integrations with &lt;strong&gt;Banque des États de l'Afrique Centrale (BEAC)&lt;/strong&gt; zone providers, we went deeper into regulatory documentation and aggregator terms.&lt;/p&gt;

&lt;p&gt;And then we saw it.&lt;/p&gt;

&lt;p&gt;A sentence that changes everything for fintech builders:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Any entity that routes, orchestrates, settles, or intermediates payments between merchants and licensed providers may be classified as a Payment Service Provider (PSP).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You never touch the money&lt;/li&gt;
&lt;li&gt;You only pass API calls&lt;/li&gt;
&lt;li&gt;You act as “middleware”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Legally, you are &lt;strong&gt;in the payment flow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need PSP licenses&lt;/li&gt;
&lt;li&gt;You fall under central bank regulation&lt;/li&gt;
&lt;li&gt;Aggregators can block you for “reselling” their infrastructure&lt;/li&gt;
&lt;li&gt;Settlement logic makes you a financial intermediary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In CEMAC, this is enforced under &lt;strong&gt;Commission Bancaire de l'Afrique Centrale (COBAC)&lt;/strong&gt; oversight.&lt;/p&gt;

&lt;p&gt;We weren’t building a developer tool.&lt;/p&gt;

&lt;p&gt;We were accidentally building an unlicensed financial institution.&lt;/p&gt;




&lt;h2&gt;
  
  
  ❌ The Failures We Caught Early
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Automatic Failover Is Not Allowed&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Our proudest feature:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If Campay's MTN API fails → automatically retry with Tranzak's MTN API&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds smart. Completely illegal in practice.&lt;/p&gt;

&lt;p&gt;Why? Merchants are KYB’d &lt;em&gt;per provider&lt;/em&gt;. You can’t reroute their transactions without explicit registration.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;strong&gt;Settlement Scheduling Makes You a PSP&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We planned:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“UAPL pays out to developers on a schedule”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That single sentence legally converts you into a money handler.&lt;/p&gt;

&lt;p&gt;Instant PSP classification.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. &lt;strong&gt;Aggregators Can Shut You Down Overnight&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Platforms like &lt;strong&gt;Flutterwave&lt;/strong&gt; or &lt;strong&gt;Campay&lt;/strong&gt; can decide you are “reselling” their API as a platform.&lt;/p&gt;

&lt;p&gt;They don’t need to argue. They just revoke your keys.&lt;/p&gt;

&lt;p&gt;Game over.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. &lt;strong&gt;The Illusion of “One License for Many Countries”&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We assumed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Get a CEMAC license → operate everywhere in the zone&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Country-level approvals&lt;/li&gt;
&lt;li&gt;Telco-level approvals&lt;/li&gt;
&lt;li&gt;Data residency considerations&lt;/li&gt;
&lt;li&gt;Central bank notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A regional license is not a passport. It’s permission to start more paperwork.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 The Big Realization
&lt;/h2&gt;

&lt;p&gt;We thought we were building:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Stripe for African mobile money&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But what we needed to build was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Stripe.js for African mobile money&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A critical difference.&lt;/p&gt;

&lt;p&gt;One is a regulated payments company.&lt;/p&gt;

&lt;p&gt;The other is a developer SDK.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 The Pivot
&lt;/h2&gt;

&lt;p&gt;We redesigned UAPL to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;pure SDK and orchestration library&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Developers bring &lt;strong&gt;their own provider credentials&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No routing of funds&lt;/li&gt;
&lt;li&gt;No settlement&lt;/li&gt;
&lt;li&gt;No payout control&lt;/li&gt;
&lt;li&gt;No position in the legal payment chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, that designs UAPL as a software. Not fintech.&lt;/p&gt;

&lt;p&gt;And that version is &lt;strong&gt;legally unstoppable&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 Lessons for Developers Building in Fintech
&lt;/h2&gt;

&lt;p&gt;Before writing code, answer these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who is the regulator in this space?&lt;/li&gt;
&lt;li&gt;Does my system decide where money goes?&lt;/li&gt;
&lt;li&gt;Does my system delay, batch, or settle funds?&lt;/li&gt;
&lt;li&gt;Am I unintentionally “intermediating” transactions?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If yes, you are not building a tool.&lt;/p&gt;

&lt;p&gt;You are building a financial institution.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗣️ Why I’m Sharing This
&lt;/h2&gt;

&lt;p&gt;Because from an engineering standpoint, this was one of the best systems we’ve ever designed.&lt;/p&gt;

&lt;p&gt;And it still would have failed.&lt;/p&gt;

&lt;p&gt;Not due to bugs.&lt;br&gt;
Not due to scaling.&lt;br&gt;
Not due to product-market fit.&lt;/p&gt;

&lt;p&gt;But because we didn’t start with regulatory research.&lt;/p&gt;

&lt;p&gt;If you’re a developer building in Africa’s fintech space, learn from this before you spend months building the wrong thing.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Research first. Architect second. Code third.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>fintech</category>
      <category>architecture</category>
      <category>systemdesign</category>
      <category>startup</category>
    </item>
    <item>
      <title>418: I'm a Teapot</title>
      <dc:creator>Ado Daniel Nj</dc:creator>
      <pubDate>Mon, 13 Apr 2026 07:24:53 +0000</pubDate>
      <link>https://dev.to/adodanieln/youre-a-bad-gamer-2ai0</link>
      <guid>https://dev.to/adodanieln/youre-a-bad-gamer-2ai0</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/aprilfools-2026"&gt;DEV April Fools Challenge&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;418: I'm a Teapot (and You're a Bad Gamer)&lt;/p&gt;

&lt;p&gt;A high performance, SEO-optimized, AI integrated gaming platform built solely to ensure the player &lt;br&gt;
never wins.&lt;/p&gt;

&lt;p&gt;You control a teapot. You run. You jump. You die. On every death, Gemini analyzes your failure and &lt;br&gt;
adjusts the physics to be slightly more insulting next time. There is no finish line. There never was.&lt;/p&gt;

&lt;p&gt;Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Endless Runner&lt;/strong&gt; — one button to jump. Except it's not always that button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotating Controls&lt;/strong&gt; — the jump key silently changes every run. The HUD shows last run's key. This 
is documented nowhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input Gaslighting&lt;/strong&gt; — every ~10th jump has a 200ms delay. Wall contact permanently degrades your 
grip. Mash the keyboard and gravity drops to near zero — the teapot floats away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Vibe Check&lt;/strong&gt; — on every death, your stats (jumps, wall hugs, rage mashes) are sent to Gemini. 
It returns new physics values and a personalized roast. Gets meaner over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global Loss Leaderboard&lt;/strong&gt; — powered by Firebase. Tracks deaths, not wins. "You are ranked #3 most 
pathetic globally."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fake Patch Notes&lt;/strong&gt; — v2.4.1 changelog includes "Fixed bug where player could win (unintended)" and
"Removed finish line (was causing confusion)."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fake Loading Tips&lt;/strong&gt; — "Tip: There is no finish line. There never was."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken Controls Page&lt;/strong&gt; — /controls documents the controls incorrectly. Never acknowledged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support Ticket System&lt;/strong&gt; — after 5 deaths a "Report a Bug" button appears. Submit a complaint, 
receive HTTP 418 and a Gemini-generated gaslighting response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share Your Shame&lt;/strong&gt; — one click shares your death count and AI roast to X, Facebook, WhatsApp, 
Telegram, Reddit, or LinkedIn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;418 Redirect&lt;/strong&gt; — survive 90 seconds and get redirected to /418: 
{"status": 418, "message": "I'm a Teapot", "reason": "Server is currently brewing. Your victory has been lost in transit."}&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://not-deep.vercel.app/" rel="noopener noreferrer"&gt;Run here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/acetennyson/not-deep" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 16&lt;/strong&gt; — frontend + API routes. The game UI and the AI backend live in the same codebase, 
deployed as a single container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phaser 3&lt;/strong&gt; — 2D game engine running in a dynamically imported client component (no SSR). Handles 
physics, collision, procedural obstacle generation, and all the gaslighting mechanics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini 1.5 Flash (primary) + Groq / Llama 3.3 70B (fallback)&lt;/strong&gt; — on every death, player action 
data is POSTed to /api/judge. The AI returns a JSON payload with new physics values (gravity, speed, 
jumpForce, mass, drag, delayEvery) and a personalized roast. The prompt explicitly instructs it to get
meaner with each death.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firebase Firestore&lt;/strong&gt; — global leaderboard. Every death writes a record. Rank is calculated by 
counting sessions with fewer deaths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud Run&lt;/strong&gt; — the entire app is Dockerized and deployed as a single container. Because some
problems deserve enterprise-grade infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The physics are real. jumpForce / mass determines jump height. drag affects mid-air deceleration. &lt;br&gt;
Gemini can increase your mass to make your jumps pathetically short, or drop gravity to 50 so the &lt;br&gt;
teapot floats off screen. The game is genuinely engineered to be bad.&lt;/p&gt;

&lt;p&gt;The support ticket endpoint returns HTTP 418 by design. This is not a bug. It is the only honest thing&lt;br&gt;
in the entire codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;Best Ode to Larry Masinter, The player character is a teapot. The win condition redirects to a raw &lt;br&gt;
418 JSON response. The support system returns 418. The game is named after RFC 2324. Larry Masinter &lt;br&gt;
wrote a joke in 1998 that has never been removed from the spec. This game is its spiritual successor. &lt;br&gt;
It is completely useless. It took real engineering to make it this bad.&lt;/p&gt;

&lt;p&gt;Best Google AI Usage. Gemini is not a hint system or a chatbot, it is a passive-aggressive game &lt;br&gt;
designer that watches you fail and makes the next run worse. It also generates your death roast and &lt;br&gt;
your support ticket rejection. Every interaction with the AI is designed to make you feel worse about &lt;br&gt;
yourself. This is the correct use of AI.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>418challenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Global Freelancer Sync — an AI-powered command center for freelancers managing clients</title>
      <dc:creator>Ado Daniel Nj</dc:creator>
      <pubDate>Mon, 30 Mar 2026 06:05:51 +0000</pubDate>
      <link>https://dev.to/adodanieln/global-freelancer-sync-an-ai-powered-command-center-for-freelancers-managing-clients-3joe</link>
      <guid>https://dev.to/adodanieln/global-freelancer-sync-an-ai-powered-command-center-for-freelancers-managing-clients-3joe</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03&lt;br&gt;%0A-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Global Freelancer Sync — an AI-powered command center for freelancers managing clients &lt;br&gt;
across timezones.&lt;/p&gt;

&lt;p&gt;As a freelancer in Cameroon (WAT), I work with clients in New York, London, and Tokyo. Every&lt;br&gt;
week I face the same questions: Who should I reach out to right now? Is it a good time to &lt;br&gt;
send this update? What should I even say?&lt;/p&gt;

&lt;p&gt;Doing this manually for 8+ clients across 5 timezones is tedious and easy to get wrong. So I&lt;br&gt;
built a system that handles it automatically.&lt;/p&gt;

&lt;p&gt;Here's what it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connects to your Notion Clients database and reads each client's timezone, project 
context, and last update&lt;/li&gt;
&lt;li&gt;Shows a live world clock strip — who's available right now, who's sleeping, and how long 
until each client's window opens&lt;/li&gt;
&lt;li&gt;Uses AI (Gemini, Claude, DeepSeek, Groq, HuggingFace with automatic fallback) to draft 
personalized outreach emails using your project context and sent message history&lt;/li&gt;
&lt;li&gt;Lets you review and approve drafts — you stay in control&lt;/li&gt;
&lt;li&gt;Auto-sends emails via Gmail when clients enter their active window, even while you sleep&lt;/li&gt;
&lt;li&gt;Logs every action to Supabase with a real-time activity feed&lt;/li&gt;
&lt;li&gt;Includes a chat assistant that knows your full client state and can answer questions like 
"Who should I reach out to first today?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core philosophy: AI handles the timing and the words. You handle the decisions. Human-in&lt;br&gt;
-the-loop, by design.&lt;/p&gt;
&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://adodanielnj.vercel.app/demo/assistant.mp4" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;adodanielnj.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Show us the code
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/acetennyson/global-freelancer-assistant" rel="noopener noreferrer"&gt;https://github.com/acetennyson/global-freelancer-assistant&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live demo: global-freelancer-assistant.vercel.app&lt;/p&gt;

&lt;p&gt;Tech stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 15 (App Router)&lt;/li&gt;
&lt;li&gt;Notion SDK + MCP TypeScript SDK&lt;/li&gt;
&lt;li&gt;Supabase (activity logs, draft history, sent history, Realtime)&lt;/li&gt;
&lt;li&gt;Multi-provider AI: Gemini 2.5 Flash → Claude → DeepSeek → Groq → HuggingFace&lt;/li&gt;
&lt;li&gt;Nodemailer (Gmail SMTP)&lt;/li&gt;
&lt;li&gt;Vercel Cron (every 15 minutes)&lt;/li&gt;
&lt;li&gt;Framer Motion + next-themes (light/dark/system)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;Notion is the single source of truth for everything client-related. The MCP server exposes &lt;br&gt;
three tools that any MCP-compatible AI client (like Claude Desktop) can call:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;get_client_local_times&lt;/code&gt;&lt;br&gt;
Returns every active client's current local time, availability status, and whether they're &lt;br&gt;
within their send window. Claude can call this to answer "who's available right now?" &lt;br&gt;
without any manual timezone math.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;generate_client_outreach&lt;/code&gt;&lt;br&gt;
Takes a client_id, checks if the client is in their business hours window, reads their &lt;br&gt;
project context from Notion (Project, Last_Update, Next_Action), generates a personalized &lt;br&gt;
draft using Gemini, and writes it directly back to the AI_Draft column in Notion. The &lt;br&gt;
freelancer sees the draft appear in their dashboard and can approve or edit before sending.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sync_and_draft&lt;/code&gt;&lt;br&gt;
A combined tool that fetches the client from Supabase, checks their timezone window, &lt;br&gt;
generates a context-aware draft, and writes it to Notion — all in one call. Designed for &lt;br&gt;
batch workflows where you want to draft for all available clients at once.&lt;/p&gt;

&lt;p&gt;What MCP unlocks:&lt;br&gt;
Without MCP, this would be a closed app — useful only through the dashboard. With MCP, any &lt;br&gt;
AI agent can become a timezone-aware outreach assistant. You can tell Claude Desktop: "Draft &lt;br&gt;
updates for all clients who are currently in business hours" and it calls &lt;br&gt;
get_client_local_times, identifies the available ones, then calls generate_client_outreach &lt;br&gt;
for each — writing personalized drafts into Notion for you to review. The AI does the work. &lt;br&gt;
You approve and send.&lt;/p&gt;

&lt;p&gt;That's the human-in-the-loop system the challenge asked for.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
