<?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: Alexandru Pavelescu </title>
    <description>The latest articles on DEV Community by Alexandru Pavelescu  (@idlecyrex).</description>
    <link>https://dev.to/idlecyrex</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%2F4045584%2F1e3171d0-5b62-4548-aa5a-edf38effe048.jpg</url>
      <title>DEV Community: Alexandru Pavelescu </title>
      <link>https://dev.to/idlecyrex</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/idlecyrex"/>
    <language>en</language>
    <item>
      <title>Revealed: the Lando Norris website hero effect, as a 19 kB WebGL library</title>
      <dc:creator>Alexandru Pavelescu </dc:creator>
      <pubDate>Wed, 26 Aug 2026 13:53:25 +0000</pubDate>
      <link>https://dev.to/idlecyrex/revealed-the-lando-norris-website-hero-effect-as-a-19-kb-webgl-library-14cn</link>
      <guid>https://dev.to/idlecyrex/revealed-the-lando-norris-website-hero-effect-as-a-19-kb-webgl-library-14cn</guid>
      <description>&lt;p&gt;If you have seen the hero on landonorris.com, you know the effect: you drag the cursor across his face and a helmet appears where you pass, torn open along a wet ragged edge, then it heals back when you leave.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;revealed&lt;/code&gt; is that effect as a library. Two images stacked in one box, the top one tears open where the visitor drags, the one underneath shows through, and it closes again behind the cursor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fib2bz6plyjyr8qziy23l.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fib2bz6plyjyr8qziy23l.gif" alt="A beige 1990s CRT monitor showing a 1997 website tears open along a carved edge to reveal a modern flat panel showing the same site redesigned, with a faint grey outline of the modern monitor visible over the CRT before it is uncovered" width="760" height="604"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i revealed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Revealed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;revealed/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Revealed&lt;/span&gt; &lt;span class="na"&gt;front&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/before.webp"&lt;/span&gt; &lt;span class="na"&gt;back&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/after.webp"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole setup. It renders one div, injects a canvas and the two image plates, takes the aspect ratio from the front image, and starts. No config, no refs, no effect hooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;19.2 kB gzipped. Zero runtime dependencies.&lt;/strong&gt; React is an optional peer, used only by the &lt;code&gt;/react&lt;/code&gt; entry. There is a vanilla &lt;code&gt;RevealField&lt;/code&gt; class and a CDN build too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it does not look like a normal hover mask
&lt;/h2&gt;

&lt;p&gt;I built this after failing to copy that hero. I assumed it was a brush trail: paint into a mask, decay the mask, threshold it, mix two images. Two things about theirs I could not reproduce that way. The reveal keeps spreading for about a second after you stop moving, and it has holes in it, islands of the original image standing inside an area that is otherwise uncovered.&lt;/p&gt;

&lt;p&gt;Their bundles are on a public CDN, so I read the shader. It is not a brush trail. It is a fluid solver, and the cut thresholds the &lt;strong&gt;velocity field&lt;/strong&gt; rather than a painted mask. It reveals where the fluid is moving, not where the pointer has been. That one decision is where both behaviours come from: the wave outliving the stroke is momentum, and the holes are what thresholding an incompressible field gives you, because a vortex core has no velocity at its centre while everything around it does.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;revealed&lt;/code&gt; reproduces that character without shipping a fluid solver. The mask is RGBA8 and only the red channel holds the reveal, so the velocity lives in the two spare channels. No extra render target, no extra shader program, one extra texture fetch. WebGL1 throughout, no extensions, no float textures.&lt;/p&gt;

&lt;p&gt;Measured on Intel integrated graphics: the leading edge travels 44px over 2.4s after the pointer stops, against a control that recedes 34px. At 1500x1192 the wave costs 11.4ms per frame versus 10.5ms without it, which is inside run-to-run noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three layers, the third optional
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdfpvqf8b9oxit5hc5i3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdfpvqf8b9oxit5hc5i3s.png" alt="The three layers side by side: the front plate, the back plate, and the skeleton line art" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A front plate, a back plate, and an optional skeleton layer: line art of the hidden image, ghosted onto the front so people know there is something to find, and wiped exactly where the back plate is already uncovered. It can also be a real 3D wireframe, behind the &lt;code&gt;revealed/mesh&lt;/code&gt; subpath so the core stays small.&lt;/p&gt;

