<?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: lichongyang130</title>
    <description>The latest articles on DEV Community by lichongyang130 (@lichongyang130).</description>
    <link>https://dev.to/lichongyang130</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%2F4134662%2F2b6a3e30-f474-4991-9151-1f7997c3c402.png</url>
      <title>DEV Community: lichongyang130</title>
      <link>https://dev.to/lichongyang130</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lichongyang130"/>
    <language>en</language>
    <item>
      <title>Your hero background is a JPEG. It could be zero kilobytes.</title>
      <dc:creator>lichongyang130</dc:creator>
      <pubDate>Thu, 24 Sep 2026 06:19:53 +0000</pubDate>
      <link>https://dev.to/lichongyang130/your-hero-background-is-a-jpeg-it-could-be-zero-kilobytes-kda</link>
      <guid>https://dev.to/lichongyang130/your-hero-background-is-a-jpeg-it-could-be-zero-kilobytes-kda</guid>
      <description>&lt;p&gt;Your hero background is a JPEG. It could be zero kilobytes.&lt;/p&gt;

&lt;p&gt;I audited a landing page last month and the heaviest thing on it was not the framework, not the fonts, not the analytics script. It was a 412 KB hero image.&lt;/p&gt;

&lt;p&gt;The image was a gradient. A soft purple-to-teal wash with a bit of grain. Someone had built it in a design tool and exported it as a JPEG, because that is how we have always done it.&lt;/p&gt;

&lt;p&gt;It was also the LCP element. So the single biggest number on the performance report was a decoration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cost of a background image
&lt;/h2&gt;

&lt;p&gt;A background image is never just a background image. Count what it actually costs you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One network request&lt;/strong&gt; on the critical path, before the hero can render.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple exports&lt;/strong&gt; if you support dark mode, or a second brand colour, or a seasonal campaign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple resolutions&lt;/strong&gt; if you care about retina. A 1x and a 2x, at minimum.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A build step&lt;/strong&gt; nobody remembers to re-run when the brand colour shifts two degrees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An LCP penalty&lt;/strong&gt;, because you cannot lazy-load the thing above the fold.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five costs, zero of which are the gradient itself. The gradient is trivial. Everything around it is the tax.&lt;/p&gt;

&lt;h2&gt;
  
  
  What CSS gives you for free
&lt;/h2&gt;

&lt;p&gt;Here is an aurora wash in pure CSS. No image, no request, no export step:&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;.aurora&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;60%&lt;/span&gt; &lt;span class="m"&gt;80%&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;20%&lt;/span&gt; &lt;span class="m"&gt;10%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oklch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;72%&lt;/span&gt; &lt;span class="m"&gt;0.18&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;70%&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;70%&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;80%&lt;/span&gt; &lt;span class="m"&gt;20%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oklch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;70%&lt;/span&gt; &lt;span class="m"&gt;0.16&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;70%&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;70%&lt;/span&gt; &lt;span class="m"&gt;60%&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;90%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oklch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;78%&lt;/span&gt; &lt;span class="m"&gt;0.14&lt;/span&gt; &lt;span class="m"&gt;160&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;0.40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;70%&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="m"&gt;#0b0b12&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 is it. Four layers, a few hundred bytes, and it scales to any viewport without a second export. Change &lt;code&gt;300&lt;/code&gt; to &lt;code&gt;320&lt;/code&gt; and you have a new brand direction — in one commit, not one afternoon.&lt;/p&gt;

&lt;p&gt;The grain is the part people assume needs an image. It does not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.grain&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;""&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="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.28&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;soft-light&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;"data:image/svg+xml,\
&amp;lt;svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'&amp;gt;\
&amp;lt;filter id='n'&amp;gt;&amp;lt;feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/&amp;gt;&amp;lt;/filter&amp;gt;\
&amp;lt;rect width='140' height='140' filter='url(%23n)'/&amp;gt;&amp;lt;/svg&amp;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;An inline SVG turbulence filter, URL-encoded into the stylesheet. It is a data URI, so it ships inside the CSS bundle — no extra round trip. The browser generates the noise procedurally.&lt;/p&gt;

