<?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: Mike</title>
    <description>The latest articles on DEV Community by Mike (@minhlong2605).</description>
    <link>https://dev.to/minhlong2605</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%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png</url>
      <title>DEV Community: Mike</title>
      <link>https://dev.to/minhlong2605</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/minhlong2605"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sun, 09 Aug 2026 14:51:35 +0000</pubDate>
      <link>https://dev.to/minhlong2605/-2khc</link>
      <guid>https://dev.to/minhlong2605/-2khc</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/minhlong2605/vaya-an-ai-loan-advisor-that-asks-whether-you-can-still-afford-to-live-gkc" class="crayons-story__hidden-navigation-link"&gt;🏦 Vaya: an AI loan advisor that asks whether you can still afford to live&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/n1LNNoiijIQ" title="🏦 Vaya: an AI loan advisor that asks whether you can still afford to live"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/minhlong2605" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png" alt="minhlong2605 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/minhlong2605" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Mike
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Mike
                
              
              &lt;div id="story-author-preview-content-4354081" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/minhlong2605" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Mike&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/minhlong2605/vaya-an-ai-loan-advisor-that-asks-whether-you-can-still-afford-to-live-gkc" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 9&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/minhlong2605/vaya-an-ai-loan-advisor-that-asks-whether-you-can-still-afford-to-live-gkc" id="article-link-4354081"&gt;
          🏦 Vaya: an AI loan advisor that asks whether you can still afford to live
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/showdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;showdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/qoderhackathon"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;qoderhackathon&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/qodervietnam"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;qodervietnam&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/minhlong2605/vaya-an-ai-loan-advisor-that-asks-whether-you-can-still-afford-to-live-gkc" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;18&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/minhlong2605/vaya-an-ai-loan-advisor-that-asks-whether-you-can-still-afford-to-live-gkc#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              3&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>🏦 Vaya: an AI loan advisor that asks whether you can still afford to live</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sun, 09 Aug 2026 14:51:26 +0000</pubDate>
      <link>https://dev.to/minhlong2605/vaya-an-ai-loan-advisor-that-asks-whether-you-can-still-afford-to-live-gkc</link>
      <guid>https://dev.to/minhlong2605/vaya-an-ai-loan-advisor-that-asks-whether-you-can-still-afford-to-live-gkc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdic1bplkf55bun90o224.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdic1bplkf55bun90o224.png" alt="Image 0" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every loan comparison site asks the same question: &lt;strong&gt;which bank has the lowest rate?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question has almost nothing to do with whether the loan will ruin you.&lt;/p&gt;

&lt;p&gt;A 6.2% loan you can barely service is worse than a 6.8% one that leaves you room to breathe. A rate that resets after twelve promotional months is not a 6.2% loan at all. And none of the twenty banks whose products you are comparing has any interest in telling you that.&lt;/p&gt;

&lt;p&gt;So we built &lt;a href="https://vaya-project.vercel.app/" rel="noopener noreferrer"&gt;&lt;strong&gt;Vaya&lt;/strong&gt;&lt;/a&gt; around the question people are actually asking, which is: &lt;em&gt;if I take this, can I still live?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live demo: &lt;a href="https://vaya-project.vercel.app/" rel="noopener noreferrer"&gt;vaya-project.vercel.app&lt;/a&gt;&lt;/strong&gt; — no signup, nothing to install.&lt;/p&gt;

&lt;p&gt;👨‍💻 &lt;strong&gt;Source code&lt;/strong&gt;: &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/alibaba-hackathon-fsi" rel="noopener noreferrer"&gt;
        alibaba-hackathon-fsi
      &lt;/a&gt; / &lt;a href="https://github.com/alibaba-hackathon-fsi/frontend-vaya" rel="noopener noreferrer"&gt;
        frontend-vaya
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Vaya — AI Loan Advisor for Vietnam&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Vaya is a conversational AI loan advisor that compares loan products across major Vietnamese banks. Users describe their borrowing needs in natural language — "I have a house and want to borrow 12 million for studying" — and the system collects what's missing through natural dialogue, then returns ranked recommendations with full financial transparency: monthly payments, DTI ratios, risk assessments, eligibility recovery steps, and cashflow survival analysis.&lt;/p&gt;
&lt;p&gt;The platform is built on one architectural contract: &lt;strong&gt;a deterministic Decision Engine owns every number, and the LLM only talks&lt;/strong&gt;. The engine (pure functions, zero LLM involvement) filters, prices, scores, and ranks. The AI layer extracts intent, converses with the customer, narrates pre-computed results, and answers policy questions via RAG — it never calculates, scores, ranks, or decides eligibility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Target users:&lt;/strong&gt; Vietnamese consumers evaluating loan options across multiple banks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Core value proposition:&lt;/strong&gt; Unbiased, explainable loan…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/alibaba-hackathon-fsi/frontend-vaya" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Describe your situation in one sentence.&lt;/strong&gt; No forms, no wizard. The homepage is a chat input, because that is the product. "Car loan ₫800M, income ₫30M/month" is enough to start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It compares real products from 20+ Vietnamese banks.&lt;/strong&gt; Rates, maximum amounts, terms, processing time, LTV caps — side by side, sortable, filterable, with the actual bank logos so you know who you are dealing with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It simulates the repayment against your cashflow.&lt;/strong&gt; Full amortisation, month by month. What you pay, what is interest, what is left. Charts you can hover, not a single number in a box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It gives you a survival score.&lt;/strong&gt; This is the part I would show you first. Vaya runs a Monte Carlo simulation over your income, expenses and existing debt, and tells you the probability that the loan pushes you underwater — plus DTI, PTI, LTV and disposable income, each with a plain-language explanation attached. If a loan is risky it does not just say &lt;em&gt;"not suitable"&lt;/em&gt;; it suggests borrowing less, extending the term, increasing the down payment, or which expenses to cut to get back into safe territory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It flips the market.&lt;/strong&gt; There is a marketplace where you post an anonymous request and banks bid on it — the reverse of shopping across twenty websites. Every offer is displayed next to that bank's own public listed rate, because the gap between the two is the entire point. Banks see your amount, term and income band. They never see your name, phone or email.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It speaks three languages.&lt;/strong&gt; English, Vietnamese, Chinese — everything, including the AI's explanations, not just the navigation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And it has a proper dark mode&lt;/strong&gt;, if you are the kind of person who checks. Toggle is in the navbar; right-click it to hand control back to your OS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;1/ The homepage hero (chat input front and centre -&amp;gt; product-centric)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy80zqb5vud9s2b5i2pi7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy80zqb5vud9s2b5i2pi7.png" alt="Image 1" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2/ The survival score page — the most distinctive screen&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsvrh7ptix00undyv6go.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsvrh7ptix00undyv6go.png" alt="Image 2" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3/ The marketplace with bank offers stacked against listed rates&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffkt3jeqlx2qhxf33mb78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffkt3jeqlx2qhxf33mb78.png" alt="Image 3" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4/ Bank screener&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhgiqj6s2d0418sv90ylh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhgiqj6s2d0418sv90ylh.png" alt="Image 4" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5/ Magic Bento&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ppy6lpo62nsgaa4bc3h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ppy6lpo62nsgaa4bc3h.gif" alt="Image 5" width="720" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;6/ AI chat&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpy8mux7vabbqpctbgj99.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpy8mux7vabbqpctbgj99.gif" alt="Image 6" width="720" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/n1LNNoiijIQ"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;p&gt;&lt;/p&gt;
  Tech stack
  &lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 14&lt;/strong&gt; App Router, TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Tailwind&lt;/strong&gt; — a hand-written design system in a single stylesheet, ~5,500 lines, square corners and one brand green throughout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom SVG charts&lt;/strong&gt; rather than a charting library, so the tooltips and the amortisation curves behave exactly as the product needs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monte Carlo simulation&lt;/strong&gt; in pure TypeScript for the survival score, running client-side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;i18n via React context&lt;/strong&gt;, ~600 keys per language, three sets kept strictly in sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://reactbits.dev/" rel="noopener noreferrer"&gt;React Bits&lt;/a&gt;&lt;/strong&gt; &lt;code&gt;&amp;lt;MagicBento /&amp;gt;&lt;/code&gt; for the feature grid&lt;/li&gt;
&lt;li&gt;Deployed on Vercel&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;/p&gt;

&lt;p&gt;A few things I enjoyed building more than expected: the reverse-auction board has no AI in it at all — it is a forum, and forums already work. The whole survival engine is pure functions, which meant the report generator could reuse it without a single API call. And retrofitting dark mode onto a 5,500-line stylesheet that three people were editing daily turned out to be the hardest CSS problem of the project, by a distance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where it came from
&lt;/h2&gt;

&lt;p&gt;Vaya was built for the &lt;strong&gt;Alibaba Cloud FSI Hackathon&lt;/strong&gt; by a team of four. Results are announced on 11 August, which means it is currently in that nice window where I can only improve the parts nobody is judging.&lt;/p&gt;

&lt;p&gt;If you want the longer story of how it was built — the product decisions, the tooling, what we got wrong — the team wrote it up here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐦 &lt;a href="https://x.com/jackvi810/status/2084244336682979530" rel="noopener noreferrer"&gt;The full build thread on X&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💼 &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7490082630332456960/" rel="noopener noreferrer"&gt;Write-up on LinkedIn&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  I would like your eyes on it
&lt;/h2&gt;

&lt;p&gt;Genuinely, not rhetorically — the demo is open and takes about two minutes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://vaya-project.vercel.app/" rel="noopener noreferrer"&gt;vaya-project.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three things I am unsure about and would love to be argued with on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is the survival score legible to someone who is not a finance person?&lt;/strong&gt; It is the most valuable thing in the product and the easiest thing to over-engineer into noise. Try it and tell me whether the number means anything to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the reverse-auction marketplace feel like something you would actually use&lt;/strong&gt;, or like a mechanic that only makes sense to the people who built it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat-first homepage — right call or annoying?&lt;/strong&gt; We deliberately did not build a landing page with a "Get Started" button. Some people love it. Some people scroll looking for the marketing site.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Drop a comment with what broke, what confused you, or what you would cut. I will reply to all of them.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ai</category>
      <category>qoderhackathon</category>
      <category>qodervietnam</category>
    </item>
    <item>
      <title>check it out</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 06 Aug 2026 17:47:46 +0000</pubDate>
      <link>https://dev.to/minhlong2605/check-it-out-cmp</link>
      <guid>https://dev.to/minhlong2605/check-it-out-cmp</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/minhlong2605/mam-com-landing-page-i-built-a-vietnamese-dinner-tray-on-a-3000-year-old-bronze-drum-3e6h" class="crayons-story__hidden-navigation-link"&gt;🥁 Mâm Cơm Landing Page: I built a Vietnamese dinner tray on a 3,000-year-old bronze drum&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/2z9cRo0Ya5M" title="🥁 Mâm Cơm Landing Page: I built a Vietnamese dinner tray on a 3,000-year-old bronze drum"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/minhlong2605/mam-com-landing-page-i-built-a-vietnamese-dinner-tray-on-a-3000-year-old-bronze-drum-3e6h" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Frontend Challenge Perfect Landing Submission 🍲🥧&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/minhlong2605" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png" alt="minhlong2605 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/minhlong2605" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Mike
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Mike
                
              
              &lt;div id="story-author-preview-content-4334370" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/minhlong2605" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Mike&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/minhlong2605/mam-com-landing-page-i-built-a-vietnamese-dinner-tray-on-a-3000-year-old-bronze-drum-3e6h" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 6&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/minhlong2605/mam-com-landing-page-i-built-a-vietnamese-dinner-tray-on-a-3000-year-old-bronze-drum-3e6h" id="article-link-4334370"&gt;
          🥁 Mâm Cơm Landing Page: I built a Vietnamese dinner tray on a 3,000-year-old bronze drum
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontendchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontendchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/minhlong2605/mam-com-landing-page-i-built-a-vietnamese-dinner-tray-on-a-3000-year-old-bronze-drum-3e6h" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;68&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/minhlong2605/mam-com-landing-page-i-built-a-vietnamese-dinner-tray-on-a-3000-year-old-bronze-drum-3e6h#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              15&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>🥁 Mâm Cơm Landing Page: I built a Vietnamese dinner tray on a 3,000-year-old bronze drum</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 06 Aug 2026 17:47:27 +0000</pubDate>
      <link>https://dev.to/minhlong2605/mam-com-landing-page-i-built-a-vietnamese-dinner-tray-on-a-3000-year-old-bronze-drum-3e6h</link>
      <guid>https://dev.to/minhlong2605/mam-com-landing-page-i-built-a-vietnamese-dinner-tray-on-a-3000-year-old-bronze-drum-3e6h</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2026-07-29"&gt;Frontend Challenge - Comfort Food Edition, Perfect Landing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;When the prompt said &lt;em&gt;comfort food&lt;/em&gt;, I did not think of a dish. I thought of a &lt;strong&gt;shape&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vietnamese has no word for "entrée." There is only &lt;code&gt;cơm&lt;/code&gt; - rice - and everything laid around it. Every evening, in a hundred million homes, six or seven small dishes go onto one round tray and get eaten all at once, in any order, by everyone. We call it &lt;strong&gt;mâm cơm&lt;/strong&gt;: the family rice tray. It is not a sequence of courses. It is a conversation.&lt;/p&gt;

