@razorpay/blade
Version:
The Design System that powers Razorpay
6 lines (4 loc) • 41.1 kB
JavaScript
var rzpGlassVertexShader = /* glsl */"\nprecision mediump float;\n\nattribute vec2 position;\nattribute vec2 uv;\n\n// Zoom & Pan uniforms (computed in vertex shader for efficiency)\nuniform float uZoom;\nuniform vec2 uPan; // vec2(uPanX, uPanY)\n\n// Output varyings\nvarying vec2 vUv; // Raw screen UV (for screen-space effects like feathering)\nvarying vec2 vContentUv; // Transformed UV for video/content sampling (zoom + pan applied)\n\nvoid main() {\n // Raw screen UV for screen-space effects\n vUv = uv;\n \n // Compute zoomed/panned UV for content sampling\n // Zoom: scale around center (0.5, 0.5)\n // Pan: offset the view\n vContentUv = (uv - 0.5) / uZoom + 0.5;\n vContentUv += uPan;\n \n gl_Position = vec4(position, 0, 1);\n}\n";
var rzpGlassFragmentShader = /* glsl */"\nprecision mediump float;\n\nuniform float uTime;\nuniform vec2 iResolution;\nuniform float uDpr;\nuniform sampler2D uVideoTexture;\nuniform sampler2D uGradientMap;\nuniform sampler2D uGradientMap2; // Second gradient map for cross-fade blending\nuniform sampler2D uCenterGradientMap; // Separate gradient map for center ellipse\n\n// Layer toggles (enable/disable actual effects)\nuniform float uEnableDisplacement;\nuniform float uEnableColorama;\nuniform float uEnableBloom;\nuniform float uEnableLightSweep;\n\n// ============================================\n// COLORAMA UNIFORMS (Adobe AE v5 Pipeline)\n// Pipeline: Scalar \u2192 Remap \u2192 Warp \u2192 Wrap \u2192 Lookup \u2192 Blend\n// ============================================\n\n// --- 1. INPUT PHASE (Scalar Index Generation) ---\nuniform float uInputMin; // Input range min (default 0.0)\nuniform float uInputMax; // Input range max (default 1.0)\n\n// --- 2. MODIFY PHASE (Index Space Warping) ---\nuniform float uModifyGamma; // Gamma curve: <1 = brights, >1 = darks (default 1.0)\nuniform float uPosterizeLevels; // 0 = off, >0 = number of discrete steps\nuniform float uCycleRepetitions; // Stretch/compress the index (default 1.0)\nuniform float uPhaseShift; // Static offset (default 0.0)\nuniform float uCycleSpeed; // Cycling animation speed (default 0.0)\n\n// --- 3. OUTPUT CYCLE (Wrap & Lookup) ---\nuniform float uWrapMode; // 0 = clamp, 1 = wrap/fract (default 1.0)\nuniform float uReverse; // 0 = normal, 1 = reverse gradient (default 0.0)\n\n// --- 4. COMPOSITE ---\nuniform float uBlendWithOriginal; // 0 = full effect, 1 = original (default 0.0)\nuniform float uGradientMapBlend; // 0 = uGradientMap, 1 = uGradientMap2 (default 0.0)\n\n// --- 5. LIGHT EFFECT ---\nuniform float uLightIntensity; // Strength of light sweep effect\nuniform float uFrameCount; // Current frame number\nuniform float uLightStartFrame; // Frame when light effect starts\n\n// --- 6. DISPLACEMENT ---\nuniform float uNumSegments; // Number of glass slits (default 45.0)\nuniform float uSlitAngle; // Angle of slits in radians (default 0.13)\nuniform float uDisplacementX; // X displacement amount (default -12.0)\nuniform float uDisplacementY; // Y displacement amount (default -20.0)\n\n// --- 7. CENTER ELEMENT ---\nuniform float uEnableCenterElement; // Toggle center element (0 = off, 1 = on)\nuniform float uCenterAnimDuration; // Duration of one animation cycle in seconds\nuniform float uCenterAnimTime; // Current animation time in seconds (resets with video loop)\n\n// --- 8. COLOR CORRECTION ---\nuniform float uCCBlackPoint; // Levels black point (default 0.0)\nuniform float uCCWhitePoint; // Levels white point (default 1.0)\nuniform float uCCMidtoneGamma; // Midtone gamma (default 1.2)\nuniform float uCCGamma; // Output gamma (default 1.2)\nuniform float uCCContrast; // Contrast boost (default 0.0)\n\n// --- 9. ZOOM & PAN ---\nuniform float uZoom; // Zoom level (1.0 = normal, 2.0 = 2x zoom) - still needed for edge feather check\nuniform vec4 uEdgeFeather; // Per-side feathering: vec4(top, right, bottom, left) clockwise, 0 = none, 1 = max\nuniform vec2 uRefResolution; // Reference resolution for zoom-independent displacement\nuniform vec4 uVisibleUvBounds; // vec4(minX, minY, maxX, maxY) - visible portion of canvas in container\n\n// --- 10. BACKGROUND COLOR ---\nuniform vec3 uBackgroundColor; // Background color to blend with (RGB 0-1)\n\n// UV coordinates from the vertex shader\nvarying vec2 vUv; // Raw screen UV (for screen-space effects)\nvarying vec2 vContentUv; // Transformed UV with zoom/pan applied (for content sampling)\n\n// ============================================\n// UTILITY FUNCTIONS\n// ============================================\n// Rec. 709 luminance calculation\nfloat luminance(vec3 color) {\n return dot(color, vec3(0.2126, 0.7152, 0.0722));\n}\n\n// ============================================\n// COLORAMA EFFECT (Adobe After Effects v5 Pipeline)\n// ============================================\n//\n// Pipeline:\n// Image \u2192 Scalar Field (0-1) \u2192 Warp/Animate \u2192 Gradient Lookup \u2192 Composite\n//\n// This matches AE's indexed gradient remapping with time-domain cycling.\nvec3 applyColoramaWithGradient(\n sampler2D gradientMap, // Gradient map texture to sample from\n float rawIntensity, // Raw luminance from pixel\n float inputMin, // Input range min\n float inputMax, // Input range max\n float gamma, // Gamma curve (pow)\n float posterizeLevels, // 0 = off, else discrete steps\n float cycleReps, // Cycle repetitions (stretch)\n float phaseShift, // Static offset\n float cycleSpeed, // Time-based cycling\n float wrapMode, // 0 = clamp, 1 = wrap\n float reverse // 0 = normal, 1 = flip\n) {\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // STEP 1: INPUT PHASE - Scalar Index Generation\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Normalize intensity to input range\n float t = clamp((rawIntensity - inputMin) / (inputMax - inputMin), 0.0, 1.0);\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // STEP 2: MODIFY PHASE - Index Space Warping\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // a) Gamma / Curves - reshape the intensity distribution\n t = pow(t, gamma);\n\n // b) Posterize - quantize to discrete levels (branchless)\n // When posterizeLevels <= 0, keeps t unchanged\n float posterized = floor(t * posterizeLevels + 0.0001) / max(posterizeLevels, 0.0001);\n t = mix(t, posterized, step(0.001, posterizeLevels));\n\n // c) Cycle Repetitions - stretch/compress across gradient\n t = t * cycleReps;\n\n // d) Phase Shift - static offset\n t = t + phaseShift;\n\n // e) Cycling Animation - time-based offset\n t = t + cycleSpeed * uTime;\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // STEP 3: OUTPUT CYCLE - Wrap & Lookup (branchless)\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // Wrap (fract) vs Clamp - branchless selection\n t = mix(clamp(t, 0.0, 1.0), fract(t), step(0.5, wrapMode));\n\n // Reverse direction - branchless\n t = mix(t, 1.0 - t, step(0.5, reverse));\n\n // Gradient lookup (1D texture sample)\n return texture2D(gradientMap, vec2(t, 0.5)).rgb;\n}\n\n// ============================================\n// DISPLACEMENT FUNCTIONS\n// ============================================\n// Create striped displacement map for glass refraction effect\n// Returns: x = signed displacement (-1 to 1), y = local UV x position within segment\n// gradientStart: gradient value at left edge (typically 1.0 for white)\n// gradientEnd: gradient value at right edge (typically 0.0 for black)\n// gradientPower: power curve for falloff (1.0 = linear, <1.0 = steeper, >1.0 = gentler)\n// centerPoint: center value for signed conversion (typically 0.5)\n// aspect: screen aspect ratio (width/height) for consistent slit angle\nvec2 createStripedDisplacement(\n vec2 uv,\n float numSegments,\n float angle,\n float gradientStart,\n float gradientEnd,\n float gradientPower,\n float centerPoint,\n float aspect\n) {\n // Work in aspect-corrected UV space where x and y have equal visual scale\n // This ensures consistent slit angle regardless of viewport dimensions\n vec2 aspectUV = uv * vec2(aspect, 1.0);\n \n // Apply slant in aspect-corrected space\n float slantedX = aspectUV.x - aspectUV.y * tan(angle);\n\n // Calculate segment properties (account for aspect-scaled x range)\n float segmentWidth = aspect / numSegments;\n float localUVx = fract(slantedX / segmentWidth); // 0-1 within each segment\n\n // Create the displacement map gradient\n // Use smoothstep for smoother interpolation\n float smoothUVx = smoothstep(0.0, 1.0, localUVx);\n // Interpolate from gradientEnd (left) to gradientStart (right)\n float rawGradient = mix(gradientEnd, gradientStart, smoothUVx);\n\n // Apply power curve for falloff control\n rawGradient = pow(rawGradient, gradientPower);\n\n // Convert to signed displacement (-1 to 1) using centerPoint\n // centerPoint is the neutral value (typically 0.5)\n float signedDisplacement = (rawGradient - centerPoint) / centerPoint;\n\n return vec2(signedDisplacement, localUVx);\n}\n\n// Apply displacement offset to UV coordinates\nvec2 applyDisplacement(vec2 uv, float signedDisplacement, vec2 maxDisplacement, vec2 resolution) {\n vec2 displaceOffset = vec2(\n signedDisplacement * maxDisplacement.x / resolution.x,\n signedDisplacement * maxDisplacement.y / resolution.y\n );\n return uv + displaceOffset;\n}\n\n// Create thin slanted stripes with multi-stop gradient color\n// Returns RGB color: gradient stripes with 3 color stops\n// Stop 1 (0%): colorStart, Stop 2 (stopPosition): colorMid, Stop 3 (100%): transparent\nvec4 createStripes(\n vec2 uv,\n float numSegments,\n float angle,\n float stopPosition, // Position of middle stop (0.0 to 1.0)\n vec4 colorStart, // Color at 0% (left edge)\n vec4 colorMid, // Color at stopPosition\n float aspect // Screen aspect ratio for consistent angle\n) {\n // Work in aspect-corrected UV space where x and y have equal visual scale\n vec2 aspectUV = uv * vec2(aspect, 1.0);\n \n // Apply slant in aspect-corrected space\n float slantedX = aspectUV.x - aspectUV.y * tan(angle);\n\n // Calculate segment properties (account for aspect-scaled x range)\n float segmentWidth = aspect / numSegments;\n float localUVx = 1.0 - fract(slantedX / segmentWidth); // 0-1 within each segment, reversed\n\n // Multi-stop gradient:\n // 0% -> colorStart (green)\n // stopPosition -> colorMid (white)\n // 100% -> transparent (black with 0 opacity)\n\n vec4 gradientColor;\n float opacity;\n\n if (localUVx < stopPosition) {\n float t = localUVx / stopPosition;\n gradientColor = mix(colorMid, colorStart, t);\n opacity = 0.5;\n } else {\n float t = (localUVx - stopPosition) / (1.0 - stopPosition);\n gradientColor = mix(vec4(0.0), colorMid, t);\n opacity = 0.5 - t * 0.5;\n }\n\n return gradientColor * opacity;\n}\n\n// Sample texture with displacement applied\nvec4 sampleWithDisplacement(\n sampler2D tex,\n vec2 uv,\n float signedDisplacement,\n vec2 maxDisplacement,\n vec2 resolution\n) {\n vec2 displacedUV = applyDisplacement(uv, signedDisplacement, maxDisplacement, resolution);\n return texture2D(tex, displacedUV);\n}\n\n// ============================================\n// POST-PROCESSING EFFECTS\n// ============================================\nvec3 applyBloom(vec3 color, float intensity, float innerMask) {\n const float whiteCoreThresholdMin = 0.5; // Start of white core mask\n const float whiteCoreThresholdMax = 0.85; // End of white core mask\n const float whiteCoreBlendStrength = 0.85; // How much to blend towards pure white\n\n const float bloomThresholdMin = 0.3; // Start of bloom glow\n const float bloomThresholdMax = 0.7; // End of bloom glow\n const vec3 bloomColor = vec3(1.0, 0.99, 0.97); // Warm white bloom tint\n const float bloomStrength = 0.10; // Intensity of bloom glow\n // -------------------------------\n\n // Calculate how much we're in the \"center\" bright area\n // Use a tighter threshold for the white core\n float whiteCoreMask = smoothstep(whiteCoreThresholdMin, whiteCoreThresholdMax, intensity) * innerMask;\n\n // Pure white target\n vec3 pureWhite = vec3(1.0);\n\n // Blend the center towards pure white (not additive, but replacement blend)\n // This ensures the very center goes to white, not gray\n color = mix(color, pureWhite, whiteCoreMask * whiteCoreBlendStrength);\n\n // Additional soft bloom glow around the white core\n float bloomBase = smoothstep(bloomThresholdMin, bloomThresholdMax, intensity);\n float bloomAmount = bloomBase * innerMask;\n color += bloomColor * bloomAmount * bloomStrength;\n\n return color;\n}\n\n// ============================================\n// ANIMATED POLYGON SHAPE\n// ============================================\n// Struct to return shape data\nstruct ShapeData {\n float shape; // The shape value (0 = outside, 1 = center)\n float gradient; // Gradient from gray (edge) to white (center)\n};\n\n// Signed distance function for a regular polygon\n// p: point to test, r: radius, n: number of sides\nfloat sdPolygon(vec2 p, float r, float n) {\n // Angle and radius\n float an = 3.141593 / n;\n vec2 acs = vec2(cos(an), sin(an));\n\n // Reduce to first sector\n float bn = mod(atan(p.x, p.y), 2.0 * an) - an;\n p = length(p) * vec2(cos(bn), abs(sin(bn)));\n\n // Line sdf\n p -= r * acs;\n p.y += clamp(-p.y, 0.0, r * acs.y);\n\n return length(p) * sign(p.x);\n}\n\n// Fast signed distance function for an ellipse\n// Approximation that avoids expensive acos(), cube roots, and branching\n// Accurate enough for visual effects, ~5x faster than exact version\nfloat sdEllipse(vec2 p, vec2 ab) {\n // Normalize point by ellipse radii\n vec2 q = p / ab;\n float k1 = length(q);\n \n // Early out for points very close to center (avoid division issues)\n if (k1 < 0.0001) return -min(ab.x, ab.y);\n \n // Gradient-based distance approximation\n vec2 q2 = q / ab; // Second normalization for gradient\n float k2 = length(q2);\n \n return k1 * (k1 - 1.0) / k2;\n}\n\n// Helper: Calculate a single shape instance at a given animation phase\nShapeData calculateSingleShape(\n vec2 uv,\n float linearT, // Animation phase 0-1\n float solidCoreMask,\n vec2 resolution,\n // Shape parameters\n float shapeType,\n float shapeWidth,\n float shapeHeight,\n float centerY,\n float animRange,\n float edgeSoftness,\n float grayLevel,\n float shapeAngleStart,\n float shapeAngleEnd,\n float shapeSize\n) {\n // Map 0-1 to position range (left to right)\n float posOffset = (linearT * 2.0 - 1.0) * animRange;\n float centerX = 0.5 + posOffset;\n\n // Animate rotation\n float animatedAngle = mix(shapeAngleStart, shapeAngleEnd, linearT);\n\n // Correct for aspect ratio\n float aspect = resolution.x / resolution.y;\n\n // Calculate position relative to center\n vec2 shapeCenter = vec2(centerX, centerY);\n vec2 delta = uv - shapeCenter;\n\n // Rotate delta by animated angle FIRST\n float cosA = cos(animatedAngle);\n float sinA = sin(animatedAngle);\n vec2 rotatedDelta = vec2(\n delta.x * cosA - delta.y * sinA,\n delta.x * sinA + delta.y * cosA\n );\n\n // Apply aspect correction AFTER rotation\n rotatedDelta.x *= aspect;\n\n // Calculate distance based on shape type\n float dist;\n if (shapeType < 0.5) {\n dist = sdEllipse(rotatedDelta, vec2(shapeWidth, shapeHeight));\n } else {\n vec2 scaledDelta = rotatedDelta / vec2(shapeWidth, shapeHeight);\n dist = sdPolygon(scaledDelta, shapeSize / min(shapeWidth, shapeHeight), shapeType);\n dist *= min(shapeWidth, shapeHeight);\n }\n\n // Normalize distance for gradient\n float normalizedDist;\n if (shapeType < 0.5) {\n normalizedDist = dist / max(shapeWidth, shapeHeight) + 1.0;\n } else {\n normalizedDist = (dist / shapeSize) + 1.0;\n }\n normalizedDist = clamp(normalizedDist, 0.0, 1.0);\n\n // Create soft shape mask\n float shapeMask = 1.0 - smoothstep(-edgeSoftness * 0.1, edgeSoftness * 0.05, dist);\n\n // Create gradient\n float gradient = mix(1.0, grayLevel, smoothstep(0.0, 1.0, normalizedDist));\n\n // Apply solid core mask\n shapeMask *= solidCoreMask;\n\n ShapeData result;\n result.shape = shapeMask;\n result.gradient = gradient;\n return result;\n}\n\n// Creates two staggered animated shapes that move left-to-right\n// When one reaches the far edge, another appears from the left\nShapeData calculateAnimatedShape(\n vec2 uv,\n float time, // Current animation time in seconds\n float solidCoreMask,\n vec2 resolution\n) {\n // --- Configurable parameters ---\n const float shapeType = 3.0; // 0 = ellipse, 3 = triangle, 4 = square, 5 = pentagon, etc.\n const float shapeWidth = 0.1; // Width/horizontal scale (wide)\n const float shapeHeight = 0.8; // Height/vertical scale (short)\n const float centerY = 0.4; // Vertical center position\n float cycleDuration = uCenterAnimDuration; // Seconds per animation cycle (from uniform)\n const float animRange = 0.7; // How far left/right it travels\n const float edgeSoftness = 0.5; // Softness of shape edge\n const float grayLevel = 0.5; // Gray color at shape edge\n const float shapeAngleStart = 0.6; // Rotation angle at start (slight tilt)\n const float shapeAngleEnd = 0.1; // Rotation angle at end (opposite tilt)\n const float shapeSize = 0.4; // Overall size for polygon mode\n const float staggerOffset = 0.4; // Offset between the two shapes (0.5 = half cycle apart)\n const float shape2Scale = 1.3; // Scale multiplier for 2nd shape (1.0 = same size)\n // -------------------------------\n\n // Time-based animation: time / cycleDuration gives progress through cycle\n float linearT1 = fract(time / cycleDuration); // Shape 1: 0->1 repeating\n float linearT2 = fract(time / cycleDuration + staggerOffset); // Shape 2: offset by staggerOffset\n\n // Calculate both shapes (shape 2 is slightly larger)\n ShapeData shape1 = calculateSingleShape(\n uv, linearT1, solidCoreMask, resolution,\n shapeType, shapeWidth, shapeHeight, centerY, animRange,\n edgeSoftness, grayLevel, shapeAngleStart, shapeAngleEnd, shapeSize\n );\n\n ShapeData shape2 = calculateSingleShape(\n uv, linearT2, solidCoreMask, resolution,\n shapeType, shapeWidth * shape2Scale, shapeHeight * shape2Scale, centerY, animRange,\n edgeSoftness, grayLevel, shapeAngleStart, shapeAngleEnd, shapeSize * shape2Scale\n );\n\n // Combine both shapes (take max of masks, blend gradients)\n ShapeData result;\n result.shape = max(shape1.shape, shape2.shape);\n // Weighted average of gradients based on shape masks\n float totalMask = shape1.shape + shape2.shape;\n if (totalMask > 0.001) {\n result.gradient = (shape1.gradient * shape1.shape + shape2.gradient * shape2.shape) / totalMask;\n } else {\n result.gradient = 0.5;\n }\n\n return result;\n}\n\n// Apply shape effect to intensity (for colorama/displacement pipeline)\nfloat applyShapeToIntensity(\n float baseIntensity,\n ShapeData shapeData,\n float effectStrength\n) {\n // Blend the shape gradient into the base intensity\n // This makes the shape area brighter/differently colored through colorama\n float shapeContribution = shapeData.gradient * shapeData.shape;\n return mix(baseIntensity, shapeContribution, shapeData.shape * effectStrength);\n}\n\n\n// AE-style color processing (levels, gamma, contrast). Used in both ripple and normal mode.\nvec3 applyColorCorrection(vec3 color) {\n color = (color - uCCBlackPoint) / (uCCWhitePoint - uCCBlackPoint);\n color = pow(max(color, vec3(0.0)), vec3(1.0 / (uCCMidtoneGamma * uCCGamma)));\n color = color * (1.0 + uCCContrast) - uCCContrast * 0.5;\n return clamp(color, 0.0, 1.0);\n}\n\n// Feather at container edges (visible bounds). Used in both ripple and normal mode.\nvec3 applyEdgeFeathering(vec3 color, vec3 bgColor) {\n // Apply edge feathering when zoomed in\n // Feathering is applied at the container edges (visible bounds), not canvas edges\n // uEdgeFeather = vec4(top, right, bottom, left) \u2014 clockwise like CSS\n if (any(greaterThan(uEdgeFeather, vec4(0.0)))) {\n vec2 screenUV = vUv;\n\n // Get visible UV bounds (where container clips the canvas)\n float visMinX = uVisibleUvBounds.x;\n float visMinY = uVisibleUvBounds.y;\n float visMaxX = uVisibleUvBounds.z;\n float visMaxY = uVisibleUvBounds.w;\n\n // Calculate visible dimensions in UV space for aspect-correct feathering on X axis\n float visibleWidth = visMaxX - visMinX;\n float visibleHeight = visMaxY - visMinY;\n float visibleAspect = visibleWidth / visibleHeight;\n\n // Per-side feather amounts (X sides get aspect correction for pixel-equal width)\n float featherTop = uEdgeFeather.x * 0.15 / visibleAspect;\n float featherRight = uEdgeFeather.y * 0.15 / visibleAspect;\n float featherBottom = uEdgeFeather.z * 0.15 / visibleAspect;\n float featherLeft = uEdgeFeather.w * 0.15 / visibleAspect;\n\n // Apply feathering at container edges (visible bounds)\n float left = smoothstep(visMinX, visMinX + featherLeft, screenUV.x);\n float right = smoothstep(visMaxX, visMaxX - featherRight, screenUV.x);\n float bottom = smoothstep(visMinY, visMinY + featherBottom, screenUV.y);\n float top = smoothstep(visMaxY, visMaxY - featherTop, screenUV.y);\n\n float edgeMask = left * right * bottom * top;\n // Blend towards background color instead of white to avoid visible edges\n color = mix(bgColor, color, edgeMask);\n }\n return color;\n}\n\nvoid main() {\n // ============================================\n // LAYER 1: Base glass effect (fine slits, whole image)\n // ============================================\n float numSegments = uNumSegments;\n float angle = uSlitAngle;\n const float blurRadius = 9.0;\n const float sigma = 4.0;\n\n // Calculate aspect ratio for consistent slit angles\n float aspect = iResolution.x / iResolution.y;\n\n // Displacement parameters (in pixels, scaled by DPR)\n // Scale displacement by reference resolution ratio to maintain consistency across browser zoom levels\n float resolutionScale = iResolution.x / uRefResolution.x;\n vec2 maxDisplacement = vec2(uDisplacementX, uDisplacementY) * uDpr * resolutionScale;\n\n // Use pre-computed UV from vertex shader (zoom + pan already applied)\n vec2 uv = vContentUv;\n\n // Base displacement (fine slits)\n // Use raw screen UV (vUv) so slit positions stay fixed on screen regardless of pan/zoom\n float gradientStart = 1.0;\n float gradientEnd = 0.0;\n float gradientPower = 1.0;\n float centerPoint = 0.5;\n vec2 displacementData = createStripedDisplacement(\n vUv,\n numSegments,\n angle,\n gradientStart,\n gradientEnd,\n gradientPower,\n centerPoint,\n aspect\n );\n float signedDisplacement = displacementData.x;\n float localUVx = displacementData.y;\n\n // ============================================\n // LAYER 2: Inner glass effect (larger slits, center only, STATIC)\n // ============================================\n // Inner segments are a divisor of outer segments for perfect alignment\n // 45 / 5 = 9 segments (5x larger slits that align with outer grid)\n float innerNumSegments = numSegments;\n float innerAngle = angle; // Same angle as outer\n\n // Inner slits are STATIC - use raw screen UV so slits stay fixed on screen\n vec2 innerDisplacementData = createStripedDisplacement(\n vUv,\n innerNumSegments,\n innerAngle,\n gradientStart,\n gradientEnd,\n gradientPower,\n centerPoint,\n aspect\n );\n float innerSignedDisplacement = innerDisplacementData.x;\n float innerLocalUVx = innerDisplacementData.y;\n\n // ============================================\n // COMPUTE SOLID CORE MASK EARLY (needed for light & displacement masking)\n // ============================================\n vec4 videoFrameSample = texture2D(uVideoTexture, uv);\n float maskIntensity = luminance(videoFrameSample.rgb);\n float solidCoreMask = smoothstep(0.4, 0.7, maskIntensity);\n\n // Store original displacement for shape refraction (before masking)\n float originalInnerDisplacement = innerSignedDisplacement;\n\n // Mask inner displacement by solidCoreMask - no inner displacement in center\n innerSignedDisplacement *= (1.0 - solidCoreMask);\n\n // Inner layer displacement uses same values (scaled by resolutionScale for zoom independence)\n vec2 innerMaxDisplacement = vec2(30.0, 0.0) * uDpr * resolutionScale;\n\n // ============================================\n // CENTER ELEMENT (Static Ellipse + Animated Shapes)\n // ============================================\n float staticEllipseMask = 0.0;\n float staticEllipseGradient = 0.0;\n ShapeData shapeData;\n shapeData.shape = 0.0;\n shapeData.gradient = 0.0;\n\n float centerFadeInDuration = 10.0;\n float centerFramesSinceStart = uFrameCount - uLightStartFrame;\n float centerEffectActivation = clamp(centerFramesSinceStart / centerFadeInDuration, 0.0, 1.0);\n\n if (uEnableCenterElement > 0.5) {\n // Calculate static ellipse mask\n {\n const float ellipseCenterX = 0.3;\n const float ellipseCenterY = -0.15;\n const float ellipseWidth = 0.5;\n const float ellipseHeight = 0.8;\n const float ellipseAngle = 0.3;\n const float ellipseSoftness = 1.0;\n const float ellipseGrayLevel = 0.5;\n\n float aspect = iResolution.x / iResolution.y;\n\n // Use RAW UV (no displacement) for the mask calculation\n vec2 delta = uv - vec2(ellipseCenterX, ellipseCenterY);\n\n // Rotate\n float cosA = cos(ellipseAngle);\n float sinA = sin(ellipseAngle);\n vec2 rotatedDelta = vec2(\n delta.x * cosA - delta.y * sinA,\n delta.x * sinA + delta.y * cosA\n );\n rotatedDelta.x *= aspect;\n\n // Calculate ellipse distance\n float ellipseDist = sdEllipse(rotatedDelta, vec2(ellipseWidth, ellipseHeight));\n\n // Soft shape mask\n staticEllipseMask = 1.0 - smoothstep(-ellipseSoftness * 0.1, ellipseSoftness * 0.05, ellipseDist);\n\n // Gradient\n float normalizedDist = ellipseDist / max(ellipseWidth, ellipseHeight) + 1.0;\n normalizedDist = clamp(normalizedDist, 0.0, 1.0);\n staticEllipseGradient = mix(1.0, ellipseGrayLevel, smoothstep(0.0, 1.0, normalizedDist));\n\n // Apply solid core mask\n staticEllipseMask *= solidCoreMask;\n }\n\n // Block displacement inside the static ellipse\n originalInnerDisplacement *= (1.0 - staticEllipseMask);\n innerSignedDisplacement *= (1.0 - staticEllipseMask);\n signedDisplacement *= (1.0 - staticEllipseMask);\n\n\n\n // Calculate displaced UV for shape - use inner displacement for the shape refraction\n vec2 shapeDisplacedUV = applyDisplacement(uv, originalInnerDisplacement, innerMaxDisplacement, iResolution);\n\n // Calculate animated shape using DISPLACED UVs so glass slits refract through it\n shapeData = calculateAnimatedShape(\n shapeDisplacedUV, // Use displaced UVs!\n uCenterAnimTime, // Time-based animation in seconds (resets with video loop)\n solidCoreMask,\n iResolution\n );\n\n // Combine static ellipse with animated shapes\n {\n float combinedShape = max(shapeData.shape, staticEllipseMask) * centerEffectActivation;\n float totalMask = shapeData.shape + staticEllipseMask;\n float combinedGradient = shapeData.gradient;\n if (totalMask > 0.001) {\n combinedGradient = (shapeData.gradient * shapeData.shape + staticEllipseGradient * staticEllipseMask) / totalMask;\n }\n shapeData.shape = combinedShape;\n shapeData.gradient = combinedGradient;\n }\n }\n\n // ============================================\n // INNER EFFECT ACTIVATION - starts after uLightStartFrame\n // ============================================\n float innerFadeInDuration = 10.0; // Slower fade-in for inner effect\n float innerFramesSinceStart = uFrameCount - uLightStartFrame;\n float innerEffectActivation = clamp(innerFramesSinceStart / innerFadeInDuration, 0.0, 1.0);\n\n // Create edge mask (solidCoreMask already computed earlier for light masking)\n float edgeMask = smoothstep(0.3, 0.6, maskIntensity); // Where shape starts\n\n // Blend outer and inner slits on the edges\n // Inner contribution is multiplied by activation (fades in after frame 200)\n float outerContribution = 1.0 - edgeMask * 0.5 * innerEffectActivation;\n float innerContribution = edgeMask * innerEffectActivation;\n\n // Combined displacement: inner effect fades in over time\n float combinedDisplacement = signedDisplacement * outerContribution +\n innerSignedDisplacement * innerContribution;\n\n // Blend max displacement smoothly\n vec2 combinedMaxDisplacement = maxDisplacement * outerContribution +\n innerMaxDisplacement * innerContribution;\n\n // For the CENTER area (solidCoreMask): use 100% inner (larger) slits displacement\n // For the EDGES: use the combined displacement (blended outer + inner)\n // This ensures the logo center always has the larger slits\n combinedDisplacement = mix(combinedDisplacement, originalInnerDisplacement, solidCoreMask);\n combinedMaxDisplacement = mix(combinedMaxDisplacement, innerMaxDisplacement, solidCoreMask);\n\n // ============================================\n // LAYER 1: DISPLACEMENT (toggleable)\n // ============================================\n vec4 textureSample;\n if (uEnableDisplacement > 0.5) {\n textureSample = sampleWithDisplacement(\n uVideoTexture, uv,\n combinedDisplacement, combinedMaxDisplacement,\n iResolution\n );\n } else {\n // No displacement - sample directly\n textureSample = texture2D(uVideoTexture, uv);\n }\n\n // Blend localUVx for highlights - also fades in with activation\n float combinedLocalUVx = localUVx * outerContribution + innerLocalUVx * innerContribution;\n localUVx = combinedLocalUVx;\n\n // Store innerMask for later use (bloom, etc.) - also tied to activation\n float innerMask = edgeMask * innerEffectActivation;\n\n // Get Phase From: Intensity (Rec. 709 luminance)\n float baseIntensity = luminance(textureSample.rgb);\n\n // Boost intensity in center areas to push them more towards white\n float centerWhiteBoost = 0.1;\n baseIntensity = baseIntensity + innerMask * centerWhiteBoost;\n baseIntensity = clamp(baseIntensity, 0.0, 1.0);\n\n // Keep base intensity for outer colorama (using uGradientMap)\n float outerIntensity = baseIntensity;\n\n // ============================================\n // CENTER GREEN STRIPES OVERLAY (before colorama)\n // ============================================\n float stripeFadeOutDuration = 10.0;\n float stripeFramesSinceStart = uFrameCount - uLightStartFrame;\n // Effect is active UNTIL uLightStartFrame, then fades out\n float stripeEffectActivation = 1.0 - clamp(stripeFramesSinceStart / stripeFadeOutDuration, 0.0, 1.0);\n\n // Animated vertical height mask - grows from 0 to full height\n // Height animation: starts at center (0.5) and expands outward\n float stripeHeightDelay = 15.0; // frames to wait before starting\n float stripeHeightGrowDuration = 80.0; // frames to reach full height\n float stripeHeightProgress = clamp((uFrameCount - stripeHeightDelay) / stripeHeightGrowDuration, 0.0, 1.0);\n // Ease the progress for smoother animation\n float easedHeightProgress = 1.0 - pow(1.0 - stripeHeightProgress, 2.0);\n \n // Calculate animated bounds - expand from center (0.5) outward\n float heightHalfSpan = 0.2 * easedHeightProgress; // 0.06 = half of the 0.12 total height (0.44 to 0.56)\n float softEdge = 0.1 * easedHeightProgress; // Soft edge also scales with progress\n float bottomEdge = 0.5 - heightHalfSpan - softEdge;\n float bottomFull = 0.5 - heightHalfSpan;\n float topFull = 0.5 + heightHalfSpan;\n float topEdge = 0.5 + heightHalfSpan + softEdge;\n \n // When progress is 0, force mask to 0 to avoid glitchy edge at y=0.5\n float stripeHeightMask = easedHeightProgress > 0.001 \n ? smoothstep(bottomEdge, bottomFull, uv.y) * smoothstep(topEdge, topFull, uv.y)\n : 0.0;\n\n vec4 centerGreenSlantedLines = createStripes(\n vUv + vec2(-0.0035, 0.0), // slight offset to avoid moir\xE9\n numSegments,\n uSlitAngle,\n 0.15, // stopPosition (20%)\n vec4(20.0/255.0, 200.0/255.0, 20.0/255.0, 0.2), // colorStart (green at 0%)\n vec4(0.0, 0.0, 0.0, 0.0), // colorMid (white at 20%)\n aspect // aspect ratio for consistent angle\n ) * solidCoreMask * stripeHeightMask;\n\n // Overlay green stripes on center element area\n float stripeOverlayMask = solidCoreMask * stripeEffectActivation;\n // Modify baseIntensity/outerIntensity with stripes before colorama\n vec4 stripedTexture = vec4(textureSample.rgb, 1.0) - centerGreenSlantedLines*0.7 * stripeOverlayMask;\n stripedTexture = clamp(stripedTexture, 0.0, 1.0);\n\n // Update intensity for colorama input\n float stripedIntensity = luminance(stripedTexture.rgb);\n outerIntensity = mix(baseIntensity, stripedIntensity, stripeOverlayMask);\n\n // ============================================\n // LAYER 2: COLORAMA (toggleable)\n // ============================================\n vec3 color;\n if (uEnableColorama > 0.5) {\n // OUTER colorama: uses base intensity, cross-fades between uGradientMap and uGradientMap2\n vec3 outerColoramaResult1 = applyColoramaWithGradient(\n uGradientMap, outerIntensity,\n uInputMin, uInputMax, uModifyGamma, uPosterizeLevels,\n uCycleRepetitions, uPhaseShift, uCycleSpeed,\n uWrapMode, uReverse\n );\n vec3 outerColoramaResult2 = applyColoramaWithGradient(\n uGradientMap2, outerIntensity,\n uInputMin, uInputMax, uModifyGamma, uPosterizeLevels,\n uCycleRepetitions, uPhaseShift, uCycleSpeed,\n uWrapMode, uReverse\n );\n vec3 outerColoramaResult = mix(outerColoramaResult1, outerColoramaResult2, uGradientMapBlend);\n\n // Start with outer colorama as base\n vec3 blendedColorama = outerColoramaResult;\n\n // CENTER colorama: only compute when inside shape (skip texture sample otherwise)\n // This saves a texture lookup + specular pow() for ~90% of pixels\n if (shapeData.shape > 0.001) {\n vec3 centerColoramaResult = applyColoramaWithGradient(\n uCenterGradientMap, shapeData.gradient,\n uInputMin, uInputMax, uModifyGamma, uPosterizeLevels,\n uCycleRepetitions, uPhaseShift, uCycleSpeed,\n uWrapMode, uReverse\n );\n\n // Add specular highlight to center (shiny reflection effect)\n float specularPower = 8.0; // Higher = tighter/smaller highlight\n float specularIntensity = 1.9; // Brightness of the specular\n float specular = pow(shapeData.gradient, specularPower) * specularIntensity;\n centerColoramaResult += vec3(specular); // Add bright highlight\n\n // Blend between outer and center colorama based on shape\n blendedColorama = mix(outerColoramaResult, centerColoramaResult, shapeData.shape);\n }\n\n color = mix(blendedColorama, textureSample.rgb, uBlendWithOriginal);\n } else {\n color = textureSample.rgb;\n }\n\n // Store intensity for bloom (use the blended value)\n float intensity = mix(outerIntensity, shapeData.gradient, shapeData.shape);\n\n // ============================================\n // LAYER 3: BLOOM (toggleable)\n // ============================================\n if (uEnableBloom > 0.5) {\n color = applyBloom(color, intensity, innerMask);\n }\n\n // ============================================\n // LAYER 4: SHAPE HIGHLIGHT (toggleable via light sweep toggle)\n // ============================================\n if (uEnableLightSweep > 0.5) {\n float lightFadeInDuration = 30.0;\n float framesSinceStart = uFrameCount - uLightStartFrame;\n float lightActivation = clamp(framesSinceStart / lightFadeInDuration, 0.0, 1.0);\n\n // Add subtle brightness boost at shape center\n float shapeHighlight = pow(shapeData.gradient, 2.0) * shapeData.shape;\n color += vec3(1.0) * shapeHighlight * 0.15 * uLightIntensity * lightActivation;\n }\n\n // Color correction and edge feathering (shared with ripple mode)\n color = applyColorCorrection(color);\n // Blend with background color if provided (uBackgroundColor.r < 0 means not set)\n if (uBackgroundColor.r >= 0.0) {\n // Calculate luminance to determine how bright the pixel is\n float brightness = luminance(color);\n \n // Blend white/bright areas with background color\n // Bright areas (close to white) become the background color\n float blendStart = 0.94; // Start blending at this brightness\n float blendEnd = 1.0; // Fully background color at this brightness\n float blendAmount = smoothstep(blendStart, blendEnd, brightness);\n \n // Mix the shader color with background color\n color = mix(color, uBackgroundColor, blendAmount);\n }\n\n // Use background color for feathering if set, otherwise default to white\n vec3 featherColor = uBackgroundColor.r >= 0.0 ? uBackgroundColor : vec3(1.0);\n color = applyEdgeFeathering(color, featherColor);\n\n gl_FragColor = vec4(color, 1.0);\n}\n";
export { rzpGlassFragmentShader, rzpGlassVertexShader };
//# sourceMappingURL=rzpGlassShader.js.map