Browse Source

more improvements

Fszontagh 4 months ago
parent
commit
0d3959540d

+ 2 - 2
android/app/src/main/AndroidManifest.xml

@@ -4,7 +4,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     
     <application
-        android:label="zentap"
+        android:label="ZenTap"
         android:name="${applicationName}"
         android:icon="@mipmap/ic_launcher">
         
@@ -17,7 +17,7 @@
             android:value="@integer/google_play_services_version" />
         
         <activity
-            android:name="com.fsociety.zentap.zentap.MainActivity"
+            android:name="hu.fsociety.zentap.MainActivity"
             android:exported="true"
             android:launchMode="singleTop"
             android:taskAffinity=""

+ 2 - 2
android/app/src/main/kotlin/com/fsociety/zentap/zentap/MainActivity.kt → android/app/src/main/kotlin/hu/fsociety/zentap/MainActivity.kt

@@ -1,4 +1,4 @@
-package com.fsociety.zentap.zentap
+package hu.fsociety.zentap
 
 import io.flutter.embedding.android.FlutterActivity
 import io.flutter.embedding.engine.FlutterEngine
@@ -8,4 +8,4 @@ class MainActivity : FlutterActivity() {
     override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
         GeneratedPluginRegistrant.registerWith(flutterEngine)
     }
-}
+}

+ 6 - 6
ios/Runner.xcodeproj/project.pbxproj

@@ -368,7 +368,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.fsociety.zentap.zentap;
+				PRODUCT_BUNDLE_IDENTIFIER = hu.fsociety.zentap;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
 				SWIFT_VERSION = 5.0;
@@ -384,7 +384,7 @@
 				CURRENT_PROJECT_VERSION = 1;
 				GENERATE_INFOPLIST_FILE = YES;
 				MARKETING_VERSION = 1.0;
-				PRODUCT_BUNDLE_IDENTIFIER = com.fsociety.zentap.zentap.RunnerTests;
+				PRODUCT_BUNDLE_IDENTIFIER = hu.fsociety.zentap.RunnerTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
 				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -401,7 +401,7 @@
 				CURRENT_PROJECT_VERSION = 1;
 				GENERATE_INFOPLIST_FILE = YES;
 				MARKETING_VERSION = 1.0;
-				PRODUCT_BUNDLE_IDENTIFIER = com.fsociety.zentap.zentap.RunnerTests;
+				PRODUCT_BUNDLE_IDENTIFIER = hu.fsociety.zentap.RunnerTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_VERSION = 5.0;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@@ -416,7 +416,7 @@
 				CURRENT_PROJECT_VERSION = 1;
 				GENERATE_INFOPLIST_FILE = YES;
 				MARKETING_VERSION = 1.0;
-				PRODUCT_BUNDLE_IDENTIFIER = com.fsociety.zentap.zentap.RunnerTests;
+				PRODUCT_BUNDLE_IDENTIFIER = hu.fsociety.zentap.RunnerTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_VERSION = 5.0;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@@ -547,7 +547,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.fsociety.zentap.zentap;
+				PRODUCT_BUNDLE_IDENTIFIER = hu.fsociety.zentap;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
 				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -569,7 +569,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.fsociety.zentap.zentap;
+				PRODUCT_BUNDLE_IDENTIFIER = hu.fsociety.zentap;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
 				SWIFT_VERSION = 5.0;

+ 2 - 2
ios/Runner/Info.plist

@@ -5,7 +5,7 @@
 	<key>CFBundleDevelopmentRegion</key>
 	<string>$(DEVELOPMENT_LANGUAGE)</string>
 	<key>CFBundleDisplayName</key>
-	<string>Zentap</string>
+	<string>ZenTap</string>
 	<key>CFBundleExecutable</key>
 	<string>$(EXECUTABLE_NAME)</string>
 	<key>CFBundleIdentifier</key>
@@ -13,7 +13,7 @@
 	<key>CFBundleInfoDictionaryVersion</key>
 	<string>6.0</string>
 	<key>CFBundleName</key>
-	<string>zentap</string>
+	<string>ZenTap</string>
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>

+ 190 - 140
lib/game/components/bubble.dart

@@ -7,46 +7,46 @@ import 'package:flame/collisions.dart';
 import 'package:flutter/material.dart';
 import '../../utils/colors.dart';
 
