Skip to content

commit 357b925

abduznik edited this page May 23, 2026 · 1 revision

feat: Customizable Library Display System (#4)

Commit: 357b92581e05eb6a843187fb711a78910909828d

Author: abduznik

Date: 2026-03-25

Message

  • WIP: Ui improvements

  • feat: customizable library display system

  • Added preset system (Windows, Steam Deck, Cozy, Compact, Custom)
  • Added column count slider (2-8 columns per row)
  • Added card shape modes (Square 1.0, Portrait 0.72, Tall 0.58)
  • Added card spacing options (Tight, Normal, Airy)
  • Added show title toggle
  • Added hover-only buttons toggle
  • Fixed card height calculation using mainAxisExtent instead of childAspectRatio
  • Fixed aspect ratio not affecting card height due to ref.read vs ref.watch
  • Added shimmer skeleton loading grid instead of plain spinner
  • Added pull to refresh on game grid
  • Added long press context menu with download, launch and sync saves
  • Clean game display names stripping ROM filename junk and region codes
  • Fixed blank gap between filter bar and game grid
  • All display settings persist via SharedPreferences and load on startup
  • Settings screen correctly reflects saved values on reopen
  • Removed all debugPrint statements from production code
  • fix: resolve linting issues and satisfy curly_braces_in_flow_control_structures

  • fix: performance and stability improvements

  • Fixed accessibility tree flooding by adding ExcludeSemantics at app root level in app.dart
  • Fixed shimmer skeleton causing 60fps full grid rebuilds by isolating animation per card with _SkeletonCard widget
  • Fixed settings screen FutureBuilder per emulator replaced with single cached load in initState
  • Fixed 627 FutureBuilders per game card replaced with single cached Map loaded once in _LibraryScreenState
  • Added game library cache with 7 day expiry and 10MB size limit
  • Cache skips gracefully for libraries exceeding 10MB size limit
  • Pull to refresh invalidates cache and forces fresh fetch
  • Background refresh silently updates cache after serving cached data on launch
  • Removed all debugPrint statements from production code
  • perf: improve game grid scroll performance
  • Added RepaintBoundary to GameCard to cache rendered cards
  • Added cacheExtent 500 to GridView for pre-rendering
  • Added BouncingScrollPhysics for smoother scroll feel
  • Fixed _loadDownloadStates to run parallel with Future.wait instead of 627 sequential file checks blocking main thread
  • test: Update GameCard test to reflect architectural change

Why: Adds a new feature or capability to the application.

Files Changed

.gitignore                                         |   1 +
 lib/app.dart                                       |  35 +-
 lib/core/downloader/download_service.dart          |  15 -
 lib/core/emulator/github_release_service.dart      |   5 -
 lib/core/emulator/strategies/windows_strategy.dart |   6 -
 lib/core/romm/romm_models.dart                     |  21 ++
 lib/core/romm/romm_service.dart                    |  25 --
 lib/core/save/save_strategy.dart                   |   3 +-
 lib/core/save/save_sync_service.dart               |  13 -
 .../save/strategies/dolphin_save_strategy.dart     |   3 -
 lib/core/save/strategies/eden_save_strategy.dart   |  14 +-
 lib/core/save/strategies/pcsx2_save_strategy.dart  |   3 -
 .../save/strategies/retroarch_save_strategy.dart   |   3 -
 lib/core/save/strategies/rpcs3_save_strategy.dart  |   8 -
 .../save/strategies/windows_save_strategy.dart     |   7 +-
 lib/core/save/strategies/xenia_save_strategy.dart  |   4 -
 lib/core/storage/directory_service.dart            |   3 -
 lib/core/windows/pcgamingwiki_service.dart         |   8 +-
 lib/core/windows/windows_game_service.dart         |   3 -
 lib/main.dart                                      |   1 +
 lib/providers/library_provider.dart                | 258 +++++++++++++-
 lib/providers/romm_provider.dart                   |   2 -
 lib/ui/screens/download_screen.dart                |  34 +-
 lib/ui/screens/library_screen.dart                 | 334 ++++++++++++++----
 lib/ui/screens/settings_screen.dart                | 383 +++++++++++++++------
 lib/ui/widgets/game_card.dart                      | 231 ++++++++-----
 lib/ui/widgets/platform_filter_bar.dart            |   2 +-
 test/game_card_test.dart                           |  30 ++
 28 files changed, 1050 insertions(+), 405 deletions(-)
  • .gitignore
  • lib/app.dart
  • lib/core/downloader/download_service.dart
  • lib/core/emulator/github_release_service.dart
  • lib/core/emulator/strategies/windows_strategy.dart
  • lib/core/romm/romm_models.dart
  • lib/core/romm/romm_service.dart
  • lib/core/save/save_strategy.dart
  • lib/core/save/save_sync_service.dart
  • lib/core/save/strategies/dolphin_save_strategy.dart
  • lib/core/save/strategies/eden_save_strategy.dart
  • lib/core/save/strategies/pcsx2_save_strategy.dart
  • lib/core/save/strategies/retroarch_save_strategy.dart
  • lib/core/save/strategies/rpcs3_save_strategy.dart
  • lib/core/save/strategies/windows_save_strategy.dart
  • lib/core/save/strategies/xenia_save_strategy.dart
  • lib/core/storage/directory_service.dart
  • lib/core/windows/pcgamingwiki_service.dart
  • lib/core/windows/windows_game_service.dart
  • lib/main.dart
  • lib/providers/library_provider.dart
  • lib/providers/romm_provider.dart
  • lib/ui/screens/download_screen.dart
  • lib/ui/screens/library_screen.dart
  • lib/ui/screens/settings_screen.dart
  • lib/ui/widgets/game_card.dart
  • lib/ui/widgets/platform_filter_bar.dart
  • test/game_card_test.dart

Diff

diff --git a/.gitignore b/.gitignore
index 3820a95..ea0b2ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,4 @@ app.*.map.json
 /android/app/debug
 /android/app/profile
 /android/app/release
+.aider*
diff --git a/lib/app.dart b/lib/app.dart
index d08c16a..0b9a161 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -14,6 +14,7 @@ class FreegosyApp extends ConsumerStatefulWidget {
 
 class _FreegosyAppState extends ConsumerState<FreegosyApp> {
   int _currentIndex = 0;
+  bool _settingsLoaded = false;
 
   final List<Widget> _screens = const [
     LibraryScreen(),
@@ -24,15 +25,38 @@ class _FreegosyAppState extends ConsumerState<FreegosyApp> {
   @override
   void initState() {
     super.initState();
-    // Eagerly load persisted card aspect ratio
-    ref.read(cardAspectRatioLoaderProvider);
-    ref.read(retroarchSyncModeLoaderProvider);
+    Future.wait([
+      ref.read(cardAspectRatioLoaderProvider.future),
+      ref.read(retroarchSyncModeLoaderProvider.future),
+      ref.read(columnCountLoaderProvider.future),
+      ref.read(cardSpacingLoaderProvider.future),
+      ref.read(showTitleLoaderProvider.future),
+      ref.read(showButtonsOnHoverLoaderProvider.future),
+      ref.read(activePresetLoaderProvider.future),
+    ]).then((_) {
+      if (mounted) {
+        setState(() => _settingsLoaded = true);
+      }
+    });
   }
 
   @override
   Widget build(BuildContext context) {
-    return MaterialApp(
-      title: 'Freegosy',
+    if (!_settingsLoaded) {
+      return const MaterialApp(
+        home: Scaffold(
+          backgroundColor: Color(0xFF0f0f0f),
+          body: Center(
+            child: CircularProgressIndicator(
+              color: Colors.deepPurple,
+            ),
+          ),
+        ),
+      );
+    }
+    return ExcludeSemantics(
+      child: MaterialApp(
+        title: 'Freegosy',
       theme: ThemeData(
         useMaterial3: true,
         colorScheme: ColorScheme.fromSeed(
@@ -99,6 +123,7 @@ class _FreegosyAppState extends ConsumerState<FreegosyApp> {
           ],
         ),
       ),
+      ),
     );
   }
 }
diff --git a/lib/core/downloader/download_service.dart b/lib/core/downloader/download_service.dart
index b3e5a42..e83fab2 100644
--- a/lib/core/downloader/download_service.dart
+++ b/lib/core/downloader/download_service.dart
@@ -2,7 +2,6 @@ import 'package:dio/dio.dart';
 import 'dart:async';
 import 'dart:io';
 import 'package:archive/archive_io.dart';
-import 'package:flutter/rendering.dart';
 import 'package:freegosy/core/storage/directory_service.dart';
 import 'package:freegosy/core/romm/romm_models.dart';
 
@@ -135,19 +134,5 @@ class DownloadService {
 
     // Delete the archive after extraction
     await File(zipPath).delete();
-
-    // Log the largest file found (main ROM)
-    File? largestFile;
-    int largestSize = 0;
-    await for (final entity in Directory(extractDir).list(recursive: true)) {
-      if (entity is File) {
-        final size = await entity.length();
-        if (size > largestSize) {
-          largestSize = size;
-          largestFile = entity;
-        }
-      }
-    }
-    debugPrint('[DownloadService] multi-file extracted to $extractDir, main ROM: ${largestFile?.path}');
   }
 }
\ No newline at end of file
diff --git a/lib/core/emulator/github_release_service.dart b/lib/core/emulator/github_release_service.dart
index 404a530..acb8b57 100644
--- a/lib/core/emulator/github_release_service.dart
+++ b/lib/core/emulator/github_release_service.dart
@@ -1,5 +1,4 @@
 import 'package:dio/dio.dart';
-import 'package:flutter/foundation.dart';
 
 class GithubReleaseService {
   final Dio _dio;
@@ -22,7 +21,6 @@ class GithubReleaseService {
       if (response.statusCode != 200) return null;
 
       final assets = response.data['assets'] as List<dynamic>;
-      debugPrint('[GithubReleaseService] $repo — ${assets.length} assets found');
 
       for (final asset in assets) {
         final name = (asset['name'] as String).toLowerCase();
@@ -36,14 +34,11 @@ class GithubReleaseService {
         final matchesExcluded = excluded.any((f) => name.contains(f.toLowerCase()));
         if (matchesExcluded) continue;
 
-        debugPrint('[GithubReleaseService] matched asset: $name');
         return url;
       }
 
-      debugPrint('[GithubReleaseService] no matching asset found for $repo');
       return null;
     } catch (e) {
-      debugPrint('[GithubReleaseService] error fetching $repo: $e');
       return null;
     }
   }
diff --git a/lib/core/emulator/strategies/windows_strategy.dart b/lib/core/emulator/strategies/windows_strategy.dart
index 17cf244..391e944 100644
--- a/lib/core/emulator/strategies/windows_strategy.dart
+++ b/lib/core/emulator/strategies/windows_strategy.dart
@@ -1,5 +1,4 @@
 import 'dart:io';
-import 'package:flutter/foundation.dart';
 import 'package:freegosy/core/emulator/emulator_strategy.dart';
 import 'package:freegosy/core/romm/romm_models.dart';
 import 'package:freegosy/core/storage/directory_service.dart';
@@ -60,7 +59,6 @@ class WindowsStrategy extends EmulatorStrategy {
 
     // If stored override no longer exists on disk, discard and auto-detect
     if (exePath != null && exePath.isNotEmpty && !await File(exePath).exists()) {
-      debugPrint('[WindowsStrategy] override exe not found at $exePath, falling back to auto-detect');
       exePath = null;
     }
 
@@ -81,7 +79,6 @@ class WindowsStrategy extends EmulatorStrategy {
       );
     }
 
-    debugPrint('[WindowsStrategy] launching: $exePath');
     final process = await Process.start(
       exePath,
       [],
@@ -94,14 +91,11 @@ class WindowsStrategy extends EmulatorStrategy {
         .catchError((_) => -99999); // timeout = still running = fine
 
     if (exitCode != -99999 && exitCode != 0) {
-      debugPrint('[WindowsStrategy] ${game.name} exited with code $exitCode');
       throw Exception(
         '${game.name} crashed immediately (exit code $exitCode). '
         'This is likely due to missing DirectX, Visual C++ redistributables, or other dependencies.',
       );
     }
-
-    debugPrint('[WindowsStrategy] ${game.name} still running after 5s — OK');
   }
 
   @override
diff --git a/lib/core/romm/romm_models.dart b/lib/core/romm/romm_models.dart
index c9d6d00..afca4b0 100644
--- a/lib/core/romm/romm_models.dart
+++ b/lib/core/romm/romm_models.dart
@@ -16,6 +16,27 @@ class Game {
 
   bool get isMultiFile => hasMultipleFiles;
 
+  String get displayName {
+    String cleaned = name;
+
+    // Remove leading hex IDs like 00040000000EC400
+    cleaned = cleaned.replaceAll(RegExp(r'^[0-9A-Fa-f]{16}\s*'), '');
+
+    // Remove region/version codes in parentheses like (CTR-P-BZLP) (v0.0.0) (En)
+    cleaned = cleaned.replaceAll(RegExp(r'\([^)]*\)'), '');
+
+    // Remove region/version codes in brackets like [!] [b] [T+Eng]
+    cleaned = cleaned.replaceAll(RegExp(r'\[[^\]]*\]'), '');
+
+    // Remove trailing dots, dashes, underscores and whitespace
+    cleaned = cleaned.replaceAll(RegExp(r'[\s._-]+$'), '');
+
+    // Collapse multiple spaces into one
+    cleaned = cleaned.replaceAll(RegExp(r'\s+'), ' ');
+
+    return cleaned.trim().isEmpty ? name : cleaned.trim();
+  }
+
   Game({
     required this.id,
     required this.name,
diff --git a/lib/core/romm/romm_service.dart b/lib/core/romm/romm_service.dart
index 54fcc69..61e4934 100644
--- a/lib/core/romm/romm_service.dart
+++ b/lib/core/romm/romm_service.dart
@@ -2,7 +2,6 @@ import 'dart:convert';
 import 'dart:io';
 import 'dart:typed_data';
 import 'package:dio/dio.dart';
-import 'package:flutter/widgets.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 import 'romm_models.dart';
 
@@ -19,14 +18,6 @@ class RommService {
           connectTimeout: const Duration(seconds: 10),
           receiveTimeout: const Duration(seconds: 15),
         )) {
-    debugPrint('[RommService] created — baseUrl=${_normalizeBaseUrl(config.baseUrl)} user=${config.username} hasToken=${config.token != null && config.token!.isNotEmpty}');
-    // _dio.interceptors.add(LogInterceptor(
-    //   requestHeader: true,
-    //   responseHeader: false,
-    //   responseBody: true,
-    //   requestBody: true,
-    //   logPrint: (o) => debugPrint('[DIO] $o'),
-    // ));
     // If the server rejects the Bearer token with 403, retry once with Basic auth.
     _dio.interceptors.add(InterceptorsWrapper(
       onError: (DioException e, ErrorInterceptorHandler handler) async {
@@ -35,7 +26,6 @@ class RommService {
           e.requestOptions.data is! FormData &&
           config.token != null &&
           config.token!.isNotEmpty) {
-          debugPrint('[RommService] Bearer got 403 — retrying with Basic auth');
           final basic = 'Basic ${base64Encode(utf8.encode('${config.username}:${config.password}'))}';
           final opts = e.requestOptions
             ..headers['Authorization'] = basic
@@ -56,10 +46,8 @@ class RommService {
   Options get _authOptions {
     final token = config.token;
     if (token != null && token.isNotEmpty) {
-      debugPrint('[RommService] _authOptions using Bearer token');
       return Options(headers: {'Authorization': 'Bearer $token'});
     }
-    debugPrint('[RommService] _authOptions using Basic auth user=${config.username} passLen=${config.password.length}');
     final basic = 'Basic ${base64Encode(utf8.encode('${config.username}:${config.password}'))}';
     return Options(headers: {'Authorization': basic});
   }
@@ -68,13 +56,11 @@ class RommService {
   /// stores the Bearer token in SharedPreferences, and returns it.
   static Future<String> fetchToken(String baseUrl, String username, String password) async {
     final normalizedUrl = _normalizeBaseUrl(baseUrl);
-    debugPrint('[fetchToken] POST $normalizedUrl/api/token user=$username');
     final dio = Dio(BaseOptions(
       baseUrl: normalizedUrl,
       connectTimeout: const Duration(seconds: 10),
       receiveTimeout: const Duration(seconds: 15),
     ));
-    dio.interceptors.add(LogInterceptor(responseBody: true, requestBody: true, logPrint: (o) => debugPrint('[DIO/token] $o')));
     final response = await dio.post(
       '/api/token',
       data: {
@@ -85,14 +71,12 @@ class RommService {
       },
       options: Options(contentType: 'application/x-www-form-urlencoded'),
     );
-    debugPrint('[fetchToken] response status=${response.statusCode} data=${response.data}');
     final token = response.data['access_token'] as String?;
     if (token == null || token.isEmpty) {
       throw Exception('Login failed: no access_token in response');
     }
     final prefs = await SharedPreferences.getInstance();
     await prefs.setString('rommAuthToken', token);
-    debugPrint('[fetchToken] token stored OK');
     return token;
   }
 
@@ -157,9 +141,6 @@ class RommService {
         final Map<String, dynamic> data = response.data is Map ? response.data : {'items': response.data};
         final List<dynamic> items = data['items'] ?? [];
         total = data['total'] ?? items.length;
-        if (offset == 0 && items.isNotEmpty) {
-        debugPrint('[RommService] sample raw game: ${items.first}');
-        }
         allGames.addAll(items.map((item) => Game.fromJson(item)).toList());
         offset += limit;
       } else {
@@ -201,8 +182,6 @@ class RommService {
         ? config.baseUrl.substring(0, config.baseUrl.length - 1) 
         : config.baseUrl;
     
-    debugPrint('[RommService] getDownloadUrl: name=${game.name} fileName=${game.fileName} fsName=${game.fsName} isMultiFile=${game.isMultiFile}');
-
     final name = game.fileName ?? game.fsName ?? game.name;
     final encoded = Uri.encodeComponent(name);
     return '$baseUrl/api/roms/${game.id}/content/$encoded';
@@ -233,10 +212,8 @@ class RommService {
       final ok = response.statusCode != null &&
           response.statusCode! >= 200 &&
           response.statusCode! < 300;
-      debugPrint('[RommService] uploadSave ${ok ? 'ok' : 'failed'} status=${response.statusCode} file=$fileName');
       return ok;
     } catch (e) {
-      debugPrint('[RommService] uploadSave error: $e');
       return false;
     }
   }
@@ -273,7 +250,6 @@ class RommService {
       });
       return sorted.first;
     } catch (e) {
-      debugPrint('[RommService] getLatestSave error: $e');
       return null;
     }
   }
@@ -295,7 +271,6 @@ class RommService {
       }
       return null;
     } catch (e) {
-      debugPrint('[RommService] downloadSave error: $e');
       return null;
     }
   }
diff --git a/lib/core/save/save_strategy.dart b/lib/core/save/save_strategy.dart
index 08a9265..e854ffd 100644
--- a/lib/core/save/save_strategy.dart
+++ b/lib/core/save/save_strategy.dart
@@ -1,6 +1,5 @@
 import 'dart:io';
 import 'dart:typed_data';
-import 'package:flutter/widgets.dart';
 
 import '../romm/romm_models.dart';
 
@@ -33,7 +32,7 @@ abstract class SaveStrategy {
       if (await bak.exists()) await bak.rename('$path.bak1');
       await file.copy('$path.bak');
     } catch (e) {
-      debugPrint('[SaveStrategy] backupSave error for $path: $e');
+      // Error handled silently
     }
   }
 
diff --git a/lib/core/save/save_sync_service.dart b/lib/core/save/save_sync_service.dart
index 1a87b9c..ddda8da 100644
--- a/lib/core/save/save_sync_service.dart
+++ b/lib/core/save/save_sync_service.dart
@@ -1,5 +1,4 @@
 import 'dart:io';
-import 'package:flutter/foundation.dart';
 import '../romm/romm_models.dart';
 import '../romm/romm_service.dart';
 import '../storage/directory_service.dart';
@@ -104,13 +103,11 @@ class SaveSyncService {
     try {
       final strategy = getStrategyForSlug(game.platformSlug);
       if (strategy == null) {
-        debugPrint('[SaveSyncService] no strategy for ${game.platformSlug}');
         return false;
       }
 
       final files = await strategy.getSaveFiles(game, romPath, sessionStart: sessionStart, syncMode: syncMode);
       if (files.isEmpty) {
-        debugPrint('[SaveSyncService] no save files found for ${game.name}');
         return false;
       }
 
@@ -139,10 +136,8 @@ class SaveSyncService {
   }
 }
 
-      debugPrint('[SaveSyncService] pushed $uploaded/${files.length} saves for ${game.name}');
       return uploaded > 0;
     } catch (e) {
-      debugPrint('[SaveSyncService] pushSaves error: $e');
       return false;
     }
   }
@@ -154,38 +149,30 @@ class SaveSyncService {
     try {
       final strategy = getStrategyForSlug(game.platformSlug);
       if (strategy == null) {
-        debugPrint('[SaveSyncService] no strategy for ${game.platformSlug}');
         return false;
       }
 
       final save = await _rommService.getLatestSave(game.id);
       if (save == null) {
-        debugPrint('[SaveSyncService] no cloud save for ${game.name}');
         return false;
       }
 
       final downloadUrl = save['download_path'] as String? ?? save['url'] as String?;
       if (downloadUrl == null) {
-        debugPrint('[SaveSyncService] save has no download URL');
         return false;
       }
 
       final bytes = await _rommService.downloadSave(downloadUrl);
       if (bytes == null) {
-        debugPrint('[SaveSyncService] failed to download save bytes');
         return false;
       }
 
       final filename = save['file_name'] as String? ??
           downloadUrl.split('/').last;
 
-      debugPrint('[SaveSyncService] calling restoreSave: filename=$filename romPath=$romPath bytesLen=${bytes.length}');
       final ok = await strategy.restoreSave(game, romPath, bytes, filename);
-      debugPrint('[SaveSyncService] restoreSave returned: $ok');
-      debugPrint('[SaveSyncService] pullSave ${ok ? 'ok' : 'failed'} for ${game.name}');
       return ok;
     } catch (e) {
-      debugPrint('[SaveSyncService] pullSave error: $e');
       rethrow;
     }
   }
diff --git a/lib/core/save/strategies/dolphin_save_strategy.dart b/lib/core/save/strategies/dolphin_save_strategy.dart
index 971cf22..be16d8f 100644
--- a/lib/core/save/strategies/dolphin_save_strategy.dart
+++ b/lib/core/save/strategies/dolphin_save_strategy.dart
@@ -1,6 +1,5 @@
 import 'dart:io';
 import 'dart:typed_data';
-import 'package:flutter/rendering.dart';
 
 import '../../romm/romm_models.dart';
 import '../../storage/directory_service.dart';
@@ -68,10 +67,8 @@ Future<List<File>> getSaveFiles(Game game, String romPath, {DateTime? sessionSta
       final targetPath = '$saveDir/$filename';
       await backupSave(targetPath);
       await File(targetPath).writeAsBytes(data);
-      debugPrint('[DolphinSaveStrategy] restored $filename to $targetPath');
       return true;
     } catch (e) {
-      debugPrint('[DolphinSaveStrategy] restoreSave error: $e');
       return false;
     }
   }
diff --git a/lib/core/save/strategies/eden_save_strategy.dart b/lib/core/save/strategies/eden_save_strategy.dart
index 3a83909..9274893 100644
--- a/lib/core/save/strategies/eden_save_strategy.dart
+++ b/lib/core/save/strategies/eden_save_strategy.dart
@@ -1,7 +1,6 @@
 import 'dart:io';
 import 'dart:typed_data';
 import 'package:archive/archive.dart';
-import 'package:flutter/widgets.dart';
 import '../../romm/romm_models.dart';
 import '../save_strategy.dart';
 
@@ -52,7 +51,6 @@ class EdenSaveStrategy extends SaveStrategy {
         await raf.close();
       }
     } catch (e) {
-      debugPrint('[EdenSaveStrategy] XCI parse error: $e');
       return null;
     }
   }
@@ -85,7 +83,7 @@ class EdenSaveStrategy extends SaveStrategy {
         }
       }
     } catch (e) {
-      debugPrint('[EdenSaveStrategy] scan error: $e');
+      // Error handled silently
     }
 
     return bestTitleId;
@@ -101,7 +99,6 @@ class EdenSaveStrategy extends SaveStrategy {
       if (appData.isEmpty || appData.contains('%APPDATA%')) return null;
       return '$appData/eden/nand/user/save/0000000000000000';
     } catch (e) {
-      debugPrint('[EdenSaveStrategy] APPDATA lookup error: $e');
       return null;
     }
   }
@@ -157,7 +154,7 @@ class EdenSaveStrategy extends SaveStrategy {
         return '${profileDirs.first.path}/$titleId';
       }
     } catch (e) {
-      debugPrint('[EdenSaveStrategy] getSaveDir error: $e');
+      // Error handled silently
     }
     return null;
   }
@@ -178,13 +175,10 @@ Future<List<File>> getSaveFiles(Game game, String romPath, {DateTime? sessionSta
     @override
     Future<bool> restoreSave(Game game, String destPath, Uint8List data, String filename) async {
       try {
-        debugPrint('[EdenSaveStrategy] restoreSave called: filename=$filename destPath=$destPath');
         final titleMatch = _titleIdRegex.firstMatch(filename.toUpperCase());
-        debugPrint('[EdenSaveStrategy] titleMatch from filename: ${titleMatch?.group(0)}');
         final saveDir = titleMatch != null
             ? await _getSaveDirForTitleId(titleMatch.group(0)!)
             : await getSaveDir(game, destPath);
-        debugPrint('[EdenSaveStrategy] saveDir resolved: $saveDir');
         if (saveDir == null) return false;
 
         final dir = Directory(saveDir);
@@ -198,10 +192,8 @@ Future<List<File>> getSaveFiles(Game game, String romPath, {DateTime? sessionSta
         final targetPath = '$saveDir/$filename';
         await backupSave(targetPath);
         await File(targetPath).writeAsBytes(data);
-        debugPrint('[EdenSaveStrategy] restored $filename to $targetPath');
         return true;
       } catch (e) {
-        debugPrint('[EdenSaveStrategy] restoreSave error: $e');
         rethrow;
       }
     }
@@ -225,10 +217,8 @@ Future<List<File>> getSaveFiles(Game game, String romPath, {DateTime? sessionSta
           await Directory(outPath).create(recursive: true);
         }
       }
-      debugPrint('[EdenSaveStrategy] extracted ${archive.length} entries to $destDir');
       return true;
     } catch (e) {
-      debugPrint('[EdenSaveStrategy] zip extraction error: $e');
       return false;
     }
   }
diff --git a/lib/core/save/strategies/pcsx2_save_strategy.dart b/lib/core/save/strategies/pcsx2_save_strategy.dart
index d70a186..7a44a61 100644
--- a/lib/core/save/strategies/pcsx2_save_strategy.dart
+++ b/lib/core/save/strategies/pcsx2_save_strategy.dart
@@ -97,7 +97,6 @@ class Pcsx2SaveStrategy extends SaveStrategy {
             final outFile = File(targetPath);
             await outFile.parent.create(recursive: true);
             await outFile.writeAsBytes(entry.content as List<int>);
-            debugPrint('[Pcsx2SaveStrategy] restored ${entry.name} to $targetPath');
           }
         }
         return true;
@@ -111,10 +110,8 @@ class Pcsx2SaveStrategy extends SaveStrategy {
       final targetPath = '$targetDir\\$filename';
       await backupSave(targetPath);
       await File(targetPath).writeAsBytes(data);
-      debugPrint('[Pcsx2SaveStrategy] restored $filename to $targetPath');
       return true;
     } catch (e) {
-      debugPrint('[Pcsx2SaveStrategy] restoreSave error: $e');
       return false;
     }
   }
diff --git a/lib/core/save/strategies/retroarch_save_strategy.dart b/lib/core/save/strategies/retroarch_save_strategy.dart
index 50ac4cd..b9accd9 100644
--- a/lib/core/save/strategies/retroarch_save_strategy.dart
+++ b/lib/core/save/strategies/retroarch_save_strategy.dart
@@ -1,6 +1,5 @@
 import 'dart:io';
 import 'dart:typed_data';
-import 'package:flutter/rendering.dart';
 
 import '../../romm/romm_models.dart';
 import '../../storage/directory_service.dart';
@@ -110,10 +109,8 @@ class RetroArchSaveStrategy extends SaveStrategy {
       final targetPath = '$targetDir/$filename';
       await backupSave(targetPath);
       await File(targetPath).writeAsBytes(data);
-      debugPrint('[RetroArchSaveStrategy] restored $filename to $targetPath');
       return true;
     } catch (e) {
-      debugPrint('[RetroArchSaveStrategy] restoreSave error: $e');
       return false;
     }
   }
diff --git a/lib/core/save/strategies/rpcs3_save_strategy.dart b/lib/core/save/strategies/rpcs3_save_strategy.dart
index 4a8b721..4e22b1e 100644
--- a/lib/core/save/strategies/rpcs3_save_strategy.dart
+++ b/lib/core/save/strategies/rpcs3_save_strategy.dart
@@ -48,7 +48,6 @@ class Rpcs3SaveStrategy extends SaveStrategy {
         return folderName.startsWith(titleId);
       }).toList();
       if (byTitleId.isNotEmpty) {
-        debugPrint('[Rpcs3SaveStrategy] matched by title ID $titleId');
         return byTitleId;
       }
     }
