diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a3e1fb7..724dbcf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -42,42 +42,50 @@
Переключается в рантайме (LauncherIconManager) при смене темы. -->
+ android:icon="@mipmap/ic_launcher_forest" android:roundIcon="@mipmap/ic_launcher_forest_round"
+ android:theme="@style/Theme.Radiola.Splash.Forest">
+ android:icon="@mipmap/ic_launcher_ocean" android:roundIcon="@mipmap/ic_launcher_ocean_round"
+ android:theme="@style/Theme.Radiola.Splash.Ocean">
+ android:icon="@mipmap/ic_launcher_sunset" android:roundIcon="@mipmap/ic_launcher_sunset_round"
+ android:theme="@style/Theme.Radiola.Splash.Sunset">
+ android:icon="@mipmap/ic_launcher_amethyst" android:roundIcon="@mipmap/ic_launcher_amethyst_round"
+ android:theme="@style/Theme.Radiola.Splash.Amethyst">
+ android:icon="@mipmap/ic_launcher_neon" android:roundIcon="@mipmap/ic_launcher_neon_round"
+ android:theme="@style/Theme.Radiola.Splash.Neon">
+ android:icon="@mipmap/ic_launcher_amber" android:roundIcon="@mipmap/ic_launcher_amber_round"
+ android:theme="@style/Theme.Radiola.Splash.Amber">
+ android:icon="@mipmap/ic_launcher_ice" android:roundIcon="@mipmap/ic_launcher_ice_round"
+ android:theme="@style/Theme.Radiola.Splash.Ice">
+ android:icon="@mipmap/ic_launcher_rose" android:roundIcon="@mipmap/ic_launcher_rose_round"
+ android:theme="@style/Theme.Radiola.Splash.Rose">
diff --git a/app/src/main/java/com/radiola/MainActivity.kt b/app/src/main/java/com/radiola/MainActivity.kt
index 04791c4..de652be 100644
--- a/app/src/main/java/com/radiola/MainActivity.kt
+++ b/app/src/main/java/com/radiola/MainActivity.kt
@@ -61,20 +61,39 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
- startService(Intent(this, PlayerService::class.java))
lifecycleScope.launch {
tokenDataStore.preload()
+ // Старт плеер-сервиса уводим с критического пути запуска — ускоряет
+ // появление первого кадра (сплэша).
+ startService(Intent(this@MainActivity, PlayerService::class.java))
}
ensureBackgroundPlaybackAllowed()
enableEdgeToEdge()
+ // Тему берём из быстрого SharedPreferences (его пишет LauncherIconManager при
+ // смене темы) — синхронно и МГНОВЕННО, без блокировки первого кадра. Так сплэш
+ // и приложение сразу нужного цвета, и тёмный системный сплэш не висит лишнее.
+ val initialPaletteId = getSharedPreferences("radiola_prefs", MODE_PRIVATE)
+ .getString("icon_alias", "forest") ?: "forest"
setContent {
// Выбранная цветовая тема (мгновенно перекрашивает всё приложение).
- val paletteId by settingsRepository.getThemePalette().collectAsState(initial = "forest")
+ val paletteId by settingsRepository.getThemePalette().collectAsState(initial = initialPaletteId)
// Иконка лаунчера следует теме (срабатывает на старте и при смене темы).
LaunchedEffect(paletteId) {
launcherIconManager.applyIfNeeded(com.radiola.ui.theme.ThemePalette.fromId(paletteId))
}
RadiolaTheme(palette = com.radiola.ui.theme.ThemePalette.fromId(paletteId)) {
+ // Сплэш рисуем на ПЕРВОМ (дешёвом) кадре; тяжёлый контент (ViewModels,
+ // плеер) композим следующим кадром ПОД сплэшем — так логотип появляется
+ // почти сразу, без долгого тёмного ожидания холодного старта.
+ var showSplash by remember { mutableStateOf(true) }
+ var contentReady by remember { mutableStateOf(false) }
+ LaunchedEffect(Unit) { contentReady = true }
+ LaunchedEffect(Unit) {
+ kotlinx.coroutines.delay(1600)
+ showSplash = false
+ }
+
+ if (contentReady) {
val navController = rememberNavController()
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var showPlayer by remember { mutableStateOf(false) }
@@ -298,6 +317,20 @@ class MainActivity : ComponentActivity() {
onDismiss = { if (!update.forceUpdate) pendingUpdate = null }
)
}
+
+ } // конец if (contentReady): тяжёлый контент композится под сплэшем
+
+ // Тематический экран загрузки поверх всего (рисуем сами — системный
+ // сплэш Android 12+ нельзя перекрасить под выбранную тему).
+ androidx.compose.animation.AnimatedVisibility(
+ visible = showSplash,
+ enter = androidx.compose.animation.EnterTransition.None,
+ exit = androidx.compose.animation.fadeOut(androidx.compose.animation.core.tween(450))
+ ) {
+ com.radiola.ui.components.SplashOverlay(
+ com.radiola.ui.theme.ThemePalette.fromId(paletteId)
+ )
+ }
}
}
}
diff --git a/app/src/main/java/com/radiola/ui/components/SplashOverlay.kt b/app/src/main/java/com/radiola/ui/components/SplashOverlay.kt
new file mode 100644
index 0000000..550b56f
--- /dev/null
+++ b/app/src/main/java/com/radiola/ui/components/SplashOverlay.kt
@@ -0,0 +1,96 @@
+package com.radiola.ui.components
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.blur
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.radiola.ui.theme.RadiolaWordmark
+import com.radiola.ui.theme.ThemePalette
+import kotlinx.coroutines.launch
+
+/**
+ * Свой экран загрузки: тематический 3D-логотип с акцентным свечением под цвет темы,
+ * мягкой тенью и плавным появлением. Рисуем сами (а не системный сплэш), потому что
+ * Android 12+ не даёт менять иконку системного сплэша под выбранную пользователем тему.
+ */
+@Composable
+fun SplashOverlay(palette: ThemePalette, modifier: Modifier = Modifier) {
+ val colors = palette.colors
+ // Появление снапное: лого видно почти сразу (короткий fade), затем мягкий «вдох».
+ val scale = remember { Animatable(0.92f) }
+ val fade = remember { Animatable(0f) }
+ LaunchedEffect(Unit) {
+ launch { fade.animateTo(1f, tween(200)) }
+ launch { scale.animateTo(1f, tween(420, easing = FastOutSlowInEasing)) }
+ }
+ Box(
+ modifier = modifier.fillMaxSize().background(colors.bgBase),
+ contentAlignment = Alignment.Center
+ ) {
+ // Акцентное свечение под цвет темы
+ Box(
+ Modifier
+ .size(380.dp)
+ .graphicsLayer { alpha = fade.value }
+ .background(
+ Brush.radialGradient(
+ listOf(
+ colors.accent.copy(alpha = 0.32f),
+ colors.accent.copy(alpha = 0.10f),
+ Color.Transparent
+ )
+ )
+ )
+ )
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Box(contentAlignment = Alignment.Center) {
+ // Мягкая тень логотипа
+ Image(
+ painter = painterResource(palette.logoRes),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(Color.Black.copy(alpha = 0.5f)),
+ modifier = Modifier
+ .size(176.dp)
+ .scale(scale.value)
+ .offset(y = 14.dp)
+ .blur(18.dp)
+ .graphicsLayer { alpha = fade.value }
+ )
+ // Логотип
+ Image(
+ painter = painterResource(palette.logoRes),
+ contentDescription = "radiOLA",
+ modifier = Modifier
+ .size(176.dp)
+ .scale(scale.value)
+ .graphicsLayer { alpha = fade.value }
+ )
+ }
+ Spacer(Modifier.height(20.dp))
+ Box(Modifier.graphicsLayer { alpha = fade.value }) {
+ RadiolaWordmark(fontSize = 30)
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/drawable-nodpi/ic_bg_amber.png b/app/src/main/res/drawable-nodpi/ic_bg_amber.png
new file mode 100644
index 0000000..66d3ad4
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_amber.png differ
diff --git a/app/src/main/res/drawable-nodpi/ic_bg_amethyst.png b/app/src/main/res/drawable-nodpi/ic_bg_amethyst.png
new file mode 100644
index 0000000..4b3e1a9
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_amethyst.png differ
diff --git a/app/src/main/res/drawable-nodpi/ic_bg_forest.png b/app/src/main/res/drawable-nodpi/ic_bg_forest.png
new file mode 100644
index 0000000..a3d065d
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_forest.png differ
diff --git a/app/src/main/res/drawable-nodpi/ic_bg_ice.png b/app/src/main/res/drawable-nodpi/ic_bg_ice.png
new file mode 100644
index 0000000..7635682
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_ice.png differ
diff --git a/app/src/main/res/drawable-nodpi/ic_bg_neon.png b/app/src/main/res/drawable-nodpi/ic_bg_neon.png
new file mode 100644
index 0000000..6e1e024
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_neon.png differ
diff --git a/app/src/main/res/drawable-nodpi/ic_bg_ocean.png b/app/src/main/res/drawable-nodpi/ic_bg_ocean.png
new file mode 100644
index 0000000..524c1d1
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_ocean.png differ
diff --git a/app/src/main/res/drawable-nodpi/ic_bg_rose.png b/app/src/main/res/drawable-nodpi/ic_bg_rose.png
new file mode 100644
index 0000000..841d3d3
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_rose.png differ
diff --git a/app/src/main/res/drawable-nodpi/ic_bg_sunset.png b/app/src/main/res/drawable-nodpi/ic_bg_sunset.png
new file mode 100644
index 0000000..b568d96
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_bg_sunset.png differ
diff --git a/app/src/main/res/drawable-nodpi/splash_logo_amber.png b/app/src/main/res/drawable-nodpi/splash_logo_amber.png
new file mode 100644
index 0000000..efa4976
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_amber.png differ
diff --git a/app/src/main/res/drawable-nodpi/splash_logo_amethyst.png b/app/src/main/res/drawable-nodpi/splash_logo_amethyst.png
new file mode 100644
index 0000000..013a796
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_amethyst.png differ
diff --git a/app/src/main/res/drawable-nodpi/splash_logo_forest.png b/app/src/main/res/drawable-nodpi/splash_logo_forest.png
new file mode 100644
index 0000000..5c3b73c
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_forest.png differ
diff --git a/app/src/main/res/drawable-nodpi/splash_logo_ice.png b/app/src/main/res/drawable-nodpi/splash_logo_ice.png
new file mode 100644
index 0000000..8eaf931
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_ice.png differ
diff --git a/app/src/main/res/drawable-nodpi/splash_logo_neon.png b/app/src/main/res/drawable-nodpi/splash_logo_neon.png
new file mode 100644
index 0000000..3383692
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_neon.png differ
diff --git a/app/src/main/res/drawable-nodpi/splash_logo_ocean.png b/app/src/main/res/drawable-nodpi/splash_logo_ocean.png
new file mode 100644
index 0000000..376f1d6
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_ocean.png differ
diff --git a/app/src/main/res/drawable-nodpi/splash_logo_rose.png b/app/src/main/res/drawable-nodpi/splash_logo_rose.png
new file mode 100644
index 0000000..81bb0aa
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_rose.png differ
diff --git a/app/src/main/res/drawable-nodpi/splash_logo_sunset.png b/app/src/main/res/drawable-nodpi/splash_logo_sunset.png
new file mode 100644
index 0000000..7bf6f97
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/splash_logo_sunset.png differ
diff --git a/app/src/main/res/drawable/splash_transparent.xml b/app/src/main/res/drawable/splash_transparent.xml
new file mode 100644
index 0000000..057f153
--- /dev/null
+++ b/app/src/main/res/drawable/splash_transparent.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 015762b..6d02a6a 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml
index 279f7bc..0cd6a7f 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber_round.xml
index 279f7bc..0cd6a7f 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber_round.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst.xml
index 8afb6f2..9da4444 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst_round.xml
index 8afb6f2..9da4444 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amethyst_round.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml
index 6840300..0747f26 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest_round.xml
index 6840300..0747f26 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest_round.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice.xml
index 470f0e1..6cd4f40 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice_round.xml
index 470f0e1..6cd4f40 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ice_round.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon.xml
index ca6c953..2ce13f7 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon_round.xml
index ca6c953..2ce13f7 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_neon_round.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean.xml
index 7d788fa..baed001 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean_round.xml
index 7d788fa..baed001 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ocean_round.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose.xml
index 1e2976d..7feee71 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_round.xml
index 1e2976d..7feee71 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_round.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 015762b..6d02a6a 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset.xml
index e145e77..acf3874 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset_round.xml
index e145e77..acf3874 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_sunset_round.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index a000b81..3fc1852 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -11,7 +11,53 @@
тёмный фон + наша иконка, затем переход в основную тему. -->
+
+
+
+
+
+
+
+
+
+
diff --git a/design/logos/gen_bg.py b/design/logos/gen_bg.py
new file mode 100644
index 0000000..fa62b67
--- /dev/null
+++ b/design/logos/gen_bg.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+"""Фоны adaptive-иконки: градиент под акцент темы + радиальное свечение + тень от логотипа."""
+from PIL import Image, ImageDraw, ImageFilter
+import numpy as np, os
+
+TH = r"C:\radiOLA\design\logos\gen\themed"
+DRW = r"C:\radiOLA\app\src\main\res\drawable-nodpi"
+PREV = os.environ["LOCALAPPDATA"] + r"\Temp\radiola_bg_preview.png"
+S = 432
+THEMES = [
+ ("forest", (0xA8,0xE0,0x5F), (0x0C,0x14,0x10)),
+ ("ocean", (0x4F,0xD6,0xE0), (0x0A,0x0F,0x1A)),
+ ("sunset", (0xFF,0x8A,0x5B), (0x1A,0x0F,0x0C)),
+ ("amethyst",(0xB3,0x88,0xFF), (0x12,0x0E,0x1A)),
+ ("neon", (0xFF,0x4D,0x9D), (0x0D,0x0A,0x12)),
+ ("amber", (0xFF,0xC2,0x47), (0x14,0x11,0x0A)),
+ ("ice", (0x7F,0xB3,0xFF), (0x0C,0x10,0x14)),
+ ("rose", (0xFF,0x7E,0xA8), (0x16,0x0E,0x12)),
+]
+
+def lerp(a,b,t): return tuple(a[i]+(b[i]-a[i])*t for i in range(3))
+
+yy,xx = np.mgrid[0:S,0:S]
+cx,cy=S*0.5,S*0.46
+rad = np.sqrt((xx-cx)**2+(yy-cy)**2)/(S*0.62) # 0 в центре → ~1 к краю
+diag = (xx+yy)/(2*S) # 0 верх-лево → 1 низ-право
+
+def make_bg(accent, bg):
+ acc=np.array(accent,float); base=np.array(bg,float)
+ # 1) база: лёгкий диагональный градиент (верх чуть светлее с оттенком акцента, низ темнее)
+ top = lerp(bg, accent, 0.10); top=np.array([min(255,c*1.15) for c in top])
+ bot = np.array([c*0.7 for c in bg])
+ g = diag[...,None]
+ img = top*(1-g) + bot*g
+ # 2) радиальное акцентное свечение в центре
+ glow = np.clip(1-rad,0,1)**1.7
+ img = img + (acc-img)*(glow[...,None]*0.30)
+ # 3) виньетка по углам
+ vig = np.clip(rad-0.6,0,1)
+ img = img*(1-vig[...,None]*0.35)
+ return Image.fromarray(np.clip(img,0,255).astype("uint8"),"RGB").convert("RGBA")
+
+def r_shadow(theme):
+ """Мягкая тень от логотипа (силуэт R), запечённая в фон."""
+ logo=Image.open(os.path.join(TH,f"logo_{theme}.png")).convert("RGBA")
+ logo=logo.crop(logo.getbbox())
+ target=196; sc=target/max(logo.size)
+ logo=logo.resize((int(logo.width*sc),int(logo.height*sc)))
+ sh=Image.new("RGBA",(S,S),(0,0,0,0))
+ # силуэт из альфы
+ sil=Image.new("RGBA",(S,S),(0,0,0,0))
+ a=logo.split()[3]
+ blk=Image.new("RGBA",logo.size,(0,0,0,150))
+ sil.paste(blk,((S-logo.width)//2+7,(S-logo.height)//2+14),a) # сдвиг вниз-вправо
+ sil=sil.filter(ImageFilter.GaussianBlur(16))
+ return sil
+
+cols=4; rows=2; pad=16
+sheet=Image.new("RGB",(cols*S//2+(cols+1)*pad, rows*S//2+(rows+1)*pad),(28,28,32))
+for i,(name,acc,bg) in enumerate(THEMES):
+ base=make_bg(acc,bg)
+ base.alpha_composite(r_shadow(name))
+ base.convert("RGB").save(os.path.join(DRW,f"ic_bg_{name}.png"))
+ # превью: фон + foreground + круглая маска
+ fg=Image.open(os.path.join(DRW,f"ic_fg_{name}.png")).convert("RGBA")
+ full=base.copy(); full.alpha_composite(fg)
+ mask=Image.new("L",(S,S),0); ImageDraw.Draw(mask).ellipse([0,0,S,S],fill=255)
+ out=Image.new("RGB",(S,S),(28,28,32)); out.paste(full.convert("RGB"),(0,0),mask)
+ out=out.resize((S//2,S//2))
+ r=i//cols; c=i%cols; x=pad+c*(S//2+pad); y=pad+r*(S//2+pad)
+ sheet.paste(out,(x,y))
+sheet.save(PREV)
+print("bg generated + preview", PREV)