diff --git a/android/app/build.gradle b/android/app/build.gradle
index 789da53f79d4f06086315ff99a2bb67df1e0c778..26a3890d2622f80fd5ad86f3c5b9d1eab8a03ce5 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -37,7 +37,7 @@ if (keystorePropertiesFile.exists()) {
 }
 
 android {
-    compileSdkVersion 33
+    compileSdkVersion 34
     namespace "org.benoitharrault.puzzlegame"
 
     defaultConfig {
diff --git a/android/gradle.properties b/android/gradle.properties
index bc12805202e72c604bb3162b9bf117c69fcc8db2..9b96205e6609eb50043f1f335634fdf02ccc3108 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.61
-app.versionCode=61
+app.versionName=0.1.0
+app.versionCode=62
diff --git a/assets/files/images.json b/assets/files/images.json
deleted file mode 100644
index c0c454503034f01b0792b3875f552a85c3d8335b..0000000000000000000000000000000000000000
--- a/assets/files/images.json
+++ /dev/null
@@ -1,103 +0,0 @@
-{
-  "images": [
-    "00_default_08b2107e3c30da02a6c18613a1e90857",
-    "00_default_3bcda1b5fb7adcdac1824dde11060462",
-    "00_default_3c245519d89fefd792d05b72631ef8af",
-    "00_default_494b2e821e8e7130088ec2929bf49be8",
-    "animals_0824b924323459fb8e8a90054443c77b",
-    "animals_0a86f77291858c10ca6dcc631ba14f96",
-    "animals_36c401aebe4293803710b05a08d6a248",
-    "animals_5cb4af89a40d60e8839a43e915d15a2f",
-    "animals_60f53fdf0217001c7a89f48f243f2aa9",
-    "animals_677d788f482bffdd7f79c525aae93832",
-    "animals_76baeb891edc3f20814fcda6de541c8b",
-    "animals_815079fbba4a96c699b25fca11cf303e",
-    "animals_83f2c647801c57722f5582687a95071b",
-    "animals_8786f3eced6b176537608b50398b464d",
-    "animals_88c3071723a3d93b9d581313e2b25c65",
-    "animals_8cb1144c6afa8ccd37871a1c237e7c3a",
-    "animals_9b0b293a1f2393d46e2d8f7ca841e652",
-    "animals_9bd7055c69b6471477bc67e508a0ec7a",
-    "animals_a42fb9cddd68b019a1e6216f01d94742",
-    "animals_d3f924d97c3d6bb735a27c0e4edfcd7c",
-    "animals_e937f221caaa87337c1dc98002e13c56",
-    "animals_f05f46e9ea9f18d47fbd7733a608536b",
-    "animals_fc0a31d2c32467fdda711ffd521e79d9",
-    "anime_aa293fb70ed59c7b610f0e6e218a0917",
-    "anime_f48648cbbff8370f18bdc592d98bb563",
-    "dinosaurs_61c5d5727722d957ffa39b7cc5519bf6",
-    "dinosaurs_d2fafe9651b48459a72aa15cfeac3b61",
-    "ghibli_2c6fd10faee15c612ebfe71628069d36",
-    "ghibli_914deb9acd1f1ab2a50ec9f7924eb799",
-    "ghibli_b2c74d93a2c32f2ccbda73c4bee03b00",
-    "ghibli_f2798b83ce4d01142bdcd5d8803f5412",
-    "harrypotter_439fe7c78ebbf945de2eb18f2f78f7a8",
-    "harrypotter_d3664e7e2920eb8cee5b69982f710644",
-    "nature_38a414b6051a9b528fedc47878663c03",
-    "nature_98574287a17f734d52dc64dfc0f45fc8",
-    "nature_c0e131e9a2a813c337965ddfb413baf8",
-    "personal_0694cfbc9bb292f9da8c7c65d793a378",
-    "personal_0a0b014b37ddb90d9af1fcdedccef37d",
-    "personal_0d973affb8ffe7d0abe9ddd89957f580",
-    "personal_0ea16afc573d649222e512306872106f",
-    "personal_12381e1ed7bed4ec56c5d871a8b21a5d",
-    "personal_183bcd7c31fdece731a373ed375fd227",
-    "personal_1b98afe55b410ed03ea8b3f18fa5cc27",
-    "personal_22169df124ce293fbd519eb6d987a9b7",
-    "personal_259dd16880502cbc8fbbff87cee60a24",
-    "personal_29244801ee5399bc5a445f9c9f1e5487",
-    "personal_2f9d874fbd41462a6c115022e088f12e",
-    "personal_31320d5c42d2b0e81a3aa365af7bbd9d",
-    "personal_3b2274b4265177568057601aef1a4e87",
-    "personal_3cdb7de70fb4052ac849a6a5fa990eee",
-    "personal_3debd2d0b3b968d5a895d776bc8d7e03",
-    "personal_43f57a142340066e3137434a43b6cbbb",
-    "personal_4936d509c25ceaf585c94067f4f9061b",
-    "personal_4afcce1786d0265f45ca083b07bd407b",
-    "personal_4ee0e7589f440418d41e7e8283c6bcef",
-    "personal_4fe95f154947c4efdf9a35045b47e137",
-    "personal_548f73139e968e67d9737393e9cba794",
-    "personal_58e4fa6bdbfea985511936074d40a5e1",
-    "personal_5a2ec8b6ee98b400ae87207fcace71e3",
-    "personal_5b6e5f629039ff65178bac0b9d979185",
-    "personal_626e4843a78dbcc43e49bce849bc82cc",
-    "personal_642d854a9d4252d78faeb392b09ff04d",
-    "personal_6761903f2186e75e74fcbde92a4c6865",
-    "personal_67def669a778cd84299235188df223f9",
-    "personal_6baedd08c35004321ac26effade8221e",
-    "personal_6f28c333f1100e7d2cb316ba839a59b9",
-    "personal_70d72351d07f93710539592d8dd38d8a",
-    "personal_7d7e4fb5727fc23ab3fa7b40f8769fc1",
-    "personal_7ea40aebbbde03cbbcc19e5f92609b77",
-    "personal_84f358d1b64d7947617d85b21ab2375a",
-    "personal_90deb4cb0578338c442f268d6a24d5dc",
-    "personal_96b82ec73f9d83066297fa30a5b4384f",
-    "personal_9e44f127b4ec68efad8ce87da1d3fc51",
-    "personal_a6819fc32906ac2ca4a0f47503772307",
-    "personal_b27ef14b2c835df1ae1aeae938823193",
-    "personal_b50323b0dc6ce5fb9796adb80b814cd8",
-    "personal_b78c59c08302100769e3469fccff36c1",
-    "personal_b87fcb24c5d44e1328cddd66b75bc119",
-    "personal_b89b9d4366696fe1623b5b5d4f10f0fc",
-    "personal_b8d2facf43700d6f4e6c230c548e771b",
-    "personal_b961b8d3386fa525ac754eea04b32003",
-    "personal_bf4520a9582da41ecc280365afebb30f",
-    "personal_c0eb4eb4d39d58eecebc3218a56f6148",
-    "personal_c132137e54f68136af709edd6a8f602d",
-    "personal_c72571b7f7ac673b63754699e1b8c31c",
-    "personal_d0d1e8e150782b694d663b6cc838cf61",
-    "personal_d5d163c09ae4da219a7ae54b5a98b830",
-    "personal_d5f3483ee2a8598123a0d0a9a965aea0",
-    "personal_daf7dab4c768da753dede24093cef6c3",
-    "personal_e124afc5e5b3770e53f058431d37bf31",
-    "personal_e9da5393d39bebb0a5d393b4c55366a5",
-    "personal_efc2a3bf7597c3991afa800c4515a515",
-    "personal_f3991aee0215111c5e4c13994477e85b",
-    "personal_f4059dfc787363cc3437be52a47779a0",
-    "personal_fb47095d9556e3e7f00711afe128d5b2",
-    "sea_f2c615363f892ba270dae2fceb3a06e9",
-    "sea_f64def749dd7133248814a902ea140ab",
-    "space_438d8689bcc7e7cafd89a97d336d7981",
-    ""
-  ]
-}
diff --git a/assets/fonts/Nunito-Bold.ttf b/assets/fonts/Nunito-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..6519feb781449ebe0015cbc74dfd9e13110fbba9
Binary files /dev/null and b/assets/fonts/Nunito-Bold.ttf differ
diff --git a/assets/fonts/Nunito-Light.ttf b/assets/fonts/Nunito-Light.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..8a0736c41cd6c2a1225d356bf274de1d0afc3497
Binary files /dev/null and b/assets/fonts/Nunito-Light.ttf differ
diff --git a/assets/fonts/Nunito-Medium.ttf b/assets/fonts/Nunito-Medium.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..88fccdc0638b6f5d6ac49d9d269dc3d518618ad1
Binary files /dev/null and b/assets/fonts/Nunito-Medium.ttf differ
diff --git a/assets/fonts/Nunito-Regular.ttf b/assets/fonts/Nunito-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..e7b8375a896ef0cd8e06730a78c84532b377e784
Binary files /dev/null and b/assets/fonts/Nunito-Regular.ttf differ
diff --git a/assets/icons/difficulty_3x3.png b/assets/icons/difficulty_3x3.png
deleted file mode 100644
index 9893a1c3d32dc5e9818d0b4da793268833e740ee..0000000000000000000000000000000000000000
Binary files a/assets/icons/difficulty_3x3.png and /dev/null differ
diff --git a/assets/icons/difficulty_4x4.png b/assets/icons/difficulty_4x4.png
deleted file mode 100644
index 57c99a774f4910ade3d46af33d7ce204b36562ac..0000000000000000000000000000000000000000
Binary files a/assets/icons/difficulty_4x4.png and /dev/null differ
diff --git a/assets/icons/difficulty_5x5.png b/assets/icons/difficulty_5x5.png
deleted file mode 100644
index 11e087ec80f029539736637ddacbcd4ccb24ee72..0000000000000000000000000000000000000000
Binary files a/assets/icons/difficulty_5x5.png and /dev/null differ
diff --git a/assets/images/placeholder.png b/assets/images/placeholder.png
deleted file mode 100644
index 725353a203906d2188d8c31cd4ad86bfd280c252..0000000000000000000000000000000000000000
Binary files a/assets/images/placeholder.png and /dev/null differ
diff --git a/assets/translations/en.json b/assets/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..7c987554a0784ef90fd559894b614c4f8855a791
--- /dev/null
+++ b/assets/translations/en.json
@@ -0,0 +1,12 @@
+{
+  "app_name": "Jigsaw puzzle",
+
+  "settings_title": "Settings",
+  "settings_label_theme": "Theme mode",
+
+  "about_title": "Informations",
+  "about_content": "Jigsaw puzzle",
+  "about_version": "Version: {version}",
+
+  "": ""
+}
diff --git a/assets/translations/fr.json b/assets/translations/fr.json
new file mode 100644
index 0000000000000000000000000000000000000000..70581d6a87ccf7a95f62d1abfefe721f0f7b52ca
--- /dev/null
+++ b/assets/translations/fr.json
@@ -0,0 +1,12 @@
+{
+  "app_name": "Puzzle",
+
+  "settings_title": "Réglages",
+  "settings_label_theme": "Thème de couleurs",
+
+  "about_title": "Informations",
+  "about_content": "Puzzle.",
+  "about_version": "Version : {version}",
+
+  "": ""
+}
diff --git a/assets/icons/button_back.png b/assets/ui/button_back.png
similarity index 100%
rename from assets/icons/button_back.png
rename to assets/ui/button_back.png
diff --git a/assets/ui/button_delete_saved_game.png b/assets/ui/button_delete_saved_game.png
new file mode 100644
index 0000000000000000000000000000000000000000..5e4f217689b11e444b7163557d7e5d68f3bbfe7d
Binary files /dev/null and b/assets/ui/button_delete_saved_game.png differ
diff --git a/assets/icons/button_random_pick.png b/assets/ui/button_random_pick.png
similarity index 100%
rename from assets/icons/button_random_pick.png
rename to assets/ui/button_random_pick.png
diff --git a/assets/ui/button_resume_game.png b/assets/ui/button_resume_game.png
new file mode 100644
index 0000000000000000000000000000000000000000..b2ea0a02d05e42377eb551a4b51428b511a32f5d
Binary files /dev/null and b/assets/ui/button_resume_game.png differ
diff --git a/assets/icons/button_shuffle.png b/assets/ui/button_shuffle.png
similarity index 100%
rename from assets/icons/button_shuffle.png
rename to assets/ui/button_shuffle.png
diff --git a/assets/ui/button_start.png b/assets/ui/button_start.png
new file mode 100644
index 0000000000000000000000000000000000000000..6845e2f5c21598ab61f1684d2075aeec0334bf23
Binary files /dev/null and b/assets/ui/button_start.png differ
diff --git a/assets/icons/game_win.png b/assets/ui/game_win.png
similarity index 100%
rename from assets/icons/game_win.png
rename to assets/ui/game_win.png
diff --git a/assets/icons/placeholder.png b/assets/ui/placeholder.png
similarity index 100%
rename from assets/icons/placeholder.png
rename to assets/ui/placeholder.png
diff --git a/assets/icons/tip_hidden.png b/assets/ui/tip_hidden.png
similarity index 100%
rename from assets/icons/tip_hidden.png
rename to assets/ui/tip_hidden.png
diff --git a/fastlane/metadata/android/en-US/changelogs/62.txt b/fastlane/metadata/android/en-US/changelogs/62.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d4afd512e55b3fd8ffbfd795adb9b00832e5aaef
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/62.txt
@@ -0,0 +1 @@
+Improve/normalize game architecture.
diff --git a/fastlane/metadata/android/fr-FR/changelogs/62.txt b/fastlane/metadata/android/fr-FR/changelogs/62.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6a9871a5eb8eb3c6e9106520f1cbf1f39f9e5ef7
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/62.txt
@@ -0,0 +1 @@
+Amélioration/normalisation de l'architecture du jeu.
diff --git a/icons/difficulty_3x3.svg b/icons/difficulty_3x3.svg
deleted file mode 100644
index 9806d6450e14e7ea2c3c01e5842c13e820816e3f..0000000000000000000000000000000000000000
--- a/icons/difficulty_3x3.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="#009d21"/><g transform="matrix(1.1542 0 0 1.1542 -7.5435 -7.7088)" fill="#f9f9f9" stroke="#005c0c" stroke-width="2" aria-label="3x3"><path d="m19.443 43.965-7.5195-1.3477q0.9375-3.5938 3.5938-5.5078 2.6758-1.9141 7.5586-1.9141 5.6055 0 8.1055 2.0898t2.5 5.2539q0 1.8555-1.0156 3.3594t-3.0664 2.6367q1.6602 0.41016 2.5391 0.95703 1.4258 0.87891 2.207 2.3242 0.80078 1.4258 0.80078 3.418 0 2.5-1.3086 4.8047-1.3086 2.2852-3.7695 3.5352-2.4609 1.2305-6.4648 1.2305-3.9062 0-6.1719-0.91797-2.2461-0.91797-3.7109-2.6758-1.4453-1.7773-2.2266-4.4531l7.9492-1.0547q0.46875 2.4023 1.4453 3.3398 0.99609 0.91797 2.5195 0.91797 1.6016 0 2.6562-1.1719 1.0742-1.1719 1.0742-3.125 0-1.9922-1.0352-3.0859-1.0156-1.0938-2.7734-1.0938-0.9375 0-2.5781 0.46875l0.41016-5.6836q0.66406 0.09766 1.0352 0.09766 1.5625 0 2.5977-0.99609 1.0547-0.99609 1.0547-2.3633 0-1.3086-0.78125-2.0898t-2.1484-0.78125q-1.4062 0-2.2852 0.85938-0.87891 0.83984-1.1914 2.9688z"/><path d="m37.373 43.574h9.4336l3.3008 5.7812 3.8281-5.7812h8.7695l-7.0703 9.8828 7.5781 10.859h-9.2773l-3.8281-6.6797-4.5117 6.6797h-8.6133l7.5391-10.859z"/><path d="m72.803 43.965-7.5195-1.3477q0.9375-3.5938 3.5938-5.5078 2.6758-1.9141 7.5586-1.9141 5.6055 0 8.1055 2.0898t2.5 5.2539q0 1.8555-1.0156 3.3594t-3.0664 2.6367q1.6602 0.41016 2.5391 0.95703 1.4258 0.87891 2.207 2.3242 0.80078 1.4258 0.80078 3.418 0 2.5-1.3086 4.8047-1.3086 2.2852-3.7695 3.5352-2.4609 1.2305-6.4648 1.2305-3.9062 0-6.1719-0.91797-2.2461-0.91797-3.7109-2.6758-1.4453-1.7773-2.2266-4.4531l7.9492-1.0547q0.46875 2.4023 1.4453 3.3398 0.99609 0.91797 2.5195 0.91797 1.6016 0 2.6562-1.1719 1.0742-1.1719 1.0742-3.125 0-1.9922-1.0352-3.0859-1.0156-1.0938-2.7734-1.0938-0.9375 0-2.5781 0.46875l0.41016-5.6836q0.66406 0.09766 1.0352 0.09766 1.5625 0 2.5977-0.99609 1.0547-0.99609 1.0547-2.3633 0-1.3086-0.78125-2.0898t-2.1484-0.78125q-1.4062 0-2.2852 0.85938-0.87891 0.83984-1.1914 2.9688z"/></g></svg>
diff --git a/icons/difficulty_4x4.svg b/icons/difficulty_4x4.svg
deleted file mode 100644
index 9d30392df74c6b32a119ce67fa14528a8c440a7a..0000000000000000000000000000000000000000
--- a/icons/difficulty_4x4.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="#eeb517"/><g transform="matrix(1.1547 0 0 1.1547 -7.7373 -7.7368)" fill="#fff" stroke="#969400" stroke-width="2" aria-label="4x4"><path d="m25.303 59.209h-14.492v-6.543l14.492-17.227h6.9336v17.598h3.5938v6.1719h-3.5938v5.3516h-6.9336zm0-6.1719v-9.0039l-7.6562 9.0039z"/><path d="m37.275 43.818h9.4336l3.3008 5.7812 3.8281-5.7812h8.7695l-7.0703 9.8828 7.5781 10.859h-9.2773l-3.8281-6.6797-4.5117 6.6797h-8.6133l7.5391-10.859z"/><path d="m78.662 59.209h-14.492v-6.543l14.492-17.227h6.9336v17.598h3.5938v6.1719h-3.5938v5.3516h-6.9336zm0-6.1719v-9.0039l-7.6562 9.0039z"/></g></svg>
diff --git a/icons/difficulty_5x5.svg b/icons/difficulty_5x5.svg
deleted file mode 100644
index 93595012b5e8c6c41aff690d4b44d9542338e0e9..0000000000000000000000000000000000000000
--- a/icons/difficulty_5x5.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="#d31158"/><g transform="matrix(1.1387 0 0 1.1387 -6.7678 -6.9338)" fill="#fff" stroke="#700934" stroke-width="2" aria-label="5x5"><path d="m14.971 35.439h18.887v6.3477h-12.793l-0.68359 4.2969q1.3281-0.625 2.6172-0.9375 1.3086-0.3125 2.5781-0.3125 4.2969 0 6.9727 2.5977t2.6758 6.543q0 2.7734-1.3867 5.332-1.3672 2.5586-3.9062 3.9062-2.5195 1.3477-6.4648 1.3477-2.832 0-4.8633-0.52734-2.0117-0.54688-3.4375-1.6016-1.4062-1.0742-2.2852-2.4219-0.87891-1.3477-1.4648-3.3594l8.0469-0.87891q0.29297 1.9336 1.3672 2.9492 1.0742 0.99609 2.5586 0.99609 1.6602 0 2.7344-1.25 1.0938-1.2695 1.0938-3.7695 0-2.5586-1.0938-3.75t-2.9102-1.1914q-1.1523 0-2.2266 0.56641-0.80078 0.41016-1.7578 1.4844l-6.7773-0.97656z"/><path d="m37.393 43.33h9.4336l3.3008 5.7812 3.8281-5.7812h8.7695l-7.0703 9.8828 7.5781 10.859h-9.2773l-3.8281-6.6797-4.5117 6.6797h-8.6133l7.5391-10.859z"/><path d="m68.33 35.439h18.887v6.3477h-12.793l-0.68359 4.2969q1.3281-0.625 2.6172-0.9375 1.3086-0.3125 2.5781-0.3125 4.2969 0 6.9727 2.5977t2.6758 6.543q0 2.7734-1.3867 5.332-1.3672 2.5586-3.9062 3.9062-2.5195 1.3477-6.4648 1.3477-2.832 0-4.8633-0.52734-2.0117-0.54688-3.4375-1.6016-1.4062-1.0742-2.2852-2.4219-0.87891-1.3477-1.4648-3.3594l8.0469-0.87891q0.29297 1.9336 1.3672 2.9492 1.0742 0.99609 2.5586 0.99609 1.6602 0 2.7344-1.25 1.0938-1.2695 1.0938-3.7695 0-2.5586-1.0938-3.75t-2.9102-1.1914q-1.1523 0-2.2266 0.56641-0.80078 0.41016-1.7578 1.4844l-6.7773-0.97656z"/></g></svg>
diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b18afd1aaeeb28be070a1b76f7ce7d184565bb1f
--- /dev/null
+++ b/lib/config/default_game_settings.dart
@@ -0,0 +1,49 @@
+import 'package:puzzlegame/utils/tools.dart';
+
+class DefaultGameSettings {
+  // available game parameters codes
+  static const String parameterCodeTilesetSize = 'tilesetSize';
+  static const String parameterCodeImageName = 'imageName';
+  static const List<String> availableParameters = [
+    parameterCodeTilesetSize,
+    parameterCodeImageName,
+  ];
+
+  // tileset size: available values
+  static const String tilesetSizeValueSmall = '3x3';
+  static const String tilesetSizeValueMedium = '4x4';
+  static const String tilesetSizeValueLarge = '5x5';
+  static const List<String> allowedTilesetSizeValues = [
+    tilesetSizeValueSmall,
+    tilesetSizeValueMedium,
+    tilesetSizeValueLarge,
+  ];
+  // tileset size: default value
+  static const String defaultTilesetSizeValue = tilesetSizeValueMedium;
+
+  // image name: available values
+  static const String imageNameRandomlyPicked = 'random';
+  static const List<String> allowedImageNameS = [
+    imageNameRandomlyPicked,
+  ];
+  // image name: default value
+  static const String defaultImageName = imageNameRandomlyPicked;
+
+  // available values from parameter code
+  static List<String> getAvailableValues(String parameterCode) {
+    switch (parameterCode) {
+      case parameterCodeTilesetSize:
+        return DefaultGameSettings.allowedTilesetSizeValues;
+      case parameterCodeImageName:
+        return DefaultGameSettings.allowedImageNameS;
+    }
+
+    printlog('Did not find any available value for game parameter "$parameterCode".');
+    return [];
+  }
+
+  // parameters displayed with assets (instead of painter)
+  static List<String> displayedWithAssets = [
+    //
+  ];
+}
diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart
new file mode 100644
index 0000000000000000000000000000000000000000..dac4541466fd2fafb7eaaf3c016a8bc92b20d589
--- /dev/null
+++ b/lib/config/default_global_settings.dart
@@ -0,0 +1,33 @@
+import 'package:puzzlegame/utils/tools.dart';
+
+class DefaultGlobalSettings {
+  // available global parameters codes
+  static const String parameterCodeSkin = 'skin';
+  static const List<String> availableParameters = [
+    parameterCodeSkin,
+  ];
+
+  // skin: available values
+  static const String skinValueDefault = 'default';
+  static const List<String> allowedSkinValues = [
+    skinValueDefault,
+  ];
+  // skin: default value
+  static const String defaultSkinValue = skinValueDefault;
+
+  // available values from parameter code
+  static List<String> getAvailableValues(String parameterCode) {
+    switch (parameterCode) {
+      case parameterCodeSkin:
+        return DefaultGlobalSettings.allowedSkinValues;
+    }
+
+    printlog('Did not find any available value for global parameter "$parameterCode".');
+    return [];
+  }
+
+  // parameters displayed with assets (instead of painter)
+  static List<String> displayedWithAssets = [
+    //
+  ];
+}
diff --git a/lib/config/menu.dart b/lib/config/menu.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b68e1deabe3b96fb41da4c28cc9b98f95228e576
--- /dev/null
+++ b/lib/config/menu.dart
@@ -0,0 +1,52 @@
+import 'package:flutter/material.dart';
+import 'package:unicons/unicons.dart';
+
+import 'package:puzzlegame/ui/screens/page_about.dart';
+import 'package:puzzlegame/ui/screens/page_game.dart';
+import 'package:puzzlegame/ui/screens/page_settings.dart';
+
+class MenuItem {
+  final Icon icon;
+  final Widget page;
+
+  const MenuItem({
+    required this.icon,
+    required this.page,
+  });
+}
+
+class Menu {
+  static const indexGame = 0;
+  static const menuItemGame = MenuItem(
+    icon: Icon(UniconsLine.home),
+    page: PageGame(),
+  );
+
+  static const indexSettings = 1;
+  static const menuItemSettings = MenuItem(
+    icon: Icon(UniconsLine.setting),
+    page: PageSettings(),
+  );
+
+  static const indexAbout = 2;
+  static const menuItemAbout = MenuItem(
+    icon: Icon(UniconsLine.info_circle),
+    page: PageAbout(),
+  );
+
+  static Map<int, MenuItem> items = {
+    indexGame: menuItemGame,
+    indexSettings: menuItemSettings,
+    indexAbout: menuItemAbout,
+  };
+
+  static bool isIndexAllowed(int pageIndex) {
+    return items.keys.contains(pageIndex);
+  }
+
+  static Widget getPageWidget(int pageIndex) {
+    return items[pageIndex]?.page ?? menuItemGame.page;
+  }
+
+  static int itemsCount = Menu.items.length;
+}
diff --git a/lib/config/theme.dart b/lib/config/theme.dart
new file mode 100644
index 0000000000000000000000000000000000000000..74f532fd5abf693979118609564d29167e902009
--- /dev/null
+++ b/lib/config/theme.dart
@@ -0,0 +1,190 @@
+import 'package:flutter/material.dart';
+
+/// Colors from Tailwind CSS (v3.0) - June 2022
+///
+/// https://tailwindcss.com/docs/customizing-colors
+
+const int _primaryColor = 0xFF6366F1;
+const MaterialColor primarySwatch = MaterialColor(_primaryColor, <int, Color>{
+  50: Color(0xFFEEF2FF), // indigo-50
+  100: Color(0xFFE0E7FF), // indigo-100
+  200: Color(0xFFC7D2FE), // indigo-200
+  300: Color(0xFFA5B4FC), // indigo-300
+  400: Color(0xFF818CF8), // indigo-400
+  500: Color(_primaryColor), // indigo-500
+  600: Color(0xFF4F46E5), // indigo-600
+  700: Color(0xFF4338CA), // indigo-700
+  800: Color(0xFF3730A3), // indigo-800
+  900: Color(0xFF312E81), // indigo-900
+});
+
+const int _textColor = 0xFF64748B;
+const MaterialColor textSwatch = MaterialColor(_textColor, <int, Color>{
+  50: Color(0xFFF8FAFC), // slate-50
+  100: Color(0xFFF1F5F9), // slate-100
+  200: Color(0xFFE2E8F0), // slate-200
+  300: Color(0xFFCBD5E1), // slate-300
+  400: Color(0xFF94A3B8), // slate-400
+  500: Color(_textColor), // slate-500
+  600: Color(0xFF475569), // slate-600
+  700: Color(0xFF334155), // slate-700
+  800: Color(0xFF1E293B), // slate-800
+  900: Color(0xFF0F172A), // slate-900
+});
+
+const Color errorColor = Color(0xFFDC2626); // red-600
+
+final ColorScheme lightColorScheme = ColorScheme.light(
+  primary: primarySwatch.shade500,
+  secondary: primarySwatch.shade500,
+  onSecondary: Colors.white,
+  error: errorColor,
+  onSurface: textSwatch.shade500,
+  surface: textSwatch.shade50,
+  surfaceContainerHighest: Colors.white,
+  shadow: textSwatch.shade900.withOpacity(.1),
+);
+
+final ColorScheme darkColorScheme = ColorScheme.dark(
+  primary: primarySwatch.shade500,
+  secondary: primarySwatch.shade500,
+  onSecondary: Colors.white,
+  error: errorColor,
+  onSurface: textSwatch.shade300,
+  surface: const Color(0xFF262630),
+  surfaceContainerHighest: const Color(0xFF282832),
+  shadow: textSwatch.shade900.withOpacity(.2),
+);
+
+final ThemeData lightTheme = ThemeData(
+  colorScheme: lightColorScheme,
+  fontFamily: 'Nunito',
+  textTheme: TextTheme(
+    displayLarge: TextStyle(
+      color: textSwatch.shade700,
+      fontFamily: 'Nunito',
+    ),
+    displayMedium: TextStyle(
+      color: textSwatch.shade600,
+      fontFamily: 'Nunito',
+    ),
+    displaySmall: TextStyle(
+      color: textSwatch.shade500,
+      fontFamily: 'Nunito',
+    ),
+    headlineLarge: TextStyle(
+      color: textSwatch.shade700,
+      fontFamily: 'Nunito',
+    ),
+    headlineMedium: TextStyle(
+      color: textSwatch.shade600,
+      fontFamily: 'Nunito',
+    ),
+    headlineSmall: TextStyle(
+      color: textSwatch.shade500,
+      fontFamily: 'Nunito',
+    ),
+    titleLarge: TextStyle(
+      color: textSwatch.shade700,
+      fontFamily: 'Nunito',
+    ),
+    titleMedium: TextStyle(
+      color: textSwatch.shade600,
+      fontFamily: 'Nunito',
+    ),
+    titleSmall: TextStyle(
+      color: textSwatch.shade500,
+      fontFamily: 'Nunito',
+    ),
+    bodyLarge: TextStyle(
+      color: textSwatch.shade700,
+      fontFamily: 'Nunito',
+    ),
+    bodyMedium: TextStyle(
+      color: textSwatch.shade600,
+      fontFamily: 'Nunito',
+    ),
+    bodySmall: TextStyle(
+      color: textSwatch.shade500,
+      fontFamily: 'Nunito',
+    ),
+    labelLarge: TextStyle(
+      color: textSwatch.shade700,
+      fontFamily: 'Nunito',
+    ),
+    labelMedium: TextStyle(
+      color: textSwatch.shade600,
+      fontFamily: 'Nunito',
+    ),
+    labelSmall: TextStyle(
+      color: textSwatch.shade500,
+      fontFamily: 'Nunito',
+    ),
+  ),
+);
+
+final ThemeData darkTheme = lightTheme.copyWith(
+  colorScheme: darkColorScheme,
+  textTheme: TextTheme(
+    displayLarge: TextStyle(
+      color: textSwatch.shade200,
+      fontFamily: 'Nunito',
+    ),
+    displayMedium: TextStyle(
+      color: textSwatch.shade300,
+      fontFamily: 'Nunito',
+    ),
+    displaySmall: TextStyle(
+      color: textSwatch.shade400,
+      fontFamily: 'Nunito',
+    ),
+    headlineLarge: TextStyle(
+      color: textSwatch.shade200,
+      fontFamily: 'Nunito',
+    ),
+    headlineMedium: TextStyle(
+      color: textSwatch.shade300,
+      fontFamily: 'Nunito',
+    ),
+    headlineSmall: TextStyle(
+      color: textSwatch.shade400,
+      fontFamily: 'Nunito',
+    ),
+    titleLarge: TextStyle(
+      color: textSwatch.shade200,
+      fontFamily: 'Nunito',
+    ),
+    titleMedium: TextStyle(
+      color: textSwatch.shade300,
+      fontFamily: 'Nunito',
+    ),
+    titleSmall: TextStyle(
+      color: textSwatch.shade400,
+      fontFamily: 'Nunito',
+    ),
+    bodyLarge: TextStyle(
+      color: textSwatch.shade200,
+      fontFamily: 'Nunito',
+    ),
+    bodyMedium: TextStyle(
+      color: textSwatch.shade300,
+      fontFamily: 'Nunito',
+    ),
+    bodySmall: TextStyle(
+      color: textSwatch.shade400,
+      fontFamily: 'Nunito',
+    ),
+    labelLarge: TextStyle(
+      color: textSwatch.shade200,
+      fontFamily: 'Nunito',
+    ),
+    labelMedium: TextStyle(
+      color: textSwatch.shade300,
+      fontFamily: 'Nunito',
+    ),
+    labelSmall: TextStyle(
+      color: textSwatch.shade400,
+      fontFamily: 'Nunito',
+    ),
+  ),
+);
diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..41f8b274a7c22fc3f23e6066740afce820629c4e
--- /dev/null
+++ b/lib/cubit/game_cubit.dart
@@ -0,0 +1,244 @@
+import 'dart:async';
+import 'dart:math';
+import 'dart:typed_data';
+
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart' show rootBundle;
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+import 'package:image/image.dart' as imglib;
+
+import 'package:puzzlegame/models/game/game.dart';
+import 'package:puzzlegame/models/game/moving_tile.dart';
+import 'package:puzzlegame/models/settings/settings_game.dart';
+import 'package:puzzlegame/models/settings/settings_global.dart';
+
+part 'game_state.dart';
+
+class GameCubit extends HydratedCubit<GameState> {
+  GameCubit()
+      : super(GameState(
+          currentGame: Game.createEmpty(),
+        ));
+
+  void updateState(Game game) {
+    emit(GameState(
+      currentGame: game,
+    ));
+  }
+
+  void refresh() {
+    final Game game = Game(
+      // Settings
+      gameSettings: state.currentGame.gameSettings,
+      globalSettings: state.currentGame.globalSettings,
+      // State
+      isRunning: state.currentGame.isRunning,
+      isStarted: state.currentGame.isStarted,
+      isFinished: state.currentGame.isFinished,
+      animationInProgress: state.currentGame.animationInProgress,
+      shufflingInProgress: state.currentGame.shufflingInProgress,
+      // Base data
+      image: state.currentGame.image,
+      tiles: state.currentGame.tiles,
+      // Game data
+      movesCount: state.currentGame.movesCount,
+      displayTip: state.currentGame.displayTip,
+      tileSize: state.currentGame.tileSize,
+    );
+    // game.dump();
+
+    updateState(game);
+  }
+
+  void startNewGame({
+    required GameSettings gameSettings,
+    required GlobalSettings globalSettings,
+  }) {
+    final Game newGame = Game.createNew(
+      // Settings
+      gameSettings: gameSettings,
+      globalSettings: globalSettings,
+    );
+
+    newGame.dump();
+
+    updateState(newGame);
+    refresh();
+
+    state.currentGame.isRunning = true;
+    state.currentGame.shufflingInProgress = true;
+    refresh();
+
+    Timer(const Duration(seconds: 1), () {
+      splitImageInTiles();
+    });
+  }
+
+  void quitGame() {
+    state.currentGame.isRunning = false;
+    refresh();
+  }
+
+  void resumeSavedGame() {
+    state.currentGame.isRunning = true;
+    refresh();
+  }
+
+  void deleteSavedGame() {
+    state.currentGame.isRunning = false;
+    state.currentGame.isFinished = true;
+    refresh();
+  }
+
+  void updateTileSize(double tileSize) {
+    if (tileSize != state.currentGame.tileSize) {
+      state.currentGame.tileSize = tileSize;
+      for (var i = 0; i < state.currentGame.tiles.length; i++) {
+        state.currentGame.tiles[i].size = tileSize;
+      }
+      refresh();
+    }
+  }
+
+  void toggleDisplayTipImage() {
+    state.currentGame.displayTip = !state.currentGame.displayTip;
+    refresh();
+  }
+
+  void incrementMovesCount() {
+    state.currentGame.movesCount++;
+    refresh();
+  }
+
+  bool checkTilesetIsCleared() {
+    for (MovingTile tile in state.currentGame.tiles) {
+      if (!tile.isCorrect()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  void swapTiles(List<int> tile1, List<int> tile2) {
+    state.currentGame.isStarted = true;
+
+    final int indexTile1 = state.currentGame.tiles.indexWhere(
+        (tile) => ((tile.currentCol == tile1[0]) && (tile.currentRow == tile1[1])));
+    final int indexTile2 = state.currentGame.tiles.indexWhere(
+        (tile) => ((tile.currentCol == tile2[0]) && (tile.currentRow == tile2[1])));
+
+    final MovingTile swap = state.currentGame.tiles[indexTile1];
+    state.currentGame.tiles[indexTile1] = state.currentGame.tiles[indexTile2];
+    state.currentGame.tiles[indexTile2] = swap;
+
+    final int swapCol = state.currentGame.tiles[indexTile1].currentCol;
+    state.currentGame.tiles[indexTile1].currentCol =
+        state.currentGame.tiles[indexTile2].currentCol;
+    state.currentGame.tiles[indexTile2].currentCol = swapCol;
+
+    final int swapRow = state.currentGame.tiles[indexTile1].currentRow;
+    state.currentGame.tiles[indexTile1].currentRow =
+        state.currentGame.tiles[indexTile2].currentRow;
+    state.currentGame.tiles[indexTile2].currentRow = swapRow;
+
+    incrementMovesCount();
+    if (checkTilesetIsCleared()) {
+      state.currentGame.isFinished = true;
+    }
+
+    refresh();
+  }
+
+  Future<void> splitImageInTiles() async {
+    final String imageAsset = 'assets/images/${state.currentGame.image}.png';
+    final Uint8List imageData = (await rootBundle.load(imageAsset)).buffer.asUint8List();
+
+    final int tilesCount = state.currentGame.gameSettings.tilesetSizeValue;
+
+    final imglib.Image image = imglib.decodeImage(imageData) ??
+        imglib.Image.fromBytes(
+          height: 1,
+          width: 1,
+          bytes: Uint8List.fromList([]).buffer,
+        );
+
+    int x = 0, y = 0;
+    final int width = (image.width / tilesCount).round();
+    final int height = (image.height / tilesCount).round();
+
+    final List<MovingTile> tiles = [];
+    for (int i = 0; i < tilesCount; i++) {
+      for (int j = 0; j < tilesCount; j++) {
+        final Uint8List tileData = Uint8List.fromList(imglib.encodeJpg(imglib.copyCrop(
+          image,
+          x: x,
+          y: y,
+          width: width,
+          height: height,
+        )));
+
+        tiles.add(MovingTile(
+          currentCol: j,
+          currentRow: i,
+          image: Image.memory(tileData),
+          size: state.currentGame.tileSize,
+          originalCol: j,
+          originalRow: i,
+        ));
+
+        x += width;
+      }
+      x = 0;
+      y += height;
+    }
+
+    state.currentGame.tiles = tiles;
+    shuffleTiles();
+
+    state.currentGame.shufflingInProgress = false;
+    refresh();
+  }
+
+  void shuffleTiles() {
+    final Random random = Random();
+
+    final List<MovingTile> tiles = state.currentGame.tiles;
+    final int tilesCount = tiles.length;
+
+    for (int i = 0; i < (10 * tilesCount); i++) {
+      final int indexTile1 = random.nextInt(tilesCount);
+      final int indexTile2 = random.nextInt(tilesCount);
+
+      final MovingTile swap = tiles[indexTile1];
+      tiles[indexTile1] = tiles[indexTile2];
+      tiles[indexTile2] = swap;
+
+      final int swapCol = tiles[indexTile1].currentCol;
+      tiles[indexTile1].currentCol = tiles[indexTile2].currentCol;
+      tiles[indexTile2].currentCol = swapCol;
+
+      final int swapRow = tiles[indexTile1].currentRow;
+      tiles[indexTile1].currentRow = tiles[indexTile2].currentRow;
+      tiles[indexTile2].currentRow = swapRow;
+    }
+
+    state.currentGame.tiles = tiles;
+  }
+
+  @override
+  GameState? fromJson(Map<String, dynamic> json) {
+    final Game currentGame = json['currentGame'] as Game;
+
+    return GameState(
+      currentGame: currentGame,
+    );
+  }
+
+  @override
+  Map<String, dynamic>? toJson(GameState state) {
+    return <String, dynamic>{
+      'currentGame': state.currentGame.toJson(),
+    };
+  }
+}
diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..00e211668c3269255926939324355792abd61c41
--- /dev/null
+++ b/lib/cubit/game_state.dart
@@ -0,0 +1,15 @@
+part of 'game_cubit.dart';
+
+@immutable
+class GameState extends Equatable {
+  const GameState({
+    required this.currentGame,
+  });
+
+  final Game currentGame;
+
+  @override
+  List<dynamic> get props => <dynamic>[
+        currentGame,
+      ];
+}
diff --git a/lib/cubit/nav_cubit.dart b/lib/cubit/nav_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..6f2839c50daad5736723a285304ad879ad995105
--- /dev/null
+++ b/lib/cubit/nav_cubit.dart
@@ -0,0 +1,37 @@
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+import 'package:puzzlegame/config/menu.dart';
+
+class NavCubit extends HydratedCubit<int> {
+  NavCubit() : super(0);
+
+  void updateIndex(int index) {
+    if (Menu.isIndexAllowed(index)) {
+      emit(index);
+    } else {
+      goToGamePage();
+    }
+  }
+
+  void goToGamePage() {
+    emit(Menu.indexGame);
+  }
+
+  void goToSettingsPage() {
+    emit(Menu.indexSettings);
+  }
+
+  void goToAboutPage() {
+    emit(Menu.indexAbout);
+  }
+
+  @override
+  int fromJson(Map<String, dynamic> json) {
+    return Menu.indexGame;
+  }
+
+  @override
+  Map<String, dynamic>? toJson(int state) {
+    return <String, int>{'pageIndex': state};
+  }
+}
diff --git a/lib/cubit/settings_game_cubit.dart b/lib/cubit/settings_game_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..0cc90f3d61d60474f837324cb83d1a0038175f71
--- /dev/null
+++ b/lib/cubit/settings_game_cubit.dart
@@ -0,0 +1,72 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+import 'package:puzzlegame/config/default_game_settings.dart';
+import 'package:puzzlegame/models/settings/settings_game.dart';
+
+part 'settings_game_state.dart';
+
+class GameSettingsCubit extends HydratedCubit<GameSettingsState> {
+  GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault()));
+
+  void setValues({
+    String? tilesetSize,
+    String? imageName,
+  }) {
+    emit(
+      GameSettingsState(
+        settings: GameSettings(
+          tilesetSize: tilesetSize ?? state.settings.tilesetSize,
+          imageName: imageName ?? state.settings.imageName,
+        ),
+      ),
+    );
+  }
+
+  String getParameterValue(String code) {
+    switch (code) {
+      case DefaultGameSettings.parameterCodeTilesetSize:
+        return GameSettings.getTilesetSizeValueFromUnsafe(state.settings.tilesetSize);
+      case DefaultGameSettings.parameterCodeImageName:
+        return GameSettings.getImageNameFromUnsafe(state.settings.imageName);
+    }
+
+    return '';
+  }
+
+  void setParameterValue(String code, String value) {
+    final String tilesetSize = code == DefaultGameSettings.parameterCodeTilesetSize
+        ? value
+        : getParameterValue(DefaultGameSettings.parameterCodeTilesetSize);
+    final String imageName = code == DefaultGameSettings.parameterCodeImageName
+        ? value
+        : getParameterValue(DefaultGameSettings.parameterCodeImageName);
+
+    setValues(
+      tilesetSize: tilesetSize,
+      imageName: imageName,
+    );
+  }
+
+  @override
+  GameSettingsState? fromJson(Map<String, dynamic> json) {
+    final String tilesetSize = json[DefaultGameSettings.parameterCodeTilesetSize] as String;
+    final String imageName = json[DefaultGameSettings.parameterCodeImageName] as String;
+
+    return GameSettingsState(
+      settings: GameSettings(
+        tilesetSize: tilesetSize,
+        imageName: imageName,
+      ),
+    );
+  }
+
+  @override
+  Map<String, dynamic>? toJson(GameSettingsState state) {
+    return <String, dynamic>{
+      DefaultGameSettings.parameterCodeTilesetSize: state.settings.tilesetSize,
+      DefaultGameSettings.parameterCodeImageName: state.settings.imageName,
+    };
+  }
+}
diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..5acd85b44ba541e1c5e9c26af1c4be26a385b9ed
--- /dev/null
+++ b/lib/cubit/settings_game_state.dart
@@ -0,0 +1,15 @@
+part of 'settings_game_cubit.dart';
+
+@immutable
+class GameSettingsState extends Equatable {
+  const GameSettingsState({
+    required this.settings,
+  });
+
+  final GameSettings settings;
+
+  @override
+  List<dynamic> get props => <dynamic>[
+        settings,
+      ];
+}
diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..aab5e8a44addc47c2426d229c866b62e35c6f47a
--- /dev/null
+++ b/lib/cubit/settings_global_cubit.dart
@@ -0,0 +1,60 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+import 'package:puzzlegame/config/default_global_settings.dart';
+import 'package:puzzlegame/models/settings/settings_global.dart';
+
+part 'settings_global_state.dart';
+
+class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> {
+  GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault()));
+
+  void setValues({
+    String? skin,
+  }) {
+    emit(
+      GlobalSettingsState(
+        settings: GlobalSettings(
+          skin: skin ?? state.settings.skin,
+        ),
+      ),
+    );
+  }
+
+  String getParameterValue(String code) {
+    switch (code) {
+      case DefaultGlobalSettings.parameterCodeSkin:
+        return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin);
+    }
+    return '';
+  }
+
+  void setParameterValue(String code, String value) {
+    final String skin = (code == DefaultGlobalSettings.parameterCodeSkin)
+        ? value
+        : getParameterValue(DefaultGlobalSettings.parameterCodeSkin);
+
+    setValues(
+      skin: skin,
+    );
+  }
+
+  @override
+  GlobalSettingsState? fromJson(Map<String, dynamic> json) {
+    final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String;
+
+    return GlobalSettingsState(
+      settings: GlobalSettings(
+        skin: skin,
+      ),
+    );
+  }
+
+  @override
+  Map<String, dynamic>? toJson(GlobalSettingsState state) {
+    return <String, dynamic>{
+      DefaultGlobalSettings.parameterCodeSkin: state.settings.skin,
+    };
+  }
+}
diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..ebcddd700f252257223ca8e16c85202b04f3ff24
--- /dev/null
+++ b/lib/cubit/settings_global_state.dart
@@ -0,0 +1,15 @@
+part of 'settings_global_cubit.dart';
+
+@immutable
+class GlobalSettingsState extends Equatable {
+  const GlobalSettingsState({
+    required this.settings,
+  });
+
+  final GlobalSettings settings;
+
+  @override
+  List<dynamic> get props => <dynamic>[
+        settings,
+      ];
+}
diff --git a/lib/cubit/theme_cubit.dart b/lib/cubit/theme_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b793e895dbb0c672d451cd403e0036c3d9ac9b42
--- /dev/null
+++ b/lib/cubit/theme_cubit.dart
@@ -0,0 +1,31 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+part 'theme_state.dart';
+
+class ThemeCubit extends HydratedCubit<ThemeModeState> {
+  ThemeCubit() : super(const ThemeModeState());
+
+  void getTheme(ThemeModeState state) {
+    emit(state);
+  }
+
+  @override
+  ThemeModeState? fromJson(Map<String, dynamic> json) {
+    switch (json['themeMode']) {
+      case 'ThemeMode.dark':
+        return const ThemeModeState(themeMode: ThemeMode.dark);
+      case 'ThemeMode.light':
+        return const ThemeModeState(themeMode: ThemeMode.light);
+      case 'ThemeMode.system':
+      default:
+        return const ThemeModeState(themeMode: ThemeMode.system);
+    }
+  }
+
+  @override
+  Map<String, String>? toJson(ThemeModeState state) {
+    return <String, String>{'themeMode': state.themeMode.toString()};
+  }
+}
diff --git a/lib/cubit/theme_state.dart b/lib/cubit/theme_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e479a50f12fe72a35a1fd1722ff72afbb692a136
--- /dev/null
+++ b/lib/cubit/theme_state.dart
@@ -0,0 +1,15 @@
+part of 'theme_cubit.dart';
+
+@immutable
+class ThemeModeState extends Equatable {
+  const ThemeModeState({
+    this.themeMode,
+  });
+
+  final ThemeMode? themeMode;
+
+  @override
+  List<Object?> get props => <Object?>[
+        themeMode,
+      ];
+}
diff --git a/lib/data/fetch_data_helper.dart b/lib/data/fetch_data_helper.dart
new file mode 100644
index 0000000000000000000000000000000000000000..33c50c0d6834b245fdd110f8bb0dee0b230f2f4a
--- /dev/null
+++ b/lib/data/fetch_data_helper.dart
@@ -0,0 +1,39 @@
+import 'package:puzzlegame/data/game_data.dart';
+import 'package:puzzlegame/utils/tools.dart';
+
+class FetchDataHelper {
+  FetchDataHelper();
+
+  final List<String> _images = [];
+  List<String> get images => _images;
+
+  void init() {
+    try {
+      final List<String> rawImages = GameData.data['images'] as List<String>;
+      for (var imageCode in rawImages) {
+        _images.add(imageCode.toString());
+      }
+    } catch (e) {
+      printlog("$e");
+    }
+  }
+
+  List<String> getRandomItems(int count) {
+    if (_images.isEmpty) {
+      init();
+    }
+
+    List<String> shuffleableList = [];
+    for (var i = 0; i < _images.length; i++) {
+      shuffleableList.add(_images[i]);
+    }
+
+    shuffleableList.shuffle();
+
+    return shuffleableList.sublist(0, count);
+  }
+
+  String getRandomItem() {
+    return getRandomItems(1).first;
+  }
+}
diff --git a/lib/data/game_data.dart b/lib/data/game_data.dart
new file mode 100644
index 0000000000000000000000000000000000000000..d6979485be40240ad41c92df21b180f29f3cebb8
--- /dev/null
+++ b/lib/data/game_data.dart
@@ -0,0 +1,104 @@
+class GameData {
+  static const Map<String, dynamic> data = {
+    "images": [
+      "00_default_08b2107e3c30da02a6c18613a1e90857",
+      "00_default_3bcda1b5fb7adcdac1824dde11060462",
+      "00_default_3c245519d89fefd792d05b72631ef8af",
+      "00_default_494b2e821e8e7130088ec2929bf49be8",
+      "animals_0824b924323459fb8e8a90054443c77b",
+      "animals_0a86f77291858c10ca6dcc631ba14f96",
+      "animals_36c401aebe4293803710b05a08d6a248",
+      "animals_5cb4af89a40d60e8839a43e915d15a2f",
+      "animals_60f53fdf0217001c7a89f48f243f2aa9",
+      "animals_677d788f482bffdd7f79c525aae93832",
+      "animals_76baeb891edc3f20814fcda6de541c8b",
+      "animals_815079fbba4a96c699b25fca11cf303e",
+      "animals_83f2c647801c57722f5582687a95071b",
+      "animals_8786f3eced6b176537608b50398b464d",
+      "animals_88c3071723a3d93b9d581313e2b25c65",
+      "animals_8cb1144c6afa8ccd37871a1c237e7c3a",
+      "animals_9b0b293a1f2393d46e2d8f7ca841e652",
+      "animals_9bd7055c69b6471477bc67e508a0ec7a",
+      "animals_a42fb9cddd68b019a1e6216f01d94742",
+      "animals_d3f924d97c3d6bb735a27c0e4edfcd7c",
+      "animals_e937f221caaa87337c1dc98002e13c56",
+      "animals_f05f46e9ea9f18d47fbd7733a608536b",
+      "animals_fc0a31d2c32467fdda711ffd521e79d9",
+      "anime_aa293fb70ed59c7b610f0e6e218a0917",
+      "anime_f48648cbbff8370f18bdc592d98bb563",
+      "dinosaurs_61c5d5727722d957ffa39b7cc5519bf6",
+      "dinosaurs_d2fafe9651b48459a72aa15cfeac3b61",
+      "ghibli_2c6fd10faee15c612ebfe71628069d36",
+      "ghibli_914deb9acd1f1ab2a50ec9f7924eb799",
+      "ghibli_b2c74d93a2c32f2ccbda73c4bee03b00",
+      "ghibli_f2798b83ce4d01142bdcd5d8803f5412",
+      "harrypotter_439fe7c78ebbf945de2eb18f2f78f7a8",
+      "harrypotter_d3664e7e2920eb8cee5b69982f710644",
+      "nature_38a414b6051a9b528fedc47878663c03",
+      "nature_98574287a17f734d52dc64dfc0f45fc8",
+      "nature_c0e131e9a2a813c337965ddfb413baf8",
+      "personal_0694cfbc9bb292f9da8c7c65d793a378",
+      "personal_0a0b014b37ddb90d9af1fcdedccef37d",
+      "personal_0d973affb8ffe7d0abe9ddd89957f580",
+      "personal_0ea16afc573d649222e512306872106f",
+      "personal_12381e1ed7bed4ec56c5d871a8b21a5d",
+      "personal_183bcd7c31fdece731a373ed375fd227",
+      "personal_1b98afe55b410ed03ea8b3f18fa5cc27",
+      "personal_22169df124ce293fbd519eb6d987a9b7",
+      "personal_259dd16880502cbc8fbbff87cee60a24",
+      "personal_29244801ee5399bc5a445f9c9f1e5487",
+      "personal_2f9d874fbd41462a6c115022e088f12e",
+      "personal_31320d5c42d2b0e81a3aa365af7bbd9d",
+      "personal_3b2274b4265177568057601aef1a4e87",
+      "personal_3cdb7de70fb4052ac849a6a5fa990eee",
+      "personal_3debd2d0b3b968d5a895d776bc8d7e03",
+      "personal_43f57a142340066e3137434a43b6cbbb",
+      "personal_4936d509c25ceaf585c94067f4f9061b",
+      "personal_4afcce1786d0265f45ca083b07bd407b",
+      "personal_4ee0e7589f440418d41e7e8283c6bcef",
+      "personal_4fe95f154947c4efdf9a35045b47e137",
+      "personal_548f73139e968e67d9737393e9cba794",
+      "personal_58e4fa6bdbfea985511936074d40a5e1",
+      "personal_5a2ec8b6ee98b400ae87207fcace71e3",
+      "personal_5b6e5f629039ff65178bac0b9d979185",
+      "personal_626e4843a78dbcc43e49bce849bc82cc",
+      "personal_642d854a9d4252d78faeb392b09ff04d",
+      "personal_6761903f2186e75e74fcbde92a4c6865",
+      "personal_67def669a778cd84299235188df223f9",
+      "personal_6baedd08c35004321ac26effade8221e",
+      "personal_6f28c333f1100e7d2cb316ba839a59b9",
+      "personal_70d72351d07f93710539592d8dd38d8a",
+      "personal_7d7e4fb5727fc23ab3fa7b40f8769fc1",
+      "personal_7ea40aebbbde03cbbcc19e5f92609b77",
+      "personal_84f358d1b64d7947617d85b21ab2375a",
+      "personal_90deb4cb0578338c442f268d6a24d5dc",
+      "personal_96b82ec73f9d83066297fa30a5b4384f",
+      "personal_9e44f127b4ec68efad8ce87da1d3fc51",
+      "personal_a6819fc32906ac2ca4a0f47503772307",
+      "personal_b27ef14b2c835df1ae1aeae938823193",
+      "personal_b50323b0dc6ce5fb9796adb80b814cd8",
+      "personal_b78c59c08302100769e3469fccff36c1",
+      "personal_b87fcb24c5d44e1328cddd66b75bc119",
+      "personal_b89b9d4366696fe1623b5b5d4f10f0fc",
+      "personal_b8d2facf43700d6f4e6c230c548e771b",
+      "personal_b961b8d3386fa525ac754eea04b32003",
+      "personal_bf4520a9582da41ecc280365afebb30f",
+      "personal_c0eb4eb4d39d58eecebc3218a56f6148",
+      "personal_c132137e54f68136af709edd6a8f602d",
+      "personal_c72571b7f7ac673b63754699e1b8c31c",
+      "personal_d0d1e8e150782b694d663b6cc838cf61",
+      "personal_d5d163c09ae4da219a7ae54b5a98b830",
+      "personal_d5f3483ee2a8598123a0d0a9a965aea0",
+      "personal_daf7dab4c768da753dede24093cef6c3",
+      "personal_e124afc5e5b3770e53f058431d37bf31",
+      "personal_e9da5393d39bebb0a5d393b4c55366a5",
+      "personal_efc2a3bf7597c3991afa800c4515a515",
+      "personal_f3991aee0215111c5e4c13994477e85b",
+      "personal_f4059dfc787363cc3437be52a47779a0",
+      "personal_fb47095d9556e3e7f00711afe128d5b2",
+      "sea_f2c615363f892ba270dae2fceb3a06e9",
+      "sea_f64def749dd7133248814a902ea140ab",
+      "space_438d8689bcc7e7cafd89a97d336d7981",
+    ]
+  };
+}
diff --git a/lib/main.dart b/lib/main.dart
index 33c9734800800bafa2feb43450a630b9571e2a2d..034c064ec7dcb7cac09f7d761ed54267f5174537 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,14 +1,42 @@
+import 'dart:io';
+
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
-import 'package:provider/provider.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:hive/hive.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+import 'package:path_provider/path_provider.dart';
 