@@ -67,11 +66,9 @@ class Rpcs3SaveStrategy extends SaveStrategy {
     }).toList();
 
     if (byName.isNotEmpty) {
-      debugPrint('[Rpcs3SaveStrategy] matched by name: ${byName.map((d) => d.path.split('\\').last).toList()}');
       return byName;
     }
 
-    debugPrint('[Rpcs3SaveStrategy] no save dirs found for ${game.name} in $saveRoot');
     return [];
   }
 
@@ -91,7 +88,6 @@ class Rpcs3SaveStrategy extends SaveStrategy {
 
     final saveDirs = await _findSaveDirs(saveRoot, game);
     if (saveDirs.isEmpty) {
-      debugPrint('[Rpcs3SaveStrategy] no save dirs found for ${game.name}');
       return [];
     }
 
@@ -119,7 +115,6 @@ class Rpcs3SaveStrategy extends SaveStrategy {
     }
     encoder.close();
 
-    debugPrint('[Rpcs3SaveStrategy] packaged saves to $zipPath');
     return [File(zipPath)];
   }
 
@@ -143,7 +138,6 @@ class Rpcs3SaveStrategy extends SaveStrategy {
             await Directory(entryPath).create(recursive: true);
           }
         }
-        debugPrint('[Rpcs3SaveStrategy] extracted $filename to $saveRoot');
         return true;
       }
 
