Fluted Glass
Installation
npx shadcn@latest add https://uibrik.com/r/fluted-glass.jsonAdd the cn helper
Imports
cnfrom@/lib/utils, whichshadcn initcreates. Add it by hand without shadcn.Copy the source into your project
components/uibrik/fluted-glass/fluted-glass.tsx"use client"; import { ShaderSurface, type ShaderBackgroundProps } from "../_shader-background/ShaderSurface"; // A band of light seen through fluted glass. // // Behind the glass is one simple, smooth scene: a soft diagonal band of light that sways slowly, // with a wider glow around it, fading into the dark. The colour list runs from the dark to the // band's core and is blended in OKLCH, so every step of the gradient stays vivid. // // Each rib of the glass squeezes a wide strip of that scene into its own width (the lens // magnification is below 1), so a gently sloping band turns into a steep sliver inside every rib, // and neighbouring ribs show neighbouring strips: the band breaks into the stepped, sawtooth // pattern of real fluted glass. Each rib is shaded from a darker leading edge to a lit trailing one, // with a hairline seam between ribs. The band bends toward the pointer, and the ribs near it catch // the light, with stronger colour fringes. const SHADER = /* glsl */ ` uniform float u_ribs; // rib width, css px uniform float u_magnify; // how much of the scene each rib squeezes in: 1 is flat glass uniform float u_curve; // extra squeeze toward the rib edges uniform float u_shading; // dark-to-light shading across each rib uniform float u_frost; // blur through the glass uniform float u_dispersion; // colour split toward the rib edges uniform float u_angle; // rib direction, degrees; 0 is vertical uniform float u_bandAngle; // direction of the band of light, degrees uniform float u_bandWidth; // thickness of the band uniform float u_flow; // how much the band sways uniform float u_bend; // how far the band bends toward the pointer uniform float u_shine; // how strongly the glass catches light near the pointer uniform float u_reach; // reach of the pointer's effects, css px // 0 dark .. 1 the core of the band float scene(vec2 frag, float t) { vec2 p = frag / u_res.y; vec2 q = p - vec2(0.5 * u_res.x / u_res.y, 0.5); float a = radians(u_bandAngle); vec2 along = vec2(cos(a), sin(a)); vec2 across = vec2(-along.y, along.x); float s = dot(q, along); float sway = u_flow * (0.14 * sin(s * 2.2 + t) + 0.05 * sin(s * 5.3 - t * 1.3)); float d = dot(q, across) - sway; float w = 0.11 * u_bandWidth; // the band bends toward the pointer: near the pointer (along the band) its centre line is pulled // part of the way to it. The pull fades with distance from the band, so a far pointer only nudges. vec2 qm = u_pointer / u_res.y - vec2(0.5 * u_res.x / u_res.y, 0.5); float sm = dot(qm, along); float dm = dot(qm, across) - u_flow * (0.14 * sin(sm * 2.2 + t) + 0.05 * sin(sm * 5.3 - t * 1.3)); float pull = 0.65 * u_bend * exp(-pow((s - sm) / 0.32, 2.0)) * exp(-dm * dm / 0.16) * u_pointerAmt; d -= dm * pull; float v = 0.22 + 0.5 * exp(-d * d / (w * w)) + 0.3 * exp(-d * d / (w * w * 8.0)); // slow, broad variation, so parts of the frame sink into the dark v += 0.36 * snoise(vec3(p * 0.9, t * 0.4)); return clamp(v, 0.0, 1.0); } vec3 sceneColor(vec2 frag, float t) { return oklabToLinear(paletteRampLch(scene(frag, t))); } vec3 shade(vec2 frag) { float t = u_time * 0.12; float rib = u_ribs * u_dpr; float a = radians(u_angle); vec2 across = vec2(cos(a), sin(a)); vec2 along = vec2(-across.y, across.x); float f = fract(dot(frag, across) / rib); // 0 .. 1 across the rib float u = f * 2.0 - 1.0; // the rib looks at a strip of the scene u_magnify times its own width, centred on itself float look = (u * u_magnify + u_curve * u * u * u) * 0.5 * rib; vec2 base = frag + across * (look - u * 0.5 * rib); // how close this point is to the pointer, for the glass catching the light there vec2 dp = (frag - u_pointer) / (u_reach * u_dpr); float near = exp(-dot(dp, dp)) * u_pointerAmt * u_shine; vec3 c; if (u_frost > 0.0) { float b = rib * 0.12 * u_frost; c = sceneColor(base, t) * 0.4 + (sceneColor(base + along * b * 2.0, t) + sceneColor(base - along * b * 2.0, t)) * 0.2 + (sceneColor(base + across * b, t) + sceneColor(base - across * b, t)) * 0.1; } else { c = sceneColor(base, t); } float disp = u_dispersion + near * 2.0; if (disp > 0.0) { float split = u * rib * 0.05 * disp * u_magnify; c.r = sceneColor(base + across * split, t).r; c.b = sceneColor(base - across * split, t).b; } // across each rib: darker where it starts, brighter where it curves away, a fine seam between ribs float shading = mix(1.0 - 0.55 * u_shading, 1.0 + 0.1 * u_shading, smoothstep(0.0, 0.85, f)); c *= shading; float edge = min(f, 1.0 - f) * rib; c *= 1.0 - 0.5 * u_shading * (1.0 - smoothstep(0.0, 1.2, edge)); // near the pointer the ribs catch the light: a bright line where each one curves away, and a // softer lift across it, stronger where the light behind is already bright float lum = dot(c, vec3(0.3, 0.5, 0.2)); float catchLight = smoothstep(0.02, 0.5, lum); c += vec3(1.0) * near * catchLight * (0.55 * exp(-pow((f - 0.86) / 0.05, 2.0)) + 0.18 * lum); return c; } `; const COLORS = ["#03060c", "#0b3f86", "#1a7fd4", "#cfe3f4", "#f7c2a6"]; export type FlutedGlassProps = ShaderBackgroundProps & { /** Width of each rib, in px. */ ribs?: number; /** How much of the scene each rib squeezes into its width; 1 is flat glass. */ magnify?: number; /** Extra squeeze toward the rib edges. */ curve?: number; /** Dark-to-light shading across each rib, and the seams between them. */ shading?: number; /** Blur through the glass. */ frost?: number; /** Colour split toward the rib edges. */ dispersion?: number; /** Rib direction in degrees; 0 is vertical. */ angle?: number; /** Direction of the band of light, in degrees. */ bandAngle?: number; /** Thickness of the band of light. */ bandWidth?: number; /** How much the band sways. */ flow?: number; /** How far the band of light bends toward the pointer. */ bend?: number; /** How strongly the glass catches the light near the pointer (rib highlights, colour fringes). */ shine?: number; /** How far the pointer's effects reach, in px. */ reach?: number; /** How lazily the light follows the pointer, 0 (locked) to 1 (drifts after it). */ follow?: number; }; export function FlutedGlass({ colors = COLORS, speed = 1, animate = true, interactive = true, grain = 0.05, seed = 0, ribs = 28, magnify = 3, curve = 0.6, shading = 0.8, frost = 0.15, dispersion = 0.3, angle = 0, bandAngle = -50, bandWidth = 1, flow = 1, follow = 0.7, bend = 1, shine = 1, reach = 160, ...rest }: FlutedGlassProps) { return ( <ShaderSurface shader={SHADER} uniforms={{ u_ribs: ribs, u_magnify: magnify, u_curve: curve, u_shading: shading, u_frost: frost, u_dispersion: dispersion, u_angle: angle, u_bandAngle: bandAngle, u_bandWidth: bandWidth, u_flow: flow, u_bend: bend, u_shine: shine, u_reach: reach, }} colors={colors} speed={speed} animate={animate} interactive={interactive} grain={grain} seed={seed} follow={follow} fallback={() => { const ribsCss = `repeating-linear-gradient(${90 + angle}deg, #00000059 0, transparent ${ribs * 0.6}px, #ffffff1a ${ribs - 1}px, #00000080 ${ribs}px)`; const mid = colors.slice(1, -1); const stops = [`${colors[0]} 15%`, ...mid, `${colors[colors.length - 1]} 50%`, ...[...mid].reverse(), `${colors[0]} 85%`]; const band = `linear-gradient(${90 - bandAngle}deg, ${stops.join(", ")})`; return `${ribsCss}, ${band}`; }} {...rest} /> ); }
Adds the files to
components/uibrik/fluted-glass. With the @uibrik namespace set up: npx shadcn@latest add @uibrik/fluted-glass.Props
FlutedGlass- colors
- Type string[]
- ["#03060c","#0b3f86","#1a7fd4","#cfe3f4","#f7c2a6"]
- From the dark to the core of the band, blended in OKLCH; 2 to 6 colours.
- ribs
- Type number
- 28
- Width of each rib.
- magnify
- Type number
- 3
- How much of the scene each rib squeezes into its width; 1 is flat glass.
- curve
- Type number
- 0.6
- Extra squeeze toward the rib edges.
- shading
- Type number
- 0.8
- Dark-to-light shading across each rib, and the seams between them.
- frost
- Type number
- 0.15
- Blur through the glass.
- dispersion
- Type number
- 0.3
- Colour split toward the rib edges.
- angle
- Type number
- 0
- Rib direction; 0 is vertical.
- bandAngle
- Type number
- -50
- Direction of the band of light.
- bandWidth
- Type number
- 1
- Thickness of the band of light.
- flow
- Type number
- 1
- How much the band sways.
- speed
- Type number
- 1
- Animation speed; 1 is the designed pace.
- grain
- Type number
- 0.05
- Film grain strength.
- seed
- Type number
- 0
- Shifts the composition, so two backgrounds with the same settings still differ.
- animate
- Type boolean
- true
- Move on its own. Off draws one still frame.
- interactive
- Type boolean
- true
- React to the pointer.
- bend
- Type number
- 1
- How far the band of light bends toward the pointer.
- shine
- Type number
- 1
- How strongly the glass catches the light near the pointer.
- reach
- Type number
- 160
- How far the pointer's effects reach.
- follow
- Type number
- 0.7
- How lazily the light follows the pointer, 0 (locked) to 1 (drifts after it).
- children
- Type ReactNode
- –
- Content shown on top of the background.
- className
- Type string
- –
- Classes for the box; it fills whatever size you give it.