&lt;p&gt;And glass, which is the one everybody reaches for a PNG to fake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.glass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;color-mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;oklab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt; &lt;span class="m"&gt;12%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;backdrop-filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;18px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;saturate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;140%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;color-mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;oklab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt; &lt;span class="m"&gt;22%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;backdrop-filter&lt;/code&gt; is genuinely doing the work here — it samples what is behind the element. A blurred PNG of a blurred background is a snapshot of a moment that stops being true the second anything moves behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part nobody warns you about
&lt;/h2&gt;

&lt;p&gt;CSS backgrounds are cheap, not free. &lt;code&gt;backdrop-filter&lt;/code&gt; and large animated gradients both cost GPU time, and on a low-end Android device a full-viewport animated blur can turn a smooth 60 fps into a stutter.&lt;/p&gt;

&lt;p&gt;So the useful mental model is not "CSS good, images bad." It is &lt;strong&gt;tiering&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low tier&lt;/strong&gt; — static CSS gradients, SVG turbulence, solid fills. Safe everywhere, including old devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mid tier&lt;/strong&gt; — animated gradients, &lt;code&gt;backdrop-filter&lt;/code&gt;, &lt;code&gt;mix-blend-mode&lt;/code&gt;. Fine on modern hardware, measurable cost on cheap hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High tier&lt;/strong&gt; — WebGL / canvas particle fields. Beautiful, and the first thing that should degrade.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tier is a property of the effect, and it should be documented next to the effect. That is exactly how the background section on &lt;a href="https://www.56juqingba.com" rel="noopener noreferrer"&gt;Motif UI&lt;/a&gt; is organised — each background is labelled with its implementation (&lt;code&gt;Pure CSS&lt;/code&gt;, &lt;code&gt;CSS / SVG&lt;/code&gt;, &lt;code&gt;CSS / WebGL&lt;/code&gt;) and its performance tier, so you can pick with your eyes open instead of discovering the cost in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Degrading well
&lt;/h2&gt;

&lt;p&gt;Once you accept tiers, the fallback writes itself. Keep the static gradient as the base layer, and only add the expensive layer when the device can take it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.motes&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="c"&gt;/* static gradient base */&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;no-preference&lt;/span&gt;&lt;span class="p"&gt;)&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;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.motes&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;drift&lt;/span&gt; &lt;span class="m"&gt;24s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="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;Two guards, both free. &lt;code&gt;prefers-reduced-motion&lt;/code&gt; handles the users who asked for stillness. The width check is a crude but effective proxy for "probably not a 2016 budget phone."&lt;/p&gt;

&lt;p&gt;For the genuinely low-end case, a &lt;code&gt;@supports&lt;/code&gt; check on &lt;code&gt;backdrop-filter&lt;/code&gt; keeps you honest:&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;@supports&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backdrop-filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.glass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;0.92&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;A near-opaque panel instead of glass. Slightly less pretty, completely legible, and it never causes a repaint storm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two accessibility details that bite
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mark decoration as decoration.&lt;/strong&gt; A purely visual background layer should be &lt;code&gt;aria-hidden="true"&lt;/code&gt; if it is an element, and must never be the only carrier of meaning. If your gradient encodes a state — red for error, green for success — that is a text problem, not a background problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check contrast against the worst case, not the average.&lt;/strong&gt; A gradient means the contrast ratio varies across the panel. Measure your text against the lightest and darkest points the text actually sits on. A wash that passes at the top can fail badly at the bottom corner where the third radial layer lands.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do this week
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open DevTools, filter the network panel by &lt;code&gt;Img&lt;/code&gt;, and find the largest image above the fold.&lt;/li&gt;
&lt;li&gt;Ask honestly: is this a photograph, or is it a gradient / noise / blur?&lt;/li&gt;
&lt;li&gt;If it is the latter, rebuild it as CSS. Keep the image as a &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; fallback only if you genuinely need one.&lt;/li&gt;
&lt;li&gt;Write the tier next to the effect in a comment. Future-you will not remember.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The gradient was never the expensive part. The pipeline around it was.&lt;/p&gt;