@@ -153,10 +147,8 @@ class Rpcs3SaveStrategy extends SaveStrategy {
       final targetPath = '$saveDir\\$filename';
       await backupSave(targetPath);
       await File(targetPath).writeAsBytes(data);
-      debugPrint('[Rpcs3SaveStrategy] restored $filename to $targetPath');
       return true;
     } catch (e) {
-      debugPrint('[Rpcs3SaveStrategy] restoreSave error: $e');
       return false;
     }
   }
diff --git a/lib/core/save/strategies/windows_save_strategy.dart b/lib/core/save/strategies/windows_save_strategy.dart
index 68dfc3c..d2ebe2c 100644
--- a/lib/core/save/strategies/windows_save_strategy.dart
+++ b/lib/core/save/strategies/windows_save_strategy.dart
@@ -50,11 +50,10 @@ class WindowsSaveStrategy extends SaveStrategy {
     try {
       final locations = await _wikiService.getSaveLocations(game.name);
       if (locations.isNotEmpty) {
-        debugPrint('[WindowsSaveStrategy] wiki found ${locations.length} locations for ${game.name}');
         return locations.first['path'];
       }
     } catch (e) {
-      debugPrint('[WindowsSaveStrategy] wiki lookup failed: $e');
+      //
     }
 
     return null;
@@ -88,7 +87,6 @@ class WindowsSaveStrategy extends SaveStrategy {
     await encoder.addDirectory(dir);
     encoder.close();
 
-    debugPrint('[WindowsSaveStrategy] packaged saves to $zipPath');
     return [File(zipPath)];
   }
 
@@ -117,7 +115,6 @@ class WindowsSaveStrategy extends SaveStrategy {
             await Directory(entryPath).create(recursive: true);
           }
         }
-        debugPrint('[WindowsSaveStrategy] extracted $filename to $saveDir');
         return true;
       }
 
@@ -125,10 +122,8 @@ class WindowsSaveStrategy extends SaveStrategy {
       final targetPath = '$saveDir/$filename';
       await backupSave(targetPath);
       await File(targetPath).writeAsBytes(data);
-      debugPrint('[WindowsSaveStrategy] restored $filename to $targetPath');
       return true;
     } catch (e) {
-      debugPrint('[WindowsSaveStrategy] restoreSave error: $e');
       rethrow;
     }
   }
diff --git a/lib/core/save/strategies/xenia_save_strategy.dart b/lib/core/save/strategies/xenia_save_strategy.dart
index 7958407..b111f98 100644
--- a/lib/core/save/strategies/xenia_save_strategy.dart
+++ b/lib/core/save/strategies/xenia_save_strategy.dart
@@ -66,7 +66,6 @@ class XeniaSaveStrategy extends SaveStrategy {
     await encoder.addDirectory(dir);
     encoder.close();
 
-    debugPrint('[XeniaSaveStrategy] packaged saves to $zipPath');
     return [File(zipPath)];
   }
 
@@ -92,17 +91,14 @@ class XeniaSaveStrategy extends SaveStrategy {
             await Directory(entryPath).create(recursive: true);
           }
         }
-        debugPrint('[XeniaSaveStrategy] extracted $filename to $saveDir');
         return true;
       }
 
       final targetPath = '$saveDir\\$filename';
       await backupSave(targetPath);
       await File(targetPath).writeAsBytes(data);
-      debugPrint('[XeniaSaveStrategy] restored $filename to $targetPath');
       return true;
     } catch (e) {
-      debugPrint('[XeniaSaveStrategy] restoreSave error: $e');
       return false;
     }
   }
diff --git a/lib/core/storage/directory_service.dart b/lib/core/storage/directory_service.dart
index 2ee7855..90f3fa9 100644
--- a/lib/core/storage/directory_service.dart
+++ b/lib/core/storage/directory_service.dart
@@ -149,7 +149,6 @@ class DirectoryService {
         final result = await Process.run('cmd', ['/c', 'echo %APPDATA%'], runInShell: false);
         appData = result.stdout.toString().trim();
       } catch (e) {
-        debugPrint('[DirectoryService] failed to get APPDATA: $e');
         return null;
       }
       if (appData.isEmpty || appData.contains('%APPDATA%')) return null;
@@ -167,10 +166,8 @@ class DirectoryService {
       await dest.parent.create(recursive: true);
       final byteData = await rootBundle.load('thirdparty/7zr.exe');
       await dest.writeAsBytes(byteData.buffer.asUint8List());
-      debugPrint('[DirectoryService] 7zr.exe extracted to ${dest.path}');
       return dest.path;
     } catch (e) {
-      debugPrint('[DirectoryService] failed to extract 7zr.exe: $e');
       return null;
     }
   }
diff --git a/lib/core/windows/pcgamingwiki_service.dart b/lib/core/windows/pcgamingwiki_service.dart
index bb74ec2..8ad3eb9 100644
--- a/lib/core/windows/pcgamingwiki_service.dart
+++ b/lib/core/windows/pcgamingwiki_service.dart
@@ -1,5 +1,4 @@
 import 'package:dio/dio.dart';
