<?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: Natasha Pierre-Louis</title>
    <description>The latest articles on DEV Community by Natasha Pierre-Louis (@natashapl).</description>
    <link>https://dev.to/natashapl</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%2F940041%2Ff9f84ceb-8e5c-46c3-863c-d2578667d420.jpeg</url>
      <title>DEV Community: Natasha Pierre-Louis</title>
      <link>https://dev.to/natashapl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/natashapl"/>
    <language>en</language>
    <item>
      <title>Stay for Dinner - CSS Art Submission</title>
      <dc:creator>Natasha Pierre-Louis</dc:creator>
      <pubDate>Mon, 17 Aug 2026 02:53:32 +0000</pubDate>
      <link>https://dev.to/natashapl/stay-for-dinner-2hao</link>
      <guid>https://dev.to/natashapl/stay-for-dinner-2hao</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;Pasta and meatballs - but really, the kitchen it gets made in.&lt;/p&gt;

&lt;p&gt;Comfort food isn't just the plate. It's the warm room at the end of the day, the window going orange, someone saying &lt;em&gt;stay, there's plenty&lt;/em&gt;. So I didn't draw a bowl of spaghetti on a flat background. I drew the whole kitchen at dusk: sage cabinets, subway tile, an island with three stools, and dinner already sitting out.&lt;/p&gt;

&lt;p&gt;Then I made it explorable, because a room you can only look at isn't really an invitation.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://stay-for-dinner-three.vercel.app/" rel="noopener noreferrer"&gt;https://stay-for-dinner-three.vercel.app/&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/natashapl/stay-for-dinner" rel="noopener noreferrer"&gt;https://github.com/natashapl/stay-for-dinner&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything you see is HTML elements and CSS. No SVG, no canvas, no images, no icon fonts, no emoji. React is in there, but only as a composition tool. The components return nested &lt;code&gt;div&lt;/code&gt;s and CSS paints all of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things to try:&lt;/strong&gt; click either bowl for the recipe, click the plate stack to serve dinner, click the window to watch night fall, turn on the faucet, and open the fridge and the wall cabinets.&lt;/p&gt;
&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;A note on how this was built: I've been a frontend developer for 15+ years, but this was my first real attempt at CSS art. So, I worked through it in collaboration with Claude Code and Codex. I wrote the brief, set the twelve build phases, made the design calls, and reviewed every phase against the reference. The lessons below are the ones I hit doing that.&lt;/p&gt;
&lt;h3&gt;
  
  
  One unit ran the whole drawing
&lt;/h3&gt;

&lt;p&gt;The scene is a CSS size container, so &lt;code&gt;--u: 1cqw&lt;/code&gt; is always 1% of the frame's width no matter how big the frame is:&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;.kitchen&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--u&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="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;That gave me one rule I applied everywhere: &lt;strong&gt;positions in &lt;code&gt;%&lt;/code&gt;, sizes in multiples of &lt;code&gt;--u&lt;/code&gt;.&lt;/strong&gt; The payoff is that the kitchen rescales as a single composition - a 4u bowl rim is still 4u on a phone - and my responsive stylesheet only touches the page around the frame. Not one media query moves a cabinet. Any time an object seemed to need a breakpoint to stay put, it turned out I'd sized it in &lt;code&gt;px&lt;/code&gt; by mistake.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lighting is a pass over the room, not shading on each object
&lt;/h3&gt;

&lt;p&gt;My first version shaded every object individually and looked flat no matter how many gradients I piled on. The fix was to stop lighting objects and light the &lt;em&gt;room&lt;/em&gt;, in two layers on top of the finished artwork:&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-shade&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;multiply&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c"&gt;/* shadow */&lt;/span&gt;
&lt;span class="nc"&gt;.scene-light&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="c"&gt;/* light  */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A shadow painted as &lt;code&gt;rgb(0 0 0 / 0.3)&lt;/code&gt; is a grey film - it lowers brightness but leaves hue alone, so a shadowed sage cabinet and a shadowed terracotta floor both drift toward the same dead grey. &lt;code&gt;multiply&lt;/code&gt; in a warm brown instead pushes the sage toward olive and the terracotta toward oxblood: &lt;strong&gt;each surface darkens through its own hue.&lt;/strong&gt; &lt;code&gt;screen&lt;/code&gt; is the same trick inverted for light. That one change was most of the difference between flat and lit.&lt;/p&gt;

&lt;p&gt;They have to be separate layers, because shadow and light aren't opposites - occlusion collects in corners and under overhangs, light pours from one direction. One gradient trying to do both just reads as a vignette.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bug that taught me the most
&lt;/h3&gt;

