package com.peachpasswords.ui.settings import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.CubicBezierEasing import androidx.compose.animation.core.keyframes import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.peachpasswords.ui.components.PeachMark import com.peachpasswords.ui.theme.LocalPeachAccent import com.peachpasswords.ui.theme.LocalReduceMotion import com.peachpasswords.ui.theme.peachColors import com.peachpasswords.ui.theme.PeachType import kotlinx.coroutines.delay import kotlinx.coroutines.launch // CSS cubic-bezier(0.16, 1, 0.3, 1) — the browser's bloom/wordmark-in easing // (a pronounced ease-out-expo feel). private val ExpoOut = CubicBezierEasing(0.16f, 1f, 0.3f, 1f) /** * Full-screen Pro activation celebration. Ports the browser's `.peach-pro-celebration` * (ProCelebration.tsx + popup.css): two corner accent blooms behind a near-black * shell, PeachMark with a glow pulse (`peach-pro-bloom`), then "peach pro" / * "thank you." fading up in a stagger (`peach-pro-wordmark-in`). Holds ~1800ms, * fades out over 400ms (`is-leaving`), then calls [onDone]. A tap skips straight * to [onDone] without playing the exit fade. */ @Composable fun ProCelebration(onDone: () -> Unit) { val accent = LocalPeachAccent.current val reduceMotion = LocalReduceMotion.current val glow = remember { Animatable(0f) } val wordmarkAlpha = remember { Animatable(0f) } val wordmarkOffset = remember { Animatable(4f) } val thankyouAlpha = remember { Animatable(0f) } val thankyouOffset = remember { Animatable(4f) } val exitAlpha = remember { Animatable(1f) } LaunchedEffect(Unit) { if (reduceMotion) { wordmarkAlpha.snapTo(1f); wordmarkOffset.snapTo(0f) thankyouAlpha.snapTo(1f); thankyouOffset.snapTo(0f) } else { launch { // peach-pro-bloom: 0 -> peak at 40% -> 0, over 600ms. glow.animateTo(1f, keyframes { durationMillis = 600; 0f at 0; 1f at 240; 0f at 600 }) } launch { delay(500) launch { wordmarkAlpha.animateTo(1f, tween(400, easing = ExpoOut)) } wordmarkOffset.animateTo(0f, tween(400, easing = ExpoOut)) } launch { delay(800) launch { thankyouAlpha.animateTo(1f, tween(400, easing = ExpoOut)) } thankyouOffset.animateTo(0f, tween(400, easing = ExpoOut)) } } delay(1800) if (!reduceMotion) exitAlpha.animateTo(0f, tween(400)) onDone() } Box( modifier = Modifier .fillMaxSize() .graphicsLayer { alpha = exitAlpha.value } .background(peachColors.Bg) .clickable(onClick = onDone), contentAlignment = Alignment.Center, ) { // Two corner blooms (radial-gradient(...at 0% 0%) / (...at 100% 100%) // in popup.css), replacing the old single centred flat-alpha circle. Canvas(Modifier.fillMaxSize()) { drawRect( brush = Brush.radialGradient( colors = listOf(accent.copy(alpha = 0.16f), accent.copy(alpha = 0f)), center = Offset.Zero, radius = size.maxDimension * 0.9f, ), ) drawRect( brush = Brush.radialGradient( colors = listOf(accent.copy(alpha = 0.12f), accent.copy(alpha = 0f)), center = Offset(size.width, size.height), radius = size.maxDimension * 0.9f, ), ) } Column(horizontalAlignment = Alignment.CenterHorizontally) { Box(Modifier.size(180.dp), contentAlignment = Alignment.Center) { // Soft glow behind the mark, standing in for the CSS drop-shadow // pulse — a portable Compose equivalent that works below API 31 // (Modifier.blur's minimum), unlike a true blur filter. Canvas(Modifier.fillMaxSize()) { if (glow.value > 0f) { drawCircle( brush = Brush.radialGradient( colors = listOf(accent.copy(alpha = 0.45f * glow.value), accent.copy(alpha = 0f)), radius = 90.dp.toPx(), ), radius = 90.dp.toPx(), ) } } PeachMark(size = 80.dp) } Text( "peach pro", color = accent, style = TextStyle(fontFamily = PeachType.Display, fontStyle = FontStyle.Italic, fontSize = 44.sp), textAlign = TextAlign.Center, modifier = Modifier .padding(top = 18.dp) .offset(y = wordmarkOffset.value.dp) .graphicsLayer { alpha = wordmarkAlpha.value }, ) Text( "thank you.", color = peachColors.T1, style = TextStyle(fontFamily = PeachType.Display, fontStyle = FontStyle.Italic, fontSize = 26.sp), textAlign = TextAlign.Center, modifier = Modifier .padding(top = 6.dp) .offset(y = thankyouOffset.value.dp) .graphicsLayer { alpha = thankyouAlpha.value }, ) } } }