<?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>Forem: Philip Warkentien II</title>
    <description>The latest articles on Forem by Philip Warkentien II (@warkentien2).</description>
    <link>https://forem.com/warkentien2</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F31902%2F0deb6df5-faa6-4056-a1a5-fce8c1b09526.jpeg</url>
      <title>Forem: Philip Warkentien II</title>
      <link>https://forem.com/warkentien2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/warkentien2"/>
    <language>en</language>
    <item>
      <title>Desert Racer 🏜️: World's First CSS-only Swipe-Aware Game!</title>
      <dc:creator>Philip Warkentien II</dc:creator>
      <pubDate>Wed, 27 Mar 2024 13:20:00 +0000</pubDate>
      <link>https://forem.com/warkentien2/desert-racer-worlds-first-css-only-swipe-aware-game-4j0h</link>
      <guid>https://forem.com/warkentien2/desert-racer-worlds-first-css-only-swipe-aware-game-4j0h</guid>
      <description>&lt;h4&gt;
  
  
  A CSS-only, no JS, no checkbox, &lt;strong&gt;swipe-aware&lt;/strong&gt; (scroll-aware) game. With config options and music!
&lt;/h4&gt;




&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;



&lt;p&gt;I built Desert Racer &lt;strong&gt;to showcase&lt;/strong&gt; the unique and unorthodox tricks of &lt;strong&gt;CSS-only Swipe Awareness&lt;/strong&gt; and &lt;strong&gt;CSS-only Collision Detection.&lt;/strong&gt; I believe these tricks to be &lt;strong&gt;the first of their kind.&lt;/strong&gt; You're welcome to challenge these claims. This article covers the aforementioned tricks, plus the overall process of building a Swipe-Aware game.&lt;/p&gt;