&lt;p&gt;Two trees outside the window looked wrong, and I was certain it was a layering problem - the hill appeared to pass straight through the trunks. I reordered the DOM. I added z-indexes. Nothing.&lt;/p&gt;

&lt;p&gt;It was never a z-index bug. The trunk's mid-tone landed on roughly &lt;code&gt;#a2703f&lt;/code&gt;; the hill behind it was &lt;code&gt;#a96a3b&lt;/code&gt;. &lt;strong&gt;A silhouette is read entirely by value, so two shapes at the same value merge no matter what the stacking order says.&lt;/strong&gt; Darkening the trunk fixed it instantly.&lt;/p&gt;

&lt;p&gt;The canopies had a second problem: I'd backed the leaf crowns with a large filler ellipse, and because radial-gradient radii are percentages of the &lt;em&gt;box&lt;/em&gt;, that one ellipse reached every edge and rendered as a rectangle with rounded corners. The twelve crowns I'd carefully placed were all sitting inside it, drawing nothing. The rule I took away: the filler has to be &lt;em&gt;smaller&lt;/em&gt; than the ring of crowns, so the crowns are the silhouette.&lt;/p&gt;

&lt;h3&gt;
  
  
  Night mode had to be registered to interpolate
&lt;/h3&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%2F0oph0pdswurn82gneuyo.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%2F0oph0pdswurn82gneuyo.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All the scene colour lives on &lt;code&gt;.kitchen&lt;/code&gt; rather than &lt;code&gt;:root&lt;/code&gt;, so &lt;code&gt;.kitchen.is-night&lt;/code&gt; can redeclare the same names and re-light everything downstream. But the transition still snapped, because plain custom properties don't interpolate. Registering the palette fixes 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="k"&gt;@property&lt;/span&gt; &lt;span class="n"&gt;--wall&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="s1"&gt;"&amp;lt;color&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;#ece0cc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the sky, cabinets, wood and lighting strengths all blend through the in-between values and night &lt;em&gt;settles in&lt;/em&gt; instead of arriving as a palette flash. The strengths being typed &lt;code&gt;&amp;lt;number&amp;gt;&lt;/code&gt; also means other effects can just multiply by them - the fridge light gets brighter as the room darkens, for free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making artwork accessible
&lt;/h3&gt;

&lt;p&gt;This was the part my day job actually prepared me for. Buttons may only contain phrasing content, and my artwork is divs - so every interactive object is a &lt;strong&gt;transparent button overlaid on the art&lt;/strong&gt;, not a button wrapping it. Hover and &lt;code&gt;:focus-within&lt;/code&gt; then drive the same visual response.&lt;/p&gt;

&lt;p&gt;The recipe is a native &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; styled as a right-hand drawer, which hands me Escape and focus containment for free; JavaScript only returns focus to the bowl that opened it. Plating announces its steps through an &lt;code&gt;aria-live&lt;/code&gt; region, toggles carry &lt;code&gt;aria-pressed&lt;/code&gt; and &lt;code&gt;aria-expanded&lt;/code&gt;, and decorative motion respects &lt;code&gt;prefers-reduced-motion&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two fixes I only found by measuring rather than eyeballing: the scrollable recipe body had no focusable element inside it, so keyboard users couldn't scroll the ingredients at all - the container needed to be focusable. And the bowls and plate stack came out &lt;strong&gt;34px and 27px tall on a 360px phone&lt;/strong&gt;. That led to the one place I deliberately broke my own unit rule: a touch target isn't part of the drawing, it's the size of a fingertip, and a fingertip doesn't shrink when the frame does. Those insets are in &lt;code&gt;px&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it landed
&lt;/h3&gt;

&lt;p&gt;Production Lighthouse: &lt;strong&gt;Performance 100, Accessibility 100, Best Practices 100, SEO 100&lt;/strong&gt;, with CLS 0 and TBT 0, from about 450 DOM elements and 22 KB of gzipped CSS.&lt;/p&gt;

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

&lt;p&gt;I want to go back into the refrigerator and give every item inside its own top face - the three-face rule (lightest top, base-colour front, darker side) is what sells volume, and I applied it to the room's big objects but not to the small ones. And the doors want real thickness: at 68° open you're looking at a plane edge-on, and zero-thickness planes are the giveaway.&lt;/p&gt;

&lt;p&gt;Mostly I want to keep doing this. I came in thinking CSS art was a party trick and left with a set of techniques - blend-mode lighting, container-relative units, typed custom properties - that I'm going to use in ordinary product work moving forward.&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
