float4 balatroSwirl(float2 screenSize, float2 screenCoords, float time, float seed)
{
	const float pi = 3.14159265359;
	float safeScreenLength = max(length(screenSize), 1.0);
	float2 seedOffset = float2(sin(seed * 6.2831853), cos(seed * 6.2831853)) * 0.035;
	float2 uv = (screenCoords - 0.5 * screenSize) / safeScreenLength - offset - seedOffset;
	float uvLength = length(uv);

	// First warp: convert to polar space and twist the angle more near the
	// center, creating the large spiral motion.
	float speed = spinRotation * spinEase * 0.2;
	if (isRotate)
		speed = time * speed;
	speed += 302.2 + seed * 6.2831853;

	float newPixelAngle = atan2(uv.y, uv.x) + speed - spinEase * 20.0 * (spinAmount * uvLength + (1.0 - spinAmount));
	float2 mid = (screenSize / safeScreenLength) * 0.5;
	uv = float2(uvLength * cos(newPixelAngle) + mid.x, uvLength * sin(newPixelAngle) + mid.y) - mid;

	uv *= 30.0;
	speed = (time + seed * 17.0) * spinSpeed;
	float2 uv2 = float2(uv.x + uv.y, uv.x + uv.y);

	// Second warp: a short iterative feedback loop turns the spiral into
	// painterly bands while preserving a fixed compile-time loop bound.
	for (int i = 0; i < 5; ++i)
	{
		uv2 += float2(sin(max(uv.x, uv.y)), sin(max(uv.x, uv.y))) + uv;
		uv += 0.5 * float2(cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121), sin(uv2.x - 0.113 * speed));
		float warp = cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);
		uv -= float2(warp, warp);
	}

	float contrastMod = 0.25 * contrast + 0.5 * spinAmount + 1.2;
	float paintRes = min(2.0, max(0.0, length(uv) * 0.035 * contrastMod));
	float c1p = max(0.0, 1.0 - contrastMod * abs(1.0 - paintRes));
	float c2p = max(0.0, 1.0 - contrastMod * abs(paintRes));
	float c3p = 1.0 - min(1.0, c1p + c2p);
	// Three soft band weights drive the palette; lighting rides on the brightest
	// bands so the swirl keeps dimensional highlights.
	float light = (lighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + lighting * max(c2p * 5.0 - 4.0, 0.0);

	float safeContrast = max(contrast, 0.001);
	float4 base = (0.3 / safeContrast) * colour1;
	float4 paint = colour1 * c1p + colour2 * c2p + float4(c3p * colour3.rgb, c3p * colour1.a);
	return base + (1.0 - 0.3 / safeContrast) * paint + float4(light, light, light, light);
}

float4 shadeVideo(ShaderContext context)
{
	float2 screenSize = max(context.outputResolution, float2(1.0, 1.0));
	float4 swirl = balatroSwirl(screenSize, context.uv * screenSize, context.time, context.startupRandom);
	return saturate(lerp(swirl, context.sourceColor, sourceMix));
}
