<?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: Maneesh Thakur</title>
    <description>The latest articles on DEV Community by Maneesh Thakur (@maneesh_thakur_d16c2852fa).</description>
    <link>https://dev.to/maneesh_thakur_d16c2852fa</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%2F3714917%2F2541daad-f26f-4567-b793-78ab43d38ead.png</url>
      <title>DEV Community: Maneesh Thakur</title>
      <link>https://dev.to/maneesh_thakur_d16c2852fa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maneesh_thakur_d16c2852fa"/>
    <language>en</language>
    <item>
      <title>The 2 AM Call ....</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Sun, 23 Aug 2026 06:20:49 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/the-2-am-call--30lc</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/the-2-am-call--30lc</guid>
      <description>&lt;h1&gt;
  
  
  comic-Strip #De-Stress
&lt;/h1&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%2Fhurwb7blj7dvyppqciz5.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%2Fhurwb7blj7dvyppqciz5.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>programming</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>A leveled map of 149 GenAI tools — sorted by concept depth, not hype</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Thu, 20 Aug 2026 19:13:52 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/a-leveled-map-of-149-genai-tools-sorted-by-concept-depth-not-hype-18ch</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/a-leveled-map-of-149-genai-tools-sorted-by-concept-depth-not-hype-18ch</guid>
      <description>&lt;p&gt;Most "AI tools" lists are a wall of 500 links sorted by… nothing. A beginner and a distributed-systems engineer open the same undifferentiated pile and both bounce off it.&lt;/p&gt;