-import 'package:flutter/foundation.dart';
 import 'dart:io';
 
 class PcGamingWikiService {
@@ -39,7 +38,7 @@ class PcGamingWikiService {
         if (results.isNotEmpty) return results.first['title'] as String;
       }
     } catch (e) {
-      debugPrint('[PcGamingWiki] findPageTitle error: $e');
+      //
     }
     return null;
   }
@@ -57,7 +56,7 @@ class PcGamingWikiService {
         return response.data['parse']['wikitext']['*'] as String?;
       }
     } catch (e) {
-      debugPrint('[PcGamingWiki] getWikitext error: $e');
+      //
     }
     return null;
   }
@@ -73,7 +72,6 @@ class PcGamingWikiService {
 
       return _parseSaveLocations(wikitext, gameTitle, gameDir);
     } catch (e) {
-      debugPrint('[PcGamingWiki] getSaveLocations error: $e');
       return [];
     }
   }
@@ -111,7 +109,7 @@ class PcGamingWikiService {
           });
         }
       } catch (e) {
-        debugPrint('[PcGamingWiki] parse error on line: $e');
+        //
       }
     }
     return results;
diff --git a/lib/core/windows/windows_game_service.dart b/lib/core/windows/windows_game_service.dart
index db76fb2..e212723 100644
--- a/lib/core/windows/windows_game_service.dart
+++ b/lib/core/windows/windows_game_service.dart
@@ -1,5 +1,4 @@
 import 'dart:io';
-import 'package:flutter/foundation.dart';
 import 'package:path/path.dart' as p;
 
 class WindowsGameService {
@@ -29,7 +28,6 @@ class WindowsGameService {
             .toLowerCase()
             .replaceAll(RegExp(r'[^a-z0-9]'), '');
         if (exeName.contains(hintLower) || hintLower.contains(exeName)) {
-          debugPrint('[WindowsGameService] matched exe by hint: ${exe.path}');
           return exe.path;
         }
       }
@@ -46,7 +44,6 @@ class WindowsGameService {
       }
     }
 
-    debugPrint('[WindowsGameService] largest exe: ${largest?.path}');
     return largest?.path;
   }
 
diff --git a/lib/main.dart b/lib/main.dart
index 96328b3..4f4a952 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'app.dart';
 
 void main() {
+  WidgetsFlutterBinding.ensureInitialized();
   runApp(
     const ProviderScope(
       child: FreegosyApp(),
diff --git a/lib/providers/library_provider.dart b/lib/providers/library_provider.dart
index aac1605..7432bda 100644
--- a/lib/providers/library_provider.dart
+++ b/lib/providers/library_provider.dart
@@ -1,14 +1,152 @@
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:shared_preferences/shared_preferences.dart';
+import 'dart:convert';
 import '../core/romm/romm_models.dart';
 import 'romm_provider.dart';
 
+const String _gamesCacheKey = 'cached_games';
+const String _platformsCacheKey = 'cached_platforms';
+const String _gamesCacheTimeKey = 'cached_games_time';
+const String _platformsCacheTimeKey = 'cached_platforms_time';
+const int _cacheExpiryDays = 7;
+const int _cacheMaxBytes = 10 * 1024 * 1024; // 10MB
+
+Future<bool> _isCacheValid(SharedPreferences prefs, String timeKey) async {
+  final savedTime = prefs.getString(timeKey);
+  if (savedTime == null) return false;
+  final cacheTime = DateTime.tryParse(savedTime);
+  if (cacheTime == null) return false;
+  return DateTime.now().difference(cacheTime).inDays < _cacheExpiryDays;
+}
+
+Future<void> _saveGamesCache(List<Game> games) async {
+  final prefs = await SharedPreferences.getInstance();
+  final jsonList = games.map((g) => {
+    'id': g.id,
+    'name': g.name,
+    'platform_id': g.platformId,
+    'platform_slug': g.platformSlug,
+    'platform_display_name': g.platformDisplayName,
+    'path_cover_large': g.pathCoverLarge,
+    'path_cover_small': g.pathCoverSmall,
+    'url_cover': g.urlCover,
+    'url_download': g.fileUrl,
+    'file_name': g.fileName,
+    'fs_name': g.fsName,
+    'file_size_bytes': g.fileSize,
+    'multi_file_path': g.multiFilePath,
+    'has_multiple_files': g.hasMultipleFiles,
+  }).toList();
+  
+  final jsonString = jsonEncode(jsonList);
+  final sizeInBytes = jsonString.length; // Use .length for string size in bytes (UTF-8)
+  
+  if (sizeInBytes > _cacheMaxBytes) {
+    // Library too large to cache safely
+    // Store a flag so we know caching was skipped
+    await prefs.setBool('cache_size_exceeded', true);
+    await prefs.remove(_gamesCacheKey);
+    await prefs.remove(_gamesCacheTimeKey);
+    return;
+  }
+  
+  await prefs.setBool('cache_size_exceeded', false);
+  await prefs.setString(_gamesCacheKey, jsonString);
+  await prefs.setString(
+    _gamesCacheTimeKey, 
+    DateTime.now().toIso8601String(),
+  );
+}
+
+Future<void> _savePlatformsCache(List<Platform> platforms) async {
+  final prefs = await SharedPreferences.getInstance();
+  final jsonList = platforms.map((p) => {
+    'id': p.id,
+    'name': p.name,
+    'slug': p.slug,
+  }).toList();
+  await prefs.setString(_platformsCacheKey, jsonEncode(jsonList));
+  await prefs.setString(_platformsCacheTimeKey, DateTime.now().toIso8601String());
+}
+
+Future<List<Game>?> _loadGamesCache() async {
+  final prefs = await SharedPreferences.getInstance();
+  
+  // If cache was previously skipped due to size, 
+  // don't attempt to load
+  final sizeExceeded = 
+    prefs.getBool('cache_size_exceeded') ?? false;
+  if (sizeExceeded) return null;
+  
+  final isValid = await _isCacheValid(prefs, _gamesCacheTimeKey);
+  if (!isValid) return null;
+  
+  final jsonString = prefs.getString(_gamesCacheKey);
+  if (jsonString == null) return null;
+  
+  try {
+    final jsonList = jsonDecode(jsonString) as List<dynamic>;
+    return jsonList
+        .map((item) => Game.fromJson(item as Map<String, dynamic>))
+        .toList();
+  } catch (e) {
+    return null;
+  }
+}
+
+Future<List<Platform>?> _loadPlatformsCache() async {
+  final prefs = await SharedPreferences.getInstance();
+  final isValid = await _isCacheValid(prefs, _platformsCacheTimeKey);
+  if (!isValid) return null;
+  final jsonString = prefs.getString(_platformsCacheKey);
+  if (jsonString == null) return null;
+  try {
+    final jsonList = jsonDecode(jsonString) as List<dynamic>;
+    return jsonList
+        .map((item) => Platform.fromJson(item as Map<String, dynamic>))
+        .toList();
+  } catch (e) {
+    return null;
+  }
+}
+
+const Map<String, Map<String, dynamic>> kDisplayPresets = {
+  'windows_best': {
+    'columnCount': 5,
+    'cardAspectRatio': 0.72,
+    'cardSpacing': 8.0,
+    'showTitle': true,
+    'showButtonsOnHover': false,
+  },
+  'steamdeck_best': {
+    'columnCount': 3,
+    'cardAspectRatio': 0.72,
+    'cardSpacing': 12.0,
+    'showTitle': true,
+    'showButtonsOnHover': false,
+  },
+  'cozy': {
+    'columnCount': 4,
+    'cardAspectRatio': 0.72,
+    'cardSpacing': 8.0,
+    'showTitle': true,
+    'showButtonsOnHover': false,
+  },
+  'compact': {
+    'columnCount': 7,
+    'cardAspectRatio': 1.0,
+    'cardSpacing': 4.0,
+    'showTitle': false,
+    'showButtonsOnHover': true,
+  },
+};
+
 final searchQueryProvider = StateProvider<String>((ref) => '');
 final selectedPlatformIdProvider = StateProvider<int?>((ref) => null);
 
 final cardAspectRatioProvider = StateProvider<double>((ref) {
   // Synchronous init — actual persisted value is loaded in _loadCardAspectRatio
-  // and set via the notifier. Default is 0.72 (square).
+  // and set via the notifier. Default is 0.72 (portrait).
   return 0.72;
 });
 
@@ -21,16 +159,121 @@ final cardAspectRatioLoaderProvider = FutureProvider<void>((ref) async {
   }
 });
 
+// Display Settings Providers
+final columnCountProvider = StateProvider<int>((ref) {
+  return 4;
+});
+
+final columnCountLoaderProvider = FutureProvider<void>((ref) async {
+  final prefs = await SharedPreferences.getInstance();
+  final saved = prefs.getInt('column_count');
+  if (saved != null) {
+    ref.read(columnCountProvider.notifier).state = saved;
+  }
+});
+
+final cardSpacingProvider = StateProvider<double>((ref) {
+  return 8.0;
+});
+
+final cardSpacingLoaderProvider = FutureProvider<void>((ref) async {
+  final prefs = await SharedPreferences.getInstance();
+  final saved = prefs.getDouble('card_spacing');
+  if (saved != null) {
+    ref.read(cardSpacingProvider.notifier).state = saved;
+  }
+});
+
+final showTitleProvider = StateProvider<bool>((ref) {
+  return true;
+});
+
+final showTitleLoaderProvider = FutureProvider<void>((ref) async {
+  final prefs = await SharedPreferences.getInstance();
+  final saved = prefs.getBool('show_title');
+  if (saved != null) {
+    ref.read(showTitleProvider.notifier).state = saved;
+  }
+});
+
+final showButtonsOnHoverProvider = StateProvider<bool>((ref) {
+  return false;
+});
+
+final showButtonsOnHoverLoaderProvider = FutureProvider<void>((ref) async {
+  final prefs = await SharedPreferences.getInstance();
+  final saved = prefs.getBool('show_buttons_on_hover');
+  if (saved != null) {
+    ref.read(showButtonsOnHoverProvider.notifier).state = saved;
+  }
+});
+
+final activePresetProvider = StateProvider<String>((ref) {
+  return 'custom';
+});
+
+final activePresetLoaderProvider = FutureProvider<void>((ref) async {
+  final prefs = await SharedPreferences.getInstance();
+  final saved = prefs.getString('active_preset');
+  if (saved != null) {
+    ref.read(activePresetProvider.notifier).state = saved;
+  }
+});
+
 final platformsProvider = FutureProvider<List<Platform>>((ref) async {
   final service = ref.watch(rommServiceProvider);
   if (service == null) return [];
-  return await service.getPlatforms();
+
+  // Try cache first
+  final cached = await _loadPlatformsCache();
+  if (cached != null) {
+    // Refresh in background
+    Future.microtask(() async {
+      try {
+        final fresh = await service.getPlatforms();
+        if (fresh.isNotEmpty) {
+          await _savePlatformsCache(fresh);
+          ref.invalidateSelf();
+        }
+      } catch (_) {}
+    });
+    return cached;
+  }
+
+  // No valid cache — fetch fresh
+  final platforms = await service.getPlatforms();
+  if (platforms.isNotEmpty) {
+    await _savePlatformsCache(platforms);
+  }
+  return platforms;
 });
 
 final allGamesProvider = FutureProvider<List<Game>>((ref) async {
   final service = ref.watch(rommServiceProvider);
   if (service == null) return [];
-  return await service.getAllGames();
+
+  // Try cache first — return instantly if valid
+  final cached = await _loadGamesCache();
+  if (cached != null) {
+    // Refresh in background without blocking UI
+    Future.microtask(() async {
+      try {
+        final fresh = await service.getAllGames();
+        if (fresh.isNotEmpty) {
+          await _saveGamesCache(fresh);
+          ref.invalidateSelf();
+        }
+      } catch (_) {}
+    });
+    return cached;
+  }
+
+  // No valid cache — fetch fresh and cache result
+  final games = await service.getAllGames();
+  if (games.isNotEmpty) {
+    await _saveGamesCache(games);
+  }
+  return games;
 });
 
 final filteredGamesProvider = Provider<List<Game>>((ref) {
@@ -45,11 +288,14 @@ final filteredGamesProvider = Provider<List<Game>>((ref) {
 
   if (searchQuery.isNotEmpty) {
     filtered = filtered
-        .where((g) => g.name.toLowerCase().contains(searchQuery.toLowerCase()))
+        .where((g) =>
+            g.displayName.toLowerCase().contains(searchQuery.toLowerCase()) ||
+            g.name.toLowerCase().contains(searchQuery.toLowerCase()))
         .toList();
   }
 
-  filtered.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
+  filtered.sort((a, b) =>
+      a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase()));
   return filtered;
 });
 
@@ -59,4 +305,4 @@ final retroarchSyncModeLoaderProvider = FutureProvider<void>((ref) async {
   final prefs = await SharedPreferences.getInstance();
   final saved = prefs.getString('retroarch_sync_mode') ?? 'both';
   ref.read(retroarchSyncModeProvider.notifier).state = saved;
-});
\ No newline at end of file
+});
diff --git a/lib/providers/romm_provider.dart b/lib/providers/romm_provider.dart
index 989865d..ff962b6 100644
--- a/lib/providers/romm_provider.dart
+++ b/lib/providers/romm_provider.dart
@@ -1,4 +1,3 @@
-import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 import 'package:freegosy/core/storage/directory_service.dart';
@@ -17,7 +16,6 @@ final rommConfigProvider = FutureProvider<RomMConfig>((ref) async {
   final password = prefs.getString('rommPassword') ?? '';
   final token = prefs.getString('rommAuthToken');
 
-  debugPrint('[rommConfigProvider] loaded baseUrl=$baseUrl user=$username passLen=${password.length} hasToken=${token != null && token.isNotEmpty}');
   return RomMConfig(baseUrl: baseUrl, username: username, password: password, token: token);
 });
 
diff --git a/lib/ui/screens/download_screen.dart b/lib/ui/screens/download_screen.dart
index af3eb13..d46c258 100644
--- a/lib/ui/screens/download_screen.dart
+++ b/lib/ui/screens/download_screen.dart
@@ -12,22 +12,24 @@ class DownloadScreen extends ConsumerWidget {
 
     return Scaffold(
       appBar: AppBar(title: const Text('Downloads')),
-      body: downloads.isEmpty
-          ? const Center(child: Text('No active downloads'))
-          : ListView.builder(
-              itemCount: downloads.length,
-              itemBuilder: (context, index) {
-                final gameId = downloads.keys.elementAt(index);
-                final progress = downloads[gameId]!;
-                return DownloadProgressCard(
-                  gameName: progress.gameName,
-                  progress: progress,
-                  onCancel: () {
-                    ref.read(downloadProvider.notifier).removeDownload(gameId);
-                  },
-                );
-              },
-            ),
+      body: ExcludeSemantics(
+        child: downloads.isEmpty
+            ? const Center(child: Text('No active downloads'))
+            : ListView.builder(
+                itemCount: downloads.length,
+                itemBuilder: (context, index) {
+                  final gameId = downloads.keys.elementAt(index);
+                  final progress = downloads[gameId]!;
+                  return DownloadProgressCard(
+                    gameName: progress.gameName,
+                    progress: progress,
+                    onCancel: () {
+                      ref.read(downloadProvider.notifier).removeDownload(gameId);
+                    },
+                  );
+                },
+              ),
+      ),
     );
   }
 }
