package com.rizoma.orbita.ui import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import com.rizoma.orbita.ui.theme.NeonCian import com.rizoma.orbita.ui.theme.NeonRosa import com.rizoma.orbita.ui.theme.NeonVerde import com.rizoma.orbita.ui.theme.Vacio import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin private data class Blob( val color: Color, val periodMs: Int, val ax: Float, val ay: Float, // amplitud relativa del vaivén val ox: Float, val oy: Float, // origen relativo val freqY: Float, // relación de frecuencia vertical (Lissajous) val phase: Float, ) private val blobs = listOf( Blob(NeonVerde, 49_000, 0.34f, 0.30f, 0.42f, 0.40f, 1.30f, 0f), Blob(NeonRosa, 67_000, 0.38f, 0.26f, 0.58f, 0.62f, 0.80f, 2.1f), Blob(NeonCian, 83_000, 0.30f, 0.34f, 0.50f, 0.30f, 1.70f, 4.0f), ) /** * Fondo dinámico congruente con el rizoma: manchas de luz neón que derivan en * trayectorias de Lissajous y laten suavemente, mezcladas de forma aditiva sobre el vacío. */ @Composable fun DynamicBackground(modifier: Modifier = Modifier) { val transition = rememberInfiniteTransition(label = "fondo") val tau = (2 * PI).toFloat() // Una fase por mancha (llamadas directas: animateFloat es @Composable, no cabe en un map/función local). val p0 by transition.animateFloat( 0f, tau, infiniteRepeatable(tween(blobs[0].periodMs, easing = LinearEasing), RepeatMode.Restart), label = "p0", ) val p1 by transition.animateFloat( 0f, tau, infiniteRepeatable(tween(blobs[1].periodMs, easing = LinearEasing), RepeatMode.Restart), label = "p1", ) val p2 by transition.animateFloat( 0f, tau, infiniteRepeatable(tween(blobs[2].periodMs, easing = LinearEasing), RepeatMode.Restart), label = "p2", ) val phases = floatArrayOf(p0, p1, p2) Canvas(modifier.fillMaxSize()) { drawRect(Vacio) val w = size.width val h = size.height val base = maxOf(w, h) blobs.forEachIndexed { i, blob -> val t = phases[i] + blob.phase val cx = w * (blob.ox + blob.ax * cos(t)) val cy = h * (blob.oy + blob.ay * sin(t * blob.freqY)) val pulse = 0.82f + 0.18f * sin(t * 1.7f) val radius = base * 0.62f * pulse drawCircle( brush = Brush.radialGradient( colors = listOf(blob.color.copy(alpha = 0.16f), Color.Transparent), center = Offset(cx, cy), radius = radius, ), radius = radius, center = Offset(cx, cy), blendMode = BlendMode.Plus, ) } } }