&lt;p&gt;So I built a different kind of map: &lt;strong&gt;149 GenAI tools sorted by concept depth&lt;/strong&gt; — not by how &lt;em&gt;hard&lt;/em&gt; a tool is, but by how much you need to &lt;em&gt;understand&lt;/em&gt; to use it well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five levels
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Level 0 — Consume ready-made GenAI.&lt;/strong&gt; No code, no theory. (ChatGPT, Claude, Copilot.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 1 — Run &amp;amp; build, no/low code.&lt;/strong&gt; GUI model runners, drag-and-drop apps. (Ollama, Flowise.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 2 — Code with libraries.&lt;/strong&gt; Python against SDKs and frameworks. (OpenAI SDK, LangChain.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 3 — Build, fine-tune &amp;amp; ship.&lt;/strong&gt; RAG, agents, vector DBs, eval, serving. (LangGraph, Pinecone, PEFT.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 4 — Research &amp;amp; scale.&lt;/strong&gt; Distributed training, inference engines, GPU kernels. (vLLM, DeepSpeed, FlashAttention.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trick: &lt;strong&gt;ease of use ≠ concept depth.&lt;/strong&gt; ChatGPT is trivial to use, so it's Level 0. FlashAttention is Level 4 — not hard to install, but you need to understand attention and GPU memory to know why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two links per tool
&lt;/h2&gt;

&lt;p&gt;On the map, every tool has exactly two links — 📖 one thing to &lt;strong&gt;read&lt;/strong&gt; (a hand-picked, approachable guide) and 🐙 the &lt;strong&gt;code&lt;/strong&gt; (repo or homepage). No taxonomy, no noise. Each entry looks like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ollama&lt;/strong&gt; — run open models locally · 📖 &lt;em&gt;a beginner's tutorial&lt;/em&gt; · 🐙 &lt;em&gt;the repo&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sorting by depth turns a directory into a &lt;em&gt;path&lt;/em&gt;: look at your rung, see 10–25 tools instead of 500, pick one — and the reading and the code are right there. It's a searchable single-page app: no signup, no tracking, open source (CC0).&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Browse the map:&lt;/strong&gt; &lt;a href="https://maneesh-kumar-thakur.github.io/self-serve-learnings-4-all/" rel="noopener noreferrer"&gt;https://maneesh-kumar-thakur.github.io/self-serve-learnings-4-all/&lt;/a&gt;&lt;br&gt;
⭐ &lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/maneesh-kumar-thakur/self-serve-learnings-4-all" rel="noopener noreferrer"&gt;https://github.com/maneesh-kumar-thakur/self-serve-learnings-4-all&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to share feedback or if you are looking for help/content on specific topics&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>machinelearning</category>
      <category>opensource</category>
    </item>
    <item>
      <title>🐾 Translate My Dog - AI Canine Micro-Expression Reader</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Mon, 17 Aug 2026 06:58:36 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/translate-my-dog-ai-canine-micro-expression-reader-330i</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/translate-my-dog-ai-canine-micro-expression-reader-330i</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-08-13"&gt;Weekend Challenge: Dog Days Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Translate My Dog is a multimodal AI web application that reads your dog's subtle facial micro-expressions, ear positions, and body posture from photos to reveal what they are actually thinking. It generates witty, character-accurate inner monologues and synthesizes character voice audio.&lt;/p&gt;

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


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://translate-my-dog.ai.studio" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;translate-my-dog.ai.studio&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&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/maneesh-kumar-thakur" rel="noopener noreferrer"&gt;
        maneesh-kumar-thakur
      &lt;/a&gt; / &lt;a href="https://github.com/maneesh-kumar-thakur/devto-challenge-dog-days-edition" rel="noopener noreferrer"&gt;
        devto-challenge-dog-days-edition
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      dev.to weekend challenge
    &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;🐾 Translate My Dog (AI Canine Micro-Expression Reader)&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Translate My Dog&lt;/strong&gt; is a multimodal AI web application that reads your dog's subtle facial micro-expressions, ear positions, and body posture from photos to reveal what they are &lt;em&gt;actually&lt;/em&gt; thinking. It generates witty, character-accurate inner monologues and synthesizes character voice audio.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🌟 Key Features&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. 🔍 Multimodal Canine Micro-Expression Vision Analysis&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Powered by &lt;strong&gt;Google Gemini Vision&lt;/strong&gt; models (&lt;code&gt;gemini-2.5-flash&lt;/code&gt;, &lt;code&gt;gemini-flash-lite-latest&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;Evaluates canine facial cues, pupil dilation, ear angles, head tilts, tail dynamics, and environmental clues (e.g. vacuum cleaners, treat jars, tennis balls).&lt;/li&gt;
&lt;li&gt;Outputs a comprehensive translation report
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First-Person Inner Monologue&lt;/strong&gt;: Spoken directly in character as the dog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detected Mood Title&lt;/strong&gt;: Humorous real-time emotional state (e.g., &lt;em&gt;"94% Betrayed By Diet Kibble"&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identified Breed &amp;amp; Heritage Insight&lt;/strong&gt;: Visual breed detection with evolutionary and behavioral notes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Clues Breakdown&lt;/strong&gt;: 3 specific visual observations justifying the monologue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canine&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/maneesh-kumar-thakur/devto-challenge-dog-days-edition" 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;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;Applying for Best Use of ElevenLabs &amp;amp; Best Use of Google AI&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Open Late — a diner menu that follows the clock, not the other way round</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Sun, 16 Aug 2026 19:00:28 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/open-late-a-diner-menu-that-follows-the-clock-not-the-other-way-round-20ga</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/open-late-a-diner-menu-that-follows-the-clock-not-the-other-way-round-20ga</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;&lt;strong&gt;Open Late&lt;/strong&gt; is the landing page for an imaginary 24-hour comfort diner with four kitchens and one rule: &lt;em&gt;the menu follows the clock.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Open it at 3am and it opens on the Small Hours menu — khichdi, drunken&lt;br&gt;
noodles, grilled cheese. Open it at 8am and the same page is serving congee and soft eggs. The page reads your actual clock, picks the kitchen that's cooking, highlights the right row in the hours table, and tells you what it did. You can override it and look ahead at any other service.&lt;/p&gt;

&lt;p&gt;It's the companion piece to my five CSS art entries —&lt;br&gt;
&lt;a href="https://dev.to/maneesh_thakur_d16c2852fa/midnight-fridge-raid-comfort-food-isnt-a-dish-its-2am-1l04"&gt;Midnight Fridge Raid&lt;/a&gt;,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/maneesh_thakur_d16c2852fa/cutting-chai-the-comfort-food-that-isnt-food-drawn-in-css-1c27"&gt;Cutting Chai&lt;/a&gt;,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/maneesh_thakur_d16c2852fa/soft-boil-six-minutes-and-you-cannot-get-it-wrong-5dj6"&gt;Soft Boil&lt;/a&gt;,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/maneesh_thakur_d16c2852fa/slow-turn-nine-birds-one-fire-and-twenty-minutes-of-waiting-1k17"&gt;Slow Turn&lt;/a&gt;&lt;br&gt;
and &lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/maneesh_thakur_d16c2852fa/the-dabba-the-box-somebody-packed-for-you-before-you-were-awake-19l7"&gt;The Dabba&lt;/a&gt;&lt;br&gt;
— same idea, that comfort food is defined by the hour you need it, not by the cuisine.&lt;/p&gt;

&lt;p&gt;No framework. No build step. No image files — every plate on the page is&lt;br&gt;
two radial gradients and a shadow.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The Open Late landing page at 12:13am: the header pill reads "Open now · 12:13 AM", the hero says it is serving the Small Hours menu, six dishes are shown including 3am Khichdi and Midnight Maggi, the hours table marks Small Hours as on now, and a booking form sits at the foot 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%2F48ue6k0u32chcp3t7usz.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%2F48ue6k0u32chcp3t7usz.png" alt=" " width="800" height="1552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://maneesh-kumar-thakur.github.io/devto-frontend-challenge-food-edition/landing/" rel="noopener noreferrer"&gt;Live demo →&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
·&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/maneesh-kumar-thakur/devto-frontend-challenge-food-edition" rel="noopener noreferrer"&gt;Source →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The idea: make the clock the interface
&lt;/h3&gt;

&lt;p&gt;Restaurant landing pages are a solved genre, which makes them a bad place to compete on layout. So I looked for a &lt;em&gt;behaviour&lt;/em&gt; instead of a look. "The menu changes with the hour" turned out to be the right hook, because it isn't decoration — it removes a decision. You never scroll past breakfast to find what's actually available.&lt;/p&gt;

&lt;p&gt;That one idea then paid for the whole page: it gives the hero something true to say, it gives the hours table a live state, and it gives the booking confirmation something useful to tell you (&lt;em&gt;"that lands on the Evening menu"&lt;/em&gt;).&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;SERVICES&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;small&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;the Small Hours menu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sunrise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;the Sunrise menu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;afternoon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;the Afternoon menu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;evening&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;the Evening menu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The clock re-checks every 30 seconds and rolls the menu over — but only for someone who hasn't taken manual control of the picker. Yanking the page out from under a person who deliberately chose "Evening" would be rude.&lt;/p&gt;

&lt;h3&gt;
  
  
  Twenty-four dishes: a vessel and a glyph
&lt;/h3&gt;

&lt;p&gt;My first version was honestly poor: one bowl recipe rendered in two dozen&lt;br&gt;
colours. It looked tidy and told you nothing. Colour alone can't say &lt;em&gt;soup&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The fix came in two layers. First, a &lt;strong&gt;vessel&lt;/strong&gt;, drawn in CSS — how the thing arrives at your table:&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;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dish"&lt;/span&gt; &lt;span class="na"&gt;data-form=&lt;/span&gt;&lt;span class="s"&gt;"mug"&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-form&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"plate"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;.dish__art&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c"&gt;/* mound on a flat oval  */&lt;/span&gt;
&lt;span class="nc"&gt;.dish&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-form&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"tray"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;  &lt;span class="nc"&gt;.dish__art&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c"&gt;/* browned, forked top   */&lt;/span&gt;
&lt;span class="nc"&gt;.dish&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-form&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"mug"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;   &lt;span class="nc"&gt;.dish__art&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c"&gt;/* + ::after is a handle */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a &lt;strong&gt;glyph&lt;/strong&gt; on top of it — what the thing actually is. Fourteen of them, hand-drawn as one inline &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt; sprite:&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;svg&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dish__icon"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;use&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#i-noodles"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I deliberately didn't reach for an icon library. A CDN link would break the page's zero-dependency claim and die in any offline or sandboxed context, and none of the good free sets (Lucide, Tabler, Phosphor) has a khichdi, an idli or a paratha in it. Fourteen hand-drawn glyphs is a couple of hours and it's &lt;em&gt;mine&lt;/em&gt;, with no licence to honour and no request to make.&lt;/p&gt;

&lt;p&gt;Two things I got wrong first time, both worth stealing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The glyphs originally drew their own bowls.&lt;/strong&gt; A bowl glyph sitting inside a CSS bowl reads as doubled and slightly broken. Every glyph is now the &lt;em&gt;food only&lt;/em&gt; — a nest of noodles, a mound of rice, a fried egg — and the vessel underneath supplies the container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One of my "vessels" wasn't a vessel.&lt;/strong&gt; I had &lt;code&gt;stack&lt;/code&gt; alongside bowl, plate, tray and mug — but a stack is a shape food &lt;em&gt;takes&lt;/em&gt;, not a thing you serve it in. Once the glyph did the stacking, a stack vessel under a stack glyph was the same doubling again. Those dishes are plates now.&lt;/p&gt;

&lt;p&gt;The lesson generalises past food: when you're differentiating a list of things visually, &lt;strong&gt;spend your effort on silhouette before colour.&lt;/strong&gt; Shape survives being small, greyscale, and glanced at.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;hidden&lt;/code&gt; bug that broke my own progressive enhancement
&lt;/h3&gt;

&lt;p&gt;Worth its own section, because I'd written a whole paragraph in this post&lt;br&gt;
claiming the filter panel is invisible without JavaScript — and it wasn't.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hidden&lt;/code&gt; is just &lt;code&gt;display: none&lt;/code&gt; in the UA stylesheet, which means &lt;strong&gt;any&lt;/strong&gt;&lt;br&gt;
author rule that sets &lt;code&gt;display&lt;/code&gt; silently beats it. I had two:&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;.btn&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c"&gt;/* un-hid "Clear filters"     */&lt;/span&gt;
&lt;span class="nc"&gt;.filters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;          &lt;span class="c"&gt;/* un-hid the whole panel!    */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So with scripting off, the panel appeared anyway, full of dead controls — the exact failure I'd designed it to avoid. One line fixes it, and it's the only place on the page I'd defend an &lt;code&gt;!important&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="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;hidden&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I only caught it because I rendered the page with the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag deleted and looked. Claiming progressive enhancement is easy; &lt;strong&gt;testing&lt;/strong&gt; it means actually removing the script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility, and why each choice was made
&lt;/h3&gt;

&lt;p&gt;I tried to make every a11y decision a &lt;em&gt;design&lt;/em&gt; decision rather than a&lt;br&gt;
retrofit:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The filters don't exist until they work.&lt;/strong&gt; They ship with &lt;code&gt;hidden&lt;/code&gt; and JS removes it. With scripting off you get all twenty-four dishes, each badged with the kitchen that cooks it — a perfectly good menu — instead of dead controls that lie about what they do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native controls, styled as chips.&lt;/strong&gt; The kitchen picker is &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; +&lt;br&gt;
&lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; + real radios; the dietary filters are real checkboxes, because&lt;br&gt;
picking zero or more things is exactly what a checkbox group is. Arrow-key&lt;br&gt;
navigation, group semantics and checked state all come free. The inputs are visually invisible but still focusable, and the chip is styled through&lt;br&gt;
&lt;code&gt;:has(input:checked)&lt;/code&gt;. No &lt;code&gt;div&lt;/code&gt; can be talked into any of that.&lt;/p&gt;

&lt;p&gt;It also means the checkbox &lt;em&gt;is&lt;/em&gt; the state — nothing is mirrored into a JS&lt;br&gt;
variable that can drift out of sync with what a screen reader announces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A live region that says something useful.&lt;/strong&gt; Filtering is pointless if you can't tell what's left, so the count sits in an &lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; — the native "result of a calculation" element, which carries an implicit status role and needs no ARIA at all. It announces &lt;em&gt;"Showing 4 dishes on Small Hours menu."&lt;/em&gt; after every change, debounced by 250ms so typing doesn't machine-gun it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And one live region I deliberately removed.&lt;/strong&gt; The header clock started life as &lt;code&gt;role="status"&lt;/code&gt;. It updates every 30 seconds — so it would have read the time aloud twice a minute, forever. A kitchen changeover is worth announcing once, under the menu, where it changes what you can order. The clock is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; instead of a JS modal&lt;/strong&gt; for the ingredient lists. Native, keyboard operable, works with scripting off, and screen readers already know how to announce it. I saved &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; for the one genuine modal moment — the booking confirmation — where &lt;code&gt;showModal()&lt;/code&gt; gives me the focus trap, Esc-to-close and focus restoration for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;novalidate&lt;/code&gt;, deliberately.&lt;/strong&gt; Native validation bubbles vanish on blur, can't be styled, and aren't announced consistently. So the form owns its messaging: &lt;code&gt;aria-invalid&lt;/code&gt; on the field, the message wired up through &lt;code&gt;aria-describedby&lt;/code&gt;, and a &lt;code&gt;role="alert"&lt;/code&gt; summary that takes focus on a failed submit. Fields only &lt;em&gt;re&lt;/em&gt;-validate once they've already been marked wrong — nobody wants an error the first time they tab out of an empty box.&lt;/p&gt;

&lt;p&gt;Plus the usual, done properly: a skip link, landmarks with &lt;code&gt;aria-labelledby&lt;/code&gt;, correct heading order, one focus style everywhere that clears 3:1 in both themes, 44px touch targets, &lt;code&gt;&amp;lt;time datetime&amp;gt;&lt;/code&gt;, a &lt;code&gt;forced-colors&lt;/code&gt; fallback so selection survives High Contrast mode, and a full &lt;code&gt;prefers-reduced-motion&lt;/code&gt; block.&lt;/p&gt;

&lt;h3&gt;
  
  
  Night by default
&lt;/h3&gt;

&lt;p&gt;It's a nocturnal diner, so it's dark by default and light is the "day shift". A stored choice wins; otherwise it follows the OS. Every colour resolves through one &lt;code&gt;data-theme&lt;/code&gt; attribute on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, so nothing needs a second definition per component.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;localStorage&lt;/code&gt; access is wrapped — it throws outright in some privacy modes, and a theme toggle is not worth a broken page.&lt;/p&gt;

&lt;h3&gt;
  
  
  One bug worth mentioning
&lt;/h3&gt;

&lt;p&gt;The neon sign's glow is deliberately larger than the sign itself&lt;br&gt;
(&lt;code&gt;inset: -18%&lt;/code&gt;), which quietly added 60px of horizontal scroll on narrow&lt;br&gt;
screens. Absolutely-positioned children still contribute to document width. &lt;code&gt;overflow: hidden&lt;/code&gt; on the hero section fixed it — but I only found it by measuring &lt;code&gt;scrollWidth&lt;/code&gt; against &lt;code&gt;clientWidth&lt;/code&gt; at four widths, not by looking. Worth adding to your own checklist.&lt;/p&gt;




&lt;p&gt;Thanks for reading. The whole thing is three files and no dependencies, so&lt;br&gt;
it's easy to pull apart if any of it is useful to you.&lt;/p&gt;

&lt;p&gt;Code is MIT licensed and on &lt;a href="https://github.com/maneesh-kumar-thakur/devto-frontend-challenge-food-edition" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The Dabba — the box somebody packed for you before you were awake</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Sun, 16 Aug 2026 12:21:52 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/the-dabba-the-box-somebody-packed-for-you-before-you-were-awake-19l7</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/the-dabba-the-box-somebody-packed-for-you-before-you-were-awake-19l7</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, CSS Art&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;The brief's quiet line is &lt;strong&gt;"or the dish you grew up eating."&lt;/strong&gt; My other four entries all answered it with a dish. This one answers it with the &lt;strong&gt;box&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A dabba isn't food. It's four steel tins in a wire frame, and the reason it belongs in a comfort food challenge is that nobody ever packed one for&lt;br&gt;
themselves at seven in the morning. Somebody else did. That's the whole&lt;br&gt;
subject — the dal, the rice, the chapatis and the sabzi are just the evidence.&lt;/p&gt;

&lt;p&gt;There was a technical reason too. Four pieces in, I'd drawn clay, glass, water, fire and meat, but never &lt;strong&gt;metal&lt;/strong&gt; — and metal is the one material where the usual approach fails outright. It turned out to be the most interesting thing in the set.&lt;/p&gt;

&lt;p&gt;Everything here is a &lt;code&gt;div&lt;/code&gt;, a gradient or a shadow. No SVG, no images, no&lt;br&gt;
canvas.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/editor/Maneesh-Thakur/embed/01a00a77-b19c-7344-8066-6b8832108a59?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Press &lt;strong&gt;Pack it up&lt;/strong&gt;. The three loose boxes travel back into the frame in&lt;br&gt;
order, the sabzi box shuts where it stands, the lid seats on top and the wire clamp slides down the posts to hold it all together.&lt;/p&gt;
&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Steel is a mirror, not a grey gradient
&lt;/h3&gt;

&lt;p&gt;This is the thing I'd keep from the whole build.&lt;/p&gt;

&lt;p&gt;My first tins were a light-to-dark ramp and they looked like &lt;strong&gt;painted&lt;br&gt;
plastic&lt;/strong&gt;. The mistake is thinking of metal as a colour. A polished cylinder is a mirror bent into a curve — what you are drawing isn't the object, it's &lt;em&gt;the room reflected in it&lt;/em&gt;, squeezed into vertical bands. Bright where it catches the window, dark where it catches the shadow under the table, bright again at the edge where the curve turns away.&lt;/p&gt;

&lt;p&gt;So the band has fourteen stops and no even spacing anywhere:&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="nt"&gt;--steel-band&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;90&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="err"&gt;#3&lt;/span&gt;&lt;span class="nt"&gt;d454b&lt;/span&gt;  &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;  &lt;span class="err"&gt;#78838&lt;/span&gt;&lt;span class="nt"&gt;c&lt;/span&gt;  &lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;  &lt;span class="nf"&gt;#c2cbd3&lt;/span&gt; &lt;span class="err"&gt;13&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;  &lt;span class="nf"&gt;#eff4f9&lt;/span&gt; &lt;span class="err"&gt;19&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="nf"&gt;#a9b4bc&lt;/span&gt; &lt;span class="err"&gt;27&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;  &lt;span class="err"&gt;#6&lt;/span&gt;&lt;span class="nt"&gt;d777f&lt;/span&gt; &lt;span class="err"&gt;37&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;  &lt;span class="err"&gt;#586169&lt;/span&gt; &lt;span class="err"&gt;45&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;  &lt;span class="err"&gt;#919&lt;/span&gt;&lt;span class="nt"&gt;ba4&lt;/span&gt; &lt;span class="err"&gt;55&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="nf"&gt;#dce3e9&lt;/span&gt; &lt;span class="err"&gt;63&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;  &lt;span class="nf"&gt;#f8fbfe&lt;/span&gt; &lt;span class="err"&gt;69&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;  &lt;span class="nf"&gt;#b3bdc5&lt;/span&gt; &lt;span class="err"&gt;77&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;  &lt;span class="err"&gt;#778189&lt;/span&gt; &lt;span class="err"&gt;85&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="err"&gt;#474&lt;/span&gt;&lt;span class="nt"&gt;f56&lt;/span&gt; &lt;span class="err"&gt;93&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;  &lt;span class="err"&gt;#6&lt;/span&gt;&lt;span class="nt"&gt;a737b&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The unevenness is the point. A real reflection has no rhythm, and the moment your stops fall at tidy intervals the eye reads &lt;em&gt;gradient&lt;/em&gt; instead of &lt;em&gt;metal&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The second half is &lt;strong&gt;anisotropy&lt;/strong&gt;. Brushed steel scatters light along the&lt;br&gt;
grain, so the highlight smears in one direction instead of pooling in a spot. On a spun tin the grain runs &lt;em&gt;around&lt;/em&gt; the cylinder — so the grain layer is horizontal while the reflection bands are vertical, and those two crossing at right angles are what separates steel from chrome.&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="nt"&gt;--steel-grain&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;repeating-linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;06&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;07&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;055&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;      &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;07&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;15&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;transparent&lt;/span&gt;            &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;15&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;33&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One more thing, which cost nothing and fixed a lot: three cylinders showing an &lt;strong&gt;identical&lt;/strong&gt; reflection reads as computer-generated, because real objects stand in different places and mirror different parts of the room. Oversizing the band to 118% and sliding each box to its own offset gives every tin its own reflection out of one shared gradient.&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;.box__body&lt;/span&gt;            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;118%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.box--dal&lt;/span&gt;   &lt;span class="nc"&gt;.box__body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;6%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.box--rice&lt;/span&gt;  &lt;span class="nc"&gt;.box__body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;76%&lt;/span&gt; &lt;span class="m"&gt;50%&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;
  
  
  The mouth is the camera
&lt;/h3&gt;

&lt;p&gt;The four foods are the only colour in the picture, and for a long stretch two of them were unreadable.&lt;/p&gt;

&lt;p&gt;The wells were &lt;strong&gt;26 × 6.4cqw — a 4:1 ellipse.&lt;/strong&gt; Dal and rice were fine, because a uniform texture survives being squashed flat. But the chapatis and a chunky sabzi turned to mush, and no amount of redrawing them helped.&lt;/p&gt;

&lt;p&gt;The wells were the bug. &lt;strong&gt;Opening the mouth is the same as raising the&lt;br&gt;
camera&lt;/strong&gt; — food has to be looked &lt;em&gt;down into&lt;/em&gt;, and at 4:1 there was simply no vertical room for anything with a shape in it. Going to 8cqw on an 11cqw box means the mouth is nearly as tall as the tin, which sounds wrong and looks right.&lt;/p&gt;
&lt;h3&gt;
  
  
  A sector is an ambiguous shape
&lt;/h3&gt;

&lt;p&gt;The chapatis went through two complete rewrites and the lesson is about&lt;br&gt;
&lt;em&gt;legibility&lt;/em&gt;, not technique.&lt;/p&gt;

&lt;p&gt;Version one was four chapatis folded into quarters. A quarter-fold is a&lt;br&gt;
&lt;strong&gt;sector&lt;/strong&gt;, and a sector is genuinely elegant in CSS — one square with a single corner rounded to 100%, no &lt;code&gt;clip-path&lt;/code&gt; anywhere:&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="nt"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;4&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;4&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;border-radius&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also didn't work. At this viewing angle a sector is an ambiguous shape — it could be bread, or a wedge of anything — and four of them in the same pale wheat merged into one smear. I outlined them, fanned them, enlarged them. Still a smear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A stack is unambiguous.&lt;/strong&gt; Round flatbread piled flat is the most recognisable form bread takes. So it's four discs, each nudged up 0.6cqw from the one below so the pile shows its thickness as layer edges along the near rim — and those edges do all the work of saying &lt;em&gt;there are several of these&lt;/em&gt;, which four separate shapes had been failing to say.&lt;/p&gt;

&lt;p&gt;The top one is folded in half and laid across, which is both the fold I wanted and the thing that stops the stack reading as one big pancake. A half-fold seen from above is a &lt;strong&gt;dome&lt;/strong&gt; — flat edge towards you, curve away — which is a single &lt;code&gt;border-radius&lt;/code&gt; and reads as folded instantly.&lt;/p&gt;

&lt;p&gt;Worth saying plainly: the elegant version lost to the obvious one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transforms apply right to left, and it matters
&lt;/h3&gt;

&lt;p&gt;While the sectors were still in, they had to be rotated &lt;em&gt;and&lt;/em&gt; foreshortened. This is correct:&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="nt"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;scaleY&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;46&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;rotate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-20deg&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Transforms apply &lt;strong&gt;right to left&lt;/strong&gt;, so that rotates the fold in true square space and flattens it afterwards. Written the other way round the shape is squashed first and the rotation then &lt;em&gt;shears&lt;/em&gt; it — the folds looked bent rather than laid down, and I spent a while adjusting numbers before reading the line properly.&lt;/p&gt;

&lt;p&gt;The same principle survives in the lid. When it lifts onto the stack it travels 46cqw upward, which is much closer to eye level, so the same circle must be far more foreshortened up there:&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;.scene.is-packed&lt;/span&gt; &lt;span class="nc"&gt;.lid&lt;/span&gt; &lt;span class="p"&gt;{&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;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-36.5&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;-47.6&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;scaleY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.62&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;That &lt;code&gt;scaleY&lt;/code&gt; is the whole illusion.&lt;/strong&gt; Without it the lid is a disc sliding across the picture, and pressing the button looks like nothing happening — which is exactly how the first version felt.&lt;/p&gt;

&lt;h3&gt;
  
  
  The arc is two elements
&lt;/h3&gt;

&lt;p&gt;Everything moved on one transform at first, and the packing felt like a&lt;br&gt;
machine rather than like hands. &lt;br&gt;
Two changes fixed it. The first: &lt;strong&gt;horizontal travel lives on &lt;code&gt;.box&lt;/code&gt; and&lt;br&gt;
vertical on an inner &lt;code&gt;.box__lift&lt;/code&gt;&lt;/strong&gt;, running different curves — X on a plain ease, Y with a slight overshoot so the box settles instead of arriving. Nothing physical moves diagonally in a straight line at constant proportion.&lt;/p&gt;

&lt;p&gt;The second is my favourite detail in the piece. The contact shadow is on&lt;br&gt;
&lt;code&gt;.box&lt;/code&gt;, so it inherits the horizontal travel but &lt;strong&gt;not&lt;/strong&gt; the vertical — the shadow stays down on the table while the box rises off it. One line, and it's the cheapest possible way to make a lift read as a lift.&lt;/p&gt;
&lt;h3&gt;
  
  
  Sequence, not a chord
&lt;/h3&gt;

&lt;p&gt;You cannot put the lid on before the boxes are in, so now it doesn't: dal,&lt;br&gt;
rice, chapatis, mouths go dark, lid, clamp. About 2.2 seconds.&lt;br&gt;
The part that isn't obvious is that opening has to reverse the &lt;em&gt;order&lt;/em&gt;, not just the direction — clamp, lid, chapatis, rice, dal, food, steam. So the delays are declared on &lt;strong&gt;both&lt;/strong&gt; states rather than shared:&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;.box&lt;/span&gt;           &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;0.8s&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;--travel-x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0.9s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.box--chapati&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transition-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.scene.is-packed&lt;/span&gt; &lt;span class="nc"&gt;.box--dal&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transition-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.scene.is-packed&lt;/span&gt; &lt;span class="nc"&gt;.box--chapati&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transition-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.45s&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;Reversing a sequence is not the same as playing it backwards, and CSS will not work that out for you.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The tiled-texture trick has a scale limit
&lt;/h3&gt;

&lt;p&gt;In an earlier piece I made blistered skin out of two tiled dot-grids at&lt;br&gt;
deliberately mismatched sizes — because the tiles never align, the pattern they beat out reads as random. It's cheap and it's convincing.&lt;br&gt;
I reused it for rice and it failed loudly: at this size the dots are big enough to see individually, so the eye locks onto the &lt;strong&gt;lattice&lt;/strong&gt; and reads &lt;em&gt;mesh&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The fix was to stop asking it to be the rice. There's a third grid now so no two ever line up, the whole thing is turned down to 40% and rotated 9° off-axis so there's no shared horizontal to latch onto — and fourteen grains are drawn individually on top. The grids are texture &lt;em&gt;under&lt;/em&gt; the rice; the grains are the rice.&lt;/p&gt;

&lt;p&gt;A trick that works at one scale is not a technique. It's a trick.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composition is occlusion
&lt;/h3&gt;

&lt;p&gt;My first arrangement had the carrier and the boxes in a row with clear air&lt;br&gt;
between them. That's an &lt;strong&gt;exploded diagram&lt;/strong&gt;, not a photograph.&lt;br&gt;
Real still lifes overlap, and occlusion is most of what builds depth. The boxes now lap over the frame and each other, each one lower in frame than the last, because lower reads as nearer. The carrier is dead centre so the packed stack fills the frame rather than sitting off to one side with half the picture empty — which is what it did until I looked at a screenshot properly.&lt;/p&gt;

&lt;h3&gt;
  
  
  One container query, zero breakpoints
&lt;/h3&gt;

&lt;p&gt;Every dimension is in &lt;code&gt;cqw&lt;/code&gt; off a single &lt;code&gt;container-type: inline-size&lt;/code&gt;, and the frame is capped by viewport height so the page never grows a scrollbar. Same as the other four.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;role="img"&lt;/code&gt; with a description, everything inside &lt;code&gt;aria-hidden&lt;/code&gt;, a real&lt;br&gt;
&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; with &lt;code&gt;aria-pressed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The part that isn't decoration: &lt;strong&gt;the &lt;code&gt;aria-label&lt;/code&gt; swaps with the state.&lt;/strong&gt; An open dabba with four foods in it becomes a closed steel tin, and a screen reader being told it's still looking at dal and rice would simply be wrong.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt; keeps both states reachable — they arrive instead of travelling. Less movement, not less picture.&lt;/p&gt;




&lt;p&gt;Code is MIT licensed and on &lt;a href="https://github.com/maneesh-kumar-thakur/devto-frontend-challenge-food-edition" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Slow Turn — nine birds, one fire, and twenty minutes of waiting</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Sun, 16 Aug 2026 10:11:00 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/slow-turn-nine-birds-one-fire-and-twenty-minutes-of-waiting-1k17</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/slow-turn-nine-birds-one-fire-and-twenty-minutes-of-waiting-1k17</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, CSS Art&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;The other three pieces in this set are quiet. A fridge at 2am, a cup of chai, a pan of eggs. This one is the opposite: a whole tandoor of birds turning over a fire, which is the most theatrical way anyone has ever cooked anything.&lt;/p&gt;

&lt;p&gt;The comfort here isn't the eating. It's the &lt;strong&gt;waiting&lt;/strong&gt; — standing in front of the glass while nine chickens go round, knowing yours isn't ready, watching it anyway. Every city has a window like this and everybody has stood at one.&lt;/p&gt;

&lt;p&gt;Skinless and deep red, the way tandoori actually is, because the marinade&lt;br&gt;
becomes the colour when you take the skin off — and because a cabinet full of red against a cold grey kitchen means the fire is the only warm thing in the frame. Same lesson the fridge piece ended on, used on purpose.&lt;/p&gt;

&lt;p&gt;Everything here is a &lt;code&gt;div&lt;/code&gt;, a gradient or a shadow. No SVG, no images, no&lt;br&gt;
canvas.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/editor/Maneesh-Thakur/embed/01a009fb-7131-7600-be1e-1187a1408104?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Press &lt;strong&gt;Take one out&lt;/strong&gt;. A bird leaves the rod, the cabinet falls out of focus, and a plated one rises into the foreground — onion rings, mint chutney in a ramekin, mayo, lemon, coriander, still steaming. The gap it left on the rod stays a gap.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The rotation is not a rotation
&lt;/h3&gt;

&lt;p&gt;This is the whole piece, and it nearly sank it.&lt;br&gt;
The rods spin about their own long axis, and that axis runs &lt;strong&gt;left to right across the screen&lt;/strong&gt;. So the birds are turning towards and away from you. In CSS that is &lt;code&gt;rotateX&lt;/code&gt; — and &lt;code&gt;rotateX&lt;/code&gt; on a flat shape squashes it vertically and flips it, like turning a sheet of paper edge-on. It looks like nothing on earth, and it is the obvious first attempt.&lt;/p&gt;

&lt;p&gt;The way out is that a chicken on a spit is an ellipsoid about &lt;em&gt;that same axis&lt;/em&gt;, so &lt;strong&gt;its silhouette barely changes as it turns.&lt;/strong&gt; Watch a real rotisserie: you read the rotation almost entirely off the surface. Char climbs over the crest and vanishes; fresh char comes round from underneath.&lt;/p&gt;

&lt;p&gt;So the silhouette is held completely still and the char scrolls &lt;strong&gt;vertically&lt;/strong&gt; inside it:&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;.bird__char&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c"&gt;/* three tiles tall */&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;repeating-linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;177deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;/* char bands */&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;turn&lt;/span&gt; &lt;span class="m"&gt;16s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animation-delay&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;--phase&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;turn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&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;33.3333%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three hundred percent tall, moving exactly one third, inside a parent with&lt;br&gt;
&lt;code&gt;overflow: hidden&lt;/code&gt; — so the loop is seamless and never shows a join. Sixteen seconds a turn.&lt;/p&gt;

&lt;p&gt;There is &lt;strong&gt;not one degree of rotation on any bird body.&lt;/strong&gt; The only actual&lt;br&gt;
rotate is a ±1.1° wobble, because a spitted bird is never perfectly balanced.&lt;/p&gt;

&lt;p&gt;One detail I'd defend: birds on the same rod share a &lt;code&gt;--phase&lt;/code&gt; and turn in&lt;br&gt;
lockstep, because it is one rod. The three rods are offset from each other. Getting that wrong makes a cabinet look like a screensaver.&lt;/p&gt;
&lt;h3&gt;
  
  
  A chicken is its drumsticks
&lt;/h3&gt;

&lt;p&gt;My first bird was one ovoid with a small leg attached. It read as a &lt;strong&gt;bread roll&lt;/strong&gt;, and no amount of work on the ovoid fixed it.&lt;/p&gt;

&lt;p&gt;Looking properly at reference photos, the thing your eye locks onto is not the torso at all. It is two drumsticks, angled apart, bone knuckles out, pointing back along the spit. The torso behind them can be close to a plain ellipse and nobody looks twice.&lt;/p&gt;

&lt;p&gt;So the bird became six overlapping parts sharing one gradient, drawn in&lt;br&gt;
anatomical order: far drumstick, thigh mass, torso, wing, near drumstick, neck.&lt;/p&gt;

&lt;p&gt;Two things do most of the work. The near leg is drawn &lt;strong&gt;in front&lt;/strong&gt; of the body and the far leg &lt;strong&gt;behind&lt;/strong&gt; it, which is the entire three-dimensional read with no 3D anywhere. And each drumstick chars hardest at the &lt;strong&gt;ankle&lt;/strong&gt;, because it is thinnest and closest to the flame — without that dark note a drumstick is just a sausage.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;filter&lt;/code&gt; inherits, and it cost me two attempts
&lt;/h3&gt;

&lt;p&gt;The flames stayed a warm smudge through two rounds of "fixing" them, and the reason is worth the whole article.&lt;/p&gt;

&lt;p&gt;A flame needs a soft halo &lt;em&gt;and&lt;/em&gt; a hard bright core; the core is what makes it read as fire rather than as glow. So I built exactly that — a blurred parent with a barely-blurred core inside it:&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;.flame&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c"&gt;/* halo   */&lt;/span&gt;
&lt;span class="nc"&gt;.flame&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.13&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c"&gt;/* "core" */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That cannot work. &lt;strong&gt;A filter on a parent rasterises the parent and every&lt;br&gt;
descendant&lt;/strong&gt;, so the core was being blurred &lt;code&gt;0.3 + 0.13&lt;/code&gt;. There was never a hard edge anywhere in the flame to see. Adding a sharper core to a blurred parent achieves nothing at all — a fact I confirmed twice before reading my own code properly.&lt;/p&gt;

&lt;p&gt;The fix is that the parent carries &lt;strong&gt;no filter&lt;/strong&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;.flame&lt;/span&gt;         &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* no filter, no blend mode */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.flame&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c"&gt;/* halo, paints first */&lt;/span&gt;
&lt;span class="nc"&gt;.flame&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.08&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c"&gt;/* core, paints on top */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Blend modes need somewhere to go
&lt;/h3&gt;

&lt;p&gt;Same flames, second bug, and it is one I had already written up in the fridge piece and then walked straight back into.&lt;/p&gt;

&lt;p&gt;The flames used &lt;code&gt;mix-blend-mode: screen&lt;/code&gt;. Screen &lt;strong&gt;adds&lt;/strong&gt; light — and &lt;code&gt;.glow&lt;/code&gt; had already flooded the bottom of the cabinet with orange, so the flames were adding brightness to a region with no headroom left. They washed out precisely where they should have been sharpest.&lt;/p&gt;

&lt;p&gt;They now use no blend mode at all, and the ambient glow came down from &lt;code&gt;0.62&lt;/code&gt; to &lt;code&gt;0.44&lt;/code&gt;. &lt;strong&gt;The fire has to be brighter than the light it casts&lt;/strong&gt;, which sounds obvious written down and was not obvious at all in the file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blistering out of three grids that do not line up
&lt;/h3&gt;

&lt;p&gt;Tandoori skin is covered in small raised blisters, and a perfectly even&lt;br&gt;
gradient is the single biggest tell that something was drawn rather than&lt;br&gt;
cooked.&lt;/p&gt;

&lt;p&gt;Fifty hand-placed gradients would do it. This is cheaper:&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="nt"&gt;background-image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nt"&gt;radial-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;circle&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;201&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;138&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;30&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;24&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt; &lt;span class="err"&gt;27&lt;/span&gt;&lt;span class="o"&gt;%),&lt;/span&gt;
  &lt;span class="nt"&gt;radial-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;circle&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;16&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;34&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;      &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;20&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt; &lt;span class="err"&gt;23&lt;/span&gt;&lt;span class="o"&gt;%),&lt;/span&gt;
  &lt;span class="nt"&gt;radial-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;circle&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;168&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;96&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;22&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;17&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt; &lt;span class="err"&gt;20&lt;/span&gt;&lt;span class="o"&gt;%);&lt;/span&gt;
&lt;span class="nt"&gt;background-size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;15&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;62&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;63&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;27&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;19&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;07&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three tiled dot-grids at deliberately mismatched sizes. Because the tiles never line up, the pattern they beat out reads as random — and at this scale it is indistinguishable from actually being random.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sometimes you move the camera instead
&lt;/h3&gt;

&lt;p&gt;The plate was always going to have to cover the fire, and the fire is the best thing in the picture. That looked like a dead end for a while.&lt;/p&gt;

&lt;p&gt;The answer was that the fire stops being the subject:&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;.scene.is-taken&lt;/span&gt; &lt;span class="nc"&gt;.cabinet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.45&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.58&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;saturate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;);&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;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.985&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;A rack focus. Once the machine is background, covering it is not a loss — it is the point. The bird also leaves the rod &lt;em&gt;downward and to the right&lt;/em&gt;, towards where the plate rises from, so the two read as one bird rather than two events.&lt;/p&gt;

&lt;p&gt;The plated bird is lit &lt;strong&gt;from above&lt;/strong&gt;, while every bird in the cabinet is lit &lt;strong&gt;from below&lt;/strong&gt; by the fire. That reversal is the strongest single signal that it is out of the machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bone that looked like a fingernail
&lt;/h3&gt;

&lt;p&gt;A small one, but it is the kind of mistake that is invisible until someone says it out loud.&lt;/p&gt;

&lt;p&gt;My drumstick bone was &lt;code&gt;1.7cqw&lt;/code&gt; wide by &lt;code&gt;0.82cqw&lt;/code&gt; tall with a left-to-right&lt;br&gt;
gradient. That is precisely the recipe for a &lt;strong&gt;fingernail&lt;/strong&gt;, because a nail &lt;em&gt;is&lt;/em&gt; a flat pale oval lying along the end of a finger.&lt;/p&gt;

&lt;p&gt;A drumstick bone ends in a knob. So: nearly round instead of long, and a&lt;br&gt;
&lt;strong&gt;radial&lt;/strong&gt; gradient with the highlight up and to the left, which gives it a top and a side instead of a flat face.&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="c"&gt;/* before — a nail */&lt;/span&gt;
&lt;span class="nt"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;7&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;82&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;92&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;#8&lt;/span&gt;&lt;span class="nt"&gt;e7357&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;#c6b393&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;#e8dac2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c"&gt;/* after — a knuckle */&lt;/span&gt;
&lt;span class="nt"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;6&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;45&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;radial-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;62&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;58&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;at&lt;/span&gt; &lt;span class="err"&gt;34&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;28&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="nf"&gt;#fdf7ea&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;#e5d7ba&lt;/span&gt; &lt;span class="err"&gt;44&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="nf"&gt;#b39d79&lt;/span&gt; &lt;span class="err"&gt;78&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;#806&lt;/span&gt;&lt;span class="nt"&gt;d51&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  One container query, zero breakpoints
&lt;/h3&gt;

&lt;p&gt;Every dimension is in &lt;code&gt;cqw&lt;/code&gt; — 1% of the scene's own width — off a single&lt;br&gt;
&lt;code&gt;container-type: inline-size&lt;/code&gt;, and the frame is capped by viewport height so the page never grows a scrollbar. Same approach as the other three pieces.&lt;/p&gt;

&lt;h3&gt;
  
  
  The sprinkle of JavaScript
&lt;/h3&gt;

&lt;p&gt;About fifty lines, for two things CSS could not do: scatter the smoke so no two wisps ever line up twice, and take a bird off the rod.&lt;br&gt;
Both are optional. Every moving part has a hand-authored fallback in the&lt;br&gt;
stylesheet, so emptying the JS pane costs you the button and nothing else.&lt;/p&gt;

&lt;p&gt;The one part that is &lt;strong&gt;not&lt;/strong&gt; decoration is the label. The scene genuinely&lt;br&gt;
changes what it depicts, so &lt;code&gt;aria-label&lt;/code&gt; swaps with it — otherwise a screen reader would still be told it is looking at nine birds on a rod while a sighted viewer is looking at a plated dinner.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;role="img"&lt;/code&gt; with a description, everything inside &lt;code&gt;aria-hidden&lt;/code&gt;, a real&lt;br&gt;
&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; with &lt;code&gt;aria-pressed&lt;/code&gt; and a label that changes with state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt; freezes nine turning birds and seven flames at a pose that still reads as cooking rather than at an empty one, and the plate still arrives — it just does not fly in. Reduced motion means less movement, not less picture.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I'd tell anyone attempting one of these:&lt;/strong&gt; when something looks wrong and tuning the numbers does not help, the bug is usually structural, not aesthetic. My flames were not badly tuned. They were being blurred by a parent I had forgotten applies to its children. I adjusted colours twice before I read the cascade.&lt;/p&gt;




&lt;p&gt;Code is MIT licensed and on &lt;a href="https://github.com/maneesh-kumar-thakur/devto-frontend-challenge-food-edition" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Soft Boil — six minutes, and you cannot get it wrong</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Sun, 16 Aug 2026 06:58:33 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/soft-boil-six-minutes-and-you-cannot-get-it-wrong-5dj6</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/soft-boil-six-minutes-and-you-cannot-get-it-wrong-5dj6</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, CSS Art&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;My other two entries were about a moment and a ritual. This one is about the opposite: the dish you fall back on when you have no skill, no energy and no plan. Boiled eggs are what you make when you cannot cook. Six minutes, one pan,and the comfort is precisely that &lt;strong&gt;it is not possible to get it wrong&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Two choices made it worth drawing rather than just worth eating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A glass bowl, so you can see the boil.&lt;/strong&gt; In a steel pan the interesting half of this is hidden. Glass also turned out to be the exact opposite problem to the terracotta in my chai piece — unglazed clay is matte and forgives a sloppy gradient, glass shows you every single one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An induction hob, for the light.&lt;/strong&gt; I finished the fridge piece saying the one piece of advice I'd give is &lt;em&gt;pick a scene with a light source in it&lt;/em&gt;. So I did it again on purpose. The element ring is the only warm thing in an otherwise cold grey kitchen, and it lights the water from underneath.&lt;/p&gt;

&lt;p&gt;Everything here is a &lt;code&gt;div&lt;/code&gt;, a gradient or a shadow. No SVG, no images, no&lt;br&gt;
canvas.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/editor/Maneesh-Thakur/embed/01a00952-4692-7510-92f4-e94d150044e0?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Press &lt;strong&gt;Turn off the heat&lt;/strong&gt; and give it a few seconds. The ring dies back, the bubbles thin out, and the eggs slowly stop moving — then put it back on and watch the pan come to the boil in stages. That build-up is the part I'd most like you to see, and it's the whole subject of this post.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Nothing in this picture is transparent
&lt;/h3&gt;

&lt;p&gt;The obvious way to draw a glass bowl is &lt;code&gt;backdrop-filter&lt;/code&gt;. I'd advise against building a picture on it — support is uneven enough that the piece falls apart somewhere, and it's expensive.&lt;/p&gt;

&lt;p&gt;So the transparency is painted. The water is drawn first as its own element, and then a front wall of highlights sits over the top of it: two vertical speculars down the sides for the curve of the glass, a soft wash across the middle for the thickness of the pane, and a rolled lip at the top, which is the one place glass is genuinely opaque enough to draw as a solid.&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;.glass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;101deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;13%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.07&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;47%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;84%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.17&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;100%&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;Read &lt;code&gt;.glass&lt;/code&gt; as the pane you are looking &lt;strong&gt;through&lt;/strong&gt;. Everything that lives in the water is a sibling behind it, clipped by the water's own &lt;code&gt;border-radius&lt;/code&gt;, so no egg can drift out through a wall however hard it gets shoved.&lt;/p&gt;

&lt;p&gt;An egg is an ellipse with two &lt;code&gt;border-radius&lt;/code&gt; axes — fatter at the bottom than the top. Worth doing that rather than reaching for &lt;code&gt;clip-path&lt;/code&gt;, because a &lt;code&gt;border-radius&lt;/code&gt; shape stays correct under &lt;code&gt;rotate&lt;/code&gt; and an equivalent polygon does not.&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="nt"&gt;border-radius&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="err"&gt;62&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;62&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;38&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;38&lt;/span&gt;&lt;span class="o"&gt;%;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Two numbers run the entire picture
&lt;/h3&gt;

&lt;p&gt;This is the part I'd keep from the whole build.&lt;/p&gt;

&lt;p&gt;The scene has a switch, and my first version was symmetric: turn it off and everything faded out, turn it on and everything snapped back to a full rolling boil. Which is nonsense. &lt;strong&gt;A pan does not do that.&lt;/strong&gt; Heat has to arrive before the water can boil, and the water has to boil before it can move an egg.&lt;/p&gt;

&lt;p&gt;Getting that lag right meant the picture needed to be a &lt;em&gt;quantity&lt;/em&gt;, not a&lt;br&gt;
state. So it runs on two numbers:&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="k"&gt;@property&lt;/span&gt; &lt;span class="n"&gt;--heat&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'&amp;lt;number&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;inherits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;initial-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@property&lt;/span&gt; &lt;span class="n"&gt;--amp&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'&amp;lt;number&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;inherits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;initial-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--heat&lt;/code&gt; is how hot the element is. &lt;code&gt;--amp&lt;/code&gt; is how hard the water is throwing the eggs about. Both 0 to 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;@property&lt;/code&gt; is the whole trick.&lt;/strong&gt; An unregistered custom property is just a string, and a string cannot be transitioned. Registered as a &lt;code&gt;&amp;lt;number&amp;gt;&lt;/code&gt; it interpolates like any other animatable value — so a single transition drives the ring, the bubbles, the churn, the steam and the eggs together:&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;.scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--heat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--amp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--heat&lt;/span&gt; &lt;span class="m"&gt;4s&lt;/span&gt; &lt;span class="n"&gt;ease-in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;--amp&lt;/span&gt;  &lt;span class="m"&gt;7s&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;2.6s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--amp&lt;/code&gt; doesn't start for 2.6 seconds, so there's a real stretch where the ring is glowing and the eggs still haven't moved.&lt;/p&gt;

&lt;p&gt;Every element then just reads the number. The ring grows and warms in colour at the same time, because a cold element doesn't light up dim-orange — it lights up a small sullen red and spreads outward as it takes:&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="nt"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;translateX&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-50&lt;/span&gt;&lt;span class="o"&gt;%)&lt;/span&gt; &lt;span class="nt"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;22&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;78&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--heat&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;span class="nt"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;blur&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nt"&gt;brightness&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;34&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;66&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--heat&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
        &lt;span class="nt"&gt;saturate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;9&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;9&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--heat&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bubbles each carry their own threshold, spaced by index, so they arrive a few at a time instead of all at once:&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="nt"&gt;--vis&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;clamp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--heat&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--i&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;062&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;7&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the positions are scattered independently, the first few appear in random places — it reads as a pan starting to catch, not as a row filling up left to right. The surface churn holds off entirely until &lt;code&gt;--heat&lt;/code&gt; clears 0.55, because a surface only breaks at a genuine rolling boil.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cooling is not warming played backwards&lt;/strong&gt;, so the timings differ by&lt;br&gt;
direction. Going off, the boil drops away first and the element stays warm&lt;br&gt;
after it, the way a hob actually behaves. It's also slower overall — pans lose heat more reluctantly than they gain it.&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;.scene.is-off&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--heat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;--amp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--amp&lt;/span&gt;  &lt;span class="m"&gt;5s&lt;/span&gt;   &lt;span class="n"&gt;ease-out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;--heat&lt;/span&gt; &lt;span class="m"&gt;6.5s&lt;/span&gt; &lt;span class="n"&gt;ease-out&lt;/span&gt; &lt;span class="m"&gt;1s&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;
  
  
  An animation beats a declaration, silently
&lt;/h3&gt;

&lt;p&gt;I hit this twice in one build, the second time after I thought I understood it, so it earns its own heading.&lt;/p&gt;

&lt;p&gt;If an element has a running animation that touches &lt;code&gt;opacity&lt;/code&gt;, then any&lt;br&gt;
&lt;code&gt;opacity&lt;/code&gt; declaration in your rule is &lt;strong&gt;ignored&lt;/strong&gt;. Not overridden at some&lt;br&gt;
specific moment — ignored for the entire duration. Animations sit above normal declarations in the cascade.&lt;/p&gt;

&lt;p&gt;Which meant this did precisely nothing:&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;.hob__ring&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.92&lt;/span&gt; &lt;span class="err"&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;--heat&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;            &lt;span class="c"&gt;/* dead code */&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ringPulse&lt;/span&gt; &lt;span class="m"&gt;3.4s&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;/* ...because of this */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ring pulsed away at full brightness with the hob switched off, and no&lt;br&gt;
amount of staring at &lt;code&gt;--heat&lt;/code&gt; in DevTools explained it, because &lt;code&gt;--heat&lt;/code&gt; was correctly zero the whole time. The fix is to stop fighting the animation and&lt;br&gt;
multiply inside it:&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;.hob__ring&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--vis&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;--heat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.spill&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--vis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&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;--heat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&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;--heat&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;ringPulse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.82&lt;/span&gt; &lt;span class="err"&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;--vis&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;    &lt;span class="err"&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;--vis&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom properties substitute live, so the keyframes pick up the new value every frame. Keep the plain declaration too — reduced motion drops the animation, and then it becomes the only thing setting opacity at all.&lt;/p&gt;

&lt;p&gt;The same rule is why the bubbles gate their opacity through &lt;code&gt;--vis&lt;/code&gt; rather than setting it directly. Once you've been bitten, you start recognising it: &lt;em&gt;the value is obviously right and the pixels obviously disagree.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Going still without ever stopping
&lt;/h3&gt;

&lt;p&gt;Same lesson, applied to movement.&lt;/p&gt;

&lt;p&gt;The eggs need to come to rest when the boil dies. The obvious approach is to drop the animation and let a transition carry them home:&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;.scene.is-off&lt;/span&gt; &lt;span class="nc"&gt;.egg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c"&gt;/* snaps */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It jumps. The moment the animation goes, the computed transform reverts, and the transition often has nothing to interpolate from.&lt;/p&gt;

&lt;p&gt;So the eggs never stop. Every offset in the keyframes is multiplied by&lt;br&gt;
&lt;code&gt;--amp&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="err"&gt;25&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&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;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc&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;--dx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;  &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="err"&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;--amp&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                           &lt;span class="n"&gt;calc&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;--dy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt; &lt;span class="err"&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;--amp&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                 &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc&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;--r1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&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;--amp&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;At &lt;code&gt;--amp: 0&lt;/code&gt; every keyframe collapses to &lt;code&gt;translate(0,0) rotate(0deg)&lt;/code&gt; and the egg rests exactly where &lt;code&gt;left&lt;/code&gt;/&lt;code&gt;top&lt;/code&gt; put it. As &lt;code&gt;--amp&lt;/code&gt; ramps back up, the same loop simply opens out again. Nothing pauses, nothing restarts, and there's no discontinuity at either end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two details that are the difference between water and not-water
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Bubbles get bigger as they rise&lt;/strong&gt;, because pressure drops on the way up.&lt;br&gt;
Leaving that out was the single thing that made my first attempt look like&lt;br&gt;
falling snow played backwards.&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="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;   &lt;span class="p"&gt;{&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;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&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="n"&gt;calc&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;--h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.35&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;Buoyancy is slow.&lt;/strong&gt; My first easing on the eggs was springy and they read as ping-pong balls. Water doesn't throw things around, it lifts and turns them — so it's a long &lt;code&gt;ease-in-out&lt;/code&gt; with rotation that lags the translation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Percentages don't mean what you want them to mean
&lt;/h3&gt;

&lt;p&gt;Late on I cropped the frame, because there was a band of empty room across the top doing no work, and because the page had grown a vertical scrollbar.&lt;/p&gt;

&lt;p&gt;The crop immediately broke the scene, and the reason is worth knowing: I had positioned the room and counter with percentages. &lt;strong&gt;Percentages in &lt;code&gt;inset&lt;/code&gt; resolve against the element's height&lt;/strong&gt;, while every other measurement in the piece is &lt;code&gt;cqw&lt;/code&gt; — 1% of the scene's &lt;em&gt;width&lt;/em&gt;. Change the aspect ratio and the two systems silently slide out of register. Both are &lt;code&gt;cqw&lt;/code&gt; now.&lt;/p&gt;

&lt;p&gt;The scrollbar itself is worth a line, since it's a general problem with&lt;br&gt;
fixed-ratio art. Capping the width alone isn't enough; it has to be capped by the height available too:&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;.scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;37&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;dvh&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;15rem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1.35&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;At this ratio width is 1.35× height, so subtracting the caption, the button and the page padding gives the widest the scene can be and still fit on screen. Whichever limit bites first wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  One container query, zero breakpoints
&lt;/h3&gt;

&lt;p&gt;Every dimension is in &lt;code&gt;cqw&lt;/code&gt; off a single &lt;code&gt;container-type: inline-size&lt;/code&gt;. The whole picture scales to any viewport with no media queries and no magic numbers — the same approach as the other two pieces.&lt;/p&gt;

&lt;h3&gt;
  
  
  The sprinkle of JavaScript
&lt;/h3&gt;

&lt;p&gt;This is the piece where the JS got &lt;em&gt;smaller&lt;/em&gt;, and that's the point.&lt;/p&gt;

&lt;p&gt;Coming to the boil takes about nine seconds across four overlapping stages, and none of them are timed in JavaScript. The script toggles one class. Everything else is two custom properties transitioning in the stylesheet.&lt;/p&gt;

&lt;p&gt;Doing it with &lt;code&gt;setTimeout&lt;/code&gt; was my first instinct and it's a trap: every stage becomes a timer that an impatient click can fire twice, and the stages drift apart. There's nothing to double-fire here — toggling the class mid-ramp just reverses the transitions from wherever they'd reached.&lt;/p&gt;

&lt;p&gt;The rest of the JS scatters the bubbles and steam so the boil never repeats identically. All of it is optional: every moving part has a hand-authored fallback in the stylesheet, so emptying the JS pane costs you the switch and nothing else.&lt;/p&gt;

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

&lt;p&gt;CSS art is still an image, so it's marked as one — &lt;code&gt;role="img"&lt;/code&gt; with an&lt;br&gt;
&lt;code&gt;aria-label&lt;/code&gt; describing the scene, everything inside it &lt;code&gt;aria-hidden&lt;/code&gt;. The&lt;br&gt;
switch is a real &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; with &lt;code&gt;aria-pressed&lt;/code&gt; and a label that changes with the state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt; freezes the boil at a pose that still reads as&lt;br&gt;
boiling, rather than at an empty one, and the switch still works — it just&lt;br&gt;
arrives instead of ramping. Someone who asked for less motion has not asked for less picture.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I'd tell anyone attempting one of these:&lt;/strong&gt; if your scene has a state, make it a &lt;em&gt;number&lt;/em&gt; rather than a boolean. Registering one custom property with &lt;code&gt;@property&lt;/code&gt; bought me a nine-second, four-stage, physically-plausible boil out of two lines of transition — and left the JavaScript with nothing to do but toggle a class.&lt;/p&gt;




&lt;p&gt;Code is MIT licensed and on &lt;a href="https://github.com/maneesh-kumar-thakur/devto-frontend-challenge-food-edition" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Cutting Chai — the comfort food that isn't food, drawn in CSS</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Sat, 15 Aug 2026 19:33:12 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/cutting-chai-the-comfort-food-that-isnt-food-drawn-in-css-1c27</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/cutting-chai-the-comfort-food-that-isnt-food-drawn-in-css-1c27</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, CSS Art&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;A kulhad of masala chai, steaming, on a wooden table. Whole spices scattered round it — star anise, a cinnamon stick, cardamom pods, cloves — and a biscuit leaning against the cup with one bite already gone.&lt;/p&gt;

&lt;p&gt;The brief says &lt;em&gt;"the dish you grew up eating"&lt;/em&gt;, and for a very large number of people the honest answer isn't a dish at all. It's four o'clock, every day, for as long as anyone can remember. Chai isn't a meal, isn't a course, and isn't optional.&lt;/p&gt;

&lt;p&gt;The cup matters as much as what's in it. A &lt;strong&gt;kulhad&lt;/strong&gt; is unfired terracotta, thrown by hand, used once and thrown back into the earth — which means it's never quite symmetrical, always a little rough, and it makes the tea taste of wet clay. That roughness is the entire reason I picked this subject: matte clay is &lt;em&gt;far&lt;/em&gt; kinder to layered CSS gradients than glass or ceramic will ever be.&lt;/p&gt;

&lt;p&gt;Everything here is a &lt;code&gt;div&lt;/code&gt;, a gradient, a &lt;code&gt;clip-path&lt;/code&gt; or a shadow. No SVG, no images, no canvas.&lt;/p&gt;

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

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/editor/Maneesh-Thakur/embed/019ffcd3-4318-7442-82ff-1320fe69a6df?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Getting the silhouette right matters more than the shading
&lt;/h3&gt;

&lt;p&gt;My first attempt was technically fine and completely wrong — it read as a&lt;br&gt;
plant pot. I'd made it tall, and tapered the base to about a quarter of the rim width.&lt;/p&gt;

&lt;p&gt;A real kulhad is &lt;strong&gt;squat&lt;/strong&gt;, and its base is a bit over half the mouth. Once I fixed the ratio, everything else in the picture suddenly worked. The shading hadn't changed at all.&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;.cup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;clip-path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;polygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="m"&gt;1%&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;99%&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;97.5%&lt;/span&gt; &lt;span class="m"&gt;13%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;95.5%&lt;/span&gt; &lt;span class="m"&gt;26%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;93%&lt;/span&gt; &lt;span class="m"&gt;40%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;90%&lt;/span&gt; &lt;span class="m"&gt;54%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;87%&lt;/span&gt; &lt;span class="m"&gt;67%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;84%&lt;/span&gt; &lt;span class="m"&gt;79%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;81%&lt;/span&gt; &lt;span class="m"&gt;90%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;78%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c"&gt;/* base ends at 78% / 22% */&lt;/span&gt;
    &lt;span class="m"&gt;22%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;19%&lt;/span&gt; &lt;span class="m"&gt;90%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;16%&lt;/span&gt; &lt;span class="m"&gt;79%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;13%&lt;/span&gt; &lt;span class="m"&gt;67%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;10%&lt;/span&gt; &lt;span class="m"&gt;54%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;7%&lt;/span&gt; &lt;span class="m"&gt;40%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4.5%&lt;/span&gt; &lt;span class="m"&gt;26%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2.5%&lt;/span&gt; &lt;span class="m"&gt;13%&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;Eighteen points instead of four, because a hand-thrown cup shouldn't have a single straight edge anywhere.&lt;/p&gt;

&lt;p&gt;The clay itself is five stacked gradient layers — throwing rings from the&lt;br&gt;
potter's fingers, a rim light down the left, the shadow side, a specular&lt;br&gt;
bloom, then the body colour:&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="nt"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nt"&gt;repeating-linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;94&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;46&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;26&lt;/span&gt;&lt;span class="o"&gt;,.&lt;/span&gt;&lt;span class="err"&gt;16&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;28&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;28&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="nt"&gt;cqw&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="nt"&gt;linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;220&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;186&lt;/span&gt;&lt;span class="o"&gt;,.&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt; &lt;span class="err"&gt;30&lt;/span&gt;&lt;span class="o"&gt;%),&lt;/span&gt;
  &lt;span class="nt"&gt;linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;260&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;48&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;20&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;8&lt;/span&gt;&lt;span class="o"&gt;,.&lt;/span&gt;&lt;span class="err"&gt;62&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;7&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt; &lt;span class="err"&gt;40&lt;/span&gt;&lt;span class="o"&gt;%),&lt;/span&gt;
  &lt;span class="nt"&gt;radial-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;52&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;38&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;at&lt;/span&gt; &lt;span class="err"&gt;32&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;24&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;228&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;198&lt;/span&gt;&lt;span class="o"&gt;,.&lt;/span&gt;&lt;span class="err"&gt;4&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt; &lt;span class="err"&gt;68&lt;/span&gt;&lt;span class="o"&gt;%),&lt;/span&gt;
  &lt;span class="nt"&gt;linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;180&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;#d09468&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;#b9714a&lt;/span&gt; &lt;span class="err"&gt;44&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;#8&lt;/span&gt;&lt;span class="nt"&gt;c4a2c&lt;/span&gt; &lt;span class="err"&gt;84&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;#5&lt;/span&gt;&lt;span class="nt"&gt;e2e1a&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That first line — the throwing rings — is what stops it looking like a&lt;br&gt;
rendered 3D cone.&lt;/p&gt;
&lt;h3&gt;
  
  
  Steam is the whole trick, and it took three goes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;First go: invisible.&lt;/strong&gt; Low-alpha white, heavily blurred, over a dark brown room. It vanished.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second go: a chimney.&lt;/strong&gt; I brightened it, but every wisp was positioned at &lt;code&gt;left: 0&lt;/code&gt; inside a zero-width container and centred with &lt;code&gt;translate(-50%)&lt;/code&gt;, so all five rose from the &lt;em&gt;same point&lt;/em&gt;. Real steam doesn't do that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third go: right.&lt;/strong&gt; Two changes. Each wisp got its own horizontal origin, and the whole group got &lt;code&gt;mix-blend-mode: screen&lt;/code&gt; so it &lt;em&gt;adds light&lt;/em&gt; to the room instead of laying grey film over it.&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;.steam&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;mix-blend-mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.steam&lt;/span&gt; &lt;span class="nt"&gt;i&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;left&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;--x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;        &lt;span class="c"&gt;/* not left: 0 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a third subtlety I only found by staring at it: each wisp's gradient fades in over its first quarter, so anchoring the group at the liquid surface leaves a &lt;strong&gt;visible gap&lt;/strong&gt; between cup and steam. The steam has to start &lt;em&gt;inside the cup&lt;/em&gt;, where the cup itself hides the transparent lead-in.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bite is a mask, not a drawing
&lt;/h3&gt;

&lt;p&gt;The most satisfying five lines in the file:&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;.biscuit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;.42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;.28&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;.3&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2.7&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt; &lt;span class="m"&gt;2.7&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c"&gt;/* docker holes, tiled */&lt;/span&gt;
              &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;170deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ecc78c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#d8ac6b&lt;/span&gt; &lt;span class="m"&gt;52%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#b98a4a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;mask-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;circle&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;96%&lt;/span&gt; &lt;span class="m"&gt;16%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;3.2&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt; &lt;span class="m"&gt;3.35&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mask-image&lt;/code&gt; punches a real hole through the biscuit — crumb pattern, toasted edge, box-shadow and all. Overlaying a background-coloured circle would have faked it, and would have broken the moment anything passed behind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eight petals, one custom property
&lt;/h3&gt;

&lt;p&gt;The star anise is one rule and an index. Each petal is the same element&lt;br&gt;
rotated by its own &lt;code&gt;--i&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;.anise&lt;/span&gt; &lt;span class="nt"&gt;i&lt;/span&gt; &lt;span class="p"&gt;{&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;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc&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;--i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;45deg&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;-2.6&lt;/span&gt;&lt;span class="n"&gt;cqw&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--i:0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--i:1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt; … &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"--i:7"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rotate-then-translate is the whole radial-layout pattern, and it's worth&lt;br&gt;
keeping in your pocket.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sizing: one container query, zero breakpoints
&lt;/h3&gt;

&lt;p&gt;Every dimension is in &lt;code&gt;cqw&lt;/code&gt; — 1% of the scene's own width — off a single&lt;br&gt;
&lt;code&gt;container-type: inline-size&lt;/code&gt;. No media queries anywhere. The same technique let me drop a simplified version of this cup straight into the hero of my landing page entry without touching a single number.&lt;/p&gt;

&lt;h3&gt;
  
  
  The sprinkle of JavaScript
&lt;/h3&gt;

&lt;p&gt;About forty lines, for two things CSS genuinely can't do: randomise the steam so no two wisps ever line up the same way twice, and dunk the biscuit.&lt;/p&gt;

&lt;p&gt;Both are enhancements. The steam has hand-authored &lt;code&gt;nth-child&lt;/code&gt; fallbacks and the biscuit sits exactly where it should — empty the JS pane and you still get the finished picture, you just lose the dunk.&lt;/p&gt;

&lt;p&gt;One detail worth stealing: the dunk button re-enables itself on &lt;code&gt;animationend&lt;/code&gt;, but reduced-motion users never get an &lt;code&gt;animationend&lt;/code&gt; — so there's an explicit fallback rather than a button that stays disabled forever.&lt;/p&gt;

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

&lt;p&gt;CSS art is still an image, so it's marked as one: &lt;code&gt;role="img"&lt;/code&gt; with an&lt;br&gt;
&lt;code&gt;aria-label&lt;/code&gt; describing the scene, everything inside &lt;code&gt;aria-hidden&lt;/code&gt;. The dunk is a real &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, and &lt;code&gt;prefers-reduced-motion&lt;/code&gt; stills the steam without hiding it — reduced motion means &lt;em&gt;less movement&lt;/em&gt;, not &lt;em&gt;less picture&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;This is my second CSS Art entry this round, alongside Midnight Fridge&lt;br&gt;
Raid. Two very different answers to the same question: comfort food isn't a recipe, it's a time of day.&lt;/p&gt;




&lt;p&gt;Code is MIT licensed and on &lt;a href="https://github.com/maneesh-kumar-thakur/devto-frontend-challenge-food-edition" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
    </item>
    <item>
      <title>Midnight Fridge Raid — comfort food isn't a dish, it's 2am</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Sat, 15 Aug 2026 19:23:20 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/midnight-fridge-raid-comfort-food-isnt-a-dish-its-2am-1l04</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/midnight-fridge-raid-comfort-food-isnt-a-dish-its-2am-1l04</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, CSS Art&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;The brief lists a steaming bowl of ramen, a stack of pancakes, a perfectly cut slice of pie — and then, quietly, &lt;strong&gt;"or the dish you grew up eating."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That last one is the real prompt. Comfort food isn't a recipe. It's a &lt;em&gt;moment&lt;/em&gt;, and the most honest one I could think of isn't a plate at all: it's standing barefoot in a dark kitchen at 2am, deciding whether last night's leftovers count as a meal. (They do.)&lt;/p&gt;

&lt;p&gt;So I drew the moment instead of the meal.&lt;/p&gt;

&lt;p&gt;There was a practical reason too. Ramen and pancakes are the most-drawn subjects in every round of this challenge, and they're bright, warm-toned and daylit — a feed full of them blurs together. A dark scene with a single hot light source stands out on contrast alone, and it happens to be far more interesting to build: the fridge is just a box, the &lt;strong&gt;light&lt;/strong&gt; is the whole picture.&lt;/p&gt;

&lt;p&gt;Everything in the scene is a &lt;code&gt;div&lt;/code&gt;, a gradient, a &lt;code&gt;clip-path&lt;/code&gt; or a shadow. No SVG, no images, no canvas.&lt;/p&gt;

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

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/editor/Maneesh-Thakur/embed/019ffcc6-745c-72fe-88f0-e59b41cb8fac?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The subject is the light, not the fridge
&lt;/h3&gt;

&lt;p&gt;Three stacked cones do the work — a hot core at the opening, a wash across the back wall, a pool thrown along the floor. My first attempt used linear gradients and looked like grey fog lying on the wall. One change fixed it:&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="c"&gt;/* before — fog */&lt;/span&gt;
&lt;span class="nt"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;90&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;197&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;122&lt;/span&gt;&lt;span class="o"&gt;,.&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt; &lt;span class="err"&gt;76&lt;/span&gt;&lt;span class="o"&gt;%);&lt;/span&gt;

&lt;span class="c"&gt;/* after — light */&lt;/span&gt;
&lt;span class="nt"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;radial-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;128&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;66&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;at&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;244&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;216&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;214&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;148&lt;/span&gt;&lt;span class="o"&gt;,.&lt;/span&gt;&lt;span class="err"&gt;62&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="err"&gt;18&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;172&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;84&lt;/span&gt;&lt;span class="o"&gt;,.&lt;/span&gt;&lt;span class="err"&gt;22&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="err"&gt;44&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="nt"&gt;transparent&lt;/span&gt;           &lt;span class="err"&gt;76&lt;/span&gt;&lt;span class="o"&gt;%);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anchoring the gradient &lt;strong&gt;at the opening&lt;/strong&gt; (&lt;code&gt;at 0% 50%&lt;/code&gt;) rather than running it across the element means the falloff radiates from the source, the way light actually behaves. Same element, same blur — completely different picture.&lt;/p&gt;

&lt;p&gt;A clipped cone also shows its own edges. &lt;code&gt;clip-path&lt;/code&gt; gives you a hard-edged polygon and &lt;code&gt;blur()&lt;/code&gt; softens it, but the straight rails still read. A &lt;code&gt;mask-image&lt;/code&gt; on the cross axis feathers them properly:&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="nt"&gt;clip-path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;polygon&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;12&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;88&lt;/span&gt;&lt;span class="o"&gt;%);&lt;/span&gt;
&lt;span class="nt"&gt;mask-image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;180&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;#000&lt;/span&gt; &lt;span class="err"&gt;12&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;#000&lt;/span&gt; &lt;span class="err"&gt;88&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The door has two sides, and it should
&lt;/h3&gt;