-class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, CollisionCallbacks {
+class Bubble extends SpriteComponent
+    with HasGameReference, TapCallbacks, CollisionCallbacks {
   static const double startSize = 40.0; // Small starting size for all bubbles
   static const double maxSize = 50.0; // Same final size for all bubbles
-  static const double lifecycleDuration = 12.0; // Same lifecycle duration for all bubbles
-  
+  static const double lifecycleDuration =
+      12.0; // Same lifecycle duration for all bubbles
+
   // Triple collision rule constants
   static const int maxCollisions = 3;
   static const double collisionGracePeriod = 2.0; // seconds
-  
+
   late String bubbleImagePath;
   late Color bubbleColor;
   bool isPopping = false;
   bool poppedByUser = false;
-  bool isAutoSpawned = true; // Whether bubble was auto-spawned or user-triggered
+  bool isAutoSpawned =
+      true; // Whether bubble was auto-spawned or user-triggered
   Function(Bubble, bool)? onPop;
-  
+
   // Collision tracking for triple collision rule
   int _collisionCount = 0;
   double _lastCollisionTime = 0.0;
-  
+
   double _age = 0.0;
   double _lifecycleProgress = 0.0;
   late double _targetSize;
-  
+
   // Physics properties for collision and movement
   Vector2 velocity = Vector2.zero();
   static const double maxSpeed = 50.0;
   static const double friction = 0.98;
   static const double collisionDamping = 0.7;
   double _floatTimer = 0.0;
-  
-  Bubble({
-    required Vector2 position,
-    this.onPop,
-    this.isAutoSpawned = true,
-  }) : super(
-    size: Vector2.all(startSize),
-    position: position,
-    anchor: Anchor.center,
-  ) {
+
+  Bubble({required Vector2 position, this.onPop, this.isAutoSpawned = true})
+    : super(
+        size: Vector2.all(startSize),
+        position: position,
+        anchor: Anchor.center,
+      ) {
     // All bubbles have the same target size regardless of type
     _targetSize = maxSize;
   }
@@ -57,28 +57,30 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
     final bubbleData = _getRandomBubbleData();
     bubbleImagePath = bubbleData['path'];
     bubbleColor = bubbleData['color'];
-    
+
     // Load the sprite
     sprite = await Sprite.load(bubbleImagePath);
-    
+
     await super.onLoad();
-    
+
     // Add circular collision hitbox - will be updated dynamically
-    add(CircleHitbox(
-      radius: startSize / 2, // Start with initial size
-      anchor: Anchor.center,
-    ));
-    
+    add(
+      CircleHitbox(
+        radius: startSize / 2, // Start with initial size
+        anchor: Anchor.center,
+      ),
+    );
+
     // Start with zero scale for smooth entrance animation
     scale = Vector2.zero();
-    
+
     // Add smooth entrance animation
     _addEntranceAnimation();
-    
+
     // Add varied, organic floating animations
     _addRandomFloatingAnimation();
     _addRandomRotationAnimation();
-    
+
     // Initialize random velocity for natural movement
     final random = Random();
     velocity = Vector2(
@@ -90,50 +92,58 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
   @override
   void update(double dt) {
     super.update(dt);
-    
+
     if (isPopping) return;
-    
+
     // Update age and lifecycle
     _age += dt;
     _lifecycleProgress = (_age / lifecycleDuration).clamp(0.0, 1.0);
-    
+
     // Update scale based on lifecycle progress - grow from startSize to maxSize
     final currentScale = _lifecycleProgress * (_targetSize / startSize);
-    scale = Vector2.all(1.0 + currentScale * 3.0); // Scale from 1.0 to 4.0 over lifetime
-    
+    scale = Vector2.all(
+      1.0 + currentScale * 3.0,
+    ); // Scale from 1.0 to 4.0 over lifetime
+
     // Update collision hitbox radius to match current visual size
-    final currentRadius = (startSize / 2) * scale.x; // Use scale.x since it's uniform scaling
+    // Use a slightly smaller radius for collision detection to prevent edge cases
+    final currentRadius =
+        ((startSize / 2) * scale.x) * 0.9; // 90% of visual size
     final hitbox = children.whereType<CircleHitbox>().firstOrNull;
     if (hitbox != null) {
       hitbox.radius = currentRadius;
     }
-    
+
     // Update opacity based on lifecycle (fade out towards the end)
-    final opacityFactor = 1.0 - (_lifecycleProgress * 0.6); // Fade to 40% opacity
+    final opacityFactor =
+        1.0 - (_lifecycleProgress * 0.6); // Fade to 40% opacity
     opacity = opacityFactor.clamp(0.4, 1.0);
-    
+
     // Physics-based movement
     _updatePhysics(dt);
-    
+
     // Keep bubble within screen bounds
     _keepInBounds();
-    
+
     // Update collision tracking
     _updateCollisionTracking(dt);
-    
+
+    // Periodic overlap check to prevent stuck overlaps
+    _preventOverlapSticking(dt);
+
     // Auto-pop when lifecycle is complete
     if (_lifecycleProgress >= 1.0) {
       pop(userTriggered: false);
     }
   }
-  
+
   void _updatePhysics(double dt) {
     // Apply velocity to position
     position.add(velocity * dt);
-    
+
     // Apply friction
     velocity.scale(friction);
-    
+
     // Add periodic floating effects
     _floatTimer += dt;
     if (_floatTimer > 2.0) {
@@ -145,18 +155,18 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
       );
       velocity.add(floatForce);
     }
-    
+
     // Clamp velocity to max speed
     if (velocity.length > maxSpeed) {
       velocity.normalize();
       velocity.scale(maxSpeed);
     }
   }
-  
+
   void _keepInBounds() {
     const margin = 60.0;
     final gameSize = game.size;
-    
+
     // Bounce off screen edges
     if (position.x < margin) {
       position.x = margin;
@@ -165,8 +175,9 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
       position.x = gameSize.x - margin;
       velocity.x = -velocity.x.abs(); // Bounce left
     }
-    
-    if (position.y < margin + 100) { // Account for UI at top
+
+    if (position.y < margin + 100) {
+      // Account for UI at top
       position.y = margin + 100;
       velocity.y = velocity.y.abs(); // Bounce down
     } else if (position.y > gameSize.y - margin) {
@@ -178,32 +189,20 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
   Map<String, dynamic> _getRandomBubbleData() {
     final random = Random();
     final bubbleTypes = [
-      {
-        'path': 'bubble_blue.png',
-        'color': ZenColors.defaultLink,
-      },
-      {
-        'path': 'bubble_cyan.png',
-        'color': ZenColors.buttonBackground,
-      },
-      {
-        'path': 'bubble_green.png',
-        'color': const Color(0xFF00FF80),
-      },
-      {
-        'path': 'bubble_purple.png',
-        'color': ZenColors.lightModeHover,
-      },
+      {'path': 'bubble_blue.png', 'color': ZenColors.defaultLink},
+      {'path': 'bubble_cyan.png', 'color': ZenColors.buttonBackground},
+      {'path': 'bubble_green.png', 'color': const Color(0xFF00FF80)},
+      {'path': 'bubble_purple.png', 'color': ZenColors.lightModeHover},
     ];
     return bubbleTypes[random.nextInt(bubbleTypes.length)];
   }
 
   void _addRandomFloatingAnimation() {
     final random = Random();
-    
+
     // Add a small upward bias to the initial velocity
     velocity.y += -5 - random.nextDouble() * 10; // -5 to -15 upward bias
-    
+
     // The floating effect will now be handled by the physics system
     // and periodic velocity changes in the update method
   }
@@ -211,34 +210,31 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
   void _addEntranceAnimation() {
     // Start at scale 1.0 (startSize) and grow gradually throughout lifecycle
     // The growth will be handled in the update method based on lifecycle progress
-    
+
     // Simple fade-in animation only
     opacity = 0.0;
     add(
       OpacityEffect.to(
         1.0,
-        EffectController(
-          duration: 0.4,
-          curve: Curves.easeOut,
-        ),
+        EffectController(duration: 0.4, curve: Curves.easeOut),
       ),
     );
   }
 
   void _addRandomRotationAnimation() {
     final random = Random();
-    
+
     // 30% chance for rotation animation
     if (random.nextDouble() < 0.3) {
       // Small random rotation between -0.2 and 0.2 radians
       final rotationAngle = (random.nextDouble() - 0.5) * 0.4;
-      
+
       // Random rotation duration between 3 and 8 seconds
       final rotationDuration = 3.0 + random.nextDouble() * 5.0;
-      
+
       // Random delay
       final delay = random.nextDouble() * 3.0;
-      
+
       Future.delayed(Duration(milliseconds: (delay * 1000).round()), () {
         if (isMounted) {
           add(
@@ -257,7 +253,6 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
     }
   }
 
-
   @override
   bool onTapDown(TapDownEvent event) {
     if (!isPopping) {
@@ -268,42 +263,42 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
 
   void pop({bool userTriggered = false}) {
     if (isPopping) return;
-    
+
     isPopping = true;
     poppedByUser = userTriggered;
-    
+
     // Create exciting multi-stage pop animation
-    
+
     // Stage 1: Quick expand (0.1s)
     final expandEffect = ScaleEffect.to(
       Vector2.all(1.8),
       EffectController(duration: 0.1, curve: Curves.easeOut),
     );
-    
+
     // Stage 2: Slight compression (0.05s)
     final compressEffect = ScaleEffect.to(
       Vector2.all(0.8),
       EffectController(duration: 0.05, curve: Curves.easeIn),
     );
-    
+
     // Stage 3: Final explosion (0.15s)
     final explodeEffect = ScaleEffect.to(
       Vector2.all(2.5),
       EffectController(duration: 0.15, curve: Curves.easeOut),
     );
-    
+
     // Fade out effect
     final fadeEffect = OpacityEffect.to(
       0.0,
       EffectController(duration: 0.3, curve: Curves.easeIn),
     );
-    
+
     // Rotation effect for more dynamic feel
     final rotateEffect = RotateEffect.by(
       0.5, // Half rotation
       EffectController(duration: 0.3),
     );
-    
+
     // Chain the scale effects
     add(expandEffect);
     expandEffect.onComplete = () {
@@ -316,16 +311,16 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
         };
       }
     };
-    
+
     add(fadeEffect);
     add(rotateEffect);
-    
+
     // Create enhanced particle effects
     _createExcitingPopParticles();
-    
+
     // Notify parent and remove bubble
     onPop?.call(this, userTriggered);
-    
+
     Future.delayed(const Duration(milliseconds: 300), () {
       if (isMounted) {
         removeFromParent();
@@ -336,42 +331,46 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
   void _createExcitingPopParticles() {
     final random = Random();
     const particleCount = 12; // Further reduced for better performance
-    
+
     // Use object pooling approach - create fewer, simpler particles
     for (int i = 0; i < particleCount; i++) {
       final angle = (i / particleCount) * 2 * pi;
-      
+
       // Simplified particle creation
       final baseSpeed = 50 + random.nextDouble() * 30; // 50-80 speed
       final particleVelocity = Vector2(
         cos(angle) * baseSpeed,
         sin(angle) * baseSpeed,
       );
-      
+
       // Fixed particle size for performance
       const particleSize = 3.0;
-      
+
       // Simplified particle color
       final particleColor = bubbleColor.withValues(alpha: 0.8);
-      
+
       final particle = CircleComponent(
         radius: particleSize,
         position: position.clone(),
-        paint: Paint()
-          ..color = particleColor
-          ..style = PaintingStyle.fill,
+        paint:
+            Paint()
+              ..color = particleColor
+              ..style = PaintingStyle.fill,
       );
-      
+
       parent?.add(particle);
-      
+
       // Combine effects for better performance
       final combinedEffect = MoveEffect.by(
         particleVelocity + Vector2(0, 40), // Combined movement + gravity
-        EffectController(duration: 0.3, curve: Curves.easeOut), // Faster animation
+        EffectController(
+          duration: 0.3,
+          curve: Curves.easeOut,
+        ), // Faster animation
       );
-      
+
       particle.add(combinedEffect);
-      
+
       // Single fade effect instead of multiple effects
       particle.add(
         OpacityEffect.to(
@@ -379,7 +378,7 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
           EffectController(duration: 0.3, curve: Curves.easeIn),
         ),
       );
-      
+
       // Faster cleanup
       Future.delayed(const Duration(milliseconds: 300), () {
         if (particle.isMounted) {
@@ -391,19 +390,19 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
 
   // Track if shake effect is currently active to prevent overlapping effects
   bool _isShakeEffectActive = false;
-  
+
   /// Add shake effect to this bubble (called when phone is shaken)
   /// Fixed version that only adds visual effects without movement or size flickering
   void addShakeEffect() {
     if (isPopping || _isShakeEffectActive) return;
-    
+
     _isShakeEffectActive = true;
     final random = Random();
-    
+
     // Store current scale to return to it after effect
     final currentScale = scale.clone();
     final targetScale = currentScale * 1.1; // Only 10% bigger than current size
-    
+
     // Use absolute scaling to prevent flickering with lifecycle scaling
     final scaleUpEffect = ScaleEffect.to(
       targetScale,
@@ -412,7 +411,7 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
         curve: Curves.easeOut,
       ),
     );
-    
+
     final scaleDownEffect = ScaleEffect.to(
       currentScale,
       EffectController(
@@ -420,7 +419,7 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
         curve: Curves.easeIn,
       ),
     );
-    
+
     // Add slight rotation effect - reduced intensity
     final rotateEffect = RotateEffect.by(
       (random.nextDouble() - 0.5) * 0.15, // Further reduced rotation
@@ -430,7 +429,7 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
         curve: Curves.easeInOut,
       ),
     );
-    
+
     // Chain the scale effects
     add(scaleUpEffect);
     scaleUpEffect.onComplete = () {
@@ -438,9 +437,9 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
         add(scaleDownEffect);
       }
     };
-    
+
     add(rotateEffect);
-    
+
     // Set completion callback to reset shake effect flag
     Future.delayed(const Duration(milliseconds: 200), () {
       _isShakeEffectActive = false;
@@ -451,101 +450,152 @@ class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, Collis
   @override
   bool onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
     super.onCollision(intersectionPoints, other);
-    
+
     if (other is Bubble && !isPopping && !other.isPopping) {
       _handleBubbleCollision(other, intersectionPoints);
     }
     return true;
   }
-  
+
   void _handleBubbleCollision(Bubble other, Set<Vector2> intersectionPoints) {
     if (intersectionPoints.isEmpty) return;
-    
+
     // Track collision for triple collision rule
     _incrementCollisionCount();
     other._incrementCollisionCount();
-    
+
     // Calculate collision direction
     final direction = (position - other.position);
     final distance = direction.length;
-    
+
     // Prevent division by zero
     if (distance < 0.1) {
       // If bubbles are too close, separate them with a random direction
       final random = Random();
-      direction.setFrom(Vector2(
-        (random.nextDouble() - 0.5) * 2,
-        (random.nextDouble() - 0.5) * 2,
-      ));
+      direction.setFrom(
+        Vector2(
+          (random.nextDouble() - 0.5) * 2,
+          (random.nextDouble() - 0.5) * 2,
+        ),
+      );
     }
     direction.normalize();
-    
+
     // Calculate required separation based on current bubble sizes
     final thisRadius = (startSize / 2) * scale.x;
     final otherRadius = (startSize / 2) * other.scale.x;
-    final requiredDistance = thisRadius + otherRadius + 5.0; // 5px padding
-    
-    // If bubbles are overlapping, separate them immediately
+    final requiredDistance =
+        thisRadius + otherRadius + 10.0; // Increased padding to 10px
+
+    // If bubbles are overlapping, separate them immediately with more aggressive separation
     if (distance < requiredDistance) {
-      final separationNeeded = requiredDistance - distance;
+      final separationNeeded =
+          requiredDistance - distance + 5.0; // Extra 5px buffer
       final separationVector = direction * (separationNeeded / 2);
-      
+
       // Move both bubbles away from each other
       position.add(separationVector);
       other.position.sub(separationVector);
-      
+
       // Ensure bubbles stay within bounds after separation
       _keepInBounds();
       other._keepInBounds();
+
+      // Additional check: if still overlapping after separation, apply emergency separation
+      final newDistance = (position - other.position).length;
+      if (newDistance < requiredDistance) {
+        final emergencyVector =
+            direction * ((requiredDistance - newDistance + 10.0) / 2);
+        position.add(emergencyVector);
+        other.position.sub(emergencyVector);
+        _keepInBounds();
+        other._keepInBounds();
+      }
     }
-    
+
     // Apply collision response - bubbles bounce off each other
     final collisionForce = 25.0; // Reduced from 30.0 for gentler bouncing
     velocity.add(direction * collisionForce);
     other.velocity.add(direction * -collisionForce);
-    
+
     // Apply stronger damping to prevent infinite acceleration
     velocity.scale(0.6); // Increased damping from 0.7 to 0.6
     other.velocity.scale(0.6);
   }
-  
+
   /// Update collision tracking and handle grace period reset
   void _updateCollisionTracking(double dt) {
     final currentTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
-    
+
     // Reset collision count if grace period has passed
     if (currentTime - _lastCollisionTime > collisionGracePeriod) {
       _collisionCount = 0;
     }
   }
-  
+
   /// Increment collision count and check for auto-pop
   void _incrementCollisionCount() {
     if (isPopping) return;
-    
+
     _collisionCount++;
     _lastCollisionTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
-    
+
     // Auto-pop after max collisions
     if (_collisionCount >= maxCollisions) {
       pop(userTriggered: false); // Auto-pop due to collision rule
     }
   }
-  
+
   /// Get current collision count (for debugging/testing)
   int get collisionCount => _collisionCount;
-  
+
   /// Apply tilt force to bubble based on device orientation
   void applyTiltForce(double tiltStrength) {
     if (isPopping) return;
-    
+
     // Reduced tilt force to prevent excessive movement during shaking
     // Apply force opposite to tilt direction
     // If device tilts right, bubbles move left and vice versa
     final tiltForce = -tiltStrength * 8.0; // Reduced from 15.0 to 8.0
     velocity.x += tiltForce;
-    
+
     // Add slight vertical component for more natural movement
     velocity.y += tiltForce * 0.2; // Reduced from 0.3 to 0.2
   }
-}
+
+  double _lastOverlapCheck = 0.0;
+  static const double overlapCheckInterval = 0.1; // Check every 100ms
+
+  /// Periodic check to prevent bubbles from getting stuck overlapping
+  void _preventOverlapSticking(double dt) {
+    _lastOverlapCheck += dt;
+
+    // Only check periodically to avoid performance issues
+    if (_lastOverlapCheck < overlapCheckInterval) return;
+    _lastOverlapCheck = 0.0;
+
+    // Get all other bubbles from the game
+    final allBubbles = game.children.whereType<Bubble>();
+
+    for (final otherBubble in allBubbles) {
+      if (otherBubble == this || otherBubble.isPopping || isPopping) continue;
+
+      final distance = position.distanceTo(otherBubble.position);
+      final thisRadius = (startSize / 2) * scale.x;
+      final otherRadius = (startSize / 2) * otherBubble.scale.x;
+      final requiredDistance = thisRadius + otherRadius + 8.0; // 8px padding
+
+      // If bubbles are overlapping, apply gentle separation force
+      if (distance < requiredDistance && distance > 0.1) {
+        final direction = (position - otherBubble.position).normalized();
+        final separationForce =
+            (requiredDistance - distance) * 0.5; // Gentle force
+
+        // Apply separation force to velocities instead of direct position changes
+        // to make the movement more natural
+        velocity.add(direction * separationForce);
+        otherBubble.velocity.add(direction * -separationForce);
+      }
+    }
+  }
+}

+ 121 - 78
lib/game/components/bubble_spawner.dart

@@ -5,28 +5,28 @@ import 'bubble.dart';
 class BubbleSpawner extends Component with HasGameReference {
   static const double baseSpawnInterval = 1.5; // base seconds
   static const double spawnVariation = 1.0; // +/- variation in seconds
-  
+
   // Bubble rules constants
   static const double bubbleMaxDiameter = 50.0;
   static const double bubbleMinSpacing = 10.0;
   static const double screenMargin = 120.0;
   static const double uiTopMargin = 100.0;
-  
+
   // Dynamic max bubbles based on screen size
   int _maxBubbles = 8;
   int get maxBubbles => _maxBubbles;
-  
+
   double _timeSinceLastSpawn = 0;
   double _nextSpawnTime = 0;
   final List<Bubble> _activeBubbles = [];
   final Random _random = Random();
-  
+
   Function(Bubble, bool)? onBubblePopped;
   bool isActive = true;
-  
+
   // Screen capacity calculation cache
   Vector2 _lastCalculatedScreenSize = Vector2.zero();
-  
+
   // Shake-enhanced spawning
   bool _isShakeMode = false;
   double _shakeModeEndTime = 0;
@@ -38,32 +38,35 @@ class BubbleSpawner extends Component with HasGameReference {
   }
 
   double _lastCleanupTime = 0;
-  static const double cleanupInterval = 0.5; // Clean up bubbles every 0.5 seconds instead of every frame
+  static const double cleanupInterval =
+      0.5; // Clean up bubbles every 0.5 seconds instead of every frame
 
   @override
   void update(double dt) {
     super.update(dt);
-    
+
     if (!isActive) return;
-    
+
     // Update max bubbles if screen size changed
     _updateMaxBubblesIfNeeded();
-    
+
     _timeSinceLastSpawn += dt;
     _lastCleanupTime += dt;
-    
+
     // Check if shake mode should end
-    if (_isShakeMode && _shakeModeEndTime <= DateTime.now().millisecondsSinceEpoch / 1000.0) {
+    if (_isShakeMode &&
+        _shakeModeEndTime <= DateTime.now().millisecondsSinceEpoch / 1000.0) {
       _isShakeMode = false;
     }
-    
+
     // Spawn new bubble if conditions are met
-    if (_timeSinceLastSpawn >= _nextSpawnTime && _activeBubbles.length < maxBubbles) {
+    if (_timeSinceLastSpawn >= _nextSpawnTime &&
+        _activeBubbles.length < maxBubbles) {
       _spawnBubble();
       _timeSinceLastSpawn = 0;
       _calculateNextSpawnTime();
     }
-    
+
     // Clean up popped bubbles less frequently to improve performance
     if (_lastCleanupTime >= cleanupInterval) {
       _activeBubbles.removeWhere((bubble) => !bubble.isMounted);
@@ -73,18 +76,21 @@ class BubbleSpawner extends Component with HasGameReference {
 
   void _spawnBubble({bool isAutoSpawned = true}) {
     if (game.size.x == 0 || game.size.y == 0) return;
-    
+
     final spawnPosition = _getValidSpawnPosition();
     if (spawnPosition == null) return;
-    
+
     final bubble = Bubble(
       position: spawnPosition,
       onPop: _onBubblePopped,
       isAutoSpawned: isAutoSpawned,
     );
-    
-    _activeBubbles.add(bubble);
-    game.add(bubble);
+
+    // Double-check for overlaps before adding to prevent edge cases
+    if (_validateBubblePosition(bubble)) {
+      _activeBubbles.add(bubble);
+      game.add(bubble);
+    }
   }
 
   Vector2? _getValidSpawnPosition() {
@@ -92,30 +98,34 @@ class BubbleSpawner extends Component with HasGameReference {
     // Increased margin to account for larger bubble sprites
     const margin = 120.0;
     const maxAttempts = 20; // Increased attempts
-    
+
     final minX = margin;
     final maxX = game.size.x - margin;
     final minY = margin + 100; // Extra margin for score display
     final maxY = game.size.y - margin;
-    
+
     if (maxX <= minX || maxY <= minY) return null;
-    
+
     // Try to find a position that doesn't overlap with existing bubbles
     for (int attempt = 0; attempt < maxAttempts; attempt++) {
       final position = Vector2(
         minX + _random.nextDouble() * (maxX - minX),
         minY + _random.nextDouble() * (maxY - minY),
       );
-      
+
       // Check if position is safe from all existing bubbles
       bool isSafe = true;
       for (final bubble in _activeBubbles) {
         if (!bubble.isPopping) {
           // Calculate required distance based on bubble sizes
           final bubbleRadius = (bubbleMaxDiameter / 2) * bubble.scale.x;
-          final newBubbleRadius = bubbleMaxDiameter / 2; // New bubble starts small
-          final requiredDistance = bubbleRadius + newBubbleRadius + 20.0; // 20px padding
-          
+          final newBubbleRadius =
+              bubbleMaxDiameter / 2; // New bubble starts small
+          final requiredDistance =
+              bubbleRadius +
+              newBubbleRadius +
+              30.0; // Increased padding to 30px
+
           final distance = position.distanceTo(bubble.position);
           if (distance < requiredDistance) {
             isSafe = false;
@@ -123,12 +133,12 @@ class BubbleSpawner extends Component with HasGameReference {
           }
         }
       }
-      
+
       if (isSafe) {
         return position;
       }
     }
-    
+
     // If no safe position found after max attempts, return null
     return null;
   }
@@ -142,67 +152,74 @@ class BubbleSpawner extends Component with HasGameReference {
     // Find a safe position near the requested position
     final safePosition = _findSafePositionNear(position);
     if (safePosition == null) return; // Don't spawn if no safe position found
-    
+
     final bubble = Bubble(
       position: safePosition,
       onPop: _onBubblePopped,
       isAutoSpawned: false, // User-triggered bubbles
     );
-    
-    _activeBubbles.add(bubble);
-    game.add(bubble);
+
+    // Double-check for overlaps before adding to prevent edge cases
+    if (_validateBubblePosition(bubble)) {
+      _activeBubbles.add(bubble);
+      game.add(bubble);
+    }
   }
-  
+
   /// Find a safe position near the requested position that doesn't overlap with existing bubbles
   Vector2? _findSafePositionNear(Vector2 requestedPosition) {
     const maxAttempts = 15; // Increased attempts
     const searchRadius = 100.0; // How far to search around requested position
-    
+
     // First, ensure the requested position is within bounds
     final clampedPosition = _clampPositionToBounds(requestedPosition);
-    
+
     // Try the exact position first
     if (_isPositionSafeImproved(clampedPosition)) {
       return clampedPosition;
     }
-    
+
     // If not safe, try positions in a circle around the requested position
     for (int attempt = 0; attempt < maxAttempts; attempt++) {
       final angle = _random.nextDouble() * 2 * pi; // Random angle
       final distance = _random.nextDouble() * searchRadius; // Random distance
-      
+
       final testPosition = Vector2(
         clampedPosition.x + distance * cos(angle),
         clampedPosition.y + distance * sin(angle),
       );
-      
+
       final boundedPosition = _clampPositionToBounds(testPosition);
-      
+
       if (_isPositionSafeImproved(boundedPosition)) {
         return boundedPosition;
       }
     }
-    
+
     return null; // No safe position found
   }
-  
+
   /// Check if a position is safe (far enough from all existing bubbles)
   /// This improved version accounts for bubble sizes and scaling
   bool _isPositionSafe(Vector2 position, [double? fixedMinDistance]) {
     for (final bubble in _activeBubbles) {
       if (!bubble.isPopping) {
         double requiredDistance;
-        
+
         if (fixedMinDistance != null) {
           // Use fixed distance for backwards compatibility
           requiredDistance = fixedMinDistance;
         } else {
           // Calculate required distance based on bubble sizes
           final bubbleRadius = (bubbleMaxDiameter / 2) * bubble.scale.x;
-          final newBubbleRadius = bubbleMaxDiameter / 2; // New bubble starts small
-          requiredDistance = bubbleRadius + newBubbleRadius + 20.0; // 20px padding
+          final newBubbleRadius =
+              bubbleMaxDiameter / 2; // New bubble starts small
+          requiredDistance =
+              bubbleRadius +
+              newBubbleRadius +
+              30.0; // Increased padding to 30px
         }
-        
+
         final distance = position.distanceTo(bubble.position);
         if (distance < requiredDistance) {
           return false;
@@ -211,7 +228,7 @@ class BubbleSpawner extends Component with HasGameReference {
     }
     return true;
   }
-  
+
   /// Improved position safety check that accounts for bubble sizes
   bool _isPositionSafeImproved(Vector2 position) {
     return _isPositionSafe(position); // Use the improved version by default
@@ -223,13 +240,10 @@ class BubbleSpawner extends Component with HasGameReference {
     final maxX = game.size.x - margin;
     final minY = margin + 100;
     final maxY = game.size.y - margin;
-    
+
     if (maxX <= minX || maxY <= minY) return position;
-    
-    return Vector2(
-      position.x.clamp(minX, maxX),
-      position.y.clamp(minY, maxY),
-    );
+
+    return Vector2(position.x.clamp(minX, maxX), position.y.clamp(minY, maxY));
   }
 
   void clearAllBubbles() {
@@ -247,32 +261,37 @@ class BubbleSpawner extends Component with HasGameReference {
 
   void _calculateNextSpawnTime() {
     // Base spawn time calculation
-    double baseTime = baseSpawnInterval + (_random.nextDouble() - 0.5) * 2 * spawnVariation;
-    
+    double baseTime =
+        baseSpawnInterval + (_random.nextDouble() - 0.5) * 2 * spawnVariation;
+
     // Apply shake mode speed multiplier if active
     if (_isShakeMode) {
       baseTime /= shakeModeSpeedMultiplier;
     }
-    
-    _nextSpawnTime = baseTime.clamp(0.1, double.infinity); // Minimum 0.1 seconds for shake mode
+
+    _nextSpawnTime = baseTime.clamp(
+      0.1,
+      double.infinity,
+    ); // Minimum 0.1 seconds for shake mode
   }
 
   int get activeBubbleCount => _activeBubbles.length;
-  
+
   /// Get list of active bubbles for performance optimization
   List<Bubble> getActiveBubbles() => List.unmodifiable(_activeBubbles);
-  
+
   /// Activate shake mode for faster bubble generation
   void activateShakeMode() {
     _isShakeMode = true;
-    _shakeModeEndTime = (DateTime.now().millisecondsSinceEpoch / 1000.0) + shakeModeDuration;
+    _shakeModeEndTime =
+        (DateTime.now().millisecondsSinceEpoch / 1000.0) + shakeModeDuration;
     // Immediately recalculate spawn time for current bubble
     _calculateNextSpawnTime();
   }
-  
+
   /// Check if shake mode is currently active
   bool get isShakeModeActive => _isShakeMode;
-  
+
   /// Update max bubbles calculation if screen size changed
   void _updateMaxBubblesIfNeeded() {
     if (game.size != _lastCalculatedScreenSize) {
@@ -280,45 +299,69 @@ class BubbleSpawner extends Component with HasGameReference {
       _lastCalculatedScreenSize = game.size.clone();
     }
   }
-  
+
   /// Calculate maximum number of full-size bubbles that can fit on screen
   /// based on bubble size, spacing, and screen dimensions
   int calculateMaxBubblesForScreen() {
-    if (game.size.x == 0 || game.size.y == 0) return 8; // fallback
-    
+    if (game.size.x == 0 || game.size.y == 0) {
+      return 8; // fallback
+    }
+
     return calculateMaxBubblesForScreenSize(game.size);
   }
-  
+
   /// Static method to calculate max bubbles for given screen size
   /// This can be used for testing without requiring a game instance
   static int calculateMaxBubblesForScreenSize(Vector2 screenSize) {
-    if (screenSize.x == 0 || screenSize.y == 0) return 8; // fallback
-    
+    if (screenSize.x == 0 || screenSize.y == 0) {
+      return 8; // fallback
+    }
+
     // Calculate available screen area
     final availableWidth = screenSize.x - (2 * screenMargin);
     final availableHeight = screenSize.y - (2 * screenMargin) - uiTopMargin;
-    
-    if (availableWidth <= 0 || availableHeight <= 0) return 4; // minimum fallback
-    
+
+    if (availableWidth <= 0 || availableHeight <= 0) {
+      return 4; // minimum fallback
+    }
+
     // Calculate effective bubble area (including spacing)
     final effectiveBubbleSize = bubbleMaxDiameter + bubbleMinSpacing;
     final effectiveBubbleArea = effectiveBubbleSize * effectiveBubbleSize;
-    
+
     // Calculate total available area
     final availableArea = availableWidth * availableHeight;
-    
+
     // Calculate theoretical max bubbles
     final theoreticalMax = (availableArea / effectiveBubbleArea).floor();
-    
+
     // Apply practical limits for performance and gameplay
     final practicalMax = theoreticalMax.clamp(4, 15); // between 4-15 bubbles
-    
+
     return practicalMax;
   }
-  
+
   /// Get current screen capacity utilization as percentage
   double getScreenCapacityUtilization() {
-    if (maxBubbles == 0) return 0.0;
+    if (maxBubbles == 0) {
+      return 0.0;
+    }
     return (_activeBubbles.length / maxBubbles).clamp(0.0, 1.0);
   }
-}
+
+  /// Validate that a bubble position doesn't overlap with existing bubbles
+  /// This is a final check before actually spawning the bubble
+  bool _validateBubblePosition(Bubble newBubble) {
+    const minSafeDistance = 40.0; // Minimum safe distance for new bubbles
+
+    for (final existingBubble in _activeBubbles) {
+      if (!existingBubble.isPopping) {
+        final distance = newBubble.position.distanceTo(existingBubble.position);
+        if (distance < minSafeDistance) {
+          return false; // Position is not safe
+        }
+      }
+    }
+    return true; // Position is safe
+  }
+}

+ 2 - 1
lib/l10n/app_de.arb

@@ -122,5 +122,6 @@
   "springTheme": "Frühlingsblüte",
   "summerTheme": "Sommerhell",
   "autumnTheme": "Herbstblätter",
-  "winterTheme": "Winterfrost"
+  "winterTheme": "Winterfrost",
+  "automaticTheme": "Automatisch"
 }

+ 4 - 0
lib/l10n/app_en.arb

@@ -519,5 +519,9 @@
   "winterTheme": "Winter Frost",
   "@winterTheme": {
     "description": "Winter theme name"
+  },
+  "automaticTheme": "Automatic",
+  "@automaticTheme": {
+    "description": "Automatic theme name"
   }
 }

+ 2 - 1
lib/l10n/app_hu.arb

@@ -122,5 +122,6 @@
   "springTheme": "Tavaszi virágzás",
   "summerTheme": "Nyári fényesség",
   "autumnTheme": "Őszi levelek",
-  "winterTheme": "Téli fagy"
+  "winterTheme": "Téli fagy",
+  "automaticTheme": "Automatikus"
 }

+ 6 - 0
lib/l10n/app_localizations.dart

@@ -837,6 +837,12 @@ abstract class AppLocalizations {
   /// In en, this message translates to:
   /// **'Winter Frost'**
   String get winterTheme;
+
+  /// Automatic theme name
+  ///
+  /// In en, this message translates to:
+  /// **'Automatic'**
+  String get automaticTheme;
 }
 
 class _AppLocalizationsDelegate

+ 3 - 0
lib/l10n/app_localizations_de.dart

@@ -396,4 +396,7 @@ class AppLocalizationsDe extends AppLocalizations {
 
   @override
   String get winterTheme => 'Winterfrost';
+
+  @override
+  String get automaticTheme => 'Automatisch';
 }

+ 3 - 0
lib/l10n/app_localizations_en.dart

@@ -390,4 +390,7 @@ class AppLocalizationsEn extends AppLocalizations {
 
   @override
   String get winterTheme => 'Winter Frost';
+
+  @override
+  String get automaticTheme => 'Automatic';
 }

+ 3 - 0
lib/l10n/app_localizations_hu.dart

@@ -392,4 +392,7 @@ class AppLocalizationsHu extends AppLocalizations {
 
   @override
   String get winterTheme => 'Téli fagy';
+
+  @override
+  String get automaticTheme => 'Automatikus';
 }

+ 3 - 8
lib/main.dart

@@ -15,13 +15,13 @@ void main() async {
   await SettingsManager.init();
   await ScoreManager.initialize();
   await LocaleManager.init();
-  
+
   // Initialize Google Play Games (silent init, don't require sign-in)
   await GooglePlayGamesManager.instance.initialize();
-  
+
   // Initialize app lifecycle manager for game suspension
   AppLifecycleManager.instance.initialize();
-  
+
   runApp(const ZenTapApp());
 }
 
@@ -33,12 +33,9 @@ class ZenTapApp extends StatefulWidget {
 }
 
 class _ZenTapAppState extends State<ZenTapApp> {
-  static _ZenTapAppState? _instance;
-
   @override
   void initState() {
     super.initState();
-    _instance = this;
     // Set up locale change callback
     LocaleManager.setLocaleChangeCallback(() {
       setState(() {});
@@ -47,11 +44,9 @@ class _ZenTapAppState extends State<ZenTapApp> {
 
   @override
   void dispose() {
-    _instance = null;
     super.dispose();
   }
 
-
   @override
   Widget build(BuildContext context) {
     return MaterialApp(

+ 576 - 26
lib/ui/components/animated_background.dart

@@ -1,6 +1,7 @@
 import 'package:flutter/material.dart';
 import 'dart:math' as math;
 import '../../utils/colors.dart';
+import '../../utils/theme_manager.dart';
 import '../../utils/theme_notifier.dart';
 
 class AnimatedBackground extends StatefulWidget {
@@ -25,29 +26,66 @@ class _AnimatedBackgroundState extends State<AnimatedBackground>
   late AnimationController _controller2;
   late AnimationController _controller3;
   late AnimationController _shakeController;
-  
+  late AnimationController _seasonalController;
+
   bool _isShaking = false;
 
+  // Static variables to preserve animation state across navigation
+  static double _preservedLayer1Value = 0.0;
+  static double _preservedLayer2Value = 0.0;
+  static double _preservedLayer3Value = 0.0;
+  static double _preservedSeasonalValue = 0.0;
+  static DateTime _lastPreservedTime = DateTime.now();
+
   @override
   void initState() {
     super.initState();
-    
-    // Create multiple animation controllers for layered effects (optimized for performance)
+
+    // Calculate time elapsed since last preservation
+    final now = DateTime.now();
+    final elapsedSeconds =
+        now.difference(_lastPreservedTime).inMilliseconds / 1000.0;
+
+    // Update preserved values based on elapsed time
+    _preservedLayer1Value =
+        (_preservedLayer1Value + elapsedSeconds / 60.0) % 1.0;
+    _preservedLayer2Value =
+        (_preservedLayer2Value + elapsedSeconds / 80.0) % 1.0;
+    _preservedLayer3Value =
+        (_preservedLayer3Value + elapsedSeconds / 100.0) % 1.0;
+    _preservedSeasonalValue =
+        (_preservedSeasonalValue + elapsedSeconds / 60.0) % 1.0;
+
+    // Create multiple animation controllers for layered effects
     _controller1 = AnimationController(
-      duration: const Duration(seconds: 60), // Slower animations for better performance
+      duration: const Duration(seconds: 60),
       vsync: this,
-    )..repeat();
-    
+    );
+    _controller1.value = _preservedLayer1Value;
+    _controller1.repeat();
+
     _controller2 = AnimationController(
-      duration: const Duration(seconds: 80), // Slower animations for better performance
+      duration: const Duration(seconds: 80),
       vsync: this,
-    )..repeat();
-    
+    );
+    _controller2.value = _preservedLayer2Value;
+    _controller2.repeat();
+
     _controller3 = AnimationController(
-      duration: const Duration(seconds: 100), // Much slower for better performance
+      duration: const Duration(seconds: 100),
       vsync: this,
-    )..repeat();
-    
+    );
+    _controller3.value = _preservedLayer3Value;
+    _controller3.repeat();
+
+    // Faster controller for seasonal patterns
+    _seasonalController = AnimationController(
+      duration: const Duration(seconds: 60), // Faster for seasonal effects
+      vsync: this,
+    );
+    _seasonalController.value = _preservedSeasonalValue;
+    _seasonalController.repeat();
+
     // Shake effect controller
     _shakeController = AnimationController(
       duration: const Duration(milliseconds: 800),
@@ -57,13 +95,21 @@ class _AnimatedBackgroundState extends State<AnimatedBackground>
 
   @override
   void dispose() {
+    // Preserve current animation values for next instance
+    _preservedLayer1Value = _controller1.value;
+    _preservedLayer2Value = _controller2.value;
+    _preservedLayer3Value = _controller3.value;
+    _preservedSeasonalValue = _seasonalController.value;
+    _lastPreservedTime = DateTime.now();
+
     _controller1.dispose();
     _controller2.dispose();
     _controller3.dispose();
     _shakeController.dispose();
+    _seasonalController.dispose();
     super.dispose();
   }
-  
+
   void triggerShake() {
     if (!_isShaking) {
       _isShaking = true;
@@ -79,6 +125,8 @@ class _AnimatedBackgroundState extends State<AnimatedBackground>
   Widget build(BuildContext context) {
     return ThemeAwareBuilder(
       builder: (context, theme) {
+        final seasonalPattern = ThemeManager.effectiveTheme;
+
         return Stack(
           children: [
             // Base background
@@ -94,10 +142,14 @@ class _AnimatedBackgroundState extends State<AnimatedBackground>
                 ),
               ),
             ),
-            
-            // Animated gradient layers (reduced to 2 for better performance)
+
+            // Seasonal pattern overlay
+            if (seasonalPattern != SeasonalTheme.default_)
+              _buildSeasonalPattern(seasonalPattern),
+
+            // Animated gradient layers
             ...List.generate(2, (index) => _buildAnimatedLayer(index)),
-            
+
             // Content overlay
             widget.child,
           ],
@@ -106,16 +158,122 @@ class _AnimatedBackgroundState extends State<AnimatedBackground>
     );
   }
 
+  Widget _buildSeasonalPattern(SeasonalTheme theme) {
+    switch (theme) {
+      case SeasonalTheme.winter:
+        return _buildSnowfall();
+      case SeasonalTheme.summer:
+        return _buildSunshine();
+      case SeasonalTheme.autumn:
+        return _buildFallingLeaves();
+      case SeasonalTheme.spring:
+        return _buildFlowersBlooming();
+      default:
+        return const SizedBox.shrink();
+    }
+  }
+
+  Widget _buildSnowfall() {
+    return AnimatedBuilder(
+      animation: _seasonalController,
+      builder: (context, child) {
+        return Container(
+          decoration: BoxDecoration(
+            gradient: RadialGradient(
+              center: Alignment.topCenter,
+              radius: 1.5,
+              colors: [Colors.white.withValues(alpha: 0.1), Colors.transparent],
+            ),
+          ),
+          child: CustomPaint(
+            painter: SnowfallPainter(_seasonalController.value),
+            size: Size.infinite,
+          ),
+        );
+      },
+    );
+  }
+
+  Widget _buildSunshine() {
+    return AnimatedBuilder(
+      animation: _seasonalController,
+      builder: (context, child) {
+        return Container(
+          decoration: BoxDecoration(
+            gradient: RadialGradient(
+              center: Alignment.topCenter,
+              radius: 1.5,
+              colors: [
+                Colors.yellow.withValues(alpha: 0.15),
+                Colors.transparent,
+              ],
+            ),
+          ),
+          child: CustomPaint(
+            painter: SunshinePainter(_seasonalController.value),
+            size: Size.infinite,
+          ),
+        );
+      },
+    );
+  }
+
+  Widget _buildFallingLeaves() {
+    return AnimatedBuilder(
+      animation: _seasonalController,
+      builder: (context, child) {
+        return Container(
+          decoration: BoxDecoration(
+            gradient: RadialGradient(
+              center: Alignment.topCenter,
+              radius: 1.5,
+              colors: [
+                Colors.orange.withValues(alpha: 0.1),
+                Colors.transparent,
+              ],
+            ),
+          ),
+          child: CustomPaint(
+            painter: FallingLeavesPainter(_seasonalController.value),
+            size: Size.infinite,
+          ),
+        );
+      },
+    );
+  }
+
+  Widget _buildFlowersBlooming() {
+    return AnimatedBuilder(
+      animation: _seasonalController,
+      builder: (context, child) {
+        return Container(
+          decoration: BoxDecoration(
+            gradient: RadialGradient(
+              center: Alignment.topCenter,
+              radius: 1.5,
+              colors: [Colors.pink.withValues(alpha: 0.1), Colors.transparent],
+            ),
+          ),
+          child: CustomPaint(
+            painter: FlowersPainter(_seasonalController.value),
+            size: Size.infinite,
+          ),
+        );
+      },
+    );
+  }
+
   Widget _buildAnimatedLayer(int layerIndex) {
     AnimationController controller;
     List<Color> colors;
-    
+
     switch (layerIndex) {
       case 0:
         controller = _controller1;
-        colors = widget.isZenMode
-          ? ZenColors.zenModeColors
-          : ZenColors.animationLayer1;
+        colors =
+            widget.isZenMode
+                ? ZenColors.zenModeColors
+                : ZenColors.animationLayer1;
         break;
       case 1:
         controller = _controller2;
@@ -132,10 +290,12 @@ class _AnimatedBackgroundState extends State<AnimatedBackground>
         // Add shake effect
         double shakeIntensity = 0.0;
         if (_isShaking) {
-          shakeIntensity = math.sin(_shakeController.value * math.pi * 8) *
-                          (1 - _shakeController.value) * 10; // Decay over time
+          shakeIntensity =
+              math.sin(_shakeController.value * math.pi * 8) *
+              (1 - _shakeController.value) *
+              10;
         }
-        
+
         return Transform.translate(
           offset: Offset(
             shakeIntensity * (math.Random().nextDouble() - 0.5) * 2,
@@ -150,8 +310,10 @@ class _AnimatedBackgroundState extends State<AnimatedBackground>
                     math.sin(controller.value * 2 * math.pi) * 0.5,
                     math.cos(controller.value * 2 * math.pi) * 0.5,
                   ),
-                  radius: 1.5 + math.sin(controller.value * math.pi) * 0.5 +
-                          (shakeIntensity * 0.1), // Shake affects radius too
+                  radius:
+                      1.5 +
+                      math.sin(controller.value * math.pi) * 0.5 +
+                      (shakeIntensity * 0.1),
                   colors: colors,
                 ),
               ),
@@ -161,4 +323,392 @@ class _AnimatedBackgroundState extends State<AnimatedBackground>
       },
     );
   }
-}
+}
+
+class SnowfallPainter extends CustomPainter {
+  final double animationValue;
+
+  SnowfallPainter(this.animationValue);
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final paint =
+        Paint()
+          ..color = Colors.white.withValues(alpha: 0.3)
+          ..style = PaintingStyle.fill;
+
+    // Fixed seed for consistent positions (removed unused variable)
+
+    // Draw 80 snowflakes with falling animation covering entire screen
+    for (int i = 0; i < 80; i++) {
+      // Better distribution using sine and cosine functions with different frequencies
+      final angle1 =
+          i * 2.39996; // Use irrational number for better distribution
+      final baseX = (math.sin(angle1) * 0.5 + 0.5) * size.width;
+
+      // Consistent falling animation starting from top
+      const fallSpeed = 0.2; // Same speed for all snowflakes
+      final fallOffset = animationValue * fallSpeed * size.height * 1.2;
+      final animatedY =
+          (fallOffset + (i * 20) % size.height) % (size.height + 100) - 100;
+
+      final snowflakeSize = (2 + math.sin(i * 0.3) * 2) * 3; // 3x larger
+      _drawDetailedSnowflake(
+        canvas,
+        Offset(baseX, animatedY),
+        snowflakeSize,
+        paint,
+      );
+    }
+  }
+
+  void _drawDetailedSnowflake(
+    Canvas canvas,
+    Offset center,
+    double size,
+    Paint paint,
+  ) {
+    // Draw a 6-pointed snowflake
+    final strokePaint =
+        Paint()
+          ..color = paint.color
+          ..strokeWidth = size * 0.1
+          ..style = PaintingStyle.stroke;
+
+    // Draw 6 main spokes
+    for (int i = 0; i < 6; i++) {
+      final angle = i * math.pi / 3;
+      final startX = center.dx + math.cos(angle) * size * 0.2;
+      final startY = center.dy + math.sin(angle) * size * 0.2;
+      final endX = center.dx + math.cos(angle) * size;
+      final endY = center.dy + math.sin(angle) * size;
+
+      canvas.drawLine(Offset(startX, startY), Offset(endX, endY), strokePaint);
+
+      // Draw small branches on each spoke
+      final branchSize = size * 0.3;
+      final branchX = center.dx + math.cos(angle) * size * 0.6;
+      final branchY = center.dy + math.sin(angle) * size * 0.6;
+
+      // Left branch
+      final leftBranchAngle = angle - math.pi / 4;
+      canvas.drawLine(
+        Offset(branchX, branchY),
+        Offset(
+          branchX + math.cos(leftBranchAngle) * branchSize,
+          branchY + math.sin(leftBranchAngle) * branchSize,
+        ),
+        strokePaint,
+      );
+
+      // Right branch
+      final rightBranchAngle = angle + math.pi / 4;
+      canvas.drawLine(
+        Offset(branchX, branchY),
+        Offset(
+          branchX + math.cos(rightBranchAngle) * branchSize,
+          branchY + math.sin(rightBranchAngle) * branchSize,
+        ),
+        strokePaint,
+      );
+    }
+
+    // Draw center circle
+    canvas.drawCircle(center, size * 0.15, paint);
+  }
+
+  @override
+  bool shouldRepaint(CustomPainter oldDelegate) => true;
+}
+
+class SunshinePainter extends CustomPainter {
+  final double animationValue;
+
+  SunshinePainter(this.animationValue);
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final center = Offset(size.width * 0.8, size.height * 0.2);
+
+    // Draw sun rays
+    final rayPaint =
+        Paint()
+          ..color = Colors.yellow.withValues(alpha: 0.2)
+          ..strokeWidth = 2
+          ..style = PaintingStyle.stroke;
+
+    for (int i = 0; i < 12; i++) {
+      final angle = (i * math.pi * 2 / 12) + (animationValue * math.pi * 2);
+      final startRadius = 30;
+      final endRadius = 60 + math.sin(animationValue * math.pi * 4) * 10;
+
+      final start = Offset(
+        center.dx + math.cos(angle) * startRadius,
+        center.dy + math.sin(angle) * startRadius,
+      );
+      final end = Offset(
+        center.dx + math.cos(angle) * endRadius,
+        center.dy + math.sin(angle) * endRadius,
+      );
+
+      canvas.drawLine(start, end, rayPaint);
+    }
+
+    // Draw sun
+    final sunPaint =
+        Paint()
+          ..color = Colors.yellow.withValues(alpha: 0.3)
+          ..style = PaintingStyle.fill;
+
+    canvas.drawCircle(center, 25, sunPaint);
+  }
+
+  @override
+  bool shouldRepaint(CustomPainter oldDelegate) => true;
+}
+
+class FallingLeavesPainter extends CustomPainter {
+  final double animationValue;
+
+  FallingLeavesPainter(this.animationValue);
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final paint =
+        Paint()
+          ..color = Colors.orange.withValues(alpha: 0.4)
+          ..style = PaintingStyle.fill;
+
+    // Fixed seed for consistent positions (removed unused variable)
+
+    // Draw 40 leaves with slow falling animation
+    for (int i = 0; i < 40; i++) {
+      // Better distribution using trigonometric functions
+      final angle1 = i * 1.618; // Golden ratio for better distribution
+      final baseX = (math.sin(angle1) * 0.5 + 0.5) * size.width;
+
+      // Consistent falling animation starting from top with slight swaying
+      const fallSpeed = 0.2; // Same speed as snowflakes
+      final swayAmount = math.sin(animationValue * math.pi * 2 + i) * 25;
+      final fallOffset = animationValue * fallSpeed * size.height * 1.2;
+      final animatedY =
+          (fallOffset + (i * 30) % size.height) % (size.height + 100) - 100;
+      final animatedX = baseX + swayAmount;
+
+      final leafSize = (6 + math.sin(i * 0.4) * 4) * 3; // 3x larger
+      _drawDetailedLeaf(
+        canvas,
+        Offset(animatedX, animatedY),
+        leafSize,
+        paint,
+        i,
+      );
+    }
+  }
+
+  void _drawDetailedLeaf(
+    Canvas canvas,
+    Offset center,
+    double size,
+    Paint paint,
+    int index,
+  ) {
+    // Create a more detailed leaf shape
+    final leafPaint =
+        Paint()
+          ..color = paint.color
+          ..style = PaintingStyle.fill;
+
+    final strokePaint =
+        Paint()
+          ..color = paint.color.withValues(alpha: 0.8)
+          ..strokeWidth = size * 0.05
+          ..style = PaintingStyle.stroke;
+
+    // Different leaf shapes based on index
+    final leafType = index % 3;
+
+    if (leafType == 0) {
+      // Oak leaf shape
+      _drawOakLeaf(canvas, center, size, leafPaint, strokePaint);
+    } else if (leafType == 1) {
+      // Maple leaf shape
+      _drawMapleLeaf(canvas, center, size, leafPaint, strokePaint);
+    } else {
+      // Simple oval leaf with stem
+      _drawSimpleLeaf(canvas, center, size, leafPaint, strokePaint);
+    }
+  }
+
+  void _drawOakLeaf(
+    Canvas canvas,
+    Offset center,
+    double size,
+    Paint fillPaint,
+    Paint strokePaint,
+  ) {
+    final path = Path();
+    path.moveTo(center.dx, center.dy - size * 0.4);
+
+    // Create wavy edges
+    for (int i = 0; i < 6; i++) {
+      final angle = i * math.pi / 3;
+      final radius = size * (0.3 + math.sin(i * 2) * 0.1);
+      path.lineTo(
+        center.dx + math.cos(angle) * radius,
+        center.dy + math.sin(angle) * radius,
+      );
+    }
+
+    path.close();
+    canvas.drawPath(path, fillPaint);
+    canvas.drawPath(path, strokePaint);
+
+    // Draw stem
+    canvas.drawLine(
+      center,
+      Offset(center.dx, center.dy + size * 0.3),
+      strokePaint,
+    );
+  }
+
+  void _drawMapleLeaf(
+    Canvas canvas,
+    Offset center,
+    double size,
+    Paint fillPaint,
+    Paint strokePaint,
+  ) {
+    final path = Path();
+
+    // Create 5-pointed maple leaf shape
+    for (int i = 0; i < 5; i++) {
+      final angle = i * math.pi * 2 / 5 - math.pi / 2;
+      final radius = size * (i % 2 == 0 ? 0.4 : 0.2);
+      if (i == 0) {
+        path.moveTo(
+          center.dx + math.cos(angle) * radius,
+          center.dy + math.sin(angle) * radius,
+        );
+      } else {
+        path.lineTo(
+          center.dx + math.cos(angle) * radius,
+          center.dy + math.sin(angle) * radius,
+        );
+      }
+    }
+
+    path.close();
+    canvas.drawPath(path, fillPaint);
+    canvas.drawPath(path, strokePaint);
+
+    // Draw stem
+    canvas.drawLine(
+      center,
+      Offset(center.dx, center.dy + size * 0.3),
+      strokePaint,
+    );
+  }
+
+  void _drawSimpleLeaf(
+    Canvas canvas,
+    Offset center,
+    double size,
+    Paint fillPaint,
+    Paint strokePaint,
+  ) {
+    // Draw oval leaf
+    canvas.drawOval(
+      Rect.fromCenter(center: center, width: size, height: size * 0.6),
+      fillPaint,
+    );
+
+    // Draw center vein
+    canvas.drawLine(
+      Offset(center.dx, center.dy - size * 0.3),
+      Offset(center.dx, center.dy + size * 0.3),
+      strokePaint,
+    );
+
+    // Draw side veins
+    for (int i = 0; i < 3; i++) {
+      final y = center.dy + (i - 1) * size * 0.15;
+      canvas.drawLine(
+        Offset(center.dx, y),
+        Offset(center.dx + size * 0.3, y),
+        strokePaint,
+      );
+      canvas.drawLine(
+        Offset(center.dx, y),
+        Offset(center.dx - size * 0.3, y),
+        strokePaint,
+      );
+    }
+
+    // Draw stem
+    canvas.drawLine(
+      center,
+      Offset(center.dx, center.dy + size * 0.4),
+      strokePaint,
+    );
+  }
+
+  @override
+  bool shouldRepaint(CustomPainter oldDelegate) => true;
+}
+
+class FlowersPainter extends CustomPainter {
+  final double animationValue;
+
+  FlowersPainter(this.animationValue);
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final petalPaint =
+        Paint()
+          ..color = Colors.pink.withValues(alpha: 0.3)
+          ..style = PaintingStyle.fill;
+
+    final centerPaint =
+        Paint()
+          ..color = Colors.yellow.withValues(alpha: 0.4)
+          ..style = PaintingStyle.fill;
+
+    // Fixed seed for consistent positions (removed unused variable)
+
+    // Draw 25 flowers with gentle falling animation
+    for (int i = 0; i < 25; i++) {
+      // Better distribution using sine for X position
+      final angle = i * 2.399963; // Golden angle in radians
+      final baseX = (math.sin(angle) * 0.5 + 0.5) * size.width;
+
+      // Consistent falling animation starting from top
+      const fallSpeed = 0.2; // Same speed as other elements
+      final fallOffset = animationValue * fallSpeed * size.height * 1.2;
+      final animatedY =
+          (fallOffset + (i * 25) % size.height) % (size.height + 100) - 100;
+      final center = Offset(baseX, animatedY);
+
+      // Blooming animation - flowers grow and shrink gently
+      final bloomPhase = (animationValue + i * 0.1) % 1.0;
+      final scale =
+          (0.5 + math.sin(bloomPhase * math.pi * 2) * 0.3) * 2; // 2x larger
+
+      // Draw petals
+      for (int petal = 0; petal < 5; petal++) {
+        final petalAngle = petal * math.pi * 2 / 5;
+        final petalCenter = Offset(
+          center.dx + math.cos(petalAngle) * 8 * scale,
+          center.dy + math.sin(petalAngle) * 8 * scale,
+        );
+        canvas.drawCircle(petalCenter, 6 * scale, petalPaint);
+      }
+
+      // Draw flower center
+      canvas.drawCircle(center, 4 * scale, centerPaint);
+    }
+  }
+
+  @override
+  bool shouldRepaint(CustomPainter oldDelegate) => true;
+}

+ 308 - 240
lib/ui/settings_screen.dart

@@ -1,3 +1,4 @@
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:url_launcher/url_launcher.dart';
 import '../l10n/app_localizations.dart';
@@ -61,7 +62,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
                             size: 28,
                           ),
                           style: IconButton.styleFrom(
-                            backgroundColor: ZenColors.black.withValues(alpha: 0.3),
+                            backgroundColor: ZenColors.black.withValues(
+                              alpha: 0.3,
+                            ),
                             shape: const CircleBorder(),
                           ),
                         ),
@@ -78,7 +81,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
                       ],
                     ),
                   ),
-                  
+
                   // Settings List
                   Expanded(
                     child: SingleChildScrollView(
@@ -86,13 +89,19 @@ class _SettingsScreenState extends State<SettingsScreen> {
                       child: Column(
                         children: [
                           const SizedBox(height: 20),
-                          
+
                           // Audio Section
-                          _buildSectionHeader(AppLocalizations.of(context)!.audio),
+                          _buildSectionHeader(
+                            AppLocalizations.of(context)!.audio,
+                          ),
                           _buildSettingTile(
                             icon: Icons.music_note,
-                            title: AppLocalizations.of(context)!.backgroundMusic,
-                            subtitle: AppLocalizations.of(context)!.backgroundMusicDesc,
+                            title:
+                                AppLocalizations.of(context)!.backgroundMusic,
+                            subtitle:
+                                AppLocalizations.of(
+                                  context,
+                                )!.backgroundMusicDesc,
                             value: _musicEnabled,
                             onChanged: _toggleMusic,
                           ),
@@ -102,65 +111,84 @@ class _SettingsScreenState extends State<SettingsScreen> {
                             value: _bgmVolume,
                             onChanged: _setBgmVolume,
                           ),
-                          
+
                           const SizedBox(height: 10),
-                          
+
                           _buildVolumeSlider(
                             icon: Icons.volume_up,
-                            title: AppLocalizations.of(context)!.soundEffectsVolume,
+                            title:
+                                AppLocalizations.of(
+                                  context,
+                                )!.soundEffectsVolume,
                             value: _sfxVolume,
                             onChanged: _setSfxVolume,
                           ),
-                          
+
                           const SizedBox(height: 30),
-                          
+
                           // Feedback Section
-                          _buildSectionHeader(AppLocalizations.of(context)!.feedback),
+                          _buildSectionHeader(
+                            AppLocalizations.of(context)!.feedback,
+                          ),
                           _buildSettingTile(
                             icon: Icons.vibration,
                             title: AppLocalizations.of(context)!.hapticFeedback,
-                            subtitle: AppLocalizations.of(context)!.hapticFeedbackDesc,
+                            subtitle:
+                                AppLocalizations.of(
+                                  context,
+                                )!.hapticFeedbackDesc,
                             value: _hapticsEnabled,
                             onChanged: _toggleHaptics,
                           ),
-                          
+
                           const SizedBox(height: 30),
-                          
+
                           // Appearance Section
-                          _buildSectionHeader(AppLocalizations.of(context)!.appearance),
+                          _buildSectionHeader(
+                            AppLocalizations.of(context)!.appearance,
+                          ),
                           _buildThemeTile(),
-                          
+
                           const SizedBox(height: 30),
-                          
+
                           // Language Section
-                          _buildSectionHeader(AppLocalizations.of(context)!.language),
+                          _buildSectionHeader(
+                            AppLocalizations.of(context)!.language,
+                          ),
                           _buildLanguageTile(),
-                          
+
                           const SizedBox(height: 30),
-                          
+
                           // Tutorial Section
-                          _buildSectionHeader(AppLocalizations.of(context)!.help),
+                          _buildSectionHeader(
+                            AppLocalizations.of(context)!.help,
+                          ),
                           _buildActionTile(
                             icon: Icons.help_outline,
                             title: AppLocalizations.of(context)!.showTutorial,
-                            subtitle: AppLocalizations.of(context)!.showTutorialDesc,
+                            subtitle:
+                                AppLocalizations.of(context)!.showTutorialDesc,
                             onTap: _showTutorial,
                           ),
-                          
+
                           const SizedBox(height: 30),
-                          
+
                           // Google Play Games Section
-                          _buildSectionHeader(AppLocalizations.of(context)!.googlePlayGames),
+                          _buildSectionHeader(
+                            AppLocalizations.of(context)!.googlePlayGames,
+                          ),
                           const GooglePlayGamesWidget(),
-                          
+
                           const SizedBox(height: 30),
-                          
+
                           // Support Section
-                          _buildSectionHeader(AppLocalizations.of(context)!.supportZenTap),
+                          _buildSectionHeader(
+                            AppLocalizations.of(context)!.supportZenTap,
+                          ),
                           _buildDonationTile(),
-                          
+
                           const SizedBox(height: 30),
-                          
+
                           // About Section
                           Padding(
                             padding: const EdgeInsets.only(bottom: 20),
@@ -175,7 +203,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
                                 ),
                                 const SizedBox(height: 8),
                                 Text(
-                                  AppLocalizations.of(context)!.stressReliefGame,
+                                  AppLocalizations.of(
+                                    context,
+                                  )!.stressReliefGame,
                                   style: TextStyle(
                                     color: ZenColors.currentMutedText,
                                     fontSize: 12,
@@ -233,11 +263,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
       ),
       child: Row(
         children: [
-          Icon(
-            icon,
-            color: ZenColors.currentPrimaryText,
-            size: 24,
-          ),
+          Icon(icon, color: ZenColors.currentPrimaryText, size: 24),
           const SizedBox(width: 16),
           Expanded(
             child: Column(
@@ -266,9 +292,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
             value: value,
             onChanged: onChanged,
             activeColor: ZenColors.currentButtonBackground,
-            activeTrackColor: ZenColors.currentButtonBackground.withValues(alpha: 0.3),
+            activeTrackColor: ZenColors.currentButtonBackground.withValues(
+              alpha: 0.3,
+            ),
             inactiveThumbColor: ZenColors.currentMutedText,
-            inactiveTrackColor: ZenColors.currentMutedText.withValues(alpha: 0.2),
+            inactiveTrackColor: ZenColors.currentMutedText.withValues(
+              alpha: 0.2,
+            ),
           ),
         ],
       ),
@@ -300,11 +330,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
             ),
             child: Row(
               children: [
-                Icon(
-                  icon,
-                  color: ZenColors.currentPrimaryText,
-                  size: 24,
-                ),
+                Icon(icon, color: ZenColors.currentPrimaryText, size: 24),
                 const SizedBox(width: 16),
                 Expanded(
                   child: Column(
@@ -361,11 +387,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
       ),
       child: Row(
         children: [
-          Icon(
-            icon,
-            color: ZenColors.currentPrimaryText,
-            size: 24,
-          ),
+          Icon(icon, color: ZenColors.currentPrimaryText, size: 24),
           const SizedBox(width: 16),
           Expanded(
             child: Column(
@@ -388,7 +410,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
                   divisions: 10,
                   label: '${(value * 100).round()}%',
                   activeColor: ZenColors.currentButtonBackground,
-                  inactiveColor: ZenColors.currentMutedText.withValues(alpha: 0.2),
+                  inactiveColor: ZenColors.currentMutedText.withValues(
+                    alpha: 0.2,
+                  ),
                 ),
               ],
             ),
@@ -399,9 +423,19 @@ class _SettingsScreenState extends State<SettingsScreen> {
   }
 
   Widget _buildThemeTile() {
-    final currentTheme = ThemeManager.currentTheme;
+    var currentTheme = ThemeManager.currentTheme;
+
+    // In release builds, if default theme is selected, switch to automatic
+    if (!kDebugMode && currentTheme == SeasonalTheme.default_) {
+      currentTheme = SeasonalTheme.automatic;
+      // Update the setting asynchronously
+      WidgetsBinding.instance.addPostFrameCallback((_) {
+        ThemeManager.setTheme(SeasonalTheme.automatic);
+      });
+    }
+
     final currentThemeName = _getLocalizedThemeName(currentTheme);
-    
+
     return Container(
       margin: const EdgeInsets.only(top: 12),
       child: Material(
@@ -465,8 +499,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
 
   Widget _buildLanguageTile() {
     final currentLocale = LocaleManager.currentLocale;
-    final currentLanguageName = LocaleManager.getLocaleDisplayName(currentLocale);
-    
+    final currentLanguageName = LocaleManager.getLocaleDisplayName(
+      currentLocale,
+    );
+
     return Container(
       margin: const EdgeInsets.only(top: 12),
       child: Material(
@@ -543,7 +579,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
       _musicEnabled = value;
     });
     await SettingsManager.setMusicEnabled(value);
-    
+
     if (_hapticsEnabled) {
       await HapticUtils.vibrate(duration: 50);
     }
@@ -570,7 +606,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
       _hapticsEnabled = value;
     });
     await SettingsManager.setHapticsEnabled(value);
-    
+
     // Give immediate feedback if enabling haptics
     if (value) {
       await HapticUtils.vibrate(duration: 100);
@@ -581,53 +617,39 @@ class _SettingsScreenState extends State<SettingsScreen> {
     if (_hapticsEnabled) {
       HapticUtils.vibrate(duration: 50);
     }
-    
-    showDialog(
-      context: context,
-      builder: (context) => _buildTutorialDialog(),
-    );
+
+    showDialog(context: context, builder: (context) => _buildTutorialDialog());
   }
 
   void _showDonationDialog() {
     if (_hapticsEnabled) {
       HapticUtils.vibrate(duration: 50);
     }
-    
-    showDialog(
-      context: context,
-      builder: (context) => _buildDonationDialog(),
-    );
+
+    showDialog(context: context, builder: (context) => _buildDonationDialog());
   }
 
   void _showLanguageDialog() {
     if (_hapticsEnabled) {
       HapticUtils.vibrate(duration: 50);
     }
-    
-    showDialog(
-      context: context,
-      builder: (context) => _buildLanguageDialog(),
-    );
+
+    showDialog(context: context, builder: (context) => _buildLanguageDialog());
   }
 
   void _showThemeDialog() {
     if (_hapticsEnabled) {
       HapticUtils.vibrate(duration: 50);
     }
-    
-    showDialog(
-      context: context,
-      builder: (context) => _buildThemeDialog(),
-    );
+
+    showDialog(context: context, builder: (context) => _buildThemeDialog());
   }
 
   // Dialog Builders
   Widget _buildTutorialDialog() {
     return AlertDialog(
       backgroundColor: ZenColors.currentUiElements,
-      shape: RoundedRectangleBorder(
-        borderRadius: BorderRadius.circular(20),
-      ),
+      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
       title: Text(
         AppLocalizations.of(context)!.howToUseZenTap,
         style: TextStyle(
@@ -677,17 +699,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
     );
   }
 
-  Widget _buildTutorialStep({
-    required IconData icon,
-    required String text,
-  }) {
+  Widget _buildTutorialStep({required IconData icon, required String text}) {
     return Row(
       children: [
-        Icon(
-          icon,
-          color: ZenColors.currentButtonBackground,
-          size: 20,
-        ),
+        Icon(icon, color: ZenColors.currentButtonBackground, size: 20),
         const SizedBox(width: 12),
         Expanded(
           child: Text(
@@ -704,12 +719,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
 
   Widget _buildLanguageDialog() {
     final currentLocale = LocaleManager.currentLocale;
-    
+
     return AlertDialog(
       backgroundColor: ZenColors.currentUiElements,
-      shape: RoundedRectangleBorder(
-        borderRadius: BorderRadius.circular(20),
-      ),
+      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
       title: Text(
         AppLocalizations.of(context)!.selectLanguage,
         style: TextStyle(
@@ -720,65 +733,78 @@ class _SettingsScreenState extends State<SettingsScreen> {
       ),
       content: Column(
         mainAxisSize: MainAxisSize.min,
-        children: LocaleManager.supportedLocales.map((locale) {
-          final isSelected = locale.languageCode == currentLocale.languageCode;
-          final languageName = LocaleManager.getLocaleDisplayName(locale);
-          
-          return Container(
-            margin: const EdgeInsets.only(bottom: 8),
-            child: Material(
-              color: isSelected
-                ? ZenColors.currentButtonBackground.withValues(alpha: 0.1)
-                : Colors.transparent,
-              borderRadius: BorderRadius.circular(12),
-              child: InkWell(
-                onTap: () => _selectLanguage(locale),
-                borderRadius: BorderRadius.circular(12),
-                child: Container(
-                  padding: const EdgeInsets.all(12),
-                  decoration: BoxDecoration(
+        children:
+            LocaleManager.supportedLocales.map((locale) {
+              final isSelected =
+                  locale.languageCode == currentLocale.languageCode;
+              final languageName = LocaleManager.getLocaleDisplayName(locale);
+
+              return Container(
+                margin: const EdgeInsets.only(bottom: 8),
+                child: Material(
+                  color:
+                      isSelected
+                          ? ZenColors.currentButtonBackground.withValues(
+                            alpha: 0.1,
+                          )
+                          : Colors.transparent,
+                  borderRadius: BorderRadius.circular(12),
+                  child: InkWell(
+                    onTap: () => _selectLanguage(locale),
                     borderRadius: BorderRadius.circular(12),
-                    border: Border.all(
-                      color: isSelected
-                        ? ZenColors.currentButtonBackground.withValues(alpha: 0.3)
-                        : ZenColors.currentUiElements.withValues(alpha: 0.2),
-                      width: 1,
-                    ),
-                  ),
-                  child: Row(
-                    children: [
-                      Expanded(
-                        child: Text(
-                          languageName,
-                          style: TextStyle(
-                            color: isSelected ? ZenColors.currentButtonBackground : ZenColors.currentPrimaryText,
-                            fontSize: 16,
-                            fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
-                          ),
+                    child: Container(
+                      padding: const EdgeInsets.all(12),
+                      decoration: BoxDecoration(
+                        borderRadius: BorderRadius.circular(12),
+                        border: Border.all(
+                          color:
+                              isSelected
+                                  ? ZenColors.currentButtonBackground
+                                      .withValues(alpha: 0.3)
+                                  : ZenColors.currentUiElements.withValues(
+                                    alpha: 0.2,
+                                  ),
+                          width: 1,
                         ),
                       ),
-                      if (isSelected)
-                        Icon(
-                          Icons.check,
-                          color: ZenColors.currentButtonBackground,
-                          size: 20,
-                        ),
-                    ],
+                      child: Row(
+                        children: [
+                          Expanded(
+                            child: Text(
+                              languageName,
+                              style: TextStyle(
+                                color:
+                                    isSelected
+                                        ? ZenColors.currentButtonBackground
+                                        : ZenColors.currentPrimaryText,
+                                fontSize: 16,
+                                fontWeight:
+                                    isSelected
+                                        ? FontWeight.w600
+                                        : FontWeight.normal,
+                              ),
+                            ),
+                          ),
+                          if (isSelected)
+                            Icon(
+                              Icons.check,
+                              color: ZenColors.currentButtonBackground,
+                              size: 20,
+                            ),
+                        ],
+                      ),
+                    ),
                   ),
                 ),
-              ),
-            ),
-          );
-        }).toList(),
+              );
+            }).toList(),
       ),
       actions: [
         TextButton(
           onPressed: () => Navigator.of(context).pop(),
           child: Text(
             AppLocalizations.of(context)!.cancel,
-            style: TextStyle(
-              color: ZenColors.currentMutedText,
-            ),
+            style: TextStyle(color: ZenColors.currentMutedText),
           ),
         ),
       ],
@@ -787,12 +813,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
 
   Widget _buildThemeDialog() {
     final currentTheme = ThemeManager.currentTheme;
-    
+
     return AlertDialog(
       backgroundColor: ZenColors.currentUiElements,
-      shape: RoundedRectangleBorder(
-        borderRadius: BorderRadius.circular(20),
-      ),
+      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
       title: Text(
         AppLocalizations.of(context)!.selectTheme,
         style: TextStyle(
@@ -803,72 +827,96 @@ class _SettingsScreenState extends State<SettingsScreen> {
       ),
       content: Column(
         mainAxisSize: MainAxisSize.min,
-        children: SeasonalTheme.values.map((theme) {
-          final isSelected = theme == currentTheme;
-          final themeName = _getLocalizedThemeName(theme);
-          final themeIcon = ThemeManager.getThemeIcon(theme);
-          
-          return Container(
-            margin: const EdgeInsets.only(bottom: 8),
-            child: Material(
-              color: isSelected
-                ? ZenColors.currentButtonBackground.withValues(alpha: 0.1)
-                : Colors.transparent,
-              borderRadius: BorderRadius.circular(12),
-              child: InkWell(
-                onTap: () => _selectTheme(theme),
-                borderRadius: BorderRadius.circular(12),
-                child: Container(
-                  padding: const EdgeInsets.all(12),
-                  decoration: BoxDecoration(
-                    borderRadius: BorderRadius.circular(12),
-                    border: Border.all(
-                      color: isSelected
-                        ? ZenColors.currentButtonBackground.withValues(alpha: 0.3)
-                        : ZenColors.currentUiElements.withValues(alpha: 0.2),
-                      width: 1,
-                    ),
-                  ),
-                  child: Row(
-                    children: [
-                      Icon(
-                        themeIcon,
-                        color: isSelected ? ZenColors.currentButtonBackground : ZenColors.currentPrimaryText,
-                        size: 20,
-                      ),
-                      const SizedBox(width: 12),
-                      Expanded(
-                        child: Text(
-                          themeName,
-                          style: TextStyle(
-                            color: isSelected ? ZenColors.currentButtonBackground : ZenColors.currentPrimaryText,
-                            fontSize: 16,
-                            fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
+        children:
+            SeasonalTheme.values
+                .where((theme) {
+                  // Hide default theme in release builds
+                  if (!kDebugMode && theme == SeasonalTheme.default_) {
+                    return false;
+                  }
+                  return true;
+                })
+                .map((theme) {
+                  final isSelected = theme == currentTheme;
+                  final themeName = _getLocalizedThemeName(theme);
+                  final themeIcon = ThemeManager.getThemeIcon(theme);
+
+                  return Container(
+                    margin: const EdgeInsets.only(bottom: 8),
+                    child: Material(
+                      color:
+                          isSelected
+                              ? ZenColors.currentButtonBackground.withValues(
+                                alpha: 0.1,
+                              )
+                              : Colors.transparent,
+                      borderRadius: BorderRadius.circular(12),
+                      child: InkWell(
+                        onTap: () => _selectTheme(theme),
+                        borderRadius: BorderRadius.circular(12),
+                        child: Container(
+                          padding: const EdgeInsets.all(12),
+                          decoration: BoxDecoration(
+                            borderRadius: BorderRadius.circular(12),
+                            border: Border.all(
+                              color:
+                                  isSelected
+                                      ? ZenColors.currentButtonBackground
+                                          .withValues(alpha: 0.3)
+                                      : ZenColors.currentUiElements.withValues(
+                                        alpha: 0.2,
+                                      ),
+                              width: 1,
+                            ),
+                          ),
+                          child: Row(
+                            children: [
+                              Icon(
+                                themeIcon,
+                                color:
+                                    isSelected
+                                        ? ZenColors.currentButtonBackground
+                                        : ZenColors.currentPrimaryText,
+                                size: 20,
+                              ),
+                              const SizedBox(width: 12),
+                              Expanded(
+                                child: Text(
+                                  themeName,
+                                  style: TextStyle(
+                                    color:
+                                        isSelected
+                                            ? ZenColors.currentButtonBackground
+                                            : ZenColors.currentPrimaryText,
+                                    fontSize: 16,
+                                    fontWeight:
+                                        isSelected
+                                            ? FontWeight.w600
+                                            : FontWeight.normal,
+                                  ),
+                                ),
+                              ),
+                              if (isSelected)
+                                Icon(
+                                  Icons.check,
+                                  color: ZenColors.currentButtonBackground,
+                                  size: 20,
+                                ),
+                            ],
                           ),
                         ),
                       ),
-                      if (isSelected)
-                        Icon(
-                          Icons.check,
-                          color: ZenColors.currentButtonBackground,
-                          size: 20,
-                        ),
-                    ],
-                  ),
-                ),
-              ),
-            ),
-          );
-        }).toList(),
+                    ),
+                  );
+                })
+                .toList(),
       ),
       actions: [
         TextButton(
           onPressed: () => Navigator.of(context).pop(),
           child: Text(
             AppLocalizations.of(context)!.cancel,
-            style: TextStyle(
-              color: ZenColors.currentMutedText,
-            ),
+            style: TextStyle(color: ZenColors.currentMutedText),
           ),
         ),
       ],
@@ -878,16 +926,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
   Widget _buildDonationDialog() {
     return AlertDialog(
       backgroundColor: ZenColors.currentUiElements,
-      shape: RoundedRectangleBorder(
-        borderRadius: BorderRadius.circular(20),
-      ),
+      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
       title: Row(
         children: [
-          Icon(
-            Icons.favorite,
-            color: ZenColors.red,
-            size: 24,
-          ),
+          Icon(Icons.favorite, color: ZenColors.red, size: 24),
           const SizedBox(width: 12),
           Text(
             AppLocalizations.of(context)!.supportZenTapTitle,
@@ -912,7 +954,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
             ),
           ),
           const SizedBox(height: 20),
-          
+
           // Ko-fi Button
           _buildDonationButton(
             icon: Icons.coffee,
@@ -921,9 +963,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
             color: const Color(0xFF13C3FF),
             onTap: () => _openDonationLink('https://ko-fi.com/fsociety_hu'),
           ),
-          
+
           const SizedBox(height: 12),
-          
+
           // PayPal Button
           _buildDonationButton(
             icon: Icons.payment,
@@ -932,20 +974,22 @@ class _SettingsScreenState extends State<SettingsScreen> {
             color: const Color(0xFF0070BA),
             onTap: () => _openDonationLink('https://paypal.me/fsocietyhu'),
           ),
-          
+
           const SizedBox(height: 12),
-          
+
           // GitHub Sponsors Button
           _buildDonationButton(
             icon: Icons.code,
             title: AppLocalizations.of(context)!.githubSponsors,
             subtitle: AppLocalizations.of(context)!.monthlySupport,
             color: const Color(0xFFEA4AAA),
-            onTap: () => _openDonationLink('https://github.com/sponsors/fszontagh'),
+            onTap:
+                () =>
+                    _openDonationLink('https://github.com/sponsors/fszontagh'),
           ),
-          
+
           const SizedBox(height: 16),
-          
+
           Text(
             AppLocalizations.of(context)!.thankYouMessage,
             style: TextStyle(
@@ -962,9 +1006,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
           onPressed: () => Navigator.of(context).pop(),
           child: Text(
             AppLocalizations.of(context)!.maybeLater,
-            style: TextStyle(
-              color: ZenColors.currentMutedText,
-            ),
+            style: TextStyle(color: ZenColors.currentMutedText),
           ),
         ),
       ],
@@ -988,18 +1030,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
           padding: const EdgeInsets.all(12),
           decoration: BoxDecoration(
             borderRadius: BorderRadius.circular(12),
-            border: Border.all(
-              color: color.withValues(alpha: 0.3),
-              width: 1,
-            ),
+            border: Border.all(color: color.withValues(alpha: 0.3), width: 1),
           ),
           child: Row(
             children: [
-              Icon(
-                icon,
-                color: color,
-                size: 20,
-              ),
+              Icon(icon, color: color, size: 20),
               const SizedBox(width: 12),
               Expanded(
                 child: Column(
@@ -1023,11 +1058,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
                   ],
                 ),
               ),
-              Icon(
-                Icons.open_in_new,
-                color: color,
-                size: 16,
-              ),
+              Icon(Icons.open_in_new, color: color, size: 16),
             ],
           ),
         ),
@@ -1039,6 +1070,39 @@ class _SettingsScreenState extends State<SettingsScreen> {
   String _getLocalizedThemeName(SeasonalTheme theme) {
     final localizations = AppLocalizations.of(context)!;
     switch (theme) {
+      case SeasonalTheme.automatic:
+        // Get the auto-detected season (not the effective theme)
+        final detectedSeason = ThemeManager.autoDetectedSeason;
+        String seasonName;
+
+        // Get the season name based on auto-detected season
+        switch (detectedSeason) {
+          case SeasonalTheme.spring:
+            seasonName = localizations.springTheme;
+            break;
+          case SeasonalTheme.summer:
+            seasonName = localizations.summerTheme;
+            break;
+          case SeasonalTheme.autumn:
+            seasonName = localizations.autumnTheme;
+            break;
+          case SeasonalTheme.winter:
+            seasonName = localizations.winterTheme;
+            break;
+          default:
+            seasonName = localizations.defaultTheme;
+            break;
+        }
+
+        // Concatenate automatic with detected season
+        switch (Localizations.localeOf(context).languageCode) {
+          case 'de':
+            return 'Automatisch ($seasonName)';
+          case 'hu':
+            return 'Automatikus ($seasonName)';
+          default:
+            return 'Automatic ($seasonName)';
+        }
       case SeasonalTheme.default_:
         return localizations.defaultTheme;
       case SeasonalTheme.spring:
@@ -1056,7 +1120,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
     if (_hapticsEnabled) {
       HapticUtils.vibrate(duration: 30);
     }
-    
+
     await LocaleManager.setLocale(locale);
     if (mounted) {
       Navigator.of(context).pop(); // Close language dialog
@@ -1068,7 +1132,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
     if (_hapticsEnabled) {
       HapticUtils.vibrate(duration: 30);
     }
-    
+
     await SettingsManager.setSelectedTheme(theme);
     if (mounted) {
       Navigator.of(context).pop(); // Close theme dialog
@@ -1080,7 +1144,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
     if (_hapticsEnabled) {
       HapticUtils.vibrate(duration: 30);
     }
-    
+
     try {
       final Uri uri = Uri.parse(url);
       if (await canLaunchUrl(uri)) {
@@ -1091,7 +1155,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
         if (mounted) {
           ScaffoldMessenger.of(context).showSnackBar(
             SnackBar(
-              content: Text(AppLocalizations.of(context)!.couldNotOpenDonationLink),
+              content: Text(
+                AppLocalizations.of(context)!.couldNotOpenDonationLink,
+              ),
               backgroundColor: ZenColors.currentMutedText,
               behavior: SnackBarBehavior.floating,
             ),
@@ -1103,7 +1169,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
       if (mounted) {
         ScaffoldMessenger.of(context).showSnackBar(
           SnackBar(
-            content: Text(AppLocalizations.of(context)!.errorOpeningDonationLink),
+            content: Text(
+              AppLocalizations.of(context)!.errorOpeningDonationLink,
+            ),
             backgroundColor: ZenColors.currentMutedText,
             behavior: SnackBarBehavior.floating,
           ),
@@ -1111,4 +1179,4 @@ class _SettingsScreenState extends State<SettingsScreen> {
       }
     }
   }
-}
+}

+ 415 - 305
lib/ui/stats_screen.dart

@@ -12,7 +12,8 @@ class StatsScreen extends StatefulWidget {
   State<StatsScreen> createState() => _StatsScreenState();
 }
 
-class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin {
+class _StatsScreenState extends State<StatsScreen>
+    with TickerProviderStateMixin {
   late TabController _tabController;
   bool _isLoading = true;
 
@@ -48,10 +49,7 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
             elevation: 0,
             leading: IconButton(
               onPressed: () => Navigator.of(context).pop(true),
-              icon: Icon(
-                Icons.arrow_back,
-                color: ZenColors.currentPrimaryText,
-              ),
+              icon: Icon(Icons.arrow_back, color: ZenColors.currentPrimaryText),
             ),
             title: Text(
               AppLocalizations.of(context)!.statistics,
@@ -73,19 +71,19 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
               ],
             ),
           ),
-          body: _isLoading
-              ? Center(
-                  child: CircularProgressIndicator(
-                    valueColor: AlwaysStoppedAnimation<Color>(ZenColors.currentButtonBackground),
+          body:
+              _isLoading
+                  ? Center(
+                    child: CircularProgressIndicator(
+                      valueColor: AlwaysStoppedAnimation<Color>(
+                        ZenColors.currentButtonBackground,
+                      ),
+                    ),
+                  )
+                  : TabBarView(
+                    controller: _tabController,
+                    children: [_buildOverviewTab(), _buildChartsTab()],
                   ),
-                )
-              : TabBarView(
-                  controller: _tabController,
-                  children: [
-                    _buildOverviewTab(),
-                    _buildChartsTab(),
-                  ],
-                ),
         );
       },
     );
@@ -100,11 +98,11 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
           // Quick Stats Cards
           _buildQuickStatsGrid(),
           const SizedBox(height: 30),
-          
+
           // Achievements Section
           _buildAchievementsSection(),
           const SizedBox(height: 30),
-          
+
           // Recent Activity
           _buildRecentActivitySection(),
         ],
@@ -125,75 +123,83 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
           ),
         ),
         const SizedBox(height: 15),
-        GridView.count(
-          crossAxisCount: 2,
-          shrinkWrap: true,
-          physics: const NeverScrollableScrollPhysics(),
-          childAspectRatio: 1.5,
-          crossAxisSpacing: 15,
-          mainAxisSpacing: 15,
-          children: [
-            _buildStatCard(
-              AppLocalizations.of(context)!.todaysPoints,
-              ScoreManager.todayScore.toString(),
-              Icons.today,
-              ZenColors.currentButtonBackground,
-            ),
-            _buildStatCard(
-              AppLocalizations.of(context)!.totalPoints,
-              ScoreManager.totalScore.toString(),
-              Icons.stars,
-              ZenColors.red,
-            ),
-            _buildStatCard(
-              AppLocalizations.of(context)!.bubblesPopped,
-              ScoreManager.totalBubblesPopped.toString(),
-              Icons.bubble_chart,
-              ZenColors.navyBlue,
-            ),
-            _buildStatCard(
-              AppLocalizations.of(context)!.dailyAverage,
-              ScoreManager.averageDailyScore.toStringAsFixed(0),
-              Icons.trending_up,
-              Colors.green,
-            ),
-            _buildStatCard(
-              AppLocalizations.of(context)!.currentStreak,
-              AppLocalizations.of(context)!.streakDays(ScoreManager.currentStreak),
-              Icons.local_fire_department,
-              Colors.orange,
-            ),
-            _buildStatCard(
-              AppLocalizations.of(context)!.bestDay,
-              ScoreManager.bestDayScore.toString(),
-              Icons.military_tech,
-              Colors.purple,
-            ),
-          ],
+        LayoutBuilder(
+          builder: (context, constraints) {
+            final screenWidth = constraints.maxWidth;
+            final crossAxisCount = screenWidth < 600 ? 2 : 3;
+            final childAspectRatio = screenWidth < 400 ? 1.3 : 1.5;
+
+            return GridView.count(
+              crossAxisCount: crossAxisCount,
+              shrinkWrap: true,
+              physics: const NeverScrollableScrollPhysics(),
+              childAspectRatio: childAspectRatio,
+              crossAxisSpacing: 15,
+              mainAxisSpacing: 15,
+              children: [
+                _buildStatCard(
+                  AppLocalizations.of(context)!.todaysPoints,
+                  ScoreManager.todayScore.toString(),
+                  Icons.today,
+                  ZenColors.currentButtonBackground,
+                ),
+                _buildStatCard(
+                  AppLocalizations.of(context)!.totalPoints,
+                  ScoreManager.totalScore.toString(),
+                  Icons.stars,
+                  ZenColors.red,
+                ),
+                _buildStatCard(
+                  AppLocalizations.of(context)!.bubblesPopped,
+                  ScoreManager.totalBubblesPopped.toString(),
+                  Icons.bubble_chart,
+                  ZenColors.navyBlue,
+                ),
+                _buildStatCard(
+                  AppLocalizations.of(context)!.dailyAverage,
+                  ScoreManager.averageDailyScore.toStringAsFixed(0),
+                  Icons.trending_up,
+                  Colors.green,
+                ),
+                _buildStatCard(
+                  AppLocalizations.of(context)!.currentStreak,
+                  AppLocalizations.of(
+                    context,
+                  )!.streakDays(ScoreManager.currentStreak),
+                  Icons.local_fire_department,
+                  Colors.orange,
+                ),
+                _buildStatCard(
+                  AppLocalizations.of(context)!.bestDay,
+                  ScoreManager.bestDayScore.toString(),
+                  Icons.military_tech,
+                  Colors.purple,
+                ),
+              ],
+            );
+          },
         ),
       ],
     );
   }
 
-  Widget _buildStatCard(String title, String value, IconData icon, Color color) {
+  Widget _buildStatCard(
+    String title,
+    String value,
+    IconData icon,
+    Color color,
+  ) {
     return Container(
       padding: const EdgeInsets.all(16),
       decoration: BoxDecoration(
         color: ZenColors.currentUiElements.withValues(alpha: 0.3),
         borderRadius: BorderRadius.circular(15),
-        border: Border.all(
-          color: color.withValues(alpha: 0.3),
-          width: 1,
-        ),
+        border: Border.all(color: color.withValues(alpha: 0.3), width: 1),
       ),
       child: Column(
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
-          Icon(
-            icon,
-            color: color,
-            size: 28,
-          ),
+          Icon(icon, color: color, size: 28),
           const SizedBox(height: 8),
           Text(
             value,
@@ -219,7 +225,7 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
 
   Widget _buildAchievementsSection() {
     final achievements = _getAchievements();
-    
+
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
@@ -259,14 +265,16 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
     return Container(
       padding: const EdgeInsets.all(12),
       decoration: BoxDecoration(
-        color: unlocked
-            ? ZenColors.currentButtonBackground.withValues(alpha: 0.2)
-            : ZenColors.currentUiElements.withValues(alpha: 0.1),
+        color:
+            unlocked
+                ? ZenColors.currentButtonBackground.withValues(alpha: 0.2)
+                : ZenColors.currentUiElements.withValues(alpha: 0.1),
         borderRadius: BorderRadius.circular(12),
         border: Border.all(
-          color: unlocked
-              ? ZenColors.currentButtonBackground.withValues(alpha: 0.5)
-              : ZenColors.currentUiElements.withValues(alpha: 0.3),
+          color:
+              unlocked
+                  ? ZenColors.currentButtonBackground.withValues(alpha: 0.5)
+                  : ZenColors.currentUiElements.withValues(alpha: 0.3),
           width: 1,
         ),
       ),
@@ -275,14 +283,20 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
         children: [
           Icon(
             icon,
-            color: unlocked ? ZenColors.currentButtonBackground : ZenColors.currentMutedText,
+            color:
+                unlocked
+                    ? ZenColors.currentButtonBackground
+                    : ZenColors.currentMutedText,
             size: 32,
           ),
           const SizedBox(height: 8),
           Text(
             title,
             style: TextStyle(
-              color: unlocked ? ZenColors.currentPrimaryText : ZenColors.currentMutedText,
+              color:
+                  unlocked
+                      ? ZenColors.currentPrimaryText
+                      : ZenColors.currentMutedText,
               fontSize: 11,
               fontWeight: unlocked ? FontWeight.w600 : FontWeight.normal,
             ),
@@ -337,7 +351,7 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
 
   Widget _buildRecentActivitySection() {
     final lastDays = ScoreManager.getLastDaysScores(7);
-    
+
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
@@ -357,55 +371,90 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
             borderRadius: BorderRadius.circular(15),
           ),
           child: Column(
-            children: lastDays.map((entry) {
-              final date = DateTime.parse(entry.key);
-              final dayName = _getDayName(date.weekday);
-              final isToday = _isToday(date);
-              
-              return Padding(
-                padding: const EdgeInsets.symmetric(vertical: 4),
-                child: Row(
-                  children: [
-                    SizedBox(
-                      width: 60,
-                      child: Text(
-                        dayName,
-                        style: TextStyle(
-                          color: isToday ? ZenColors.currentButtonBackground : ZenColors.currentSecondaryText,
-                          fontSize: 14,
-                          fontWeight: isToday ? FontWeight.bold : FontWeight.normal,
-                        ),
-                      ),
-                    ),
-                    Expanded(
-                      child: Container(
-                        height: 8,
-                        margin: const EdgeInsets.symmetric(horizontal: 10),
-                        child: LinearProgressIndicator(
-                          value: entry.value / (ScoreManager.bestDayScore.clamp(1, double.infinity)),
-                          backgroundColor: ZenColors.currentUiElements.withValues(alpha: 0.3),
-                          valueColor: AlwaysStoppedAnimation<Color>(
-                            isToday ? ZenColors.currentButtonBackground : ZenColors.currentUiElements,
-                          ),
-                        ),
-                      ),
-                    ),
-                    SizedBox(
-                      width: 50,
-                      child: Text(
-                        entry.value.toString(),
-                        style: TextStyle(
-                          color: isToday ? ZenColors.currentButtonBackground : ZenColors.currentPrimaryText,
-                          fontSize: 14,
-                          fontWeight: isToday ? FontWeight.bold : FontWeight.normal,
-                        ),
-                        textAlign: TextAlign.right,
-                      ),
+            children:
+                lastDays.map((entry) {
+                  final date = DateTime.parse(entry.key);
+                  final dayName = _getDayName(date.weekday);
+                  final isToday = _isToday(date);
+
+                  return Padding(
+                    padding: const EdgeInsets.symmetric(vertical: 4),
+                    child: LayoutBuilder(
+                      builder: (context, constraints) {
+                        final screenWidth = constraints.maxWidth;
+                        final dayWidth =
+                            screenWidth * 0.2; // 20% of available width
+                        final scoreWidth =
+                            screenWidth * 0.15; // 15% of available width
+
+                        return Row(
+                          children: [
+                            SizedBox(
+                              width: dayWidth,
+                              child: Text(
+                                dayName,
+                                style: TextStyle(
+                                  color:
+                                      isToday
+                                          ? ZenColors.currentButtonBackground
+                                          : ZenColors.currentSecondaryText,
+                                  fontSize: 14,
+                                  fontWeight:
+                                      isToday
+                                          ? FontWeight.bold
+                                          : FontWeight.normal,
+                                ),
+                                overflow: TextOverflow.ellipsis,
+                              ),
+                            ),
+                            Expanded(
+                              child: Container(
+                                height: 8,
+                                margin: const EdgeInsets.symmetric(
+                                  horizontal: 10,
+                                ),
+                                child: LinearProgressIndicator(
+                                  value:
+                                      entry.value /
+                                      (ScoreManager.bestDayScore.clamp(
+                                        1,
+                                        double.infinity,
+                                      )),
+                                  backgroundColor: ZenColors.currentUiElements
+                                      .withValues(alpha: 0.3),
+                                  valueColor: AlwaysStoppedAnimation<Color>(
+                                    isToday
+                                        ? ZenColors.currentButtonBackground
+                                        : ZenColors.currentUiElements,
+                                  ),
+                                ),
+                              ),
+                            ),
+                            SizedBox(
+                              width: scoreWidth,
+                              child: Text(
+                                entry.value.toString(),
+                                style: TextStyle(
+                                  color:
+                                      isToday
+                                          ? ZenColors.currentButtonBackground
+                                          : ZenColors.currentPrimaryText,
+                                  fontSize: 14,
+                                  fontWeight:
+                                      isToday
+                                          ? FontWeight.bold
+                                          : FontWeight.normal,
+                                ),
+                                textAlign: TextAlign.right,
+                                overflow: TextOverflow.ellipsis,
+                              ),
+                            ),
+                          ],
+                        );
+                      },
                     ),
-                  ],
-                ),
-              );
-            }).toList(),
+                  );
+                }).toList(),
           ),
         ),
       ],
@@ -428,7 +477,9 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
 
   Widget _buildDailyChart() {
     final lastDays = ScoreManager.getLastDaysScores(14);
-    final maxScore = lastDays.fold(0, (max, entry) => entry.value > max ? entry.value : max).clamp(1, double.infinity);
+    final maxScore = lastDays
+        .fold(0, (max, entry) => entry.value > max ? entry.value : max)
+        .clamp(1, double.infinity);
 
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
@@ -442,107 +493,138 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
           ),
         ),
         const SizedBox(height: 15),
-        Container(
-          height: 300,
-          padding: const EdgeInsets.all(16),
-          decoration: BoxDecoration(
-            color: ZenColors.currentUiElements.withValues(alpha: 0.2),
-            borderRadius: BorderRadius.circular(15),
-          ),
-          child: LineChart(
-            LineChartData(
-              backgroundColor: Colors.transparent,
-              gridData: FlGridData(
-                show: true,
-                drawVerticalLine: false,
-                getDrawingHorizontalLine: (value) {
-                  return FlLine(
-                    color: ZenColors.currentUiElements.withValues(alpha: 0.3),
-                    strokeWidth: 1,
-                  );
-                },
-              ),
-              titlesData: FlTitlesData(
-                bottomTitles: AxisTitles(
-                  sideTitles: SideTitles(
-                    showTitles: true,
-                    getTitlesWidget: (value, meta) {
-                      if (value.toInt() >= 0 && value.toInt() < lastDays.length) {
-                        final date = DateTime.parse(lastDays[value.toInt()].key);
-                        return Text(
-                          '${date.day}',
-                          style: TextStyle(
-                            color: ZenColors.currentSecondaryText,
-                            fontSize: 12,
-                          ),
-                        );
-                      }
-                      return const Text('');
-                    },
-                  ),
-                ),
-                leftTitles: AxisTitles(
-                  sideTitles: SideTitles(
-                    showTitles: true,
-                    reservedSize: 40,
-                    getTitlesWidget: (value, meta) {
-                      return Text(
-                        value.toInt().toString(),
-                        style: TextStyle(
-                          color: ZenColors.currentSecondaryText,
-                          fontSize: 12,
-                        ),
-                      );
-                    },
-                  ),
-                ),
-                topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
-                rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
+        LayoutBuilder(
+          builder: (context, constraints) {
+            final chartHeight = constraints.maxWidth < 400 ? 250.0 : 300.0;
+            final labelFontSize = constraints.maxWidth < 400 ? 10.0 : 12.0;
+
+            return Container(
+              height: chartHeight,
+              padding: const EdgeInsets.all(16),
+              decoration: BoxDecoration(
+                color: ZenColors.currentUiElements.withValues(alpha: 0.2),
+                borderRadius: BorderRadius.circular(15),
               ),
-              borderData: FlBorderData(show: false),
-              minX: 0,
-              maxX: (lastDays.length - 1).toDouble(),
-              minY: 0,
-              maxY: maxScore.toDouble(),
-              lineBarsData: [
-                LineChartBarData(
-                  spots: lastDays.asMap().entries.map((entry) {
-                    return FlSpot(entry.key.toDouble(), entry.value.value.toDouble());
-                  }).toList(),
-                  isCurved: true,
-                  gradient: LinearGradient(
-                    colors: [
-                      ZenColors.currentButtonBackground.withValues(alpha: 0.8),
-                      ZenColors.currentButtonBackground,
-                    ],
-                  ),
-                  barWidth: 3,
-                  dotData: FlDotData(
+              child: LineChart(
+                LineChartData(
+                  backgroundColor: Colors.transparent,
+                  gridData: FlGridData(
                     show: true,
-                    getDotPainter: (spot, percent, barData, index) {
-                      return FlDotCirclePainter(
-                        radius: 4,
-                        color: ZenColors.currentButtonBackground,
-                        strokeWidth: 2,
-                        strokeColor: ZenColors.currentPrimaryText,
+                    drawVerticalLine: false,
+                    getDrawingHorizontalLine: (value) {
+                      return FlLine(
+                        color: ZenColors.currentUiElements.withValues(
+                          alpha: 0.3,
+                        ),
+                        strokeWidth: 1,
                       );
                     },
                   ),
-                  belowBarData: BarAreaData(
-                    show: true,
-                    gradient: LinearGradient(
-                      begin: Alignment.topCenter,
-                      end: Alignment.bottomCenter,
-                      colors: [
-                        ZenColors.currentButtonBackground.withValues(alpha: 0.3),
-                        ZenColors.currentButtonBackground.withValues(alpha: 0.1),
-                      ],
+                  titlesData: FlTitlesData(
+                    bottomTitles: AxisTitles(
+                      sideTitles: SideTitles(
+                        showTitles: true,
+                        reservedSize: 30,
+                        interval: lastDays.length > 10 ? 2 : 1,
+                        getTitlesWidget: (value, meta) {
+                          if (value.toInt() >= 0 &&
+                              value.toInt() < lastDays.length) {
+                            final date = DateTime.parse(
+                              lastDays[value.toInt()].key,
+                            );
+                            return Transform.rotate(
+                              angle: constraints.maxWidth < 400 ? -0.5 : 0,
+                              child: Text(
+                                '${date.day}',
+                                style: TextStyle(
+                                  color: ZenColors.currentSecondaryText,
+                                  fontSize: labelFontSize,
+                                ),
+                              ),
+                            );
+                          }
+                          return const Text('');
+                        },
+                      ),
+                    ),
+                    leftTitles: AxisTitles(
+                      sideTitles: SideTitles(
+                        showTitles: true,
+                        reservedSize: 40,
+                        getTitlesWidget: (value, meta) {
+                          return Text(
+                            value.toInt().toString(),
+                            style: TextStyle(
+                              color: ZenColors.currentSecondaryText,
+                              fontSize: labelFontSize,
+                            ),
+                          );
+                        },
+                      ),
+                    ),
+                    topTitles: const AxisTitles(
+                      sideTitles: SideTitles(showTitles: false),
+                    ),
+                    rightTitles: const AxisTitles(
+                      sideTitles: SideTitles(showTitles: false),
                     ),
                   ),
+                  borderData: FlBorderData(show: false),
+                  minX: 0,
+                  maxX: (lastDays.length - 1).toDouble(),
+                  minY: 0,
+                  maxY: maxScore.toDouble(),
+                  lineBarsData: [
+                    LineChartBarData(
+                      spots:
+                          lastDays.asMap().entries.map((entry) {
+                            return FlSpot(
+                              entry.key.toDouble(),
+                              entry.value.value.toDouble(),
+                            );
+                          }).toList(),
+                      isCurved: true,
+                      gradient: LinearGradient(
+                        colors: [
+                          ZenColors.currentButtonBackground.withValues(
+                            alpha: 0.8,
+                          ),
+                          ZenColors.currentButtonBackground,
+                        ],
+                      ),
+                      barWidth: 3,
+                      dotData: FlDotData(
+                        show: true,
+                        getDotPainter: (spot, percent, barData, index) {
+                          return FlDotCirclePainter(
+                            radius: 4,
+                            color: ZenColors.currentButtonBackground,
+                            strokeWidth: 2,
+                            strokeColor: ZenColors.currentPrimaryText,
+                          );
+                        },
+                      ),
+                      belowBarData: BarAreaData(
+                        show: true,
+                        gradient: LinearGradient(
+                          begin: Alignment.topCenter,
+                          end: Alignment.bottomCenter,
+                          colors: [
+                            ZenColors.currentButtonBackground.withValues(
+                              alpha: 0.3,
+                            ),
+                            ZenColors.currentButtonBackground.withValues(
+                              alpha: 0.1,
+                            ),
+                          ],
+                        ),
+                      ),
+                    ),
+                  ],
                 ),
-              ],
-            ),
-          ),
+              ),
+            );
+          },
         ),
       ],
     );
@@ -550,7 +632,9 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
 
   Widget _buildWeeklyChart() {
     final weeklyScores = ScoreManager.getWeeklyScores(8);
-    final maxScore = weeklyScores.fold(0, (max, entry) => entry.value > max ? entry.value : max).clamp(1, double.infinity);
+    final maxScore = weeklyScores
+        .fold(0, (max, entry) => entry.value > max ? entry.value : max)
+        .clamp(1, double.infinity);
 
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
@@ -564,89 +648,105 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
           ),
         ),
         const SizedBox(height: 15),
-        Container(
-          height: 300,
-          padding: const EdgeInsets.all(16),
-          decoration: BoxDecoration(
-            color: ZenColors.currentUiElements.withValues(alpha: 0.2),
-            borderRadius: BorderRadius.circular(15),
-          ),
-          child: BarChart(
-            BarChartData(
-              backgroundColor: Colors.transparent,
-              gridData: FlGridData(
-                show: true,
-                drawVerticalLine: false,
-                getDrawingHorizontalLine: (value) {
-                  return FlLine(
-                    color: ZenColors.currentUiElements.withValues(alpha: 0.3),
-                    strokeWidth: 1,
-                  );
-                },
+        LayoutBuilder(
+          builder: (context, constraints) {
+            final chartHeight = constraints.maxWidth < 400 ? 250.0 : 300.0;
+            final labelFontSize = constraints.maxWidth < 400 ? 10.0 : 12.0;
+
+            return Container(
+              height: chartHeight,
+              padding: const EdgeInsets.all(16),
+              decoration: BoxDecoration(
+                color: ZenColors.currentUiElements.withValues(alpha: 0.2),
+                borderRadius: BorderRadius.circular(15),
               ),
-              titlesData: FlTitlesData(
-                bottomTitles: AxisTitles(
-                  sideTitles: SideTitles(
-                    showTitles: true,
-                    getTitlesWidget: (value, meta) {
-                      if (value.toInt() >= 0 && value.toInt() < weeklyScores.length) {
-                        return Text(
-                          'W${value.toInt() + 1}',
-                          style: TextStyle(
-                            color: ZenColors.currentSecondaryText,
-                            fontSize: 12,
-                          ),
-                        );
-                      }
-                      return const Text('');
-                    },
-                  ),
-                ),
-                leftTitles: AxisTitles(
-                  sideTitles: SideTitles(
-                    showTitles: true,
-                    reservedSize: 40,
-                    getTitlesWidget: (value, meta) {
-                      return Text(
-                        value.toInt().toString(),
-                        style: TextStyle(
-                          color: ZenColors.currentSecondaryText,
-                          fontSize: 12,
+              child: BarChart(
+                BarChartData(
+                  backgroundColor: Colors.transparent,
+                  gridData: FlGridData(
+                    show: true,
+                    drawVerticalLine: false,
+                    getDrawingHorizontalLine: (value) {
+                      return FlLine(
+                        color: ZenColors.currentUiElements.withValues(
+                          alpha: 0.3,
                         ),
+                        strokeWidth: 1,
                       );
                     },
                   ),
-                ),
-                topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
-                rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
-              ),
-              borderData: FlBorderData(show: false),
-              maxY: maxScore.toDouble(),
-              barGroups: weeklyScores.asMap().entries.map((entry) {
-                return BarChartGroupData(
-                  x: entry.key,
-                  barRods: [
-                    BarChartRodData(
-                      toY: entry.value.value.toDouble(),
-                      gradient: LinearGradient(
-                        begin: Alignment.bottomCenter,
-                        end: Alignment.topCenter,
-                        colors: [
-                          ZenColors.navyBlue.withValues(alpha: 0.8),
-                          ZenColors.navyBlue,
-                        ],
+                  titlesData: FlTitlesData(
+                    bottomTitles: AxisTitles(
+                      sideTitles: SideTitles(
+                        showTitles: true,
+                        reservedSize: 30,
+                        getTitlesWidget: (value, meta) {
+                          if (value.toInt() >= 0 &&
+                              value.toInt() < weeklyScores.length) {
+                            return Text(
+                              'W${value.toInt() + 1}',
+                              style: TextStyle(
+                                color: ZenColors.currentSecondaryText,
+                                fontSize: labelFontSize,
+                              ),
+                            );
+                          }
+                          return const Text('');
+                        },
                       ),
-                      width: 20,
-                      borderRadius: const BorderRadius.only(
-                        topLeft: Radius.circular(6),
-                        topRight: Radius.circular(6),
+                    ),
+                    leftTitles: AxisTitles(
+                      sideTitles: SideTitles(
+                        showTitles: true,
+                        reservedSize: 40,
+                        getTitlesWidget: (value, meta) {
+                          return Text(
+                            value.toInt().toString(),
+                            style: TextStyle(
+                              color: ZenColors.currentSecondaryText,
+                              fontSize: labelFontSize,
+                            ),
+                          );
+                        },
                       ),
                     ),
-                  ],
-                );
-              }).toList(),
-            ),
-          ),
+                    topTitles: const AxisTitles(
+                      sideTitles: SideTitles(showTitles: false),
+                    ),
+                    rightTitles: const AxisTitles(
+                      sideTitles: SideTitles(showTitles: false),
+                    ),
+                  ),
+                  borderData: FlBorderData(show: false),
+                  maxY: maxScore.toDouble(),
+                  barGroups:
+                      weeklyScores.asMap().entries.map((entry) {
+                        return BarChartGroupData(
+                          x: entry.key,
+                          barRods: [
+                            BarChartRodData(
+                              toY: entry.value.value.toDouble(),
+                              gradient: LinearGradient(
+                                begin: Alignment.bottomCenter,
+                                end: Alignment.topCenter,
+                                colors: [
+                                  ZenColors.navyBlue.withValues(alpha: 0.8),
+                                  ZenColors.navyBlue,
+                                ],
+                              ),
+                              width: 20,
+                              borderRadius: const BorderRadius.only(
+                                topLeft: Radius.circular(6),
+                                topRight: Radius.circular(6),
+                              ),
+                            ),
+                          ],
+                        );
+                      }).toList(),
+                ),
+              ),
+            );
+          },
         ),
       ],
     );
@@ -654,12 +754,22 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
 
   String _getDayName(int weekday) {
     final l10n = AppLocalizations.of(context)!;
-    final days = [l10n.monday, l10n.tuesday, l10n.wednesday, l10n.thursday, l10n.friday, l10n.saturday, l10n.sunday];
+    final days = [
+      l10n.monday,
+      l10n.tuesday,
+      l10n.wednesday,
+      l10n.thursday,
+      l10n.friday,
+      l10n.saturday,
+      l10n.sunday,
+    ];
     return days[weekday - 1];
   }
 
   bool _isToday(DateTime date) {
     final now = DateTime.now();
-    return date.year == now.year && date.month == now.month && date.day == now.day;
+    return date.year == now.year &&
+        date.month == now.month &&
+        date.day == now.day;
   }
-}
+}

+ 68 - 10
lib/utils/theme_manager.dart

@@ -4,6 +4,7 @@ import 'theme_notifier.dart';
 
 /// Seasonal theme system for ZenTap
 enum SeasonalTheme {
+  automatic('automatic'),
   default_('default'),
   spring('spring'),
   summer('summer'),
@@ -16,7 +17,7 @@ enum SeasonalTheme {
   static SeasonalTheme fromId(String id) {
     return SeasonalTheme.values.firstWhere(
       (theme) => theme.id == id,
-      orElse: () => SeasonalTheme.default_,
+      orElse: () => SeasonalTheme.automatic,
     );
   }
 }
@@ -39,7 +40,7 @@ class SeasonalColors {
   final Color links;
   final Color linkHover;
   final Color selectedMenuItem;
-  
+
   // Animation colors
   final List<Color> animationLayer1;
   final List<Color> animationLayer2;
@@ -73,11 +74,11 @@ class SeasonalColors {
 class ThemeManager {
   static const String _keySelectedTheme = 'selected_theme';
   static SharedPreferences? _prefs;
-  static SeasonalTheme _currentTheme = SeasonalTheme.default_;
+  static SeasonalTheme _currentTheme = SeasonalTheme.automatic;
 
   static Future<void> init() async {
     _prefs = await SharedPreferences.getInstance();
-    final themeId = _prefs?.getString(_keySelectedTheme) ?? 'default';
+    final themeId = _prefs?.getString(_keySelectedTheme) ?? 'automatic';
     _currentTheme = SeasonalTheme.fromId(themeId);
     ThemeNotifier().initFromManager();
   }
@@ -90,9 +91,50 @@ class ThemeManager {
     ThemeNotifier().setTheme(theme);
   }
 
+  /// Detect current season based on date
+  static SeasonalTheme _detectCurrentSeason() {
+    final now = DateTime.now();
+    final month = now.month;
+    final day = now.day;
+
+    // Northern hemisphere seasons
+    if ((month == 3 && day >= 20) ||
+        month == 4 ||
+        month == 5 ||
+        (month == 6 && day < 21)) {
+      return SeasonalTheme.spring; // March 20 - June 20
+    } else if ((month == 6 && day >= 21) ||
+        month == 7 ||
+        month == 8 ||
+        (month == 9 && day < 22)) {
+      return SeasonalTheme.summer; // June 21 - September 21
+    } else if ((month == 9 && day >= 22) ||
+        month == 10 ||
+        month == 11 ||
+        (month == 12 && day < 21)) {
+      return SeasonalTheme.autumn; // September 22 - December 20
+    } else {
+      return SeasonalTheme.winter; // December 21 - March 19
+    }
+  }
+
+  /// Get the effective theme (resolves automatic to actual season)
+  static SeasonalTheme get effectiveTheme {
+    if (_currentTheme == SeasonalTheme.automatic) {
+      return _detectCurrentSeason();
+    }
+    return _currentTheme;
+  }
+
+  /// Get the auto-detected season (regardless of current theme setting)
+  static SeasonalTheme get autoDetectedSeason {
+    return _detectCurrentSeason();
+  }
+
   /// Get the current theme's color scheme
   static SeasonalColors get colors {
-    switch (_currentTheme) {
+    final theme = effectiveTheme;
+    switch (theme) {
       case SeasonalTheme.spring:
         return _springColors;
       case SeasonalTheme.summer:
@@ -103,12 +145,23 @@ class ThemeManager {
         return _winterColors;
       case SeasonalTheme.default_:
         return _defaultColors;
+      case SeasonalTheme.automatic:
+        // This should never happen since effectiveTheme resolves it
+        return _detectCurrentSeason() == SeasonalTheme.spring
+            ? _springColors
+            : _detectCurrentSeason() == SeasonalTheme.summer
+            ? _summerColors
+            : _detectCurrentSeason() == SeasonalTheme.autumn
+            ? _autumnColors
+            : _winterColors;
     }
   }
 
   /// Get theme display name
   static String getThemeDisplayName(SeasonalTheme theme) {
     switch (theme) {
+      case SeasonalTheme.automatic:
+        return 'Automatic';
       case SeasonalTheme.default_:
         return 'Default (fSociety)';
       case SeasonalTheme.spring:
@@ -125,6 +178,8 @@ class ThemeManager {
   /// Get theme icon
   static IconData getThemeIcon(SeasonalTheme theme) {
     switch (theme) {
+      case SeasonalTheme.automatic:
+        return Icons.auto_mode;
       case SeasonalTheme.default_:
         return Icons.contrast;
       case SeasonalTheme.spring:
@@ -204,9 +259,9 @@ class ThemeManager {
     links: Color(0xFF1E90FF), // Dodger blue
     linkHover: Color(0xFFFF4500), // Orange red
     selectedMenuItem: Color(0xFFF0F8FF),
-    animationLayer1: [Color(0x3300BFFF), Colors.transparent], // Deep sky blue
-    animationLayer2: [Color(0x1AFFD700), Colors.transparent], // Gold
-    animationLayer3: [Color(0x0D87CEEB), Colors.transparent], // Sky blue
+    animationLayer1: [Color(0x33FFD700), Colors.transparent], // Gold
+    animationLayer2: [Color(0x1AFFFF00), Colors.transparent], // Yellow
+    animationLayer3: [Color(0x0DFFA500), Colors.transparent], // Orange
     zenModeColors: [Color(0x4D1E90FF), Colors.transparent], // Dodger blue
   );
 
@@ -254,7 +309,10 @@ class ThemeManager {
     selectedMenuItem: Color(0xFFF0F8FF),
     animationLayer1: [Color(0x3387CEFA), Colors.transparent], // Light sky blue
     animationLayer2: [Color(0x1AFFFFFF), Colors.transparent], // White
-    animationLayer3: [Color(0x0DB0C4DE), Colors.transparent], // Light steel blue
+    animationLayer3: [
+      Color(0x0DB0C4DE),
+      Colors.transparent,
+    ], // Light steel blue
     zenModeColors: [Color(0x4D4169E1), Colors.transparent], // Royal blue
   );
-}
+}

+ 1 - 1
linux/CMakeLists.txt

@@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
 set(BINARY_NAME "zentap")
 # The unique GTK application identifier for this application. See:
 # https://wiki.gnome.org/HowDoI/ChooseApplicationID
-set(APPLICATION_ID "com.fsociety.zentap.zentap")
+set(APPLICATION_ID "hu.fsociety.zentap")
 
 # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
 # versions of CMake.

+ 3 - 3
macos/Runner.xcodeproj/project.pbxproj

@@ -385,7 +385,7 @@
 				CURRENT_PROJECT_VERSION = 1;
 				GENERATE_INFOPLIST_FILE = YES;
 				MARKETING_VERSION = 1.0;
-				PRODUCT_BUNDLE_IDENTIFIER = com.fsociety.zentap.zentap.RunnerTests;
+				PRODUCT_BUNDLE_IDENTIFIER = hu.fsociety.zentap.RunnerTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_VERSION = 5.0;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/zentap.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/zentap";
@@ -399,7 +399,7 @@
 				CURRENT_PROJECT_VERSION = 1;
 				GENERATE_INFOPLIST_FILE = YES;
 				MARKETING_VERSION = 1.0;
-				PRODUCT_BUNDLE_IDENTIFIER = com.fsociety.zentap.zentap.RunnerTests;
+				PRODUCT_BUNDLE_IDENTIFIER = hu.fsociety.zentap.RunnerTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_VERSION = 5.0;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/zentap.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/zentap";
@@ -413,7 +413,7 @@
 				CURRENT_PROJECT_VERSION = 1;
 				GENERATE_INFOPLIST_FILE = YES;
 				MARKETING_VERSION = 1.0;
-				PRODUCT_BUNDLE_IDENTIFIER = com.fsociety.zentap.zentap.RunnerTests;
+				PRODUCT_BUNDLE_IDENTIFIER = hu.fsociety.zentap.RunnerTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_VERSION = 5.0;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/zentap.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/zentap";

+ 2 - 2
macos/Runner/Configs/AppInfo.xcconfig

@@ -8,7 +8,7 @@
 PRODUCT_NAME = zentap
 
 // The application's bundle identifier
-PRODUCT_BUNDLE_IDENTIFIER = com.fsociety.zentap.zentap
+PRODUCT_BUNDLE_IDENTIFIER = hu.fsociety.zentap
 
 // The copyright displayed in application information
-PRODUCT_COPYRIGHT = Copyright © 2025 com.fsociety.zentap. All rights reserved.
+PRODUCT_COPYRIGHT = Copyright © 2025 hu.fsociety.zentap. All rights reserved.

+ 1 - 1
scripts/build_app.sh

@@ -18,7 +18,7 @@ BUILD_DIR="build/outputs"
 
 # App information
 APP_NAME="ZenTap"
-PACKAGE_NAME="com.fsociety.zentap"
+PACKAGE_NAME="hu.fsociety.zentap"
 
 echo -e "${BLUE}$APP_NAME Build Script${NC}"
 echo "======================="

+ 2 - 2
windows/runner/Runner.rc

@@ -89,11 +89,11 @@ BEGIN
     BEGIN
         BLOCK "040904e4"
         BEGIN
-            VALUE "CompanyName", "com.fsociety.zentap" "\0"
+            VALUE "CompanyName", "hu.fsociety.zentap" "\0"
             VALUE "FileDescription", "zentap" "\0"
             VALUE "FileVersion", VERSION_AS_STRING "\0"
             VALUE "InternalName", "zentap" "\0"
-            VALUE "LegalCopyright", "Copyright (C) 2025 com.fsociety.zentap. All rights reserved." "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2025 hu.fsociety.zentap. All rights reserved." "\0"
             VALUE "OriginalFilename", "zentap.exe" "\0"
             VALUE "ProductName", "zentap" "\0"
             VALUE "ProductVersion", VERSION_AS_STRING "\0"