REFERENCE
- Gallery page: https://magpui.com/en/i/dithered-shader-feature-rows-709a28
- First frame (image): https://dnioievkvxejjbxgwvko.supabase.co/storage/v1/object/public/media/dithered-shader-feature-rows-709a28/shot.jpg
- Clip (mp4): https://dnioievkvxejjbxgwvko.supabase.co/storage/v1/object/public/media/dithered-shader-feature-rows-709a28/clip.mp4
- Original post: https://x.com/Ashishgogula/status/2117947076785102850 by @Ashishgogula
Before building, download the first frame and look at it. If ffmpeg is available, download the clip and extract frames (ffmpeg -i clip.mp4 -vf fps=2 frame-%02d.png), then study the motion frame by frame. Match what you see; the text below describes it, the frames are the truth.
BUILD: A vertical feature list component (six rows) on a warm off-white page. Each row shows a title, a one-line description and a small outline icon on the right. Hovering a row (tap on mobile) floods that row edge to edge with an animated dithered shader texture in the row's own three-color ink palette: it wipes in from the side the cursor entered, the title and description turn white, the icon becomes a solid white circle with the dark glyph inside, and every other row dims. When the cursor leaves, the texture drains out toward the side it exits. Moving straight from one row to the next feels like the ink pouring from row to row. It should feel tactile, printed and calm, never flashy.
DESIGN SYSTEM: Container about 760px wide, centered, rows about 90px tall with 28px horizontal padding, separated by 1px hairlines (#E9E4DC) with hairlines also above the first and below the last row. Page background a soft warm radial gradient from #FBF8F2 at center to #F1ECE3 at the edges. Title: clean grotesque sans, 20px, weight 500, color #9A958E at rest; description: same family, 13px, #BDB7AE at rest. Active row: title #FFFFFF, description rgba(255,255,255,0.8), icon chip 32px white circle with a 16px dark glyph. Icons at rest: 18px outline, #B5AFA6. No radius on the active band (square edges, full row width), no shadows. Default palettes, darkest first: violet (#0B0614, #3B1A8F, #7B4DFF), cobalt (#050A1F, #1730A8, #4C7DFF), emerald (#04140D, #0F5C3E, #3FBF8A), plum (#12040E, #6A1450, #D04AA0), rust (#140603, #6B1F0C, #C2582B). Each row accepts { title, description, icon, colors } and a seed that changes the pattern but not the inks; with no icon the row ends in an arrow.
MOTION: Enter: on pointerenter compute the entry side from the pointer x relative to the row center (left or right; use top or bottom only for vertical entries if you want). A shader uniform 'progress' goes 0 to 1 over 520ms, cubic-bezier(0.22, 1, 0.36, 1); the reveal mask is a soft, noise-jagged edge sweeping from the entry side, so the leading edge looks like ink bleeding through dither rather than a straight wipe. Exit: on pointerleave flip the direction to the exit side and run progress 1 to 0 over 420ms, cubic-bezier(0.4, 0, 0.2, 1), draining out of the exit side. Text color crossfade 240ms ease-out, starting 80ms after the wipe begins; icon chip scales 0.8 to 1 with opacity 0 to 1 over 260ms, cubic-bezier(0.34, 1.56, 0.64, 1). Other rows dim to opacity 0.45 over 200ms while any row is active. Inside the band the texture keeps drifting slowly: domain-warped fbm noise advected at about 0.03 units per second, mapped to the three inks and then ordered-dithered with a 4x4 Bayer matrix at 2px cells so it reads as a coarse print.
SECTIONS: Parts: list container, row (title, description, icon or arrow), shader band behind the row content, dim overlay state. States: idle (all rows muted gray text), hover or tap active (one row inked, others dimmed), leaving (draining), keyboard focus (same as hover, entering from the left), reduced motion (instant fade of a static dithered frame).
IMPLEMENTATION: Use one shared WebGL canvas (OGL or plain WebGL2, or three.js if already in the project) positioned behind the list and draw only the active and draining rows as quads with scissor or per-row uniforms, instead of one canvas per row. Fragment shader: fbm noise with domain warping, three-stop palette ramp, Bayer 4x4 ordered dither, reveal mask = smoothstep on (direction-adjusted x + noise*0.15) against progress. Drive progress with Motion or a small rAF tween. Rows are real buttons or links with visible focus (2px outline in the row's mid ink, 2px offset); Enter and Space activate. Pause rendering when no row is active and when offscreen. Cap devicePixelRatio at 2. prefers-reduced-motion: no sweep or drift, crossfade a static dithered frame over 150ms. Provide a CSS-only fallback (static gradient plus SVG noise) when WebGL is unavailable.
ADAPT TO THE PROJECT: Reuse the project's existing design tokens, fonts, icon set and list or card components; map the background, hairline and text grays to the closest existing tokens and keep the ink palettes as props. Never copy logos, product names, copy or icons from the original; use placeholder feature titles and descriptions.
SUCCESS CHECK:
- Entering a row from the left sweeps the ink in from the left, entering from the right sweeps from the right, and leaving drains toward the exit side.
- Only one row is inked at a time, the others dim, and each row uses its own three-color palette with a visible coarse dither pattern.
- Keyboard focus activates rows the same way, and with prefers-reduced-motion the effect becomes a short static fade with no motion.