<?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: NeroTran</title>
    <description>The latest articles on DEV Community by NeroTran (@nerotran2408).</description>
    <link>https://dev.to/nerotran2408</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%2F4043453%2F27f8183e-e621-4ae1-8c24-0c6f3d25b1d8.jpg</url>
      <title>DEV Community: NeroTran</title>
      <link>https://dev.to/nerotran2408</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nerotran2408"/>
    <language>en</language>
    <item>
      <title>I let an AI write 6 WebGL hero sections — here's the one-CSS-variable reskin trick, and the honest review count</title>
      <dc:creator>NeroTran</dc:creator>
      <pubDate>Mon, 27 Jul 2026 02:06:36 +0000</pubDate>
      <link>https://dev.to/nerotran2408/i-let-an-ai-write-6-webgl-hero-sections-heres-the-one-css-variable-reskin-trick-and-the-honest-ha4</link>
      <guid>https://dev.to/nerotran2408/i-let-an-ai-write-6-webgl-hero-sections-heres-the-one-css-variable-reskin-trick-and-the-honest-ha4</guid>
      <description>&lt;p&gt;I've been building &lt;strong&gt;SDTK&lt;/strong&gt;, a toolkit that puts real gates in front of AI-written code instead of trusting the first draft. To show what that actually buys, I pointed it at something visual and free: six "stop-scrolling" hero sections — a WebGL ember aurora, a floating 3D product, molten liquid metal, a particle swarm that spells your brand word, a scroll-driven film, and kinetic type.&lt;/p&gt;

&lt;p&gt;All six in motion (60-second showcase):&lt;/p&gt;

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

&lt;p&gt;The honest part first, because it's the reason I'm writing this: &lt;strong&gt;every line was AI-generated, and not one of the six was right on the first try.&lt;/strong&gt; Each needed 1–3 human-reviewed passes — &lt;strong&gt;12 in total&lt;/strong&gt; — to clear a fixed bar: real browser render checks, WCAG contrast math, reduced-motion and JS-off fallbacks, and a hard file-weight budget. I'm posting the review count instead of the "AI nailed it cold" version, because that version didn't happen, and the number is more useful to you than the highlight reel.&lt;/p&gt;

&lt;p&gt;Now the technique I actually want to share — the thing that makes these reusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Re-skinning a WebGL shader with one CSS variable
&lt;/h2&gt;

&lt;p&gt;The effects don't hard-code their colours. Each hero reads its palette from CSS custom properties at runtime:&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="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ff6a2b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c"&gt;/* ember */&lt;/span&gt;
  &lt;span class="py"&gt;--glow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;#ffb07a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shader reads those values on init — and a &lt;code&gt;MutationObserver&lt;/code&gt; re-reads them the instant the theme or palette changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getComputedStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getPropertyValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;accent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--accent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;accent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--accent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// re-read the token&lt;/span&gt;
  &lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uAccent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// push it to the shader&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;attributeFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the aurora shader, the liquid metal, even the three.js scenes all follow your brand from a single token edit. Change &lt;code&gt;--accent&lt;/code&gt;, and the whole effect re-skins — no shader edits, no rebuild. Six palettes × two themes ship in every file; adding your own is one CSS line.&lt;/p&gt;

&lt;p&gt;The lesson that stuck with me: &lt;strong&gt;design tokens aren't just for buttons.&lt;/strong&gt; Make your effects obey them too, and "brand-matched" stops being a bespoke job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boring parts the review passes forced
&lt;/h2&gt;

&lt;p&gt;The "wow" is easy; the honest bit is what the gates caught:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/strong&gt;: every hero ships a static poster and honours the reduced-motion query — opt out of motion and you get a clean, fast hero, not a blank box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JS off&lt;/strong&gt;: it still renders. The spectacle is progressive enhancement, not a dependency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No CDN&lt;/strong&gt;: the three.js engine is bundled beside the file. Works from disk, offline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weight budget&lt;/strong&gt;: the whole pack is 309 KB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that survived the first AI draft. That's the entire point of gating the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grab the pack (free, no email wall)
&lt;/h2&gt;

&lt;p&gt;Six self-contained HTML files — double-click and they run, the 3D ones too. Play with all six live, then take the zip:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://sdtk.dev/heroes?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=distribution-r2" rel="noopener noreferrer"&gt;https://sdtk.dev/heroes?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=distribution-r2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does NOT do&lt;/strong&gt;, so you don't waste your time: it's not a page builder, no CMS, no hosting — it's raw HTML/CSS/JS you drop into whatever you're already building.&lt;/p&gt;

&lt;p&gt;If you try the one-variable reskin trick on your own shader, I'd genuinely like to see it. And if "publish the review count" reads as trust-building — or worse than just showing the finished thing — tell me. I keep going back and forth on how much of the messy process to publish.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webgl</category>
      <category>css</category>
      <category>buildinpublic</category>
    </item>
  </channel>
</rss>