&lt;p&gt;And here is the thing I could not stop thinking about once I noticed it: Việt Nam's oldest surviving artefact is a &lt;strong&gt;Đông Sơn bronze drum&lt;/strong&gt;, cast around 1000 BCE. Its face is a perfect disc of concentric life - a fourteen-rayed sun at dead centre, ringed by bands of Lạc herons, long boats, and dancers in feathered headdresses. Our most ordinary household object is a bamboo tray of &lt;em&gt;exactly the same shape&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So I built the tray &lt;strong&gt;on&lt;/strong&gt; the drum. Three thousand years apart, same circle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fanpkrxyurcdi3sgzedhc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fanpkrxyurcdi3sgzedhc.png" alt="image 1" width="799" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The centrepiece
&lt;/h3&gt;

&lt;p&gt;The tray is a lazy susan you can actually spin. Seven dishes orbit the drum face; choose one and the whole drum turns until that dish arrives at twelve o'clock, while each dish counter-rotates so the food never ends up upside down. The centre medallion names whatever is on top, and the panel beside it opens with a full-width close-up.&lt;/p&gt;

&lt;p&gt;You can &lt;strong&gt;drag it&lt;/strong&gt;, &lt;strong&gt;swipe it&lt;/strong&gt;, &lt;strong&gt;click a dish&lt;/strong&gt;, &lt;strong&gt;press the arrow keys&lt;/strong&gt;, or use the two spin buttons. Because a lazy susan is a thing you push.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frh0i7gvh0tcw0o5lrr0o.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frh0i7gvh0tcw0o5lrr0o.gif" alt="Image 2 gif" width="560" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under the hood it is just an &lt;strong&gt;ARIA tablist bent into a circle&lt;/strong&gt;. Each dish is a &lt;code&gt;role="tab"&lt;/code&gt;, each card a &lt;code&gt;role="tabpanel"&lt;/code&gt;, with roving &lt;code&gt;tabindex&lt;/code&gt;. It is the same widget pattern as a boring row of tabs - it just happens to be a bronze drum.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rest of the page
&lt;/h3&gt;

&lt;p&gt;A full-bleed photograph of a complete tray. The &lt;strong&gt;Con Rồng Cháu Tiên&lt;/strong&gt; origin myth - children of a dragon and a fairy, a hundred sons, fifty to the mountains and fifty to the sea. A recipe for &lt;strong&gt;thịt kho trứng&lt;/strong&gt; with a working servings scaler. The unwritten rules of the Vietnamese table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk17a3wifjbxd4b3ki576.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk17a3wifjbxd4b3ki576.png" alt="Image 3" width="799" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It closes on the only greeting that matters: &lt;strong&gt;"Ăn cơm chưa?"&lt;/strong&gt; - &lt;em&gt;have you eaten rice yet?&lt;/em&gt; It is how we say hello. It is also how we say &lt;em&gt;I was thinking about you&lt;/em&gt;, and &lt;em&gt;are you all right&lt;/em&gt;, and &lt;em&gt;there is still some in the pot&lt;/em&gt;. There is no way to translate it that does not come out sounding like love.&lt;/p&gt;

&lt;p&gt;Under those words is a drawing of three generations around one tray, and the drum shows through behind them - both are gold on black, screen-blended, so the three-thousand-year-old artefact ends up as a halo behind a family eating dinner tonight. I did not plan that. The compositing did it, and it turned out to be the whole argument of the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ztm43xkvvhxdgyztbr0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ztm43xkvvhxdgyztbr0.png" alt="Image 5" width="799" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;🔗 &lt;strong&gt;Live:&lt;/strong&gt; &lt;/p&gt;
&lt;div class="crayons-card c-embed"&gt;

  
&lt;h3&gt;
  
  
  🐉🪘 MÂM CƠM 🪘🐲
&lt;/h3&gt;

&lt;p&gt;A Vietnamese dinner tray experience inspired by the Đông Sơn bronze drum.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://the-family-rice-tray.vercel.app" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Live Demo 🔥&lt;/a&gt;
 - &lt;a href="https://the-family-rice-tray.vercel.app" rel="noopener noreferrer"&gt;https://the-family-rice-tray.vercel.app&lt;/a&gt;&lt;br&gt;

&lt;/p&gt;
&lt;/div&gt;


&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/longphanquangminh/embed/myRrpyw?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Try it with a keyboard. Try it with reduced motion on. Try it with JavaScript off - the tray steps aside and all seven dish cards render in full.&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/2z9cRo0Ya5M"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

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

&lt;p&gt;💻 &lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/longphanquangminh/the-family-rice-tray" rel="noopener noreferrer"&gt;https://github.com/longphanquangminh/the-family-rice-tray&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/longphanquangminh" rel="noopener noreferrer"&gt;
        longphanquangminh
      &lt;/a&gt; / &lt;a href="https://github.com/longphanquangminh/the-family-rice-tray" rel="noopener noreferrer"&gt;
        the-family-rice-tray
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A love letter to mâm cơm, the Vietnamese family rice tray - seven dishes orbiting a Đông Sơn bronze drum. No framework, no build step.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Mâm Cơm — the Vietnamese family rice tray&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A love letter to &lt;strong&gt;mâm cơm&lt;/strong&gt;, the round tray of shared dishes at the centre of every Vietnamese home — laid out on the face of a Đông Sơn bronze drum.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/frontend-2026-07-29" rel="nofollow"&gt;DEV Frontend Challenge: Comfort Food Edition&lt;/a&gt; (&lt;em&gt;Perfect Landing&lt;/em&gt; prompt).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://the-family-rice-tray.vercel.app" rel="nofollow noopener noreferrer"&gt;https://the-family-rice-tray.vercel.app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/longphanquangminh/the-family-rice-tray/docs/hero.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flongphanquangminh%2Fthe-family-rice-tray%2FHEAD%2Fdocs%2Fhero.jpg" alt="The Mâm Cơm hero: the words MÂM CƠM in gold over a Lý-dynasty dragon and a Đông Sơn bronze drum"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/longphanquangminh/the-family-rice-tray/docs/tray.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flongphanquangminh%2Fthe-family-rice-tray%2FHEAD%2Fdocs%2Ftray.jpg" alt="The tray: seven dishes orbiting a bronze drum, with the selected dish shown large beside it"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The idea&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Vietnamese has no word for &lt;em&gt;entrée&lt;/em&gt;. There is only &lt;code&gt;cơm&lt;/code&gt; — rice — and everything laid around it. Every evening six or seven small dishes go onto one round tray and are eaten all at once, in any order, by everyone.&lt;/p&gt;
&lt;p&gt;Việt Nam's oldest surviving artefact is a &lt;strong&gt;Đông Sơn bronze drum&lt;/strong&gt;, cast around 1000 BCE: a perfect disc of concentric life with a fourteen-rayed sun at its centre. Our most ordinary household object is a bamboo tray of &lt;em&gt;exactly the same shape&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So the tray is built &lt;strong&gt;on&lt;/strong&gt; the…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/longphanquangminh/the-family-rice-tray" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Design
&lt;/h3&gt;