-import 'package:puzzlegame/provider/data.dart';
-import 'package:puzzlegame/screens/home.dart';
+import 'package:puzzlegame/config/theme.dart';
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/cubit/nav_cubit.dart';
+import 'package:puzzlegame/cubit/settings_game_cubit.dart';
+import 'package:puzzlegame/cubit/settings_global_cubit.dart';
+import 'package:puzzlegame/cubit/theme_cubit.dart';
+import 'package:puzzlegame/ui/skeleton.dart';
 
-void main() {
+void main() async {
+  // Initialize packages
   WidgetsFlutterBinding.ensureInitialized();
+  await EasyLocalization.ensureInitialized();
+  final Directory tmpDir = await getTemporaryDirectory();
+  Hive.init(tmpDir.toString());
+  HydratedBloc.storage = await HydratedStorage.build(
+    storageDirectory: tmpDir,
+  );
+
   SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
-      .then((value) => runApp(const MyApp()));
+      .then((value) => runApp(EasyLocalization(
+            path: 'assets/translations',
+            supportedLocales: const <Locale>[
+              Locale('en'),
+              Locale('fr'),
+            ],
+            fallbackLocale: const Locale('en'),
+            useFallbackTranslations: true,
+            child: const MyApp(),
+          )));
 }
 
 class MyApp extends StatelessWidget {
@@ -16,23 +44,60 @@ class MyApp extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return ChangeNotifierProvider(
-      create: (BuildContext context) => Data(),
-      child: Consumer<Data>(
-        builder: (context, data, child) {
+    final List<String> assets = getImagesAssets();
+    for (String asset in assets) {
+      precacheImage(AssetImage(asset), context);
+    }
+
+    return MultiBlocProvider(
+      providers: [
+        BlocProvider<NavCubit>(create: (context) => NavCubit()),
+        BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()),
+        BlocProvider<GameCubit>(create: (context) => GameCubit()),
+        BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
+        BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()),
+      ],
+      child: BlocBuilder<ThemeCubit, ThemeModeState>(
+        builder: (BuildContext context, ThemeModeState state) {
           return MaterialApp(
+            title: 'Template app',
+            home: const SkeletonScreen(),
+
+            // Theme stuff
+            theme: lightTheme,
+            darkTheme: darkTheme,
+            themeMode: state.themeMode,
+
+            // Localization stuff
+            localizationsDelegates: context.localizationDelegates,
+            supportedLocales: context.supportedLocales,
+            locale: context.locale,
             debugShowCheckedModeBanner: false,
-            theme: ThemeData(
-              primaryColor: Colors.blue,
-              visualDensity: VisualDensity.adaptivePlatformDensity,
-            ),
-            home: const Home(),
-            routes: {
-              Home.id: (context) => const Home(),
-            },
           );
         },
       ),
     );
   }
+
+  List<String> getImagesAssets() {
+    final List<String> assets = [];
+
+    const List<String> gameImages = [
+      'button_back',
+      'button_delete_saved_game',
+      'button_random_pick',
+      'button_resume_game',
+      'button_shuffle',
+      'button_start',
+      'game_win',
+      'placeholder',
+      'tip_hidden',
+    ];
+
+    for (String image in gameImages) {
+      assets.add('assets/ui/$image.png');
+    }
+
+    return assets;
+  }
 }
diff --git a/lib/models/game/game.dart b/lib/models/game/game.dart
new file mode 100644
index 0000000000000000000000000000000000000000..1d0dccef293089b1662b7f01aba54bf1203d8aa7
--- /dev/null
+++ b/lib/models/game/game.dart
@@ -0,0 +1,127 @@
+import 'package:puzzlegame/data/fetch_data_helper.dart';
+import 'package:puzzlegame/models/game/moving_tile.dart';
+import 'package:puzzlegame/models/settings/settings_game.dart';
+import 'package:puzzlegame/models/settings/settings_global.dart';
+import 'package:puzzlegame/utils/tools.dart';
+
+class Game {
+  Game({
+    // Settings
+    required this.gameSettings,
+    required this.globalSettings,
+
+    // State
+    this.isRunning = false,
+    this.isStarted = false,
+    this.isFinished = false,
+    this.animationInProgress = false,
+    this.shufflingInProgress = false,
+
+    // Base data
+    required this.image,
+    this.tiles = const [],
+
+    // Game data
+    this.movesCount = 0,
+    this.displayTip = false,
+    this.tileSize = 0.0,
+  });
+
+  // Settings
+  final GameSettings gameSettings;
+  final GlobalSettings globalSettings;
+
+  // State
+  bool isRunning;
+  bool isStarted;
+  bool isFinished;
+  bool animationInProgress;
+  bool shufflingInProgress;
+
+  // Base data
+  String image;
+  List<MovingTile> tiles;
+
+  // Game data
+  int movesCount;
+  bool displayTip;
+  double tileSize;
+
+  factory Game.createEmpty() {
+    return Game(
+      gameSettings: GameSettings.createDefault(),
+      globalSettings: GlobalSettings.createDefault(),
+      image: '',
+      tiles: [],
+    );
+  }
+
+  factory Game.createNew({
+    GameSettings? gameSettings,
+    GlobalSettings? globalSettings,
+  }) {
+    final GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault();
+    final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault();
+
+    final String image = FetchDataHelper().getRandomItem();
+
+    return Game(
+      // Settings
+      gameSettings: newGameSettings,
+      globalSettings: newGlobalSettings,
+      // Base data
+      image: image,
+    );
+  }
+
+  bool get canBeResumed => isStarted && !isFinished;
+
+  void dump() {
+    printlog('');
+    printlog('## Current game dump:');
+    printlog('');
+    printlog('$Game:');
+    printlog('  Settings');
+    gameSettings.dump();
+    globalSettings.dump();
+    printlog('  State');
+    printlog('    isRunning: $isRunning');
+    printlog('    isStarted: $isStarted');
+    printlog('    isFinished: $isFinished');
+    printlog('    animationInProgress: $animationInProgress');
+    printlog('    shufflingInProgress: $shufflingInProgress');
+    printlog('  Base data');
+    printlog('    image: $image');
+    printlog('  Game data');
+    printlog('    movesCount: $movesCount');
+    printlog('    displayTip: $displayTip');
+    printlog('    tileSize: $tileSize');
+    printlog('');
+  }
+
+  @override
+  String toString() {
+    return '$Game(${toJson()})';
+  }
+
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      // Settings
+      'gameSettings': gameSettings.toJson(),
+      'globalSettings': globalSettings.toJson(),
+      // State
+      'isRunning': isRunning,
+      'isStarted': isStarted,
+      'isFinished': isFinished,
+      'animationInProgress': animationInProgress,
+      'shufflingInProgress': shufflingInProgress,
+      // Base data
+      'image': image,
+      'tiles': tiles,
+      // Game data
+      'movesCount': movesCount,
+      'displayTip': displayTip,
+      'tileSize': tileSize,
+    };
+  }
+}
diff --git a/lib/models/game/moving_tile.dart b/lib/models/game/moving_tile.dart
new file mode 100644
index 0000000000000000000000000000000000000000..3ef918c9a8e29e04746bb95f6b976546e4fd0d0a
--- /dev/null
+++ b/lib/models/game/moving_tile.dart
@@ -0,0 +1,35 @@
+import 'package:puzzlegame/models/game/tile.dart';
+
+class MovingTile extends Tile {
+  int currentCol;
+  int currentRow;
+
+  MovingTile({
+    required super.image,
+    required super.size,
+    required super.originalCol,
+    required super.originalRow,
+    required this.currentCol,
+    required this.currentRow,
+  });
+
+  bool isCorrect() {
+    return ((currentRow == originalRow) && (currentCol == originalCol));
+  }
+
+  @override
+  String toString() {
+    return '$Tile(${toJson()})';
+  }
+
+  @override
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      'size': size,
+      'originalCol': originalCol,
+      'originalRow': originalRow,
+      'currentRow': currentRow,
+      'currentCol': currentCol,
+    };
+  }
+}
diff --git a/lib/entities/tile.dart b/lib/models/game/tile.dart
similarity index 52%
rename from lib/entities/tile.dart
rename to lib/models/game/tile.dart
index 76e58df62b27e29bb8d2e5f6b915ef42bd288c4f..77a369bb32b2d98be11c5faa85bc0e127b6d82e4 100644
--- a/lib/entities/tile.dart
+++ b/lib/models/game/tile.dart
@@ -1,10 +1,8 @@
 import 'package:flutter/material.dart';
 