&lt;p&gt;I keep the ones I actually ship — with their tier labels and audit notes attached — over at &lt;a href="https://www.56juqingba.com" rel="noopener noreferrer"&gt;Motif UI&lt;/a&gt;, if you would rather start from something measured than from a blank stylesheet.&lt;/p&gt;

</description>
      <category>css</category>
      <category>performance</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Your animations are fine. Your prefers-reduced-motion handling probably isn't.</title>
      <dc:creator>lichongyang130</dc:creator>
      <pubDate>Tue, 22 Sep 2026 08:08:03 +0000</pubDate>
      <link>https://dev.to/lichongyang130/your-animations-are-fine-your-prefers-reduced-motion-handling-probably-isnt-2a2</link>
      <guid>https://dev.to/lichongyang130/your-animations-are-fine-your-prefers-reduced-motion-handling-probably-isnt-2a2</guid>
      <description>&lt;p&gt;Your animations are fine. Your &lt;code&gt;prefers-reduced-motion&lt;/code&gt; handling probably isn't.&lt;/p&gt;

&lt;p&gt;Here's a trap I fell into for years: I did add a reduced-motion block. It was right there at the bottom of my global CSS, three lines long, and I felt good about it. It looked like this:&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;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&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="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&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;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This passes an automated a11y audit. It also breaks your product for the exact people it was written for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the nuclear option backfires
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;animation: none&lt;/code&gt; doesn't "reduce" anything. It deletes the motion — including the motion that carries meaning. Kill that and you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drawers and modals that teleport.&lt;/strong&gt; A slide-in panel that used to take 240ms now appears instantly in its final position. There's no longer any spatial relationship between where it was and where it went.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus loss on scroll-triggered content.&lt;/strong&gt; A section that faded in on scroll now just doesn't render its transition state — and depending on how you built it, sometimes doesn't render at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skeletons that lie.&lt;/strong&gt; A pulsing loading skeleton frozen mid-opacity can read as "finished, empty" instead of "still loading."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reduced motion is not "no motion." It's &lt;strong&gt;less vestibular-triggering motion&lt;/strong&gt;. The WCAG target is animation that doesn't involve large-scale movement, parallax, or scaling — not the absence of all feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do instead
&lt;/h2&gt;

&lt;p&gt;The rule I've landed on: &lt;strong&gt;replace, don't remove.&lt;/strong&gt; Four patterns cover almost everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Swap a transform for a cross-fade.&lt;/strong&gt; Movement is the trigger, opacity isn't. So a slide becomes a fade at the same duration.&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;.panel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;240ms&lt;/span&gt; &lt;span class="n"&gt;ease&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;240ms&lt;/span&gt; &lt;span class="n"&gt;ease&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.panel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="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;240ms&lt;/span&gt; &lt;span class="n"&gt;linear&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;2. Keep the easing, cap the travel.&lt;/strong&gt; A 16° tilt or a 400px parallax is the problem. 2° of tilt still reads as "interactive" and triggers nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Animate a non-spatial property.&lt;/strong&gt; Progress is the useful half of a loading animation. Drive it with &lt;code&gt;width&lt;/code&gt;, a background gradient position, or a text counter instead of a spinning element.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Keep state changes, shorten them.&lt;/strong&gt; A 40ms opacity flip is not a vestibular trigger. It's still enough to tell the user "this changed because of you."&lt;/p&gt;