&lt;p&gt;Checked again today – March 27th, 2024 – and &lt;strong&gt;Gemini (Google's new AI)&lt;/strong&gt; still believes swipe awareness to be impossible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34j59uk4af3ahfs6jsgj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34j59uk4af3ahfs6jsgj.png" alt="Screenshot of Gemini, see caption below" width="800" height="225"&gt;&lt;/a&gt;&lt;br&gt;transcription:&lt;br&gt;&lt;br&gt;
  👨 — "Can I achieve bi-directional swipe detection with only CSS and HTML?"&lt;br&gt;🤖 — &lt;b&gt;"No,&lt;/b&gt; achieving bi-directional swipe detection with CSS only and HTML &lt;b&gt;is not possible.&lt;/b&gt; CSS lacks the functionalities needed to determine the direction and intent of a swipe gesture."
  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;✨ Creativity is not yet obsolete! ✨&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Is CSS the right tool for this job? Not at this moment. However... CSS has been offloading JavaScript event handlers. Hopefully, this article contributes to that goal. Either way, as somewhat of an artist myself, building things with the wrong tool – for the sake of debunking something considered impossible – is a natural impulse. I had a hunch that it would work, and it did.&lt;/p&gt;

&lt;p&gt;This article will be &lt;strong&gt;interactive&lt;/strong&gt; and hopefully inspiring. So, stick around! &lt;em&gt;(Or at least scroll to see the &lt;strong&gt;cool GIFs&lt;/strong&gt;)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The idea&lt;/li&gt;
&lt;li&gt;
Building a swipe-aware game

&lt;ol&gt;
&lt;li&gt; Primary obstacles (idea validation)
&lt;/li&gt;
&lt;li&gt; Fine-tuning the mobile UX
&lt;/li&gt;
&lt;li&gt; Secondary obstacles
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Lessons learned&lt;/li&gt;

&lt;li&gt;Kudos&lt;/li&gt;

&lt;li&gt;Credits&lt;/li&gt;

&lt;li&gt;FAQ&lt;/li&gt;

&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. The idea &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;On October 6th, 2023, I caught wind of a brand-new CSS feature: &lt;strong&gt;scroll-driven animations&lt;/strong&gt;. All thanks to &lt;a href="https://twitter.com/bramus" rel="noopener noreferrer"&gt;Bramus'&lt;/a&gt; excellent expository &lt;a href="https://scroll-driven-animations.style/" rel="noopener noreferrer"&gt;article&lt;/a&gt;. Later that night I WhatsApped myself the following idea:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjkdm65f92mh0u74aysi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjkdm65f92mh0u74aysi.png" alt="WhatsApp screenshot dated October, 6th, 2023. See caption below" width="800" height="247"&gt;&lt;/a&gt;&lt;br&gt;👨 — "X and Y scroll-driven animations with snap-to-center when released to emulate swipe detection!!!! Hide the scrollbar with CSS."
  &lt;/p&gt;

&lt;p&gt; &lt;br&gt;
You can feel the excitement with all the typos, the misplaced comma, missing article, and missing preposition. &lt;strong&gt;Could I achieve swipe awareness without JavaScript?&lt;/strong&gt; This idea grew into this HTML and CSS-only game you see today. Go test-drive, then come back to see how I built it.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://000699350.deployed.codepen.website/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwraw4epyxe1f0vc4bi7i.png" alt="Click to play Desert Racer" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;📙: Original CSS background, assets generated with AI, music from &lt;a href="https://pixabay.com/" rel="noopener noreferrer"&gt;Pixabay&lt;/a&gt;.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;em&gt;📙: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-timeline" rel="noopener noreferrer"&gt;scroll-timeline&lt;/a&gt; is only supported by Blink/Chromium browsers (Chrome Desktop, Edge Desktop, and Chrome for Android). For iPhone users: Chrome for iOS is just Safari with a skin, so use Chrome on your MacBook (that we all know you have).&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  2. Building a swipe-aware game &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This process demanded multiple prototype validations, adapting, refactoring, and inventing never-before-seen CSS tricks (which can lead you down an exploration rabbit hole).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;⚠️ Keep in mind that at the time I created this game I wasn't aware of the &lt;a href="https://codepen.io/2kool2/pen/abjdZzJ/1000" rel="noopener noreferrer"&gt;space toggle hack&lt;/a&gt;. So &lt;strong&gt;the entire logic&lt;/strong&gt; of this game &lt;strong&gt;relies on CSS&lt;/strong&gt; properties that accept &lt;strong&gt;numerical values.&lt;/strong&gt; For instance.: I could manipulate &lt;code&gt;animation-duration&lt;/code&gt; but not &lt;code&gt;animation-play-state&lt;/code&gt;.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Are we swiping or scrolling?
&lt;/h3&gt;

&lt;p&gt;Under the hood, we are using CSS scroll properties and the experimental &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-timeline" rel="noopener noreferrer"&gt;scroll-timeline 🧪&lt;/a&gt;. However, for touch devices and trackpads, the real-world action is literally to swipe, hence the name &lt;strong&gt;swipe-detection.&lt;/strong&gt; I made this as a swipe-first game with a Mouse-wheel-detection fallback. If your mouse supports side-scrolling, remember to activate the mouse input setting on Desert Racer's home screen.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftba8owymc6dxoxhsmq2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftba8owymc6dxoxhsmq2o.png" alt="Mouse" width="260" height="360"&gt;&lt;/a&gt; &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhenbxd6noqlhe4famez.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhenbxd6noqlhe4famez.png" alt="Mouse input config" width="225" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  2.1.: Primary obstacles (validating ideas) &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;h4&gt;
  
  
  · 2.1.a.: Achieving bi-directional scroll-driven animation &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;The first thing I needed to validate was if I could control my timeline by scrolling on both axes. At first, I got misled by the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-timeline-axis" rel="noopener noreferrer"&gt;scroll-timeline-axis&lt;/a&gt; property definition, since accepted values were only unidirectional (&lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;block&lt;/code&gt;, and &lt;code&gt;inline&lt;/code&gt;). To bypass this I nested two scrollable containers to handle each axis. This is still the solution used for mobile, as it limits motion and avoids accidental horizontal motion while swiping up and down.&lt;/p&gt;

&lt;p&gt;Over halfway through the project, I ran into &lt;strong&gt;Bramus'&lt;/strong&gt; clever single-element bi-directional solution: comma-separated scroll timelines! It's obvious after you see it. I don't automatically think a new CSS property has comma support, so it didn't cross my mind.&lt;/p&gt;
&lt;h5&gt;
  
  
  Snapping back to center
&lt;/h5&gt;

&lt;p&gt;What differentiates this technique from a normal scroll-driven animation is two-fold: Purpose and Repeatability.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Purpose: we are not scrolling to animate the content but to detect the swipe. The DOM stays fixed, only &lt;code&gt;--x&lt;/code&gt; and &lt;code&gt;--y&lt;/code&gt; values update.&lt;/li&gt;
&lt;li&gt;Repeatability: by snapping back to center, we can repeat our action as much as we need. We wouldn't want a scroll box that always restarts, but we do want to be able to swipe again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1o44lgt6txnv6vep3bdl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1o44lgt6txnv6vep3bdl.gif" alt="Basic structure for swipe aware CSS" width="760" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;💡: &lt;strong&gt;[ Previous Art ]&lt;/strong&gt; &lt;a href="https://twitter.com/argyleink" rel="noopener noreferrer"&gt;Adam Argyle&lt;/a&gt; also mixed scroll animation + scroll snap to mimic a mobile's "refresh page" swipe interaction. &lt;a href="https://nerdy.dev/pull-to-refresh-prototype-with-scroll-snap-and-scroll-driven-animation" rel="noopener noreferrer"&gt;Article&lt;/a&gt; 👏&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;
  
  
  Main trick
&lt;/h5&gt;

&lt;p&gt;We use two separate scroll timelines to control changes in the horizontal and vertical axes. For the scroll-timelines to activate we need the content to be larger than the scroll wrapper – as you would expect. Depending on the goal of your game we can have any size grid (3x3 recommended). We can also decide on snapping or not snapping back to the center. For Desert Racer the jump axis (y-axis) snapped back to the ground level, but changing lanes didn't trigger a snap action. You can also use Houdini's @property declaration to achieve interpolated values between 0 and 1, thus making the swipe detectable to the smallest movement. With this, you can create motions such as drawing circles.&lt;/p&gt;

&lt;p&gt;Use the &lt;strong&gt;settings ⚙️ menu&lt;/strong&gt; to play with different swipe-aware configurations.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;📙: Settings menu was also written in pure CSS, because "why not?"&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  · 2.1.b.: Detecting collision &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;To detect collision I check if the vehicle's current cell is also an obstacle cell.&lt;br&gt;
&lt;code&gt;--collision-on-cell-4: calc(var(--vehicle-on-cell-4) * var(--obstacle-on-cell-4))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Broken into steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect what cell the vehicle is on by converting &lt;code&gt;--x&lt;/code&gt; and &lt;code&gt;--y&lt;/code&gt; swipe coordinates into their corresponding &lt;strong&gt;current&lt;/strong&gt; grid cell.
&lt;iframe height="600" src="https://codepen.io/warkentien2/embed/LYvZZWo?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;

&lt;code&gt;--cell-pattern-n&lt;/code&gt; represents &lt;code&gt;--vehicle-on-cell-n&lt;/code&gt;
&lt;strong&gt;E.g.:&lt;/strong&gt; if our swipe coordinates are &lt;code&gt;(-1, 0)&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;   &lt;span class="na"&gt;--vehicle-on-cell-4&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Place obstacles on the 3x3 grid.
&lt;strong&gt;E.g.:&lt;/strong&gt; for a tree on the left side of the road:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;   &lt;span class="na"&gt;--obstacle-on-cell-1&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="na"&gt;--obstacle-on-cell-4&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="na"&gt;--obstacle-on-cell-7&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Detect collision if the &lt;strong&gt;current&lt;/strong&gt; cell also has an obstacle.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;   &lt;span class="na"&gt;--collision-on-cell-4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;vehicle-on-cell-4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;obstacle-on-cell-4&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;em&gt;You can see this logic in motion on .gif below:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vgisz702a24hj9kjpsq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vgisz702a24hj9kjpsq.gif" alt="Collisions" width="760" height="411"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  · 2.1.c.: Animating a collision map over time &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Animate the value of &lt;code&gt;--obstacle-on-cell-k&lt;/code&gt;, where &lt;code&gt;1 ≤ k ≤ 9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To simplify my work, I declaratively generated the animation. &lt;small&gt;(With .SCSS)&lt;/small&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nv"&gt;$OBSTACLES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;(),&lt;/span&gt;
  &lt;span class="o"&gt;(),&lt;/span&gt;
  &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;"tree"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tree-arch"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
  &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;"tree-arch"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tree"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;2&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tree-arch"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
  &lt;span class="o"&gt;(),&lt;/span&gt;
  &lt;span class="o"&gt;(),&lt;/span&gt;
  &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;"arch"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"arch"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
  &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;"rock"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"rock"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
  &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;"arch"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"rock"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;2&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"arch"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
  &lt;span class="o"&gt;(),&lt;/span&gt;
  &lt;span class="o"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each item represents a keyframe.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can see obstacles animating across time on the .gif below:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhzl1xlejnmbn764d146.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhzl1xlejnmbn764d146.gif" alt="obstacle depth" width="760" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;📙: &lt;strong&gt;blue&lt;/strong&gt; overlay for obstacles, &lt;strong&gt;red&lt;/strong&gt; overlay for collisions&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;em&gt;📙: Transparent floor lets you see how I planned subterranean obstacles to block under passage&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  · 2.1.d.: Immediately stopping the game after any collision &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;I check each cell for a possible collision and store those results on &lt;code&gt;--collision-on-cell-k&lt;/code&gt;, where &lt;code&gt;1 ≤ k ≤ 9&lt;/code&gt;.&lt;br&gt;
If the sum of all possible collisions is greater than zero, we have a collision!&lt;/p&gt;

&lt;p&gt;Now the tricky part.&lt;/p&gt;

&lt;p&gt;As soon as the animation ticks to the next keyframe the collision drops. So, how do I keep the collision state? Remember that I can't control non-numerical CSS properties, so I can't simply set &lt;code&gt;animation-play-state: paused;&lt;/code&gt;. By changing the duration to &lt;code&gt;animation-duration: calc(var(--virtually-infinite) * 1s);&lt;/code&gt; I also change the progress of the current animation. (E.g.: If I'm at 50% and suddenly increase the animation duration by 10x, the total animation progress will drop to 5%).&lt;/p&gt;

&lt;p&gt;So what did I do?&lt;/p&gt;

&lt;p&gt;I immediately slid in the Game Over screen, and set the slide-out transition to 31.7 years! This means that, unless you plan on waiting it out, the Game Over state is perceivably static.&lt;/p&gt;

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

&lt;p&gt;Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;--virtually-infinite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000000000s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 31.7 years&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.game-over&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="no"&gt;black&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="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;zero-collisions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;200lvh&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="nb"&gt;bottom&lt;/span&gt; &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;zero-collisions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;virtually-infinite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1ms&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&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;On the split second that &lt;code&gt;--zero-collisions&lt;/code&gt; is 0, &lt;code&gt;bottom&lt;/code&gt; is set to &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;transition-duration&lt;/code&gt; to &lt;code&gt;1ms&lt;/code&gt;. The trapdoor has fallen. &lt;code&gt;--zero-collisions&lt;/code&gt; is once more set to 1, but it will take 31.7 to reset the trapdoor. If &lt;code&gt;--zero-collisions&lt;/code&gt; is set to 0 in the background due to a secondary collision, we won't notice since the trapdoor is already down.&lt;/p&gt;

&lt;h4&gt;
  
  
  · 2.1.e.: Detecting victory &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;This one was easy. I set &lt;code&gt;--you-win&lt;/code&gt; to true at the end of the round. The victory screen slides up – behind any possible Game over screens – and stays up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="nt"&gt;move-obstacles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="nt"&gt;99&lt;/span&gt;&lt;span class="nc"&gt;.999&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;--you-win&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="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;100&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;--you-win&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="c1"&gt;// last keyframe&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.victory&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;opacity&lt;/span&gt; &lt;span class="m"&gt;250ms&lt;/span&gt; &lt;span class="n"&gt;ease-out&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="nf"&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="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;you-win&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;200lvh&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="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.875&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;you-win&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;99&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;
  
  
  2.2.: Fine-tuning a swipe-first mobile UX &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  · 2.2.a.: Disabling native swipe-navigation &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;When you start creating a swipe-first mobile web experience, you quickly realize that the Browser already uses swipe-actions for things such as horizontal swipe native browser navigation, vertical pull-to-refresh gesture, toggling the address bar, and pinch-zooming.&lt;/p&gt;

&lt;p&gt;Here's how we block it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;The contain value disables native browser navigation, including the vertical pull-to-refresh gesture and horizontal swipe navigation.&lt;/em&gt;&lt;/small&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;overscroll-behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contain&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;h4&gt;
  
  
  · 2.2.b.: Fixing vertical swipe layout shift &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Vertical swipe toggles address bar visibility, which resizes the UI. &lt;br&gt;
Solution: Tie layout mechanics to the bottom of the viewport.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.container&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;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// or absolute;&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;100lvh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.game-view&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;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100svh&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;0&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;h4&gt;
  
  
  · 2.2.c.: Blocking pinch-zoom &lt;a&gt;&lt;/a&gt;
&lt;/h4&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;meta&lt;/span&gt;
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt;
  &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"&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 scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.view&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;touch-action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pan-x&lt;/span&gt; &lt;span class="n"&gt;pan-y&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;
  
  
  2.3.: Secondary obstacles &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  · 2.3.a.: How to handle huge CSS files &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;To load very large CSS files, I created an old-school MPA (multiple-page application). This is a fancy way of saying that I loaded a different CSS file per page, thus allowing me to add multiple phases to this game without bloating the CSS file. However, since I was hopping between pages I couldn't hold state with checkboxes. So I decided to hold state with the URL and the&lt;code&gt;:target&lt;/code&gt; selector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nn"&gt;#color-1&lt;/span&gt;&lt;span class="nd"&gt;:target&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;#color-1--lowres&lt;/span&gt;&lt;span class="nd"&gt;:target&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;#color-1--mouse&lt;/span&gt;&lt;span class="nd"&gt;:target&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;#color-1--lowres--mouse&lt;/span&gt;&lt;span class="nd"&gt;:target&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;#color-1--muted&lt;/span&gt;&lt;span class="nd"&gt;:target&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;#color-1--lowres--muted&lt;/span&gt;&lt;span class="nd"&gt;:target&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;#color-1--mouse--muted&lt;/span&gt;&lt;span class="nd"&gt;:target&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;#color-1--lowres--mouse--muted&lt;/span&gt;&lt;span class="nd"&gt;:target&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;--car-color&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="nc"&gt;.dynamic-link&lt;/span&gt;&lt;span class="nd"&gt;:nth-of-type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;1&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="n"&gt;inline-block&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;h4&gt;
  
  
  · 2.3.b.: How to animate as much as 126 obstacles in a single phase in 3D space &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;I don't, GPU doesn't handle animating 126 obstacles in 3D space, so it's just an illusion. All obstacles are hidden and fixed at a given distance, and only animate toward the screen on cue &lt;code&gt;animation-delay&lt;/code&gt;. This way we don't have more than a couple dozen obstacles animating in any given time.&lt;/p&gt;

&lt;h5&gt;
  
  
  other tricks
&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;will-animate&lt;/code&gt;, &lt;code&gt;contain: strict&lt;/code&gt;, &lt;code&gt;backface-visibility: hidden&lt;/code&gt; &lt;em&gt;(This goes beyond the scope of this article)&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  · 2.3.c.: How to auto-play sounds &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;It's as straightforward as placing &lt;code&gt;&amp;lt;audio autoplay&amp;gt;&lt;/code&gt; elements in your HTML documents and hiding them with CSS.&lt;br&gt;
For the &lt;strong&gt;muted&lt;/strong&gt; option, use a separate HTML document without &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
&lt;h4&gt;
  
  
  · 2.3.a.: How to persist state between page changes &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;By storing the car color and game config state on the URL and reading it with the &lt;code&gt;:target&lt;/code&gt; selector. However, for audio on and off I directly rendered a page with or without &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tags, since there is no way to turn audio on and off without JavaScript.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Lessons learned &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;It's incredible how much we can create with the current state of CSS math and CSS logic. &lt;a href="https://twitter.com/GrahamTheDev" rel="noopener noreferrer"&gt;GrahamTheDev&lt;/a&gt; is here to prove it!&lt;/p&gt;

&lt;p&gt;I'll leave a single &lt;strong&gt;Lesson Learned:&lt;/strong&gt; Keep CSS variables unit-free until the very end, which is until you have to use it. I'm not the first person to say this, but it's worth the emphasis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;complex-logic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1vw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Kudos &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I'd like to thank a few developers who indirectly contributed to this project by providing such quality educational content.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Kudos to &lt;a href="https://twitter.com/bramus" rel="noopener noreferrer"&gt;Bramus&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;for all the great tutorials on scroll-driven animations.&lt;/li&gt;
&lt;li&gt;for the clean bi-directional scroll-driven animation setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Kudos to &lt;a href="https://twitter.com/jamiecoulter89" rel="noopener noreferrer"&gt;Jamie Coulter&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;for raising the quality bar on CSS-only games.

&lt;ul&gt;
&lt;li&gt;You so masterfully showcased the power of checkboxes, that I purposefully refrained from using them&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Kudos to &lt;a href="https://twitter.com/Amys_Kapers" rel="noopener noreferrer"&gt;Amy Kapernick&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;for spreading the word about HTML state storing hack with &lt;code&gt;:target&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Car color and configuration options were stored in &lt;code&gt;:target&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Kudos to &lt;a href="https://twitter.com/KevinJPowell" rel="noopener noreferrer"&gt;Kevin Powell&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;for spreading the word about named grid lines.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The home page's highly dynamic Bento Style Grid wouldn't be possible without it!
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="highlight css"&gt;&lt;code&gt;   &lt;span class="c"&gt;/* 
       I pretty much only had to reset these two properties per media query. 
       7 grid sections styled across 11 @media definitions totaled 22 style declarations.
       The traditional grid-area approach could need as many as 77 style declarations.
       e.g.:
   */&lt;/span&gt;

   &lt;span class="nc"&gt;.bento-box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-start&lt;/span&gt; &lt;span class="n"&gt;display-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;display-end&lt;/span&gt; &lt;span class="n"&gt;share-start&lt;/span&gt; &lt;span class="n"&gt;actions-start&lt;/span&gt; &lt;span class="n"&gt;specs-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-end&lt;/span&gt; &lt;span class="n"&gt;share-end&lt;/span&gt; &lt;span class="n"&gt;config-start&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;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;config-end&lt;/span&gt; &lt;span class="n"&gt;actions-end&lt;/span&gt; &lt;span class="n"&gt;specs-end&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
     &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-start&lt;/span&gt; &lt;span class="n"&gt;config-start&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;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-end&lt;/span&gt; &lt;span class="n"&gt;display-start&lt;/span&gt; &lt;span class="n"&gt;share-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;0.75&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;config-end&lt;/span&gt; &lt;span class="n"&gt;share-end&lt;/span&gt; &lt;span class="n"&gt;actions-start&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;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;actions-end&lt;/span&gt; &lt;span class="n"&gt;specs-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;display-end&lt;/span&gt; &lt;span class="n"&gt;specs-end&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1300px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nc"&gt;.bento-box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-start&lt;/span&gt; &lt;span class="n"&gt;config-start&lt;/span&gt; &lt;span class="n"&gt;display-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;3.25&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;config-end&lt;/span&gt; &lt;span class="n"&gt;display-end&lt;/span&gt; &lt;span class="n"&gt;actions-start&lt;/span&gt; &lt;span class="n"&gt;specs-start&lt;/span&gt; &lt;span class="n"&gt;share-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;3.875&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-end&lt;/span&gt; &lt;span class="n"&gt;display-end&lt;/span&gt; &lt;span class="n"&gt;actions-end&lt;/span&gt; &lt;span class="n"&gt;specs-end&lt;/span&gt; &lt;span class="n"&gt;share-end&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
       &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;0.875&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-end&lt;/span&gt; &lt;span class="n"&gt;config-start&lt;/span&gt; &lt;span class="n"&gt;actions-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;0.625&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;config-end&lt;/span&gt; &lt;span class="n"&gt;display-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;0.125&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;actions-end&lt;/span&gt; &lt;span class="n"&gt;specs-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;specs-end&lt;/span&gt; &lt;span class="n"&gt;share-start&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;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;display-end&lt;/span&gt; &lt;span class="n"&gt;share-end&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nc"&gt;.bento-box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-start&lt;/span&gt; &lt;span class="n"&gt;config-start&lt;/span&gt; &lt;span class="n"&gt;display-start&lt;/span&gt; &lt;span class="n"&gt;specs-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;2.75&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;config-end&lt;/span&gt; &lt;span class="n"&gt;display-end&lt;/span&gt; &lt;span class="n"&gt;specs-end&lt;/span&gt; &lt;span class="n"&gt;actions-start&lt;/span&gt; &lt;span class="n"&gt;share-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-end&lt;/span&gt; &lt;span class="n"&gt;actions-end&lt;/span&gt; &lt;span class="n"&gt;share-end&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
       &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;1.125&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;header-end&lt;/span&gt; &lt;span class="n"&gt;config-start&lt;/span&gt; &lt;span class="n"&gt;actions-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;0.75&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;config-end&lt;/span&gt; &lt;span class="n"&gt;display-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;actions-end&lt;/span&gt; &lt;span class="n"&gt;share-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;display-end&lt;/span&gt; &lt;span class="n"&gt;specs-start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="m"&gt;2.5&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;share-end&lt;/span&gt; &lt;span class="n"&gt;specs-end&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="c"&gt;/* ... 8 other queries */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Kudos to &lt;strong&gt;LEGO® Friends Heartlake Rush&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;for the UI and gameplay inspiration!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  5. Credits &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Assets and UI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ground and Sky — CSS art — by &lt;strong&gt;warkentien2&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Irregular road — SVG designed in &lt;a href="https://figma.com/" rel="noopener noreferrer"&gt;Figma&lt;/a&gt; — by &lt;strong&gt;warkentien2&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Obstacles — by &lt;strong&gt;warkentien2&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;AI-generated obstacles — &lt;a href="https://www.aiartgenerator.cc/" rel="noopener noreferrer"&gt;AI Art Generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI background removal — &lt;a href="https://www.remove.bg/upload" rel="noopener noreferrer"&gt;remove.bg&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI image to pixel art conversion — &lt;a href="https://pixelied.com/" rel="noopener noreferrer"&gt;Pixelied&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sprite sheet generation - &lt;a href="https://pixlr.com/" rel="noopener noreferrer"&gt;Pixlr Express&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Vehicle — by &lt;strong&gt;warkentien2&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;AI-generated vehicle — &lt;a href="https://app.recraft.ai/" rel="noopener noreferrer"&gt;Recraft&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI background removal — &lt;a href="https://www.remove.bg/upload" rel="noopener noreferrer"&gt;remove.bg&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI image to pixel art conversion — &lt;a href="https://pixelied.com/" rel="noopener noreferrer"&gt;Pixelied&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drawing details and cropping — &lt;a href="https://www.pixilart.com/draw" rel="noopener noreferrer"&gt;PixilArt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Comping and enhancing assets — &lt;a href="https://pixlr.com/" rel="noopener noreferrer"&gt;Pixlr Express&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Color variations and sprite sheet generation — &lt;a href="https://pixlr.com/" rel="noopener noreferrer"&gt;Pixlr Express&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Dust adapted from &lt;strong&gt;Esteban Díaz's&lt;/strong&gt; &lt;a href="https://www.youtube.com/watch?v=FdEX-4lZsnk" rel="noopener noreferrer"&gt;Youtube channel&lt;/a&gt;
&lt;/li&gt;

&lt;li&gt;Image Landscape — by &lt;strong&gt;warkentien2&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;AI-generated scenery — &lt;a href="https://app.recraft.ai/" rel="noopener noreferrer"&gt;Recraft&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;First sketch
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1j889c30w7s8gjg47ot.png" alt="Desert Racer UI mockup on paper" width="800" height="590"&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sound snippets and soundtrack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Home — revving — by &lt;strong&gt;warkentien2&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;All phases — driving noises — by &lt;strong&gt;warkentien2&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Phase 1 — Dark Country Rock — by &lt;a href="https://pixabay.com/users/moodmode-33139253/" rel="noopener noreferrer"&gt;moodmode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Phase 2 — Western Cowboy — by &lt;a href="https://pixabay.com/users/music_for_videos-26992513/" rel="noopener noreferrer"&gt;Music_For_Videos&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Phase 3 — Tumbleweed Tango — by &lt;a href="https://pixabay.com/users/moodmode-33139253/" rel="noopener noreferrer"&gt;moodmode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Phase 4 — Excess Voltage — by &lt;a href="https://pixabay.com/users/moodmode-33139253/" rel="noopener noreferrer"&gt;moodmode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Phase 5 — Spirit of the Road — by &lt;a href="https://pixabay.com/users/sergepavkinmusic-6130722/" rel="noopener noreferrer"&gt;SergePavkinMusic&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Phase X — Cowboy Redemption — by &lt;a href="https://pixabay.com/users/music_unlimited-27600023/" rel="noopener noreferrer"&gt;Music_Unlimited&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. FAQ &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://000699350.deployed.codepen.website/" rel="noopener noreferrer"&gt;Desert Racer's F.A.Q.&lt;/a&gt; section at the bottom of the homepage.&lt;/p&gt;

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

&lt;p&gt;Thank you for reading!&lt;/p&gt;

&lt;p&gt;You're welcome to ask questions and speak your mind.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;strong&gt;X&lt;/strong&gt;, &lt;a href="https://twitter.com/warkentien2" rel="noopener noreferrer"&gt;@warkentien2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>frontend</category>
      <category>html</category>
    </item>
    <item>
      <title>Curious Geckos 🦎🦎🦎: The Most Precise CSS-only Position-Aware Mini-Game!</title>
      <dc:creator>Philip Warkentien II</dc:creator>
      <pubDate>Wed, 27 Mar 2024 13:15:00 +0000</pubDate>
      <link>https://forem.com/warkentien2/curious-geckos-the-most-precise-css-only-position-aware-mini-game-16i2</link>
      <guid>https://forem.com/warkentien2/curious-geckos-the-most-precise-css-only-position-aware-mini-game-16i2</guid>
      <description>&lt;h4&gt;
  
  
  A &lt;strong&gt;CSS-only,&lt;/strong&gt; no JS, no checkbox, &lt;strong&gt;position-aware&lt;/strong&gt; (hover/touch aware) interactive gecko-luring pastime!
&lt;/h4&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; I revisited an old CSS-only hack with new CSS features and some ingenuity to bypass rendering limitations and achieve as much as a &lt;strong&gt;25.6x precision gain&lt;/strong&gt; over the best previous solution – getting much closer to native position awareness! Try out the &lt;a href="https://codepen.io/warkentien2/full/jOJJwEp" rel="noopener noreferrer"&gt;mini-game&lt;/a&gt;, play with just the &lt;a href="https://codepen.io/warkentien2/pen/oNmwLdy" rel="noopener noreferrer"&gt;“debounced CSS algorithm,”&lt;/a&gt; and if you are in a rush, start reading from Section 2.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;We're gathered here today to gain &lt;strong&gt;awareness&lt;/strong&gt; about an ingenious CSS trick I call &lt;strong&gt;CSS-only Position Awareness.&lt;/strong&gt; We could also call it Position-Aware CSS, but it fails to unambiguously state that no JavaScript was used.&lt;/p&gt;

&lt;p&gt;It's based on a known trick, but, as you'll soon find out, CSS &lt;code&gt;:has&lt;/code&gt; enabled us to push it further. And so we did. Besides, &lt;strong&gt;AI&lt;/strong&gt; still has no clue about it and maybe neither do you. Here's what &lt;strong&gt;ChatGPT&lt;/strong&gt; had to say this morning – March 27th, 2024:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwk4dmto3iudmam75zfsv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwk4dmto3iudmam75zfsv.png" alt="Screenshot of ChatGPT, see caption below" width="800" height="246"&gt;&lt;/a&gt;&lt;br&gt;transcription:&lt;br&gt;&lt;br&gt;
   👨 — Is it possible to achieve position-awareness with only CSS and HTML?"&lt;br&gt;🤖 — &lt;b&gt;"No, it's not possible&lt;/b&gt; to achieve position-awareness using only CSS and HTML. JavaScript is necessary to interact with the mouse events and update CSS properties or HTML content based on the mouse's position."
  &lt;/p&gt;

&lt;p&gt;This article will be &lt;strong&gt;interactive&lt;/strong&gt; and hopefully inspiring. So, stick around! &lt;em&gt;(Or at least scroll to see the &lt;strong&gt;cute photos&lt;/strong&gt;)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Idea&lt;/li&gt;
&lt;li&gt;Position Awareness&lt;/li&gt;
&lt;li&gt;Building a Position-Aware Game&lt;/li&gt;
&lt;li&gt;Cast and Credits&lt;/li&gt;
&lt;li&gt;Previous Art&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. The idea &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Back in &lt;strong&gt;September 2023,&lt;/strong&gt; I was custom styling a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; component and ran into a fun use case for the &lt;code&gt;:has()&lt;/code&gt; pseudo-class. Without resorting to a "CSS-grid table," but using the actual – correct and accessible – &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; markup, I could now highlight not only the row but also the column being hovered!&lt;/p&gt;

&lt;p&gt;See it in action:&lt;br&gt;
&lt;iframe height="600" src="https://codepen.io/warkentien2/embed/ExGZLLe?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;💡: &lt;strong&gt;[ Fun fact ]&lt;/strong&gt; &lt;a href="https://twitter.com/netsi1964" rel="noopener noreferrer"&gt;Sten Hougaard&lt;/a&gt; discovered this same trick a month later and got &lt;a href="https://www.instagram.com/p/C0fp9WhNvM7/" rel="noopener noreferrer"&gt;featured&lt;/a&gt; on CodePen's Instagram. 👏&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What stood out was&lt;/strong&gt; how little CSS I needed to write. All it took was 37 lines of pure CSS to highlight a 7-column table by any given number of rows. What if I needed more columns? Could I refactor this CSS algorithm to support any number of columns? While digging for this answer &lt;strong&gt;I stumbled upon something else,&lt;/strong&gt; a better algorithm for &lt;strong&gt;CSS-only Position Awareness.&lt;/strong&gt; And that's where the game comes in.&lt;/p&gt;

&lt;p&gt;Before we get all scientific, &lt;strong&gt;let's play&lt;/strong&gt; the mini-game!&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://codepen.io/warkentien2/full/jOJJwEp" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3un03r334rd05w9a6jq6.png" alt="Click to play Curious Geckos" width="603" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;📙: Supported on all devices and modern browsers. For the best mouse-tracking UX try Chrome Desktop (or Edge Desktop)&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was all achieved without a single line of JavaScript! You're welcome to double-check and disable JavaScript on your browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Position Awareness &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A code is &lt;strong&gt;position-aware&lt;/strong&gt; when we can pinpoint the &lt;code&gt;--x&lt;/code&gt; and &lt;code&gt;--y&lt;/code&gt; coordinates of a cursor on the screen and make these values available for any element on the page. To achieve this without JavaScript requires a position grid to detect which subsection of the screen is being hovered. Ideally, we would have a position cell per pixel, but we aren't there yet. We might, however, be just a Moore's Law cycle away.&lt;/p&gt;

&lt;p&gt;Previous iterations of the Position Grid were modest, only tracking 4 quadrants and calling it &lt;strong&gt;Direction Awareness.&lt;/strong&gt; It evolved into &lt;strong&gt;10x10&lt;/strong&gt; grids (100 cells) called &lt;strong&gt;Mouse Tracking,&lt;/strong&gt; some as large as &lt;strong&gt;16x16&lt;/strong&gt;. However, Mouse Tracking was not the best name for something that should include track pads and touch devices. Now it evolves once more, with the addition of &lt;code&gt;:has&lt;/code&gt; and dynamic rendering. I present to you the &lt;strong&gt;Position Awareness&lt;/strong&gt; grid, with &lt;strong&gt;81x81&lt;/strong&gt;, &lt;strong&gt;6561 cells&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;There's a catch to Position Awareness: we change the expected behavior of hovering on a page. Instead of tracking &lt;code&gt;:hover&lt;/code&gt; interactions with DOM elements, we are tracking interactions with subsections of the screen.&lt;/p&gt;

&lt;p&gt;Here's a “debounced” Position-Aware CSS algorithm in action:&lt;br&gt;
&lt;iframe height="600" src="https://codepen.io/warkentien2/embed/oNmwLdy?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;🖱️💻 : Intended for mouse and trackpad devices.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;em&gt;📙: 81x81 grid for Desktop, 27x27 grid mobile&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;h3&gt;
  
  
  2.a.: Precision Gains &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Previous approach&lt;/strong&gt; was achieved by placing non-nested cells before a target element, and transferring placement coordinates through the “any sibling ahead” selector &lt;code&gt;.cell:hover ~ .position-aware-element&lt;/code&gt;. This overloads the wrapper component with a huge number of child nodes and can impact performance.&lt;/p&gt;

&lt;p&gt;Here's a brute-force image generated by painting each pixel with CSS to illustrate what happens when we style too many elements. &lt;strong&gt;Hit &lt;a href="https://codepen.io/warkentien2/pen/ZEMKdpq" rel="noopener noreferrer"&gt;play&lt;/a&gt; at your peril 😱.&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkubaxyl9s2sx6orla4yt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkubaxyl9s2sx6orla4yt.png" alt="Brute force image generated with CSS, painting a pixel grid with CSS" width="645" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current approach&lt;/strong&gt; takes advantage of the &lt;code&gt;:has()&lt;/code&gt; pseudo-class which supports nested position cells. By nesting a single level deep we reduce the max number &lt;strong&gt;N&lt;/strong&gt; of children cells to the &lt;strong&gt;square root of N&lt;/strong&gt;. E.g.: a previous shallow position grid with 100 children cells can now be represented with 10 children, each with 10 children. This way, every single cell has only at most 10 children. Imagine if we nest even deeper! This two-level approach hits a performance cap at around &lt;strong&gt;33x33&lt;/strong&gt; (1089 points of awareness). This is the resolution I'm using for &lt;strong&gt;Curious Geckos.&lt;/strong&gt; Beyond that limit I would need to create dynamically rendered grid subsections, like I showcased on the blue Position-Aware CSS example above. This is similar to how 3D game engines dynamically increase the level of detail on elements near the camera.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;📙: More on this in a future article&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  3. Building a Position-Aware Game &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Now that we have a Position-Aware Grid filled with &lt;code&gt;(x, y)&lt;/code&gt; coordinates, how do we go from &lt;code&gt;--x&lt;/code&gt; and &lt;code&gt;--y&lt;/code&gt; values to a working mini-game?&lt;/p&gt;

&lt;p&gt;Here's the trimmed version of the steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.1.:&lt;/strong&gt; Feed &lt;code&gt;--x&lt;/code&gt; and &lt;code&gt;--y&lt;/code&gt; coordinates into the 🪰(fly) and 🦎(gecko) wrappers&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@for&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="ow"&gt;from&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;through&lt;/span&gt; &lt;span class="m"&gt;33&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nc"&gt;.position-aware-container&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.fly__wrapper&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.gecko__placement&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;--y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$axis-step&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.position-aware-container&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.fly__wrapper&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.gecko__placement&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;--x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$axis-step&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.2.:&lt;/strong&gt; Use CSS &lt;code&gt;calc&lt;/code&gt; to rotate the 🪰(fly) by a centrifugal angle&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tv8qqhg4z4w2epq8pl5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tv8qqhg4z4w2epq8pl5.jpg" alt="angles" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.3.:&lt;/strong&gt; Create SVG puppets for the geckos:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fim54e3wfpw8c20bkyxw9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fim54e3wfpw8c20bkyxw9.png" alt="SVG Puppet" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;strong&gt;· 3.3.a.:&lt;/strong&gt;&lt;/small&gt; A Puppet, also known as a Rig Model, is a technique used for animation. You might have seen it before on my &lt;a href="https://warkentien2.com/" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkx1u5trg8e7w3x5y9h9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkx1u5trg8e7w3x5y9h9.gif" alt="Tarantula" width="760" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;📙: More on this in a future article&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;small&gt;&lt;strong&gt;· 3.3.b.:&lt;/strong&gt;&lt;/small&gt; Group each body part in a hierarchical structure. &lt;strong&gt;E.g.:&lt;/strong&gt; If I rotate the shoulder, the entire arm rotates.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;strong&gt;· 3.3.c.:&lt;/strong&gt;&lt;/small&gt; Anchor geckos by their thorax to facilitate head angle calculation:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qn81qpizi31aect5nb8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qn81qpizi31aect5nb8.png" alt="Trigonometry diagram showcasing how the Gecko's head rotation angle is being calculated" width="800" height="547"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="na"&gt;--head-angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;min-head-angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="n"&gt;CCW&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="n"&gt;rotation&lt;/span&gt;
   &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;max-head-angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="n"&gt;CW&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="n"&gt;rotation&lt;/span&gt;
      &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;head-angle-intensity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
               &lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;head-initial-rotation&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                     &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;head-angle-intensity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                     &lt;span class="m"&gt;1&lt;/span&gt;
                     &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1deg&lt;/span&gt;
               &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;head-angle-correction&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="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;small&gt;&lt;strong&gt;· 3.3.d.:&lt;/strong&gt;&lt;/small&gt; Split the head rotation in three for a more natural motion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.gecko__neck__body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;--rotate-member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;head-angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.gecko__neck__head&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;--rotate-member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;head-angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.gecko__head&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;--rotate-member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;head-angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2&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;3.4.:&lt;/strong&gt; Set attack zones per gecko and per &lt;a class="mentioned-user" href="https://dev.to/media"&gt;@media&lt;/a&gt; query.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht3aywuimhcjsua40xm1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht3aywuimhcjsua40xm1.png" alt="Attack zones: position grid cells that trigger the gecko's attack animation when hovered" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.5.:&lt;/strong&gt; Detect attack zone &lt;code&gt;:hover&lt;/code&gt; and trigger attack state&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ----- Gecko 1 attacks ----- //&lt;/span&gt;
&lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_base&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_min200&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_min250&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_min300&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_min350&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_min450&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_max166&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_max150&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_max125&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_max100&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_max75&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_max66&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
   &lt;span class="nc"&gt;.gecko-trap-1_max50&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Expand the hovered cell to full-screen. This locks hover to the last hovered cell. Locking the attack state.&lt;/li&gt;
&lt;li&gt;Attack state freezes fly movement and animates the desired gecko.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3.6.:&lt;/strong&gt; Slide in a pulsating call-to-action with a higher &lt;code&gt;z-index&lt;/code&gt;, this will trigger the restart state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.7.:&lt;/strong&gt; Add some flourishes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tail animations&lt;/li&gt;
&lt;li&gt;fine-tuned gecko placements to best fit the aspect ratio&lt;/li&gt;
&lt;li&gt;Black and White gecko's resting arm changes position based on the &lt;a class="mentioned-user" href="https://dev.to/media"&gt;@media&lt;/a&gt; query for best composition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3.8.:&lt;/strong&gt; You're done!&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Cast and Credits &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcs2cxkmkg3ikbtje0eiy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcs2cxkmkg3ikbtje0eiy.png" alt="cast" width="800" height="655"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
      &lt;pre&gt;&lt;br&gt;
      Super Hypo Tangerine Leopard Gecko - by &lt;a href="https://www.deviantart.com/geekusmaximus/art/Leopard-Gecko-2-107247050" rel="noopener noreferrer"&gt;Tamara Locke&lt;/a&gt;&lt;br&gt;
      Oreo Whiteout African Fat-Tailed Gecko — by unknown&lt;br&gt;
      Mandarin Tangerine Leopard Gecko — by &lt;a href="https://www.cbreptile.com/wp-content/uploads/2018/09/mandarin-leopard-gecko-for-sale.jpg" rel="noopener noreferrer"&gt;CB Reptile&lt;/a&gt;&lt;br&gt;
      &lt;/pre&gt;
&lt;br&gt;
   
   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Geckos — SVGs designed in Figma — by &lt;strong&gt;warkentien2&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Scales by &lt;a href="https://pattern.monster/leaves-4/" rel="noopener noreferrer"&gt;pattern.monster&lt;/a&gt;, thanks &lt;a href="https://twitter.com/MrNaveenCS" rel="noopener noreferrer"&gt;Naveen CS&lt;/a&gt;!&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Fly - &lt;a href="https://codepen.io/warkentien2/pen/abPGgyZ" rel="noopener noreferrer"&gt;CSS art&lt;/a&gt; – by &lt;strong&gt;warkentien2&lt;/strong&gt;
&lt;/li&gt;

&lt;li&gt;First sketch &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0mjrhcdj9rnqublo01u4.jpeg" alt="Curious Geckos mockup on paper" width="800" height="511"&gt;
&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Previous art &lt;small&gt;(Post-project research)&lt;/small&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Although many developers have tackled some form of Position Awareness, I would like to highlight a few:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/FWeinb" rel="noopener noreferrer"&gt;Fabrice Weinberg&lt;/a&gt;, June 2013: &lt;strong&gt;2x2&lt;/strong&gt; (first ever?) Direction-Aware Grid &lt;a href="https://codepen.io/FWeinb/pen/nxdYZN" rel="noopener noreferrer"&gt;(Code)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/gabriewee" rel="noopener noreferrer"&gt;Gabrielle Wee&lt;/a&gt;, Jan 2017: 4 cells. Beautiful direction-aware effect. &lt;a href="https://codepen.io/gabriellewee/pen/Qdpgwx" rel="noopener noreferrer"&gt;(Code)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gabrielle Wee, April 2018: &lt;strong&gt;10x10&lt;/strong&gt; grid with 100 individual &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tags and 100 individual style definitions. &lt;a href="https://codepen.io/gabriellewee/pen/mLyaZL" rel="noopener noreferrer"&gt;(Code)&lt;/a&gt;
&amp;gt; "It would be smoother with more links but also would take longer to load, so I only used 100 instead of something like 1000."
&amp;gt; – Gabrielle Wee's notes on this Pen&lt;/li&gt;
&lt;li&gt;Christopher Joshua, Nov 2019: &lt;strong&gt;16x16&lt;/strong&gt; grid with 256 individual &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; tags and 768 lines of CSS just for tracking. &lt;a href="https://codepen.io/xiell/pen/JjjxoQz" rel="noopener noreferrer"&gt;(Code)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honorable Mention &lt;sup&gt;(1)&lt;/sup&gt;&lt;/strong&gt; &lt;a href="https://twitter.com/Jane0ri" rel="noopener noreferrer"&gt;Jane Ori&lt;/a&gt;'s amazing etch-a-sketch with an impressive &lt;strong&gt;75x50&lt;/strong&gt; grid! &lt;a href="https://codepen.io/propjockey/pen/mdBvjgg" rel="noopener noreferrer"&gt;(Code)&lt;/a&gt;
Genius state tracking with the &lt;a href="https://lea.verou.me/blog/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/" rel="noopener noreferrer"&gt;space toggle hack&lt;/a&gt; + animations.
   ______________________________________   

&lt;strong&gt;(1): Honorable mention&lt;/strong&gt; since it achieves Position Tracking but not Position Awareness.
&lt;small&gt;&lt;strong&gt;· 5.a.:&lt;/strong&gt;&lt;/small&gt; Position information is dynamically set, not extracted.
&lt;small&gt;&lt;strong&gt;· 5.b.:&lt;/strong&gt;&lt;/small&gt; Cell can't be hovered twice.
&lt;small&gt;&lt;strong&gt;· 5.c.:&lt;/strong&gt;&lt;/small&gt; Regardless, it's impressive and deserves to be shared!&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  6. FAQ &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Is this really a game?
Well, it's as much of a game as playing catch is a game. ⚾ It's the reason I called it a mini-game &lt;small&gt;⚾&lt;/small&gt; or a pastime.&lt;/li&gt;
&lt;li&gt;Why make this?
To Flex... and to Grid.&lt;/li&gt;
&lt;li&gt;What do you mean by “no checkbox”?

&lt;ul&gt;
&lt;li&gt;Most CSS-only games use checkbox for state management, but not this one.&lt;/li&gt;
&lt;li&gt;This game runs completely on &lt;code&gt;:hover&lt;/code&gt; events, even to track victory/restart.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UX is jittery, can I improve it?
&lt;strong&gt;Troubleshooting:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Try Chrome Desktop or Edge Desktop for the best UX.

&lt;ul&gt;
&lt;li&gt;If you're a MacBook user, you can emulate Chrome inside Safari:
&lt;code&gt;Safari &amp;gt; Develop (tab) &amp;gt; Open Page With &amp;gt; Google Chrome&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use a &lt;a href="https://chromewebstore.google.com/detail/quick-javascript-switcher/geddoclleiomckbhadiaipdggiiccfje" rel="noopener noreferrer"&gt;JS disabler (extension)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If it's still slow, use Firefox or Safari Desktop for a debounced hover tracking experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Thank you for reading!&lt;/p&gt;

&lt;p&gt;You're welcome to ask questions and speak your mind.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;strong&gt;X&lt;/strong&gt;, &lt;a href="https://twitter.com/warkentien2" rel="noopener noreferrer"&gt;@warkentien2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>frontend</category>
      <category>html</category>
    </item>
  </channel>
</rss>