-import 'package:puzzlegame/provider/data.dart';
-
 class Tile {
   final Image image;
-  final double size;
+  double size;
   final int originalCol;
   final int originalRow;
 
@@ -15,11 +13,16 @@ class Tile {
     required this.originalRow,
   });
 
-  Widget widget(Data myProvider) {
-    return Container();
+  @override
+  String toString() {
+    return '$Tile(${toJson()})';
   }
 
-  bool isCorrect() {
-    return false;
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      'size': size,
+      'originalCol': originalCol,
+      'originalRow': originalRow,
+    };
   }
 }
diff --git a/lib/models/settings/settings_game.dart b/lib/models/settings/settings_game.dart
new file mode 100644
index 0000000000000000000000000000000000000000..4ed699fb3f5119a6af2aabcdef7ad2b5836ff55f
--- /dev/null
+++ b/lib/models/settings/settings_game.dart
@@ -0,0 +1,56 @@
+import 'package:puzzlegame/config/default_game_settings.dart';
+import 'package:puzzlegame/utils/tools.dart';
+
+class GameSettings {
+  final String tilesetSize;
+  final String imageName;
+
+  GameSettings({
+    required this.tilesetSize,
+    required this.imageName,
+  });
+
+  static String getTilesetSizeValueFromUnsafe(String tilesetSize) {
+    if (DefaultGameSettings.allowedTilesetSizeValues.contains(tilesetSize)) {
+      return tilesetSize;
+    }
+
+    return DefaultGameSettings.defaultTilesetSizeValue;
+  }
+
+  static String getImageNameFromUnsafe(String imageName) {
+    if (DefaultGameSettings.allowedImageNameS.contains(imageName)) {
+      return imageName;
+    }
+
+    return DefaultGameSettings.defaultImageName;
+  }
+
+  factory GameSettings.createDefault() {
+    return GameSettings(
+      tilesetSize: DefaultGameSettings.defaultTilesetSizeValue,
+      imageName: DefaultGameSettings.defaultImageName,
+    );
+  }
+
+  int get tilesetSizeValue => int.parse(tilesetSize.split('x')[0]);
+
+  void dump() {
+    printlog('$GameSettings:');
+    printlog('  ${DefaultGameSettings.parameterCodeTilesetSize}: $tilesetSize');
+    printlog('  ${DefaultGameSettings.parameterCodeImageName}: $imageName');
+    printlog('');
+  }
+
+  @override
+  String toString() {
+    return '$GameSettings(${toJson()})';
+  }
+
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      DefaultGameSettings.parameterCodeTilesetSize: tilesetSize,
+      DefaultGameSettings.parameterCodeImageName: imageName,
+    };
+  }
+}
diff --git a/lib/models/settings/settings_global.dart b/lib/models/settings/settings_global.dart
new file mode 100644
index 0000000000000000000000000000000000000000..4c9879f16449d0786ae4b6396009c629cf1efbc9
--- /dev/null
+++ b/lib/models/settings/settings_global.dart
@@ -0,0 +1,41 @@
+import 'package:puzzlegame/config/default_global_settings.dart';
+import 'package:puzzlegame/utils/tools.dart';
+
+class GlobalSettings {
+  String skin;
+
+  GlobalSettings({
+    required this.skin,
+  });
+
+  static String getSkinValueFromUnsafe(String skin) {
+    if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) {
+      return skin;
+    }
+
+    return DefaultGlobalSettings.defaultSkinValue;
+  }
+
+  factory GlobalSettings.createDefault() {
+    return GlobalSettings(
+      skin: DefaultGlobalSettings.defaultSkinValue,
+    );
+  }
+
+  void dump() {
+    printlog('$GlobalSettings:');
+    printlog('  ${DefaultGlobalSettings.parameterCodeSkin}: $skin');
+    printlog('');
+  }
+
+  @override
+  String toString() {
+    return '$GlobalSettings(${toJson()})';
+  }
+
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      DefaultGlobalSettings.parameterCodeSkin: skin,
+    };
+  }
+}
diff --git a/lib/provider/data.dart b/lib/provider/data.dart
deleted file mode 100644
index dd85da9091f51786a56c70984313fdbb2e1d253c..0000000000000000000000000000000000000000
--- a/lib/provider/data.dart
+++ /dev/null
@@ -1,103 +0,0 @@
-import 'package:flutter/foundation.dart';
-
-import 'package:puzzlegame/entities/moving_tile.dart';
-
-class Data extends ChangeNotifier {
-  // application configuration
-  int _tilesCount = 4;
-
-  // Game data
-  List<String> _availableImages = [];
-  String _selectedImage = '';
-  List<MovingTile> _tiles = [];
-  double _tileImageSize = 1.0;
-  int _movesCount = 0;
-
-  // application state
-  bool _isShufflingBoard = false;
-  bool _isTipImageDisplayed = false;
-
-  String get selectedImage => _selectedImage;
-
-  void updateSelectedImage(String value) {
-    _selectedImage = value;
-    _movesCount = 0;
-    notifyListeners();
-  }
-
-  void resetSelectedImage() {
-    updateSelectedImage('');
-  }
-
-  int get tilesCount => _tilesCount;
-
-  void updateTilesCount(int tilesCountPerSide) {
-    _tilesCount = tilesCountPerSide;
-    notifyListeners();
-  }
-
-  List<String> get availableImages => _availableImages;
-
-  void updateAvailableImages(List<String> availableImages) {
-    _availableImages = availableImages;
-    notifyListeners();
-  }
-
-  List<MovingTile> get tiles => _tiles;
-
-  void updateTiles(List<MovingTile> tiles) {
-    _tiles = tiles;
-    notifyListeners();
-  }
-
-  int get movesCount => _movesCount;
-  void updateMovesCount(int movesCount) {
-    _movesCount = movesCount;
-    notifyListeners();
-  }
-
-  void incrementMovesCount() {
-    updateMovesCount(movesCount + 1);
-  }
-
-  bool get isShufflingBoard => _isShufflingBoard;
-
-  void updateIsShufflingBoard(bool isShuffling) {
-    _isShufflingBoard = isShuffling;
-    notifyListeners();
-  }
-
-  bool get displayTipImage => _isTipImageDisplayed;
-
-  void updateIsTipImageDisplayed(bool isDisplayed) {
-    _isTipImageDisplayed = isDisplayed;
-    notifyListeners();
-  }
-
-  double get tileImageSize => _tileImageSize;
-  void updateTileImageSize(double tileImageSize) {
-    _tileImageSize = tileImageSize;
-  }
-
-  void swapTiles(List<int> tile1, List<int> tile2) {
-    final int indexTile1 = _tiles.indexWhere(
-        (tile) => ((tile.currentCol == tile1[0]) && (tile.currentRow == tile1[1])));
-    final int indexTile2 = _tiles.indexWhere(
-        (tile) => ((tile.currentCol == tile2[0]) && (tile.currentRow == tile2[1])));
-
-    final MovingTile swap = _tiles[indexTile1];
-    _tiles[indexTile1] = _tiles[indexTile2];
-    _tiles[indexTile2] = swap;
-
-    final int swapCol = _tiles[indexTile1].currentCol;
-    _tiles[indexTile1].currentCol = _tiles[indexTile2].currentCol;
-    _tiles[indexTile2].currentCol = swapCol;
-
-    final int swapRow = _tiles[indexTile1].currentRow;
-    _tiles[indexTile1].currentRow = _tiles[indexTile2].currentRow;
-    _tiles[indexTile2].currentRow = swapRow;
-
-    incrementMovesCount();
-    notifyListeners();
-  }
-}
diff --git a/lib/screens/game.dart b/lib/screens/game.dart
deleted file mode 100644
index 073e39b41a9ceb382c966bbf326198a105177fba..0000000000000000000000000000000000000000
--- a/lib/screens/game.dart
+++ /dev/null
@@ -1,225 +0,0 @@
-import 'package:flutter/material.dart';
-
-import 'package:puzzlegame/entities/moving_tile.dart';
-import 'package:puzzlegame/provider/data.dart';
-import 'package:puzzlegame/utils/game_utils.dart';
-
-class Game {
-  static void toggleDisplayTipImage(Data myProvider) {
-    myProvider.updateIsTipImageDisplayed(!myProvider.displayTipImage);
-  }
-
-  static Widget buildTilesetWidget(Data myProvider) {
-    final List<MovingTile> tiles = myProvider.tiles;
-
-    final Color borderColor =
-        GameUtils.checkTilesetIsCleared(tiles) ? Colors.green : Colors.orange;
-    int tileIndex = 0;
-
-    final Table tileset = Table(
-      defaultColumnWidth: const IntrinsicColumnWidth(),
-      border: TableBorder.all(
-        color: Colors.black,
-        style: BorderStyle.solid,
-        width: 2,
-      ),
-      children: [
-        for (int row = 0; row < myProvider.tilesCount; row++)
-          TableRow(
-            children: [
-              for (int col = 0; col < myProvider.tilesCount; col++)
-                Column(children: [tiles[tileIndex++].widget(myProvider)]),
-            ],
-          ),
-      ],
-    );
-
-    return Container(
-      margin: const EdgeInsets.all(8),
-      padding: const EdgeInsets.all(8),
-      decoration: BoxDecoration(
-        color: borderColor,
-        borderRadius: BorderRadius.circular(8),
-        border: Border.all(
-          color: borderColor,
-          width: 8,
-        ),
-      ),
-      child: tileset,
-    );
-  }
-
-  static Widget buildTipWidget(Data myProvider) {
-    return Container(
-      margin: const EdgeInsets.all(2),
-      padding: const EdgeInsets.all(2),
-      child: Table(
-        defaultColumnWidth: const IntrinsicColumnWidth(),
-        children: [
-          TableRow(
-            children: [
-              const Column(
-                children: [
-                  Image(
-                    image: AssetImage('assets/images/placeholder.png'),
-                    fit: BoxFit.fill,
-                  ),
-                ],
-              ),
-              Column(
-                children: [
-                  TextButton(
-                    child: Container(
-                      decoration: BoxDecoration(
-                        color: Colors.blue,
-                        borderRadius: BorderRadius.circular(4),
-                        border: Border.all(
-                          color: Colors.blue,
-                          width: 4,
-                        ),
-                      ),
-                      child: Image(
-                        image: AssetImage(
-                          myProvider.displayTipImage
-                              ? GameUtils.getImageAssetName(myProvider.selectedImage)
-                              : 'assets/icons/tip_hidden.png',
-                        ),
-                        fit: BoxFit.contain,
-                      ),
-                    ),
-                    onPressed: () => Game.toggleDisplayTipImage(myProvider),
-                  ),
-                ],
-              ),
-              const Column(
-                children: [
-                  Image(
-                    image: AssetImage('assets/images/placeholder.png'),
-                    fit: BoxFit.fill,
-                  ),
-                ],
-              ),
-            ],
-          ),
-        ],
-      ),
-    );
-  }
-
-  static Widget buildGameWidget(Data myProvider) {
-    return Column(
-      mainAxisAlignment: MainAxisAlignment.start,
-      crossAxisAlignment: CrossAxisAlignment.center,
-      children: [
-        const SizedBox(height: 8),
-        buildTopIndicatorWidget(myProvider),
-        const SizedBox(height: 2),
-        Expanded(
-          child: buildGameBoard(myProvider),
-        ),
-        const SizedBox(height: 2),
-        Container(
-          child: GameUtils.checkTilesetIsCleared(myProvider.tiles)
-              ? Game.buildWinMessage(myProvider)
-              : Game.buildTipWidget(myProvider),
-        ),
-      ],
-    );
-  }
-
-  static Widget buildGameBoard(Data myProvider) {
-    return Container(
-      margin: const EdgeInsets.all(4),
-      padding: const EdgeInsets.all(4),
-      child: Column(
-        children: [
-          buildTilesetWidget(myProvider),
-        ],
-      ),
-    );
-  }
-
-  static Widget buildTopIndicatorWidget(Data myProvider) {
-    return Table(
-      children: [
-        TableRow(
-          children: [
-            Column(children: [
-              Text(
-                myProvider.movesCount.toString(),
-                style: const TextStyle(
-                  fontSize: 40,
-                  fontWeight: FontWeight.w600,
-                  color: Colors.black,
-                ),
-              ),
-            ]),
-            const Column(children: []),
-          ],
-        ),
-      ],
-    );
-  }
-
-  static Widget buildWinMessage(Data myProvider) {
-    return Container(
-      margin: const EdgeInsets.all(2),
-      padding: const EdgeInsets.all(2),
-      child: Table(
-        defaultColumnWidth: const IntrinsicColumnWidth(),
-        children: [
-          TableRow(
-            children: [
-              const Column(
-                children: [
-                  Image(
-                    image: AssetImage('assets/icons/game_win.png'),
-                    fit: BoxFit.fill,
-                  ),
-                ],
-              ),
-              Column(
-                children: [
-                  TextButton(
-                    child: const Image(
-                      image: AssetImage('assets/icons/button_back.png'),
-                      fit: BoxFit.fill,
-                    ),
-                    onPressed: () => GameUtils.resetGame(myProvider),
-                  ),
-                ],
-              ),
-              const Column(
-                children: [
-                  Image(
-                    image: AssetImage('assets/icons/game_win.png'),
-                    fit: BoxFit.fill,
-                  ),
-                ],
-              ),
-            ],
-          ),
-        ],
-      ),
-    );
-  }
-
-  static Widget buildShufflingIndicatorWidget() {
-    return const Column(
-      mainAxisSize: MainAxisSize.min,
-      mainAxisAlignment: MainAxisAlignment.center,
-      children: [
-        Text(
-          '⏳',
-          style: TextStyle(
-            fontSize: 60,
-            fontWeight: FontWeight.w600,
-            color: Colors.black,
-          ),
-        ),
-        SizedBox(height: 20),
-        CircularProgressIndicator(),
-      ],
-    );
-  }
-}
diff --git a/lib/screens/home.dart b/lib/screens/home.dart
deleted file mode 100644
index 1cee132b7767715e18274e26a7f9e3ce97bbde13..0000000000000000000000000000000000000000
--- a/lib/screens/home.dart
+++ /dev/null
@@ -1,212 +0,0 @@
-import 'dart:async';
-
-import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
-
-import 'package:puzzlegame/provider/data.dart';
-import 'package:puzzlegame/screens/game.dart';
-import 'package:puzzlegame/utils/game_utils.dart';
-import 'package:puzzlegame/utils/get_images_list.dart';
-
-class Home extends StatelessWidget {
-  const Home({super.key});
-
-  static const String id = 'home';
-
-  final Color themePrimaryColor = Colors.blue;
-  final int _selectImageColumnsCount = 3;
-
-  Future<void> getImagesList(Data myProvider) async {
-    GetImagesList getImagesList = GetImagesList();
-    await getImagesList.init();
-    if (getImagesList.availableImages.isNotEmpty) {
-      myProvider.updateAvailableImages(getImagesList.availableImages);
-      shuffleAvailableImages(myProvider);
-    }
-  }
-
-  Future<void> selectImage(Data myProvider, String imageCode) async {
-    GameUtils.startGame(myProvider, imageCode);
-  }
-
-  void shuffleAvailableImages(Data myProvider) {
-    final List<String> images = myProvider.availableImages;
-    images.shuffle();
-    myProvider.updateAvailableImages(images);
-  }
-
-  Widget _buildImageSelectorItem(Data myProvider, String image) {
-    return TextButton(
-      style: TextButton.styleFrom(
-        padding: const EdgeInsets.all(2),
-      ),
-      child: Container(
-        decoration: BoxDecoration(
-          borderRadius: BorderRadius.circular(4),
-          border: Border.all(
-            color: Colors.blue.shade200,
-            width: 4,
-          ),
-        ),
-        child: Image(
-          image: AssetImage(GameUtils.getImageAssetName(image)),
-          fit: BoxFit.fill,
-        ),
-      ),
-      onPressed: () {
-        selectImage(myProvider, image);
-      },
-    );
-  }
-
-  Widget _buildImageSelector(Data myProvider) {
-    if (myProvider.availableImages.isEmpty) {
-      getImagesList(myProvider);
-    }
-    final List<String> images = myProvider.availableImages;
-
-    return Container(
-      padding: const EdgeInsets.all(2),
-      child: ListView(
-        children: [
-          Table(
-            defaultColumnWidth: const IntrinsicColumnWidth(),
-            children: [
-              for (int imgIndex = 0;
-                  imgIndex < images.length;
-                  imgIndex += _selectImageColumnsCount)
-                TableRow(
-                  children: [
-                    for (int colIndex = 0; colIndex < _selectImageColumnsCount; colIndex++)
-                      Column(
-                        children: [
-                          if (imgIndex + colIndex < images.length)
-                            _buildImageSelectorItem(myProvider, images[imgIndex + colIndex])
-                        ],
-                      ),
-                  ],
-                ),
-            ],
-          ),
-        ],
-      ),
-    );
-  }
-
-  TextButton _buildTilesetSizeSelectorItem(Data myProvider, int value) {
-    String assetName = 'assets/icons/difficulty_${value}x$value.png';
-
-    Color borderColor = themePrimaryColor;
-
-    if (myProvider.tilesCount == value) {
-      borderColor = Colors.white;
-    }
-
-    return TextButton(
-      child: Container(
-        decoration: BoxDecoration(
-          borderRadius: BorderRadius.circular(4),
-          border: Border.all(
-            color: borderColor,
-            width: 4,
-          ),
-        ),
-        child: Image(
-          image: AssetImage(assetName),
-          fit: BoxFit.fill,
-        ),
-      ),
-      onPressed: () {
-        myProvider.updateTilesCount(value);
-      },
-    );
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final Data myProvider = Provider.of<Data>(context);
-
-    Widget content;
-
-    myProvider
-        .updateTileImageSize((MediaQuery.of(context).size.width - 70) / myProvider.tilesCount);
-
-    if (myProvider.isShufflingBoard) {
-      content = Game.buildShufflingIndicatorWidget();
-      precacheImage(const AssetImage('assets/icons/game_win.png'), context);
-    } else {
-      if (myProvider.selectedImage == '') {
-        content = _buildImageSelector(myProvider);
-      } else {
-        content = Game.buildGameWidget(myProvider);
-      }
-    }
-
-    final List<Widget> menuActions = [
-      _buildTilesetSizeSelectorItem(myProvider, 3),
-      _buildTilesetSizeSelectorItem(myProvider, 4),
-      _buildTilesetSizeSelectorItem(myProvider, 5),
-      TextButton(
-        child: Container(
-          decoration: BoxDecoration(
-            borderRadius: BorderRadius.circular(4),
-            border: Border.all(
-              color: themePrimaryColor,
-              width: 4,
-            ),
-          ),
-          child: const Image(
-            image: AssetImage('assets/icons/button_shuffle.png'),
-            fit: BoxFit.fill,
-          ),
-        ),
-        onPressed: () => shuffleAvailableImages(myProvider),
-      ),
-      TextButton(
-        child: Container(
-          decoration: BoxDecoration(
-            borderRadius: BorderRadius.circular(4),
-            border: Border.all(
-              color: themePrimaryColor,
-              width: 4,
-            ),
-          ),
-          child: const Image(
-            image: AssetImage('assets/icons/button_random_pick.png'),
-            fit: BoxFit.fill,
-          ),
-        ),
-        onPressed: () => GameUtils.startRandomGame(myProvider),
-      ),
-    ];
-
-    final List<Widget> gameActions = [
-      TextButton(
-        child: Container(
-          decoration: BoxDecoration(
-            borderRadius: BorderRadius.circular(4),
-            border: Border.all(
-              color: Colors.blue,
-              width: 4,
-            ),
-          ),
-          child: const Image(
-            image: AssetImage('assets/icons/button_back.png'),
-            fit: BoxFit.fill,
-          ),
-        ),
-        onPressed: () => GameUtils.resetGame(myProvider),
-      ),
-    ];
-
-    return Scaffold(
-      appBar: AppBar(
-        backgroundColor: themePrimaryColor,
-        actions: myProvider.selectedImage == '' ? menuActions : gameActions,
-      ),
-      body: SafeArea(
-        child: Center(child: content),
-      ),
-    );
-  }
-}
diff --git a/lib/ui/game/game_bottom.dart b/lib/ui/game/game_bottom.dart
new file mode 100644
index 0000000000000000000000000000000000000000..3fb6d305abb6c129d9725fa7baa16662ef8e3708
--- /dev/null
+++ b/lib/ui/game/game_bottom.dart
@@ -0,0 +1,23 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/models/game/game.dart';
+import 'package:puzzlegame/ui/widgets/game/game_tip.dart';
+
+class GameBottomWidget extends StatelessWidget {
+  const GameBottomWidget({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game currentGame = gameState.currentGame;
+
+        return (!currentGame.isFinished && !currentGame.shufflingInProgress)
+            ? const GameTipWidget()
+            : const SizedBox.shrink();
+      },
+    );
+  }
+}
diff --git a/lib/ui/game/game_end.dart b/lib/ui/game/game_end.dart
new file mode 100644
index 0000000000000000000000000000000000000000..ab071e10e070f4a9414c4173e414c7aca1f856fe
--- /dev/null
+++ b/lib/ui/game/game_end.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/models/game/game.dart';
+import 'package:puzzlegame/ui/widgets/actions/button_game_quit.dart';
+
+class GameEndWidget extends StatelessWidget {
+  const GameEndWidget({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game currentGame = gameState.currentGame;
+
+        const Image decorationImage = Image(
+          image: AssetImage('assets/ui/game_win.png'),
+          fit: BoxFit.fill,
+        );
+
+        return Container(
+          margin: const EdgeInsets.all(2),
+          padding: const EdgeInsets.all(2),
+          child: Table(
+            defaultColumnWidth: const IntrinsicColumnWidth(),
+            children: [
+              TableRow(
+                children: [
+                  const Column(
+                    children: [decorationImage],
+                  ),
+                  Column(
+                    children: [
+                      currentGame.animationInProgress == true
+                          ? decorationImage
+                          : const QuitGameButton()
+                    ],
+                  ),
+                  const Column(
+                    children: [decorationImage],
+                  ),
+                ],
+              ),
+            ],
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/game/game_top.dart b/lib/ui/game/game_top.dart
new file mode 100644
index 0000000000000000000000000000000000000000..504e0a6164ac3c317c5c68d03b0e593f302f8f26
--- /dev/null
+++ b/lib/ui/game/game_top.dart
@@ -0,0 +1,28 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/models/game/game.dart';
+
+class GameTopWidget extends StatelessWidget {
+  const GameTopWidget({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game currentGame = gameState.currentGame;
+
+        return currentGame.shufflingInProgress
+            ? const SizedBox.shrink()
+            : Text(
+                '${currentGame.movesCount}',
+                style: const TextStyle(
+                  fontSize: 45,
+                  fontWeight: FontWeight.w600,
+                ),
+              );
+      },
+    );
+  }
+}
diff --git a/lib/ui/helpers/app_titles.dart b/lib/ui/helpers/app_titles.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c3ddf3cce4b952ee0674e8553f6caa0bf4794236
--- /dev/null
+++ b/lib/ui/helpers/app_titles.dart
@@ -0,0 +1,33 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+
+class AppHeader extends StatelessWidget {
+  const AppHeader({super.key, required this.text});
+
+  final String text;
+
+  @override
+  Widget build(BuildContext context) {
+    return Text(
+      tr(text),
+      textAlign: TextAlign.start,
+      style:
+          Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2),
+    );
+  }
+}
+
+class AppTitle extends StatelessWidget {
+  const AppTitle({super.key, required this.text});
+
+  final String text;
+
+  @override
+  Widget build(BuildContext context) {
+    return Text(
+      tr(text),
+      textAlign: TextAlign.start,
+      style: Theme.of(context).textTheme.titleLarge!.apply(fontWeightDelta: 2),
+    );
+  }
+}
diff --git a/lib/ui/helpers/outlined_text_widget.dart b/lib/ui/helpers/outlined_text_widget.dart
new file mode 100644
index 0000000000000000000000000000000000000000..54ff6f748a4157ec8789a2039062901481d91861
--- /dev/null
+++ b/lib/ui/helpers/outlined_text_widget.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart';
+
+import 'package:puzzlegame/utils/color_extensions.dart';
+
+class OutlinedText extends StatelessWidget {
+  const OutlinedText({
+    super.key,
+    required this.text,
+    required this.fontSize,
+    required this.textColor,
+    this.outlineColor,
+  });
+
+  final String text;
+  final double fontSize;
+  final Color textColor;
+  final Color? outlineColor;
+
+  @override
+  Widget build(BuildContext context) {
+    final double delta = fontSize / 30;
+
+    return Text(
+      text,
+      style: TextStyle(
+        inherit: true,
+        fontSize: fontSize,
+        fontWeight: FontWeight.w600,
+        color: textColor,
+        shadows: [
+          Shadow(
+            offset: Offset(-delta, -delta),
+            color: outlineColor ?? textColor.darken(),
+          ),
+          Shadow(
+            offset: Offset(delta, -delta),
+            color: outlineColor ?? textColor.darken(),
+          ),
+          Shadow(
+            offset: Offset(delta, delta),
+            color: outlineColor ?? textColor.darken(),
+          ),
+          Shadow(
+            offset: Offset(-delta, delta),
+            color: outlineColor ?? textColor.darken(),
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/ui/layouts/game_layout.dart b/lib/ui/layouts/game_layout.dart
new file mode 100644
index 0000000000000000000000000000000000000000..ed8f35329b799731dc7953379179e8db97cb7204
--- /dev/null
+++ b/lib/ui/layouts/game_layout.dart
@@ -0,0 +1,40 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/models/game/game.dart';
+import 'package:puzzlegame/ui/game/game_bottom.dart';
+import 'package:puzzlegame/ui/game/game_end.dart';
+import 'package:puzzlegame/ui/game/game_top.dart';
+import 'package:puzzlegame/ui/widgets/game/game_board.dart';
+
+class GameLayout extends StatelessWidget {
+  const GameLayout({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game currentGame = gameState.currentGame;
+
+        return Container(
+          alignment: AlignmentDirectional.topCenter,
+          padding: const EdgeInsets.all(4),
+          child: Column(
+            mainAxisAlignment: MainAxisAlignment.start,
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              const GameTopWidget(),
+              const SizedBox(height: 8),
+              const GameBoardWidget(),
+              const SizedBox(height: 8),
+              const GameBottomWidget(),
+              const Expanded(child: SizedBox.shrink()),
+              currentGame.isFinished ? const GameEndWidget() : const SizedBox.shrink(),
+            ],
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/layouts/parameters_layout.dart b/lib/ui/layouts/parameters_layout.dart
new file mode 100644
index 0000000000000000000000000000000000000000..15aae8142dd31bcdb453ce2d96450750406592b5
--- /dev/null
+++ b/lib/ui/layouts/parameters_layout.dart
@@ -0,0 +1,154 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/config/default_game_settings.dart';
+import 'package:puzzlegame/config/default_global_settings.dart';
+import 'package:puzzlegame/cubit/settings_game_cubit.dart';
+import 'package:puzzlegame/cubit/settings_global_cubit.dart';
+import 'package:puzzlegame/ui/parameters/parameter_image.dart';
+import 'package:puzzlegame/ui/parameters/parameter_painter.dart';
+import 'package:puzzlegame/ui/widgets/actions/button_delete_saved_game.dart';
+import 'package:puzzlegame/ui/widgets/actions/button_game_start_new.dart';
+import 'package:puzzlegame/ui/widgets/actions/button_resume_saved_game.dart';
+
+class ParametersLayout extends StatelessWidget {
+  const ParametersLayout({super.key, required this.canResume});
+
+  final bool canResume;
+
+  final double separatorHeight = 8.0;
+
+  @override
+  Widget build(BuildContext context) {
+    final List<Widget> lines = [];
+
+    // Game settings
+    for (String code in DefaultGameSettings.availableParameters) {
+      lines.add(Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: buildParametersLine(
+          code: code,
+          isGlobal: false,
+        ),
+      ));
+
+      lines.add(SizedBox(height: separatorHeight));
+    }
+
+    lines.add(SizedBox(height: separatorHeight));
+
+    if (canResume == false) {
+      // Start new game
+      lines.add(const Expanded(
+        child: StartNewGameButton(),
+      ));
+    } else {
+      // Resume game
+      lines.add(const Expanded(
+        child: ResumeSavedGameButton(),
+      ));
+      // Delete saved game
+      lines.add(SizedBox.square(
+        dimension: MediaQuery.of(context).size.width / 4,
+        child: const DeleteSavedGameButton(),
+      ));
+    }
+
+    lines.add(SizedBox(height: separatorHeight));
+
+    // Global settings
+    for (String code in DefaultGlobalSettings.availableParameters) {
+      lines.add(Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: buildParametersLine(
+          code: code,
+          isGlobal: true,
+        ),
+      ));
+
+      lines.add(SizedBox(height: separatorHeight));
+    }
+
+    return Column(
+      children: lines,
+    );
+  }
+
+  List<Widget> buildParametersLine({
+    required String code,
+    required bool isGlobal,
+  }) {
+    final List<Widget> parameterButtons = [];
+
+    final List<String> availableValues = isGlobal
+        ? DefaultGlobalSettings.getAvailableValues(code)
+        : DefaultGameSettings.getAvailableValues(code);
+
+    if (availableValues.length <= 1) {
+      return [];
+    }
+
+    for (String value in availableValues) {
+      final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>(
+        builder: (BuildContext context, GameSettingsState gameSettingsState) {
+          return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>(
+            builder: (BuildContext context, GlobalSettingsState globalSettingsState) {
+              final GameSettingsCubit gameSettingsCubit =
+                  BlocProvider.of<GameSettingsCubit>(context);
+              final GlobalSettingsCubit globalSettingsCubit =
+                  BlocProvider.of<GlobalSettingsCubit>(context);
+
+              final String currentValue = isGlobal
+                  ? globalSettingsCubit.getParameterValue(code)
+                  : gameSettingsCubit.getParameterValue(code);
+
+              final bool isActive = (value == currentValue);
+
+              final double displayWidth = MediaQuery.of(context).size.width;
+              final double itemWidth = displayWidth / availableValues.length - 26;
+
+              final bool displayedWithAssets =
+                  DefaultGlobalSettings.displayedWithAssets.contains(code) ||
+                      DefaultGameSettings.displayedWithAssets.contains(code);
+
+              return TextButton(
+                child: Container(
+                  child: displayedWithAssets
+                      ? SizedBox.square(
+                          dimension: itemWidth,
+                          child: ParameterImage(
+                            code: code,
+                            value: value,
+                            isSelected: isActive,
+                          ),
+                        )
+                      : CustomPaint(
+                          size: Size(itemWidth, itemWidth),
+                          willChange: false,
+                          painter: ParameterPainter(
+                            code: code,
+                            value: value,
+                            isSelected: isActive,
+                            gameSettings: gameSettingsState.settings,
+                            globalSettings: globalSettingsState.settings,
+                          ),
+                          isComplex: true,
+                        ),
+                ),
+                onPressed: () {
+                  isGlobal
+                      ? globalSettingsCubit.setParameterValue(code, value)
+                      : gameSettingsCubit.setParameterValue(code, value);
+                },
+              );
+            },
+          );
+        },
+      );
+
+      parameterButtons.add(parameterButton);
+    }
+
+    return parameterButtons;
+  }
+}
diff --git a/lib/ui/parameters/parameter_image.dart b/lib/ui/parameters/parameter_image.dart
new file mode 100644
index 0000000000000000000000000000000000000000..136518953f1b73f314a9bfedd7d5cd4a53c1fd00
--- /dev/null
+++ b/lib/ui/parameters/parameter_image.dart
@@ -0,0 +1,39 @@
+import 'package:flutter/material.dart';
+
+class ParameterImage extends StatelessWidget {
+  const ParameterImage({
+    super.key,
+    required this.code,
+    required this.value,
+    required this.isSelected,
+  });
+
+  final String code;
+  final String value;
+  final bool isSelected;
+
+  static const Color buttonBackgroundColor = Colors.white;
+  static const Color buttonBorderColorActive = Colors.blue;
+  static const Color buttonBorderColorInactive = Colors.white;
+  static const double buttonBorderWidth = 8.0;
+  static const double buttonBorderRadius = 8.0;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      decoration: BoxDecoration(
+        color: buttonBackgroundColor,
+        borderRadius: BorderRadius.circular(buttonBorderRadius),
+        border: Border.all(
+          color:
+              isSelected ? buttonBorderColorActive : buttonBorderColorInactive,
+          width: buttonBorderWidth,
+        ),
+      ),
+      child: Image(
+        image: AssetImage('assets/ui/${code}_$value.png'),
+        fit: BoxFit.fill,
+      ),
+    );
+  }
+}
diff --git a/lib/ui/parameters/parameter_painter.dart b/lib/ui/parameters/parameter_painter.dart
new file mode 100644
index 0000000000000000000000000000000000000000..9ac8eea99859cb7121633865d50e810885ab4199
--- /dev/null
+++ b/lib/ui/parameters/parameter_painter.dart
@@ -0,0 +1,151 @@
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+
+import 'package:puzzlegame/config/default_game_settings.dart';
+import 'package:puzzlegame/models/settings/settings_game.dart';
+import 'package:puzzlegame/models/settings/settings_global.dart';
+import 'package:puzzlegame/utils/tools.dart';
+
+class ParameterPainter extends CustomPainter {
+  const ParameterPainter({
+    required this.code,
+    required this.value,
+    required this.isSelected,
+    required this.gameSettings,
+    required this.globalSettings,
+  });
+
+  final String code;
+  final String value;
+  final bool isSelected;
+  final GameSettings gameSettings;
+  final GlobalSettings globalSettings;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    // force square
+    final double canvasSize = min(size.width, size.height);
+
+    const Color borderColorEnabled = Colors.blue;
+    const Color borderColorDisabled = Colors.white;
+
+    // "enabled/disabled" border
+    final paint = Paint();
+    paint.style = PaintingStyle.stroke;
+    paint.color = isSelected ? borderColorEnabled : borderColorDisabled;
+    paint.strokeJoin = StrokeJoin.round;
+    paint.strokeWidth = 10;
+    canvas.drawRect(
+        Rect.fromPoints(const Offset(0, 0), Offset(canvasSize, canvasSize)), paint);
+
+    // content
+    switch (code) {
+      case DefaultGameSettings.parameterCodeTilesetSize:
+        paintTilesetSizeParameterItem(value, canvas, canvasSize);
+        break;
+      default:
+        printlog('Unknown parameter: $code/$value');
+        paintUnknownParameterItem(value, canvas, canvasSize);
+    }
+  }
+
+  @override
+  bool shouldRepaint(CustomPainter oldDelegate) {
+    return false;
+  }
+
+  // "unknown" parameter -> simple block with text
+  void paintUnknownParameterItem(
+    final String value,
+    final Canvas canvas,
+    final double size,
+  ) {
+    final paint = Paint();
+    paint.strokeJoin = StrokeJoin.round;
+    paint.strokeWidth = 3;
+
+    paint.color = Colors.grey;
+    paint.style = PaintingStyle.fill;
+    canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint);
+
+    final textSpan = TextSpan(
+      text: '?\n$value',
+      style: const TextStyle(
+        color: Colors.black,
+        fontSize: 18,
+        fontWeight: FontWeight.bold,
+      ),
+    );
+    final textPainter = TextPainter(
+      text: textSpan,
+      textDirection: TextDirection.ltr,
+      textAlign: TextAlign.center,
+    );
+    textPainter.layout();
+    textPainter.paint(
+      canvas,
+      Offset(
+        (size - textPainter.width) * 0.5,
+        (size - textPainter.height) * 0.5,
+      ),
+    );
+  }
+
+  void paintTilesetSizeParameterItem(
+    final String value,
+    final Canvas canvas,
+    final double size,
+  ) {
+    Color backgroundColor = Colors.grey;
+    final int gridSize = int.parse(value.split('x')[0]);
+
+    switch (value) {
+      case DefaultGameSettings.tilesetSizeValueSmall:
+        backgroundColor = Colors.green;
+        break;
+      case DefaultGameSettings.tilesetSizeValueMedium:
+        backgroundColor = Colors.orange;
+        break;
+      case DefaultGameSettings.tilesetSizeValueLarge:
+        backgroundColor = Colors.red;
+        break;
+      default:
+        printlog('Wrong value for size parameter value: $value');
+    }
+
+    final paint = Paint();
+    paint.strokeJoin = StrokeJoin.round;
+    paint.strokeWidth = 3;
+
+    // Colored background
+    paint.color = backgroundColor;
+    paint.style = PaintingStyle.fill;
+    canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint);
+
+    // Mini grid
+    // TODO: add image
+    final borderColor = Colors.grey.shade800;
+
+    final drawSize = size * 0.7;
+
+    final double cellSize = drawSize / gridSize;
+    final double originX = (size - gridSize * cellSize) / 2;
+    final double originY = (size - gridSize * cellSize) / 2;
+
+    for (int row = 0; row < gridSize; row++) {
+      for (int col = 0; col < gridSize; col++) {
+        final Offset topLeft = Offset(originX + col * cellSize, originY + row * cellSize);
+        final Offset bottomRight = topLeft + Offset(cellSize, cellSize);
+
+        paint.color = Colors.grey.shade200;
+        paint.style = PaintingStyle.fill;
+        canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint);
+
+        paint.color = borderColor;
+        paint.style = PaintingStyle.stroke;
+        canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint);
+      }
+    }
+  }
+}
diff --git a/lib/ui/screens/page_about.dart b/lib/ui/screens/page_about.dart
new file mode 100644
index 0000000000000000000000000000000000000000..6716b349eb595de1c62c24cc6713b44e379d0f7f
--- /dev/null
+++ b/lib/ui/screens/page_about.dart
@@ -0,0 +1,41 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:package_info_plus/package_info_plus.dart';
+
+import 'package:puzzlegame/ui/helpers/app_titles.dart';
+
+class PageAbout extends StatelessWidget {
+  const PageAbout({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 8),
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.start,
+        crossAxisAlignment: CrossAxisAlignment.start,
+        mainAxisSize: MainAxisSize.max,
+        children: <Widget>[
+          const SizedBox(height: 8),
+          const AppTitle(text: 'about_title'),
+          const Text('about_content').tr(),
+          FutureBuilder<PackageInfo>(
+            future: PackageInfo.fromPlatform(),
+            builder: (context, snapshot) {
+              switch (snapshot.connectionState) {
+                case ConnectionState.done:
+                  return const Text('about_version').tr(
+                    namedArgs: {
+                      'version': snapshot.data!.version,
+                    },
+                  );
+                default:
+                  return const SizedBox();
+              }
+            },
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/ui/screens/page_game.dart b/lib/ui/screens/page_game.dart
new file mode 100644
index 0000000000000000000000000000000000000000..d4661980dc22b9528ba990c0bed6552f12cf7864
--- /dev/null
+++ b/lib/ui/screens/page_game.dart
@@ -0,0 +1,24 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/models/game/game.dart';
+import 'package:puzzlegame/ui/layouts/game_layout.dart';
+import 'package:puzzlegame/ui/layouts/parameters_layout.dart';
+
+class PageGame extends StatelessWidget {
+  const PageGame({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game currentGame = gameState.currentGame;
+
+        return currentGame.isRunning
+            ? const GameLayout()
+            : ParametersLayout(canResume: currentGame.canBeResumed);
+      },
+    );
+  }
+}
diff --git a/lib/ui/screens/page_settings.dart b/lib/ui/screens/page_settings.dart
new file mode 100644
index 0000000000000000000000000000000000000000..abc6288e3d125c1db006de636b804b81eb9ee5ca
--- /dev/null
+++ b/lib/ui/screens/page_settings.dart
@@ -0,0 +1,26 @@
+import 'package:flutter/material.dart';
+
+import 'package:puzzlegame/ui/helpers/app_titles.dart';
+import 'package:puzzlegame/ui/settings/settings_form.dart';
+
+class PageSettings extends StatelessWidget {
+  const PageSettings({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return const Padding(
+      padding: EdgeInsets.symmetric(horizontal: 8),
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.start,
+        crossAxisAlignment: CrossAxisAlignment.start,
+        mainAxisSize: MainAxisSize.max,
+        children: <Widget>[
+          SizedBox(height: 8),
+          AppTitle(text: 'settings_title'),
+          SizedBox(height: 8),
+          SettingsForm(),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/ui/settings/settings_form.dart b/lib/ui/settings/settings_form.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e4e513f0e67174394667df6b1384190f2941bdde
--- /dev/null
+++ b/lib/ui/settings/settings_form.dart
@@ -0,0 +1,63 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:unicons/unicons.dart';
+
+import 'package:puzzlegame/ui/settings/theme_card.dart';
+
+class SettingsForm extends StatefulWidget {
+  const SettingsForm({super.key});
+
+  @override
+  State<SettingsForm> createState() => _SettingsFormState();
+}
+
+class _SettingsFormState extends State<SettingsForm> {
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.start,
+      mainAxisSize: MainAxisSize.max,
+      children: <Widget>[
+        // Light/dark theme
+        Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: <Widget>[
+            const Text('settings_label_theme').tr(),
+            const Row(
+              mainAxisAlignment: MainAxisAlignment.end,
+              crossAxisAlignment: CrossAxisAlignment.center,
+              children: [
+                ThemeCard(
+                  mode: ThemeMode.system,
+                  icon: UniconsLine.cog,
+                ),
+                ThemeCard(
+                  mode: ThemeMode.light,
+                  icon: UniconsLine.sun,
+                ),
+                ThemeCard(
+                  mode: ThemeMode.dark,
+                  icon: UniconsLine.moon,
+                )
+              ],
+            ),
+          ],
+        ),
+
+        const SizedBox(height: 16),
+      ],
+    );
+  }
+}
diff --git a/lib/ui/settings/theme_card.dart b/lib/ui/settings/theme_card.dart
new file mode 100644
index 0000000000000000000000000000000000000000..1f4402957890c05e9ba81736ecfdf5fc1b7b90e3
--- /dev/null
+++ b/lib/ui/settings/theme_card.dart
@@ -0,0 +1,47 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/theme_cubit.dart';
+
+class ThemeCard extends StatelessWidget {
+  const ThemeCard({
+    super.key,
+    required this.mode,
+    required this.icon,
+  });
+
+  final IconData icon;
+  final ThemeMode mode;
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<ThemeCubit, ThemeModeState>(
+      builder: (BuildContext context, ThemeModeState state) {
+        return Card(
+          elevation: 2,
+          shadowColor: Theme.of(context).colorScheme.shadow,
+          color: state.themeMode == mode
+              ? Theme.of(context).colorScheme.primary
+              : Theme.of(context).colorScheme.surface,
+          shape: const RoundedRectangleBorder(
+            borderRadius: BorderRadius.all(Radius.circular(12)),
+          ),
+          margin: const EdgeInsets.all(5),
+          child: InkWell(
+            onTap: () => BlocProvider.of<ThemeCubit>(context).getTheme(
+              ThemeModeState(themeMode: mode),
+            ),
+            borderRadius: const BorderRadius.all(Radius.circular(12)),
+            child: Icon(
+              icon,
+              size: 32,
+              color: state.themeMode != mode
+                  ? Theme.of(context).colorScheme.primary
+                  : Colors.white,
+            ),
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart
new file mode 100644
index 0000000000000000000000000000000000000000..257ed0b335c25d4ccbdf786f2dab82a2f8f2fcec
--- /dev/null
+++ b/lib/ui/skeleton.dart
@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/config/menu.dart';
+import 'package:puzzlegame/cubit/nav_cubit.dart';
+import 'package:puzzlegame/ui/widgets/global_app_bar.dart';
+
+class SkeletonScreen extends StatelessWidget {
+  const SkeletonScreen({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: const GlobalAppBar(),
+      extendBodyBehindAppBar: false,
+      body: Material(
+        color: Theme.of(context).colorScheme.surface,
+        child: BlocBuilder<NavCubit, int>(
+          builder: (BuildContext context, int pageIndex) {
+            return Padding(
+              padding: const EdgeInsets.only(
+                top: 8,
+                left: 2,
+                right: 2,
+              ),
+              child: Menu.getPageWidget(pageIndex),
+            );
+          },
+        ),
+      ),
+      backgroundColor: Theme.of(context).colorScheme.surface,
+    );
+  }
+}
diff --git a/lib/ui/widgets/actions/button_delete_saved_game.dart b/lib/ui/widgets/actions/button_delete_saved_game.dart
new file mode 100644
index 0000000000000000000000000000000000000000..564f0c2529be890997be8fdf5fdfbee996c51cb7
--- /dev/null
+++ b/lib/ui/widgets/actions/button_delete_saved_game.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+
+class DeleteSavedGameButton extends StatelessWidget {
+  const DeleteSavedGameButton({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return TextButton(
+      child: const Image(
+        image: AssetImage('assets/ui/button_delete_saved_game.png'),
+        fit: BoxFit.fill,
+      ),
+      onPressed: () {
+        BlocProvider.of<GameCubit>(context).deleteSavedGame();
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/actions/button_game_quit.dart b/lib/ui/widgets/actions/button_game_quit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..5a11378f5a6fbcbec84a0878ba29e091c24cdc25
--- /dev/null
+++ b/lib/ui/widgets/actions/button_game_quit.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+
+class QuitGameButton extends StatelessWidget {
+  const QuitGameButton({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return TextButton(
+      child: const Image(
+        image: AssetImage('assets/ui/button_back.png'),
+        fit: BoxFit.fill,
+      ),
+      onPressed: () {
+        BlocProvider.of<GameCubit>(context).quitGame();
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/actions/button_game_start_new.dart b/lib/ui/widgets/actions/button_game_start_new.dart
new file mode 100644
index 0000000000000000000000000000000000000000..ce0170528901aa65ab6c658c533cdb7365a95398
--- /dev/null
+++ b/lib/ui/widgets/actions/button_game_start_new.dart
@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/cubit/settings_game_cubit.dart';
+import 'package:puzzlegame/cubit/settings_global_cubit.dart';
+
+class StartNewGameButton extends StatelessWidget {
+  const StartNewGameButton({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameSettingsCubit, GameSettingsState>(
+      builder: (BuildContext context, GameSettingsState gameSettingsState) {
+        return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>(
+          builder: (BuildContext context, GlobalSettingsState globalSettingsState) {
+            return TextButton(
+              child: const Image(
+                image: AssetImage('assets/ui/button_start.png'),
+                fit: BoxFit.fill,
+              ),
+              onPressed: () {
+                BlocProvider.of<GameCubit>(context).startNewGame(
+                  gameSettings: gameSettingsState.settings,
+                  globalSettings: globalSettingsState.settings,
+                );
+              },
+            );
+          },
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/actions/button_resume_saved_game.dart b/lib/ui/widgets/actions/button_resume_saved_game.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b90f57b157dea14e3b9d6a21ed62a9d3f126b538
--- /dev/null
+++ b/lib/ui/widgets/actions/button_resume_saved_game.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+
+class ResumeSavedGameButton extends StatelessWidget {
+  const ResumeSavedGameButton({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return TextButton(
+      child: const Image(
+        image: AssetImage('assets/ui/button_resume_game.png'),
+        fit: BoxFit.fill,
+      ),
+      onPressed: () {
+        BlocProvider.of<GameCubit>(context).resumeSavedGame();
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/game/game.dart b/lib/ui/widgets/game/game.dart
new file mode 100644
index 0000000000000000000000000000000000000000..5f280104f5aaf54b1bd5f4802dee7a452883d5e1
--- /dev/null
+++ b/lib/ui/widgets/game/game.dart
@@ -0,0 +1,32 @@
+import 'package:flutter/material.dart';
+
+import 'package:puzzlegame/ui/game/game_top.dart';
+import 'package:puzzlegame/ui/widgets/game/game_tileset.dart';
+
+class GameWidget extends StatelessWidget {
+  const GameWidget({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.center,
+      children: [
+        const SizedBox(height: 8),
+        const GameTopWidget(),
+        const SizedBox(height: 2),
+        Expanded(
+          child: Container(
+            margin: const EdgeInsets.all(4),
+            padding: const EdgeInsets.all(4),
+            child: const Column(
+              children: [
+                GameTilesetwidget(),
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}
diff --git a/lib/ui/widgets/game/game_board.dart b/lib/ui/widgets/game/game_board.dart
new file mode 100644
index 0000000000000000000000000000000000000000..ea15c33e1f93f3a78325dbab33648ad993fe5820
--- /dev/null
+++ b/lib/ui/widgets/game/game_board.dart
@@ -0,0 +1,32 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/models/game/game.dart';
+import 'package:puzzlegame/ui/widgets/game/game_shuffling.dart';
+import 'package:puzzlegame/ui/widgets/game/game_tileset.dart';
+
+class GameBoardWidget extends StatelessWidget {
+  const GameBoardWidget({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Center(
+      child: BlocBuilder<GameCubit, GameState>(
+        builder: (BuildContext context, GameState gameState) {
+          final Game currentGame = gameState.currentGame;
+
+          BlocProvider.of<GameCubit>(context).updateTileSize(
+              (MediaQuery.of(context).size.width - 60) /
+                  currentGame.gameSettings.tilesetSizeValue);
+
+          if (currentGame.shufflingInProgress) {
+            return const GameShufflingWidget();
+          } else {
+            return const GameTilesetwidget();
+          }
+        },
+      ),
+    );
+  }
+}
diff --git a/lib/ui/widgets/game/game_shuffling.dart b/lib/ui/widgets/game/game_shuffling.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e02d78cf18ef71e45001c13555352a144621822c
--- /dev/null
+++ b/lib/ui/widgets/game/game_shuffling.dart
@@ -0,0 +1,25 @@
+import 'package:flutter/material.dart';
+
+class GameShufflingWidget extends StatelessWidget {
+  const GameShufflingWidget({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return const Column(
+      mainAxisSize: MainAxisSize.min,
+      mainAxisAlignment: MainAxisAlignment.center,
+      children: [
+        Text(
+          '⏳',
+          style: TextStyle(
+            fontSize: 60,
+            fontWeight: FontWeight.w600,
+            color: Colors.black,
+          ),
+        ),
+        SizedBox(height: 20),
+        CircularProgressIndicator(),
+      ],
+    );
+  }
+}
diff --git a/lib/entities/moving_tile.dart b/lib/ui/widgets/game/game_tile.dart
similarity index 52%
rename from lib/entities/moving_tile.dart
rename to lib/ui/widgets/game/game_tile.dart
index 99fa0a94999fb974ccaeab42f76576e9823cd36b..c3bf98c5ca8f271676b03e537d7b843d597ecbf7 100644
--- a/lib/entities/moving_tile.dart
+++ b/lib/ui/widgets/game/game_tile.dart
@@ -1,41 +1,16 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
-import 'package:puzzlegame/entities/tile.dart';
-import 'package:puzzlegame/provider/data.dart';
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/models/game/moving_tile.dart';
 
-class MovingTile extends Tile {
-  int currentCol;
-  int currentRow;
+class GameTileWidget extends StatelessWidget {
+  const GameTileWidget({super.key, required this.tile});
 
-  MovingTile({
-    required this.currentCol,
-    required this.currentRow,
-    required super.image,
-    required super.size,
-    required super.originalCol,
-    required super.originalRow,
-  });
-
-  Container _tileWidget() {
-    return Container(
-      decoration: BoxDecoration(
-        color: Colors.black,
-        border: Border.all(
-          color: Colors.black,
-          width: 1,
-        ),
-      ),
-      child: Image(
-        image: image.image,
-        width: size,
-        height: size,
-        fit: BoxFit.fill,
-      ),
-    );
-  }
+  final MovingTile tile;
 
   @override
-  Widget widget(Data myProvider) {
+  Widget build(BuildContext context) {
     return DragTarget<List<int>>(
       builder: (
         BuildContext context,
@@ -43,38 +18,52 @@ class MovingTile extends Tile {
         List<dynamic> rejected,
       ) {
         return Container(
-          height: size,
-          width: size,
+          height: tile.size,
+          width: tile.size,
           color: Colors.cyan,
           child: Draggable<List<int>>(
             data: [
-              currentCol,
-              currentRow,
+              tile.currentCol,
+              tile.currentRow,
             ],
 
             // Widget when draggable is being dragged
-            feedback: _tileWidget(),
+            feedback: staticTile(),
 
             // Widget to display on original place when being dragged
             childWhenDragging: Container(
-              height: size,
-              width: size,
+              height: tile.size,
+              width: tile.size,
               color: Colors.pinkAccent,
             ),
 
             // Widget when draggable is stationary
-            child: _tileWidget(),
+            child: staticTile(),
           ),
         );
       },
       onAcceptWithDetails: (DragTargetDetails<List<int>> source) {
-        myProvider.swapTiles([currentCol, currentRow], source.data);
+        BlocProvider.of<GameCubit>(context)
+            .swapTiles([tile.currentCol, tile.currentRow], source.data);
       },
     );
   }
 
-  @override
-  bool isCorrect() {
-    return ((currentRow == originalRow) && (currentCol == originalCol));
+  Container staticTile() {
+    return Container(
+      decoration: BoxDecoration(
+        color: Colors.black,
+        border: Border.all(
+          color: Colors.black,
+          width: 1,
+        ),
+      ),
+      child: Image(
+        image: tile.image.image,
+        width: tile.size,
+        height: tile.size,
+        fit: BoxFit.fill,
+      ),
+    );
   }
 }
diff --git a/lib/ui/widgets/game/game_tileset.dart b/lib/ui/widgets/game/game_tileset.dart
new file mode 100644
index 0000000000000000000000000000000000000000..fdeeb9fb1c968135eee81aa03525830c34814d4b
--- /dev/null
+++ b/lib/ui/widgets/game/game_tileset.dart
@@ -0,0 +1,62 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/models/game/game.dart';
+import 'package:puzzlegame/models/game/moving_tile.dart';
+import 'package:puzzlegame/ui/widgets/game/game_tile.dart';
+
+class GameTilesetwidget extends StatelessWidget {
+  const GameTilesetwidget({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game currentGame = gameState.currentGame;
+
+        final Color borderColor = currentGame.isFinished ? Colors.green : Colors.orange;
+        final int tilesCount = currentGame.gameSettings.tilesetSizeValue;
+
+        final List<MovingTile> tiles = currentGame.tiles;
+        int tileIndex = 0;
+
+        return Container(
+          margin: const EdgeInsets.all(8),
+          padding: const EdgeInsets.all(8),
+          decoration: BoxDecoration(
+            color: borderColor,
+            borderRadius: BorderRadius.circular(8),
+            border: Border.all(
+              color: borderColor,
+              width: 8,
+            ),
+          ),
+          child: Table(
+            defaultColumnWidth: const IntrinsicColumnWidth(),
+            border: TableBorder.all(
+              color: Colors.black,
+              style: BorderStyle.solid,
+              width: 2,
+            ),
+            children: [
+              for (int row = 0; row < tilesCount; row++)
+                TableRow(
+                  children: [
+                    for (int col = 0; col < tilesCount; col++)
+                      Column(
+                        children: [
+                          GameTileWidget(
+                            tile: tiles[tileIndex++],
+                          )
+                        ],
+                      ),
+                  ],
+                ),
+            ],
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/game/game_tip.dart b/lib/ui/widgets/game/game_tip.dart
new file mode 100644
index 0000000000000000000000000000000000000000..dc5a3d181931fc03a5f2ec8b0ecbb92cda4d5d5d
--- /dev/null
+++ b/lib/ui/widgets/game/game_tip.dart
@@ -0,0 +1,44 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/models/game/game.dart';
+
+class GameTipWidget extends StatelessWidget {
+  const GameTipWidget({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game currentGame = gameState.currentGame;
+
+        final String asset = 'assets/images/${currentGame.image}.png';
+        precacheImage(AssetImage(asset), context);
+
+        return TextButton(
+          child: Container(
+            decoration: BoxDecoration(
+              color: Colors.blue,
+              borderRadius: BorderRadius.circular(4),
+              border: Border.all(
+                color: Colors.blue,
+                width: 4,
+              ),
+            ),
+            child: Image(
+              height: currentGame.tileSize,
+              image: AssetImage(
+                currentGame.displayTip ? asset : 'assets/ui/tip_hidden.png',
+              ),
+              fit: BoxFit.fill,
+            ),
+          ),
+          onPressed: () {
+            BlocProvider.of<GameCubit>(context).toggleDisplayTipImage();
+          },
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e2cfcb4e372486b78b942841b6b42013eaec095a
--- /dev/null
+++ b/lib/ui/widgets/global_app_bar.dart
@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/config/menu.dart';
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/cubit/nav_cubit.dart';
+import 'package:puzzlegame/models/game/game.dart';
+import 'package:puzzlegame/ui/helpers/app_titles.dart';
+
+class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
+  const GlobalAppBar({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        return BlocBuilder<NavCubit, int>(
+          builder: (BuildContext context, int pageIndex) {
+            final Game currentGame = gameState.currentGame;
+
+            final List<Widget> menuActions = [];
+
+            if (currentGame.isRunning && !currentGame.isFinished) {
+              menuActions.add(TextButton(
+                child: const Image(
+                  image: AssetImage('assets/ui/button_back.png'),
+                  fit: BoxFit.fill,
+                ),
+                onPressed: () {},
+                onLongPress: () {
+                  BlocProvider.of<GameCubit>(context).quitGame();
+                },
+              ));
+            } else {
+              if (pageIndex == Menu.indexGame) {
+                // go to Settings page
+                menuActions.add(ElevatedButton(
+                  onPressed: () {
+                    context.read<NavCubit>().goToSettingsPage();
+                  },
+                  style: ElevatedButton.styleFrom(
+                    shape: const CircleBorder(),
+                  ),
+                  child: Menu.menuItemSettings.icon,
+                ));
+
+                // go to About page
+                menuActions.add(ElevatedButton(
+                  onPressed: () {
+                    context.read<NavCubit>().goToAboutPage();
+                  },
+                  style: ElevatedButton.styleFrom(
+                    shape: const CircleBorder(),
+                  ),
+                  child: Menu.menuItemAbout.icon,
+                ));
+              } else {
+                // back to Home page
+                menuActions.add(ElevatedButton(
+                  onPressed: () {
+                    context.read<NavCubit>().goToGamePage();
+                  },
+                  style: ElevatedButton.styleFrom(
+                    shape: const CircleBorder(),
+                  ),
+                  child: Menu.menuItemGame.icon,
+                ));
+              }
+            }
+
+            return AppBar(
+              title: const AppHeader(text: 'app_name'),
+              actions: menuActions,
+            );
+          },
+        );
+      },
+    );
+  }
+
+  @override
+  Size get preferredSize => const Size.fromHeight(50);
+}
diff --git a/lib/ui/widgets/indicators/indicator_score.dart b/lib/ui/widgets/indicators/indicator_score.dart
new file mode 100644
index 0000000000000000000000000000000000000000..349f715b1e0990beeb38b224b1e1d3679a9cc7cc
--- /dev/null
+++ b/lib/ui/widgets/indicators/indicator_score.dart
@@ -0,0 +1,27 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:puzzlegame/cubit/game_cubit.dart';
+import 'package:puzzlegame/ui/helpers/outlined_text_widget.dart';
+import 'package:puzzlegame/utils/color_extensions.dart';
+
+class ScoreIndicator extends StatelessWidget {
+  const ScoreIndicator({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        const Color baseColor = Color.fromARGB(255, 218, 218, 218);
+        final Color outlineColor = baseColor.darken();
+
+        return OutlinedText(
+          text: gameState.currentGame.movesCount.toString(),
+          fontSize: 70,
+          textColor: baseColor,
+          outlineColor: outlineColor,
+        );
+      },
+    );
+  }
+}
diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart
new file mode 100644
index 0000000000000000000000000000000000000000..4e55e338f0d3ed98b233d1ef887b7b3e17e29d97
--- /dev/null
+++ b/lib/utils/color_extensions.dart
@@ -0,0 +1,33 @@
+import 'dart:ui';
+
+extension ColorExtension on Color {
+  Color darken([int percent = 40]) {
+    assert(1 <= percent && percent <= 100);
+    final value = 1 - percent / 100;
+    return Color.fromARGB(
+      alpha,
+      (red * value).round(),
+      (green * value).round(),
+      (blue * value).round(),
+    );
+  }
+
+  Color lighten([int percent = 40]) {
+    assert(1 <= percent && percent <= 100);
+    final value = percent / 100;
+    return Color.fromARGB(
+      alpha,
+      (red + ((255 - red) * value)).round(),
+      (green + ((255 - green) * value)).round(),
+      (blue + ((255 - blue) * value)).round(),
+    );
+  }
+
+  Color avg(Color other) {
+    final red = (this.red + other.red) ~/ 2;
+    final green = (this.green + other.green) ~/ 2;
+    final blue = (this.blue + other.blue) ~/ 2;
+    final alpha = (this.alpha + other.alpha) ~/ 2;
+    return Color.fromARGB(alpha, red, green, blue);
+  }
+}
diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart
deleted file mode 100644
index db7d57ae59782558acb602f567a1ec80345668fe..0000000000000000000000000000000000000000
--- a/lib/utils/game_utils.dart
+++ /dev/null
@@ -1,120 +0,0 @@
-import 'dart:async';
-import 'dart:math';
-import 'dart:typed_data';
-
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart' show rootBundle;
-import 'package:image/image.dart' as imglib;
-
-import 'package:puzzlegame/entities/moving_tile.dart';
-import 'package:puzzlegame/entities/tile.dart';
-import 'package:puzzlegame/provider/data.dart';
-
-class GameUtils {
-  static String getImageAssetName(String imageCode) {
-    return 'assets/images/$imageCode.png';
-  }
-
-  static startGame(Data myProvider, String imageCode) {
-    myProvider.updateIsShufflingBoard(true);
-    myProvider.updateSelectedImage(imageCode);
-
-    Timer(const Duration(seconds: 1), () {
-      GameUtils.splitImageInTiles(myProvider);
-    });
-  }
-
-  static startRandomGame(Data myProvider) {
-    final List<int> sizes = [3, 4, 5];
-    sizes.shuffle();
-    myProvider.updateTilesCount(sizes[0]);
-
-    final List<String> images = myProvider.availableImages;
-    images.shuffle();
-
-    startGame(myProvider, images[0]);
-  }
-
-  static Future<void> resetGame(Data myProvider) async {
-    myProvider.resetSelectedImage();
-    myProvider.updateIsTipImageDisplayed(false);
-  }
-
-  static bool checkTilesetIsCleared(List<MovingTile> tiles) {
-    for (Tile tile in tiles) {
-      if (!tile.isCorrect()) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  static List<MovingTile> shuffleMovingTiles(List<MovingTile> tiles) {
-    final Random random = Random();
-    final int tilesCount = tiles.length;
-
-    for (int i = 0; i < (10 * tilesCount); i++) {
-      final int indexTile1 = random.nextInt(tilesCount);
-      final int indexTile2 = random.nextInt(tilesCount);
-
-      final MovingTile swap = tiles[indexTile1];
-      tiles[indexTile1] = tiles[indexTile2];
-      tiles[indexTile2] = swap;
-
-      final int swapCol = tiles[indexTile1].currentCol;
-      tiles[indexTile1].currentCol = tiles[indexTile2].currentCol;
-      tiles[indexTile2].currentCol = swapCol;
-
-      final int swapRow = tiles[indexTile1].currentRow;
-      tiles[indexTile1].currentRow = tiles[indexTile2].currentRow;
-      tiles[indexTile2].currentRow = swapRow;
-    }
-
-    return tiles;
-  }
-
-  static Future<void> splitImageInTiles(Data myProvider) async {
-    final String imageAsset = getImageAssetName(myProvider.selectedImage);
-    final Uint8List imageData = (await rootBundle.load(imageAsset)).buffer.asUint8List();
-
-    final imglib.Image image = imglib.decodeImage(imageData) ??
-        imglib.Image.fromBytes(
-          height: 1,
-          width: 1,
-          bytes: Uint8List.fromList([]).buffer,
-        );
-
-    int x = 0, y = 0;
-    final int width = (image.width / myProvider.tilesCount).round();
-    final int height = (image.height / myProvider.tilesCount).round();
-
-    final List<MovingTile> tiles = [];
-    for (int i = 0; i < myProvider.tilesCount; i++) {
-      for (int j = 0; j < myProvider.tilesCount; j++) {
-        final Uint8List tileData = Uint8List.fromList(imglib.encodeJpg(imglib.copyCrop(
-          image,
-          x: x,
-          y: y,
-          width: width,
-          height: height,
-        )));
-
-        tiles.add(MovingTile(
-          currentCol: j,
-          currentRow: i,
-          image: Image.memory(tileData),
-          size: myProvider.tileImageSize,
-          originalCol: j,
-          originalRow: i,
-        ));
-
-        x += width;
-      }
-      x = 0;
-      y += height;
-    }
-
-    myProvider.updateTiles(shuffleMovingTiles(tiles));
-    myProvider.updateIsShufflingBoard(false);
-  }
-}
diff --git a/lib/utils/get_images_list.dart b/lib/utils/get_images_list.dart
deleted file mode 100644
index ea5210d3c7e8910130aa5955b76387eef2689808..0000000000000000000000000000000000000000
--- a/lib/utils/get_images_list.dart
+++ /dev/null
@@ -1,35 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-
-import 'package:flutter/services.dart';
-
-class GetImagesList {
-  GetImagesList();
-
-  List<String> _availableImages = [];
-
-  init() async {
-    await imagesFromLocalFile();
-  }
-
-  Future<void> imagesFromLocalFile() async {
-    String jsonString;
-    try {
-      jsonString = await rootBundle.loadString('assets/files/images.json');
-      final jsonResponse = await json.decode(jsonString);
-
-      List imagesList = jsonResponse['images'] as List;
-
-      for (var image in imagesList) {
-        _availableImages.add(image.toString());
-      }
-    } catch (e) {
-      _availableImages = [];
-    }
-
-    // Remove empty images
-    _availableImages.removeWhere((value) => (value == ''));
-  }
-
-  List<String> get availableImages => _availableImages;
-}
diff --git a/pubspec.lock b/pubspec.lock
index fdebf8b0e3e7878829c3a11867fe985dbaf28d88..b504a800ac956512116ad914106027b4bb08ecbe 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -5,10 +5,34 @@ packages:
     dependency: transitive
     description:
       name: archive
-      sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
+      sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
       url: "https://pub.dev"
     source: hosted
-    version: "3.4.10"
+    version: "3.6.1"
+  args:
+    dependency: transitive
+    description:
+      name: args
+      sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.5.0"
+  async:
+    dependency: transitive
+    description:
+      name: async
+      sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.11.0"
+  bloc:
+    dependency: transitive
+    description:
+      name: bloc
+      sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "8.1.4"
   characters:
     dependency: transitive
     description:
@@ -17,22 +41,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.3.0"
-  collection:
+  clock:
     dependency: transitive
     description:
-      name: collection
-      sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
+      name: clock
+      sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
       url: "https://pub.dev"
     source: hosted
-    version: "1.18.0"
-  convert:
+    version: "1.1.1"
+  collection:
     dependency: transitive
     description:
-      name: convert
-      sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
+      name: collection
+      sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
       url: "https://pub.dev"
     source: hosted
-    version: "3.1.1"
+    version: "1.18.0"
   crypto:
     dependency: transitive
     description:
@@ -41,43 +65,133 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "3.0.3"
+  easy_localization:
+    dependency: "direct main"
+    description:
+      name: easy_localization
+      sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.7"
+  easy_logger:
+    dependency: transitive
+    description:
+      name: easy_logger
+      sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.0.2"
+  equatable:
+    dependency: "direct main"
+    description:
+      name: equatable
+      sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.5"
+  ffi:
+    dependency: transitive
+    description:
+      name: ffi
+      sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.2"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "7.0.0"
   flutter:
     dependency: "direct main"
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_bloc:
+    dependency: "direct main"
+    description:
+      name: flutter_bloc
+      sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
+      url: "https://pub.dev"
+    source: hosted
+    version: "8.1.6"
   flutter_lints:
     dependency: "direct dev"
     description:
       name: flutter_lints
-      sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
+      sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.0"
+  flutter_localizations:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  flutter_web_plugins:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  hive:
+    dependency: "direct main"
+    description:
+      name: hive
+      sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.3"
+  http:
+    dependency: transitive
+    description:
+      name: http
+      sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.1"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.2"
+  hydrated_bloc:
+    dependency: "direct main"
+    description:
+      name: hydrated_bloc
+      sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.1"
+    version: "9.1.5"
   image:
     dependency: "direct main"
     description:
       name: image
-      sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
+      sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
       url: "https://pub.dev"
     source: hosted
-    version: "4.1.7"
-  js:
+    version: "4.2.0"
+  intl:
     dependency: transitive
     description:
-      name: js
-      sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
+      name: intl
+      sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
       url: "https://pub.dev"
     source: hosted
-    version: "0.7.1"
+    version: "0.19.0"
   lints:
     dependency: transitive
     description:
       name: lints
-      sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
+      sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.0"
+    version: "4.0.0"
   material_color_utilities:
     dependency: transitive
     description:
@@ -90,10 +204,10 @@ packages:
     dependency: transitive
     description:
       name: meta
-      sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
+      sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
       url: "https://pub.dev"
     source: hosted
-    version: "1.11.0"
+    version: "1.12.0"
   nested:
     dependency: transitive
     description:
@@ -102,6 +216,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.0.0"
+  package_info_plus:
+    dependency: "direct main"
+    description:
+      name: package_info_plus
+      sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0
+      url: "https://pub.dev"
+    source: hosted
+    version: "8.0.0"
+  package_info_plus_platform_interface:
+    dependency: transitive
+    description:
+      name: package_info_plus_platform_interface
+      sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.0"
   path:
     dependency: transitive
     description:
@@ -110,6 +240,54 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.9.0"
+  path_provider:
+    dependency: "direct main"
+    description:
+      name: path_provider
+      sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.3"
+  path_provider_android:
+    dependency: transitive
+    description:
+      name: path_provider_android
+      sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.5"
+  path_provider_foundation:
+    dependency: transitive
+    description:
+      name: path_provider_foundation
+      sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.0"
+  path_provider_linux:
+    dependency: transitive
+    description:
+      name: path_provider_linux
+      sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.1"
+  path_provider_platform_interface:
+    dependency: transitive
+    description:
+      name: path_provider_platform_interface
+      sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.2"
+  path_provider_windows:
+    dependency: transitive
+    description:
+      name: path_provider_windows
+      sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.1"
   petitparser:
     dependency: transitive
     description:
@@ -118,27 +296,123 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "6.0.2"
-  pointycastle:
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.5"
+  plugin_platform_interface:
     dependency: transitive
     description:
-      name: pointycastle
-      sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
+      name: plugin_platform_interface
+      sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
       url: "https://pub.dev"
     source: hosted
-    version: "3.7.4"
+    version: "2.1.8"
   provider:
-    dependency: "direct main"
+    dependency: transitive
     description:
       name: provider
       sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
       url: "https://pub.dev"
     source: hosted
     version: "6.1.2"
+  shared_preferences:
+    dependency: transitive
+    description:
+      name: shared_preferences
+      sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.3"
+  shared_preferences_android:
+    dependency: transitive
+    description:
+      name: shared_preferences_android
+      sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.3"
+  shared_preferences_foundation:
+    dependency: transitive
+    description:
+      name: shared_preferences_foundation
+      sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.0"
+  shared_preferences_linux:
+    dependency: transitive
+    description:
+      name: shared_preferences_linux
+      sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.2"
+  shared_preferences_platform_interface:
+    dependency: transitive
+    description:
+      name: shared_preferences_platform_interface
+      sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.2"
+  shared_preferences_web:
+    dependency: transitive
+    description:
+      name: shared_preferences_web
+      sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.0"
+  shared_preferences_windows:
+    dependency: transitive
+    description:
+      name: shared_preferences_windows
+      sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.2"
   sky_engine:
     dependency: transitive
     description: flutter
     source: sdk
     version: "0.0.99"
+  source_span:
+    dependency: transitive
+    description:
+      name: source_span
+      sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.10.0"
+  string_scanner:
+    dependency: transitive
+    description:
+      name: string_scanner
+      sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.0"
+  synchronized:
+    dependency: transitive
+    description:
+      name: synchronized
+      sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0+1"
+  term_glyph:
+    dependency: transitive
+    description:
+      name: term_glyph
+      sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.1"
   typed_data:
     dependency: transitive
     description:
@@ -147,6 +421,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.3.2"
+  unicons:
+    dependency: "direct main"
+    description:
+      name: unicons
+      sha256: dbfcf93ff4d4ea19b324113857e358e4882115ab85db04417a4ba1c72b17a670
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.1"
   vector_math:
     dependency: transitive
     description:
@@ -155,6 +437,30 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.4"
+  web:
+    dependency: transitive
+    description:
+      name: web
+      sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.5.1"
+  win32:
+    dependency: transitive
+    description:
+      name: win32
+      sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
+      url: "https://pub.dev"
+    source: hosted
+    version: "5.5.1"
+  xdg_directories:
+    dependency: transitive
+    description:
+      name: xdg_directories
+      sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.4"
   xml:
     dependency: transitive
     description:
@@ -164,5 +470,5 @@ packages:
     source: hosted
     version: "6.5.0"
 sdks:
-  dart: ">=3.2.0 <4.0.0"
-  flutter: ">=1.16.0"
+  dart: ">=3.4.0 <4.0.0"
+  flutter: ">=3.22.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 1d3230d7d901f2e4838b47d50eb1374e06cf88c0..5b25af4eacf71594829cf25c052efafef7f3759a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,23 +1,49 @@
 name: puzzlegame
 description: A puzzle game application.
-publish_to: 'none'
-version: 0.0.61+61
+
+publish_to: "none"
+
+version: 0.1.0+62
 
 environment:
-  sdk: '^3.0.0'
+  sdk: "^3.0.0"
 
 dependencies:
   flutter:
     sdk: flutter
+
+  # base
+  easy_localization: ^3.0.1
+  equatable: ^2.0.5
+  flutter_bloc: ^8.1.1
+  hive: ^2.2.3
+  hydrated_bloc: ^9.0.0
+  package_info_plus: ^8.0.0
+  path_provider: ^2.0.11
+  unicons: ^2.1.1
+
+  # specific
   image: ^4.1.3
-  provider: ^6.0.5
 
 dev_dependencies:
-  flutter_lints: ^3.0.1
+  flutter_lints: ^4.0.0
 
 flutter:
   uses-material-design: true
   assets:
-    - assets/files/
-    - assets/icons/
     - assets/images/
+    - assets/ui/
+    - assets/translations/
+
+  fonts:
+    - family: Nunito
+      fonts:
+        - asset: assets/fonts/Nunito-Bold.ttf
+          weight: 700
+        - asset: assets/fonts/Nunito-Medium.ttf
+          weight: 500
+        - asset: assets/fonts/Nunito-Regular.ttf
+          weight: 400
+        - asset: assets/fonts/Nunito-Light.ttf
+          weight: 300
+
diff --git a/icons/build_application_icons.sh b/resources/app/build_application_resources.sh
similarity index 98%
rename from icons/build_application_icons.sh
rename to resources/app/build_application_resources.sh
index 27dbe2647fe4e6d562fbd99451716d1b7d448570..6d67b8f4f9eca701d1aed7331ef41dfb0bd44f20 100755
--- a/icons/build_application_icons.sh
+++ b/resources/app/build_application_resources.sh
@@ -6,7 +6,7 @@ command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not ins
 command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; }
 
 CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
-BASE_DIR="$(dirname "${CURRENT_DIR}")"
+BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")"
 
 SOURCE_ICON="${CURRENT_DIR}/icon.svg"
 SOURCE_FASTLANE="${CURRENT_DIR}/featureGraphic.svg"
diff --git a/icons/featureGraphic.svg b/resources/app/featureGraphic.svg
similarity index 100%
rename from icons/featureGraphic.svg
rename to resources/app/featureGraphic.svg
diff --git a/icons/icon.svg b/resources/app/icon.svg
similarity index 100%
rename from icons/icon.svg
rename to resources/app/icon.svg
diff --git a/resources/build_resources.sh b/resources/build_resources.sh
new file mode 100755
index 0000000000000000000000000000000000000000..41cf77292b5e860ceb070e06117af2b3a2d8da6b
--- /dev/null
+++ b/resources/build_resources.sh
@@ -0,0 +1,8 @@
+#! /bin/bash
+
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
+
+${CURRENT_DIR}/app/build_application_resources.sh
+${CURRENT_DIR}/ui/build_ui_resources.sh
+${CURRENT_DIR}/data/build_images_assets_list.sh
+
diff --git a/scripts/.gitignore b/resources/data/.gitignore
similarity index 100%
rename from scripts/.gitignore
rename to resources/data/.gitignore
diff --git a/scripts/01_optimize_images.sh b/resources/data/00_optimize_images.sh
similarity index 100%
rename from scripts/01_optimize_images.sh
rename to resources/data/00_optimize_images.sh
diff --git a/scripts/02_build_images_assets_list.sh b/resources/data/build_images_assets_list.sh
similarity index 63%
rename from scripts/02_build_images_assets_list.sh
rename to resources/data/build_images_assets_list.sh
index 3e9e3db237cf7289b932894354247a9694fa98a4..ddafcb22efa49ac47b2b5bec00e1e157e493cf5a 100755
--- a/scripts/02_build_images_assets_list.sh
+++ b/resources/data/build_images_assets_list.sh
@@ -3,12 +3,10 @@
 command -v jq >/dev/null 2>&1 || { echo >&2 "I require jq (json parser) but it's not installed. Aborting."; exit 1; }
 
 CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
-BASE_DIR="$(dirname "${CURRENT_DIR}")"
+BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")"
 
 ASSETS_BASE_FOLDER="${BASE_DIR}/assets"
-OUTPUT_ASSETS_FILE="${ASSETS_BASE_FOLDER}/files/images.json"
 IMAGES_ASSETS_FOLDER="${ASSETS_BASE_FOLDER}/images"
-touch "${OUTPUT_ASSETS_FILE}"
 
 IMAGES_CACHE_FOLDER="${CURRENT_DIR}/cache"
 IMAGES_OPTIMIZED_FOLDER="${IMAGES_CACHE_FOLDER}/02_optimized_images"
@@ -24,22 +22,25 @@ echo "Building assets json file..."
 
 FILES="$(find "${IMAGES_ASSETS_FOLDER}" -type f -name "*.png" | sed "s|^${IMAGES_OPTIMIZED_FOLDER}/||g" | sort)"
 
-OUTPUT_ASSETS_FILE_TMP="${OUTPUT_ASSETS_FILE}.tmp"
-echo "{" > "${OUTPUT_ASSETS_FILE_TMP}"
-echo "  \"images\": [" >> "${OUTPUT_ASSETS_FILE_TMP}"
+echo "Injecting json file in game_data.dart..."
+
+GAME_DATA_DART_FILE="${BASE_DIR}/lib/data/game_data.dart"
+echo "class GameData {" >"${GAME_DATA_DART_FILE}"
+echo "  static const Map<String, dynamic> data = {" >>"${GAME_DATA_DART_FILE}"
+echo "    \"images\": [" >>"${GAME_DATA_DART_FILE}"
 while read -r FILE; do
   FILE_CODE="$(basename "${FILE%.*}")"
   if [[ -n "${FILE}" ]]; then
     echo "- ${FILE_CODE}"
-    echo "    \"${FILE_CODE}\"," >> "${OUTPUT_ASSETS_FILE_TMP}"
+    echo "      \"${FILE_CODE}\"," >>"${GAME_DATA_DART_FILE}"
   fi
 done < <(echo "${FILES}")
-echo "    \"\"" >> "${OUTPUT_ASSETS_FILE_TMP}"
-echo "  ]" >> "${OUTPUT_ASSETS_FILE_TMP}"
-echo "}" >> "${OUTPUT_ASSETS_FILE_TMP}"
+echo "    ]" >>"${GAME_DATA_DART_FILE}"
+echo "  };" >>"${GAME_DATA_DART_FILE}"
+
+echo "}" >>"${GAME_DATA_DART_FILE}"
 
-# Format json
-cat "${OUTPUT_ASSETS_FILE_TMP}" | jq > "${OUTPUT_ASSETS_FILE}"
-rm "${OUTPUT_ASSETS_FILE_TMP}"
+echo "Formatting dart source code file..."
+dart format "${GAME_DATA_DART_FILE}"
 
 echo "done."
diff --git a/scripts/cache/01_raw_images/.gitkeep b/resources/data/cache/01_raw_images/.gitkeep
similarity index 100%
rename from scripts/cache/01_raw_images/.gitkeep
rename to resources/data/cache/01_raw_images/.gitkeep
diff --git a/scripts/cache/01_raw_images/00_default/19-93194.jpg b/resources/data/cache/01_raw_images/00_default/19-93194.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/00_default/19-93194.jpg
rename to resources/data/cache/01_raw_images/00_default/19-93194.jpg
diff --git a/scripts/cache/01_raw_images/00_default/1902316.jpg b/resources/data/cache/01_raw_images/00_default/1902316.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/00_default/1902316.jpg
rename to resources/data/cache/01_raw_images/00_default/1902316.jpg
diff --git a/scripts/cache/01_raw_images/00_default/667852_poster_l.jpg b/resources/data/cache/01_raw_images/00_default/667852_poster_l.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/00_default/667852_poster_l.jpg
rename to resources/data/cache/01_raw_images/00_default/667852_poster_l.jpg
diff --git a/scripts/cache/01_raw_images/00_default/goodmorningforsta.jpg b/resources/data/cache/01_raw_images/00_default/goodmorningforsta.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/00_default/goodmorningforsta.jpg
rename to resources/data/cache/01_raw_images/00_default/goodmorningforsta.jpg
diff --git a/scripts/cache/01_raw_images/animals/1-34816.jpg b/resources/data/cache/01_raw_images/animals/1-34816.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/1-34816.jpg
rename to resources/data/cache/01_raw_images/animals/1-34816.jpg
diff --git a/scripts/cache/01_raw_images/animals/1869714.jpg b/resources/data/cache/01_raw_images/animals/1869714.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/1869714.jpg
rename to resources/data/cache/01_raw_images/animals/1869714.jpg
diff --git a/scripts/cache/01_raw_images/animals/1872282.jpg b/resources/data/cache/01_raw_images/animals/1872282.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/1872282.jpg
rename to resources/data/cache/01_raw_images/animals/1872282.jpg
diff --git a/scripts/cache/01_raw_images/animals/1880455.jpg b/resources/data/cache/01_raw_images/animals/1880455.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/1880455.jpg
rename to resources/data/cache/01_raw_images/animals/1880455.jpg
diff --git a/scripts/cache/01_raw_images/animals/1888275.jpg b/resources/data/cache/01_raw_images/animals/1888275.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/1888275.jpg
rename to resources/data/cache/01_raw_images/animals/1888275.jpg
diff --git a/scripts/cache/01_raw_images/animals/1889220.jpg b/resources/data/cache/01_raw_images/animals/1889220.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/1889220.jpg
rename to resources/data/cache/01_raw_images/animals/1889220.jpg
diff --git a/scripts/cache/01_raw_images/animals/1889565.jpg b/resources/data/cache/01_raw_images/animals/1889565.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/1889565.jpg
rename to resources/data/cache/01_raw_images/animals/1889565.jpg
diff --git a/scripts/cache/01_raw_images/animals/2-13953.jpg b/resources/data/cache/01_raw_images/animals/2-13953.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/2-13953.jpg
rename to resources/data/cache/01_raw_images/animals/2-13953.jpg
diff --git a/scripts/cache/01_raw_images/animals/3-54091.jpg b/resources/data/cache/01_raw_images/animals/3-54091.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/3-54091.jpg
rename to resources/data/cache/01_raw_images/animals/3-54091.jpg
diff --git a/scripts/cache/01_raw_images/animals/681366_poster_l.jpg b/resources/data/cache/01_raw_images/animals/681366_poster_l.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/681366_poster_l.jpg
rename to resources/data/cache/01_raw_images/animals/681366_poster_l.jpg
diff --git a/scripts/cache/01_raw_images/animals/683592_poster_l.jpg b/resources/data/cache/01_raw_images/animals/683592_poster_l.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/683592_poster_l.jpg
rename to resources/data/cache/01_raw_images/animals/683592_poster_l.jpg
diff --git a/scripts/cache/01_raw_images/animals/7-24566.jpg b/resources/data/cache/01_raw_images/animals/7-24566.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/7-24566.jpg
rename to resources/data/cache/01_raw_images/animals/7-24566.jpg
diff --git a/scripts/cache/01_raw_images/animals/Alpaca-In-The-Mountains-Karin-Bijlsma-Affiche.webp.png b/resources/data/cache/01_raw_images/animals/Alpaca-In-The-Mountains-Karin-Bijlsma-Affiche.webp.png
similarity index 100%
rename from scripts/cache/01_raw_images/animals/Alpaca-In-The-Mountains-Karin-Bijlsma-Affiche.webp.png
rename to resources/data/cache/01_raw_images/animals/Alpaca-In-The-Mountains-Karin-Bijlsma-Affiche.webp.png
diff --git a/scripts/cache/01_raw_images/animals/Being-Lazy-Andy-Westface-Affiche.webp.png b/resources/data/cache/01_raw_images/animals/Being-Lazy-Andy-Westface-Affiche.webp.png
similarity index 100%
rename from scripts/cache/01_raw_images/animals/Being-Lazy-Andy-Westface-Affiche.webp.png
rename to resources/data/cache/01_raw_images/animals/Being-Lazy-Andy-Westface-Affiche.webp.png
diff --git a/scripts/cache/01_raw_images/animals/Fly-High-Together-Andy-Westface-Affiche.webp.png b/resources/data/cache/01_raw_images/animals/Fly-High-Together-Andy-Westface-Affiche.webp.png
similarity index 100%
rename from scripts/cache/01_raw_images/animals/Fly-High-Together-Andy-Westface-Affiche.webp.png
rename to resources/data/cache/01_raw_images/animals/Fly-High-Together-Andy-Westface-Affiche.webp.png
diff --git a/scripts/cache/01_raw_images/animals/Me-Time-Andy-Westface-Affiche.webp.png b/resources/data/cache/01_raw_images/animals/Me-Time-Andy-Westface-Affiche.webp.png
similarity index 100%
rename from scripts/cache/01_raw_images/animals/Me-Time-Andy-Westface-Affiche.webp.png
rename to resources/data/cache/01_raw_images/animals/Me-Time-Andy-Westface-Affiche.webp.png
diff --git a/scripts/cache/01_raw_images/animals/Zebra-Balloon-Paul-Fuentes-Affiche.webp.png b/resources/data/cache/01_raw_images/animals/Zebra-Balloon-Paul-Fuentes-Affiche.webp.png
similarity index 100%
rename from scripts/cache/01_raw_images/animals/Zebra-Balloon-Paul-Fuentes-Affiche.webp.png
rename to resources/data/cache/01_raw_images/animals/Zebra-Balloon-Paul-Fuentes-Affiche.webp.png
diff --git a/scripts/cache/01_raw_images/animals/panda.jpg b/resources/data/cache/01_raw_images/animals/panda.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/panda.jpg
rename to resources/data/cache/01_raw_images/animals/panda.jpg
diff --git a/scripts/cache/01_raw_images/animals/rainmaker.jpg b/resources/data/cache/01_raw_images/animals/rainmaker.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/animals/rainmaker.jpg
rename to resources/data/cache/01_raw_images/animals/rainmaker.jpg
diff --git a/scripts/cache/01_raw_images/anime/1893736.jpg b/resources/data/cache/01_raw_images/anime/1893736.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/anime/1893736.jpg
rename to resources/data/cache/01_raw_images/anime/1893736.jpg
diff --git a/scripts/cache/01_raw_images/anime/1896891.jpg b/resources/data/cache/01_raw_images/anime/1896891.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/anime/1896891.jpg
rename to resources/data/cache/01_raw_images/anime/1896891.jpg
diff --git a/scripts/cache/01_raw_images/dinosaurs/1879022.jpg b/resources/data/cache/01_raw_images/dinosaurs/1879022.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/dinosaurs/1879022.jpg
rename to resources/data/cache/01_raw_images/dinosaurs/1879022.jpg
diff --git a/scripts/cache/01_raw_images/dinosaurs/Diplodocus-Dieter-Braun-Affiche.webp.png b/resources/data/cache/01_raw_images/dinosaurs/Diplodocus-Dieter-Braun-Affiche.webp.png
similarity index 100%
rename from scripts/cache/01_raw_images/dinosaurs/Diplodocus-Dieter-Braun-Affiche.webp.png
rename to resources/data/cache/01_raw_images/dinosaurs/Diplodocus-Dieter-Braun-Affiche.webp.png
diff --git a/scripts/cache/01_raw_images/ghibli/1882483.jpg b/resources/data/cache/01_raw_images/ghibli/1882483.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/ghibli/1882483.jpg
rename to resources/data/cache/01_raw_images/ghibli/1882483.jpg
diff --git a/scripts/cache/01_raw_images/ghibli/flat,750x,075,f-pad,750x1000,f8f8f8-1.jpg b/resources/data/cache/01_raw_images/ghibli/flat,750x,075,f-pad,750x1000,f8f8f8-1.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/ghibli/flat,750x,075,f-pad,750x1000,f8f8f8-1.jpg
rename to resources/data/cache/01_raw_images/ghibli/flat,750x,075,f-pad,750x1000,f8f8f8-1.jpg
diff --git a/scripts/cache/01_raw_images/ghibli/flat,750x,075,f-pad,750x1000,f8f8f8.jpg b/resources/data/cache/01_raw_images/ghibli/flat,750x,075,f-pad,750x1000,f8f8f8.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/ghibli/flat,750x,075,f-pad,750x1000,f8f8f8.jpg
rename to resources/data/cache/01_raw_images/ghibli/flat,750x,075,f-pad,750x1000,f8f8f8.jpg
diff --git a/scripts/cache/01_raw_images/ghibli/totoro.png b/resources/data/cache/01_raw_images/ghibli/totoro.png
similarity index 100%
rename from scripts/cache/01_raw_images/ghibli/totoro.png
rename to resources/data/cache/01_raw_images/ghibli/totoro.png
diff --git a/scripts/cache/01_raw_images/harrypotter/1893827.jpg b/resources/data/cache/01_raw_images/harrypotter/1893827.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/harrypotter/1893827.jpg
rename to resources/data/cache/01_raw_images/harrypotter/1893827.jpg
diff --git a/scripts/cache/01_raw_images/harrypotter/1893838.jpg b/resources/data/cache/01_raw_images/harrypotter/1893838.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/harrypotter/1893838.jpg
rename to resources/data/cache/01_raw_images/harrypotter/1893838.jpg
diff --git a/scripts/cache/01_raw_images/nature/11-74513.jpg b/resources/data/cache/01_raw_images/nature/11-74513.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/nature/11-74513.jpg
rename to resources/data/cache/01_raw_images/nature/11-74513.jpg
diff --git a/scripts/cache/01_raw_images/nature/5-42180.jpg b/resources/data/cache/01_raw_images/nature/5-42180.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/nature/5-42180.jpg
rename to resources/data/cache/01_raw_images/nature/5-42180.jpg
diff --git a/scripts/cache/01_raw_images/nature/8-43315.jpg b/resources/data/cache/01_raw_images/nature/8-43315.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/nature/8-43315.jpg
rename to resources/data/cache/01_raw_images/nature/8-43315.jpg
diff --git a/scripts/cache/01_raw_images/personal/00b6f4b4509f4dd42ba672d7d98e8b12.jpg b/resources/data/cache/01_raw_images/personal/00b6f4b4509f4dd42ba672d7d98e8b12.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/00b6f4b4509f4dd42ba672d7d98e8b12.jpg
rename to resources/data/cache/01_raw_images/personal/00b6f4b4509f4dd42ba672d7d98e8b12.jpg
diff --git a/scripts/cache/01_raw_images/personal/02db820b7bb1747485d006a6c31f3b26.jpg b/resources/data/cache/01_raw_images/personal/02db820b7bb1747485d006a6c31f3b26.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/02db820b7bb1747485d006a6c31f3b26.jpg
rename to resources/data/cache/01_raw_images/personal/02db820b7bb1747485d006a6c31f3b26.jpg
diff --git a/scripts/cache/01_raw_images/personal/03a5166c2c85336e307c6f7cc19aaa53.jpg b/resources/data/cache/01_raw_images/personal/03a5166c2c85336e307c6f7cc19aaa53.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/03a5166c2c85336e307c6f7cc19aaa53.jpg
rename to resources/data/cache/01_raw_images/personal/03a5166c2c85336e307c6f7cc19aaa53.jpg
diff --git a/scripts/cache/01_raw_images/personal/0e6b73b5932cd51d9e73b4ee38a15d39.jpg b/resources/data/cache/01_raw_images/personal/0e6b73b5932cd51d9e73b4ee38a15d39.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/0e6b73b5932cd51d9e73b4ee38a15d39.jpg
rename to resources/data/cache/01_raw_images/personal/0e6b73b5932cd51d9e73b4ee38a15d39.jpg
diff --git a/scripts/cache/01_raw_images/personal/1a586eb5b865bc350345057318a5728f.jpg b/resources/data/cache/01_raw_images/personal/1a586eb5b865bc350345057318a5728f.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/1a586eb5b865bc350345057318a5728f.jpg
rename to resources/data/cache/01_raw_images/personal/1a586eb5b865bc350345057318a5728f.jpg
diff --git a/scripts/cache/01_raw_images/personal/20190519_125238.jpg b/resources/data/cache/01_raw_images/personal/20190519_125238.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20190519_125238.jpg
rename to resources/data/cache/01_raw_images/personal/20190519_125238.jpg
diff --git a/scripts/cache/01_raw_images/personal/20190722_140748.jpg b/resources/data/cache/01_raw_images/personal/20190722_140748.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20190722_140748.jpg
rename to resources/data/cache/01_raw_images/personal/20190722_140748.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200606_095643.jpg b/resources/data/cache/01_raw_images/personal/20200606_095643.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200606_095643.jpg
rename to resources/data/cache/01_raw_images/personal/20200606_095643.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200606_102141.jpg b/resources/data/cache/01_raw_images/personal/20200606_102141.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200606_102141.jpg
rename to resources/data/cache/01_raw_images/personal/20200606_102141.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200618_120810.jpg b/resources/data/cache/01_raw_images/personal/20200618_120810.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200618_120810.jpg
rename to resources/data/cache/01_raw_images/personal/20200618_120810.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200618_123346.jpg b/resources/data/cache/01_raw_images/personal/20200618_123346.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200618_123346.jpg
rename to resources/data/cache/01_raw_images/personal/20200618_123346.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200619_142852.jpg b/resources/data/cache/01_raw_images/personal/20200619_142852.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200619_142852.jpg
rename to resources/data/cache/01_raw_images/personal/20200619_142852.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200619_213922_1.jpg b/resources/data/cache/01_raw_images/personal/20200619_213922_1.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200619_213922_1.jpg
rename to resources/data/cache/01_raw_images/personal/20200619_213922_1.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200623_100706.jpg b/resources/data/cache/01_raw_images/personal/20200623_100706.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200623_100706.jpg
rename to resources/data/cache/01_raw_images/personal/20200623_100706.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200625_124835.jpg b/resources/data/cache/01_raw_images/personal/20200625_124835.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200625_124835.jpg
rename to resources/data/cache/01_raw_images/personal/20200625_124835.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200626_124209.jpg b/resources/data/cache/01_raw_images/personal/20200626_124209.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200626_124209.jpg
rename to resources/data/cache/01_raw_images/personal/20200626_124209.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200630_101026.jpg b/resources/data/cache/01_raw_images/personal/20200630_101026.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200630_101026.jpg
rename to resources/data/cache/01_raw_images/personal/20200630_101026.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200630_101557.jpg b/resources/data/cache/01_raw_images/personal/20200630_101557.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200630_101557.jpg
rename to resources/data/cache/01_raw_images/personal/20200630_101557.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200630_101758.jpg b/resources/data/cache/01_raw_images/personal/20200630_101758.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200630_101758.jpg
rename to resources/data/cache/01_raw_images/personal/20200630_101758.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200701_144641.jpg b/resources/data/cache/01_raw_images/personal/20200701_144641.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200701_144641.jpg
rename to resources/data/cache/01_raw_images/personal/20200701_144641.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200705_093806.jpg b/resources/data/cache/01_raw_images/personal/20200705_093806.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200705_093806.jpg
rename to resources/data/cache/01_raw_images/personal/20200705_093806.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200717_161441.jpg b/resources/data/cache/01_raw_images/personal/20200717_161441.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200717_161441.jpg
rename to resources/data/cache/01_raw_images/personal/20200717_161441.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200722_005935.jpg b/resources/data/cache/01_raw_images/personal/20200722_005935.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200722_005935.jpg
rename to resources/data/cache/01_raw_images/personal/20200722_005935.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200724_163236.jpg b/resources/data/cache/01_raw_images/personal/20200724_163236.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200724_163236.jpg
rename to resources/data/cache/01_raw_images/personal/20200724_163236.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200724_224026.jpg b/resources/data/cache/01_raw_images/personal/20200724_224026.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200724_224026.jpg
rename to resources/data/cache/01_raw_images/personal/20200724_224026.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200727_094000.jpg b/resources/data/cache/01_raw_images/personal/20200727_094000.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200727_094000.jpg
rename to resources/data/cache/01_raw_images/personal/20200727_094000.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200728_111054.jpg b/resources/data/cache/01_raw_images/personal/20200728_111054.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200728_111054.jpg
rename to resources/data/cache/01_raw_images/personal/20200728_111054.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200728_140354.jpg b/resources/data/cache/01_raw_images/personal/20200728_140354.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200728_140354.jpg
rename to resources/data/cache/01_raw_images/personal/20200728_140354.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200731_204947.jpg b/resources/data/cache/01_raw_images/personal/20200731_204947.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200731_204947.jpg
rename to resources/data/cache/01_raw_images/personal/20200731_204947.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200801_185650.jpg b/resources/data/cache/01_raw_images/personal/20200801_185650.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200801_185650.jpg
rename to resources/data/cache/01_raw_images/personal/20200801_185650.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200802_163415.jpg b/resources/data/cache/01_raw_images/personal/20200802_163415.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200802_163415.jpg
rename to resources/data/cache/01_raw_images/personal/20200802_163415.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200802_163639.jpg b/resources/data/cache/01_raw_images/personal/20200802_163639.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200802_163639.jpg
rename to resources/data/cache/01_raw_images/personal/20200802_163639.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200802_163829.jpg b/resources/data/cache/01_raw_images/personal/20200802_163829.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200802_163829.jpg
rename to resources/data/cache/01_raw_images/personal/20200802_163829.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200802_164012.jpg b/resources/data/cache/01_raw_images/personal/20200802_164012.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200802_164012.jpg
rename to resources/data/cache/01_raw_images/personal/20200802_164012.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200802_172820.jpg b/resources/data/cache/01_raw_images/personal/20200802_172820.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200802_172820.jpg
rename to resources/data/cache/01_raw_images/personal/20200802_172820.jpg
diff --git a/scripts/cache/01_raw_images/personal/20200802_173007.jpg b/resources/data/cache/01_raw_images/personal/20200802_173007.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/20200802_173007.jpg
rename to resources/data/cache/01_raw_images/personal/20200802_173007.jpg
diff --git a/scripts/cache/01_raw_images/personal/2a16dbaeeeef30a6b5479a8ba00aab9b.jpg b/resources/data/cache/01_raw_images/personal/2a16dbaeeeef30a6b5479a8ba00aab9b.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/2a16dbaeeeef30a6b5479a8ba00aab9b.jpg
rename to resources/data/cache/01_raw_images/personal/2a16dbaeeeef30a6b5479a8ba00aab9b.jpg
diff --git a/scripts/cache/01_raw_images/personal/31f577dd1d3c6158f20bd2e3668a6d74.jpg b/resources/data/cache/01_raw_images/personal/31f577dd1d3c6158f20bd2e3668a6d74.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/31f577dd1d3c6158f20bd2e3668a6d74.jpg
rename to resources/data/cache/01_raw_images/personal/31f577dd1d3c6158f20bd2e3668a6d74.jpg
diff --git a/scripts/cache/01_raw_images/personal/75347476e42316830b3bd4276abbac29.jpg b/resources/data/cache/01_raw_images/personal/75347476e42316830b3bd4276abbac29.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/75347476e42316830b3bd4276abbac29.jpg
rename to resources/data/cache/01_raw_images/personal/75347476e42316830b3bd4276abbac29.jpg
diff --git a/scripts/cache/01_raw_images/personal/81c29851faf31e5adbe748fa0e450aa6.jpg b/resources/data/cache/01_raw_images/personal/81c29851faf31e5adbe748fa0e450aa6.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/81c29851faf31e5adbe748fa0e450aa6.jpg
rename to resources/data/cache/01_raw_images/personal/81c29851faf31e5adbe748fa0e450aa6.jpg
diff --git a/scripts/cache/01_raw_images/personal/85a0304dbc66bba40def5200ab3a07c6.jpg b/resources/data/cache/01_raw_images/personal/85a0304dbc66bba40def5200ab3a07c6.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/85a0304dbc66bba40def5200ab3a07c6.jpg
rename to resources/data/cache/01_raw_images/personal/85a0304dbc66bba40def5200ab3a07c6.jpg
diff --git a/scripts/cache/01_raw_images/personal/861e579dd0c244ec7fae3eaf3eff16b2.jpg b/resources/data/cache/01_raw_images/personal/861e579dd0c244ec7fae3eaf3eff16b2.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/861e579dd0c244ec7fae3eaf3eff16b2.jpg
rename to resources/data/cache/01_raw_images/personal/861e579dd0c244ec7fae3eaf3eff16b2.jpg
diff --git a/scripts/cache/01_raw_images/personal/a2a679f3f3ae4e3764fbfede13d28099.jpg b/resources/data/cache/01_raw_images/personal/a2a679f3f3ae4e3764fbfede13d28099.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/a2a679f3f3ae4e3764fbfede13d28099.jpg
rename to resources/data/cache/01_raw_images/personal/a2a679f3f3ae4e3764fbfede13d28099.jpg
diff --git a/scripts/cache/01_raw_images/personal/bab9d1cdac549407377e8309c287b7fa.jpg b/resources/data/cache/01_raw_images/personal/bab9d1cdac549407377e8309c287b7fa.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/bab9d1cdac549407377e8309c287b7fa.jpg
rename to resources/data/cache/01_raw_images/personal/bab9d1cdac549407377e8309c287b7fa.jpg
diff --git a/scripts/cache/01_raw_images/personal/bc428f04fadcf6e8c760e7dc7152f42b.jpg b/resources/data/cache/01_raw_images/personal/bc428f04fadcf6e8c760e7dc7152f42b.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/bc428f04fadcf6e8c760e7dc7152f42b.jpg
rename to resources/data/cache/01_raw_images/personal/bc428f04fadcf6e8c760e7dc7152f42b.jpg
diff --git a/scripts/cache/01_raw_images/personal/e9f405ebcc2304a120872bc75732c476.jpg b/resources/data/cache/01_raw_images/personal/e9f405ebcc2304a120872bc75732c476.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/e9f405ebcc2304a120872bc75732c476.jpg
rename to resources/data/cache/01_raw_images/personal/e9f405ebcc2304a120872bc75732c476.jpg
diff --git a/scripts/cache/01_raw_images/personal/ed8fb705383eb8aac33197110212124e.jpg b/resources/data/cache/01_raw_images/personal/ed8fb705383eb8aac33197110212124e.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/ed8fb705383eb8aac33197110212124e.jpg
rename to resources/data/cache/01_raw_images/personal/ed8fb705383eb8aac33197110212124e.jpg
diff --git a/scripts/cache/01_raw_images/personal/efaaa9e5d4a4f1698715507178ca4e3d.jpg b/resources/data/cache/01_raw_images/personal/efaaa9e5d4a4f1698715507178ca4e3d.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/efaaa9e5d4a4f1698715507178ca4e3d.jpg
rename to resources/data/cache/01_raw_images/personal/efaaa9e5d4a4f1698715507178ca4e3d.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-1dc365a57b9eea70d0d21fb6d1644b72.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-1dc365a57b9eea70d0d21fb6d1644b72.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-1dc365a57b9eea70d0d21fb6d1644b72.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-1dc365a57b9eea70d0d21fb6d1644b72.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-3aaafbe17a165937a260c2f38d30b648.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-3aaafbe17a165937a260c2f38d30b648.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-3aaafbe17a165937a260c2f38d30b648.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-3aaafbe17a165937a260c2f38d30b648.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-512be9e273586e00e10e2906d7446098.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-512be9e273586e00e10e2906d7446098.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-512be9e273586e00e10e2906d7446098.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-512be9e273586e00e10e2906d7446098.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-6bd2d51e9d3b639f679ccb388eb54146.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-6bd2d51e9d3b639f679ccb388eb54146.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-6bd2d51e9d3b639f679ccb388eb54146.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-6bd2d51e9d3b639f679ccb388eb54146.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-95a0a96a0220064f99f031fb9cfcaf75.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-95a0a96a0220064f99f031fb9cfcaf75.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-95a0a96a0220064f99f031fb9cfcaf75.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-95a0a96a0220064f99f031fb9cfcaf75.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-bc466b6c01a8be0039aedc535e4c2ebb.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-bc466b6c01a8be0039aedc535e4c2ebb.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-bc466b6c01a8be0039aedc535e4c2ebb.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-bc466b6c01a8be0039aedc535e4c2ebb.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-ca7ef660c832cbca8e82d92f67432175.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-ca7ef660c832cbca8e82d92f67432175.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-ca7ef660c832cbca8e82d92f67432175.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-ca7ef660c832cbca8e82d92f67432175.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-daeb8c1a86ad6406bad079886d376cb6.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-daeb8c1a86ad6406bad079886d376cb6.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-daeb8c1a86ad6406bad079886d376cb6.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-daeb8c1a86ad6406bad079886d376cb6.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-ee371f46003d800ad2db17558c04464e.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-ee371f46003d800ad2db17558c04464e.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-ee371f46003d800ad2db17558c04464e.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-ee371f46003d800ad2db17558c04464e.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-fa7601fd804ab472e8cb91e5a638e2ef.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-fa7601fd804ab472e8cb91e5a638e2ef.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-fa7601fd804ab472e8cb91e5a638e2ef.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-fa7601fd804ab472e8cb91e5a638e2ef.jpg
diff --git a/scripts/cache/01_raw_images/personal/perso-source-640-ff4c9c993ff0a303c924aa35d0386e15.jpg b/resources/data/cache/01_raw_images/personal/perso-source-640-ff4c9c993ff0a303c924aa35d0386e15.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/personal/perso-source-640-ff4c9c993ff0a303c924aa35d0386e15.jpg
rename to resources/data/cache/01_raw_images/personal/perso-source-640-ff4c9c993ff0a303c924aa35d0386e15.jpg
diff --git a/scripts/cache/01_raw_images/sea/1886961.jpg b/resources/data/cache/01_raw_images/sea/1886961.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/sea/1886961.jpg
rename to resources/data/cache/01_raw_images/sea/1886961.jpg
diff --git a/scripts/cache/01_raw_images/sea/IADX6-086.jpg b/resources/data/cache/01_raw_images/sea/IADX6-086.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/sea/IADX6-086.jpg
rename to resources/data/cache/01_raw_images/sea/IADX6-086.jpg
diff --git a/scripts/cache/01_raw_images/space/11916-large.jpg b/resources/data/cache/01_raw_images/space/11916-large.jpg
similarity index 100%
rename from scripts/cache/01_raw_images/space/11916-large.jpg
rename to resources/data/cache/01_raw_images/space/11916-large.jpg
diff --git a/scripts/cache/02_optimized_images/.gitkeep b/resources/data/cache/02_optimized_images/.gitkeep
similarity index 100%
rename from scripts/cache/02_optimized_images/.gitkeep
rename to resources/data/cache/02_optimized_images/.gitkeep
diff --git a/scripts/03_check_sliced_images.sh b/resources/data/check_sliced_images.sh
similarity index 100%
rename from scripts/03_check_sliced_images.sh
rename to resources/data/check_sliced_images.sh
diff --git a/icons/build_game_icons.sh b/resources/ui/build_ui_resources.sh
similarity index 50%
rename from icons/build_game_icons.sh
rename to resources/ui/build_ui_resources.sh
index a4c7b7573d1c165b3b3798d07458921e6aed7ad2..4f365ede7d83140ce6309a3083580f2662b30990 100755
--- a/icons/build_game_icons.sh
+++ b/resources/ui/build_ui_resources.sh
@@ -6,7 +6,7 @@ command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not ins
 command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; }
 
 CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
-BASE_DIR="$(dirname "${CURRENT_DIR}")"
+BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")"
 ASSETS_DIR="${BASE_DIR}/assets"
 
 OPTIPNG_OPTIONS="-preserve -quiet -o7"
@@ -14,20 +14,23 @@ ICON_SIZE=192
 
 #######################################################
 
-# Game images
-AVAILABLE_GAME_IMAGES="
-  button_back
-  button_shuffle
-  button_random_pick
-  game_win
-  placeholder
-  tip_hidden
-"
-
-# Settings images
-AVAILABLES_GAME_SETTINGS="
-  difficulty:3x3,4x4,5x5
-"
+# Game images (svg files found in `images` folder)
+AVAILABLE_GAME_IMAGES=""
+if [ -d "${CURRENT_DIR}/images" ]; then
+  AVAILABLE_GAME_IMAGES="$(find "${CURRENT_DIR}/images" -type f -name "*.svg" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort)"
+fi
+
+# Skins (subfolders found in `skins` folder)
+AVAILABLE_SKINS=""
+if [ -d "${CURRENT_DIR}/skins" ]; then
+  AVAILABLE_SKINS="$(find "${CURRENT_DIR}/skins" -mindepth 1 -type d | awk -F/ '{print $NF}')"
+fi
+
+# Images per skin (svg files found recursively in `skins` folder and subfolders)
+SKIN_IMAGES=""
+if [ -d "${CURRENT_DIR}/skins" ]; then
+  SKIN_IMAGES="$(find "${CURRENT_DIR}/skins" -type f -name "*.svg" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort | uniq)"
+fi
 
 #######################################################
 
@@ -49,7 +52,7 @@ function optimize_svg() {
 }
 
 # build icons
-function build_icon() {
+function build_image() {
   SOURCE="$1"
   TARGET="$2"
 
@@ -62,44 +65,46 @@ function build_icon() {
 
   optimize_svg "${SOURCE}"
 
+  mkdir -p "$(dirname "${TARGET}")"
+
   inkscape \
       --export-width=${ICON_SIZE} \
       --export-height=${ICON_SIZE} \
       --export-filename=${TARGET} \
-      ${SOURCE}
+      "${SOURCE}"
 
-  optipng ${OPTIPNG_OPTIONS} ${TARGET}
+  optipng ${OPTIPNG_OPTIONS} "${TARGET}"
 }
 
-function build_settings_icons() {
-  INPUT_STRING="$1"
+function build_image_for_skin() {
+  SKIN_CODE="$1"
 
-  SETTING_NAME="$(echo "${INPUT_STRING}" | cut -d":" -f1)"
-  SETTING_VALUES="$(echo "${INPUT_STRING}" | cut -d":" -f2 | tr "," " ")"
-
-  for SETTING_VALUE in ${SETTING_VALUES}
+  # skin images
+  for SKIN_IMAGE in ${SKIN_IMAGES}
   do
-    SETTING_CODE="${SETTING_NAME}_${SETTING_VALUE}"
-    build_icon ${CURRENT_DIR}/${SETTING_CODE}.svg ${ASSETS_DIR}/icons/${SETTING_CODE}.png
+    build_image ${CURRENT_DIR}/skins/${SKIN_CODE}/${SKIN_IMAGE}.svg ${ASSETS_DIR}/skins/${SKIN_CODE}_${SKIN_IMAGE}.png
   done
 }
 
 #######################################################
 
-# Create output folders
-mkdir -p ${ASSETS_DIR}/icons
-
 # Delete existing generated images
-find ${ASSETS_DIR}/icons -type f -name "*.png" -delete
+if [ -d "${ASSETS_DIR}/ui" ]; then
+  find ${ASSETS_DIR}/ui -type f -name "*.png" -delete
+fi
+if [ -d "${ASSETS_DIR}/skins" ]; then
+  find ${ASSETS_DIR}/skins -type f -name "*.png" -delete
+fi
 
 # build game images
 for GAME_IMAGE in ${AVAILABLE_GAME_IMAGES}
 do
-  build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png
+  build_image ${CURRENT_DIR}/images/${GAME_IMAGE}.svg ${ASSETS_DIR}/ui/${GAME_IMAGE}.png
 done
 
-# build settings images
-for GAME_SETTING in ${AVAILABLES_GAME_SETTINGS}
+# build skins images
+for SKIN in ${AVAILABLE_SKINS}
 do
-  build_settings_icons "${GAME_SETTING}"
+  build_image_for_skin "${SKIN}"
 done
+
diff --git a/icons/button_back.svg b/resources/ui/images/button_back.svg
similarity index 100%
rename from icons/button_back.svg
rename to resources/ui/images/button_back.svg
diff --git a/resources/ui/images/button_delete_saved_game.svg b/resources/ui/images/button_delete_saved_game.svg
new file mode 100644
index 0000000000000000000000000000000000000000..ac7eefef476f761903fe781b8c86d0c94323550a
--- /dev/null
+++ b/resources/ui/images/button_delete_saved_game.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#ee7d49" stroke="#fff" stroke-width=".238"/><path d="m61.07 35.601-1.7399 27.837c-0.13442 2.1535-1.9205 3.8312-4.0781 3.8312h-16.84c-2.1576 0-3.9437-1.6777-4.0781-3.8312l-1.7399-27.837h-2.6176c-0.84621 0-1.5323-0.68613-1.5323-1.5323 0-0.84655 0.68613-1.5323 1.5323-1.5323h33.711c0.84621 0 1.5323 0.68578 1.5323 1.5323 0 0.84621-0.68613 1.5323-1.5323 1.5323zm-3.2617 0h-21.953l1.4715 26.674c0.05985 1.0829 0.95531 1.9305 2.0403 1.9305h14.929c1.085 0 1.9804-0.84757 2.0403-1.9305zm-10.977 3.0647c0.78977 0 1.4301 0.6403 1.4301 1.4301v19.614c0 0.78977-0.6403 1.4301-1.4301 1.4301s-1.4301-0.6403-1.4301-1.4301v-19.614c0-0.78977 0.6403-1.4301 1.4301-1.4301zm-6.1293 0c0.80004 0 1.4588 0.62935 1.495 1.4286l0.89647 19.719c0.03182 0.70016-0.50998 1.2933-1.2101 1.3255-0.01915 7.02e-4 -0.03831 1e-3 -0.05781 1e-3 -0.74462 0-1.3596-0.58215-1.4003-1.3261l-1.0757-19.719c-0.0407-0.74701 0.53188-1.3852 1.2786-1.4259 0.02462-0.0014 0.04926-2e-3 0.07388-2e-3zm12.259 0c0.74804 0 1.3541 0.60609 1.3541 1.3541 0 0.02462-3.28e-4 0.04926-0.0017 0.07388l-1.0703 19.618c-0.04379 0.80106-0.70597 1.4281-1.5081 1.4281-0.74804 0-1.3541-0.60609-1.3541-1.3541 0-0.02462 3.49e-4 -0.04925 0.0017-0.07388l1.0703-19.618c0.04379-0.80106 0.70597-1.4281 1.5081-1.4281zm-10.216-12.259h8.1728c2.2567 0 4.086 1.8293 4.086 4.086v2.0433h-16.344v-2.0433c0-2.2567 1.8293-4.086 4.086-4.086zm0.20453 3.0647c-0.67725 0-1.2259 0.54863-1.2259 1.2259v1.8388h10.215v-1.8388c0-0.67725-0.54863-1.2259-1.2259-1.2259z" fill="#fff" fill-rule="evenodd" stroke="#bd4812" stroke-width=".75383"/></svg>
diff --git a/icons/button_random_pick.svg b/resources/ui/images/button_random_pick.svg
similarity index 100%
rename from icons/button_random_pick.svg
rename to resources/ui/images/button_random_pick.svg
diff --git a/resources/ui/images/button_resume_game.svg b/resources/ui/images/button_resume_game.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6ad8b64202d0e70f898c16c520e756fe8a934add
--- /dev/null
+++ b/resources/ui/images/button_resume_game.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#49a1ee" stroke="#fff" stroke-width=".238"/><path d="m39.211 31.236c-0.84086-0.84489-2.9911-0.84489-2.9911 0v34.329c0 0.84594 2.1554 0.84594 2.9993 0l28.178-15.637c0.84392-0.84086 0.85812-2.2091 0.01623-3.053z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="6.1726"/><path d="m40.355 33.714c-0.71948-0.72294-2.5594-0.72294-2.5594 0v29.373c0 0.72383 1.8442 0.72383 2.5663 0l24.11-13.38c0.7221-0.71948 0.73426-1.8902 0.01389-2.6124z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.225"/><path d="m28.369 66.919v-37.591" fill="#105ca2" stroke="#105ca2" stroke-linecap="round" stroke-width="4.0337"/></svg>
diff --git a/icons/button_shuffle.svg b/resources/ui/images/button_shuffle.svg
similarity index 100%
rename from icons/button_shuffle.svg
rename to resources/ui/images/button_shuffle.svg
diff --git a/resources/ui/images/button_start.svg b/resources/ui/images/button_start.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e9d49d2172b9a0305db82779971e3c1e12f34a70
--- /dev/null
+++ b/resources/ui/images/button_start.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#49a1ee" stroke="#fff" stroke-width=".238"/><path d="m34.852 25.44c-1.1248-1.1302-4.0012-1.1302-4.0012 0v45.921c0 1.1316 2.8832 1.1316 4.0121 0l37.693-20.918c1.1289-1.1248 1.1479-2.9551 0.02171-4.084z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="8.257"/><path d="m36.382 28.754c-0.96243-0.96706-3.4236-0.96706-3.4236 0v39.292c0 0.96825 2.467 0.96825 3.4329 0l32.252-17.898c0.96594-0.96243 0.9822-2.5285 0.01858-3.4945z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.314"/></svg>
diff --git a/icons/game_win.svg b/resources/ui/images/game_win.svg
similarity index 100%
rename from icons/game_win.svg
rename to resources/ui/images/game_win.svg
diff --git a/icons/placeholder.svg b/resources/ui/images/placeholder.svg
similarity index 100%
rename from icons/placeholder.svg
rename to resources/ui/images/placeholder.svg
diff --git a/icons/tip_hidden.svg b/resources/ui/images/tip_hidden.svg
similarity index 100%
rename from icons/tip_hidden.svg
rename to resources/ui/images/tip_hidden.svg