Skip to content

Instantly share code, notes, and snippets.

@volodymyr-sch
Last active February 13, 2026 10:35
Show Gist options
  • Select an option

  • Save volodymyr-sch/12b5f43e880d33b0fc3023401f037018 to your computer and use it in GitHub Desktop.

Select an option

Save volodymyr-sch/12b5f43e880d33b0fc3023401f037018 to your computer and use it in GitHub Desktop.
val ChaosDark = Color(0xFF0D0D0D)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun ChaosButton(
text: String,
modifier: Modifier = Modifier,
useV2: Boolean = true,
backgroundColor: Color = ChaosDark,
textColor: Color = Color.White,
buttonWidth: Dp = 300.dp,
buttonHeight: Dp = 84.dp
) {
val shader = remember { RuntimeShader(CHAOS_BUTTON_SHADER) }
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
var pressSeed by remember { mutableIntStateOf(0) }
var wasPressed by remember { mutableStateOf(false) }
val scale by animateFloatAsState(
targetValue = if (isPressed) 1.15f else 1.0f,
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
)
val infiniteTransition = rememberInfiniteTransition()
val time by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 100f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 100000, easing = LinearEasing),
repeatMode = RepeatMode.Restart
)
)
LaunchedEffect(isPressed) {
if (isPressed && !wasPressed) {
pressSeed += 1
}
wasPressed = isPressed
}
Box(
modifier = modifier
.width(buttonWidth)
.height(buttonHeight)
.graphicsLayer {
scaleX = scale
scaleY = scale
}
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = {}
),
contentAlignment = Alignment.Center
) {
Canvas(
modifier = Modifier
.width(buttonWidth)
.height(buttonHeight)
.drawWithCache {
shader.setFloatUniform("iResolution", size.width, size.height)
// Set background color
shader.setFloatUniform(
"backgroundColor",
backgroundColor.red,
backgroundColor.green,
backgroundColor.blue,
backgroundColor.alpha
)
shader.setFloatUniform("iTime", time)
shader.setFloatUniform("iPress", if (isPressed) 1f else 0f)
shader.setFloatUniform("iPressSeed", pressSeed.toFloat())
shader.setFloatUniform("iVersion", if (useV2) 1f else 0f)
val paint = Paint().apply {
this.shader = shader
}
onDrawBehind {
drawIntoCanvas { canvas ->
canvas.drawRect(
0f,
0f,
size.width,
size.height,
paint
)
}
}
}
) {
// Canvas drawing happens in drawWithCache
}
// Text overlay
Text(
text = text,
color = textColor,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center
)
}
}
@Language("AGSL")
internal const val CHAOS_BUTTON_SHADER = """
uniform float2 iResolution;
uniform half4 backgroundColor;
uniform float iTime;
uniform float iPress;
uniform float iPressSeed;
uniform float iVersion;
float3 hash3(float3 p) {
p = float3(dot(p, float3(127.1, 311.7, 74.7)),
dot(p, float3(269.5, 183.3, 246.1)),
dot(p, float3(113.5, 271.9, 124.6)));
return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}
float segmentDistance(float2 p, float2 a, float2 b) {
float2 pa = p - a;
float2 ba = b - a;
float denom = max(dot(ba, ba), 1e-4);
float h = clamp(dot(pa, ba) / denom, 0.0, 1.0);
return length(pa - ba * h);
}
float2 bezier2(float2 a, float2 c, float2 b, float t) {
float u = 1.0 - t;
return u * u * a + 2.0 * u * t * c + t * t * b;
}
float curveDistanceWobble(
float2 p,
float2 a,
float2 c,
float2 b,
float2 perp,
float wobbleAmp,
float wobbleFreq,
float wobblePhase
) {
float2 p0 = a;
float2 p1 = bezier2(a, c, b, 0.125);
float2 p2 = bezier2(a, c, b, 0.25);
float2 p3 = bezier2(a, c, b, 0.375);
float2 p4 = bezier2(a, c, b, 0.5);
float2 p5 = bezier2(a, c, b, 0.625);
float2 p6 = bezier2(a, c, b, 0.75);
float2 p7 = bezier2(a, c, b, 0.875);
float2 p8 = b;
float phaseScale = 6.2831;
float t1 = wobblePhase + wobbleFreq * 0.125 * phaseScale;
float t2 = wobblePhase + wobbleFreq * 0.25 * phaseScale;
float t3 = wobblePhase + wobbleFreq * 0.375 * phaseScale;
float t4 = wobblePhase + wobbleFreq * 0.5 * phaseScale;
float t5 = wobblePhase + wobbleFreq * 0.625 * phaseScale;
float t6 = wobblePhase + wobbleFreq * 0.75 * phaseScale;
float t7 = wobblePhase + wobbleFreq * 0.875 * phaseScale;
p1 += perp * (sin(t1) * wobbleAmp);
p2 += perp * (sin(t2) * wobbleAmp);
p3 += perp * (sin(t3) * wobbleAmp);
p4 += perp * (sin(t4) * wobbleAmp);
p5 += perp * (sin(t5) * wobbleAmp);
p6 += perp * (sin(t6) * wobbleAmp);
p7 += perp * (sin(t7) * wobbleAmp);
float d0 = segmentDistance(p, p0, p1);
float d1 = segmentDistance(p, p1, p2);
float d2 = segmentDistance(p, p2, p3);
float d3 = segmentDistance(p, p3, p4);
float d4 = segmentDistance(p, p4, p5);
float d5 = segmentDistance(p, p5, p6);
float d6 = segmentDistance(p, p6, p7);
float d7 = segmentDistance(p, p7, p8);
return min(min(min(d0, d1), min(d2, d3)), min(min(d4, d5), min(d6, d7)));
}
float3 neonPalette(float t) {
float3 red = float3(1.0, 0.2, 0.2);
float3 green = float3(0.2, 1.0, 0.45);
float3 navy = float3(0.05, 0.12, 0.45);
float3 deepBlue = float3(0.08, 0.18, 0.7);
float3 indigo = float3(0.18, 0.12, 0.85);
float3 violet = float3(0.65, 0.25, 0.95);
float3 pink = float3(1.0, 0.3, 0.75);
float3 c = red;
c = mix(c, green, step(0.16, t));
c = mix(c, navy, step(0.33, t));
c = mix(c, deepBlue, step(0.5, t));
c = mix(c, indigo, step(0.66, t));
c = mix(c, violet, step(0.78, t));
c = mix(c, pink, step(0.9, t));
return c;
}
float3 coolPalette(float t) {
float3 cyan = float3(0.1, 0.9, 1.0);
float3 teal = float3(0.05, 0.7, 0.9);
float3 blue = float3(0.1, 0.35, 0.95);
float3 deepBlue = float3(0.05, 0.18, 0.55);
float3 sky = float3(0.4, 0.85, 1.0);
float3 c = cyan;
c = mix(c, teal, step(0.2, t));
c = mix(c, blue, step(0.45, t));
c = mix(c, deepBlue, step(0.7, t));
c = mix(c, sky, step(0.88, t));
return c;
}
float3 warmPalette(float t) {
float3 gold = float3(1.0, 0.75, 0.2);
float3 orange = float3(1.0, 0.45, 0.15);
float3 coral = float3(1.0, 0.25, 0.35);
float3 magenta = float3(0.95, 0.15, 0.65);
float3 pink = float3(1.0, 0.35, 0.75);
float3 c = gold;
c = mix(c, orange, step(0.18, t));
c = mix(c, coral, step(0.42, t));
c = mix(c, magenta, step(0.68, t));
c = mix(c, pink, step(0.85, t));
return c;
}
float3 pickPalette(float t, float palettePick) {
if (palettePick < 0.5) {
return neonPalette(t);
}
if (palettePick < 1.5) {
return coolPalette(t);
}
return warmPalette(t);
}
// SDF
float stadiumSDF(float2 p, float2 size) {
float radius = size.y;
float halfWidth = size.x - radius;
p.x = abs(p.x) - halfWidth;
p.x = max(p.x, 0.0);
return length(p) - radius;
}
float starfield(float2 uv, float radius) {
float2 cellCoord = fract(uv) - 0.5;
float2 cellID = floor(uv);
float3 cellHashValue = hash3(float3(cellID, 1.0));
float2 starPosition = cellHashValue.xy * (0.5 - radius * 16.0);
float baseBrightness = clamp(cellHashValue.z, 0.0, 1.0);
float brightMask = smoothstep(0.85, 0.95, baseBrightness);
float starBrightness = pow(baseBrightness, 0.5) * mix(1.0, 2.0, brightMask);
float d = length(cellCoord - starPosition);
float radiusBoost = mix(1.0, 1.8, brightMask);
float glow = exp(-1.2 * d / (radius * radiusBoost));
float coreGlow = exp(-3.0 * d / (radius * 0.6)) * brightMask * 0.8;
return clamp((glow + coreGlow) * starBrightness, 0.0, 1.0);
}
float3 curveOverlay(float2 uv, float time, float aspectRatio, float pressSeed) {
float timeScaled = time * 4.0;
float frame = floor(timeScaled);
float frameNext = frame + 1.0;
float tBlend = fract(timeScaled);
float blend = tBlend * tBlend * (3.0 - 2.0 * tBlend);
float3 pressSeedHash = hash3(float3(pressSeed, 91.0, 7.0));
float countRand = clamp(pressSeedHash.x * 0.5 + 0.5, 0.0, 1.0);
float count = floor(12.0 + 8.0 * countRand);
count = min(count, 20.0);
float paletteRand = clamp(pressSeedHash.y * 0.5 + 0.5, 0.0, 1.0);
float palettePick = min(floor(paletteRand * 3.0), 2.0);
float2 uvAspect = float2((uv.x - 0.5) * aspectRatio + 0.5, uv.y);
float2 center = float2(0.5, 0.5);
float2 box = float2(0.5 * aspectRatio, 0.5);
float3 acc = float3(0.0);
for (int i = 0; i < 20; ++i) {
float fi = float(i);
float active = step(fi, count - 0.5);
if (active <= 0.0) {
continue;
}
float3 seedA = hash3(float3(frame, fi, 11.0));
float3 seedB = hash3(float3(frameNext, fi, 11.0));
float3 seed = mix(seedA, seedB, blend);
float3 seed2A = hash3(float3(frame, fi, 23.0));
float3 seed2B = hash3(float3(frameNext, fi, 23.0));
float3 seed2 = mix(seed2A, seed2B, blend);
float3 seed3A = hash3(float3(frame, fi, 37.0));
float3 seed3B = hash3(float3(frameNext, fi, 37.0));
float3 seed3 = mix(seed3A, seed3B, blend);
float rAngle = clamp(seed.x * 0.5 + 0.5, 0.0, 1.0);
float rCurve = clamp(seed.y * 0.5 + 0.5, 0.0, 1.0);
float rWidth = clamp(seed.z * 0.5 + 0.5, 0.0, 1.0);
float rOffsetX = clamp(seed2.x * 0.5 + 0.5, 0.0, 1.0);
float rOffsetY = clamp(seed2.y * 0.5 + 0.5, 0.0, 1.0);
float rPalette = clamp(seed2.z * 0.5 + 0.5, 0.0, 1.0);
float rWobbleFreq = clamp(seed3.x * 0.5 + 0.5, 0.0, 1.0);
float rWobbleAmp = clamp(seed3.y * 0.5 + 0.5, 0.0, 1.0);
float rCircle = clamp(seed3.z * 0.5 + 0.5, 0.0, 1.0);
float angle = rAngle * 6.2831;
float2 dir = float2(cos(angle), sin(angle));
float2 perp = float2(-dir.y, dir.x);
float2 baseOffset = float2(rOffsetX - 0.5, rOffsetY - 0.5);
float2 offsetDir = normalize(baseOffset + float2(1e-4, 1e-4));
float outward = mix(0.08, 0.22, rWobbleFreq);
float2 offset = baseOffset * float2(0.35 * aspectRatio, 0.35)
+ offsetDir * outward * float2(aspectRatio, 1.0);
float2 c0 = center + offset;
float2 dirAbs = max(abs(dir), float2(1e-3));
float tMax = min(box.x / dirAbs.x, box.y / dirAbs.y);
float2 a = c0 - dir * tMax * 1.5;
float2 b = c0 + dir * tMax * 1.5;
float curveBias = mix(rCurve, 0.5, 0.2);
float curveRand = mix(0.8, 1.2, rWobbleAmp);
float curvature = (curveBias - 0.5) * 3.2 * curveRand;
float2 c = c0 + perp * curvature * min(box.x, box.y);
float wobbleAmp = mix(0.02, 0.09, rWobbleAmp) * min(box.x, box.y);
float wobbleFreq = mix(0.8, 2.8, rWobbleFreq);
float wobblePhase = rOffsetX * 6.2831 + rOffsetY * 3.1415;
float d = 0.0;
if (rCircle > 0.78) {
float radius = mix(min(box.x, box.y) * 0.8, max(box.x, box.y) * 1.8, curveBias);
float2 circleJitter = float2(rOffsetX - 0.5, rOffsetY - 0.5) * float2(0.18 * aspectRatio, 0.18);
d = abs(length(uvAspect - (c0 + circleJitter)) - radius);
} else {
d = curveDistanceWobble(uvAspect, a, c, b, perp, wobbleAmp, wobbleFreq, wobblePhase);
}
float width = mix(0.0035, 0.012, rWidth);
float glowStrength = 0.2;
float bloomStrength = 0.4;
float coreStrength = 1.0;
float glow = exp(-2.0 * d / width) * 1.6 * glowStrength;
float halo = exp(-4.5 * d / (width * 2.4)) * 1.8 * glowStrength;
float core = exp(-7.0 * d / width) * 2.0 * coreStrength;
float bloom = exp(-2.0 * d / (width * 4.8)) * 1.4 * bloomStrength;
float3 neon = pickPalette(rPalette, palettePick);
acc += neon * (core + glow + halo + bloom) * 2.0 * active;
}
return acc;
}
half4 main(float2 fragCoord) {
float2 uv = fragCoord / iResolution;
float2 center = float2(0.5);
float aspectRatio = iResolution.x / iResolution.y;
float2 p = uv - center;
p.x *= aspectRatio;
float margin = 0.08;
float2 size = float2((0.5 - margin) * aspectRatio, 0.5 - margin * 2.0);
float dist = stadiumSDF(p, size);
// Anti-aliasing
float pixelSize = 1.0 / min(iResolution.x, iResolution.y);
float aa = pixelSize * 2.0;
// Inside: backgroundColor + stars, Outside: transparent
float alpha = smoothstep(aa, -aa, dist);
float3 color = backgroundColor.rgb;
if (dist < 0.0) {
// Scale UV for star density (more = denser stars)
float2 starUV = (uv + iTime * float2(0.1, 0.05)) * float2(45.0, 22.5);
float stars = starfield(starUV, 0.05);
color = mix(backgroundColor.rgb, float3(1.0), stars);
}
// Add white glow border at the edge (-0.02 to +0.02 from SDF=0)
float borderGlow = 1.0 - smoothstep(0.0, 0.02, abs(dist));
color = mix(color, float3(1.0), borderGlow * 0.5);
if (dist < 0.0) {
float3 lines = float3(0.0);
lines = curveOverlay(uv, iTime, aspectRatio, iPressSeed);
color = clamp(color + lines * iPress, 0.0, 1.0);
}
return half4(color, alpha);
}
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment