diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt
index c71dddb9382b5ee3245d1766cde4efde348800f1..231dc84aa621a74fe2378b57bc9ee077468775c2 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/App.kt
@@ -41,6 +41,7 @@ import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository
 import net.novagamestudios.kaffeekasse.repositories.i11.PortalRepository
 import net.novagamestudios.kaffeekasse.repositories.newSettingsStore
 import net.novagamestudios.kaffeekasse.repositories.releases.GitLabReleases
+import net.novagamestudios.kaffeekasse.ui.util.derived
 
 
 class App : Application(), RepositoryProvider, CoroutineScope by MainScope() + CoroutineName(App::class.simpleName!!), Logger {
@@ -101,7 +102,7 @@ class App : Application(), RepositoryProvider, CoroutineScope by MainScope() + C
         @Composable
         fun settings(): SettingsRepository = app().settingsRepository
 
-        val developerMode: Boolean @Composable get() = settings().value.developerMode
+        val developerMode: Boolean @Composable get() = settings().derived { developerMode }.value
     }
 
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Settings.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Settings.kt
index ff57c0a6a01d8be268e7865114da220a25463295..6ca7957e5ed99e6a6da986750c5999332e650bd8 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Settings.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/Settings.kt
@@ -3,6 +3,7 @@ package net.novagamestudios.kaffeekasse.repositories
 import android.content.Context
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
 import androidx.datastore.core.MultiProcessDataStoreFactory
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.serialization.ExperimentalSerializationApi
@@ -15,12 +16,14 @@ import net.novagamestudios.common_utils.compose.state.DataStoreState
 import net.novagamestudios.common_utils.compose.state.MutableDataStoreState
 import net.novagamestudios.common_utils.compose.state.stateIn
 import net.novagamestudios.kaffeekasse.model.credentials.DeviceCredentials