&lt;p&gt;And the part people forget: &lt;strong&gt;reduced motion is also about scroll and pointer.&lt;/strong&gt; Smooth-scroll libraries, scroll-jacking, magnetic cursor effects, and auto-playing background loops all need the same treatment as your CSS transitions. Your &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; scroll-behavior counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The testing gap
&lt;/h2&gt;

&lt;p&gt;Here's the honest part: most of this passes automated checks. A script can verify that a reduced-motion media query exists. It cannot tell you whether your modal now teleports.&lt;/p&gt;

&lt;p&gt;The only way I've found to actually catch it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Toggle the OS setting for real — macOS &lt;em&gt;Reduce motion&lt;/em&gt;, Windows &lt;em&gt;Show animations&lt;/em&gt; off, or Firefox's &lt;code&gt;ui.prefersReducedMotion&lt;/code&gt; flag if you want to avoid changing your desktop.&lt;/li&gt;
&lt;li&gt;Walk your five most important flows with it on. Open the modal. Submit the form. Scroll the pricing page.&lt;/li&gt;
&lt;li&gt;Ask one question per interaction: &lt;strong&gt;can I still tell what happened?&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the answer is no, you didn't reduce the motion. You removed the information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Receipts over vibes
&lt;/h2&gt;

&lt;p&gt;The reason I care about this beyond principle: motion components are the ones people paste in blind. You grab a tilt card or an aurora background, it looks great, and nobody checks what it does under reduced-motion until an audit or a complaint.&lt;/p&gt;

&lt;p&gt;That's a fixable information problem. Every animated asset on &lt;a href="https://www.56juqingba.com" rel="noopener noreferrer"&gt;Motif UI&lt;/a&gt; ships with an accessibility score next to its bundle size and dependency count — the library is nothing but original components, all MIT, and the a11y number is part of the audit gate an item has to clear before it gets a card at all. Tilt Card is 2.9 KB with zero deps; Aurora Veil is pure CSS and runs at 60fps with 0 JS. Those numbers are on the page, which is the point — you can check them instead of trusting a screenshot.&lt;/p&gt;

&lt;p&gt;Six lines of CSS is not accessibility. Knowing what those six lines do to your users is.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>css</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I stopped trusting AI website prompts. Here's how I verify them instead.</title>
      <dc:creator>lichongyang130</dc:creator>
      <pubDate>Mon, 21 Sep 2026 00:03:18 +0000</pubDate>
      <link>https://dev.to/lichongyang130/i-stopped-trusting-ai-website-prompts-heres-how-i-verify-them-instead-20c2</link>
      <guid>https://dev.to/lichongyang130/i-stopped-trusting-ai-website-prompts-heres-how-i-verify-them-instead-20c2</guid>
      <description>&lt;p&gt;I stopped trusting AI website prompts. Here's how I verify them instead.&lt;/p&gt;

&lt;p&gt;Every "10 jaw-dropping prompts for your landing page" thread has the same problem: the screenshot is real, but the prompt they actually pasted is not the one that made it. You copy it, get something three steps uglier, and assume you did it wrong.&lt;/p&gt;

&lt;p&gt;You didn't. The prompt was just oversold.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test I started running
&lt;/h2&gt;

&lt;p&gt;I stopped reading prompt round-ups cold. Instead, for any prompt I was about to use, I ran it through three different frontier models and scored the output against a fixed rubric — layout fidelity, typography, spacing, whether it actually matched the description. Not vibes. A number.&lt;/p&gt;

&lt;p&gt;The results were loud. Across a batch of prompts I'd collected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a chunk of them held up across all three models&lt;/li&gt;
&lt;li&gt;several only produced that "screenshot" on one specific model and fell apart on the other two&lt;/li&gt;
&lt;li&gt;a few failed every time and were just… bad, hidden behind a nice thumbnail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last bucket is the one nobody publishes. Which is exactly the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I look for now
&lt;/h2&gt;

