rewrite orb from opaque painted ball to transparent glass lens

The web orb looks like glass because backdrop-filter refracts the
background through it. Android has no backdrop-filter, so the old
approach painted an opaque amber ball with harsh colors (#FFF1D6,
#8D4F00 at 85% alpha) that looked like a sticker, not glass.

New approach: very low alpha accent tint (15-22%) so background
blobs bleed through, creating a lens effect. Light source at
top-left with subtle white radial, warm accent bleed bottom-right,
gentle bottom shadow for volume (not 85% dark brown). Elliptical
specular highlight instead of circular. Double rim (outer hairline
+ inner inset) matching the web's box-shadow approach. Caustic
sweep reduced to 25-30% alpha (was 50-65%).
This commit is contained in:
Bruno Deanoz 2026-06-22 00:35:21 +02:00
parent 2d57afff61
commit f7849025d7

View file

@ -137,8 +137,7 @@ fun KaizenOrb(
.pointerInput(Unit) {},
contentAlignment = Alignment.Center
) {
// --- LAYER 1: Ambient Outer Halo (Subtle larger glow) ---
// This is the ONLY layer that is blurred, providing a soft background glow.
// --- LAYER 1: Ambient Outer Halo ---
Box(
Modifier
.size(size * 1.5f)
@ -147,137 +146,143 @@ fun KaizenOrb(
.background(
Brush.radialGradient(
colors = if (recording) {
listOf(Color(0xFFE0A23E).copy(alpha = 0.45f), Color.Transparent)
listOf(accent.primary.copy(alpha = 0.45f), Color.Transparent)
} else {
listOf(accent.primary.copy(alpha = if (streaming) 0.55f else 0.32f), Color.Transparent)
listOf(accent.primary.copy(alpha = if (streaming) 0.50f else 0.28f), Color.Transparent)
}
),
CircleShape,
),
)
// --- LAYER 2: Glass Sphere body with refracted backgrounds & conic caustics ---
// DRAWN PERFECTLY SHARP (No blur modifier on the Canvas!) to ensure razor-sharp,
// solid circular boundaries that give the orb its heavy, crystal-glass volume.
Canvas(
modifier = Modifier.fillMaxSize()
) {
val w = size.toPx()
val h = size.toPx()
val radius = w / 2f
// Shift centers slightly in direction of gravity to simulate holographic refractive parallax
val centerShiftX = -tiltX * 0.12f * w
val centerShiftY = tiltY * 0.12f * h
// 1. Base Gradient Fill (Refraction glow matching active theme)
val baseGradientCenter = Offset(w * 0.35f + centerShiftX, h * 0.30f + centerShiftY)
drawCircle(
brush = Brush.radialGradient(
colorStops = arrayOf(
0.0f to Color(0xFFFFF1D6), // Ultra bright glowing cream-white core
0.35f to Color(0xFFFFCC80), // Rich luminous warm amber
0.75f to accent.primaryDeep, // Saturated accent
1.0f to Color(0xFF8D4F00) // Deep bronze-amber shadow edge (creates high contrast!)
),
center = baseGradientCenter,
radius = radius * 1.1f
)
)
// 2. Heavy 3D Inner Volume Shadow (Sphere Bottom Shading)
// Adds massive physical 3D weight by darkening the sphere's lower edge.
drawCircle(
brush = Brush.radialGradient(
colorStops = arrayOf(
0.0f to Color.Transparent,
0.5f to Color.Transparent,
1.0f to Color(0xFF4A2300).copy(alpha = 0.85f) // Deep dark brown shadow edge
),
center = Offset(w * 0.35f + centerShiftX, h * 0.30f + centerShiftY),
radius = radius * 1.0f
)
)
// Shift inner core in the opposite direction (parallax depth)
val coreShiftX = tiltX * 0.05f * w
val coreShiftY = -tiltY * 0.05f * h
val coreCenter = Offset(w / 2f + coreShiftX, h / 2f + coreShiftY)
// 3. Conic Caustic Layer #1 (SweepGradient at 30° blended via BlendMode.Overlay)
val sweepBrush1 = Brush.sweepGradient(
colorStops = arrayOf(
0.0f to Color.Transparent,
0.16f to accent.primary.copy(alpha = 0.65f),
0.33f to Color.White.copy(alpha = 0.55f),
0.50f to Color.Transparent,
0.66f to accent.primaryDeep.copy(alpha = 0.50f),
1.0f to Color.Transparent
),
center = coreCenter
)
drawCircle(
brush = sweepBrush1,
radius = radius * 0.84f,
blendMode = BlendMode.Overlay
)
// 4. Conic Caustic Layer #2 (Secondary parallax SweepGradient at 200° via BlendMode.Softlight)
val sweepBrush2 = Brush.sweepGradient(
colorStops = arrayOf(
0.0f to Color.Transparent,
0.25f to Color.White.copy(alpha = 0.40f),
0.50f to Color.Transparent,
0.75f to accent.primary.copy(alpha = 0.50f),
1.0f to Color.Transparent
),
center = coreCenter
)
drawCircle(
brush = sweepBrush2,
radius = radius * 0.72f,
blendMode = BlendMode.Softlight
)
}
// --- LAYER 3: Specular Highlights & Crisp Rim (Sharp reflections) ---
// --- LAYER 2: Glass body — transparent tint so background bleeds through ---
Canvas(modifier = Modifier.fillMaxSize()) {
val w = size.toPx()
val h = size.toPx()
val radius = w / 2f
val shiftX = -tiltX * 0.12f * w
val shiftY = tiltY * 0.12f * h
val lightCenter = Offset(w * 0.30f + shiftX, h * 0.25f + shiftY)
// Specular shifts with gravity to give a high-gloss interactive reflection
val specularShiftX = -tiltX * 0.08f * w
val specularShiftY = tiltY * 0.08f * h
val specularCenter1 = Offset(w * 0.28f + specularShiftX, h * 0.22f + specularShiftY)
val specularCenter2 = Offset(w * 0.70f + specularShiftX, h * 0.82f + specularShiftY)
// 1. Primary "wet" gloss highlight (top-left) - extremely bright, solid, and sharp!
// Base: very subtle theme tint (glass, not paint)
drawCircle(
brush = Brush.radialGradient(
colors = listOf(Color.White, Color.Transparent),
center = specularCenter1,
radius = radius * 0.38f
)
colorStops = arrayOf(
0.0f to accent.primary.copy(alpha = if (isDark) 0.22f else 0.15f),
0.6f to accent.primaryDeep.copy(alpha = if (isDark) 0.18f else 0.10f),
1.0f to accent.primaryDeep.copy(alpha = if (isDark) 0.30f else 0.20f),
),
center = lightCenter,
radius = radius * 1.1f,
),
)
// 2. Secondary ambient bounce highlight (bottom-right edge glow)
// Top-left brightness (light source)
drawCircle(
brush = Brush.radialGradient(
colorStops = arrayOf(
0.0f to Color.White.copy(alpha = if (isDark) 0.25f else 0.40f),
0.5f to Color.White.copy(alpha = 0.05f),
1.0f to Color.Transparent,
),
center = lightCenter,
radius = radius * 0.8f,
),
)
// Bottom-right accent bleed
drawCircle(
brush = Brush.radialGradient(
colorStops = arrayOf(
0.0f to accent.primary.copy(alpha = if (isDark) 0.45f else 0.35f),
0.6f to Color.Transparent,
),
center = Offset(w * 0.72f, h * 0.78f),
radius = radius * 0.65f,
),
)
// Bottom shadow for volume (subtle, not harsh)
drawCircle(
brush = Brush.radialGradient(
colorStops = arrayOf(
0.0f to Color.Transparent,
0.55f to Color.Transparent,
0.85f to Color.Black.copy(alpha = if (isDark) 0.20f else 0.08f),
1.0f to Color.Black.copy(alpha = if (isDark) 0.35f else 0.15f),
),
center = Offset(w * 0.45f, h * 0.40f),
radius = radius,
),
)
// Caustic conic layer (depth structure)
val coreShiftX = tiltX * 0.05f * w
val coreShiftY = -tiltY * 0.05f * h
val coreCenter = Offset(w / 2f + coreShiftX, h / 2f + coreShiftY)
drawCircle(
brush = Brush.sweepGradient(
colorStops = arrayOf(
0.0f to Color.Transparent,
0.16f to accent.primary.copy(alpha = 0.30f),
0.33f to Color.White.copy(alpha = 0.20f),
0.50f to Color.Transparent,
0.66f to accent.primaryDeep.copy(alpha = 0.25f),
1.0f to Color.Transparent,
),
center = coreCenter,
),
radius = radius * 0.84f,
blendMode = BlendMode.Overlay,
)
}
// --- LAYER 3: Specular highlights + rim ---
Canvas(modifier = Modifier.fillMaxSize()) {
val w = size.toPx()
val h = size.toPx()
val radius = w / 2f
val specShiftX = -tiltX * 0.08f * w
val specShiftY = tiltY * 0.08f * h
// Primary specular — elliptical for realism
drawOval(
brush = Brush.radialGradient(
colors = listOf(
Color.White.copy(alpha = if (isDark) 0.70f else 0.90f),
Color.Transparent,
),
center = Offset(w * 0.30f + specShiftX, h * 0.24f + specShiftY),
radius = radius * 0.35f,
),
topLeft = Offset(w * 0.12f + specShiftX, h * 0.10f + specShiftY),
size = androidx.compose.ui.geometry.Size(radius * 0.7f, radius * 0.55f),
)
// Secondary bounce (bottom-right, warm)
drawCircle(
brush = Brush.radialGradient(
colors = listOf(
Color(0xFFFFD180).copy(alpha = 0.80f), // Bright warm amber light refraction
Color.Transparent
accent.primary.copy(alpha = if (isDark) 0.50f else 0.35f),
Color.Transparent,
),
center = Offset(w * 0.72f + specShiftX, h * 0.80f + specShiftY),
radius = radius * 0.25f,
),
center = specularCenter2,
radius = radius * 0.28f
)
)
// 3. Ultra-sharp outer rim (1px hairline highlight for crisp crystal reflection)
// Crisp rim hairline
drawCircle(
color = Color.White.copy(alpha = if (isDark) 0.12f else 0.45f),
color = Color.White.copy(alpha = if (isDark) 0.10f else 0.30f),
radius = radius - 0.5f,
style = Stroke(width = 1.5f)
style = Stroke(width = 1f),
)
// Inner rim (inset edge for refraction feel)
drawCircle(
color = Color.White.copy(alpha = if (isDark) 0.05f else 0.12f),
radius = radius - 2f,
style = Stroke(width = 0.5f),
)
}
}