diff --git a/android/gradle.properties b/android/gradle.properties
index ed86f0f26922ea79f4d0c8e0e84f6d74259952bb..3487476dc637023e34426ce50caf3343f91e3038 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,5 +1,5 @@
 org.gradle.jvmargs=-Xmx1536M
 android.useAndroidX=true
 android.enableJetifier=true
-app.versionName=0.0.22
-app.versionCode=22
+app.versionName=0.0.23
+app.versionCode=23
diff --git a/fastlane/metadata/android/en-US/changelogs/23.txt b/fastlane/metadata/android/en-US/changelogs/23.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cf2edcda6ef3e34843d546e8687be049ca3993c8
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/23.txt
@@ -0,0 +1 @@
+Add graphic themes.
diff --git a/fastlane/metadata/android/fr-FR/changelogs/23.txt b/fastlane/metadata/android/fr-FR/changelogs/23.txt
new file mode 100644
index 0000000000000000000000000000000000000000..36ea90e476ed28f45ca08c66cb054f40ba79823b
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/23.txt
@@ -0,0 +1 @@
+Ajout de différents thèmes graphiques.
diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart
index 8d3f8be4e46d5ba48a5c318d677664272a691f3f..d8d7c9513a6bdc3ccf32c4df188466176b5731c7 100644
--- a/lib/config/default_game_settings.dart
+++ b/lib/config/default_game_settings.dart
@@ -38,6 +38,7 @@ class DefaultGameSettings {
         return DefaultGameSettings.allowedColorsCountValues;
     }
 
+    print('Did not find any available value for game parameter \"' + parameterCode + '\".');
     return [];
   }
 
diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart
index 420faf0edd22548df0c7baafc2a07b55b9a54f86..773491a601114e5d5c4a21873d4ae893d7a1c41e 100644
--- a/lib/config/default_global_settings.dart
+++ b/lib/config/default_global_settings.dart
@@ -1,6 +1,7 @@
 class DefaultGlobalSettings {
   static const List<String> availableParameters = [
     'colorsTheme',
+    'graphicTheme',
   ];
 
   static const int defaultColorsThemeValue = 1;
@@ -17,12 +18,49 @@ class DefaultGlobalSettings {
     // 9, // 0x111323,0x374566,0x50785d,0x8497b3,0xe8dcd8,0xcfb463,0xb35447,0x692e4b, // https://lospec.com/palette-list/low-8
   ];
 
+  static const int graphicThemeSolidBackground = 0;
+  static const int graphicThemeGradientAndBorder = 1;
+  static const int graphicThemeEmojis = 2;
+  static const int graphicThemePatterns = 3;
+
+  static const int defaultGraphicThemeValue = graphicThemeSolidBackground;
+  static const List<int> allowedGraphicThemeValues = [
+    graphicThemeSolidBackground,
+    graphicThemeGradientAndBorder,
+    graphicThemeEmojis,
+    graphicThemePatterns,
+  ];
+
+  static const List<String> graphicThemeContentEmojiStrings = [
+    '🍏',
+    '🤍',
+    '🦋',
+    '🐞',
+    '⭐',
+    '🍄',
+    '🍒',
+    '🐤',
+  ];
+  static const List<String> graphicThemeContentPatternStrings = [
+    '✖',
+    '✚',
+    '▲',
+    '■',
+    '●',
+    '◆',
+    '━',
+    '⧧',
+  ];
+
   static List<int> getAvailableValues(String parameterCode) {
     switch (parameterCode) {
       case 'colorsTheme':
         return DefaultGlobalSettings.allowedColorsThemeValues;
+      case 'graphicTheme':
+        return DefaultGlobalSettings.allowedGraphicThemeValues;
     }
 
+    print('Did not find any available value for global parameter \"' + parameterCode + '\".');
     return [];
   }
 }
diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart
index d4a61c8c8f2b95df71eaef47859b9b0dabe0dc3d..94a99a3ca65d2d912eacdfe6eaaa1a3ff67c4586 100644
--- a/lib/cubit/settings_global_cubit.dart
+++ b/lib/cubit/settings_global_cubit.dart
@@ -11,11 +11,13 @@ class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> {
 
   void setValues({
     int? colorsTheme,
+    int? graphicTheme,
   }) {
     emit(
       GlobalSettingsState(
         settings: GlobalSettings(
           colorsTheme: colorsTheme ?? state.settings.colorsTheme,
+          graphicTheme: graphicTheme ?? state.settings.graphicTheme,
         ),
       ),
     );
@@ -25,6 +27,8 @@ class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> {
     switch (code) {
       case 'colorsTheme':
         return GlobalSettings.getColorsThemeValueFromUnsafe(state.settings.colorsTheme);
+      case 'graphicTheme':
+        return GlobalSettings.getGraphicThemeValueFromUnsafe(state.settings.graphicTheme);
     }
     return 0;
   }
@@ -34,19 +38,23 @@ class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> {
     print('code: ' + code + '  / value: ' + value.toString());
 
     int colorsTheme = code == 'colorsTheme' ? value : getParameterValue('colorsTheme');
+    int graphicTheme = code == 'graphicTheme' ? value : getParameterValue('graphicTheme');
 
     setValues(
       colorsTheme: colorsTheme,
+      graphicTheme: graphicTheme,
     );
   }
 
   @override
   GlobalSettingsState? fromJson(Map<String, dynamic> json) {
     int colorsTheme = json['colorsTheme'] as int;
+    int graphicTheme = json['graphicTheme'] as int;
 
     return GlobalSettingsState(
       settings: GlobalSettings(
         colorsTheme: colorsTheme,
+        graphicTheme: graphicTheme,
       ),
     );
   }
@@ -55,6 +63,7 @@ class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> {
   Map<String, dynamic>? toJson(GlobalSettingsState state) {
     return <String, dynamic>{
       'colorsTheme': state.settings.colorsTheme,
+      'graphicTheme': state.settings.graphicTheme,
     };
   }
 }
diff --git a/lib/models/settings_global.dart b/lib/models/settings_global.dart
index 8156993df521018fa50f7522b5ee1d34705e7d90..eac1cf32fe955e0b91309c388d715f28a80b4a9a 100644
--- a/lib/models/settings_global.dart
+++ b/lib/models/settings_global.dart
@@ -2,9 +2,11 @@ import 'package:jeweled/config/default_global_settings.dart';
 
 class GlobalSettings {
   final int colorsTheme;
+  final int graphicTheme;
 
   GlobalSettings({
     required this.colorsTheme,
+    required this.graphicTheme,
   });
 
   static int getColorsThemeValueFromUnsafe(int colorsTheme) {
@@ -15,15 +17,25 @@ class GlobalSettings {
     return DefaultGlobalSettings.defaultColorsThemeValue;
   }
 
+  static int getGraphicThemeValueFromUnsafe(int graphicTheme) {
+    if (DefaultGlobalSettings.allowedGraphicThemeValues.contains(graphicTheme)) {
+      return graphicTheme;
+    }
+
+    return DefaultGlobalSettings.defaultGraphicThemeValue;
+  }
+
   factory GlobalSettings.createDefault() {
     return GlobalSettings(
       colorsTheme: DefaultGlobalSettings.defaultColorsThemeValue,
+      graphicTheme: DefaultGlobalSettings.defaultGraphicThemeValue,
     );
   }
 
   void dump() {
     print('Settings: ');
     print('  colorsTheme: ' + colorsTheme.toString());
+    print('  graphicTheme: ' + graphicTheme.toString());
   }
 
   String toString() {
@@ -33,6 +45,7 @@ class GlobalSettings {
   Map<String, dynamic>? toJson() {
     return <String, dynamic>{
       'colorsTheme': this.colorsTheme,
+      'graphicTheme': this.graphicTheme,
     };
   }
 }
diff --git a/lib/ui/painters/game_board_painter.dart b/lib/ui/painters/game_board_painter.dart
index 52ef2114ecd06e37972392f8d8c9f9bf61416c3a..428dbe8d9b06ee14027a5e4abcfabb0271798e00 100644
--- a/lib/ui/painters/game_board_painter.dart
+++ b/lib/ui/painters/game_board_painter.dart
@@ -1,60 +1,74 @@
 import 'dart:math';
+import 'dart:ui' as ui;
 
 import 'package:flutter/material.dart';
 
+import 'package:jeweled/config/default_global_settings.dart';
 import 'package:jeweled/models/game.dart';
 import 'package:jeweled/models/cell_location.dart';
+import 'package:jeweled/utils/color_extensions.dart';
 import 'package:jeweled/utils/color_theme.dart';
 
 class GameBoardPainter extends CustomPainter {
-  const GameBoardPainter({required this.game, required this.animations});
+  const GameBoardPainter({
+    required this.game,
+    required this.animations,
+  });
 
   final Game game;
   final List<List<Animation<double>?>> animations;
 
   @override
   void paint(Canvas canvas, Size size) {
-    final double canvasSize = min(size.width, size.height);
-
-    final int cellsCountHorizontal = game.gameSettings.boardSize;
-    final int cellsCountVertical = game.gameSettings.boardSize;
-
-    final double cellSize = canvasSize / max(cellsCountHorizontal, cellsCountVertical);
-
-    const double overlapping = 1;
-    const double borderSize = 4;
-
-    // background
-    for (int row = 0; row < cellsCountVertical; row++) {
-      final double yOrigin = cellSize * row;
-      for (int col = 0; col < cellsCountHorizontal; col++) {
-        final double x = cellSize * col;
-
-        final CellLocation cellLocation = CellLocation.go(row, col);
-        final int? cellValue = game.getCellValueShuffled(cellLocation);
-        if (cellValue != null) {
-          final int colorCode =
-              ColorTheme.getColorCode(cellValue, game.globalSettings.colorsTheme);
-
-          final Animation<double>? translation = this.animations[row][col];
-          final double y = yOrigin + (translation?.value ?? 0) * cellSize;
-
-          final cellPaintBackground = Paint();
-          cellPaintBackground.color = Color(colorCode);
-          cellPaintBackground.style = PaintingStyle.fill;
+    int graphicTheme = game.globalSettings.graphicTheme;
 
-          final Rect cellBackground = Rect.fromPoints(
-              Offset(x, y), Offset(x + cellSize + overlapping, y + cellSize + overlapping));
+    final double canvasSize = min(size.width, size.height);
 
-          canvas.drawRect(cellBackground, cellPaintBackground);
-        }
-      }
+    const double cellBorderWidth = 2;
+    const double boardBorderWidth = 3;
+
+    switch (graphicTheme) {
+      case DefaultGlobalSettings.graphicThemeSolidBackground:
+        this.drawBoard(
+          canvas: canvas,
+          canvasSize: canvasSize,
+          game: game,
+        );
+        break;
+      case DefaultGlobalSettings.graphicThemeGradientAndBorder:
+        this.drawBoard(
+          canvas: canvas,
+          canvasSize: canvasSize,
+          game: game,
+          borderWidth: cellBorderWidth,
+          gradientFrom: -10,
+          gradientTo: 10,
+        );
+        break;
+      case DefaultGlobalSettings.graphicThemeEmojis:
+        this.drawBoard(
+          canvas: canvas,
+          canvasSize: canvasSize,
+          game: game,
+          contentStrings: DefaultGlobalSettings.graphicThemeContentEmojiStrings,
+        );
+        break;
+      case DefaultGlobalSettings.graphicThemePatterns:
+        this.drawBoard(
+          canvas: canvas,
+          canvasSize: canvasSize,
+          game: game,
+          contentStrings: DefaultGlobalSettings.graphicThemeContentPatternStrings,
+        );
+        break;
+
+      default:
     }
 
     // board borders
     final boardPaintBorder = Paint();
     boardPaintBorder.color = ColorTheme.getBorderColor();
-    boardPaintBorder.strokeWidth = borderSize;
+    boardPaintBorder.strokeWidth = boardBorderWidth;
     boardPaintBorder.strokeCap = StrokeCap.round;
 
     final Offset boardTopLeft = Offset(0, 0);
@@ -72,4 +86,126 @@ class GameBoardPainter extends CustomPainter {
   bool shouldRepaint(CustomPainter oldDelegate) {
     return true;
   }
+
+  void drawBoard({
+    required Canvas canvas,
+    required double canvasSize,
+    required Game game,
+    double overlapping = 1,
+    int gradientFrom = 0,
+    int gradientTo = 0,
+    double borderWidth = 0,
+    List<String>? contentStrings,
+  }) {
+    final int cellsCountHorizontal = game.gameSettings.boardSize;
+    final int cellsCountVertical = game.gameSettings.boardSize;
+    final int colorsTheme = game.globalSettings.colorsTheme;
+
+    final double size = canvasSize / max(cellsCountHorizontal, cellsCountVertical);
+
+    for (int row = 0; row < cellsCountVertical; row++) {
+      final double yOrigin = size * row;
+      for (int col = 0; col < cellsCountHorizontal; col++) {
+        final double x = size * col;
+
+        final CellLocation cellLocation = CellLocation.go(row, col);
+        final int? cellValue = game.getCellValueShuffled(cellLocation);
+        if (cellValue != null) {
+          final Animation<double>? translation = this.animations[row][col];
+          final double y = yOrigin + (translation?.value ?? 0) * size;
+
+          this.drawCell(
+            canvas: canvas,
+            x: x,
+            y: y,
+            cellSize: size,
+            cellValue: cellValue,
+            colorsTheme: colorsTheme,
+            overlapping: overlapping,
+            gradientFrom: gradientFrom,
+            gradientTo: gradientTo,
+            borderWidth: borderWidth,
+            contentStrings: contentStrings,
+          );
+        }
+      }
+    }
+  }
+
+  void drawCell({
+    required Canvas canvas,
+    required double x,
+    required double y,
+    required double cellSize,
+    required int cellValue,
+    required int colorsTheme,
+    required double overlapping,
+    required int gradientFrom,
+    required int gradientTo,
+    required double borderWidth,
+    required List<String>? contentStrings,
+  }) {
+    final Color baseColor = ColorTheme.getColor(cellValue, colorsTheme);
+
+    final paint = Paint();
+
+    // draw background
+    final Rect rect = Rect.fromLTWH(x, y, cellSize + overlapping, cellSize + overlapping);
+
+    // solid or gradient
+    if (gradientFrom == 0 && gradientTo == 0) {
+      paint.color = baseColor;
+    } else {
+      paint.shader = ui.Gradient.linear(
+        rect.topLeft,
+        rect.bottomCenter,
+        [
+          (gradientFrom < 0)
+              ? baseColor.lighten(-gradientFrom)
+              : baseColor.darken(gradientFrom),
+          (gradientTo < 0) ? baseColor.lighten(-gradientTo) : baseColor.darken(gradientTo),
+        ],
+      );
+    }
+    paint.style = PaintingStyle.fill;
+    canvas.drawRect(rect, paint);
+
+    // draw border
+    if (borderWidth != 0) {
+      final Rect border = Rect.fromLTWH(x + borderWidth, y + borderWidth,
+          cellSize + overlapping - 2 * borderWidth, cellSize + overlapping - 2 * borderWidth);
+
+      final paintBorder = Paint();
+      paintBorder.color = baseColor.darken(20);
+      paintBorder.style = PaintingStyle.stroke;
+
+      paintBorder.strokeWidth = borderWidth;
+      paintBorder.strokeJoin = StrokeJoin.round;
+      canvas.drawRect(border, paintBorder);
+    }
+
+    // draw content
+    if (contentStrings != null) {
+      final textSpan = TextSpan(
+        text: contentStrings[cellValue - 1],
+        style: TextStyle(
+          color: Colors.black,
+          fontSize: 4 + cellSize / 2,
+          fontWeight: FontWeight.bold,
+        ),
+      );
+      final textPainter = TextPainter(
+        text: textSpan,
+        textDirection: TextDirection.ltr,
+      );
+      textPainter.layout();
+      textPainter.paint(
+        canvas,
+        Offset(
+          x + (cellSize - textPainter.width) * 0.5,
+          y + (cellSize - textPainter.height) * 0.5,
+        ),
+      );
+    }
+  }
 }
diff --git a/lib/ui/painters/parameter_painter.dart b/lib/ui/painters/parameter_painter.dart
index 9bbfd47c691e7e32b81ec91e565af00fdc73660f..775e306d55006714379f4459f5bfbdebe148706c 100644
--- a/lib/ui/painters/parameter_painter.dart
+++ b/lib/ui/painters/parameter_painter.dart
@@ -1,8 +1,10 @@
 import 'dart:math';
+import 'dart:ui' as ui;
 
 import 'package:flutter/material.dart';
 
 import 'package:jeweled/config/default_game_settings.dart';
+import 'package:jeweled/config/default_global_settings.dart';
 import 'package:jeweled/models/settings_game.dart';
 import 'package:jeweled/models/settings_global.dart';
 import 'package:jeweled/utils/color_extensions.dart';
@@ -50,6 +52,9 @@ class ParameterPainter extends CustomPainter {
       case 'colorsTheme':
         paintColorsThemeParameterItem(value, canvas, canvasSize);
         break;
+      case 'graphicTheme':
+        paintGraphicThemeParameterItem(value, canvas, canvasSize);
+        break;
       default:
         print('Unknown parameter: ' + code + '/' + value.toString());
         paintUnknownParameterItem(value, canvas, canvasSize);
@@ -62,7 +67,19 @@ class ParameterPainter extends CustomPainter {
   }
 
   // "unknown" parameter -> simple bock with text
-  void paintUnknownParameterItem(final int value, final Canvas canvas, final double size) {
+  void paintUnknownParameterItem(
+    final int value,
+    final Canvas canvas,
+    final double size,
+  ) {
+    final paint = Paint();
+    paint.strokeJoin = StrokeJoin.round;
+    paint.strokeWidth = 3 / 100 * size;
+
+    paint.color = Colors.grey;
+    paint.style = PaintingStyle.fill;
+    canvas.drawRect(Rect.fromPoints(Offset(0, 0), Offset(size, size)), paint);
+
     final textSpan = TextSpan(
       text: '?' + '\n' + value.toString(),
       style: const TextStyle(
@@ -85,7 +102,11 @@ class ParameterPainter extends CustomPainter {
     );
   }
 
-  void paintBoardSizeParameterItem(final int value, final Canvas canvas, final double size) {
+  void paintBoardSizeParameterItem(
+    final int value,
+    final Canvas canvas,
+    final double size,
+  ) {
     Color backgroundColor = Colors.grey;
     int gridWidth = 1;
 
@@ -144,7 +165,11 @@ class ParameterPainter extends CustomPainter {
     }
   }
 
-  void paintColorsCountParameterItem(final int value, final Canvas canvas, final double size) {
+  void paintColorsCountParameterItem(
+    final int value,
+    final Canvas canvas,
+    final double size,
+  ) {
     Color backgroundColor = Colors.grey;
 
     switch (value) {
@@ -232,7 +257,11 @@ class ParameterPainter extends CustomPainter {
     );
   }
 
-  void paintColorsThemeParameterItem(final int value, final Canvas canvas, final double size) {
+  void paintColorsThemeParameterItem(
+    final int value,
+    final Canvas canvas,
+    final double size,
+  ) {
     Color backgroundColor = Colors.grey;
     const int gridWidth = 4;
 
@@ -268,4 +297,116 @@ class ParameterPainter extends CustomPainter {
       }
     }
   }
+
+  void paintGraphicThemeParameterItem(
+    final int value,
+    final Canvas canvas,
+    final double size,
+  ) {
+    Color backgroundColor = Colors.grey;
+
+    final paint = Paint();
+    paint.strokeJoin = StrokeJoin.round;
+    paint.strokeWidth = 3 / 100 * size;
+
+    // Colored background
+    paint.color = backgroundColor;
+    paint.style = PaintingStyle.fill;
+    canvas.drawRect(Rect.fromPoints(Offset(0, 0), Offset(size, size)), paint);
+
+    // cells preview
+    const List<Offset> positions = [
+      Offset(0, 0),
+      Offset(1, 0),
+      Offset(2, 0),
+      Offset(2, 1),
+      Offset(2, 2),
+      Offset(1, 2),
+      Offset(0, 2),
+      Offset(0, 1),
+    ];
+
+    final double padding = 5 / 100 * size;
+    final double width = (size - 2 * padding) / 3;
+
+    bool drawBorder = false;
+    bool gradientColor = false;
+    List<String> contentStrings = [];
+
+    switch (value) {
+      case DefaultGlobalSettings.graphicThemeSolidBackground:
+        break;
+      case DefaultGlobalSettings.graphicThemeGradientAndBorder:
+        drawBorder = true;
+        gradientColor = true;
+        break;
+      case DefaultGlobalSettings.graphicThemeEmojis:
+        contentStrings = DefaultGlobalSettings.graphicThemeContentEmojiStrings;
+        break;
+      case DefaultGlobalSettings.graphicThemePatterns:
+        contentStrings = DefaultGlobalSettings.graphicThemeContentPatternStrings;
+        break;
+      default:
+        print('Wrong value for colorsCount parameter value: ' + value.toString());
+    }
+
+    for (int itemValue = 0; itemValue < positions.length; itemValue++) {
+      final Offset position = positions[itemValue];
+
+      final Offset topLeft =
+          Offset(padding + position.dx * width, padding + position.dy * width);
+      final Offset bottomRight = topLeft + Offset(width, width);
+
+      final Rect itemBox = Rect.fromPoints(topLeft, bottomRight);
+      final itemColor = ColorTheme.getColor(itemValue, globalSettings.colorsTheme);
+
+      paint.color = itemColor;
+      paint.style = PaintingStyle.fill;
+      canvas.drawRect(itemBox, paint);
+
+      // gradient background
+      if (gradientColor) {
+        paint.shader = ui.Gradient.linear(itemBox.topLeft, itemBox.bottomCenter, [
+          itemColor.lighten(10),
+          itemColor.darken(10),
+        ]);
+        paint.style = PaintingStyle.fill;
+        canvas.drawRect(itemBox, paint);
+      }
+
+      // cell border
+      if (drawBorder) {
+        final paintBorder = Paint();
+        paintBorder.color = itemColor.darken(20);
+        paintBorder.style = PaintingStyle.stroke;
+        paintBorder.strokeWidth = 2;
+
+        canvas.drawRect(itemBox, paintBorder);
+      }
+
+      // centered text value
+      if (contentStrings.length != 0) {
+        final textSpan = TextSpan(
+          text: contentStrings[itemValue],
+          style: TextStyle(
+            color: Colors.black,
+            fontSize: width / 2,
+            fontWeight: FontWeight.bold,
+          ),
+        );
+        final textPainter = TextPainter(
+          text: textSpan,
+          textDirection: TextDirection.ltr,
+        );
+        textPainter.layout();
+        textPainter.paint(
+          canvas,
+          Offset(
+            topLeft.dx + (width - textPainter.width) * 0.5,
+            topLeft.dy + (width - textPainter.height) * 0.5,
+          ),
+        );
+      }
+    }
+  }
 }
diff --git a/lib/utils/color_theme.dart b/lib/utils/color_theme.dart
index 8579cbda7f81765b26eb99ba150f0a6e162ed2bb..86369215493c9cc01c0d639da0a4bd191dac5474 100644
--- a/lib/utils/color_theme.dart
+++ b/lib/utils/color_theme.dart
@@ -89,6 +89,10 @@ class ColorTheme {
     return defaultItemColor | 0xFF000000;
   }
 
+  static Color getColor(int? value, final int skin) {
+    return Color(getColorCode(value, skin));
+  }
+
   static Color getBorderColor() {
     return Colors.grey;
   }
diff --git a/pubspec.yaml b/pubspec.yaml
index dc067a44485505be72ce42a6bc0376409c87ab62..2f2f682b18806a5824607d9488e6982ed54b5b8e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@ description: Jeweled Game
 
 publish_to: 'none'
 
-version: 0.0.22+22
+version: 0.0.23+23
 
 environment:
   sdk: '^3.0.0'