&lt;p&gt;I wanted the page to look Vietnamese without looking like a tourist poster, so I went to primary sources: lacquerware red (&lt;code&gt;#8B0C1C&lt;/code&gt;), leaf gold, jade, and unbleached rice-paper cream; a Lý-dynasty dragon and the Ngọc Lũ drum as ornament rather than decoration.&lt;/p&gt;

&lt;p&gt;Both ornaments are gold line art on pure black, composited with &lt;code&gt;mix-blend-mode: screen&lt;/code&gt; - the black drops straight out and only the gold survives, over any red I put behind it. One asset, reusable at any opacity, on any background.&lt;/p&gt;

&lt;p&gt;That trick has a sharp edge. The assets are quantised to a 12-colour palette, and I have to snap every near-black palette entry to &lt;em&gt;true&lt;/em&gt; black, because &lt;code&gt;screen(x, near-black)&lt;/code&gt; still lightens the backdrop - which paints the asset's bounding box as a visible rectangle over flat colour. One ornament came back with a "black" of &lt;code&gt;rgb(5,5,4)&lt;/code&gt; across 92% of the frame and drew a faint grey box on the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  On using photographs at all
&lt;/h3&gt;

&lt;p&gt;Half this challenge has "zero images" or "pure CSS, no photography" in the title, and I understand the appeal - drawing food in CSS is a genuine flex. But I kept coming back to the fact that this is a &lt;em&gt;comfort food&lt;/em&gt; brief. Comfort food is a sensory argument. So I went the other way deliberately: a full-bleed photograph of a whole laid tray, and a large close-up of every single dish. If someone scrolls this page and does not get hungry, the page has failed, no matter how clean the markup is.&lt;/p&gt;

&lt;p&gt;That commitment created most of the interesting problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  The maths
&lt;/h3&gt;

&lt;p&gt;Placing seven dishes on a circle in pure CSS was the fun part. Each dish carries its own &lt;code&gt;--a&lt;/code&gt; angle and uses the individual transform properties, which apply in a guaranteed order - &lt;code&gt;translate&lt;/code&gt; → &lt;code&gt;rotate&lt;/code&gt; → &lt;code&gt;transform&lt;/code&gt;:&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;.dish&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-50%&lt;/span&gt; &lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c"&gt;/* centre it */&lt;/span&gt;
  &lt;span class="py"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                  &lt;span class="c"&gt;/* aim down its ray */&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-189.5%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c"&gt;/* walk out along that ray */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the orbit is a percentage of the dish, and the dish is a percentage of the tray, the whole widget scales from 320px to 560px with &lt;strong&gt;zero media queries&lt;/strong&gt;. JS sets exactly one custom property - &lt;code&gt;--ring-rot&lt;/code&gt; - and CSS does the rest. Two levels of counter-rotation keep every bowl upright.&lt;/p&gt;

&lt;p&gt;The same trick pays off again for the shadows. Each dish sits in a frame already rotated by &lt;code&gt;--a&lt;/code&gt;, so a plain &lt;code&gt;0 -2px&lt;/code&gt; offset &lt;strong&gt;already points away from the centre&lt;/strong&gt; for every dish - one declaration gives you seven radial shadows, as if a single lamp hung over the middle of the table. No per-dish maths.&lt;/p&gt;

&lt;h3&gt;
  
  
  The contrast bug axe cannot see
&lt;/h3&gt;

&lt;p&gt;My first pass put the feast headline on top of the photograph with a gradient scrim behind it. Every automated checker passed, because &lt;strong&gt;axe-core cannot evaluate text sitting on an image&lt;/strong&gt; - it has no idea what pixels are behind the glyphs.&lt;/p&gt;

&lt;p&gt;So I wrote a Playwright script that hides the text, screenshots the composited background, samples every pixel in each text run's bounding box, and computes the worst-case WCAG ratio against the real text colour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kicker over photo    worst 1.77:1  (needs 4.5)  FAIL
headline over photo  worst 2.49:1  (needs 3.0)  FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1.77:1 for gold-on-rice. Invisible to tooling, obvious once measured. I darkened the scrim and it got &lt;em&gt;worse&lt;/em&gt; - because the text block was 589px tall and simply reached further up the photo than any tasteful gradient could cover.&lt;/p&gt;

&lt;p&gt;The honest fix was structural, not cosmetic: &lt;strong&gt;stop putting copy on the photograph.&lt;/strong&gt; The image now gets the entire frame and the copy sits in a solid band beneath it, which guarantees contrast &lt;em&gt;and&lt;/em&gt; shows more food. Those same four measurements now read 9.95:1 and 14.89:1.&lt;/p&gt;

&lt;h3&gt;
  
  
  A false positive I fixed anyway
&lt;/h3&gt;

&lt;p&gt;Later, axe flagged the plaque button at 3.24:1. My pixel sampling said 6.63:1 - and my sampling was right. I had put the gold fill on an absolutely-positioned &lt;em&gt;sibling&lt;/em&gt; of the label, so axe walked up the DOM, found the bronze rule instead, and reported a failure that does not exist.&lt;/p&gt;

&lt;p&gt;I restructured anyway: the background moved onto the element that actually contains the text. A judge running axe would have seen that red line and had no way to know it was wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four bugs that looked fine and measured wrong
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Rotating a square grows its &lt;strong&gt;bounding box&lt;/strong&gt; by up to √2, silently adding 49px of horizontal scroll on mobile. Nothing visibly overflowed; the page was just wider than the screen.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;min-block-size: 92svh&lt;/code&gt; on a hero &lt;strong&gt;does not account for a sticky header&lt;/strong&gt;, so the hero always overhung the fold by exactly the header's height. &lt;code&gt;calc(100svh - 4.5rem)&lt;/code&gt; fixed it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aspect-ratio&lt;/code&gt; together with &lt;code&gt;min-block-size&lt;/code&gt; let a box solve for &lt;strong&gt;width&lt;/strong&gt; instead of height and blew out 374px.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;offsetHeight&lt;/code&gt; &lt;strong&gt;rounds to whole pixels&lt;/strong&gt;, reporting an 804.42px panel as 804 - leaving half a pixel of jump behind. &lt;code&gt;getBoundingClientRect().height&lt;/code&gt; with &lt;code&gt;Math.ceil&lt;/code&gt; took it to 0.00px.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  And one test that passed while the UI was broken
&lt;/h3&gt;

&lt;p&gt;My mobile nav clipped its last label to "Nếp Nh", but my assertion only tested &lt;code&gt;document.scrollWidth&lt;/code&gt; - and the nav's own &lt;code&gt;overflow-x: auto&lt;/code&gt; hid the problem from it. &lt;strong&gt;Any scroll container can mask a clipped layout from a document-level check.&lt;/strong&gt; The suite now asserts &lt;code&gt;nav.scrollWidth &amp;lt;= nav.clientWidth&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;Writing the suite properly also found a real reflow failure at &lt;strong&gt;320px&lt;/strong&gt; that I had previously waved away as "old phones": the dish strip wrapped to two rows and fell below the fold. 320px is the width WCAG reflow is specified at, so it got fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  A layout that never jumps
&lt;/h3&gt;

&lt;p&gt;Each dish carries a different amount of copy, so switching dishes resized the panel and - with &lt;code&gt;align-items: center&lt;/code&gt; - dragged the drum up and down with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1440px:  panel 713→738px  (25px spread)  →  drum moves 13px
1100px:  panel 723→768px  (45px spread)  →  drum moves 23px
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The spread depends on viewport width, so a hard-coded &lt;code&gt;min-height&lt;/code&gt; is wrong at every size but one. The fix measures the tallest panel at runtime and pins it on all of them, re-measuring on resize and on &lt;code&gt;document.fonts.ready&lt;/code&gt; - web fonts swap in after first paint and change every text metric.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making the tray physical
&lt;/h3&gt;

&lt;p&gt;Adding drag-to-spin took about forty lines and broke two things I did not expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;setPointerCapture&lt;/code&gt; on &lt;code&gt;pointerdown&lt;/code&gt; kills clicks.&lt;/strong&gt; Capture retargets the eventual &lt;code&gt;click&lt;/code&gt; to the capturing element, so tapping a dish never reached that dish's button. Capture has to wait until the pointer has actually travelled past a dead zone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The browser runs its own gestures first.&lt;/strong&gt; Pressing on a dish photo starts a native image drag; sweeping across the tray starts a text selection. Either one hijacks the pointer stream. That is why the &lt;em&gt;second&lt;/em&gt; and later spins felt stuck while the first felt fine - the fix is &lt;code&gt;draggable="false"&lt;/code&gt; and &lt;code&gt;user-select: none&lt;/code&gt;, and it is invisible to synthetic test input, so I had to find it by inspecting state rather than behaviour.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I didn't ship
&lt;/h3&gt;

&lt;p&gt;I prototyped tilting the tray 40° in CSS 3D so it would read as an object on a table. The maths worked - the drum foreshortened to a 1.78 aspect while the dishes stayed at 1.0, correctly billboarded.&lt;/p&gt;

&lt;p&gt;I threw it away. Under a 3D transform the circular &lt;code&gt;overflow: hidden&lt;/code&gt; crop on each dish stopped clipping, the gold selection ring vanished, and every photo went soft from being rasterised flat and re-projected. All three are fixable with enough work - but a mâm cơm &lt;em&gt;is&lt;/em&gt; seen from above, and a drum face &lt;em&gt;is&lt;/em&gt; a flat disc. Tilting turned "same circle, three thousand years apart" into a generic dinner table. It was a worse idea rendered better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deep links
&lt;/h3&gt;

&lt;p&gt;Landing on &lt;code&gt;/#manners&lt;/code&gt; did not work, for two reasons stacked on top of each other. &lt;code&gt;scroll-behavior: smooth&lt;/code&gt; turns a fragment jump into a long animation across a 9,500px page that the reader cancels the instant they touch the wheel. And lazy images plus the measured panel height keep adding pixels &lt;em&gt;above&lt;/em&gt; the target after the browser has already scrolled.&lt;/p&gt;

&lt;p&gt;Then the real culprit: on F5, &lt;code&gt;history.scrollRestoration&lt;/code&gt; restores the old position &lt;strong&gt;after&lt;/strong&gt; the fragment jump and wins. Ironically my own "never yank the page out from under the reader" guard then saw the position change and politely gave up. When the URL carries a hash, the hash is the intent - so restoration gets switched off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility
&lt;/h3&gt;

&lt;p&gt;First-class constraint, not a pass at the end:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full keyboard tablist, plus 44×44 spin controls and page-level arrow keys that stand down inside inputs, on other controls, and when the tray is off screen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:focus-visible&lt;/code&gt; rings that follow the circular shape rather than the square hit area - which is why the plaque button needs two elements, since &lt;code&gt;clip-path&lt;/code&gt; crops an outline&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prefers-reduced-motion&lt;/code&gt; kills every spin, transition and momentum glide; &lt;code&gt;prefers-contrast: more&lt;/code&gt; drops the decorative blend layers&lt;/li&gt;
&lt;li&gt;Vietnamese phrases marked &lt;code&gt;lang="vi"&lt;/code&gt; so screen readers pronounce them properly instead of mangling them as English&lt;/li&gt;
&lt;li&gt;Progressive enhancement via a &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; stylesheet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 axe-core violations&lt;/strong&gt; (WCAG 2.1 A/AA + best-practice) at 1440px and 390px&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of it runs from &lt;code&gt;tools/verify.py&lt;/code&gt; in the repo: axe, the pixel-sampled contrast check, layout-stability assertions, every spin affordance, deep links with throttled images, and responsive integrity across 14 widths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stack
&lt;/h3&gt;

&lt;p&gt;Hand-written HTML, CSS and about 260 lines of vanilla JavaScript. No framework, no build step, one file plus images. The only external request is Google Fonts. Every below-fold image is lazy-loaded; the gold ornaments are 12-colour PNGs; the whole thing is 4.2 MB, almost all of it photography.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;A quick note on AI:&lt;/strong&gt; I used AI to help draft this write-up. I reviewed and edited it before publishing.&lt;/p&gt;




&lt;p&gt;Thanks for reading. Ăn cơm chưa? 🍚&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Mon, 15 Jun 2026 15:23:57 +0000</pubDate>
      <link>https://dev.to/minhlong2605/-h2k</link>
      <guid>https://dev.to/minhlong2605/-h2k</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/minhlong2605/solstice-shift-time-reshape-the-world-june-solstice-game-jam-entry-36bl" class="crayons-story__hidden-navigation-link"&gt;🏃 SOLSTICE: Shift Time, Reshape the World (June Solstice Game Jam Entry)&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/u7lJvqdN9Y8" title="🏃 SOLSTICE: Shift Time, Reshape the World (June Solstice Game Jam Entry)"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/minhlong2605/solstice-shift-time-reshape-the-world-june-solstice-game-jam-entry-36bl" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;June Solstice Game Jam Submission&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/minhlong2605" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png" alt="minhlong2605 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/minhlong2605" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Mike
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Mike
                
              
              &lt;div id="story-author-preview-content-3899725" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/minhlong2605" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Mike&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/minhlong2605/solstice-shift-time-reshape-the-world-june-solstice-game-jam-entry-36bl" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 14&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/minhlong2605/solstice-shift-time-reshape-the-world-june-solstice-game-jam-entry-36bl" id="article-link-3899725"&gt;
          🏃 SOLSTICE: Shift Time, Reshape the World (June Solstice Game Jam Entry)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gamechallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gamechallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gamedev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gamedev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/minhlong2605/solstice-shift-time-reshape-the-world-june-solstice-game-jam-entry-36bl" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;18&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/minhlong2605/solstice-shift-time-reshape-the-world-june-solstice-game-jam-entry-36bl#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              3&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>🏃 SOLSTICE: Shift Time, Reshape the World (June Solstice Game Jam Entry)</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sun, 14 Jun 2026 16:04:50 +0000</pubDate>
      <link>https://dev.to/minhlong2605/solstice-shift-time-reshape-the-world-june-solstice-game-jam-entry-36bl</link>
      <guid>https://dev.to/minhlong2605/solstice-shift-time-reshape-the-world-june-solstice-game-jam-entry-36bl</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;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This project was built with Google Gemini throughout: game code, level design, debugging, and this writeup. I directed the concept, reviewed output, and iterated until it played fair; Gemini did the heavy lifting on implementation and polish.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;SOLSTICE&lt;/strong&gt; is a browser puzzle platformer about the moment the year stops turning.&lt;/p&gt;

&lt;p&gt;You play as the Sunkeeper. The longest day has frozen halfway between dawn and dusk. The only way forward is to &lt;strong&gt;shift time&lt;/strong&gt; between Day and Night. That is not a visual filter. It rewires the level. Sun blocks hold you up in daylight and vanish at night. Moon blocks are ghost platforms until you flip to darkness. Water kills you under the sun and becomes walkable ice when the stars come out.&lt;/p&gt;

&lt;p&gt;The campaign is five levels that teach the rules one beat at a time, ending at the great Solstice Shrine. After that you can drop into &lt;strong&gt;Endless Mode&lt;/strong&gt;, where the game keeps serving new realms forever.&lt;/p&gt;

&lt;p&gt;I built it for the solstice theme on purpose. Light and dark are not just palette swaps here. They are the puzzle. Every jump, every collectible, the exit asks you to read the world twice and pick the right phase of time.&lt;/p&gt;

&lt;p&gt;You can try it inline below. &lt;strong&gt;For the best experience, play the standalone web version.&lt;/strong&gt; Keyboard focus, audio, and the Gemini AI settings panel all behave more reliably outside the embed.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/longphanquangminh/embed/YPNGYXZ?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Play it now (recommended):&lt;/strong&gt; &lt;a href="https://longphanquangminh.github.io/SOLSTICE-jump/" rel="noopener noreferrer"&gt;https://longphanquangminh.github.io/SOLSTICE-jump/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No install. Click once on the canvas if the keyboard feels dead, then go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tile&lt;/th&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Night&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;S&lt;/code&gt; Sun block&lt;/td&gt;
&lt;td&gt;Solid&lt;/td&gt;
&lt;td&gt;Vanishes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;M&lt;/code&gt; Moon block&lt;/td&gt;
&lt;td&gt;Invisible&lt;/td&gt;
&lt;td&gt;Solid (shift to reveal stairs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;W&lt;/code&gt; Water / Ice&lt;/td&gt;
&lt;td&gt;Deadly water&lt;/td&gt;
&lt;td&gt;Walkable ice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;#&lt;/code&gt; Stone&lt;/td&gt;
&lt;td&gt;Solid&lt;/td&gt;
&lt;td&gt;Solid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;^&lt;/code&gt; Spikes&lt;/td&gt;
&lt;td&gt;Deadly&lt;/td&gt;
&lt;td&gt;Deadly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;o&lt;/code&gt; Sun mote&lt;/td&gt;
&lt;td&gt;Collect&lt;/td&gt;
&lt;td&gt;Collect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;E&lt;/code&gt; Solstice Shrine&lt;/td&gt;
&lt;td&gt;Exit&lt;/td&gt;
&lt;td&gt;Exit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Controls:&lt;/strong&gt; &lt;code&gt;←/→&lt;/code&gt; or &lt;code&gt;A/D&lt;/code&gt; to move · &lt;code&gt;Space&lt;/code&gt;/&lt;code&gt;W&lt;/code&gt; to jump · &lt;code&gt;Shift&lt;/code&gt;/&lt;code&gt;J&lt;/code&gt;/&lt;code&gt;F&lt;/code&gt; to flip time · &lt;code&gt;Enter&lt;/code&gt; to start · &lt;code&gt;E&lt;/code&gt; for Endless Mode from the title screen · &lt;code&gt;R&lt;/code&gt; to restart · &lt;code&gt;M&lt;/code&gt; to mute&lt;/p&gt;




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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/u7lJvqdN9Y8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;▶ &lt;strong&gt;Direct link:&lt;/strong&gt; &lt;a href="https://www.youtube.com/watch?v=u7lJvqdN9Y8" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=u7lJvqdN9Y8&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The clip walks through the core loop: run, jump, collect sun motes, hit &lt;strong&gt;Shift&lt;/strong&gt; to flip the world, reach the Shrine. You will see the gold day palette snap into indigo night, moon stairs appearing out of nowhere, and a frozen river turning from hazard into a bridge.&lt;/p&gt;

&lt;p&gt;Raw capture in the repo: &lt;a href="https://github.com/longphanquangminh/SOLSTICE-jump/blob/main/jump.wmv" rel="noopener noreferrer"&gt;&lt;code&gt;jump.wmv&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;




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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/longphanquangminh" rel="noopener noreferrer"&gt;
        longphanquangminh
      &lt;/a&gt; / &lt;a href="https://github.com/longphanquangminh/SOLSTICE-jump" rel="noopener noreferrer"&gt;
        SOLSTICE-jump
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SOLSTICE — A Game of Light &amp;amp; Time&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Play now:&lt;/strong&gt; &lt;a href="https://longphanquangminh.github.io/SOLSTICE-jump/" rel="nofollow noopener noreferrer"&gt;https://longphanquangminh.github.io/SOLSTICE-jump/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A browser-based 2D puzzle-platformer built for the &lt;strong&gt;&lt;a href="https://dev.to/challenges/june-2026-solstice" rel="nofollow"&gt;DEV June Solstice Game Jam 2026&lt;/a&gt;&lt;/strong&gt;. You are the &lt;strong&gt;Sunkeeper&lt;/strong&gt;. On the longest day of the year, time has stalled between day and night. Use &lt;strong&gt;SHIFT TIME&lt;/strong&gt; (Day ↔ Night) to reshape the world, collect sun motes, and light the &lt;strong&gt;Solstice Shrines&lt;/strong&gt; to restore the cycle.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=u7lJvqdN9Y8" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4af5ee2583cbf8612935d692643ec0a1048389bb65c947cd3aa61b0aeb9ce4a9/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f75376c4a7671644e3959382f6d617872657364656661756c742e6a7067" alt="Watch SOLSTICE gameplay on YouTube"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;▶ &lt;strong&gt;&lt;a href="https://www.youtube.com/watch?v=u7lJvqdN9Y8" rel="nofollow noopener noreferrer"&gt;YouTube — SOLSTICE gameplay&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Original video file in the repo: &lt;a href="https://github.com/longphanquangminh/SOLSTICE-jump/jump.wmv" rel="noopener noreferrer"&gt;&lt;code&gt;jump.wmv&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How to Play&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Press &lt;strong&gt;SHIFT&lt;/strong&gt; to flip time — the physical world changes with the light/dark phase:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tile&lt;/th&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Night&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;S&lt;/code&gt; Sun-block&lt;/td&gt;
&lt;td&gt;Solid&lt;/td&gt;
&lt;td&gt;Vanishes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;M&lt;/code&gt; Moon-block&lt;/td&gt;
&lt;td&gt;Invisible / not standable&lt;/td&gt;
&lt;td&gt;Solid (hidden stairs — SHIFT to reveal)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;W&lt;/code&gt; Water / Ice&lt;/td&gt;
&lt;td&gt;Deadly water&lt;/td&gt;
&lt;td&gt;Walkable ice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;#&lt;/code&gt; Stone&lt;/td&gt;
&lt;td&gt;Solid in both phases&lt;/td&gt;
&lt;td&gt;Solid in both phases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;^&lt;/code&gt; Spikes&lt;/td&gt;
&lt;td&gt;Always deadly&lt;/td&gt;
&lt;td&gt;Always deadly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;o&lt;/code&gt; Sun mote&lt;/td&gt;
&lt;td&gt;Collectible&lt;/td&gt;
&lt;td&gt;Collectible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;E&lt;/code&gt; Solstice Shrine&lt;/td&gt;
&lt;td&gt;Level exit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/longphanquangminh/SOLSTICE-jump" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/longphanquangminh/SOLSTICE-jump" rel="noopener noreferrer"&gt;https://github.com/longphanquangminh/SOLSTICE-jump&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whole game lives in a single &lt;a href="https://github.com/longphanquangminh/SOLSTICE-jump/blob/main/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;/a&gt;. Canvas 2D, fixed timestep physics, tile maps, WebAudio synth, the Gemini client, and the level validator all ship in one file so GitHub Pages can host it with zero build step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SOLSTICE-jump/
├── index.html      # The entire game (HTML + CSS + JS)
├── jump.wmv        # Gameplay demo video
├── README.md
└── working-doc.md  # Dev notes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;I started with one question: what if changing time changed &lt;em&gt;geometry&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;From there I worked iteratively with Gemini: describing mechanics, getting code back, playtesting, and sending fixes until the game felt right. Levels are ASCII grids. &lt;code&gt;#&lt;/code&gt; is stone. &lt;code&gt;S&lt;/code&gt; and &lt;code&gt;M&lt;/code&gt; are phase locked blocks. &lt;code&gt;W&lt;/code&gt; is water or ice depending on the sky. &lt;code&gt;o&lt;/code&gt; is a sun mote. &lt;code&gt;E&lt;/code&gt; is the Shrine. The parser pads uneven rows so a sloppy AI row never crashes the game.&lt;/p&gt;

&lt;h3&gt;
  
  
  Physics and feel
&lt;/h3&gt;

&lt;p&gt;Movement uses coyote time and jump buffering because platformers live or die on forgiveness. Shifting phases runs through a trap check first. If the new phase would crush the player inside solid tile, the shift is denied with a little shake and a sour note. You are never soft locked by your own power.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proving levels are fair
&lt;/h3&gt;

&lt;p&gt;Early playtests caught a nasty bug: motes floating four tiles in the air when the jump apex only reaches about two. That felt like a secret rule instead of a skill check. With Gemini I rebuilt placement around three reachable patterns: a short hop, a climbable staircase, or a moon stair you only see after shifting to night.&lt;/p&gt;

&lt;p&gt;Then we added a phase aware reachability search (BFS) that understands walk, step up, jump arcs, falls, and day/night toggles. Every campaign level, every sample realm, and every runtime Gemini output gets filtered through it before you play. If the Shrine is unreachable, the level is rejected. If a mote is unreachable, it gets stripped so 100% collection stays honest.&lt;/p&gt;

&lt;p&gt;A greedy hold jump bot was also run against the campaign until it cleared all five levels including the finale. That caught missing player spawn markers and floating steps that looked cool but broke progression.&lt;/p&gt;

&lt;h3&gt;
  
  
  Campaign levels
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;I · First Light&lt;/strong&gt; · learn SHIFT and the moon bridge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;II · Meltwater&lt;/strong&gt; · deadly water by day, frozen ice by night&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;III · Twilight Rhythm&lt;/strong&gt; · alternating sun stone and moon stone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IV · Frostfall&lt;/strong&gt; · every mechanic combined&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;V · The Longest Day&lt;/strong&gt; · the finale; light the great Shrine&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Beating the campaign unlocks &lt;strong&gt;Endless Solstice&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Gemini was used to build the game
&lt;/h3&gt;

&lt;p&gt;Gemini was not a one off feature bolted on at the end. It was the main development partner.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;codebase&lt;/strong&gt;, I described systems in plain language (physics, tile parsing, phase shift, WebAudio, UI) and iterated with Gemini until everything landed in one self contained &lt;code&gt;index.html&lt;/code&gt;. Bug fixes, the reachability validator, the verification bot, and the Endless Mode pipeline all went through the same loop: prompt, generate, test, refine.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;campaign and sample levels&lt;/strong&gt;, Gemini helped draft and revise ASCII maps against the jump limits and tile rules. I still validated every map with the in game BFS before shipping, which is how unreachable motes and missing spawn points got caught.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;this post&lt;/strong&gt;, Gemini helped structure and polish the writeup so judges could actually follow the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Gemini shows up inside the game
&lt;/h3&gt;

&lt;p&gt;This is the second layer, and the one judges can play with directly.&lt;/p&gt;

&lt;p&gt;Endless Mode uses a pluggable &lt;strong&gt;LevelManager&lt;/strong&gt; with a clear fallback chain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask &lt;strong&gt;Google Gemini&lt;/strong&gt; to invent a brand new map at runtime (if the player enabled AI and supplied their own API key via the &lt;strong&gt;✦ AI&lt;/strong&gt; panel)&lt;/li&gt;
&lt;li&gt;Pull from a shuffled pool of eight verified sample realms from the build&lt;/li&gt;
&lt;li&gt;Echo a random known realm so loading never dead ends&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So Gemini shows up twice: once as the dev tool that built SOLSTICE, and once as a live level designer players can turn on in Endless Mode.&lt;/p&gt;

&lt;p&gt;The runtime path sends a detailed level design brief: tile legend, jump limits (about two tiles up, three across), staircase rules, mote counts, and a full JSON example of a real solvable level from the game. The model must answer with structured JSON via &lt;code&gt;responseSchema&lt;/code&gt;, matching fields like &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;tip&lt;/code&gt;, &lt;code&gt;startDay&lt;/code&gt;, and &lt;code&gt;rows&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Implementation details judges might care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client tries the official &lt;code&gt;@google/genai&lt;/code&gt; SDK with streaming first, then falls back to raw REST if the CDN is blocked&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AbortController&lt;/code&gt; plus a 30 second timeout so a slow network never freezes the run&lt;/li&gt;
&lt;li&gt;API keys live in &lt;code&gt;localStorage&lt;/code&gt; on the player's machine only. No key? Endless Mode still works on the built in pool. Runtime AI is a bonus layer, not a paywall&lt;/li&gt;
&lt;li&gt;Every generated map passes the same reachability validator as campaign content before it hits the canvas&lt;/li&gt;
&lt;li&gt;The HUD shows where the current realm came from (&lt;code&gt;Gemini AI&lt;/code&gt;, &lt;code&gt;Sample realm&lt;/code&gt;, or &lt;code&gt;Echo&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gemini is not generating flavor text off to the side. At build time it helped write the game. At runtime it can dream up the next lawful puzzle inside rules strict enough to reject bad output automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Polish
&lt;/h3&gt;

&lt;p&gt;Visuals are all procedural: gold sun motes, indigo night sky, star fields, phase crossfades, particle bursts on shift and collect. Audio is tiny synthesized bleeps so the file stays portable.&lt;/p&gt;




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

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

&lt;p&gt;This category fits SOLSTICE on two levels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build time:&lt;/strong&gt; Gemini helped write the full game: physics, rendering, audio, level data, the reachability validator, and the Endless Mode pipeline. I steered the design and kept iterating until every campaign level was provably solvable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime:&lt;/strong&gt; Gemini is embedded in the playable product. Endless Mode can call the same model live to invent new maps, with structured JSON output, streaming, graceful fallback, and the same validator that rejects unfair levels.&lt;/p&gt;

&lt;p&gt;That is end to end Google AI usage: from jam concept to shippable code to a feature players can switch on with their own API key.&lt;/p&gt;

&lt;p&gt;Players opt in through the in game &lt;strong&gt;✦ AI&lt;/strong&gt; settings panel. The game never blocks if they skip runtime generation. Built in sample realms and echo fallback keep Endless Mode running either way.&lt;/p&gt;

&lt;p&gt;I am not submitting for &lt;strong&gt;Best Ode to Alan Turing&lt;/strong&gt; this round. The theme here is solstice light and the turning of time rather than computing history.&lt;/p&gt;




&lt;p&gt;Thanks for reading. If you try Endless Mode with Gemini enabled, tell me the weirdest lawful realm it conjured. I am curious which shift puzzle it thinks is clever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Play (recommended): &lt;a href="https://longphanquangminh.github.io/SOLSTICE-jump/" rel="noopener noreferrer"&gt;https://longphanquangminh.github.io/SOLSTICE-jump/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CodePen: &lt;a href="https://codepen.io/longphanquangminh/pen/YPNGYXZ" rel="noopener noreferrer"&gt;https://codepen.io/longphanquangminh/pen/YPNGYXZ&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/longphanquangminh/SOLSTICE-jump" rel="noopener noreferrer"&gt;https://github.com/longphanquangminh/SOLSTICE-jump&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo: &lt;a href="https://www.youtube.com/watch?v=u7lJvqdN9Y8" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=u7lJvqdN9Y8&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Jam: &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;https://dev.to/challenges/june-game-jam-2026-06-03&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>🕹️ SOLSTICE: Hold the Light Until Dawn - A 3D Browser Game for the June Solstice Jam</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sun, 14 Jun 2026 15:42:11 +0000</pubDate>
      <link>https://dev.to/minhlong2605/solstice-hold-the-light-until-dawn-a-3d-browser-game-for-the-june-solstice-jam-3340</link>
      <guid>https://dev.to/minhlong2605/solstice-hold-the-light-until-dawn-a-3d-browser-game-for-the-june-solstice-jam-3340</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;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjcfu665kpk04z1lzp9t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjcfu665kpk04z1lzp9t.png" alt="Demo" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the June solstice the sun stays up longer than any other day of the year. Then it sets anyway. &lt;strong&gt;SOLSTICE&lt;/strong&gt; is what happens after that: the longest night, one stone ring, and you holding the last flame.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://longphanquangminh.github.io/sunbearer/" rel="noopener noreferrer"&gt;Play SOLSTICE&lt;/a&gt;&lt;/strong&gt; · no install, runs in the browser&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;SOLSTICE&lt;/strong&gt; is a 3D action survival game where daylight is not background flavor. It is the mechanic.&lt;/p&gt;

&lt;p&gt;You are the &lt;strong&gt;Sunbearer&lt;/strong&gt;, keeper of an ancient ring of standing stones. When the solstice sun goes down, shadow creatures crawl out to snuff your flame. You fight back with a glowing blade, dash through dark bolts, and vacuum up the light they drop when they break apart.&lt;/p&gt;

&lt;p&gt;The whole run hangs on two meters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Light&lt;/strong&gt; is your health, your urgency, and your fuel. It ticks down over time and falls hard when something hits you. Let it hit zero and the night wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dawn&lt;/strong&gt; is how close you are to sunrise. It climbs as you survive and as you kill. Fill it to 100% and the sky actually changes: the solstice sun crests the stones, the shadows burn off, you win.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your light is your life, your weapon, and the clock. When it runs out, the night wins.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I built this for the jam theme on purpose. Light, darkness, and time are not three separate ideas in the UI. They are one loop you feel in your hands: &lt;strong&gt;slash, dodge, collect, survive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The arena is Stonehenge inspired. The real monument lines up with the solstice sunrise, so defending that ring felt right. The toughest wave and the &lt;strong&gt;Warden of the Long Night&lt;/strong&gt; boss show up late, right before first light. Darkest before dawn is not just flavor text here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Controls&lt;/strong&gt; (laptop friendly, no gamepad needed):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;W&lt;/code&gt; &lt;code&gt;A&lt;/code&gt; &lt;code&gt;S&lt;/code&gt; &lt;code&gt;D&lt;/code&gt; or arrow keys&lt;/td&gt;
&lt;td&gt;Move&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Space&lt;/code&gt; or left click&lt;/td&gt;
&lt;td&gt;Light Slash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Shift&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dash with brief invulnerability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Solar Flare, charged AoE ultimate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Esc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pause&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three difficulties on the start screen: &lt;strong&gt;Acolyte&lt;/strong&gt; (gentler), &lt;strong&gt;Sunbearer&lt;/strong&gt; (default), &lt;strong&gt;Eclipse&lt;/strong&gt; (two bosses, meaner spawns). You can swap difficulty from pause or the end screen if a run feels wrong.&lt;/p&gt;




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

&lt;p&gt;Full run below: sunset, the swarm, the Warden, and the dawn break.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/1WjNur7JzXk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Play here (recommended):&lt;/strong&gt; &lt;strong&gt;&lt;a href="https://longphanquangminh.github.io/sunbearer/" rel="noopener noreferrer"&gt;https://longphanquangminh.github.io/sunbearer/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




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

&lt;p&gt;Whole game lives in one file. Repo:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/longphanquangminh" rel="noopener noreferrer"&gt;
        longphanquangminh
      &lt;/a&gt; / &lt;a href="https://github.com/longphanquangminh/sunbearer" rel="noopener noreferrer"&gt;
        sunbearer
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SOLSTICE — Hold the Light&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A browser-based 3D action-survival game built for the &lt;strong&gt;DEV June Solstice Game Jam (2026)&lt;/strong&gt;. You are the &lt;strong&gt;Sunbearer&lt;/strong&gt;, last keeper of an ancient stone ring. As the solstice sun sets, shadow creatures pour from the dark — your &lt;strong&gt;Light&lt;/strong&gt; is your health, your weapon fuel, and the clock. Survive the longest night and reach dawn.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://longphanquangminh.github.io/sunbearer/" rel="nofollow noopener noreferrer"&gt;Play now&lt;/a&gt;&lt;/strong&gt; · &lt;strong&gt;&lt;a href="https://www.youtube.com/watch?v=1WjNur7JzXk" rel="nofollow noopener noreferrer"&gt;Demo video&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The hook&lt;/h2&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Your light is your life, your weapon, and the clock. When it runs out, the night wins.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Light&lt;/strong&gt; drains over time and drops fast when enemies hit you. Slay shadows to collect light fragments and stay alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dawn&lt;/strong&gt; fills as you survive. Hit 100% and the solstice sun crests the horizon — you win.&lt;/li&gt;
&lt;li&gt;The sky, fog, sun, and stars &lt;strong&gt;repaint in real time&lt;/strong&gt; from sunset through midnight to golden dawn.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Controls&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;W&lt;/code&gt; &lt;code&gt;A&lt;/code&gt; &lt;code&gt;S&lt;/code&gt; &lt;code&gt;D&lt;/code&gt; / Arrow keys&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/longphanquangminh/sunbearer" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Clone and serve locally if you want to poke around (needs HTTP, not &lt;code&gt;file://&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/longphanquangminh/sunbearer.git
&lt;span class="nb"&gt;cd &lt;/span&gt;sunbearer
npx serve &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;I gave myself a hard constraint: &lt;strong&gt;one self contained HTML file&lt;/strong&gt;, no bundler, no downloaded assets, host it anywhere. Open the page, play. That shaped every decision after it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack in short:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three.js r160 over WebGL, loaded with a native &lt;code&gt;importmap&lt;/code&gt; from jsDelivr&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UnrealBloomPass&lt;/code&gt; for glow on the blade, orbs, and dawn light&lt;/li&gt;
&lt;li&gt;Web Audio only: every sound is synthesized at runtime, zero audio files&lt;/li&gt;
&lt;li&gt;Procedural canvas textures for the ground and particles&lt;/li&gt;
&lt;li&gt;No external models or image assets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three.js comes in clean with an import map, no build step:&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;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"importmap"&lt;/span&gt;&lt;span class="nt"&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;imports&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;three&lt;/span&gt;&lt;span class="dl"&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;https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js&lt;/span&gt;&lt;span class="dl"&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;three/addons/&lt;/span&gt;&lt;span class="dl"&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;https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Postprocessing loads dynamically inside &lt;code&gt;try/catch&lt;/code&gt;. If bloom fails on a weird GPU, the game still renders instead of dying on a black screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  The sky is the progress bar
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Dawn&lt;/strong&gt; meter does more than fill a UI bar. One value, &lt;code&gt;dawn&lt;/code&gt; from 0 to 1, drives keyframe blending across sky color, fog, sun angle and intensity, ambient light, hemisphere light, and star opacity. You start at hot sunset, sink into violet dusk and deep midnight, then break into gold when you win. You can read how close sunrise is by looking up, not only by checking HUD numbers.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SKY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// p,    sky,      fog,      sun,    sunInt, amb, hemi, stars&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xE8743B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x6e3a2e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xffb060&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x0c1230&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x0a1026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x6a78c0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFFC56B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x9a6a3e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xffe0a0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;updateDay&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;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dawn&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;dawnMax&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;a&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;t&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SKY&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="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lerpColors&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;sky&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;sky&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;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lerpColors&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;fog&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;fog&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="nb"&gt;sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intensity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lerp&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;sunInt&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;sunInt&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;stars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lerp&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;stars&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;stars&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Combat and waves
&lt;/h3&gt;

&lt;p&gt;Melee uses arc hit detection: range plus facing angle, knockback, combo multiplier on chained kills. Enemies are shades, fast wisps, ranged casters that make you move, and the Warden boss on a wave director that ramps up over the night.&lt;/p&gt;

&lt;p&gt;Difficulty is a thin multiplier layer at run start: enemy HP, damage, speed, spawn rate, light drain, dawn duration, orb drops, enemy cap. Three distinct feels from one codebase without forking logic everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance on a laptop
&lt;/h3&gt;

&lt;p&gt;Capped device pixel ratio, recycled sprite particle pool, blob shadows on most things with one real shadow casting sun, hard enemy cap. It should stay smooth on a normal laptop without asking for a gaming rig.&lt;/p&gt;

&lt;h3&gt;
  
  
  The design change that actually mattered
&lt;/h3&gt;

&lt;p&gt;Early builds had separate HP and a survival timer. Too noisy. Merging them into &lt;strong&gt;Light&lt;/strong&gt; made every choice sharper: dash away and let the meter tick, or push in for fragments that refill you. Tuning that drain vs reward loop took the most iteration, which is why I added selectable difficulty instead of pretending one balance fits everyone.&lt;/p&gt;




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

&lt;p&gt;Submitting for &lt;strong&gt;Best Google AI Usage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Gemini&lt;/strong&gt; helped across the whole project, not just this writeup. I used it to generate and iterate on the game code (Three.js setup, combat, wave logic, sky pipeline, Web Audio synthesis), debug issues, and tune balance. I also used it to draft and polish this DEV submission so the theme and technical choices are easy to follow.&lt;/p&gt;

&lt;p&gt;My role was directing the design (light as health, solstice theme, single file constraint), reviewing what Gemini produced, playtesting, and cutting or fixing what did not feel right in actual runs. The final game is a back and forth between my intent and Gemini's output, not something I typed out line by line solo.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This project used &lt;strong&gt;Google Gemini&lt;/strong&gt; for both game development and this blog post. I am noting that openly for transparency and because it fits the jam's Google AI prize category.&lt;/p&gt;




&lt;p&gt;Hold the light until dawn. 🌅&lt;/p&gt;

&lt;p&gt;If you try it, I would love your high score and honest feedback in the comments. Happy solstice, whichever hemisphere you are in.&lt;/p&gt;

&lt;p&gt;Fancy formatted version of this writeup: &lt;a href="https://longphanquangminh.github.io/sunbearer/post" rel="noopener noreferrer"&gt;longphanquangminh.github.io/sunbearer/post&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#DEVChallenge&lt;/code&gt; &lt;code&gt;#gamedev&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#threejs&lt;/code&gt; &lt;code&gt;#webgl&lt;/code&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>🧩LOGIC SOLSTICE CIPHER - A Code-Breaking Ode to Alan Turing</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Tue, 09 Jun 2026 16:48:18 +0000</pubDate>
      <link>https://dev.to/minhlong2605/solstice-cipher-a-code-breaking-ode-to-alan-turing-44f2</link>
      <guid>https://dev.to/minhlong2605/solstice-cipher-a-code-breaking-ode-to-alan-turing-44f2</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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A quick note on AI:&lt;/strong&gt; I used an Gemini while making this. It helped me shape the idea, write the code, and put together a first draft of this post. I read through everything, made my own edits, and did the testing and the deploy myself.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;SOLSTICE CIPHER is a small code breaking game about beating the sunset on the longest day of the year.&lt;/p&gt;

&lt;p&gt;Here is the setup. You are standing at an observatory on June 21. A scrambled signal of colored light keeps pulsing over the horizon, six colors of the spectrum locked in some hidden order, and you have to figure out that order before the sun goes down. Every guess you make costs you an hour of daylight. The sun drops a little lower, the sky slides from midday blue into orange and then deep night, and the stars start coming out. Burn through all your daylight and you lose.&lt;/p&gt;

&lt;p&gt;The part that tells you how close each guess was is not a plain hint box. I named it The Bombe, after the machine Alan Turing built to crack Enigma. If you get stuck, you can spend a Bombe Assist and let it work out one position for you.&lt;/p&gt;

&lt;p&gt;🎮 &lt;strong&gt;Play it now (nothing to install, runs in the browser):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/editor/longphanquangminh/embed/019ead2e-19d4-767f-9418-5bfc71a2a6b1?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Heads up: CodePen does not load on every network. If it will not open for you, try a different network or a VPN, or just use the full screen version below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Full screen:&lt;/strong&gt;&lt;/p&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://longphanquangminh.github.io/solstice-cipher/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;longphanquangminh.github.io&lt;/span&gt;
          

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


&lt;blockquote&gt;
&lt;p&gt;It is built to be played on a laptop keyboard, so you can run the whole thing with the arrow keys and the number row. There are on screen buttons too if you are on your phone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What I was going for was something you can understand in about five seconds but that still makes you think. I also wanted the June theme to live in how the game actually plays, instead of being pasted on top of something unrelated.&lt;/p&gt;

&lt;p&gt;A few ways it ties back to the theme:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☀️ &lt;strong&gt;The solstice and the passage of time.&lt;/strong&gt; Daylight is your resource. The sun, the sky color, and the stars all move based on how many guesses you have spent, and the longest day of the year hands you the most daylight to work with.&lt;/li&gt;
&lt;li&gt;🌗 &lt;strong&gt;Light against darkness.&lt;/strong&gt; That is literally how you win or lose. Crack the code and the beacon lights up. Run out of daylight and night takes over.&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;A nod to Alan Turing.&lt;/strong&gt; June is his birth month, the whole game is built around breaking a code, and the feedback engine is his Bombe.&lt;/li&gt;
&lt;li&gt;🏳️‍🌈 &lt;strong&gt;A nod to Pride too.&lt;/strong&gt; The signal you are decoding is the six color spectrum, basically a rainbow. Turing was prosecuted for being gay, so decoding a rainbow in his name felt like the right thing for June.&lt;/li&gt;
&lt;/ul&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/longphanquangminh" rel="noopener noreferrer"&gt;
        longphanquangminh
      &lt;/a&gt; / &lt;a href="https://github.com/longphanquangminh/solstice-cipher" rel="noopener noreferrer"&gt;
        solstice-cipher
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🌞 SOLSTICE CIPHER&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Crack the light before the longest day ends.&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;A code-breaking deduction game about racing the dying light of the June solstice — and a love letter to &lt;strong&gt;Alan Turing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://longphanquangminh.github.io/solstice-cipher/" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;▶ Play it now&lt;/strong&gt;&lt;/a&gt; · Built for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03" rel="nofollow"&gt;DEV June Solstice Game Jam 2026&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/d07e336d114f3cb35a238061527e1424021420ea22f41e06f34769247fc94171/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f48544d4c352d63616e7661732d4533344632363f6c6f676f3d68746d6c35266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/d07e336d114f3cb35a238061527e1424021420ea22f41e06f34769247fc94171/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f48544d4c352d63616e7661732d4533344632363f6c6f676f3d68746d6c35266c6f676f436f6c6f723d7768697465" alt="HTML5"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/dbee1f712fce91d1cc82f85b5e62462abf2da6cd73c0acd56df847a2a59218c2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4a6176615363726970742d76616e696c6c612d4637444631453f6c6f676f3d6a617661736372697074266c6f676f436f6c6f723d626c61636b"&gt;&lt;img src="https://camo.githubusercontent.com/dbee1f712fce91d1cc82f85b5e62462abf2da6cd73c0acd56df847a2a59218c2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4a6176615363726970742d76616e696c6c612d4637444631453f6c6f676f3d6a617661736372697074266c6f676f436f6c6f723d626c61636b" alt="Vanilla JS"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/aaf374cd2680eb93b56e603f6012fa5e6d192e2d51b43ff17a89e59d52b724bd/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275696c642d6e6f6e652d73756363657373"&gt;&lt;img src="https://camo.githubusercontent.com/aaf374cd2680eb93b56e603f6012fa5e6d192e2d51b43ff17a89e59d52b724bd/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275696c642d6e6f6e652d73756363657373" alt="No build"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/023e725d702069722fc304b22aa791e08ef3e423fe910237fecf0e6ce2550dbb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646570656e64656e636965732d302d73756363657373"&gt;&lt;img src="https://camo.githubusercontent.com/023e725d702069722fc304b22aa791e08ef3e423fe910237fecf0e6ce2550dbb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646570656e64656e636965732d302d73756363657373" alt="Dependencies"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/b8cadaa967891081f8f165695470689986c028821dd8a040132f6e661795dc0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c7565"&gt;&lt;img src="https://camo.githubusercontent.com/b8cadaa967891081f8f165695470689986c028821dd8a040132f6e661795dc0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c7565" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/longphanquangminh/solstice-cipher/gameplay.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flongphanquangminh%2Fsolstice-cipher%2FHEAD%2Fgameplay.png" alt="SOLSTICE CIPHER gameplay"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;A scrambled &lt;strong&gt;light-signal&lt;/strong&gt; pulses over the horizon: six colors of the spectrum locked in a hidden order. Your job is to &lt;strong&gt;decrypt the sequence before the sun sets&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Every guess burns one hour of daylight — the sun slides down its arc, the sky bleeds from noon-blue to golden dusk to indigo night, and the stars come out. Crack the code and the beacon fires; run out of light and &lt;strong&gt;darkness wins&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The machine that grades your guesses is &lt;strong&gt;The Bombe&lt;/strong&gt; — the device Alan Turing designed to break the Enigma cipher.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Theme connection&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thread&lt;/th&gt;
&lt;th&gt;In the game&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;☀️ &lt;strong&gt;Solstice&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/longphanquangminh/solstice-cipher" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;The whole game is one HTML file. No engine, no framework, no build step, and nothing external except two Google Fonts. You open the file and it runs. Keeping it to a single file made it really easy to host and to share.&lt;/p&gt;

&lt;p&gt;Here is what is inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML5 Canvas for the sky, the sun, the stars, the floating motes, the particles, and the screen shake&lt;/li&gt;
&lt;li&gt;Plain DOM and CSS for the board, the HUD, and the animations&lt;/li&gt;
&lt;li&gt;The Web Audio API for the little retro machine sounds (there are no audio files anywhere)&lt;/li&gt;
&lt;li&gt;Vanilla JavaScript for the game logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The deduction core.&lt;/strong&gt; Underneath, this is basically Mastermind with repeated colors allowed. The Bombe scores each guess in two passes. First it counts the exact hits, then it goes back over the leftover colors and finds the ones that are right but sitting in the wrong slot.&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="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="nx"&gt;secret&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;exact&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;partial&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;sc&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;6&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;gc&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;6&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="mi"&gt;0&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;guess&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="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="k"&gt;if &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="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="nx"&gt;secret&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="nx"&gt;exact&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// right color, right slot&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;sc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;secret&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="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;gc&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="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="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;       &lt;span class="c1"&gt;// tally the misses&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;k&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;k&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;k&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;partial&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;sc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// right color, wrong slot&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;partial&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;Daylight as the clock.&lt;/strong&gt; There is no real countdown. Your guesses are the clock. One number drives the entire mood: &lt;code&gt;f = guessesUsed / maxGuesses&lt;/code&gt;. The sky is three color keyframes that I blend together based on &lt;code&gt;f&lt;/code&gt;, and the sun rides a curve that drops faster as the light runs low.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SKY&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="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;170&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;  &lt;span class="c1"&gt;// noon&lt;/span&gt;
  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;205&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;  &lt;span class="c1"&gt;// golden dusk&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;58&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;        &lt;span class="c1"&gt;// night&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;W&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.42&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// drift toward the western horizon&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;H&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.17&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.62&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// fall faster as the daylight fades&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stars only fade in once &lt;code&gt;f&lt;/code&gt; gets past about 0.4, so dusk is what actually brings out the night sky as you get low on time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Bombe Assist.&lt;/strong&gt; Pressing B spends one of your two charges. It picks a slot you have not solved yet (it leans toward one you currently have wrong), shows you the right color, and locks it in. It is a small tip of the hat to how the real Bombe ruled out possibilities to shrink the search. It also costs you points, so you actually have to decide whether it is worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Making it feel good.&lt;/strong&gt; Crack the code and you get bursts of particles in the spectrum colors plus a quick arpeggio. A wrong guess gets a low buzz, and the lose screen gives the canvas a shake.&lt;/p&gt;

&lt;p&gt;A couple of decisions I am happy with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It plays on the keyboard. Arrows to move, up and down or the number keys to set a color, Enter to decrypt, B for the Bombe. On a laptop you never reach for the mouse.&lt;/li&gt;
&lt;li&gt;Every orb has a number from 1 to 6 printed on it, so you can play by number and not only by color. That makes it work for people who are colorblind.&lt;/li&gt;
&lt;li&gt;Reusing that one &lt;code&gt;f&lt;/code&gt; value for the sky, the sun, and the stars kept the whole atmosphere in sync with the stakes for almost no extra code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It keeps going as long as you do. The codes grow from 4 to 5 to 6 symbols, your daylight scales from 10 to 12 to 14 guesses, and your score (&lt;code&gt;level × 100 + daylight saved × 15 + Bombe charges held × 40&lt;/code&gt;) carries over from level to level until night finally wins.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;🏅 Best Ode to Alan Turing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I went after this one on purpose, and I tried to work Turing into the game itself rather than just name dropping him.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The actual gameplay is code breaking. The thing that grades your guesses is named The Bombe, after the real machine he built against Enigma, and the Bombe Assist echoes how that machine ruled out dead ends to narrow things down.&lt;/li&gt;
&lt;li&gt;The story around it leans on June being his birth month, with your deduction standing in as the light that holds off the dark.&lt;/li&gt;
&lt;li&gt;The code you are cracking is the six color spectrum, a rainbow, on purpose. Turing was prosecuted for being gay and only pardoned long after he died. Decoding a rainbow under his name pulls the solstice, Pride, and computing history into one picture.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;🌞 Thanks for playing, and happy solstice 🌞&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Mon, 08 Jun 2026 15:38:45 +0000</pubDate>
      <link>https://dev.to/minhlong2605/-3i</link>
      <guid>https://dev.to/minhlong2605/-3i</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/minhlong2605/solstice-cipher-write-code-break-ciphers-restore-daylight-5h2h" class="crayons-story__hidden-navigation-link"&gt;☀ Solstice Cipher: Write Code, Break Ciphers, Restore Daylight&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/CMjsaYY7nro" title="☀ Solstice Cipher: Write Code, Break Ciphers, Restore Daylight"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/minhlong2605/solstice-cipher-write-code-break-ciphers-restore-daylight-5h2h" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;June Solstice Game Jam Submission&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/minhlong2605" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png" alt="minhlong2605 profile" class="crayons-avatar__image" width="500" height="500"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/minhlong2605" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Mike
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Mike
                
              
              &lt;div id="story-author-preview-content-3849894" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/minhlong2605" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png" class="crayons-avatar__image" alt="" width="500" height="500"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Mike&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/minhlong2605/solstice-cipher-write-code-break-ciphers-restore-daylight-5h2h" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 8&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/minhlong2605/solstice-cipher-write-code-break-ciphers-restore-daylight-5h2h" id="article-link-3849894"&gt;
          ☀ Solstice Cipher: Write Code, Break Ciphers, Restore Daylight
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gamechallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gamechallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gamedev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gamedev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/minhlong2605/solstice-cipher-write-code-break-ciphers-restore-daylight-5h2h" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;21&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/minhlong2605/solstice-cipher-write-code-break-ciphers-restore-daylight-5h2h#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              2&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>☀ Solstice Cipher: Write Code, Break Ciphers, Restore Daylight</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Mon, 08 Jun 2026 15:38:32 +0000</pubDate>
      <link>https://dev.to/minhlong2605/solstice-cipher-write-code-break-ciphers-restore-daylight-5h2h</link>
      <guid>https://dev.to/minhlong2605/solstice-cipher-write-code-break-ciphers-restore-daylight-5h2h</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;p&gt;&lt;strong&gt;Solstice Cipher&lt;/strong&gt; is a browser puzzle game where you write real JavaScript to decrypt intercepted messages — inside a full &lt;strong&gt;Monaco Editor&lt;/strong&gt; (the same engine that powers VS Code). Each solved cipher pushes back the night until the June 21 solstice breaks.&lt;/p&gt;

&lt;p&gt;🎮 &lt;strong&gt;Play:&lt;/strong&gt; &lt;/p&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://solstice-cipher-encrypt-coding-game.vercel.app" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;solstice-cipher-encrypt-coding-game.vercel.app&lt;/span&gt;
          

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





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

&lt;p&gt;The core loop is simple: read an intercepted transmission, implement &lt;code&gt;decrypt(text)&lt;/code&gt; in a live code editor, hit &lt;strong&gt;Run decrypt()&lt;/strong&gt;, and advance if the output matches.&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;5 cipher levels&lt;/strong&gt;, each tied to a phase of the solstice day:&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;Phase&lt;/th&gt;
&lt;th&gt;Cipher&lt;/th&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Pre-dawn&lt;/td&gt;
&lt;td&gt;Caesar shift&lt;/td&gt;
&lt;td&gt;HELLO WORLD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Sunrise&lt;/td&gt;
&lt;td&gt;Atbash mirror&lt;/td&gt;
&lt;td&gt;THE COOL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Morning&lt;/td&gt;
&lt;td&gt;ROT13&lt;/td&gt;
&lt;td&gt;PRIDE HUMAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Midday&lt;/td&gt;
&lt;td&gt;Vigenère (keyword &lt;strong&gt;PRIDE&lt;/strong&gt;)&lt;/td&gt;
&lt;td&gt;BE YOU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Longest Day&lt;/td&gt;
&lt;td&gt;Caesar + word reverse&lt;/td&gt;
&lt;td&gt;THE LIGHT SHINES&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you progress, a &lt;strong&gt;daylight bar&lt;/strong&gt; fills from 15% to 100% — the sky literally brightens behind the UI. The narrative is set at &lt;strong&gt;Bletchley Park, June 1941&lt;/strong&gt;, framing each puzzle as a transmission Alan Turing's team might have intercepted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My intended goal:&lt;/strong&gt; make a game where &lt;em&gt;writing algorithms&lt;/em&gt; is the gameplay — not clicking tiles or guessing letters. The solstice theme isn't just decoration: every level is a step from darkness toward the longest day, and the final win screen lands on June 21 with Turing's own words.&lt;/p&gt;




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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/g8eMmj_o9ow"&gt;
  &lt;/iframe&gt;
 &lt;/p&gt;

&lt;p&gt;The demo walks through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Intro — solstice + Turing context&lt;/li&gt;
&lt;li&gt;Live gameplay — Caesar cipher in Monaco Editor, &lt;strong&gt;Run decrypt()&lt;/strong&gt;, daylight advancing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini AI hint&lt;/strong&gt; streaming in with line-specific guidance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suggest fix&lt;/strong&gt; → Monaco diff view → Apply patch&lt;/li&gt;
&lt;li&gt;Win screen — &lt;em&gt;THE LIGHT SHINES&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;


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




&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/longphanquangminh" rel="noopener noreferrer"&gt;
        longphanquangminh
      &lt;/a&gt; / &lt;a href="https://github.com/longphanquangminh/solstice-cipher-encrypt-coding-game" rel="noopener noreferrer"&gt;
        solstice-cipher-encrypt-coding-game
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Solstice Cipher ☀&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A &lt;strong&gt;code-breaking puzzle game&lt;/strong&gt; for the &lt;a href="https://dev.to/devteam/join-the-june-solstice-game-jam-1000-in-prizes-3jla" rel="nofollow"&gt;June Solstice Game Jam&lt;/a&gt; on DEV.&lt;/p&gt;
&lt;p&gt;Write JavaScript &lt;code&gt;decrypt()&lt;/code&gt; functions in a real &lt;strong&gt;Monaco Editor&lt;/strong&gt; (VS Code's engine via &lt;code&gt;@monaco-editor/react&lt;/code&gt;) to break ciphers before the June 21 solstice. Each solved level brings more daylight — honoring &lt;strong&gt;Alan Turing&lt;/strong&gt;, &lt;strong&gt;Pride&lt;/strong&gt;, and the passage from darkness to light.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this idea targets the prizes&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prize category&lt;/th&gt;
&lt;th&gt;How the game fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overall theme&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Solstice countdown, daylight progression, June 21 narrative&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best Ode to Alan Turing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bletchley Park story, Caesar/Enigma-style ciphers, algorithms as gameplay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best Google AI Usage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optional Gemini 3.5 Flash hints via API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;cd&lt;/span&gt; solstice-cipher
npm install
npm run dev&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open &lt;a href="http://localhost:5173" rel="nofollow noopener noreferrer"&gt;http://localhost:5173&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Optional: Gemini AI hints&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Create &lt;code&gt;.env&lt;/code&gt; in the project root:&lt;/p&gt;
&lt;div class="highlight highlight-source-dotenv notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-v"&gt;VITE_GEMINI_API_KEY&lt;/span&gt;&lt;span class="pl-k"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;your_google_ai_studio_key&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Get a free key at &lt;a href="https://aistudio.google.com/apikey" rel="nofollow noopener noreferrer"&gt;Google AI Studio&lt;/a&gt;. Without it, static hints still work.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How to play&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Read the intercepted ciphertext and…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/longphanquangminh/solstice-cipher-encrypt-coding-game" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Key files if you want to dig in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/data/levels.ts&lt;/code&gt; — all 5 ciphers, starter code, solutions, narrative&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/components/GameScreen.tsx&lt;/code&gt; — main gameplay loop&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/components/CodeEditor.tsx&lt;/code&gt; — Monaco setup + custom Bletchley terminal theme&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/utils/gemini.ts&lt;/code&gt; — Gemini streaming hints + code fix generation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/utils/analyzeCode.ts&lt;/code&gt; — local static analysis that feeds the AI context&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/utils/runUserCode.ts&lt;/code&gt; — in-browser &lt;code&gt;decrypt()&lt;/code&gt; execution&lt;/li&gt;
&lt;/ul&gt;




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

&lt;h3&gt;
  
  
  Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React 19&lt;/strong&gt; + &lt;strong&gt;TypeScript&lt;/strong&gt; + &lt;strong&gt;Vite&lt;/strong&gt; — fast dev, static deploy, no backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@monaco-editor/react" rel="noopener noreferrer"&gt;@monaco-editor/react&lt;/a&gt;&lt;/strong&gt; — the entire gameplay UI lives in Monaco, not a fake textarea&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@google/genai" rel="noopener noreferrer"&gt;@google/genai&lt;/a&gt;&lt;/strong&gt; — official Google AI SDK for streaming hints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion&lt;/strong&gt; — intro/win screen animations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monaco as the game engine
&lt;/h3&gt;

&lt;p&gt;The editor uses an &lt;strong&gt;uncontrolled&lt;/strong&gt; pattern (&lt;code&gt;defaultValue&lt;/code&gt; + ref) so typing feels native — no React re-render on every keystroke, which was critical for a smooth coding experience.&lt;/p&gt;

&lt;p&gt;I defined a custom &lt;strong&gt;&lt;code&gt;bletchley-terminal&lt;/code&gt;&lt;/strong&gt; theme: phosphor green strings, hazard red keywords, dark CRT background. Line highlighting marks the exact lines Gemini flags as buggy.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Suggest fix&lt;/strong&gt;, I use Monaco's &lt;strong&gt;DiffEditor&lt;/strong&gt; — red/green side-by-side diff — with Apply/Dismiss. Patches go through &lt;code&gt;executeEdits&lt;/code&gt; so undo works naturally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running player code safely
&lt;/h3&gt;

&lt;p&gt;Player code runs in-browser via &lt;code&gt;new Function()&lt;/code&gt; with a strict contract: define &lt;code&gt;decrypt(text)&lt;/code&gt;, return a string. Output is normalized (uppercase, trimmed) before comparison. No network, no server — fully static.&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="c1"&gt;// src/utils/runUserCode.ts — simplified&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fn&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;Function&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="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  if (typeof decrypt !== 'function') throw new Error('Define decrypt(text)');
  return decrypt(&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;encrypted&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Gemini AI — not a chatbot bolt-on
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;VITE_GEMINI_API_KEY&lt;/code&gt; is set, &lt;strong&gt;Turing Assist&lt;/strong&gt; activates via &lt;code&gt;gemini-3.5-flash&lt;/code&gt; and &lt;code&gt;generateContentStream&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before any API call, the game runs &lt;strong&gt;local static analysis&lt;/strong&gt; on the player's code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detects untouched starter template lines&lt;/li&gt;
&lt;li&gt;Finds passthrough bugs (&lt;code&gt;result += char&lt;/code&gt; without shifting)&lt;/li&gt;
&lt;li&gt;Classifies run state: &lt;code&gt;NOT_RUN&lt;/code&gt;, &lt;code&gt;RUNTIME_ERROR&lt;/code&gt;, &lt;code&gt;WRONG_OUTPUT&lt;/code&gt;, &lt;code&gt;CORRECT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Picks a hint mode: &lt;strong&gt;GUIDE&lt;/strong&gt;, &lt;strong&gt;DEBUG&lt;/strong&gt;, or &lt;strong&gt;ERROR&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That analysis is sent to Gemini along with numbered source code. The model returns hints that reference &lt;strong&gt;specific line numbers&lt;/strong&gt; — never the full plaintext answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suggest fix&lt;/strong&gt; is a second Gemini call that returns a fenced code block. Multiple candidates are validated by actually running them against the encrypted input; the first working patch wins. If Gemini fails, a &lt;strong&gt;local diff engine&lt;/strong&gt; compares starter vs solution line-by-line as fallback — the game works fully offline without an API key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual design
&lt;/h3&gt;

&lt;p&gt;Industrial brutalist CRT aesthetic: scanlines, telemetry chrome, Enigma rotor graphic, and a &lt;strong&gt;Pride stripe&lt;/strong&gt; on the intro display. The &lt;code&gt;--daylight&lt;/code&gt; CSS variable drives a dynamic sky gradient — the UI literally gets brighter as you solve puzzles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interesting decisions
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AI as tutor, not spoiler&lt;/strong&gt; — system prompts forbid revealing answers; hints are contextual to &lt;em&gt;your&lt;/em&gt; broken code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming hints&lt;/strong&gt; — text appears token-by-token like a live terminal feed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual fallback chain&lt;/strong&gt; — Gemini → local analysis → static per-level hints; no dead ends&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code fix validation&lt;/strong&gt; — never apply a patch that doesn't produce the correct decrypt output&lt;/li&gt;
&lt;/ol&gt;




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

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

&lt;p&gt;This category is the heart of the project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Setting:&lt;/strong&gt; Bletchley Park, June 1941 — the same era Turing cracked Enigma&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mechanics:&lt;/strong&gt; Gameplay &lt;em&gt;is&lt;/em&gt; writing decryption algorithms — Caesar, Atbash, ROT13, Vigenère&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrative:&lt;/strong&gt; Each level references how Turing learned patterns, saw symmetry in code, and turned hidden truth into clarity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Win screen:&lt;/strong&gt; Quotes Turing directly — &lt;em&gt;"We can only see a short distance ahead, but we can see plenty there that needs to be done."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI details:&lt;/strong&gt; Enigma rotor, &lt;code&gt;SYS / BP-1941&lt;/code&gt; header, Bletchley telemetry framing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The game doesn't just mention Turing — it asks you to &lt;em&gt;think like him&lt;/em&gt;: break ciphers by writing code.&lt;/p&gt;

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

&lt;p&gt;Gemini is integrated as &lt;strong&gt;Turing Assist&lt;/strong&gt;, a cryptography tutor that understands your current code state:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;How Gemini is used&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Smart hints&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;generateContentStream&lt;/code&gt; with system prompt + local code analysis → line-specific GUIDE/DEBUG/ERROR hints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Suggest fix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Second call returns a minimal patch; validated by running against ciphertext&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fallback&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local static analysis + offline hints when no API key — AI enhances, never blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Official &lt;code&gt;@google/genai&lt;/code&gt; — not a raw fetch wrapper&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The AI receives structured context (run status, detected issues, numbered code) rather than a generic "help me solve this puzzle" prompt. That makes hints feel like a pair programmer at Bletchley, not a spoiler bot.&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;VITE_GEMINI_API_KEY&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt; (free key from &lt;a href="https://aistudio.google.com/apikey" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt;) to enable live AI. Without it, static hints still work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Start (for judges who want to run it locally)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/longphanquangminh/solstice-cipher-encrypt-coding-game.git
&lt;span class="nb"&gt;cd &lt;/span&gt;solstice-cipher
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional — enable Gemini hints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VITE_GEMINI_API_KEY=your_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Tags
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;#devchallenge&lt;/code&gt; &lt;code&gt;#gamechallenge&lt;/code&gt; &lt;code&gt;#gamedev&lt;/code&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#typescript&lt;/code&gt; &lt;code&gt;#monacoeditor&lt;/code&gt; &lt;code&gt;#googleai&lt;/code&gt; &lt;code&gt;#gemini&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;Thanks for reading — and happy solstice! 🌈☀&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Mon, 25 May 2026 11:04:20 +0000</pubDate>
      <link>https://dev.to/minhlong2605/-3fk7</link>
      <guid>https://dev.to/minhlong2605/-3fk7</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/minhlong2605/interview-coach-ai-practice-mock-interviews-locally-with-gemma-4-jan-1816" class="crayons-story__hidden-navigation-link"&gt;🎙️Interview Coach AI — Practice Mock Interviews Locally with Gemma 4 + Jan&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/minhlong2605/interview-coach-ai-practice-mock-interviews-locally-with-gemma-4-jan-1816" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Gemma 4 Challenge: Build With Gemma 4 Submission&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/minhlong2605" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png" alt="minhlong2605 profile" class="crayons-avatar__image" width="500" height="500"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/minhlong2605" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Mike
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Mike
                
              
              &lt;div id="story-author-preview-content-3742181" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/minhlong2605" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2947620%2F14736a9e-f821-494c-a75c-e96c4808e662.png" class="crayons-avatar__image" alt="" width="500" height="500"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Mike&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/minhlong2605/interview-coach-ai-practice-mock-interviews-locally-with-gemma-4-jan-1816" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/minhlong2605/interview-coach-ai-practice-mock-interviews-locally-with-gemma-4-jan-1816" id="article-link-3742181"&gt;
          🎙️Interview Coach AI — Practice Mock Interviews Locally with Gemma 4 + Jan
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gemmachallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gemmachallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gemma"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gemma&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/minhlong2605/interview-coach-ai-practice-mock-interviews-locally-with-gemma-4-jan-1816" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;16&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/minhlong2605/interview-coach-ai-practice-mock-interviews-locally-with-gemma-4-jan-1816#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>career</category>
      <category>gemmachallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>🎙️Interview Coach AI — Practice Mock Interviews Locally with Gemma 4 + Jan</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sun, 24 May 2026 17:32:20 +0000</pubDate>
      <link>https://dev.to/minhlong2605/interview-coach-ai-practice-mock-interviews-locally-with-gemma-4-jan-1816</link>
      <guid>https://dev.to/minhlong2605/interview-coach-ai-practice-mock-interviews-locally-with-gemma-4-jan-1816</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Build with Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Interview Coach AI&lt;/strong&gt; is a local-first mock interview web app. You work through a &lt;strong&gt;10-module curriculum&lt;/strong&gt; (self-intro, behavioral, technical, capstone mock), speak or type your answers to an AI recruiter, and get a &lt;strong&gt;full performance report&lt;/strong&gt; when the session ends — radar scores, strengths, weaknesses, and written feedback.&lt;/p&gt;

&lt;p&gt;The product loop is simple on purpose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick a lesson on the practice hub&lt;/li&gt;
&lt;li&gt;Start a &lt;strong&gt;Preparation&lt;/strong&gt; session (guided flow, live transcript, real-time scores)&lt;/li&gt;
&lt;li&gt;Review analytics and track progress in your browser&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No cloud LLM bill for the core experience. The “brain” of the app is &lt;strong&gt;Gemma 4 running on your machine&lt;/strong&gt;, wired through &lt;strong&gt;Jan’s local server&lt;/strong&gt; and its OpenAPI/Swagger docs so you can inspect and test every call before the React app ever hits it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tsysvjpo0esz9zdyj1u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tsysvjpo0esz9zdyj1u.png" alt="Interview Coach AI — dashboard and mock interview" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Quick local demo (no deploy required):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/longphanquangminh/gemma-interview.git
&lt;span class="nb"&gt;cd &lt;/span&gt;gemma-interview
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;1/ Open &lt;strong&gt;Jan&lt;/strong&gt; → load &lt;strong&gt;&lt;code&gt;gemma-4-E4B-it-IQ4_XS&lt;/code&gt;&lt;/strong&gt; → start the &lt;strong&gt;local API server&lt;/strong&gt; (default &lt;code&gt;http://127.0.0.1:1337&lt;/code&gt;)&lt;br&gt;
2/ Open Jan’s &lt;strong&gt;Swagger / API docs&lt;/strong&gt; in the browser — confirm &lt;code&gt;POST /v1/messages&lt;/code&gt; responds&lt;/p&gt;

&lt;p&gt;Jan app: &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm60yw8f6k3j6zzzoxzaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm60yw8f6k3j6zzzoxzaw.png" alt="Jan app" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LLM Swagger: &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93xc44gxf9hvcj7fk3b7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93xc44gxf9hvcj7fk3b7.png" alt="Jan" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3/ Visit &lt;code&gt;http://localhost:3000&lt;/code&gt; → &lt;strong&gt;Open practice hub&lt;/strong&gt; → Module 1 → &lt;strong&gt;Preparation&lt;/strong&gt; → complete one question and open the report&lt;/p&gt;

&lt;p&gt;That’s the whole contest story: &lt;strong&gt;Gemma on Jan, in your room, in your app.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Youtube video showcasing original version of the app (Please turn up the volume to hear clearly):&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/AX8PjZ8Mb4Q"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/a5vFTzomi8s"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4u6uai982wfgztymsjgc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4u6uai982wfgztymsjgc.png" alt="Demo1" width="800" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkh3rkqys2dtn3oj5d05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkh3rkqys2dtn3oj5d05.png" alt="Demo2" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5r35c82bmsxfmir3wcin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5r35c82bmsxfmir3wcin.png" alt="Demo3" width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6mzp4dv6uvc9le74018.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6mzp4dv6uvc9le74018.png" alt="Demo4" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kuod447x9vpsl358yc4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kuod447x9vpsl358yc4.png" alt="Demo5" width="800" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbf73my99txqhmukm483t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbf73my99txqhmukm483t.png" alt="Demo6" width="800" height="769"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctsyea08r922hkbv770i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctsyea08r922hkbv770i.png" alt="Demo55" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;


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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/longphanquangminh" rel="noopener noreferrer"&gt;
        longphanquangminh
      &lt;/a&gt; / &lt;a href="https://github.com/longphanquangminh/gemma-interview" rel="noopener noreferrer"&gt;
        gemma-interview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Interview Coach AI&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;General-purpose &lt;strong&gt;mock interview&lt;/strong&gt; practice with an AI recruiter, structured curriculum, and evidence-based reports. Built for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06" rel="nofollow"&gt;Gemma 4 Challenge&lt;/a&gt; on DEV — &lt;strong&gt;Gemma 4&lt;/strong&gt; (or any OpenAI-compatible model) drives planning, turn-taking, live scoring, and post-session analytics.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Highlights&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Area&lt;/th&gt;

&lt;th&gt;Details&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Curriculum&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;10 modules (self-intro → capstone mock), sequential unlock&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Preparation&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;5-question flow, avatar + TTS, transcript, live scores, local LLM&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Live interview&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Full-screen Tavus video room; LLM report after end&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;App hub&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Dashboard, profile, settings, session history (separate from marketing landing)&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt; Node.js 18+, a Gemma 4 (or compatible) endpoint, optional Tavus key for live video.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install
cp .env.example .env
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; LOCAL_AI_BASE_URL + LOCAL_AI_MODEL → your Gemma / LM Studio / AI Studio proxy&lt;/span&gt;
npm run dev&lt;/pre&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Marketing site:&lt;/strong&gt; &lt;a href="http://localhost:3000" rel="nofollow noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice hub:&lt;/strong&gt; &lt;a href="http://localhost:3000/dashboard" rel="nofollow noopener noreferrer"&gt;http://localhost:3000/dashboard&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Environment&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Variable&lt;/th&gt;

&lt;th&gt;Used for&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;code&gt;LOCAL_AI_BASE_URL&lt;/code&gt;&lt;/td&gt;

&lt;td&gt;OpenAI-compatible Messages API (Gemma)&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;code&gt;LOCAL_AI_MODEL&lt;/code&gt;&lt;/td&gt;

&lt;td&gt;Model id on that endpoint&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;code&gt;VITE_TAVUS_*&lt;/code&gt;&lt;/td&gt;

&lt;td&gt;Live interview mode only&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;See &lt;code&gt;.env.example&lt;/code&gt;…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/longphanquangminh/gemma-interview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/longphanquangminh/gemma-interview" rel="noopener noreferrer"&gt;github.com/longphanquangminh/gemma-interview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gemma integration lives in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/services/aiService.ts   →  every prompt + parse + score calibration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app sends a single user message per request to Jan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST http://127.0.0.1:1337/v1/messages
Content-Type: application/json

{
  "model": "gemma-4-E4B-it-IQ4_XS",
  "messages": [{ "role": "user", "content": "&amp;lt;structured prompt&amp;gt;" }]
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure via &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOCAL_AI_BASE_URL="http://127.0.0.1:1337"
LOCAL_AI_MODEL="gemma-4-E4B-it-IQ4_XS"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How I Used Gemma 4
&lt;/h2&gt;

&lt;p&gt;Gemma 4 is not decoration in this project — it &lt;strong&gt;is&lt;/strong&gt; the interviewer, the examiner, and the report writer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which model, and why
&lt;/h3&gt;

&lt;p&gt;I run &lt;strong&gt;&lt;code&gt;gemma-4-E4B-it-IQ4_XS&lt;/code&gt;&lt;/strong&gt; through Jan locally — Gemma 4’s &lt;strong&gt;E4B&lt;/strong&gt; edge variant with &lt;strong&gt;IQ4_XS&lt;/strong&gt; quantization.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gemma 4 variant&lt;/th&gt;
&lt;th&gt;Why I did / didn’t choose it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E4B (edge, ~4B effective) — my pick&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fast enough for &lt;strong&gt;many calls per session&lt;/strong&gt; (plan → 5× turn/score → report). IQ4_XS keeps RAM/VRAM modest so anyone can rehearse interviews on a normal laptop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2B-class&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Even lighter, but I wanted a bit more headroom for strict JSON + multi-step turn format across a full mock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;31B Dense&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Best raw reasoning, overkill for this workflow and too heavy for “open Jan and practice tonight”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;26B MoE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strong middle ground, but I deliberately wanted to show the &lt;strong&gt;edge line&lt;/strong&gt; can carry a full structured agent loop when prompts + code guards are designed for it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Intentional fit:&lt;/strong&gt; Interview Coach is not open-ended chat. Every Gemma call has a &lt;strong&gt;fixed output shape&lt;/strong&gt; (JSON plan, &lt;code&gt;CLASSIFICATION&lt;/code&gt; / &lt;code&gt;ACTION&lt;/code&gt; / &lt;code&gt;SAY&lt;/code&gt; lines, score JSON). That lets E4B punch above its weight — and when the model drifts, &lt;code&gt;applyTurnGuards()&lt;/code&gt; and parsers in &lt;code&gt;aiService.ts&lt;/code&gt; keep the UX stable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IQ4_XS:&lt;/strong&gt; aggressive quant for speed and size. Trade-off is occasional sloppy JSON; I debug those cases in &lt;strong&gt;Jan Swagger&lt;/strong&gt; first, then tighten prompts. For a practice app with 10+ inference steps per session, &lt;strong&gt;latency and accessibility&lt;/strong&gt; mattered more than squeezing the last point of benchmark score from a 31B file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Jan + Swagger mattered
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://jan.ai/" rel="noopener noreferrer"&gt;Jan&lt;/a&gt; made the integration feel &lt;strong&gt;production-shaped&lt;/strong&gt; instead of hacky:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local server&lt;/strong&gt; with an OpenAI-compatible &lt;strong&gt;&lt;code&gt;/v1/messages&lt;/code&gt;&lt;/strong&gt; endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swagger UI&lt;/strong&gt; to prototype prompts, check payloads, and debug bad JSON &lt;em&gt;before&lt;/em&gt; wiring the React UI&lt;/li&gt;
&lt;li&gt;Same model id end-to-end — what works in Swagger is what the app calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That loop — &lt;strong&gt;Swagger first, app second&lt;/strong&gt; — saved hours. Gemma’s responses are only useful here because the contract is strict; Jan’s docs let me validate that contract in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Gemma does in the app (every call)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;aiService&lt;/code&gt; function&lt;/th&gt;
&lt;th&gt;Gemma’s job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Start session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;generateInterviewPlan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JSON plan: welcome + &lt;strong&gt;5 interview questions&lt;/strong&gt; (typed: intro, technical, behavioral, …)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Each answer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;processTurn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recruiter line + &lt;code&gt;CLASSIFICATION&lt;/code&gt; / &lt;code&gt;ACTION&lt;/code&gt; / &lt;code&gt;SAY&lt;/code&gt; (follow-up, next, repeat, end)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live feedback&lt;/td&gt;
&lt;td&gt;&lt;code&gt;analyzeResponse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scores: clarity, confidence, professionalism&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getSessionSummary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full report JSON: overall + dimensions + strengths + weaknesses&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  A[User answers] --&amp;gt; B[React app]
  B --&amp;gt; C[Jan local API]
  C --&amp;gt; D[Gemma 4 E4B]
  D --&amp;gt; C
  C --&amp;gt; B
  B --&amp;gt; E[Transcript + live scores]
  B --&amp;gt; F[Analytics report]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Gemma shines on this workload
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structured JSON&lt;/strong&gt; — plans and reports must parse; E4B + tight prompts + parsers made this workable locally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn protocol&lt;/strong&gt; — line-based &lt;code&gt;SAY:&lt;/code&gt; output is easier to guard in code than free-form chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence-based scoring&lt;/strong&gt; — rubric prompts plus calibration in TypeScript; Gemma supplies judgments, the app enforces fairness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy, cost, and speed&lt;/strong&gt; — everything stays on your machine; E4B + IQ4_XS keeps sessions responsive enough to actually finish a module.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’m impressed that &lt;strong&gt;Gemma 4 E4B can own a full agent loop&lt;/strong&gt; on consumer hardware — plan → dialogue → score → summarize — without sending transcripts to a hosted API. That’s the point of the edge family, and this app is built to showcase it.&lt;/p&gt;

&lt;h3&gt;
  
  
  One request from the judges
&lt;/h3&gt;

&lt;p&gt;If you try the repo, load &lt;strong&gt;&lt;code&gt;gemma-4-E4B-it-IQ4_XS&lt;/code&gt;&lt;/strong&gt; in Jan, hit &lt;strong&gt;Swagger&lt;/strong&gt; once, then run Module 1 in the app. The story is &lt;strong&gt;edge Gemma + strict contracts + Jan&lt;/strong&gt;, not a thin chat wrapper.&lt;/p&gt;




</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
  </channel>
</rss>