&lt;p&gt;My first version was one panel. Shut it and you were looking at the &lt;em&gt;racks&lt;/em&gt; — because a single plane can only ever show you one thing.&lt;/p&gt;

&lt;p&gt;A fridge door has an outside, and what's on the outside is the whole point of a fridge door. So it became two skins on one hinge:&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;.door__outer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.door__face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;backface-visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.door__outer&lt;/span&gt; &lt;span class="p"&gt;{&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;translateZ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.45&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;        &lt;span class="c"&gt;/* notes */&lt;/span&gt;
&lt;span class="nc"&gt;.door__face&lt;/span&gt;  &lt;span class="p"&gt;{&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;rotateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;180deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;translateZ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.45&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c"&gt;/* racks */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the flip-card pattern, but the parent never rotates a full 180° — it swings from -3° to -64°. &lt;strong&gt;The swap still happens by itself&lt;/strong&gt;, somewhere mid-swing, at whatever angle the door's plane turns away from the camera. No threshold to pick, no JavaScript. The browser works out which face you can see and draws only that one.&lt;/p&gt;

&lt;p&gt;Two details fell out of it. The inner face is mirrored by its own flip, so its lighting gradient had to reverse from &lt;code&gt;90deg&lt;/code&gt; to &lt;code&gt;270deg&lt;/code&gt; to stay brightest at the hinge. And the notes are drawn almost black — at 2am in an unlit kitchen a sticky note is a &lt;em&gt;shape and a smudge of colour&lt;/em&gt;, so the ruled lines tell you it's handwriting while giving you no chance of reading a word.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sometimes you fix the camera, not the artwork
&lt;/h3&gt;

&lt;p&gt;The inside of the door is the best part of a fridge — milk, water, beer, the one bottle nobody remembers buying — and mine was three grey smears. The problem wasn't the drawing. The door only projected about &lt;strong&gt;11cqw wide&lt;/strong&gt; on screen, so anything painted on it turned to mush.&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;.fridge__lower&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;perspective&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;perspective-origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200%&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;/* was 151% */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pushing the vanishing point further right and easing the swing back from -64° to -58° widened the projection by roughly half — the difference between "detail is impossible here" and "detail is worth doing".&lt;/p&gt;

&lt;p&gt;Then the bins. What makes a door read as a &lt;em&gt;door&lt;/em&gt; isn't the bottles, it's the &lt;strong&gt;occlusion&lt;/strong&gt;: you never see a whole bottle, only necks and shoulders standing above a moulded lip. And one bright inset line is what turns a dark band into something you could hook a finger over:&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;.bin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;42%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inset&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0.34&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;214&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;166&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;.75&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;A later fix mattered just as much — a recess &lt;em&gt;behind&lt;/em&gt; the bottles, taller than the front face, so a band of dark tray interior shows above the lip. Without it the bottles read as balanced on a ledge rather than standing in a tray.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;clip-path&lt;/code&gt; clips children too
&lt;/h3&gt;

&lt;p&gt;My takeout carton is a &lt;code&gt;clip-path&lt;/code&gt; trapezoid, and its wire handle simply vanished — as did the milk carton's cap and the cake's frosting. All three were children of clipped parents. Moving the clip onto a &lt;code&gt;::before&lt;/code&gt; and leaving the parent unclipped brought them all back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blend modes aren't free wins
&lt;/h3&gt;

&lt;p&gt;There's a vent cut into the back wall, and cold falls out of it — the same trick as steam, run in reverse, because &lt;strong&gt;cold sinks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My first attempt used &lt;code&gt;mix-blend-mode: screen&lt;/code&gt;, copied straight from the light cones outside. It was completely invisible, and the reason is obvious in hindsight: &lt;code&gt;screen&lt;/code&gt; &lt;em&gt;adds&lt;/em&gt; light, and the inside of a lit fridge is already near-white. There was nothing left to add. Cold air has to &lt;strong&gt;sit on&lt;/strong&gt; the warm light and cool it, so it's plain alpha, no blend mode at all.&lt;/p&gt;

&lt;p&gt;Blend modes only work when the backdrop has room to move in that direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Depth when you have no room for it
&lt;/h3&gt;

&lt;p&gt;Comparing against reference photos, the thing my fridge was most obviously missing was &lt;strong&gt;density&lt;/strong&gt;. Real fridges are packed. Mine had three items a shelf.&lt;/p&gt;

&lt;p&gt;But the cavity is only 31cqw wide and the open door hides a third of it, so there was no horizontal room. The answer was to build backwards instead — a second row sitting &lt;em&gt;higher&lt;/em&gt; on each shelf, which reads as further back:&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;.backrow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.4&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                          &lt;span class="c"&gt;/* higher = further back */&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.72&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.09&lt;/span&gt;&lt;span class="n"&gt;cqw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c"&gt;/* dimmer + softer = further away */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three properties, no extra width, and the shelves look stocked.&lt;/p&gt;

&lt;p&gt;The crisper drawer does the opposite thing with the same goal: the vegetables are drawn at full strength and then &lt;strong&gt;buried&lt;/strong&gt; behind a translucent panel with &lt;code&gt;backdrop-filter&lt;/code&gt;. It's the only place in the picture where &lt;em&gt;hiding&lt;/em&gt; detail is what adds it.&lt;/p&gt;

&lt;h3&gt;
  
  
  One container query, zero breakpoints
&lt;/h3&gt;

&lt;p&gt;Every dimension is in &lt;code&gt;cqw&lt;/code&gt; — 1% of the scene's own width — off a single &lt;code&gt;container-type: inline-size&lt;/code&gt;. The whole thing scales to any viewport with no media queries and no magic numbers anywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  The sprinkle of JavaScript
&lt;/h3&gt;

&lt;p&gt;This round allows a little JS, so I used about thirty lines for two things CSS genuinely couldn't do: scatter the dust motes randomly so the loop never repeats identically, and let you shut the door again.&lt;/p&gt;

&lt;p&gt;Both are strictly enhancements. The door's steady state is &lt;em&gt;open&lt;/em&gt; and the motes have hand-authored &lt;code&gt;nth-child&lt;/code&gt; fallbacks — delete the JS and you still get the finished picture. The script also waits for &lt;code&gt;DOMContentLoaded&lt;/code&gt; and bails quietly if its elements are missing, because a script that can't find its button should cost you the button and nothing else.&lt;/p&gt;

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

&lt;p&gt;CSS art is still an image, so it's marked as one — &lt;code&gt;role="img"&lt;/code&gt; with an &lt;code&gt;aria-label&lt;/code&gt; describing the scene, everything inside &lt;code&gt;aria-hidden&lt;/code&gt;. The toggle is a real &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; with &lt;code&gt;aria-pressed&lt;/code&gt;. And &lt;code&gt;prefers-reduced-motion&lt;/code&gt; stills the door, the flicker, the dust and the cold air without hiding any of it: reduced motion means &lt;em&gt;less movement&lt;/em&gt;, not &lt;em&gt;less picture&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I'd tell anyone attempting one of these:&lt;/strong&gt; pick a scene with a light source in it. Light is the most rewarding thing CSS draws, and it does more for a picture than any amount of detail work on the objects.&lt;/p&gt;




&lt;p&gt;Code is MIT licensed and on &lt;a href="https://github.com/maneesh-kumar-thakur/devto-frontend-challenge-food-edition" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I mapped ~150 AI/ML tools by "concept depth" — each with a link to read and a link to the code</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Thu, 23 Jul 2026 18:37:29 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/i-mapped-150-aiml-tools-by-concept-depth-each-with-a-link-to-read-and-a-link-to-the-code-4ogb</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/i-mapped-150-aiml-tools-by-concept-depth-each-with-a-link-to-read-and-a-link-to-the-code-4ogb</guid>
      <description>&lt;p&gt;Most "AI tool" lists are either a wall of 500 links or SEO listicles. I wanted something we all could actually &lt;em&gt;learn&lt;/em&gt; from, so I built a leveled map.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔗 Live:&lt;/strong&gt; &lt;a href="https://maneesh-kumar-thakur.github.io/self-serve-learnings-4-all/" rel="noopener noreferrer"&gt;https://maneesh-kumar-thakur.github.io/self-serve-learnings-4-all/&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;⭐ Code:&lt;/strong&gt; &lt;a href="https://github.com/maneesh-kumar-thakur/self-serve-learnings-4-all" rel="noopener noreferrer"&gt;https://github.com/maneesh-kumar-thakur/self-serve-learnings-4-all&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The idea
&lt;/h3&gt;

&lt;p&gt;~150 LLM &amp;amp; Generative AI tools sorted by &lt;strong&gt;concept depth&lt;/strong&gt;, not popularity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Level 0&lt;/strong&gt; — use ready-made AI, no theory (ChatGPT, Perplexity)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 1&lt;/strong&gt; — run/build with no code (Ollama, Flowise)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 2&lt;/strong&gt; — code with libraries (LangChain, HF Transformers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 3&lt;/strong&gt; — build, fine-tune &amp;amp; ship (PEFT, vector DBs, LangGraph)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 4&lt;/strong&gt; — research &amp;amp; scale (vLLM, DeepSpeed, FlashAttention)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every tool has &lt;strong&gt;two links&lt;/strong&gt;: 📖 &lt;em&gt;Read&lt;/em&gt; (an approachable article to understand&lt;br&gt;
it) and 🐙 &lt;em&gt;Code&lt;/em&gt; (the repo or homepage). Nothing AI-generated — the links come&lt;br&gt;
from real docs, tutorials, and courses, and a weekly GitHub Action re-checks all&lt;br&gt;
~300 of them so they don't rot.&lt;/p&gt;

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

&lt;p&gt;One static HTML page (no framework, no tracking), client-side search +&lt;br&gt;
autocomplete + filters, data in a single file, CC0.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next
&lt;/h3&gt;

&lt;p&gt;Classical/core ML, Computer Vision, and MLOps are the next topics. &lt;strong&gt;What tools am I missing, and where would you move something up or down a level?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>showdev</category>
    </item>
    <item>
      <title>RepoReady — Instant AI-powered onboarding for any GitHub, GitLab, or Bitbucket repo</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Sun, 01 Mar 2026 19:52:12 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/repoready-instant-ai-powered-onboarding-for-any-github-gitlab-or-bitbucket-repo-2m1f</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/repoready-instant-ai-powered-onboarding-for-any-github-gitlab-or-bitbucket-repo-2m1f</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;Every developer has felt it: you click a link to a new repository, the file tree opens, and you just... stare. Where do you start? What even &lt;em&gt;is&lt;/em&gt; this thing?&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;open source contributor community&lt;/strong&gt; has a deeply broken onboarding experience. READMEs are outdated within months of being written. Architecture docs don't exist. Setup instructions assume you already know the stack. First-time contributors give up before they ever submit a PR.&lt;/p&gt;

&lt;p&gt;I've been there. You've been there. Every developer you know has been there.&lt;/p&gt;

&lt;p&gt;This project is for that community — for every dev who has ever opened a codebase and immediately felt lost.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;RepoReady&lt;/strong&gt; — paste any public (or private) repo URL → get a structured AI-generated onboarding guide in seconds.&lt;/p&gt;

&lt;p&gt;No installation. No backend. No data stored. Just open the page and go.&lt;/p&gt;

&lt;p&gt;The guide covers five sections automatically extracted from the repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💡 &lt;strong&gt;What the project does&lt;/strong&gt; — plain English, no jargon&lt;/li&gt;
&lt;li&gt;🧩 &lt;strong&gt;Tech stack&lt;/strong&gt; — every language, framework, and dependency explained&lt;/li&gt;
&lt;li&gt;🗂️ &lt;strong&gt;Folder structure&lt;/strong&gt; — what each directory is actually for&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;How to get started&lt;/strong&gt; — setup steps pulled straight from the code&lt;/li&gt;
&lt;li&gt;🏁 &lt;strong&gt;Where to contribute&lt;/strong&gt; — suggested entry points for first-timers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After analysis, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⬇ &lt;strong&gt;Export as Markdown&lt;/strong&gt; — download a clean &lt;code&gt;.md&lt;/code&gt; file, commit it alongside your codebase&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;Share&lt;/strong&gt; — copy a URL pre-filled with the repo to send teammates&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;a href="https://maneesh-relanto.github.io/DevTo-CommunityChallenge-RepoReady/" rel="noopener noreferrer"&gt;https://maneesh-relanto.github.io/DevTo-CommunityChallenge-RepoReady/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://maneesh-relanto.github.io/DevTo-CommunityChallenge-RepoReady/" rel="noopener noreferrer"&gt;https://maneesh-relanto.github.io/DevTo-CommunityChallenge-RepoReady/&lt;/a&gt;)&lt;/p&gt;

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

&lt;p&gt;[&lt;a href="https://github.com/Maneesh-Relanto/DevTo-CommunityChallenge-RepoReady" rel="noopener noreferrer"&gt;https://github.com/Maneesh-Relanto/DevTo-CommunityChallenge-RepoReady&lt;/a&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Architecture: zero-backend, client-side only
&lt;/h3&gt;

&lt;p&gt;The entire app is a static HTML/CSS/JS project. No server, no API keys stored server-side, no database. Everything runs in the browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Input (Repo URL + AI API Key)
        │
        ▼
┌──────────────────────────────┐
│  Detect platform from URL    │
│  github.com / gitlab.com /   │
│  bitbucket.org               │
└──────────────────────────────┘
        │
        ▼
┌──────────────────────────────┐
│  Platform API call           │
│  • Repo metadata             │
│  • README (first 4000 chars) │
│  • File tree (up to 120)     │
│  • Package manifest          │
│    (package.json / Cargo.toml│
│     / pyproject.toml / etc.) │
└──────────────────────────────┘
        │
        ▼
┌──────────────────────────────┐
│  Structured prompt → AI      │
│  Returns JSON with 5 keys    │
└──────────────────────────────┘
        │
        ▼
┌──────────────────────────────┐
│  Render onboarding guide     │
│  + Markdown export           │
│  + Shareable link            │
└──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  File structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repoready/
├── index.html      # HTML shell + layout
├── package.json    # ESM declaration + npm test script
├── css/
│   └── styles.css  # Design tokens, animations
├── js/
│   ├── app.js      # Main entry — wires everything
│   ├── ai.js       # All 7 AI provider adapters
│   ├── git.js      # GitHub, GitLab, Bitbucket fetchers
│   ├── ui.js       # DOM rendering, export, history
│   └── config.js   # Centralised constants
├── tests/
│   ├── git.test.js     # 17 unit tests
│   ├── ai.test.js      # 16 unit tests
│   └── history.test.js # 12 unit tests
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-provider AI
&lt;/h3&gt;

&lt;p&gt;Seven providers, switchable at runtime — user brings their own key (Ollama is fully local, no key needed):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Models&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Claude (Anthropic)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;claude-sonnet-4-6, claude-opus-4-6, claude-haiku-4-5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenAI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;gpt-4o, gpt-4o-mini, gpt-4-turbo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Gemini&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;gemini-2.0-flash, gemini-2.0-flash-lite, gemini-1.5-pro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mistral AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;mistral-large-latest, mistral-small-latest, codestral-latest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;xAI Grok&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;grok-3, grok-3-fast, grok-2-1212&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Groq&lt;/strong&gt; 🆓&lt;/td&gt;
&lt;td&gt;llama-3.3-70b-versatile, llama-3.1-8b-instant, mixtral-8x7b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; 🏠&lt;/td&gt;
&lt;td&gt;llama3.2, mistral, codellama, phi4 (runs locally)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Multi-platform Git support
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt; uses the REST API (&lt;code&gt;/repos/{owner}/{repo}/git/trees/HEAD?recursive=1&lt;/code&gt;) to get the full recursive file tree in a single call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitLab&lt;/strong&gt; uses the projects API + a separate &lt;code&gt;/languages&lt;/code&gt; endpoint to correctly identify the primary language (GitLab doesn't expose this in the main project object).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bitbucket&lt;/strong&gt; required extra work — its &lt;code&gt;/src&lt;/code&gt; endpoint is not recursive, so I fetch the root directory and then fan out parallel requests for each first-level subdirectory to build a meaningful tree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt engineering
&lt;/h3&gt;

&lt;p&gt;The prompt is structured to return strict JSON — no markdown fences, no free text — which makes parsing reliable across all three AI providers. I specifically extract and inject the package manifest (e.g. &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;Cargo.toml&lt;/code&gt;, &lt;code&gt;pyproject.toml&lt;/code&gt;) into the prompt so the AI has concrete dependency information rather than guessing from filenames.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ES modules over file://&lt;/strong&gt;&lt;br&gt;
The project uses native ES modules, which browsers block when opened directly from the filesystem (CORS restriction). This bit me during early testing — &lt;code&gt;index.html&lt;/code&gt; opened from a double-click was completely silent. The fix is trivial (&lt;code&gt;python -m http.server&lt;/code&gt;), but catching it and documenting it clearly matters for contributors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bitbucket's flat file tree&lt;/strong&gt;&lt;br&gt;
Unlike GitHub and GitLab, Bitbucket's &lt;code&gt;/src&lt;/code&gt; endpoint returns only the immediate directory listing rather than a recursive tree. I worked around this by fanning out parallel requests to each root-level subdirectory — capped at 20 concurrent requests to stay polite — which gives a meaningful two-level tree in most cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI response consistency&lt;/strong&gt;&lt;br&gt;
Three different AI providers all produce slightly different formatting, even when instructed to return raw JSON. The parser uses a greedy &lt;code&gt;/{[\s\S]*}/&lt;/code&gt; regex to extract the first complete JSON object from the response, which handles models that wrap JSON in markdown code fences despite explicit instructions not to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keeping it zero-dependency&lt;/strong&gt;&lt;br&gt;
The temptation to reach for a markdown renderer, a UI library, or a bundler is real. I deliberately resisted — the whole project loads from a single &lt;code&gt;&amp;lt;script type="module"&amp;gt;&lt;/code&gt; tag. No &lt;code&gt;npm install&lt;/code&gt;, no build step, no &lt;code&gt;node_modules&lt;/code&gt;. This constraint forced cleaner separation of concerns and makes the project trivially deployable to any static host.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://maneesh-relanto.github.io/DevTo-CommunityChallenge-RepoReady/" rel="noopener noreferrer"&gt;https://maneesh-relanto.github.io/DevTo-CommunityChallenge-RepoReady/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pick an AI provider, paste your API key&lt;/li&gt;
&lt;li&gt;Enter any repo URL — try one of these to start:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://github.com/expressjs/express&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/django/django&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://github.com/rust-lang/rust&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Analyze →&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/Maneesh-Relanto/repoready" rel="noopener noreferrer"&gt;https://github.com/Maneesh-Relanto/repoready&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>LearnHub - A Community-First Learning Platform</title>
      <dc:creator>Maneesh Thakur</dc:creator>
      <pubDate>Sun, 01 Mar 2026 14:01:03 +0000</pubDate>
      <link>https://dev.to/maneesh_thakur_d16c2852fa/learnhub-a-community-first-learning-platform-4572</link>
      <guid>https://dev.to/maneesh_thakur_d16c2852fa/learnhub-a-community-first-learning-platform-4572</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;LearnHub was built for &lt;strong&gt;learning communities&lt;/strong&gt; - the study groups, coding circles, book clubs, bootcamp cohorts, and open-source learning teams that power collaborative education.&lt;/p&gt;

&lt;p&gt;These communities face real coordination challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📚 &lt;strong&gt;Resource Chaos&lt;/strong&gt;: Great materials get lost in Slack/Discord messages&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;No Discovery&lt;/strong&gt;: Hard to find quality resources shared by peers&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;No Visibility&lt;/strong&gt;: Nobody knows what's been shared, who contributed, or how valuable materials are&lt;/li&gt;
&lt;li&gt;⏰ &lt;strong&gt;No Structure&lt;/strong&gt;: Difficult to coordinate learning progression or paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LearnHub solves these problems by creating a &lt;strong&gt;centralized hub&lt;/strong&gt; where learning communities can organize, discover, and collaborate around shared resources.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;LearnHub&lt;/strong&gt; is a modern, community-first learning platform with:&lt;/p&gt;

&lt;h3&gt;
  
  
  📚 &lt;strong&gt;Core Features&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;✨ &lt;strong&gt;Resource Library&lt;/strong&gt; - Central repository for course materials, links, documents, videos, and code&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add resources with rich metadata (type, tags, difficulty, time estimate)&lt;/li&gt;
&lt;li&gt;Pin important resources to highlight them&lt;/li&gt;
&lt;li&gt;Full-text search with multi-criteria filtering&lt;/li&gt;
&lt;li&gt;Share resources in multiple formats (Plain Text, Markdown, JSON)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⭐ &lt;strong&gt;Resource Ratings&lt;/strong&gt; - Community-driven quality assessment&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rate resources 1-5 stars with optional reviews&lt;/li&gt;
&lt;li&gt;See average ratings and member feedback&lt;/li&gt;
&lt;li&gt;Build trust through peer validation&lt;/li&gt;
&lt;li&gt;Identify high-value materials at a glance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎯 &lt;strong&gt;Learning Paths&lt;/strong&gt; - Structured learning sequences&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create ordered paths through resources&lt;/li&gt;
&lt;li&gt;Set difficulty levels and time estimates&lt;/li&gt;
&lt;li&gt;Visualize learning progression&lt;/li&gt;
&lt;li&gt;Share curated learning sequences with groups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👤 &lt;strong&gt;User Profiles&lt;/strong&gt; - Showcase member contributions&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public profiles with member stats&lt;/li&gt;
&lt;li&gt;Track resources contributed, average ratings, groups joined&lt;/li&gt;
&lt;li&gt;Edit profile with custom bio and avatar&lt;/li&gt;
&lt;li&gt;See all member contributions at a glance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🌙 &lt;strong&gt;Dark Mode&lt;/strong&gt; - Beautiful theme support&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Toggle between light and dark modes&lt;/li&gt;
&lt;li&gt;Persistent theme preference&lt;/li&gt;
&lt;li&gt;System preference detection&lt;/li&gt;
&lt;li&gt;Seamless across all pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🏠 &lt;strong&gt;Group Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse and search learning groups by topic&lt;/li&gt;
&lt;li&gt;Create new groups instantly&lt;/li&gt;
&lt;li&gt;Join/leave groups with one click&lt;/li&gt;
&lt;li&gt;View group members and resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎨 &lt;strong&gt;Design Excellence&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clean, modern UI with Tailwind CSS&lt;/li&gt;
&lt;li&gt;Responsive design (mobile-first)&lt;/li&gt;
&lt;li&gt;Smooth animations and transitions&lt;/li&gt;
&lt;li&gt;Accessibility-first (WCAG AA+)&lt;/li&gt;
&lt;li&gt;Dark mode support&lt;/li&gt;
&lt;li&gt;Compact, focused layout&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;&lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href="https://devto-communitychallenge-learnhub.pages.dev/" rel="noopener noreferrer"&gt;LearnHub on Cloudflare Pages&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Demo Flow:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Homepage&lt;/strong&gt; - See platform overview with stats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browse Groups&lt;/strong&gt; - Explore learning communities by topic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join a Group&lt;/strong&gt; - Click any group to join instantly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explore Resources&lt;/strong&gt; - View, search, and filter group resources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Resources&lt;/strong&gt; - Share a new resource with your group&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Resources&lt;/strong&gt; - Give feedback with ratings and reviews&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View Profile&lt;/strong&gt; - See your contributions and stats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create Learning Path&lt;/strong&gt; - Build a structured learning sequence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Toggle Dark Mode&lt;/strong&gt; - Try light/dark theme switching&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/&lt;/code&gt; - Homepage with featured groups and stats&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/groups&lt;/code&gt; - Browse all learning groups&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/groups/[id]&lt;/code&gt; - Group detail page with resource library&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/paths&lt;/code&gt; - Learning paths browsing and creation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/profile&lt;/code&gt; - User profile with contributions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/about&lt;/code&gt; - About LearnHub&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/Maneesh-Relanto/DevTo-CommunityChallenge-LearnHub" rel="noopener noreferrer"&gt;Maneesh-Relanto/DevTo-CommunityChallenge-LearnHub&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LearnHub/
├── client/
│   ├── pages/
│   │   ├── Index.tsx              # Homepage
│   │   ├── GroupsDirectory.tsx    # Browse groups
│   │   ├── GroupDetail.tsx        # Group &amp;amp; resources
│   │   ├── LearningPaths.tsx      # Learning paths
│   │   ├── UserProfile.tsx        # User profiles
│   │   └── About.tsx              # About page
│   ├── components/
│   │   ├── groups/                # Group components
│   │   ├── resources/             # Resource components
│   │   ├── paths/                 # Learning path components
│   │   ├── user/                  # User/profile components
│   │   └── ui/                    # UI primitives
│   ├── context/
│   │   ├── UserContext.tsx        # User state management
│   │   ├── GroupContext.tsx       # Groups state management
│   │   ├── ResourceContext.tsx    # Resources state management
│   │   ├── PathContext.tsx        # Learning paths state
│   │   └── ThemeContext.tsx       # Dark mode state
│   ├── types/
│   │   └── index.ts               # TypeScript interfaces
│   └── App.tsx                    # Main app component
├── package.json
├── vite.config.ts
├── tsconfig.json
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Stats:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;Codebase&lt;/strong&gt;: ~2,500 lines of TypeScript/React&lt;/li&gt;
&lt;li&gt;🎨 &lt;strong&gt;Components&lt;/strong&gt;: 20+ reusable components&lt;/li&gt;
&lt;li&gt;🗄️ &lt;strong&gt;State Management&lt;/strong&gt;: 5 Context providers&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Tests&lt;/strong&gt;: 92% passing (48/52 unit tests)&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Build Size&lt;/strong&gt;: 796 KB (gzipped: 215 KB)&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Performance&lt;/strong&gt;: No console warnings, optimized renders&lt;/li&gt;
&lt;/ul&gt;




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

&lt;h3&gt;
  
  
  🛠️ &lt;strong&gt;Technology Stack&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Frontend Framework&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React 18.3 - Modern UI library with hooks&lt;/li&gt;
&lt;li&gt;TypeScript 5.9 - Type-safe development&lt;/li&gt;
&lt;li&gt;Vite 7 - Lightning-fast build tool&lt;/li&gt;
&lt;li&gt;React Router 6 - Client-side routing (SPA)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Styling &amp;amp; UI&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tailwind CSS 3.4 - Utility-first styling&lt;/li&gt;
&lt;li&gt;Radix UI - Accessible component primitives&lt;/li&gt;
&lt;li&gt;Lucide React - Beautiful icon library&lt;/li&gt;
&lt;li&gt;Custom CSS animations - Smooth transitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;State Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Context API - Global state management&lt;/li&gt;
&lt;li&gt;Custom hooks - Reusable logic&lt;/li&gt;
&lt;li&gt;localStorage - Client-side persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Testing &amp;amp; Quality&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vitest 3.2 - Fast unit test framework&lt;/li&gt;
&lt;li&gt;React Testing Library - Component testing&lt;/li&gt;
&lt;li&gt;TypeScript Strict Mode - Type safety&lt;/li&gt;
&lt;li&gt;ESLint &amp;amp; Prettier - Code quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloudflare Pages - Static hosting&lt;/li&gt;
&lt;li&gt;GitHub Actions - CI/CD (auto-deploy on push)&lt;/li&gt;
&lt;li&gt;SPA routing configuration - Client-side navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🏗️ &lt;strong&gt;Architecture Decisions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Component-Driven Development&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small, focused components with single responsibility&lt;/li&gt;
&lt;li&gt;Reusable UI components in &lt;code&gt;/components/ui&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Feature-specific components grouped by domain&lt;/li&gt;
&lt;li&gt;Props-based configuration for flexibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;State Management Pattern&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Context API for global state (User, Groups, Resources, Paths, Theme)&lt;/li&gt;
&lt;li&gt;Custom hooks for easy consumption (&lt;code&gt;useUser&lt;/code&gt;, &lt;code&gt;useGroups&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;localStorage for persistence across sessions&lt;/li&gt;
&lt;li&gt;Centralized action functions for predictable updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Type Safety&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strict TypeScript mode enabled&lt;/li&gt;
&lt;li&gt;Interfaces for all data models&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;any&lt;/code&gt; types in codebase&lt;/li&gt;
&lt;li&gt;Type-safe component props&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Performance Optimizations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;useMemo for expensive computations (filtering, sorting)&lt;/li&gt;
&lt;li&gt;useCallback for stable function references&lt;/li&gt;
&lt;li&gt;Lazy routing with React.lazy (when needed)&lt;/li&gt;
&lt;li&gt;Optimized re-renders with proper dependency arrays&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Testing Strategy&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests for contexts (data logic)&lt;/li&gt;
&lt;li&gt;Hook tests with React Testing Library&lt;/li&gt;
&lt;li&gt;Component snapshot tests&lt;/li&gt;
&lt;li&gt;Integration tests for workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎯 &lt;strong&gt;Key Features Implementation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Resource Ratings&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Rating interface&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Rating&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 1-5&lt;/span&gt;
  &lt;span class="nl"&gt;review&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Methods in ResourceContext&lt;/span&gt;
&lt;span class="nf"&gt;addRating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;review&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;getAverageRating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Learning Paths&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ordered array of resource IDs&lt;/li&gt;
&lt;li&gt;Difficulty level and time estimate&lt;/li&gt;
&lt;li&gt;Creator tracking and timestamps&lt;/li&gt;
&lt;li&gt;Visual step-by-step progression&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;User Profiles&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public profile pages&lt;/li&gt;
&lt;li&gt;Contribution statistics&lt;/li&gt;
&lt;li&gt;Editable profile info (name, bio, avatar)&lt;/li&gt;
&lt;li&gt;Member since date tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dark Mode&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ThemeContext for global theme state&lt;/li&gt;
&lt;li&gt;localStorage for persistence&lt;/li&gt;
&lt;li&gt;System preference detection&lt;/li&gt;
&lt;li&gt;Tailwind's dark mode utilities&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📊 &lt;strong&gt;Data Model&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// User&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;joinedGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Group&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;facilitator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;members&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="na"&gt;resourceCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Resource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;note&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DifficultyLevel&lt;/span&gt;
  &lt;span class="na"&gt;timeEstimate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TimeEstimate&lt;/span&gt;
  &lt;span class="na"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Rating&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="na"&gt;uploadedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;isPinned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Learning Path&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;resourceIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DifficultyLevel&lt;/span&gt;
  &lt;span class="na"&gt;estimatedDuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TimeEstimate&lt;/span&gt;
  &lt;span class="na"&gt;createdBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚀 &lt;strong&gt;Development Workflow&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Local Development&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   pnpm &lt;span class="nb"&gt;install
   &lt;/span&gt;pnpm dev          &lt;span class="c"&gt;# Start dev server with HMR&lt;/span&gt;
   pnpm typecheck    &lt;span class="c"&gt;# Type checking&lt;/span&gt;
   pnpm &lt;span class="nb"&gt;test&lt;/span&gt;         &lt;span class="c"&gt;# Run unit tests&lt;/span&gt;
   pnpm build:client &lt;span class="c"&gt;# Production build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Push to GitHub main branch&lt;/li&gt;
&lt;li&gt;Cloudflare Pages auto-detects build&lt;/li&gt;
&lt;li&gt;Vite builds to &lt;code&gt;dist/spa&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deployed in ~2 minutes&lt;/li&gt;
&lt;li&gt;SPA routing via &lt;code&gt;_routes.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Code Quality&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript strict mode&lt;/li&gt;
&lt;li&gt;No console warnings&lt;/li&gt;
&lt;li&gt;Accessible components&lt;/li&gt;
&lt;li&gt;Responsive design&lt;/li&gt;
&lt;li&gt;Clean code principles&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Thank You
&lt;/h2&gt;

&lt;p&gt;Built with ❤️ for learning communities everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href="https://devto-communitychallenge-learnhub.pages.dev/" rel="noopener noreferrer"&gt;LearnHub&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Repository&lt;/strong&gt;: &lt;a href="https://github.com/Maneesh-Relanto/DevTo-CommunityChallenge-LearnHub" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