&lt;p&gt;Three things, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fidelity scores per model, not per author.&lt;/strong&gt; If a prompt only works on one model, I want to know that before I build a workflow around it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures shown, not buried.&lt;/strong&gt; A list where every entry has a perfect score is a marketing page, not a tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A score I can reproduce.&lt;/strong&gt; A rubric I can hold up against my own output and get roughly the same number.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I ended up putting this scoring into the library I was already building — &lt;a href="https://www.56juqingba.com" rel="noopener noreferrer"&gt;https://www.56juqingba.com&lt;/a&gt; runs each AI website prompt through 3 frontier models and shows the real fidelity score, including the runs that scored low. No cherry-picked screenshots; the receipts are the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;You don't need to distrust every prompt you find. You just need to stop treating a screenshot as proof. Verify on your stack, against more than one model, and keep the score where you can see it later.&lt;/p&gt;

&lt;p&gt;The prompt section on that site is free to browse — poke at the ones with low scores and judge for yourself whether the scoring is honest.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>program</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I shipped an animated landing page in 90 minutes (and what I'll never hand-code again)</title>
      <dc:creator>lichongyang130</dc:creator>
      <pubDate>Sun, 20 Sep 2026 23:00:52 +0000</pubDate>
      <link>https://dev.to/lichongyang130/i-shipped-an-animated-landing-page-in-90-minutes-and-what-ill-never-hand-code-again-7ah</link>
      <guid>https://dev.to/lichongyang130/i-shipped-an-animated-landing-page-in-90-minutes-and-what-ill-never-hand-code-again-7ah</guid>
      <description>&lt;p&gt;I shipped an animated landing page in 90 minutes (and what I'll never hand-code again)&lt;/p&gt;

&lt;p&gt;The hard parts of a modern landing page aren't the layout. They're the small motions: the tilt that makes the CTA feel clickable, the text that scrambles in on load, the aurora background that looks expensive but doesn't tank your Largest Contentful Paint.&lt;/p&gt;

&lt;p&gt;I used to hand-code all of that. Now I don't — and I tracked a full build minute-by-minute to prove the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build
&lt;/h2&gt;

&lt;p&gt;I gave myself 90 minutes and one rule: no bespoke animation code. Every motion had to come from a verified source with a number attached to it.&lt;/p&gt;

&lt;p&gt;Here's what I pulled together, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hero section&lt;/strong&gt; — a tilt card that reacts to the pointer, plus scramble-text on the headline to draw the eye on load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background&lt;/strong&gt; — an aurora veil with a performance tier, so I knew exactly what it cost before I shipped it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CTA + stats&lt;/strong&gt; — a counter that animates in with an easing curve instead of a jump cut.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why I stopped hand-coding this
&lt;/h2&gt;

&lt;p&gt;Every copy-paste component site I've used does the same bait-and-switch: a pretty screenshot, and then the code you paste is either broken or bloated with 40KB of dependencies.&lt;/p&gt;

&lt;p&gt;The library I used for this build is different — and that's the actual point of this post. It ships receipts instead of marketing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every animated component has an a11y audit and a bundle-size number&lt;/li&gt;
&lt;li&gt;every AI website prompt is tested against 3 frontier models with a real fidelity score (failures shown, not hidden)&lt;/li&gt;
&lt;li&gt;a public run log, including the runs that didn't make the cut&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The three pieces I used — tilt card, scramble text, aurora veil — are all in the free tier at &lt;a href="https://www.56juqingba.com" rel="noopener noreferrer"&gt;https://www.56juqingba.com&lt;/a&gt;. Steal the patterns; that's the whole idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd still hand-code
&lt;/h2&gt;

&lt;p&gt;For the record, not everything belongs in a library. Your business logic, your data fetching, your design system tokens — keep those yours. Components and motion are the last thing you should be reinventing.&lt;/p&gt;

&lt;p&gt;If you're a frontend dev who's tired of shipping hero sections by hand, go poke around the library. It's free.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>animation</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