+import net.novagamestudios.kaffeekasse.ui.util.derived
 import java.io.File
 
 @Serializable
 data class Settings(
     val themeMode: ThemeMode = ThemeMode.Dark,
     val fullscreen: Boolean = false,
+    val animationsEnabled: Boolean = true,
     val autoLogin: Boolean = false,
     val deviceCredentials: DeviceCredentials? = null,
     val developerMode: Boolean = false,
@@ -32,7 +35,7 @@ data class Settings(
     }
 
     companion object {
-        val Settings.isDarkMode @Composable get() = when(themeMode) {
+        val State<Settings>.isDarkMode: Boolean @Composable get() = when(derived { themeMode }.value) {
             ThemeMode.Unspecified -> isSystemInDarkTheme()
             ThemeMode.Dark -> true
             ThemeMode.Light -> false
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Updates.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Updates.kt
index 084c3774e1f92a6dba846e866c1711eab61eea39..6ffe9836aea2c89254e3f5e5d92074a661f65149 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Updates.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/Updates.kt
@@ -322,6 +322,13 @@ private fun AppInfoDialog(
             Text("Made with love\nby\nJonas Broeckmann", textAlign = TextAlign.Center)
             Spacer(Modifier.height(16.dp))
             val settings = settings()
+            TransparentListItem(
+                headlineContent = { Text("Fancy animations") },
+                trailingContent = { Switch(
+                    settings.value.animationsEnabled,
+                    onCheckedChange = { new -> settings.tryUpdate { it.copy(animationsEnabled = new) } }
+                ) }
+            )
             TransparentListItem(
                 headlineContent = { Text("Developer mode") },
                 trailingContent = { Switch(
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt
index 9d500a064c914ad2291e56dcb149bcf97f5916cb..c4024d68462a82d68d71b4cd10aa8130ed84b404 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/KaffeekasseModule.kt
@@ -20,6 +20,7 @@ import net.novagamestudios.kaffeekasse.ui.AppModuleSelection
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.ManualBillViewModel.Companion.backNavigationHandler
 import net.novagamestudios.kaffeekasse.ui.navigation.AppSubpageTitle
 import net.novagamestudios.kaffeekasse.ui.navigation.KaffeekasseNavigation
+import net.novagamestudios.kaffeekasse.ui.theme.ifAnimationsEnabled
 import net.novagamestudios.kaffeekasse.ui.util.BackNavigationHandler
 
 
@@ -69,8 +70,8 @@ fun KaffeekasseTopBarNavigation(
     val backNavigationHandler = vm.backNavigationHandler(navigator)
     AnimatedVisibility(
         visible = !backNavigationHandler.canNavigateBack() && vm.cart.isNotEmpty(),
-        enter = expandHorizontally(),
-        exit = shrinkHorizontally()
+        enter = expandHorizontally().ifAnimationsEnabled(),
+        exit = shrinkHorizontally().ifAnimationsEnabled()
     ) {
         IconButton(onClick = { vm.cart.clear() }) {
             Icon(Icons.Default.Delete, "Empty cart")
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CategorizedItems.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CategorizedItems.kt
index 4dedb12b77e4372f88d0addd44886a04152036c2..62ebfdedac2549dcb6742cfe0854536ef6658571 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CategorizedItems.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CategorizedItems.kt
@@ -1,5 +1,6 @@
 package net.novagamestudios.kaffeekasse.ui.kaffeekasse.components
 
+import android.content.Context
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.ContentTransform
 import androidx.compose.animation.fadeIn
@@ -18,15 +19,18 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import kotlinx.coroutines.launch
+import net.novagamestudios.common_utils.compose.state.MutableDataStoreState
 import net.novagamestudios.common_utils.toastShort
 import net.novagamestudios.kaffeekasse.App.Companion.settings
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Cart
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Item
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.ItemCategory
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart
+import net.novagamestudios.kaffeekasse.repositories.UserSettings
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.BasicCardGrid
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.CategoryCard
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.LazyBasicCardGrid
+import net.novagamestudios.kaffeekasse.ui.theme.ifAnimationsEnabled
 
 class CategorizedItemsViewModel(
     items: Iterable<Item>
@@ -67,7 +71,7 @@ fun CategorizedItems(
     AnimatedContent(
         targetState = vm.selectedCategory,
         Modifier.fillMaxHeight(),
-        transitionSpec = {
+        transitionSpec = ifAnimationsEnabled {
             val categoryGridScale = 1.6f
             val itemGridScale = 0.5f
             if (targetState == null) ContentTransform(
@@ -166,20 +170,25 @@ private fun ItemCard(
         onRemove = { cart -= item },
         onLongClick = {
             coroutineScope.launch {
-                userSettings?.update {
-                    val list = it.favoriteItemIds.toMutableList()
-                    val wasFavorite = item.id in list
-                    if (wasFavorite) list -= item.id
-                    else list += item.id
-                    it.copy(favoriteItemIds = list).also {
-                        context.toastShort(
-                            if (wasFavorite) "Aus Favoriten entfernt"
-                            else "Zu Favoriten hinzugefügt"
-                        )
-                    }
-                }
+                userSettings?.addToFavourites(item, context)
             }
         }
     )
 }
 
+private suspend fun MutableDataStoreState<UserSettings>.addToFavourites(
+    item: Item,
+    context: Context
+) = update { old ->
+    val list = old.favoriteItemIds.toMutableList()
+    val wasFavorite = item.id in list
+    if (wasFavorite) list -= item.id
+    else list += item.id
+    old.copy(favoriteItemIds = list).also {
+        context.toastShort(
+            if (wasFavorite) "Aus Favoriten entfernt"
+            else "Zu Favoriten hinzugefügt"
+        )
+    }
+}
+
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt
index 091ac1258045e3fe377d761ad87dd810460ff0bf..ab519a0e4aa0bf45d351c5c19c276c9280138782 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt
@@ -24,7 +24,6 @@ import androidx.compose.material3.FloatingActionButtonDefaults
 import androidx.compose.material3.HorizontalDivider
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
-import androidx.compose.material3.ListItem
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.ModalBottomSheet
@@ -50,6 +49,7 @@ import kotlinx.coroutines.launch
 import net.novagamestudios.common_utils.Logger
 import net.novagamestudios.common_utils.compose.components.BoxCenter
 import net.novagamestudios.common_utils.compose.components.CircularLoadingBox
+import net.novagamestudios.common_utils.compose.components.TransparentListItem
 import net.novagamestudios.common_utils.compose.maskedCircleIcon
 import net.novagamestudios.common_utils.compose.state.ReentrantActionState
 import net.novagamestudios.common_utils.toastLong
@@ -378,7 +378,7 @@ private fun CheckoutCartEntry(
     isLoading: () -> Boolean,
     onRemove: () -> Unit,
     modifier: Modifier = Modifier
-) = ListItem(
+) = TransparentListItem(
     headlineContent = {
         Row(verticalAlignment = Alignment.CenterVertically) {
             Text("${entry.count}x", Modifier.padding(end = 8.dp))
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt
index 9c6fb2d808861665579ecae7ea3ed65d5364a85d..746ad62f788708cb2cf5a2492754aae23f7a6702 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt
@@ -146,35 +146,63 @@ private fun ItemInformation(
     }
     Spacer(Modifier.weight(1f))
 
-    // Very bad code
+    val exactPrice = item.price
+    if (exactPrice != null) ItemPrice(
+        price = exactPrice,
+        estimated = false,
+        highlighted = false
+    ) else EstimatedItemPrice(item)
+}
+
+
+@Composable
+private fun ItemPrice(
+    price: Double,
+    estimated: Boolean,
+    highlighted: Boolean,
+    modifier: Modifier = Modifier
+) = Text(
+    remember(price, estimated) {
+        listOfNotNull(
+            if (estimated) "≈" else null,
+            String.format("%.2f€", price)
+        ).joinToString(" ")
+    },
+    modifier,
+    color = if (highlighted) Color.Yellow else Color.Unspecified,
+    style = MaterialTheme.typography.bodyMedium
+)
+
+@Composable
+private fun EstimatedItemPrice(
+    item: Item,
+    modifier: Modifier = Modifier
+) {
     val app = app()
     val session = app.portalRepository.session.collectAsState().value
-    if (session is Session.WithRealUser) {
-        val transactionsState = app.kaffeekasseRepository.transactions[session.realUser].collectAsRichState()
-        val lastUnitPrice by remember {
-            derivedStateOf {
-                transactionsState.dataOrNull?.findLastUnitPrice(item)
-            }
-        }
-        (item.price ?: lastUnitPrice ?: item.estimatedPrice)?.let {
-            val highlighted = it != item.estimatedPrice && App.developerMode
-            Text(
-                remember(it) {
-                    listOfNotNull(
-                        if (item.price != null) null else "≈",
-                        String.format("%.2f€", it)
-                    ).joinToString(" ")
-                },
-                color = if (highlighted) Color.Yellow else Color.Unspecified,
-                style = MaterialTheme.typography.bodyMedium
-            )
+    if (session !is Session.WithRealUser) return
+    val transactionsState = remember { app.kaffeekasseRepository.transactions[session.realUser] }.collectAsRichState()
+    val lastUnitPrice by remember(transactionsState) {
+        derivedStateOf {
+            transactionsState.dataOrNull?.findLastUnitPrice(item)
         }
     }
+    val estimatedPrice = lastUnitPrice ?: item.estimatedPrice ?: return
+    val priceDiverges = lastUnitPrice != item.estimatedPrice
+    ItemPrice(
+        price = estimatedPrice,
+        estimated = true,
+        highlighted = priceDiverges && App.developerMode,
+        modifier = modifier
+    )
 }
 
+
 // TODO move
 private fun List<Transaction>.findLastUnitPrice(item: Item): Double? {
     val lastPurchase = this
+        .asSequence()
+        .take(100)
         .map { it.purpose }
         .filterIsInstance<Transaction.Purpose.Purchase>()
         .firstOrNull {
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/navigation/AppScaffold.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/navigation/AppScaffold.kt
index dd42d516ba64052647b5f3a57347e889f10d1819..50f2aea0f84a85d5565ef6c52e683155e88e0556 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/navigation/AppScaffold.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/navigation/AppScaffold.kt
@@ -23,6 +23,8 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.unit.dp
 import cafe.adriel.voyager.core.screen.Screen
 import cafe.adriel.voyager.navigator.Navigator
+import net.novagamestudios.kaffeekasse.ui.theme.LocalAnimationSwitch
+import net.novagamestudios.kaffeekasse.ui.theme.ifAnimationsEnabled
 import net.novagamestudios.kaffeekasse.ui.util.BackNavigationHandler
 import net.novagamestudios.kaffeekasse.ui.util.debugNavigation
 
@@ -79,7 +81,7 @@ private fun AppScaffold(
     content: @Composable () -> Unit
 ) = Scaffold(
     modifier.run {
-        if (topAppBarScrollBehavior == null) this
+        if (topAppBarScrollBehavior == null || !LocalAnimationSwitch.current) this
         else nestedScroll(topAppBarScrollBehavior.nestedScrollConnection)
     },
     topBar = topBar
@@ -108,7 +110,7 @@ fun AppTopBar(
     colors = TopAppBarDefaults.topAppBarColors(
         scrolledContainerColor = MaterialTheme.colorScheme.surface
     ),
-    scrollBehavior = scrollBehavior
+    scrollBehavior = scrollBehavior.takeIf { LocalAnimationSwitch.current }
 )
 
 @Composable
@@ -120,8 +122,8 @@ fun DefaultBackNavigation(
 ) {
     AnimatedVisibility(
         visible = handler.canNavigateBack(),
-        enter = expandHorizontally(),
-        exit = shrinkHorizontally()
+        enter = expandHorizontally().ifAnimationsEnabled(),
+        exit = shrinkHorizontally().ifAnimationsEnabled()
     ) {
         IconButton(onClick = { handler.onNavigateBack() }) {
             Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/navigation/Transitions.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/navigation/Transitions.kt
index d12e61c25bc6be5c1821c4c44d3d95700c95b6b1..863245160673bcdcb3836475acdd87473c233add 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/navigation/Transitions.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/navigation/Transitions.kt
@@ -15,6 +15,7 @@ import cafe.adriel.voyager.navigator.Navigator
 import cafe.adriel.voyager.navigator.tab.Tab
 import cafe.adriel.voyager.navigator.tab.TabNavigator
 import cafe.adriel.voyager.transitions.ScreenTransitionContent
+import net.novagamestudios.kaffeekasse.ui.theme.ifAnimationsEnabled
 
 private fun appTransitionSpec() = ContentTransform(
     targetContentEnter = fadeIn(
@@ -37,7 +38,7 @@ fun AppScreenTransition(
     content: ScreenTransitionContent = { it.Content() }
 ) = AnimatedContent(
     targetState = navigator.lastItem,
-    transitionSpec = transitionSpec,
+    transitionSpec = ifAnimationsEnabled(transitionSpec),
     modifier = modifier,
     label = key
 ) { screen ->
@@ -55,7 +56,7 @@ fun AppTabTransition(
     content: @Composable AnimatedVisibilityScope.(Tab) -> Unit = { it.Content() }
 ) = AnimatedContent(
     targetState = tabNavigator.current,
-    transitionSpec = transitionSpec,
+    transitionSpec = ifAnimationsEnabled(transitionSpec),
     modifier = modifier,
     label = key
 ) { tab ->
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/theme/Theme.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/theme/Theme.kt
index 03d33efdcd8b0b41c85d16cd02eacefa96b49c39..f52b1ff97dfa64c39309b4a318cdbf09b07bcd8c 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/theme/Theme.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/theme/Theme.kt
@@ -2,20 +2,30 @@ package net.novagamestudios.kaffeekasse.ui.theme
 
 import android.app.Activity
 import android.os.Build
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.ContentTransform
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.darkColorScheme
 import androidx.compose.material3.dynamicDarkColorScheme
 import androidx.compose.material3.dynamicLightColorScheme
 import androidx.compose.material3.lightColorScheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ProvidableCompositionLocal
 import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalView
 import androidx.core.view.WindowCompat
 import net.novagamestudios.kaffeekasse.App.Companion.settings
+import net.novagamestudios.kaffeekasse.repositories.Settings
 import net.novagamestudios.kaffeekasse.repositories.Settings.Companion.isDarkMode
+import net.novagamestudios.kaffeekasse.ui.util.derived
 
 private val DarkColorScheme = darkColorScheme(
     primary = AppYellow,
@@ -31,7 +41,8 @@ private val LightColorScheme = lightColorScheme(
 
 @Composable
 fun KaffeekasseTheme(
-    darkTheme: Boolean = settings().value.isDarkMode,
+    settings: State<Settings> = settings(),
+    darkTheme: Boolean = settings.isDarkMode,
     // Dynamic color is available on Android 12+
     dynamicColor: Boolean = true,
     content: @Composable () -> Unit
@@ -58,9 +69,14 @@ fun KaffeekasseTheme(
 
     MaterialTheme(
         colorScheme = colorScheme,
-        typography = Typography,
-        content = content
-    )
+        typography = Typography
+    ) {
+        CompositionLocalProvider(
+            LocalAnimationSwitch provides settings.derived { animationsEnabled }.value
+        ) {
+            content()
+        }
+    }
 }
 
 
@@ -68,3 +84,16 @@ fun KaffeekasseTheme(
 fun Color.enabled(enabled: Boolean = true) = disabled(!enabled)
 fun Color.disabled(disabled: Boolean = true) = if (disabled) copy(alpha = 0.38f) else this
 
+
+val LocalAnimationSwitch: ProvidableCompositionLocal<Boolean> = compositionLocalOf { true }
+
+typealias TransitionSpec<T> = AnimatedContentTransitionScope<T>.() -> ContentTransform
+@Composable
+fun <T> ifAnimationsEnabled(transitionSpec: TransitionSpec<T>): TransitionSpec<T> = if (LocalAnimationSwitch.current) transitionSpec else {
+    { ContentTransform(EnterTransition.None, ExitTransition.None) }
+}
+
+@Composable
+fun EnterTransition.ifAnimationsEnabled(): EnterTransition = if (LocalAnimationSwitch.current) this else EnterTransition.None
+@Composable
+fun ExitTransition.ifAnimationsEnabled(): ExitTransition = if (LocalAnimationSwitch.current) this else ExitTransition.None
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/Helpers.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/Helpers.kt
index 3228432299b39b6318d0d4235b18c41e5d2cbd73..419e30be826faae3db3b973a2c1f7e0beb4f2316 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/Helpers.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/util/Helpers.kt
@@ -6,7 +6,9 @@ import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.animateValueAsState
 import androidx.compose.animation.core.spring
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisallowComposableCalls
 import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
@@ -102,3 +104,15 @@ fun Navigator.requireWithKey(key: String): Navigator {
     return parent.requireWithKey(key)
 }
 
+
+
+
+@Composable
+fun <T, R> State<T>.derived(
+    calculation: @DisallowComposableCalls T.() -> R
+) = remember(this) {
+    derivedStateOf {
+        value.calculation()
+    }
+}
+