&lt;h2&gt;
  
  
  One number, three names
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Revealed&lt;/span&gt; &lt;span class="na"&gt;front&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;back&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;brush&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="na"&gt;persist&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="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;   &lt;span class="c1"&gt;// stays uncovered&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Revealed&lt;/span&gt; &lt;span class="na"&gt;front&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;back&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;brush&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="na"&gt;spotlight&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="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// torch, no trail&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Revealed&lt;/span&gt; &lt;span class="na"&gt;front&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;back&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;scrollProgress&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;   &lt;span class="c1"&gt;// no pointer at all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;persist&lt;/code&gt; is a heal rate of zero. &lt;code&gt;spotlight&lt;/code&gt; is a heal rate of infinity. &lt;code&gt;trail&lt;/code&gt; is everything in between. &lt;code&gt;progress&lt;/code&gt; drives the same cut from scroll position, so the reveal wears the identical wet edge with no pointer involved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxk7pbs7of0h65hv2z1hb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxk7pbs7of0h65hv2z1hb.gif" alt="The spotlight mode: a torch following the cursor with no trail behind it" width="648" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Six presets
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0g2he5u9dibx9eavsaqm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0g2he5u9dibx9eavsaqm.png" alt="Six presets rendered at the same stroke position: liquid, dissolve, ink, shatter, clean and plain" width="800" height="619"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Revealed&lt;/span&gt; &lt;span class="na"&gt;front&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;back&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ink"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;liquid&lt;/code&gt; is the default. &lt;code&gt;plain&lt;/code&gt; gives you a quiet, classic dissolve with no wave and no bubbles if the fluid character is not what you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  It fails safely
&lt;/h2&gt;

&lt;p&gt;The front plate is a real &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; in the DOM the whole time, so it is what you see when anything goes wrong: no WebGL, a software renderer, &lt;code&gt;prefers-reduced-motion&lt;/code&gt;, a failed shader link, a 404 on a plate, or a lost context. A restored context brings the effect back on its own. Nothing ever renders a hole where your image should be.&lt;/p&gt;

&lt;p&gt;It also pauses itself off screen and on hidden tabs, and it is SSR-safe: importing it on the server touches nothing, and &lt;code&gt;revealed/react&lt;/code&gt; ships its own &lt;code&gt;"use client"&lt;/code&gt;, so no &lt;code&gt;dynamic()&lt;/code&gt; wrapper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Docs and a playground with every option wired to a control: &lt;a href="https://revealed.idlee.xyz" rel="noopener noreferrer"&gt;revealed.idlee.xyz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source, MIT: &lt;a href="https://github.com/idleCyrex/revealed" rel="noopener noreferrer"&gt;github.com/idleCyrex/revealed&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/revealed" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Other things I have made
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://topolines.idlee.xyz" rel="noopener noreferrer"&gt;topolines&lt;/a&gt; is the same idea applied to backgrounds: animated topographic contour fields for React, one component, zero dependencies, drawn on the GPU. It is what draws the contour lines behind the &lt;code&gt;revealed&lt;/code&gt; docs site.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i topolines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you build something with either of them I would like to see it.&lt;/p&gt;

</description>
      <category>webgl</category>
      <category>javascript</category>
      <category>react</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built topolines, an animated topographic contour background for React</title>
      <dc:creator>Alexandru Pavelescu </dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:33:00 +0000</pubDate>
      <link>https://dev.to/idlecyrex/topolines-animated-topographic-contour-backgrounds-for-react-5dh0</link>
      <guid>https://dev.to/idlecyrex/topolines-animated-topographic-contour-backgrounds-for-react-5dh0</guid>
      <description>&lt;p&gt;Every couple of projects I ended up rebuilding the same effect: an animated topographic map background, the kind with slowly drifting contour lines. After copy pasting the same WebGL shader for the third time I gave up and turned it into a proper library.&lt;/p&gt;

&lt;p&gt;It's called &lt;strong&gt;topolines&lt;/strong&gt;. One React component, zero dependencies, everything drawn on the GPU.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/idleCyrex/topolines" rel="noopener noreferrer"&gt;https://github.com/idleCyrex/topolines&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Playground: &lt;a href="https://topolines.idlee.xyz/playground" rel="noopener noreferrer"&gt;https://topolines.idlee.xyz/playground&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The lines are not an image or SVG. A small fragment shader generates a noise field (simplex noise + fbm) and draws contour bands from it, so it animates smoothly at any resolution for basically no CPU cost. The component just manages a canvas and the WebGL state around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i topolines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Topolines&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;topolines/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Topolines&lt;/span&gt; &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#F2EFE6"&lt;/span&gt; &lt;span class="na"&gt;style&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="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fixed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same seed always renders the same field, so your background is stable between visits. There are props for speed, scale, line width, colors, drift, and an interactive mode where contour rings bloom around the cursor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I cared about
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Zero dependencies, the whole thing is one shader and some glue code&lt;/li&gt;
&lt;li&gt;SSR safe, works in a Next.js server component tree&lt;/li&gt;
&lt;li&gt;Pauses when offscreen or when the tab is hidden&lt;/li&gt;
&lt;li&gt;Respects prefers-reduced-motion (renders one static frame)&lt;/li&gt;
&lt;li&gt;Clean fallback when WebGL is not available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The playground lets you tweak every knob and copy the resulting code out: &lt;a href="https://topolines.idlee.xyz/playground" rel="noopener noreferrer"&gt;https://topolines.idlee.xyz/playground&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's v0.1 and my first published library, so feedback and feature ideas are very welcome.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webgl</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