diff --git a/lib/ui/screens/library_screen.dart b/lib/ui/screens/library_screen.dart
index 70c0949..e4379e0 100644
--- a/lib/ui/screens/library_screen.dart
+++ b/lib/ui/screens/library_screen.dart
@@ -1,8 +1,10 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:shared_preferences/shared_preferences.dart';
 import '../../providers/library_provider.dart';
 import '../../providers/download_provider.dart';
 import '../../providers/romm_provider.dart';
+import '../../core/storage/directory_service.dart';
 import '../../core/romm/romm_models.dart';
 import '../widgets/game_card.dart';
 import '../widgets/platform_filter_bar.dart';
@@ -10,16 +12,56 @@ import '../widgets/windows_game_config_dialog.dart';
 import '../../core/emulator/strategies/windows_strategy.dart';
 import 'dart:convert';
 
-class LibraryScreen extends ConsumerWidget {
+class LibraryScreen extends ConsumerStatefulWidget {
   const LibraryScreen({super.key});
 
+  @override
+  ConsumerState<LibraryScreen> createState() => _LibraryScreenState();
+}
+
+class _LibraryScreenState extends ConsumerState<LibraryScreen> {
+  Map<String, bool> _downloadedStates = {};
+  bool _downloadStatesLoaded = false;
+
+  Future<void> _loadDownloadStates(
+      DirectoryService dirService, List<Game> games) async {
+    if (_downloadStatesLoaded) return;
+    
+    final results = await Future.wait(
+      games.map((game) async {
+        final isDownloaded = 
+          await dirService.isRomDownloaded(game);
+        return MapEntry(game.id, isDownloaded);
+      }),
+    );
+    
+    if (mounted) {
+      setState(() {
+        _downloadedStates = Map.fromEntries(results);
+        _downloadStatesLoaded = true;
+      });
+    }
+  }
+
+  Future<void> _refreshDownloadState(
+      DirectoryService dirService, Game game) async {
+    final isDownloaded = await dirService.isRomDownloaded(game);
+    if (mounted) {
+      setState(() {
+        _downloadedStates[game.id] = isDownloaded;
+      });
+    }
+  }
+
   Future<void> _handleLaunch(BuildContext context, WidgetRef ref, game) async {
     final registry = ref.read(strategyRegistryProvider);
     final strategy = registry?.getStrategyForSlug(game.platformSlug ?? '');
 
     if (strategy == null) {
       ScaffoldMessenger.of(context).showSnackBar(
-        SnackBar(content: Text('No emulator configured for ${game.platformDisplayName ?? game.platformSlug ?? 'this platform'}')),
+        SnackBar(
+            content: Text(
+                'No emulator configured for ${game.platformDisplayName ?? game.platformSlug ?? 'this platform'}')),
       );
       return;
     }
@@ -48,7 +90,8 @@ class LibraryScreen extends ConsumerWidget {
             children: [
               Text('${game.name} is not downloaded yet.'),
               const SizedBox(height: 8),
-              const Text('Expected location:', style: TextStyle(fontWeight: FontWeight.bold)),
+              const Text('Expected location:',
+                  style: TextStyle(fontWeight: FontWeight.bold)),
               const SizedBox(height: 4),
               SelectableText(
                 expectedRomPath,
@@ -95,7 +138,9 @@ class LibraryScreen extends ConsumerWidget {
         if (!context.mounted) return;
         if (pulled) {
           ScaffoldMessenger.of(context).showSnackBar(
-            const SnackBar(content: Text('Cloud save restored'), duration: Duration(seconds: 2)),
+            const SnackBar(
+                content: Text('Cloud save restored'),
+                duration: Duration(seconds: 2)),
           );
         }
       } catch (e) {
@@ -104,7 +149,8 @@ class LibraryScreen extends ConsumerWidget {
           context: context,
           builder: (ctx) => AlertDialog(
             title: const Text('Save Sync Warning'),
-            content: Text('${e.toString().replaceAll('Exception: ', '')}\n\nYou can still play, but your cloud save will not be restored. After playing once, exit the game and sync saves manually.'),
+            content: Text(
+                '${e.toString().replaceAll('Exception: ', '')}\n\nYou can still play, but your cloud save will not be restored. After playing once, exit the game and sync saves manually.'),
             actions: [
               TextButton(
                 onPressed: () => Navigator.of(ctx).pop(false),
@@ -126,8 +172,10 @@ class LibraryScreen extends ConsumerWidget {
       await strategy.launch(game, existingRomPath);
     } catch (e) {
       if (!context.mounted) return;
-      final isWindows = ['windows', 'pc', 'win'].contains(game.platformSlug?.toLowerCase() ?? '');
-      final isMissingExe = e.toString().contains('No executable') || e.toString().contains('not found');
+      final isWindows =
+          ['windows', 'pc', 'win'].contains(game.platformSlug?.toLowerCase() ?? '');
+      final isMissingExe =
+          e.toString().contains('No executable') || e.toString().contains('not found');
       if (isWindows && isMissingExe) {
         await _handleWindowsConfig(context, ref, game);
       } else {
@@ -135,15 +183,17 @@ class LibraryScreen extends ConsumerWidget {
           SnackBar(
             content: Text('Launch failed: $e'),
             duration: const Duration(seconds: 8),
-            ),
+          ),
         );
       }
     }
   }
 
-  Future<void> _handleWindowsConfig(BuildContext context, WidgetRef ref, Game game) async {
+  Future<void> _handleWindowsConfig(
+      BuildContext context, WidgetRef ref, Game game) async {
     final registry = ref.read(strategyRegistryProvider);
-    final windowsStrategy = registry?.getStrategyForSlug(game.platformSlug ?? '') as WindowsStrategy?;
+    final windowsStrategy =
+        registry?.getStrategyForSlug(game.platformSlug ?? '') as WindowsStrategy?;
     final syncService = ref.read(saveSyncServiceProvider);
 
     final result = await showDialog<Map<String, String>>(
@@ -151,7 +201,8 @@ class LibraryScreen extends ConsumerWidget {
       builder: (ctx) => WindowsGameConfigDialog(
         game: game,
         currentExePath: windowsStrategy?.getExeOverride(game.id),
-        currentSavePath: syncService?.windowsSaveStrategy.getManualOverride(game.id),
+        currentSavePath:
+            syncService?.windowsSaveStrategy.getManualOverride(game.id),
       ),
     );
     if (result == null) return; // user cancelled
@@ -159,20 +210,23 @@ class LibraryScreen extends ConsumerWidget {
     final exe = result['exe'] ?? '';
     final save = result['save'] ?? '';
 
-    if (exe.isNotEmpty) await windowsStrategy?.setExeOverride(game.id, exe);
-    if (save.isNotEmpty) await syncService?.windowsSaveStrategy.setManualOverride(game.id, save);
+    if (exe.isNotEmpty) {
+      await windowsStrategy?.setExeOverride(game.id, exe);
+    }
+    if (save.isNotEmpty) {
+      await syncService?.windowsSaveStrategy.setManualOverride(game.id, save);
+    }
 
     if (!context.mounted) return;
-    debugPrint('[Launch] game=${game.name} slug=${game.platformSlug} fsName=${game.fsName} fileName=${game.fileName}');
     await _handleLaunch(context, ref, game);
   }
 
-  Future<void> _handleSyncSaves(BuildContext context, WidgetRef ref, Game game) async {
+  Future<void> _handleSyncSaves(
+      BuildContext context, WidgetRef ref, Game game) async {
     final syncService = ref.read(saveSyncServiceProvider);
     if (syncService == null) {
       ScaffoldMessenger.of(context).showSnackBar(
-        const SnackBar(
-          content: Text('Save sync not available')),
+        const SnackBar(content: Text('Save sync not available')),
       );
       return;
     }
@@ -206,22 +260,66 @@ class LibraryScreen extends ConsumerWidget {
       return;
     }
     final url = service.getDownloadUrl(game);
-    final basicAuth = 'Basic ${base64Encode(utf8.encode('${service.config.username}:${service.config.password}'))}';
+    final basicAuth =
+        'Basic ${base64Encode(utf8.encode('${service.config.username}:${service.config.password}'))}';
     final headers = <String, String>{'Authorization': basicAuth};
-    ref.read(downloadProvider.notifier).startDownload(game, url, headers: headers);
+    ref
+        .read(downloadProvider.notifier)
+        .startDownload(game, url, headers: headers);
     ScaffoldMessenger.of(context).showSnackBar(
       SnackBar(content: Text('Downloading ${game.name}...')),
     );
+
+    final dirService = ref.read(directoryServiceProvider).asData?.value;
+    if (dirService != null) {
+      Future.delayed(const Duration(seconds: 2), () {
+        _refreshDownloadState(dirService, game);
+      });
+    }
+  }
+
+  double _calculateCardHeight(int columnCount, double cardSpacing,
+      double cardAspectRatio, BuildContext context) {
+    final screenWidth = MediaQuery.of(context).size.width;
+    const padding = 24.0;
+    final totalSpacing = cardSpacing * (columnCount - 1);
+    final cardWidth = (screenWidth - padding - totalSpacing) / columnCount;
+    final safeRatio = cardAspectRatio <= 0 ? 0.56 : cardAspectRatio;
+    final coverHeight = cardWidth / safeRatio;
+    final totalHeight = coverHeight + 90.0;
+    return totalHeight.clamp(100.0, 900.0);
+  }
+
+  Widget _buildSkeletonGrid(
+      double cardAspectRatio, int columnCount, double cardSpacing) {
+    return GridView.builder(
+      padding: const EdgeInsets.all(12),
+      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+        crossAxisCount: columnCount,
+        crossAxisSpacing: cardSpacing,
+        mainAxisSpacing: cardSpacing,
+        mainAxisExtent: _calculateCardHeight(
+            columnCount, cardSpacing, cardAspectRatio, context),
+      ),
+      itemCount: 20,
+      itemBuilder: (context, index) {
+        return _SkeletonCard();
+      },
+    );
   }
 
   @override
-  Widget build(BuildContext context, WidgetRef ref) {
+  Widget build(BuildContext context) {
     final platformsAsync = ref.watch(platformsProvider);
     final selectedPlatformId = ref.watch(selectedPlatformIdProvider);
     final searchQuery = ref.watch(searchQueryProvider);
     final gamesAsync = ref.watch(allGamesProvider);
     final filteredGames = ref.watch(filteredGamesProvider);
     final cardAspectRatio = ref.watch(cardAspectRatioProvider);
+    final columnCount = ref.watch(columnCountProvider);
+    final cardSpacing = ref.watch(cardSpacingProvider);
+    final showTitle = ref.watch(showTitleProvider);
+    final showButtonsOnHover = ref.watch(showButtonsOnHoverProvider);
     final rommConfigAsync = ref.watch(rommConfigProvider);
     final directoryServiceAsync = ref.watch(directoryServiceProvider);
 
@@ -240,8 +338,9 @@ class LibraryScreen extends ConsumerWidget {
 
     return Scaffold(
       appBar: AppBar(title: Text(appBarTitle)),
-      body: Column(
-        children: [
+      body: ExcludeSemantics(
+        child: Column(
+          children: [
           Padding(
             padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
             child: TextField(
@@ -270,7 +369,8 @@ class LibraryScreen extends ConsumerWidget {
           ),
           Expanded(
             child: gamesAsync.when(
-              loading: () => const Center(child: CircularProgressIndicator()),
+              loading: () =>
+                  _buildSkeletonGrid(cardAspectRatio, columnCount, cardSpacing),
               error: (e, s) => Center(
                 child: Padding(
                   padding: const EdgeInsets.all(16.0),
@@ -283,14 +383,21 @@ class LibraryScreen extends ConsumerWidget {
               ),
               data: (_) {
                 final gamesCount = filteredGames.length;
-                final countDisplayText = (selectedPlatformId == null && searchQuery.isEmpty)
-                    ? 'Showing all $gamesCount games'
-                    : 'Showing $gamesCount games';
+                final countDisplayText =
+                    (selectedPlatformId == null && searchQuery.isEmpty)
+                        ? 'Showing all $gamesCount games'
+                        : 'Showing $gamesCount games';
+
+                final dirService = directoryServiceAsync.asData?.value;
+                if (dirService != null && !_downloadStatesLoaded) {
+                  final games = ref.read(allGamesProvider).asData?.value ?? [];
+                  _loadDownloadStates(dirService, games);
+                }
 
                 return Column(
                   children: [
                     Padding(
-                      padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
+                      padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 2.0),
                       child: Align(
                         alignment: Alignment.centerRight,
                         child: Text(
@@ -300,49 +407,96 @@ class LibraryScreen extends ConsumerWidget {
                       ),
                     ),
                     Expanded(
-                      child: filteredGames.isEmpty
-                          ? const Center(child: Text('No games found'))
-                          : GridView.builder(
-                              padding: const EdgeInsets.all(12),
-                              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
-                                crossAxisCount: 4,
-                                childAspectRatio: cardAspectRatio,
-                                crossAxisSpacing: 8,
-                                mainAxisSpacing: 8,
-                              ),
-                              itemCount: filteredGames.length,
-                              itemBuilder: (context, index) {
-                                final game = filteredGames[index];
-                                final dirService = directoryServiceAsync.asData?.value;
-                                final isWindowsGame = ['windows', 'pc', 'win'].contains(game.platformSlug?.toLowerCase() ?? '');
-                                if (dirService == null) {
-                                  return GestureDetector(
-                                    onLongPress: isWindowsGame ? () => _handleWindowsConfig(context, ref, game) : null,
-                                    child: GameCard(
-                                      game: game,
-                                      onDownload: () => _startDownload(context, ref, game),
-                                      onLaunch: () => _handleLaunch(context, ref, game),
-                                      onSyncSaves: () => _handleSyncSaves(context, ref, game),
-                                    ),
-                                  );
-                                }
-                                return FutureBuilder<bool>(
-                                  future: dirService.isRomDownloaded(game),
-                                  builder: (context, snapshot) {
+                      child: RefreshIndicator(
+                        onRefresh: () async {
+                          setState(() {
+                            _downloadStatesLoaded = false;
+                            _downloadedStates = {};
+                          });
+                          final prefs = await SharedPreferences.getInstance();
+                          await prefs.remove('cached_games');
+                          await prefs.remove('cached_platforms');
+                          await prefs.remove('cached_games_time');
+                          await prefs.remove('cached_platforms_time');
+                          await prefs.remove('cache_size_exceeded'); // Added this line
+                          ref.invalidate(allGamesProvider);
+                          ref.invalidate(platformsProvider);
+                          await ref.read(allGamesProvider.future);
+                        },
+                        child: filteredGames.isEmpty
+                            ? const CustomScrollView(
+                                slivers: [
+                                  SliverFillRemaining(
+                                    child:
+                                        Center(child: Text('No games found')),
+                                  ),
+                                ],
+                              )
+                            : GridView.builder(
+                                padding: const EdgeInsets.all(12),
+                                gridDelegate:
+                                    SliverGridDelegateWithFixedCrossAxisCount(
+                                  crossAxisCount: columnCount,
+                                  crossAxisSpacing: cardSpacing,
+                                  mainAxisSpacing: cardSpacing,
+                                  mainAxisExtent: _calculateCardHeight(
+                                      columnCount,
+                                      cardSpacing,
+                                      cardAspectRatio,
+                                      context),
+                                ),
+                                itemCount: filteredGames.length,
+                                itemBuilder: (context, index) {
+                                  final game = filteredGames[index];
+                                  final dirService =
+                                      directoryServiceAsync.asData?.value;
+                                  final isWindowsGame = [
+                                    'windows',
+                                    'pc',
+                                    'win'
+                                  ].contains(
+                                      game.platformSlug?.toLowerCase() ?? '');
+                                  if (dirService == null) {
                                     return GestureDetector(
-                                      onLongPress: isWindowsGame ? () => _handleWindowsConfig(context, ref, game) : null,
+                                      onLongPress: isWindowsGame
+                                          ? () => _handleWindowsConfig(
+                                              context, ref, game)
+                                          : null,
                                       child: GameCard(
                                         game: game,
-                                        isDownloaded: snapshot.data ?? false,
-                                        onDownload: () => _startDownload(context, ref, game),
-                                        onLaunch: () => _handleLaunch(context, ref, game),
-                                        onSyncSaves: () => _handleSyncSaves(context, ref, game),
+                                        showTitle: showTitle,
+                                        showButtonsOnHover: showButtonsOnHover,
+                                        onDownload: () =>
+                                            _startDownload(context, ref, game),
+                                        onLaunch: () =>
+                                            _handleLaunch(context, ref, game),
+                                        onSyncSaves: () =>
+                                            _handleSyncSaves(context, ref, game),
                                       ),
                                     );
-                                  },
-                                );
-                              },
-                            ),
+                                  }
+                                  
+                                  return GestureDetector(
+                                    onLongPress: isWindowsGame
+                                        ? () => _handleWindowsConfig(
+                                            context, ref, game)
+                                        : null,
+                                    child: GameCard(
+                                      game: game,
+                                      isDownloaded: _downloadedStates[game.id] ?? false,
+                                      showTitle: showTitle,
+                                      showButtonsOnHover: showButtonsOnHover,
+                                      onDownload: () => _startDownload(
+                                          context, ref, game),
+                                      onLaunch: () =>
+                                          _handleLaunch(context, ref, game),
+                                      onSyncSaves: () => _handleSyncSaves(
+                                          context, ref, game),
+                                    ),
+                                  );
+                                },
+                              ),
+                      ),
                     ),
                   ],
                 );
@@ -351,6 +505,56 @@ class LibraryScreen extends ConsumerWidget {
           ),
         ],
       ),
+      ),
+    );
+  }
+}
+
+class _SkeletonCard extends StatefulWidget {
+  @override
+  State<_SkeletonCard> createState() => _SkeletonCardState();
+}
+
+class _SkeletonCardState extends State<_SkeletonCard>
+    with SingleTickerProviderStateMixin {
+  late AnimationController _controller;
+  late Animation<double> _animation;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(
+      vsync: this,
+      duration: const Duration(milliseconds: 1200),
+    )..repeat(reverse: true);
+    _animation = CurvedAnimation(
+      parent: _controller,
+      curve: Curves.easeInOut,
+    );
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return AnimatedBuilder(
+      animation: _animation,
+      builder: (context, child) {
+        return Container(
+          decoration: BoxDecoration(
+            borderRadius: BorderRadius.circular(8),
+            color: Color.lerp(
+              const Color(0xFF1a1a1a),
+              const Color(0xFF2a2a2a),
+              _animation.value,
+            ),
+          ),
+        );
+      },
     );
   }
 }
diff --git a/lib/ui/screens/settings_screen.dart b/lib/ui/screens/settings_screen.dart
index 7eefcb2..55c92dd 100644
--- a/lib/ui/screens/settings_screen.dart
+++ b/lib/ui/screens/settings_screen.dart
@@ -23,6 +23,8 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
   late TextEditingController _usernameController;
   late TextEditingController _passwordController;
   bool _isSaving = false;
+  Map<String, bool> _emulatorInstallStates = {};
+  bool _emulatorsLoaded = false;
 
   @override
   void initState() {
@@ -40,12 +42,39 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
     super.dispose();
   }
 
+  Future<void> _loadEmulatorStates(DirectoryService directoryService) async {
+    if (_emulatorsLoaded) return;
+    final states = <String, bool>{};
+    for (final def in kEmulatorDefinitions) {
+      final id = def['id'] as String;
+      final exe = def['windows_executable'] as String;
+      if (exe.isEmpty) {
+        states[id] = true;
+        continue;
+      }
+      states[id] = await directoryService.isEmulatorInstalled(id, exe);
+    }
+    if (mounted) {
+      setState(() {
+        _emulatorInstallStates = states;
+        _emulatorsLoaded = true;
+      });
+    }
+  }
+
   @override
   Widget build(BuildContext context) {
     final directoryServiceAsync = ref.watch(directoryServiceProvider);
     final rommService = ref.watch(rommServiceProvider);
     final rommConfigAsync = ref.watch(rommConfigProvider);
 
+    final cardAspectRatio = ref.watch(cardAspectRatioProvider);
+    final columnCount = ref.watch(columnCountProvider);
+    final cardSpacing = ref.watch(cardSpacingProvider);
+    final showTitle = ref.watch(showTitleProvider);
+    final showButtonsOnHover = ref.watch(showButtonsOnHoverProvider);
+    final activePreset = ref.watch(activePresetProvider);
+
     return Scaffold(
       appBar: AppBar(title: const Text('Settings')),
       body: rommConfigAsync.when(
@@ -62,19 +91,32 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
               if (rommService == null) {
                 return const Center(child: CircularProgressIndicator());
               }
-              return ListView(
-                padding: const EdgeInsets.all(16.0),
-                children: [
-                  _buildRommServerSection(context, ref, rommService),
-                  const SizedBox(height: 24),
-                  _buildCardAspectRatioSection(context, ref),
-                  const SizedBox(height: 24),
-                  _buildStorageSection(directoryService),
-                  const SizedBox(height: 24),
-                  _buildRetroArchSyncModeSection(context, ref),
-                  const SizedBox(height: 24),
-                  _buildEmulatorsSection(directoryService),
-                ],
+              
+              _loadEmulatorStates(directoryService);
+
+              return ExcludeSemantics(
+                child: ListView(
+                  padding: const EdgeInsets.all(16.0),
+                  children: [
+                    _buildRommServerSection(context, ref, rommService),
+                    const SizedBox(height: 24),
+                    _buildDisplaySection(
+                      context,
+                      cardAspectRatio,
+                      columnCount,
+                      cardSpacing,
+                      showTitle,
+                      showButtonsOnHover,
+                      activePreset,
+                    ),
+                    const SizedBox(height: 24),
+                    _buildStorageSection(directoryService),
+                    const SizedBox(height: 24),
+                    _buildRetroArchSyncModeSection(context, ref),
+                    const SizedBox(height: 24),
+                    _buildEmulatorsSection(directoryService),
+                  ],
+                ),
               );
             },
             loading: () => const Center(child: CircularProgressIndicator()),
@@ -87,6 +129,161 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
     );
   }
 
+  Widget _buildDisplaySection(
+    BuildContext context,
+    double cardAspectRatio,
+    int columnCount,
+    double cardSpacing,
+    bool showTitle,
+    bool showButtonsOnHover,
+    String activePreset,
+  ) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        const Text('Library Display', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
+        const SizedBox(height: 12),
+        const Text('Presets', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
+        const SizedBox(height: 8),
+        Wrap(
+          spacing: 8,
+          children: [
+            _presetChip('Windows', 'windows_best', activePreset),
+            _presetChip('Steam Deck', 'steamdeck_best', activePreset),
+            _presetChip('Cozy', 'cozy', activePreset),
+            _presetChip('Compact', 'compact', activePreset),
+            _presetChip('Custom', 'custom', activePreset),
+          ],
+        ),
+        const SizedBox(height: 24),
+        Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          children: [
+            const Text('Columns per row', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
+            Text('$columnCount', style: const TextStyle(fontSize: 16, color: Colors.deepPurple)),
+          ],
+        ),
+        Slider(
+          value: columnCount.toDouble(),
+          min: 2,
+          max: 8,
+          divisions: 6,
+          label: '$columnCount',
+          onChanged: (value) async {
+            ref.read(activePresetProvider.notifier).state = 'custom';
+            ref.read(columnCountProvider.notifier).state = value.toInt();
+            final prefs = await SharedPreferences.getInstance();
+            await prefs.setInt('column_count', value.toInt());
+            await prefs.setString('active_preset', 'custom');
+          },
+        ),
+        const SizedBox(height: 16),
+        const Text('Card Shape', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
+        const SizedBox(height: 8),
+        SegmentedButton<double>(
+          segments: const [
+            ButtonSegment(value: 1.0, label: Text('Square')),
+            ButtonSegment(value: 0.72, label: Text('Portrait')),
+            ButtonSegment(value: 0.58, label: Text('Tall')),
+          ],
+          selected: {
+            [1.0, 0.72, 0.58].reduce((a, b) =>
+                (a - cardAspectRatio).abs() < (b - cardAspectRatio).abs()
+                    ? a
+                    : b)
+          },
+          onSelectionChanged: (selection) async {
+            ref.read(activePresetProvider.notifier).state = 'custom';
+            ref.read(cardAspectRatioProvider.notifier).state = selection.first;
+            final prefs = await SharedPreferences.getInstance();
+            await prefs.setDouble('card_aspect_ratio', selection.first);
+            await prefs.setString('active_preset', 'custom');
+          },
+        ),
+        const SizedBox(height: 16),
+        const Text('Card Spacing', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
+        const SizedBox(height: 8),
+        SegmentedButton<double>(
+          segments: const [
+            ButtonSegment(value: 4.0, label: Text('Tight')),
+            ButtonSegment(value: 8.0, label: Text('Normal')),
+            ButtonSegment(value: 12.0, label: Text('Airy')),
+          ],
+          selected: {
+            [4.0, 8.0, 12.0].reduce((a, b) =>
+                (a - cardSpacing).abs() < (b - cardSpacing).abs() ? a : b)
+          },
+          onSelectionChanged: (selection) async {
+            ref.read(activePresetProvider.notifier).state = 'custom';
+            ref.read(cardSpacingProvider.notifier).state = selection.first;
+            final prefs = await SharedPreferences.getInstance();
+            await prefs.setDouble('card_spacing', selection.first);
+            await prefs.setString('active_preset', 'custom');
+          },
+        ),
+        const SizedBox(height: 16),
+        SwitchListTile(
+          title: const Text('Show game title'),
+          subtitle: const Text('Display title text below cover art'),
+          value: showTitle,
+          contentPadding: EdgeInsets.zero,
+          onChanged: (value) async {
+            ref.read(activePresetProvider.notifier).state = 'custom';
+            ref.read(showTitleProvider.notifier).state = value;
+            final prefs = await SharedPreferences.getInstance();
+            await prefs.setBool('show_title', value);
+            await prefs.setString('active_preset', 'custom');
+          },
+        ),
+        SwitchListTile(
+          title: const Text('Show buttons on hover only'),
+          subtitle: const Text('Buttons appear when hovering over a card'),
+          value: showButtonsOnHover,
+          contentPadding: EdgeInsets.zero,
+          onChanged: (value) async {
+            ref.read(activePresetProvider.notifier).state = 'custom';
+            ref.read(showButtonsOnHoverProvider.notifier).state = value;
+            final prefs = await SharedPreferences.getInstance();
+            await prefs.setBool('show_buttons_on_hover', value);
+            await prefs.setString('active_preset', 'custom');
+          },
+        ),
+      ],
+    );
+  }
+
+  Widget _presetChip(String label, String presetKey, String activePreset) {
+    final isSelected = activePreset == presetKey;
+    return FilterChip(
+      label: Text(label),
+      selected: isSelected,
+      onSelected: (selected) async {
+        if (!selected) return;
+        ref.read(activePresetProvider.notifier).state = presetKey;
+        final prefs = await SharedPreferences.getInstance();
+        await prefs.setString('active_preset', presetKey);
+        if (presetKey == 'custom') return;
+        final preset = kDisplayPresets[presetKey];
+        if (preset == null) return;
+        final cols = preset['columnCount'] as int;
+        final ratio = preset['cardAspectRatio'] as double;
+        final spacing = preset['cardSpacing'] as double;
+        final title = preset['showTitle'] as bool;
+        final hover = preset['showButtonsOnHover'] as bool;
+        ref.read(columnCountProvider.notifier).state = cols;
+        ref.read(cardAspectRatioProvider.notifier).state = ratio;
+        ref.read(cardSpacingProvider.notifier).state = spacing;
+        ref.read(showTitleProvider.notifier).state = title;
+        ref.read(showButtonsOnHoverProvider.notifier).state = hover;
+        await prefs.setInt('column_count', cols);
+        await prefs.setDouble('card_aspect_ratio', ratio);
+        await prefs.setDouble('card_spacing', spacing);
+        await prefs.setBool('show_title', title);
+        await prefs.setBool('show_buttons_on_hover', hover);
+      },
+    );
+  }
+
   Widget _buildRetroArchSyncModeSection(BuildContext context, WidgetRef ref) {
     final syncMode = ref.watch(retroarchSyncModeProvider);
     return Column(
@@ -191,16 +388,13 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
                       // it over HTTP or at all, fall back to Basic auth silently.
                       try {
                         await RommService.fetchToken(baseUrl, username, password);
-                        debugPrint('[Settings] fetchToken succeeded');
                       } catch (e) {
-                        debugPrint('[Settings] fetchToken failed ($e), falling back to Basic auth');
                         // Clear any stale token so Basic auth is used instead.
                         final p = await SharedPreferences.getInstance();
                         await p.remove('rommAuthToken');
                       }
 
                       // Save credentials regardless of whether token fetch succeeded.
-                      debugPrint('[Settings] saving baseUrl=$baseUrl user=$username passLen=${password.length}');
                       final prefs = await SharedPreferences.getInstance();
                       await prefs.setString('rommBaseUrl', baseUrl);
                       await prefs.setString('rommUsername', username);
@@ -228,32 +422,6 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
     );
   }
 
-  Widget _buildCardAspectRatioSection(BuildContext context, WidgetRef ref) {
-    final cardAspectRatio = ref.watch(cardAspectRatioProvider);
-    return Column(
-      crossAxisAlignment: CrossAxisAlignment.start,
-      children: [
-        const Text('Library Display', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
-        const SizedBox(height: 12),
-        const Text('Card Aspect Ratio', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
-        const SizedBox(height: 8),
-        SegmentedButton<double>(
-          segments: const [
-            ButtonSegment(value: 0.72, label: Text('Square')),
-            ButtonSegment(value: 0.56, label: Text('Portrait')),
-          ],
-          selected: {cardAspectRatio},
-          onSelectionChanged: (selection) async {
-            final value = selection.first;
-            ref.read(cardAspectRatioProvider.notifier).state = value;
-            final prefs = await SharedPreferences.getInstance();
-            await prefs.setDouble('card_aspect_ratio', value);
-          },
-        ),
-      ],
-    );
-  }
-
   Widget _buildStorageSection(DirectoryService directoryService) {
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
@@ -317,78 +485,81 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
   }
 
   Widget _buildEmulatorsSection(DirectoryService directoryService) {
-    final emulatorDownloadService = EmulatorDownloadService(Dio(), directoryService);
-
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
-        const Text('Emulators', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
+        const Text('Emulators',
+            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
         const SizedBox(height: 16),
-        ...kEmulatorDefinitions.map<Widget>((def) {
-          final emulatorId = def['id'] as String;
-          final emulatorName = def['name'] as String;
-          final windowsExecutable = def['windows_executable'] as String;
-
-          return FutureBuilder<bool>(
-            future: directoryService.isEmulatorInstalled(emulatorId, windowsExecutable),
-            builder: (context, snapshot) {
-              final isInstalled = snapshot.data ?? false;
-              return Padding(
-                padding: const EdgeInsets.symmetric(vertical: 8.0),
-                child: Row(
-                  children: [
-                    Icon(
-                      isInstalled ? Icons.check_circle : Icons.cancel,
-                      color: isInstalled ? Colors.green : Colors.red,
-                    ),
-                    const SizedBox(width: 8),
-                    Expanded(child: Text(emulatorName)),
-                    ElevatedButton(
-                      onPressed: isInstalled
-                          ? null
-                          : () async {
-                              if (mounted) {
-                                ScaffoldMessenger.of(context).showSnackBar(
-                                  SnackBar(content: Text('Starting download for $emulatorName...')),
-                                );
-                              }
-                              try {
-                                await for (var progress in emulatorDownloadService.downloadEmulator(emulatorId)) {
-                                  if (progress.error != null) {
-                                    if (mounted) {
-                                      ScaffoldMessenger.of(context).showSnackBar(
-                                        SnackBar(content: Text('Error downloading $emulatorName: ${progress.error}')),
-                                      );
-                                    }
-                                    break;
-                                  }
-                                  if (progress.isComplete) {
-                                    if (mounted) {
-                                      ScaffoldMessenger.of(context).showSnackBar(
-                                        SnackBar(content: Text('$emulatorName downloaded and extracted.')),
-                                      );
-                                    }
-                                    // ignore: unused_result
-                                    ref.refresh(directoryServiceProvider);
-                                    break;
+        if (!_emulatorsLoaded)
+          const Center(child: CircularProgressIndicator())
+        else
+          ...kEmulatorDefinitions.map<Widget>((def) {
+            final emulatorId = def['id'] as String;
+            final emulatorName = def['name'] as String;
+            final isInstalled = _emulatorInstallStates[emulatorId] ?? false;
+            return Padding(
+              padding: const EdgeInsets.symmetric(vertical: 8.0),
+              child: Row(
+                children: [
+                  Icon(
+                    isInstalled ? Icons.check_circle : Icons.cancel,
+                    color: isInstalled ? Colors.green : Colors.red,
+                  ),
+                  const SizedBox(width: 8),
+                  Expanded(child: Text(emulatorName)),
+                  ElevatedButton(
+                    onPressed: isInstalled
+                        ? null
+                        : () async {
+                            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+                              content:
+                                  Text('Starting download for $emulatorName...'),
+                            ));
+                            final emulatorDownloadService =
+                                EmulatorDownloadService(
+                                    Dio(), directoryService);
+                            try {
+                              await for (final progress in emulatorDownloadService
+                                  .downloadEmulator(emulatorId)) {
+                                if (progress.error != null) {
+                                  if (mounted) {
+                                    ScaffoldMessenger.of(context)
+                                        .showSnackBar(SnackBar(
+                                      content: Text('Error: ${progress.error}'),
+                                    ));
                                   }
+                                  break;
                                 }
-                              } catch (e) {
-                                if (mounted) {
-                                  ScaffoldMessenger.of(context).showSnackBar(
-                                    SnackBar(content: Text('An unexpected error occurred: $e')),
-                                  );
+                                if (progress.isComplete) {
+                                  if (mounted) {
+                                    setState(() {
+                                      _emulatorInstallStates[emulatorId] = true;
+                                    });
+                                    ScaffoldMessenger.of(context)
+                                        .showSnackBar(SnackBar(
+                                      content:
+                                          Text('$emulatorName downloaded.'),
+                                    ));
+                                  }
+                                  break;
                                 }
                               }
-                            },
-                      child: Text(isInstalled ? 'Installed' : 'Download'),
-                    ),
-                  ],
-                ),
-              );
-            },
-          );
-        }),
+                            } catch (e) {
+                              if (mounted) {
+                                ScaffoldMessenger.of(context)
+                                    .showSnackBar(SnackBar(
+                                  content: Text('Unexpected error: $e'),
+                                ));
+                              }
+                            }
+                          },
+                    child: Text(isInstalled ? 'Installed' : 'Download'),
+                  ),
+                ],
+              ),
+            );
+          }),
       ],
     );
   }
diff --git a/lib/ui/widgets/game_card.dart b/lib/ui/widgets/game_card.dart
index a1966a5..e41b224 100644
--- a/lib/ui/widgets/game_card.dart
+++ b/lib/ui/widgets/game_card.dart
@@ -3,12 +3,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
 import '../../core/romm/romm_models.dart';
 import '../../providers/romm_provider.dart';
 
-class GameCard extends ConsumerWidget {
+class GameCard extends ConsumerStatefulWidget {
   final Game game;
   final VoidCallback onDownload;
   final VoidCallback onLaunch;
   final VoidCallback? onSyncSaves;
   final bool isDownloaded;
+  final bool showTitle;
+  final bool showButtonsOnHover;
 
   const GameCard({
     super.key,
@@ -17,109 +19,162 @@ class GameCard extends ConsumerWidget {
     required this.onLaunch,
     this.onSyncSaves,
     this.isDownloaded = false,
+    this.showTitle = true,
+    this.showButtonsOnHover = false,
   });
 
   @override
-  Widget build(BuildContext context, WidgetRef ref) {
+  ConsumerState<GameCard> createState() => _GameCardState();
+}
+
+class _GameCardState extends ConsumerState<GameCard> {
+  bool _isHovering = false;
+
+  @override
+  Widget build(BuildContext context) {
     final service = ref.watch(rommServiceProvider);
-    final finalCoverUrl = service?.resolveCoverUrl(game);
+    final finalCoverUrl = service?.resolveCoverUrl(widget.game);
 
-    return Card(
-      elevation: 2,
-      clipBehavior: Clip.antiAlias,
-      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
-      child: Column(
-        crossAxisAlignment: CrossAxisAlignment.stretch,
-        children: [
-          // Cover image - 75% height approximately
-          Expanded(
-            flex: 75,
-            child: Stack(
-              fit: StackFit.expand,
-              children: [
-                (finalCoverUrl == null || finalCoverUrl.isEmpty)
-                    ? const Center(child: Icon(Icons.sports_esports, size: 48))
-                    : Image.network(
-                        finalCoverUrl,
-                        fit: BoxFit.cover,
-                        errorBuilder: (context, error, stackTrace) => const Center(
-                          child: Icon(Icons.sports_esports, size: 48),
-                        ),
-                      ),
-                if (isDownloaded)
-                  Positioned(
-                    top: 4,
-                    left: 4,
-                    child: Container(
-                      width: 22,
-                      height: 22,
-                      decoration: const BoxDecoration(
-                        color: Colors.green,
-                        shape: BoxShape.circle,
-                      ),
-                      child: const Icon(Icons.check, size: 14, color: Colors.white),
-                    ),
-                  ),
-              ],
-            ),
-          ),
-          // Content - 25% height approximately
-          Expanded(
-            flex: 25,
+    return RepaintBoundary(
+      child: GestureDetector(
+        onLongPress: () => _showContextMenu(context),
+        child: MouseRegion(
+          onEnter: (_) => setState(() => _isHovering = true),
+          onExit: (_) => setState(() => _isHovering = false),
+          child: Card(
+            elevation: 2,
+            clipBehavior: Clip.antiAlias,
+            shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
             child: Column(
-              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              crossAxisAlignment: CrossAxisAlignment.stretch,
               children: [
-                Padding(
-                  padding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0),
-                  child: Text(
-                    game.name,
-                    maxLines: 2,
-                    textAlign: TextAlign.center,
-                    overflow: TextOverflow.ellipsis,
-                    style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
-                  ),
-                ),
-                Padding(
-                  padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0),
-                  child: Row(
-                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+                // Cover image - fills remaining space
+                Expanded(
+                  child: Stack(
+                    fit: StackFit.expand,
                     children: [
-                      IconButton(
-                        visualDensity: VisualDensity.compact,
-                        iconSize: 32,
-                        padding: EdgeInsets.zero,
-                        constraints: const BoxConstraints(),
-                        icon: const Icon(Icons.download),
-                        onPressed: onDownload,
-                        tooltip: 'Download',
-                      ),
-                      IconButton(
-                        visualDensity: VisualDensity.compact,
-                        iconSize: 32,
-                        padding: EdgeInsets.zero,
-                        constraints: const BoxConstraints(),
-                        icon: const Icon(Icons.play_arrow),
-                        onPressed: onLaunch,
-                        tooltip: 'Launch',
-                      ),
-                      if (onSyncSaves != null)
-                        IconButton(
-                          visualDensity: VisualDensity.compact,
-                          iconSize: 24,
-                          padding: EdgeInsets.zero,
-                          constraints: const BoxConstraints(),
-                          icon: const Icon(Icons.cloud_upload),
-                          onPressed: onSyncSaves,
-                          tooltip: 'Sync saves',
+                      (finalCoverUrl == null || finalCoverUrl.isEmpty)
+                          ? const Center(child: Icon(Icons.sports_esports, size: 48))
+                          : Image.network(
+                              finalCoverUrl,
+                              fit: BoxFit.cover,
+                              alignment: Alignment.topCenter,
+                              errorBuilder: (context, error, stackTrace) => const Center(
+                                child: Icon(Icons.sports_esports, size: 48),
+                              ),
+                            ),
+                      if (widget.isDownloaded)
+                        Positioned(
+                          top: 4,
+                          left: 4,
+                          child: Container(
+                            width: 22,
+                            height: 22,
+                            decoration: const BoxDecoration(
+                              color: Colors.green,
+                              shape: BoxShape.circle,
+                            ),
+                            child: const Icon(Icons.check, size: 14, color: Colors.white),
+                          ),
                         ),
                     ],
                   ),
                 ),
+                // Content - fixed height
+                SizedBox(
+                  height: 90,
+                  child: SingleChildScrollView(
+                    physics: const NeverScrollableScrollPhysics(),
+                    child: Column(
+                      mainAxisSize: MainAxisSize.min,
+                      mainAxisAlignment: MainAxisAlignment.center,
+                      children: [
+                        if (!widget.showButtonsOnHover || !_isHovering)
+                          if (widget.showTitle)
+                            Padding(
+                              padding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 1.0),
+                              child: Text(
+                                widget.game.displayName,
+                                maxLines: 2,
+                                textAlign: TextAlign.center,
+                                overflow: TextOverflow.ellipsis,
+                                style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
+                              ),
+                            )
+                          else
+                            const Padding(
+                              padding: EdgeInsets.symmetric(vertical: 1.0),
+                              child: Center(
+                                child: Icon(Icons.more_horiz, size: 16, color: Colors.grey),
+                              ),
+                            ),
+                        
+                        if (!widget.showButtonsOnHover || _isHovering)
+                          Padding(
+                            padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0),
+                            child: Row(
+                              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+                              children: [
+                                IconButton(
+                                  visualDensity: VisualDensity.compact,
+                                  iconSize: 22,
+                                  padding: EdgeInsets.zero,
+                                  constraints: const BoxConstraints(),
+                                  icon: const Icon(Icons.download),
+                                  onPressed: widget.onDownload,
+                                  tooltip: 'Download',
+                                ),
+                                IconButton(
+                                  visualDensity: VisualDensity.compact,
+                                  iconSize: 22,
+                                  padding: EdgeInsets.zero,
+                                  constraints: const BoxConstraints(),
+                                  icon: const Icon(Icons.play_arrow),
+                                  onPressed: widget.onLaunch,
+                                  tooltip: 'Launch',
+                                ),
+                                if (widget.onSyncSaves != null)
+                                  IconButton(
+                                    visualDensity: VisualDensity.compact,
+                                    iconSize: 18,
+                                    padding: EdgeInsets.zero,
+                                    constraints: const BoxConstraints(),
+                                    icon: const Icon(Icons.cloud_upload),
+                                    onPressed: widget.onSyncSaves,
+                                    tooltip: 'Sync saves',
+                                  ),
+                              ],
+                            ),
+                          ),
+                      ],
+                    ),
+                  ),
+                ),
               ],
             ),
           ),
-        ],
+        ),
       ),
     );
   }
+
+  void _showContextMenu(BuildContext context) {
+    showDialog(
+      context: context,
+      builder: (BuildContext dialogContext) {
+        return AlertDialog(
+          title: const Text('Context Menu'),
+          content: const Text('Context menu functionality will be implemented here.'),
+          actions: <Widget>[
+            TextButton(
+              child: const Text('Close'),
+              onPressed: () {
+                Navigator.of(dialogContext).pop();
+              },
+            ),
+          ],
+        );
+      },
+    );
+  }
 }
diff --git a/lib/ui/widgets/platform_filter_bar.dart b/lib/ui/widgets/platform_filter_bar.dart
index dacb1c2..48c0fb5 100644
--- a/lib/ui/widgets/platform_filter_bar.dart
+++ b/lib/ui/widgets/platform_filter_bar.dart
@@ -29,7 +29,7 @@ class PlatformFilterBar extends StatelessWidget {
       ),
       child: SingleChildScrollView(
         scrollDirection: Axis.horizontal,
-        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
         child: Row(
           children: [
             Padding(
diff --git a/test/game_card_test.dart b/test/game_card_test.dart
new file mode 100644
index 0000000..16510e3
--- /dev/null
+++ b/test/game_card_test.dart
@@ -0,0 +1,30 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:freegosy/ui/widgets/game_card.dart';
+import 'package:freegosy/core/romm/romm_models.dart';
+
+void main() {
+  testWidgets('GameCard should render without overflow', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      ProviderScope(
+        child: MaterialApp(
+          home: Scaffold(
+            body: GameCard(
+              game: Game(
+                id: '1',
+                name: 'Test Game',
+                fileSize: 0,
+              ),
+              onDownload: () {},
+              onLaunch: () {},
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(tester.takeException(), isNull);
+    expect(find.byType(GameCard), findsOneWidget);
+  });
+}

Clone this wiki locally