diff --git a/CLAUDE.md b/CLAUDE.md
index 508f70d..4027865 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -19,6 +19,7 @@ Le `Makefile` est le seul point d'entrée pour les opérations système. Tenir c
- `make content-review` : régénère `docs/review/content-review.md` à partir de `data/`
- `make generate-dictation-audio` : génère l'audio provisoire des dictées (voix neuronale Piper via `.venv`, `ffmpeg` requis sur la machine) dans `assets/audio/dictations/`, sans écraser l'existant (`--force` pour régénérer)
- `make export-linux` / `make export-windows` : exports dans `build/`
+- `make screenshot SCENE=… OUT=… ARGS=…` : capture le rendu réel d'une scène dans une fenêtre (outil de développement, ouvre brièvement une fenêtre)
- `make package` : réexporte puis produit les archives distribuables (exécutable unique + licence + crédits + notice) et `SHA256SUMS` dans `dist/`
## Conventions de code
diff --git a/Makefile b/Makefile
index 7ded539..a9cc21f 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ PYRIGHT := $(VENV)/bin/pyright
PYTHON := $(VENV)/bin/python3
GD_SOURCES := autoload scripts tests
-.PHONY: setup run lint format validate-data test check content-review generate-dictation-audio export-linux export-windows package
+.PHONY: setup run lint format validate-data test check content-review generate-dictation-audio export-linux export-windows package screenshot
setup:
tools/setup.sh
@@ -45,3 +45,8 @@ export-windows:
package:
tools/package.sh
+
+# Capture d'une scene dans une vraie fenetre (voir tools/screenshot.gd) :
+# make screenshot SCENE=res://scenes/world/world_map.tscn OUT=/tmp/map.png ARGS="CE2"
+screenshot:
+ $(GODOT_BIN) --path . -s res://tools/screenshot.gd -- $(SCENE) $(OUT) $(ARGS)
diff --git a/assets/CREDITS.md b/assets/CREDITS.md
index 5dd1e60..df9f4bd 100644
--- a/assets/CREDITS.md
+++ b/assets/CREDITS.md
@@ -1,10 +1,12 @@
# Sources des assets provisoires
-Tous les graphismes et sons actuels sont provisoires, crees pour ce projet, sans asset tiers.
+Tous les graphismes et sons actuels sont provisoires et crees pour ce projet ; seules la police et les voix de synthese reposent sur des composants tiers libres, listes ci-dessous.
| Fichier | Origine | Licence |
|---|---|---|
| `icon.svg` | Forme geometrique simple, creee pour ce projet | AGPL-3.0 (code et assets du depot) |
+| `fonts/AtkinsonHyperlegible-*.ttf` | Atkinson Hyperlegible, police conçue pour la lisibilité (Braille Institute) | SIL Open Font License 1.1 (`fonts/OFL.txt`) |
+| `environments/*.svg`, `characters/*.svg`, `interface/*.svg`, `interface/items/*.svg` | Illustrations vectorielles originales créées pour ce projet (décors, hibou Plume, étoiles, pictogrammes, objets de collection) ; provisoires | AGPL-3.0 (code et assets du dépôt) |
| `audio/dictations/*.ogg` (toutes les dictees) | Voix de synthese neuronale generee hors ligne avec Piper (modele `fr_FR-siwis-medium`, entraine sur le corpus SIWIS de l'EPFL), via `tools/generate_dictation_audio.py` | MIT (Piper) ; dataset SIWIS en CC-BY 4.0 (attribution requise) ; fichiers generes verses en AGPL-3.0 avec le reste du depot |
Chaque nouvelle dictee ajoutee a `data/dictations/` doit avoir son audio genere avec `make generate-dictation-audio`, puis une ligne ajoutee ici. Toutes les voix actuelles sont provisoires (`audio_provisional: true` dans la donnee, badge « voix provisoire » affiche a l'enfant) : a remplacer par une vraie voix humaine avant diffusion.
diff --git a/assets/characters/owl_plume.svg b/assets/characters/owl_plume.svg
new file mode 100644
index 0000000..5c16033
--- /dev/null
+++ b/assets/characters/owl_plume.svg
@@ -0,0 +1,18 @@
+
diff --git a/assets/characters/owl_plume.svg.import b/assets/characters/owl_plume.svg.import
new file mode 100644
index 0000000..4559185
--- /dev/null
+++ b/assets/characters/owl_plume.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://hokvd25q8sx8"
+path="res://.godot/imported/owl_plume.svg-5a0c30c1a3ad1c122ff173e9e224403b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/characters/owl_plume.svg"
+dest_files=["res://.godot/imported/owl_plume.svg-5a0c30c1a3ad1c122ff173e9e224403b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/environments/forest_backdrop.svg b/assets/environments/forest_backdrop.svg
new file mode 100644
index 0000000..f0cea57
--- /dev/null
+++ b/assets/environments/forest_backdrop.svg
@@ -0,0 +1,107 @@
+
diff --git a/assets/environments/forest_backdrop.svg.import b/assets/environments/forest_backdrop.svg.import
new file mode 100644
index 0000000..34fc5ae
--- /dev/null
+++ b/assets/environments/forest_backdrop.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bdho8w07lqr4h"
+path="res://.godot/imported/forest_backdrop.svg-759c497caa0fa1755bebe33bd64b97e2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/environments/forest_backdrop.svg"
+dest_files=["res://.godot/imported/forest_backdrop.svg-759c497caa0fa1755bebe33bd64b97e2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/environments/marsh_backdrop.svg b/assets/environments/marsh_backdrop.svg
new file mode 100644
index 0000000..81a307a
--- /dev/null
+++ b/assets/environments/marsh_backdrop.svg
@@ -0,0 +1,130 @@
+
diff --git a/assets/environments/marsh_backdrop.svg.import b/assets/environments/marsh_backdrop.svg.import
new file mode 100644
index 0000000..549e8df
--- /dev/null
+++ b/assets/environments/marsh_backdrop.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c3d2gu2fyacvu"
+path="res://.godot/imported/marsh_backdrop.svg-c7f73bcacccc54a2e0f9099485e3bbba.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/environments/marsh_backdrop.svg"
+dest_files=["res://.godot/imported/marsh_backdrop.svg-c7f73bcacccc54a2e0f9099485e3bbba.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/fonts/AtkinsonHyperlegible-Bold.ttf b/assets/fonts/AtkinsonHyperlegible-Bold.ttf
new file mode 100644
index 0000000..ca3f613
Binary files /dev/null and b/assets/fonts/AtkinsonHyperlegible-Bold.ttf differ
diff --git a/assets/fonts/AtkinsonHyperlegible-Bold.ttf.import b/assets/fonts/AtkinsonHyperlegible-Bold.ttf.import
new file mode 100644
index 0000000..44a92a7
--- /dev/null
+++ b/assets/fonts/AtkinsonHyperlegible-Bold.ttf.import
@@ -0,0 +1,36 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://bpcx8adosnev6"
+path="res://.godot/imported/AtkinsonHyperlegible-Bold.ttf-88b8e4bce39666c760f25dd9a57aebce.fontdata"
+
+[deps]
+
+source_file="res://assets/fonts/AtkinsonHyperlegible-Bold.ttf"
+dest_files=["res://.godot/imported/AtkinsonHyperlegible-Bold.ttf-88b8e4bce39666c760f25dd9a57aebce.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+disable_embedded_bitmaps=true
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+modulate_color_glyphs=false
+hinting=1
+subpixel_positioning=4
+keep_rounding_remainders=true
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/assets/fonts/AtkinsonHyperlegible-Regular.ttf b/assets/fonts/AtkinsonHyperlegible-Regular.ttf
new file mode 100644
index 0000000..b3cc420
Binary files /dev/null and b/assets/fonts/AtkinsonHyperlegible-Regular.ttf differ
diff --git a/assets/fonts/AtkinsonHyperlegible-Regular.ttf.import b/assets/fonts/AtkinsonHyperlegible-Regular.ttf.import
new file mode 100644
index 0000000..2c6ebcf
--- /dev/null
+++ b/assets/fonts/AtkinsonHyperlegible-Regular.ttf.import
@@ -0,0 +1,36 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://bjb7qwgg76e8t"
+path="res://.godot/imported/AtkinsonHyperlegible-Regular.ttf-b1b81e810f770645a511deee33c11e03.fontdata"
+
+[deps]
+
+source_file="res://assets/fonts/AtkinsonHyperlegible-Regular.ttf"
+dest_files=["res://.godot/imported/AtkinsonHyperlegible-Regular.ttf-b1b81e810f770645a511deee33c11e03.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+disable_embedded_bitmaps=true
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+modulate_color_glyphs=false
+hinting=1
+subpixel_positioning=4
+keep_rounding_remainders=true
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/assets/fonts/OFL.txt b/assets/fonts/OFL.txt
new file mode 100644
index 0000000..2befc89
--- /dev/null
+++ b/assets/fonts/OFL.txt
@@ -0,0 +1,92 @@
+Copyright 2020 Braille Institute of America, Inc.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/assets/interface/icon_correct.svg b/assets/interface/icon_correct.svg
new file mode 100644
index 0000000..1f8d3d0
--- /dev/null
+++ b/assets/interface/icon_correct.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/icon_correct.svg.import b/assets/interface/icon_correct.svg.import
new file mode 100644
index 0000000..97eea82
--- /dev/null
+++ b/assets/interface/icon_correct.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://qrmkbgkfqrpp"
+path="res://.godot/imported/icon_correct.svg-fe6bcc61481d18487d38d1ee8d1f1713.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/icon_correct.svg"
+dest_files=["res://.godot/imported/icon_correct.svg-fe6bcc61481d18487d38d1ee8d1f1713.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/icon_dictation.svg b/assets/interface/icon_dictation.svg
new file mode 100644
index 0000000..1606cd5
--- /dev/null
+++ b/assets/interface/icon_dictation.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/icon_dictation.svg.import b/assets/interface/icon_dictation.svg.import
new file mode 100644
index 0000000..7d6453d
--- /dev/null
+++ b/assets/interface/icon_dictation.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://51fub21m6m3u"
+path="res://.godot/imported/icon_dictation.svg-2f1dc15bf770641095d391adf607b065.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/icon_dictation.svg"
+dest_files=["res://.godot/imported/icon_dictation.svg-2f1dc15bf770641095d391adf607b065.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/icon_locked.svg b/assets/interface/icon_locked.svg
new file mode 100644
index 0000000..37b3adb
--- /dev/null
+++ b/assets/interface/icon_locked.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/icon_locked.svg.import b/assets/interface/icon_locked.svg.import
new file mode 100644
index 0000000..d2adb1c
--- /dev/null
+++ b/assets/interface/icon_locked.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://t3a58bf0b1om"
+path="res://.godot/imported/icon_locked.svg-4ecadd3abe9f5993cfa09be34ca4c5b6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/icon_locked.svg"
+dest_files=["res://.godot/imported/icon_locked.svg-4ecadd3abe9f5993cfa09be34ca4c5b6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/icon_multiple_choice.svg b/assets/interface/icon_multiple_choice.svg
new file mode 100644
index 0000000..a09ef56
--- /dev/null
+++ b/assets/interface/icon_multiple_choice.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/icon_multiple_choice.svg.import b/assets/interface/icon_multiple_choice.svg.import
new file mode 100644
index 0000000..30204cc
--- /dev/null
+++ b/assets/interface/icon_multiple_choice.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://evabcqff8aym"
+path="res://.godot/imported/icon_multiple_choice.svg-f1deff20f673f35ff192065f87228616.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/icon_multiple_choice.svg"
+dest_files=["res://.godot/imported/icon_multiple_choice.svg-f1deff20f673f35ff192065f87228616.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/icon_sentence_correction.svg b/assets/interface/icon_sentence_correction.svg
new file mode 100644
index 0000000..41e18fe
--- /dev/null
+++ b/assets/interface/icon_sentence_correction.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/icon_sentence_correction.svg.import b/assets/interface/icon_sentence_correction.svg.import
new file mode 100644
index 0000000..29f31c3
--- /dev/null
+++ b/assets/interface/icon_sentence_correction.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cxba7g3fgfls4"
+path="res://.godot/imported/icon_sentence_correction.svg-5ec7eb26565706514d6eb77c1829b1eb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/icon_sentence_correction.svg"
+dest_files=["res://.godot/imported/icon_sentence_correction.svg-5ec7eb26565706514d6eb77c1829b1eb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/icon_try_again.svg b/assets/interface/icon_try_again.svg
new file mode 100644
index 0000000..b17013c
--- /dev/null
+++ b/assets/interface/icon_try_again.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/icon_try_again.svg.import b/assets/interface/icon_try_again.svg.import
new file mode 100644
index 0000000..69942f1
--- /dev/null
+++ b/assets/interface/icon_try_again.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dgxhl6rvvku8f"
+path="res://.godot/imported/icon_try_again.svg-6c536e074f8e778a026b608a4e19c11e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/icon_try_again.svg"
+dest_files=["res://.godot/imported/icon_try_again.svg-6c536e074f8e778a026b608a4e19c11e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/items/item_carte_ancienne.svg b/assets/interface/items/item_carte_ancienne.svg
new file mode 100644
index 0000000..e237514
--- /dev/null
+++ b/assets/interface/items/item_carte_ancienne.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/items/item_carte_ancienne.svg.import b/assets/interface/items/item_carte_ancienne.svg.import
new file mode 100644
index 0000000..ebfa55d
--- /dev/null
+++ b/assets/interface/items/item_carte_ancienne.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cmqns71qq7t6r"
+path="res://.godot/imported/item_carte_ancienne.svg-207f863c1e1fa43cbb234f96b29ce806.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/items/item_carte_ancienne.svg"
+dest_files=["res://.godot/imported/item_carte_ancienne.svg-207f863c1e1fa43cbb234f96b29ce806.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/items/item_cle_argentee.svg b/assets/interface/items/item_cle_argentee.svg
new file mode 100644
index 0000000..8ea91d8
--- /dev/null
+++ b/assets/interface/items/item_cle_argentee.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/items/item_cle_argentee.svg.import b/assets/interface/items/item_cle_argentee.svg.import
new file mode 100644
index 0000000..415b599
--- /dev/null
+++ b/assets/interface/items/item_cle_argentee.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b3i1vdgp734hf"
+path="res://.godot/imported/item_cle_argentee.svg-7b2fa6b82e4ad27ec9adf3c465385bb3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/items/item_cle_argentee.svg"
+dest_files=["res://.godot/imported/item_cle_argentee.svg-7b2fa6b82e4ad27ec9adf3c465385bb3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/items/item_couronne_feuilles.svg b/assets/interface/items/item_couronne_feuilles.svg
new file mode 100644
index 0000000..8e0cd9e
--- /dev/null
+++ b/assets/interface/items/item_couronne_feuilles.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/items/item_couronne_feuilles.svg.import b/assets/interface/items/item_couronne_feuilles.svg.import
new file mode 100644
index 0000000..77aa3e4
--- /dev/null
+++ b/assets/interface/items/item_couronne_feuilles.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://blxqc6nww8i31"
+path="res://.godot/imported/item_couronne_feuilles.svg-88412268c71dd68b45cbfbd5d18901db.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/items/item_couronne_feuilles.svg"
+dest_files=["res://.godot/imported/item_couronne_feuilles.svg-88412268c71dd68b45cbfbd5d18901db.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/items/item_cristal_echo.svg b/assets/interface/items/item_cristal_echo.svg
new file mode 100644
index 0000000..a503021
--- /dev/null
+++ b/assets/interface/items/item_cristal_echo.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/items/item_cristal_echo.svg.import b/assets/interface/items/item_cristal_echo.svg.import
new file mode 100644
index 0000000..d13a935
--- /dev/null
+++ b/assets/interface/items/item_cristal_echo.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c41gjir31el76"
+path="res://.godot/imported/item_cristal_echo.svg-9bcfe1f35f228b0dc193cfe8fdb9e9cc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/items/item_cristal_echo.svg"
+dest_files=["res://.godot/imported/item_cristal_echo.svg-9bcfe1f35f228b0dc193cfe8fdb9e9cc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/items/item_graine_magique.svg b/assets/interface/items/item_graine_magique.svg
new file mode 100644
index 0000000..6dcb872
--- /dev/null
+++ b/assets/interface/items/item_graine_magique.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/items/item_graine_magique.svg.import b/assets/interface/items/item_graine_magique.svg.import
new file mode 100644
index 0000000..c1de9cf
--- /dev/null
+++ b/assets/interface/items/item_graine_magique.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://if3iy4his26n"
+path="res://.godot/imported/item_graine_magique.svg-5a1d4325cb4a33e8cfed8f37fdcffc2e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/items/item_graine_magique.svg"
+dest_files=["res://.godot/imported/item_graine_magique.svg-5a1d4325cb4a33e8cfed8f37fdcffc2e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/items/item_lanterne_foret.svg b/assets/interface/items/item_lanterne_foret.svg
new file mode 100644
index 0000000..6ca8625
--- /dev/null
+++ b/assets/interface/items/item_lanterne_foret.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/items/item_lanterne_foret.svg.import b/assets/interface/items/item_lanterne_foret.svg.import
new file mode 100644
index 0000000..0a5d92f
--- /dev/null
+++ b/assets/interface/items/item_lanterne_foret.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b3dciddplnk3f"
+path="res://.godot/imported/item_lanterne_foret.svg-1c8a8cc130821e715510a92740a99b26.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/items/item_lanterne_foret.svg"
+dest_files=["res://.godot/imported/item_lanterne_foret.svg-1c8a8cc130821e715510a92740a99b26.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/items/item_plume_doree.svg b/assets/interface/items/item_plume_doree.svg
new file mode 100644
index 0000000..1245214
--- /dev/null
+++ b/assets/interface/items/item_plume_doree.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/items/item_plume_doree.svg.import b/assets/interface/items/item_plume_doree.svg.import
new file mode 100644
index 0000000..aefa507
--- /dev/null
+++ b/assets/interface/items/item_plume_doree.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://jgnssjhtagj"
+path="res://.godot/imported/item_plume_doree.svg-74d4e5dc0a7fc3d3e0cddbe4e20a707c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/items/item_plume_doree.svg"
+dest_files=["res://.godot/imported/item_plume_doree.svg-74d4e5dc0a7fc3d3e0cddbe4e20a707c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/items/item_trophee_clairiere.svg b/assets/interface/items/item_trophee_clairiere.svg
new file mode 100644
index 0000000..ca83de3
--- /dev/null
+++ b/assets/interface/items/item_trophee_clairiere.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/items/item_trophee_clairiere.svg.import b/assets/interface/items/item_trophee_clairiere.svg.import
new file mode 100644
index 0000000..144a63f
--- /dev/null
+++ b/assets/interface/items/item_trophee_clairiere.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cnkon70qvwt8b"
+path="res://.godot/imported/item_trophee_clairiere.svg-66fdb4230e9bf038b8e39b7d230a04fb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/items/item_trophee_clairiere.svg"
+dest_files=["res://.godot/imported/item_trophee_clairiere.svg-66fdb4230e9bf038b8e39b7d230a04fb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/items/item_trophee_pont.svg b/assets/interface/items/item_trophee_pont.svg
new file mode 100644
index 0000000..0cb2a52
--- /dev/null
+++ b/assets/interface/items/item_trophee_pont.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/items/item_trophee_pont.svg.import b/assets/interface/items/item_trophee_pont.svg.import
new file mode 100644
index 0000000..e8b9835
--- /dev/null
+++ b/assets/interface/items/item_trophee_pont.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d4s28iicgsxa"
+path="res://.godot/imported/item_trophee_pont.svg-d54bc3b9c7fe8de96e743f9589ce68f4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/items/item_trophee_pont.svg"
+dest_files=["res://.godot/imported/item_trophee_pont.svg-d54bc3b9c7fe8de96e743f9589ce68f4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/items/item_trophee_pre.svg b/assets/interface/items/item_trophee_pre.svg
new file mode 100644
index 0000000..021de38
--- /dev/null
+++ b/assets/interface/items/item_trophee_pre.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/interface/items/item_trophee_pre.svg.import b/assets/interface/items/item_trophee_pre.svg.import
new file mode 100644
index 0000000..b0f1e08
--- /dev/null
+++ b/assets/interface/items/item_trophee_pre.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cyipl7ovnn3tk"
+path="res://.godot/imported/item_trophee_pre.svg-870342a1466e75042ee4ae0cca1274f3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/items/item_trophee_pre.svg"
+dest_files=["res://.godot/imported/item_trophee_pre.svg-870342a1466e75042ee4ae0cca1274f3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/star_empty.svg b/assets/interface/star_empty.svg
new file mode 100644
index 0000000..d1837cf
--- /dev/null
+++ b/assets/interface/star_empty.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/interface/star_empty.svg.import b/assets/interface/star_empty.svg.import
new file mode 100644
index 0000000..bf98fea
--- /dev/null
+++ b/assets/interface/star_empty.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dcdkxabiaebx3"
+path="res://.godot/imported/star_empty.svg-6e835b3bc52db41523b0c3d2594cd977.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/star_empty.svg"
+dest_files=["res://.godot/imported/star_empty.svg-6e835b3bc52db41523b0c3d2594cd977.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/star_full.svg b/assets/interface/star_full.svg
new file mode 100644
index 0000000..77b2551
--- /dev/null
+++ b/assets/interface/star_full.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/interface/star_full.svg.import b/assets/interface/star_full.svg.import
new file mode 100644
index 0000000..7b9c458
--- /dev/null
+++ b/assets/interface/star_full.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bhcr4bie5exhd"
+path="res://.godot/imported/star_full.svg-f0f7cbd280c649250320aebf0658d468.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/interface/star_full.svg"
+dest_files=["res://.godot/imported/star_full.svg-f0f7cbd280c649250320aebf0658d468.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/interface/theme.tres b/assets/interface/theme.tres
new file mode 100644
index 0000000..900a330
--- /dev/null
+++ b/assets/interface/theme.tres
@@ -0,0 +1,362 @@
+[gd_resource type="Theme" load_steps=20 format=3]
+
+[ext_resource type="FontFile" path="res://assets/fonts/AtkinsonHyperlegible-Regular.ttf" id="font_regular"]
+[ext_resource type="FontFile" path="res://assets/fonts/AtkinsonHyperlegible-Bold.ttf" id="font_bold"]
+
+[sub_resource type="StyleBoxFlat" id="btn_normal"]
+bg_color = Color(0.184, 0.502, 0.251, 1)
+corner_radius_top_left = 18
+corner_radius_top_right = 18
+corner_radius_bottom_right = 18
+corner_radius_bottom_left = 18
+border_color = Color(0.122, 0.361, 0.173, 1)
+border_width_left = 0
+border_width_top = 0
+border_width_right = 0
+border_width_bottom = 6
+content_margin_left = 24
+content_margin_top = 10
+content_margin_right = 24
+content_margin_bottom = 6
+
+[sub_resource type="StyleBoxFlat" id="btn_hover"]
+bg_color = Color(0.227, 0.561, 0.298, 1)
+corner_radius_top_left = 18
+corner_radius_top_right = 18
+corner_radius_bottom_right = 18
+corner_radius_bottom_left = 18
+border_color = Color(0.122, 0.361, 0.173, 1)
+border_width_left = 0
+border_width_top = 0
+border_width_right = 0
+border_width_bottom = 6
+content_margin_left = 24
+content_margin_top = 10
+content_margin_right = 24
+content_margin_bottom = 6
+
+[sub_resource type="StyleBoxFlat" id="btn_pressed"]
+bg_color = Color(1, 0.784, 0.239, 1)
+corner_radius_top_left = 18
+corner_radius_top_right = 18
+corner_radius_bottom_right = 18
+corner_radius_bottom_left = 18
+border_color = Color(0.149, 0.196, 0.290, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+content_margin_left = 24
+content_margin_top = 12
+content_margin_right = 24
+content_margin_bottom = 8
+
+[sub_resource type="StyleBoxFlat" id="btn_disabled"]
+bg_color = Color(0.78, 0.8, 0.78, 1)
+corner_radius_top_left = 18
+corner_radius_top_right = 18
+corner_radius_bottom_right = 18
+corner_radius_bottom_left = 18
+border_color = Color(0.66, 0.69, 0.66, 1)
+border_width_left = 0
+border_width_top = 0
+border_width_right = 0
+border_width_bottom = 6
+content_margin_left = 24
+content_margin_top = 10
+content_margin_right = 24
+content_margin_bottom = 6
+
+[sub_resource type="StyleBoxFlat" id="btn_focus"]
+bg_color = Color(0,0,0,0)
+draw_center = false
+corner_radius_top_left = 20
+corner_radius_top_right = 20
+corner_radius_bottom_right = 20
+corner_radius_bottom_left = 20
+border_color = Color(0.184, 0.490, 0.820, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+content_margin_left = 0
+content_margin_top = 0
+content_margin_right = 0
+content_margin_bottom = 0
+expand_margin_left = 3
+expand_margin_top = 3
+expand_margin_right = 3
+expand_margin_bottom = 3
+
+[sub_resource type="StyleBoxFlat" id="sec_normal"]
+bg_color = Color(0.992, 0.949, 0.816, 1)
+corner_radius_top_left = 18
+corner_radius_top_right = 18
+corner_radius_bottom_right = 18
+corner_radius_bottom_left = 18
+border_color = Color(0.898, 0.812, 0.604, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 6
+content_margin_left = 20
+content_margin_top = 8
+content_margin_right = 20
+content_margin_bottom = 4
+
+[sub_resource type="StyleBoxFlat" id="sec_hover"]
+bg_color = Color(1, 0.98, 0.9, 1)
+corner_radius_top_left = 18
+corner_radius_top_right = 18
+corner_radius_bottom_right = 18
+corner_radius_bottom_left = 18
+border_color = Color(0.898, 0.812, 0.604, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 6
+content_margin_left = 20
+content_margin_top = 8
+content_margin_right = 20
+content_margin_bottom = 4
+
+[sub_resource type="StyleBoxFlat" id="sec_pressed"]
+bg_color = Color(1, 0.784, 0.239, 1)
+corner_radius_top_left = 18
+corner_radius_top_right = 18
+corner_radius_bottom_right = 18
+corner_radius_bottom_left = 18
+border_color = Color(0.149, 0.196, 0.290, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+content_margin_left = 20
+content_margin_top = 10
+content_margin_right = 20
+content_margin_bottom = 6
+
+[sub_resource type="StyleBoxFlat" id="card"]
+bg_color = Color(1, 0.965, 0.878, 1)
+corner_radius_top_left = 28
+corner_radius_top_right = 28
+corner_radius_bottom_right = 28
+corner_radius_bottom_left = 28
+border_color = Color(0.898, 0.812, 0.604, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+content_margin_left = 20
+content_margin_top = 16
+content_margin_right = 20
+content_margin_bottom = 16
+shadow_color = Color(0, 0, 0, 0.28)
+shadow_size = 14
+shadow_offset = Vector2(0, 6)
+
+[sub_resource type="StyleBoxFlat" id="card_soft"]
+bg_color = Color(1, 0.965, 0.878, 1)
+corner_radius_top_left = 20
+corner_radius_top_right = 20
+corner_radius_bottom_right = 20
+corner_radius_bottom_left = 20
+border_color = Color(0.898, 0.812, 0.604, 1)
+border_width_left = 3
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 3
+content_margin_left = 14
+content_margin_top = 10
+content_margin_right = 14
+content_margin_bottom = 10
+shadow_color = Color(0, 0, 0, 0.28)
+shadow_size = 8
+shadow_offset = Vector2(0, 6)
+
+[sub_resource type="StyleBoxFlat" id="marker_normal"]
+bg_color = Color(1, 0.965, 0.878, 1)
+corner_radius_top_left = 22
+corner_radius_top_right = 22
+corner_radius_bottom_right = 22
+corner_radius_bottom_left = 22
+border_color = Color(0.898, 0.812, 0.604, 1)
+border_width_left = 3
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 7
+content_margin_left = 10
+content_margin_top = 8
+content_margin_right = 10
+content_margin_bottom = 4
+shadow_color = Color(0, 0, 0, 0.28)
+shadow_size = 6
+shadow_offset = Vector2(0, 6)
+
+[sub_resource type="StyleBoxFlat" id="marker_hover"]
+bg_color = Color(1, 0.99, 0.93, 1)
+corner_radius_top_left = 22
+corner_radius_top_right = 22
+corner_radius_bottom_right = 22
+corner_radius_bottom_left = 22
+border_color = Color(0.122, 0.361, 0.173, 1)
+border_width_left = 3
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 7
+content_margin_left = 10
+content_margin_top = 8
+content_margin_right = 10
+content_margin_bottom = 4
+shadow_color = Color(0, 0, 0, 0.28)
+shadow_size = 6
+shadow_offset = Vector2(0, 6)
+
+[sub_resource type="StyleBoxFlat" id="marker_pressed"]
+bg_color = Color(1, 0.784, 0.239, 1)
+corner_radius_top_left = 22
+corner_radius_top_right = 22
+corner_radius_bottom_right = 22
+corner_radius_bottom_left = 22
+border_color = Color(0.149, 0.196, 0.290, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+content_margin_left = 10
+content_margin_top = 10
+content_margin_right = 10
+content_margin_bottom = 6
+
+[sub_resource type="StyleBoxFlat" id="edit_normal"]
+bg_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 14
+corner_radius_top_right = 14
+corner_radius_bottom_right = 14
+corner_radius_bottom_left = 14
+border_color = Color(0.75, 0.68, 0.52, 1)
+border_width_left = 3
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 3
+content_margin_left = 16
+content_margin_top = 10
+content_margin_right = 16
+content_margin_bottom = 10
+
+[sub_resource type="StyleBoxFlat" id="edit_focus"]
+bg_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 14
+corner_radius_top_right = 14
+corner_radius_bottom_right = 14
+corner_radius_bottom_left = 14
+border_color = Color(0.184, 0.490, 0.820, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+content_margin_left = 16
+content_margin_top = 10
+content_margin_right = 16
+content_margin_bottom = 10
+
+[sub_resource type="StyleBoxFlat" id="window_panel"]
+bg_color = Color(1, 0.965, 0.878, 1)
+corner_radius_top_left = 24
+corner_radius_top_right = 24
+corner_radius_bottom_right = 24
+corner_radius_bottom_left = 24
+border_color = Color(0.898, 0.812, 0.604, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+content_margin_left = 20
+content_margin_top = 16
+content_margin_right = 20
+content_margin_bottom = 16
+shadow_color = Color(0, 0, 0, 0.28)
+shadow_size = 16
+shadow_offset = Vector2(0, 6)
+
+[sub_resource type="StyleBoxFlat" id="pill"]
+bg_color = Color(1, 0.784, 0.239, 0.95)
+corner_radius_top_left = 30
+corner_radius_top_right = 30
+corner_radius_bottom_right = 30
+corner_radius_bottom_left = 30
+border_color = Color(0.851, 0.557, 0, 1)
+border_width_left = 3
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 3
+content_margin_left = 16
+content_margin_top = 6
+content_margin_right = 16
+content_margin_bottom = 6
+
+[resource]
+default_font = ExtResource("font_regular")
+default_font_size = 22
+Label/colors/font_color = Color(0.149, 0.196, 0.290, 1)
+Label/colors/font_shadow_color = Color(0, 0, 0, 0)
+Button/colors/font_color = Color(1, 1, 1, 1)
+Button/colors/font_hover_color = Color(1, 1, 1, 1)
+Button/colors/font_pressed_color = Color(0.149, 0.196, 0.290, 1)
+Button/colors/font_focus_color = Color(1, 1, 1, 1)
+Button/colors/font_disabled_color = Color(0.36, 0.4, 0.36, 1)
+Button/fonts/font = ExtResource("font_bold")
+Button/font_sizes/font_size = 22
+Button/styles/normal = SubResource("btn_normal")
+Button/styles/hover = SubResource("btn_hover")
+Button/styles/pressed = SubResource("btn_pressed")
+Button/styles/disabled = SubResource("btn_disabled")
+Button/styles/focus = SubResource("btn_focus")
+SecondaryButton/base_type = &"Button"
+SecondaryButton/colors/font_color = Color(0.149, 0.196, 0.290, 1)
+SecondaryButton/colors/font_hover_color = Color(0.149, 0.196, 0.290, 1)
+SecondaryButton/colors/font_focus_color = Color(0.149, 0.196, 0.290, 1)
+SecondaryButton/colors/font_pressed_color = Color(0.149, 0.196, 0.290, 1)
+SecondaryButton/styles/normal = SubResource("sec_normal")
+SecondaryButton/styles/hover = SubResource("sec_hover")
+SecondaryButton/styles/pressed = SubResource("sec_pressed")
+MarkerButton/base_type = &"Button"
+MarkerButton/colors/font_color = Color(0.149, 0.196, 0.290, 1)
+MarkerButton/colors/font_hover_color = Color(0.149, 0.196, 0.290, 1)
+MarkerButton/colors/font_focus_color = Color(0.149, 0.196, 0.290, 1)
+MarkerButton/colors/font_pressed_color = Color(0.149, 0.196, 0.290, 1)
+MarkerButton/styles/normal = SubResource("marker_normal")
+MarkerButton/styles/hover = SubResource("marker_hover")
+MarkerButton/styles/pressed = SubResource("marker_pressed")
+PanelContainer/styles/panel = SubResource("card")
+Panel/styles/panel = SubResource("card")
+SoftCard/base_type = &"PanelContainer"
+SoftCard/styles/panel = SubResource("card_soft")
+Pill/base_type = &"PanelContainer"
+Pill/styles/panel = SubResource("pill")
+LineEdit/colors/font_color = Color(0.149, 0.196, 0.290, 1)
+LineEdit/colors/font_placeholder_color = Color(0.45, 0.48, 0.56, 1)
+LineEdit/colors/caret_color = Color(0.149, 0.196, 0.290, 1)
+LineEdit/font_sizes/font_size = 24
+LineEdit/styles/normal = SubResource("edit_normal")
+LineEdit/styles/focus = SubResource("edit_focus")
+HeaderTitle/base_type = &"Label"
+HeaderTitle/colors/font_color = Color(1, 1, 1, 1)
+HeaderTitle/colors/font_outline_color = Color(0.122, 0.361, 0.173, 1)
+HeaderTitle/constants/outline_size = 10
+HeaderTitle/fonts/font = ExtResource("font_bold")
+HeaderTitle/font_sizes/font_size = 40
+CardTitle/base_type = &"Label"
+CardTitle/fonts/font = ExtResource("font_bold")
+CardTitle/font_sizes/font_size = 34
+Emphasis/base_type = &"Label"
+Emphasis/fonts/font = ExtResource("font_bold")
+Emphasis/font_sizes/font_size = 24
+Small/base_type = &"Label"
+Small/font_sizes/font_size = 17
+AcceptDialog/styles/panel = SubResource("window_panel")
+Window/styles/embedded_border = SubResource("window_panel")
+Window/colors/title_color = Color(0.149, 0.196, 0.290, 1)
+Window/fonts/title_font = ExtResource("font_bold")
+Window/font_sizes/title_font_size = 26
+Window/constants/title_height = 44
diff --git a/autoload/settings_manager.gd b/autoload/settings_manager.gd
index 4182563..15e4aa4 100644
--- a/autoload/settings_manager.gd
+++ b/autoload/settings_manager.gd
@@ -1,3 +1,6 @@
extends Node
## Volume, taille du texte et autres preferences de l'enfant ou de l'adulte.
-## Logique detaillee a partir de l'etape "Scenes" (voir Ordre de construction).
+## Pour l'instant : l'interrupteur des animations, lu par Motion. L'ecran de
+## reglages qui le pilotera vient avec l'espace adulte (docs/specs/decisions.md).
+
+var animations_enabled := true
diff --git a/data/localization/fr.json b/data/localization/fr.json
index e9715fb..3988160 100644
--- a/data/localization/fr.json
+++ b/data/localization/fr.json
@@ -17,6 +17,7 @@
"profile_create.cancel_button": "Retour",
"profile_create.name_required_error": "Choisis un prénom avant de continuer.",
"world_map.title": "La forêt des accords",
+ "world_map.guide_hint": "Choisis un lieu pour commencer l'aventure !",
"world_map.title_ce2": "Le marais des homophones",
"world_map.review_ce1_button": "Revoir le CE1",
"world_map.back_to_ce2_button": "Retour au CE2",
diff --git a/docs/specs/decisions.md b/docs/specs/decisions.md
index 00a8e24..d171a12 100644
--- a/docs/specs/decisions.md
+++ b/docs/specs/decisions.md
@@ -288,3 +288,19 @@ Ajouts à l'arborescence proposée :
**Tests** : scoring, règles de déblocage, mise à jour de sauvegarde, migration v1→v2, validation du schéma, suivi des issues dans `exercise_host`, écran de récompense et collection de bout en bout. **Non vérifié** (sans écran) : rendu et lisibilité des trois lignes des marqueurs, mise en page de l'écran de récompense et de la collection, défilement de la liste.
**À faire (phases suivantes)** : habillage visuel et illustrations des objets, réglages et espace adulte (dont export/restauration des sauvegardes), son et musique.
+
+## 31. Phase « Habillage visuel » et navigation (version 0.3.0)
+
+**Navigation (demande utilisateur)** : bouton « Changer de profil » sur la carte (sauvegarde avant de quitter) ; bouton « Quitter la mission » dans l'hôte d'exercice avec confirmation. Quitter ne rapporte ni étoile ni objet et n'est jamais compté comme un échec ; les tentatives déjà faites restent enregistrées (vraies observations d'apprentissage) et sont sauvegardées.
+
+**Vérification du rendu** : `tools/screenshot.gd` (`make screenshot`) charge une scène dans une vraie fenêtre et enregistre un PNG, avec actions scriptées (`continue_rule`, `answer:N`, `toggle:N`, `type:…`, `validate`, `outcomes:…`). Il a permis de relire le rendu réel et d'y corriger ce que les tests ne voient pas : marqueurs de la carte qui se chevauchaient (zone de carte plus basse que supposé au point 26), titres tronqués, vignettes de progression vides. Le test d'écart des positions (point 26) reste un garde-fou de données ; le rendu réel passe désormais par ces captures.
+
+**Système visuel** : thème Godot unique (`assets/interface/theme.tres`, appliqué par `project.godot`) : police Atkinson Hyperlegible (libre, conçue pour la lisibilité ; tous les caractères français vérifiés), palette crème/vert/or, boutons à relief, cartes arrondies, focus clavier bleu épais, variantes de type (`SecondaryButton`, `MarkerButton`, `CardTitle`, `HeaderTitle`, `Pill`...). Fenêtre de base 1152x648 avec étirement `canvas_items`. Réussite/erreur : icône (coche / point d'interrogation) en plus du texte, jamais la couleur seule ; étoiles pleines/vides par la forme.
+
+**Illustrations** (SVG vectoriels originaux, provisoires, générés par script jetable) : décors forêt (CE1) et marais (CE2) — le décor suit le niveau de la règle —, hibou Plume, étoiles, pictogrammes de types d'exercice, icônes de retour, 10 objets de collection. Résolution par identifiant dans un répertoire fixe (`ItemIcons`), repli sur un cadenas.
+
+**Écrans refaits** : carte (barre du haut : profil, titre, total d'étoiles, collection ; lieux = cartes avec pictogramme, titre, étoiles ; position fractionnelle étalée sur toute la zone et recalculée au redimensionnement ; Plume en bas), choix et création de profil, introduction de mission (Plume porte le texte narratif, doublon supprimé), hôte d'exercice (décor + carte de contenu, indicateur d'exercice en pastille), panneaux de règle et de retour, récompense (étoiles qui apparaissent, objets débloqués illustrés), collection (grille).
+
+**Animations** : `Motion` (apparition en rebond, fondu) respecte `SettingsManager.animations_enabled` (cahier des charges §11) ; l'écran de réglages qui le pilotera reste à faire (phase suivante).
+
+**Non vérifié** : tactile, clavier virtuel, rendu sur d'autres résolutions que 1152x648, animations en mouvement (les captures montrent l'état final), rendu sous Windows.
diff --git a/docs/testing/manual-checklist.md b/docs/testing/manual-checklist.md
index 6facfec..480ede4 100644
--- a/docs/testing/manual-checklist.md
+++ b/docs/testing/manual-checklist.md
@@ -24,6 +24,9 @@ Tout ce qui suit ne peut pas être vérifié sans écran (rendu, mise en page, a
## Accessibilité et interface
+- Habillage (point 31 de `decisions.md`) : relire chaque écran (profils, carte CE1 et CE2, introduction, règle, les trois types d'exercice, retour juste/faux, récompense, collection) ; le rendu à 1152x648 a été relu sur captures, pas les autres tailles ni le tactile. Redimensionner la fenêtre : les lieux de la carte restent entiers et ne se chevauchent pas.
+- Navigation : « Changer de profil » sur la carte ramène au choix du profil sans perte ; « Quitter la mission » demande confirmation, ne donne aucune étoile et ramène à la carte.
+
- Réussite et erreur ne doivent jamais se distinguer **uniquement** par la couleur (vérifier que le texte du titre du panneau de retour diffère bien : « Bravo ! » vs « Pas tout à fait »).
- Taille et contraste du texte à vérifier sur un petit écran et un grand écran (redimensionner la fenêtre pendant que le jeu tourne, vérifier que les boutons restent utilisables et ne se chevauchent pas — conteneurs et ancres, pas de position fixe).
- Boutons testés à la souris et au clavier (tabulation / entrée). Le tactile n'a pas pu être testé (pas d'export mobile à ce stade).
diff --git a/project.godot b/project.godot
index 3dc8b4d..ccb1f44 100644
--- a/project.godot
+++ b/project.godot
@@ -11,7 +11,7 @@ config_version=5
[application]
config/name="Les Aventuriers des mots"
-config/version="0.2.0"
+config/version="0.3.0"
run/main_scene="res://scenes/boot/boot.tscn"
config/features=PackedStringArray("4.6", "Forward Plus")
config/icon="res://icon.svg"
@@ -25,3 +25,14 @@ ExerciseEngine="*res://autoload/exercise_engine.gd"
SaveManager="*res://autoload/save_manager.gd"
AudioManager="*res://autoload/audio_manager.gd"
SettingsManager="*res://autoload/settings_manager.gd"
+
+[display]
+
+window/size/viewport_width=1152
+window/size/viewport_height=648
+window/stretch/mode="canvas_items"
+window/stretch/aspect="expand"
+
+[gui]
+
+theme/custom="res://assets/interface/theme.tres"
diff --git a/scenes/characters/guide_owl.gd b/scenes/characters/guide_owl.gd
index 3b73cae..bb21405 100644
--- a/scenes/characters/guide_owl.gd
+++ b/scenes/characters/guide_owl.gd
@@ -1,15 +1,23 @@
extends Control
-## Plume, le hibou guide : affiche une remarque ou un indice sous forme de
-## texte. Portrait provisoire (forme simple, voir assets/CREDITS.md), a
-## remplacer fichier par fichier sans toucher ce script.
+## Plume, le hibou guide : affiche une remarque ou un indice dans une bulle.
+## Illustration vectorielle provisoire (voir assets/CREDITS.md), a remplacer
+## fichier par fichier sans toucher ce script.
+@onready var _bubble: Control = $HBox/Bubble
@onready var _dialogue_label: Label = %DialogueLabel
+func _ready() -> void:
+ _bubble.visible = _dialogue_label.visible
+
+
func say(text: String) -> void:
_dialogue_label.text = text
_dialogue_label.visible = true
+ _bubble.visible = true
+ Motion.fade_in(_bubble)
func clear() -> void:
_dialogue_label.visible = false
+ _bubble.visible = false
diff --git a/scenes/characters/guide_owl.tscn b/scenes/characters/guide_owl.tscn
index d016460..7f9090b 100644
--- a/scenes/characters/guide_owl.tscn
+++ b/scenes/characters/guide_owl.tscn
@@ -1,15 +1,16 @@
-[gd_scene load_steps=2 format=3]
+[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://scenes/characters/guide_owl.gd" id="1"]
+[ext_resource type="Texture2D" path="res://assets/characters/owl_plume.svg" id="2"]
[node name="GuideOwl" type="Control"]
layout_mode = 3
size_flags_horizontal = 3
-custom_minimum_size = Vector2(0, 96)
+custom_minimum_size = Vector2(0, 112)
script = ExtResource("1")
[node name="HBox" type="HBoxContainer" parent="."]
-layout_mode = 2
+layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
@@ -17,15 +18,19 @@ grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 12
-[node name="Portrait" type="ColorRect" parent="HBox"]
+[node name="Portrait" type="TextureRect" parent="HBox"]
layout_mode = 2
-custom_minimum_size = Vector2(80, 80)
-color = Color(0.56, 0.75, 0.42, 1)
+custom_minimum_size = Vector2(104, 104)
+size_flags_vertical = 4
+texture = ExtResource("2")
+expand_mode = 1
+stretch_mode = 5
[node name="Bubble" type="PanelContainer" parent="HBox"]
+theme_type_variation = &"SoftCard"
layout_mode = 2
size_flags_horizontal = 3
-size_flags_vertical = 1
+size_flags_vertical = 4
[node name="DialogueLabel" type="Label" parent="HBox/Bubble"]
unique_name_in_owner = true
diff --git a/scenes/exercises/dictation.tscn b/scenes/exercises/dictation.tscn
index fc218e5..e384984 100644
--- a/scenes/exercises/dictation.tscn
+++ b/scenes/exercises/dictation.tscn
@@ -29,6 +29,7 @@ theme_override_constants/separation = 16
[node name="InstructionLabel" type="Label" parent="Margin/VBox"]
unique_name_in_owner = true
+theme_type_variation = &"Emphasis"
layout_mode = 2
autowrap_mode = 2
diff --git a/scenes/exercises/exercise_host.gd b/scenes/exercises/exercise_host.gd
index 65c6084..767c536 100644
--- a/scenes/exercises/exercise_host.gd
+++ b/scenes/exercises/exercise_host.gd
@@ -29,6 +29,7 @@ var _pending_outcome := ""
@onready var _exercise_container: Control = %ExerciseContainer
@onready var _overlay_container: Control = %OverlayContainer
@onready var _progress_label: Label = %ProgressLabel
+@onready var _progress_pill: Control = %ProgressPill
@onready var _abandon_button: Button = %AbandonButton
@onready var _abandon_confirm_dialog: ConfirmationDialog = %AbandonConfirmDialog
@@ -64,6 +65,7 @@ func _ready() -> void:
_rule = ExerciseEngine.get_rule(_mission["rule_id"])
_skill_id = _rule.get("skill", "")
_current_difficulty = ProgressManager.next_difficulty_for_skill(_skill_id, 1)
+ $Backdrop.kind = "marsh" if _rule.get("level", "CE1") == "CE2" else "forest"
_show_rule_panel()
@@ -114,6 +116,7 @@ func _load_next_exercise() -> void:
## peut faire redonner le meme exercice d'une mission a l'autre tentative :
## sans ce repere, ca ressemble a un blocage plutot qu'a une progression.
func _update_progress_label() -> void:
+ _progress_pill.visible = true
_progress_label.text = "%s %d/%d" % [
UIText.get_text("exercise_host.progress_label"), _completed_count + 1, MISSION_LENGTH
]
diff --git a/scenes/exercises/exercise_host.tscn b/scenes/exercises/exercise_host.tscn
index 945f03f..7aa79e0 100644
--- a/scenes/exercises/exercise_host.tscn
+++ b/scenes/exercises/exercise_host.tscn
@@ -1,6 +1,7 @@
-[gd_scene load_steps=2 format=3]
+[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://scenes/exercises/exercise_host.gd" id="1"]
+[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"]
[node name="ExerciseHost" type="Control"]
layout_mode = 3
@@ -11,12 +12,36 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
+[node name="Backdrop" parent="." instance=ExtResource("backdrop")]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+unique_name_in_owner = true
+
+[node name="ContentCard" type="Panel" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 24.0
+offset_top = 64.0
+offset_right = -24.0
+offset_bottom = -24.0
+grow_horizontal = 2
+grow_vertical = 2
+
[node name="ExerciseContainer" type="Control" parent="."]
unique_name_in_owner = true
layout_mode = 1
anchor_right = 1.0
anchor_bottom = 1.0
-offset_top = 48.0
+offset_left = 40.0
+offset_top = 76.0
+offset_right = -40.0
+offset_bottom = -36.0
grow_horizontal = 2
grow_vertical = 2
@@ -33,7 +58,7 @@ mouse_filter = 2
[node name="HeaderBar" type="MarginContainer" parent="."]
layout_mode = 1
anchor_right = 1.0
-offset_bottom = 48.0
+offset_bottom = 56.0
grow_horizontal = 2
theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 8
@@ -45,16 +70,24 @@ layout_mode = 2
[node name="AbandonButton" type="Button" parent="HeaderBar/HBox"]
unique_name_in_owner = true
+theme_type_variation = &"SecondaryButton"
layout_mode = 2
[node name="Spacer" type="Control" parent="HeaderBar/HBox"]
layout_mode = 2
size_flags_horizontal = 3
-[node name="ProgressLabel" type="Label" parent="HeaderBar/HBox"]
+[node name="ProgressPill" type="PanelContainer" parent="HeaderBar/HBox"]
unique_name_in_owner = true
+visible = false
+theme_type_variation = &"Pill"
layout_mode = 2
-horizontal_alignment = 2
+
+[node name="ProgressLabel" type="Label" parent="HeaderBar/HBox/ProgressPill"]
+unique_name_in_owner = true
+theme_type_variation = &"Emphasis"
+layout_mode = 2
+horizontal_alignment = 1
[node name="AbandonConfirmDialog" type="ConfirmationDialog" parent="."]
unique_name_in_owner = true
diff --git a/scenes/exercises/multiple_choice.gd b/scenes/exercises/multiple_choice.gd
index 6f3c7bd..6c53a1d 100644
--- a/scenes/exercises/multiple_choice.gd
+++ b/scenes/exercises/multiple_choice.gd
@@ -21,7 +21,8 @@ func load_exercise(exercise: Dictionary) -> void:
for i in answers.size():
var button := Button.new()
button.text = answers[i]
- button.custom_minimum_size = Vector2(0, 56)
+ button.custom_minimum_size = Vector2(0, 64)
+ button.add_theme_font_size_override("font_size", 28)
button.pressed.connect(_on_answer_pressed.bind(i))
_answers_box.add_child(button)
diff --git a/scenes/exercises/multiple_choice.tscn b/scenes/exercises/multiple_choice.tscn
index ed7b2cb..d4f0e47 100644
--- a/scenes/exercises/multiple_choice.tscn
+++ b/scenes/exercises/multiple_choice.tscn
@@ -29,6 +29,7 @@ theme_override_constants/separation = 16
[node name="InstructionLabel" type="Label" parent="Margin/VBox"]
unique_name_in_owner = true
+theme_type_variation = &"Emphasis"
layout_mode = 2
autowrap_mode = 2
@@ -36,7 +37,7 @@ autowrap_mode = 2
unique_name_in_owner = true
layout_mode = 2
autowrap_mode = 2
-theme_override_font_sizes/font_size = 24
+theme_type_variation = &"CardTitle"
[node name="AnswersBox" type="VBoxContainer" parent="Margin/VBox"]
unique_name_in_owner = true
diff --git a/scenes/exercises/sentence_correction.gd b/scenes/exercises/sentence_correction.gd
index 088b2ac..2ed80fb 100644
--- a/scenes/exercises/sentence_correction.gd
+++ b/scenes/exercises/sentence_correction.gd
@@ -33,6 +33,9 @@ func load_exercise(exercise: Dictionary) -> void:
var button := Button.new()
button.text = tokens[i]["text"]
button.toggle_mode = true
+ button.theme_type_variation = &"SecondaryButton"
+ button.custom_minimum_size = Vector2(0, 60)
+ button.add_theme_font_size_override("font_size", 30)
button.toggled.connect(_on_token_toggled.bind(i))
_tokens_box.add_child(button)
diff --git a/scenes/exercises/sentence_correction.tscn b/scenes/exercises/sentence_correction.tscn
index e8ae1c3..f158221 100644
--- a/scenes/exercises/sentence_correction.tscn
+++ b/scenes/exercises/sentence_correction.tscn
@@ -29,6 +29,7 @@ theme_override_constants/separation = 16
[node name="InstructionLabel" type="Label" parent="Margin/VBox"]
unique_name_in_owner = true
+theme_type_variation = &"Emphasis"
layout_mode = 2
autowrap_mode = 2
diff --git a/scenes/profiles/profile_create.tscn b/scenes/profiles/profile_create.tscn
index 21ad344..bafb7f6 100644
--- a/scenes/profiles/profile_create.tscn
+++ b/scenes/profiles/profile_create.tscn
@@ -1,7 +1,9 @@
-[gd_scene load_steps=3 format=3]
+[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://scenes/profiles/profile_create.gd" id="1"]
+[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"]
+
[sub_resource type="ButtonGroup" id="SchoolLevelGroup"]
[node name="ProfileCreate" type="Control"]
@@ -13,6 +15,14 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
+[node name="Backdrop" parent="." instance=ExtResource("backdrop")]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
@@ -25,58 +35,70 @@ theme_override_constants/margin_top = 32
theme_override_constants/margin_right = 32
theme_override_constants/margin_bottom = 32
-[node name="VBox" type="VBoxContainer" parent="Margin"]
+[node name="Center" type="CenterContainer" parent="Margin"]
layout_mode = 2
-theme_override_constants/separation = 16
-[node name="TitleLabel" type="Label" parent="Margin/VBox"]
+[node name="Card" type="PanelContainer" parent="Margin/Center"]
+layout_mode = 2
+custom_minimum_size = Vector2(640, 0)
+
+[node name="VBox" type="VBoxContainer" parent="Margin/Center/Card"]
+layout_mode = 2
+theme_override_constants/separation = 14
+
+[node name="TitleLabel" type="Label" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
-theme_override_font_sizes/font_size = 32
+theme_type_variation = &"CardTitle"
+horizontal_alignment = 1
-[node name="NameLabel" type="Label" parent="Margin/VBox"]
+[node name="NameLabel" type="Label" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
-[node name="NameEdit" type="LineEdit" parent="Margin/VBox"]
+[node name="NameEdit" type="LineEdit" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
custom_minimum_size = Vector2(0, 48)
-[node name="ErrorLabel" type="Label" parent="Margin/VBox"]
+[node name="ErrorLabel" type="Label" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
autowrap_mode = 2
-[node name="LevelLabel" type="Label" parent="Margin/VBox"]
+[node name="LevelLabel" type="Label" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
-[node name="LevelBox" type="HBoxContainer" parent="Margin/VBox"]
+[node name="LevelBox" type="HBoxContainer" parent="Margin/Center/Card/VBox"]
layout_mode = 2
theme_override_constants/separation = 16
-[node name="CE1Button" type="Button" parent="Margin/VBox/LevelBox"]
+[node name="CE1Button" type="Button" parent="Margin/Center/Card/VBox/LevelBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
custom_minimum_size = Vector2(0, 56)
+theme_type_variation = &"SecondaryButton"
toggle_mode = true
button_group = SubResource("SchoolLevelGroup")
-[node name="CE2Button" type="Button" parent="Margin/VBox/LevelBox"]
+[node name="CE2Button" type="Button" parent="Margin/Center/Card/VBox/LevelBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
custom_minimum_size = Vector2(0, 56)
+theme_type_variation = &"SecondaryButton"
toggle_mode = true
button_group = SubResource("SchoolLevelGroup")
-[node name="ConfirmButton" type="Button" parent="Margin/VBox"]
+[node name="ConfirmButton" type="Button" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
custom_minimum_size = Vector2(0, 56)
-[node name="CancelButton" type="Button" parent="Margin/VBox"]
+[node name="CancelButton" type="Button" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
+theme_type_variation = &"SecondaryButton"
layout_mode = 2
+custom_minimum_size = Vector2(0, 48)
diff --git a/scenes/profiles/profile_select.gd b/scenes/profiles/profile_select.gd
index 9db9691..82cb73d 100644
--- a/scenes/profiles/profile_select.gd
+++ b/scenes/profiles/profile_select.gd
@@ -41,14 +41,17 @@ func _refresh_profile_list() -> void:
func _build_profile_row(summary: Dictionary) -> Control:
var row := HBoxContainer.new()
+ row.add_theme_constant_override("separation", 10)
var select_button := Button.new()
select_button.text = "%s (%s)" % [summary["display_name"], summary["school_level"]]
select_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ select_button.custom_minimum_size = Vector2(0, 56)
select_button.pressed.connect(_on_profile_selected.bind(summary["profile_id"]))
row.add_child(select_button)
var delete_button := Button.new()
+ delete_button.theme_type_variation = &"SecondaryButton"
delete_button.text = UIText.get_text("profile_select.delete_button")
delete_button.pressed.connect(_on_delete_requested.bind(summary["profile_id"]))
row.add_child(delete_button)
diff --git a/scenes/profiles/profile_select.tscn b/scenes/profiles/profile_select.tscn
index 93470cf..cf6a0d4 100644
--- a/scenes/profiles/profile_select.tscn
+++ b/scenes/profiles/profile_select.tscn
@@ -1,6 +1,7 @@
-[gd_scene load_steps=2 format=3]
+[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://scenes/profiles/profile_select.gd" id="1"]
+[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"]
[node name="ProfileSelect" type="Control"]
layout_mode = 3
@@ -11,6 +12,14 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
+[node name="Backdrop" parent="." instance=ExtResource("backdrop")]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
@@ -23,31 +32,40 @@ theme_override_constants/margin_top = 32
theme_override_constants/margin_right = 32
theme_override_constants/margin_bottom = 32
-[node name="VBox" type="VBoxContainer" parent="Margin"]
+[node name="Center" type="CenterContainer" parent="Margin"]
+layout_mode = 2
+
+[node name="Card" type="PanelContainer" parent="Margin/Center"]
+layout_mode = 2
+custom_minimum_size = Vector2(720, 0)
+
+[node name="VBox" type="VBoxContainer" parent="Margin/Center/Card"]
layout_mode = 2
theme_override_constants/separation = 16
-[node name="TitleLabel" type="Label" parent="Margin/VBox"]
+[node name="TitleLabel" type="Label" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
-theme_override_font_sizes/font_size = 32
+theme_type_variation = &"CardTitle"
+horizontal_alignment = 1
-[node name="Scroll" type="ScrollContainer" parent="Margin/VBox"]
+[node name="Scroll" type="ScrollContainer" parent="Margin/Center/Card/VBox"]
layout_mode = 2
+custom_minimum_size = Vector2(0, 260)
size_flags_vertical = 3
-[node name="ProfileList" type="VBoxContainer" parent="Margin/VBox/Scroll"]
+[node name="ProfileList" type="VBoxContainer" parent="Margin/Center/Card/VBox/Scroll"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 8
-[node name="EmptyStateLabel" type="Label" parent="Margin/VBox"]
+[node name="EmptyStateLabel" type="Label" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
autowrap_mode = 2
-[node name="NewProfileButton" type="Button" parent="Margin/VBox"]
+[node name="NewProfileButton" type="Button" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
custom_minimum_size = Vector2(0, 56)
diff --git a/scenes/ui/backdrop.gd b/scenes/ui/backdrop.gd
new file mode 100644
index 0000000..84fe7bc
--- /dev/null
+++ b/scenes/ui/backdrop.gd
@@ -0,0 +1,24 @@
+extends Control
+## Decor plein ecran derriere un ecran (illustration vectorielle provisoire,
+## voir assets/CREDITS.md). "forest" pour le CE1, "marsh" pour le CE2.
+
+const TEXTURES := {
+ "forest": "res://assets/environments/forest_backdrop.svg",
+ "marsh": "res://assets/environments/marsh_backdrop.svg",
+}
+
+@export_enum("forest", "marsh") var kind: String = "forest":
+ set(value):
+ kind = value
+ _apply()
+
+@onready var _picture: TextureRect = $Picture
+
+
+func _ready() -> void:
+ _apply()
+
+
+func _apply() -> void:
+ if _picture != null:
+ _picture.texture = load(TEXTURES.get(kind, TEXTURES["forest"]))
diff --git a/scenes/ui/backdrop.gd.uid b/scenes/ui/backdrop.gd.uid
new file mode 100644
index 0000000..b84f4cb
--- /dev/null
+++ b/scenes/ui/backdrop.gd.uid
@@ -0,0 +1 @@
+uid://xc0ialw4cfk0
diff --git a/scenes/ui/backdrop.tscn b/scenes/ui/backdrop.tscn
new file mode 100644
index 0000000..8d3085e
--- /dev/null
+++ b/scenes/ui/backdrop.tscn
@@ -0,0 +1,24 @@
+[gd_scene load_steps=2 format=3]
+
+[ext_resource type="Script" path="res://scenes/ui/backdrop.gd" id="1"]
+
+[node name="Backdrop" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1")
+
+[node name="Picture" type="TextureRect" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+expand_mode = 1
+stretch_mode = 6
diff --git a/scenes/ui/collection.gd b/scenes/ui/collection.gd
index 445cec5..cd84042 100644
--- a/scenes/ui/collection.gd
+++ b/scenes/ui/collection.gd
@@ -2,9 +2,11 @@ extends Control
## Collection d'objets gagnes avec les etoiles. Un objet non debloque reste
## mysterieux mais indique comment l'obtenir (jamais de mystere frustrant).
+const ICON_SIZE := 72.0
+
@onready var _title_label: Label = %TitleLabel
@onready var _progress_label: Label = %ProgressLabel
-@onready var _items_box: VBoxContainer = %ItemsBox
+@onready var _items_box: GridContainer = %ItemsBox
@onready var _back_button: Button = %BackButton
@@ -26,20 +28,39 @@ func _ready() -> void:
)
+## Une ligne = pictogramme + texte (le Label s'appelle "TextLabel").
func _build_row(item: Dictionary, is_owned: bool) -> Control:
+ var card := PanelContainer.new()
+ card.theme_type_variation = &"SoftCard"
+ card.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+
+ var row := HBoxContainer.new()
+ row.add_theme_constant_override("separation", 12)
+ card.add_child(row)
+
+ var icon := TextureRect.new()
+ icon.texture = (
+ ItemIcons.texture_for(item["item_id"]) if is_owned else ItemIcons.locked_texture()
+ )
+ icon.custom_minimum_size = Vector2(ICON_SIZE, ICON_SIZE)
+ icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
+ icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ icon.size_flags_vertical = Control.SIZE_SHRINK_CENTER
+ row.add_child(icon)
+
var label := Label.new()
+ label.name = "TextLabel"
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
- label.custom_minimum_size = Vector2(560, 0)
+ label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ label.size_flags_vertical = Control.SIZE_SHRINK_CENTER
if is_owned:
- label.text = (
- "%s — %s\n%s"
- % [item["title"], UIText.get_text("collection.owned_marker"), item["description"]]
- )
+ label.text = "%s\n%s" % [item["title"], item["description"]]
else:
label.text = (
"%s\n%s" % [UIText.get_text("collection.locked_title"), _unlock_hint(item["unlock"])]
)
- return label
+ row.add_child(label)
+ return card
func _unlock_hint(unlock: Dictionary) -> String:
diff --git a/scenes/ui/collection.tscn b/scenes/ui/collection.tscn
index bcddfa7..25bc27b 100644
--- a/scenes/ui/collection.tscn
+++ b/scenes/ui/collection.tscn
@@ -1,6 +1,7 @@
-[gd_scene load_steps=2 format=3]
+[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://scenes/ui/collection.gd" id="1"]
+[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"]
[node name="Collection" type="Control"]
layout_mode = 3
@@ -11,6 +12,14 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
+[node name="Backdrop" parent="." instance=ExtResource("backdrop")]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
@@ -18,37 +27,44 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme_override_constants/margin_left = 24
+theme_override_constants/margin_left = 40
theme_override_constants/margin_top = 24
-theme_override_constants/margin_right = 24
+theme_override_constants/margin_right = 40
theme_override_constants/margin_bottom = 24
-[node name="VBox" type="VBoxContainer" parent="Margin"]
+[node name="Card" type="PanelContainer" parent="Margin"]
+layout_mode = 2
+
+[node name="VBox" type="VBoxContainer" parent="Margin/Card"]
layout_mode = 2
theme_override_constants/separation = 12
-[node name="TitleLabel" type="Label" parent="Margin/VBox"]
-unique_name_in_owner = true
-layout_mode = 2
-theme_override_font_sizes/font_size = 32
-horizontal_alignment = 1
-
-[node name="ProgressLabel" type="Label" parent="Margin/VBox"]
+[node name="TitleLabel" type="Label" parent="Margin/Card/VBox"]
unique_name_in_owner = true
+theme_type_variation = &"CardTitle"
layout_mode = 2
horizontal_alignment = 1
-[node name="Scroll" type="ScrollContainer" parent="Margin/VBox"]
+[node name="ProgressLabel" type="Label" parent="Margin/Card/VBox"]
+unique_name_in_owner = true
+theme_type_variation = &"Emphasis"
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="Scroll" type="ScrollContainer" parent="Margin/Card/VBox"]
layout_mode = 2
size_flags_vertical = 3
+horizontal_scroll_mode = 0
-[node name="ItemsBox" type="VBoxContainer" parent="Margin/VBox/Scroll"]
+[node name="ItemsBox" type="GridContainer" parent="Margin/Card/VBox/Scroll"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
-theme_override_constants/separation = 16
+theme_override_constants/h_separation = 16
+theme_override_constants/v_separation = 12
+columns = 2
-[node name="BackButton" type="Button" parent="Margin/VBox"]
+[node name="BackButton" type="Button" parent="Margin/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
custom_minimum_size = Vector2(0, 56)
diff --git a/scenes/ui/feedback_panel.gd b/scenes/ui/feedback_panel.gd
index 937e6f2..e7bc80f 100644
--- a/scenes/ui/feedback_panel.gd
+++ b/scenes/ui/feedback_panel.gd
@@ -5,6 +5,10 @@ extends Control
signal continue_pressed
+const ICON_CORRECT := preload("res://assets/interface/icon_correct.svg")
+const ICON_TRY_AGAIN := preload("res://assets/interface/icon_try_again.svg")
+
+@onready var _icon: TextureRect = %Icon
@onready var _title_label: Label = %TitleLabel
@onready var _hint_box: VBoxContainer = %HintBox
@onready var _hint_label: Label = %HintLabel
@@ -21,6 +25,8 @@ func _ready() -> void:
func show_result(result: CorrectionResult, support_level: AttemptFlow.SupportLevel) -> void:
+ _icon.texture = ICON_CORRECT if result.is_correct else ICON_TRY_AGAIN
+ Motion.pop_in(_icon)
_title_label.text = (
UIText.get_text("feedback_panel.correct_title")
if result.is_correct
diff --git a/scenes/ui/feedback_panel.tscn b/scenes/ui/feedback_panel.tscn
index 965a251..4013c22 100644
--- a/scenes/ui/feedback_panel.tscn
+++ b/scenes/ui/feedback_panel.tscn
@@ -31,7 +31,7 @@ grow_vertical = 2
[node name="Panel" type="PanelContainer" parent="CenterContainer"]
layout_mode = 2
-custom_minimum_size = Vector2(480, 0)
+custom_minimum_size = Vector2(560, 0)
[node name="Margin" type="MarginContainer" parent="CenterContainer/Panel"]
layout_mode = 2
@@ -44,10 +44,19 @@ theme_override_constants/margin_bottom = 24
layout_mode = 2
theme_override_constants/separation = 12
-[node name="TitleLabel" type="Label" parent="CenterContainer/Panel/Margin/VBox"]
+[node name="Icon" type="TextureRect" parent="CenterContainer/Panel/Margin/VBox"]
unique_name_in_owner = true
layout_mode = 2
-theme_override_font_sizes/font_size = 28
+custom_minimum_size = Vector2(72, 72)
+size_flags_horizontal = 4
+expand_mode = 1
+stretch_mode = 5
+
+[node name="TitleLabel" type="Label" parent="CenterContainer/Panel/Margin/VBox"]
+unique_name_in_owner = true
+theme_type_variation = &"CardTitle"
+layout_mode = 2
+horizontal_alignment = 1
[node name="HintBox" type="VBoxContainer" parent="CenterContainer/Panel/Margin/VBox"]
unique_name_in_owner = true
diff --git a/scenes/ui/reward_screen.gd b/scenes/ui/reward_screen.gd
index 7bcf810..05f7f46 100644
--- a/scenes/ui/reward_screen.gd
+++ b/scenes/ui/reward_screen.gd
@@ -5,23 +5,37 @@ extends Control
## au moins une etoile. Sauvegarde groupee ici, en fin de mission
## (docs/specs/decisions.md point 7).
+const STAR_FULL := preload("res://assets/interface/star_full.svg")
+const STAR_EMPTY := preload("res://assets/interface/star_empty.svg")
+const STAR_SIZE := 88.0
+const ITEM_SIZE := 72.0
+
+@onready var _backdrop: Control = %Backdrop
@onready var _title_label: Label = %TitleLabel
+@onready var _stars_row: HBoxContainer = %StarsRow
@onready var _stars_label: Label = %StarsLabel
@onready var _message_label: Label = %MessageLabel
@onready var _record_label: Label = %RecordLabel
@onready var _total_label: Label = %TotalLabel
+@onready var _new_items_box: VBoxContainer = %NewItemsBox
@onready var _new_items_label: Label = %NewItemsLabel
+@onready var _new_items_row: HBoxContainer = %NewItemsRow
@onready var _collection_button: Button = %CollectionButton
@onready var _continue_button: Button = %ContinueButton
func _ready() -> void:
+ var mission := ExerciseEngine.get_mission(GameManager.pending_mission_id)
+ var rule := ExerciseEngine.get_rule(mission.get("rule_id", ""))
+ _backdrop.kind = "marsh" if rule.get("level", "CE1") == "CE2" else "forest"
+
var summary := ProgressManager.complete_mission(
GameManager.pending_mission_id, GameManager.last_outcomes
)
ProgressManager.save_now()
_title_label.text = UIText.get_text("reward_screen.title")
+ _show_stars(summary["stars"])
_stars_label.text = (
"%s %d/%d"
% [UIText.get_text("reward_screen.stars_label"), summary["stars"], MissionScoring.MAX_STARS]
@@ -32,17 +46,7 @@ func _ready() -> void:
_total_label.text = (
"%s %d" % [UIText.get_text("reward_screen.total_label"), summary["total_stars"]]
)
-
- var new_items: Array = summary["new_items"]
- _new_items_label.visible = not new_items.is_empty()
- if not new_items.is_empty():
- var titles: Array[String] = []
- for item in ExerciseEngine.get_reward_items():
- if new_items.has(item["item_id"]):
- titles.append(item["title"])
- _new_items_label.text = (
- "%s\n%s" % [UIText.get_text("reward_screen.new_items_label"), "\n".join(titles)]
- )
+ _show_new_items(summary["new_items"])
_collection_button.text = UIText.get_text("reward_screen.collection_button")
_collection_button.pressed.connect(_on_collection_pressed)
@@ -50,6 +54,45 @@ func _ready() -> void:
_continue_button.pressed.connect(_on_continue_pressed)
+func _show_stars(earned: int) -> void:
+ for i in MissionScoring.MAX_STARS:
+ var star := TextureRect.new()
+ star.texture = STAR_FULL if i < earned else STAR_EMPTY
+ star.custom_minimum_size = Vector2(STAR_SIZE, STAR_SIZE)
+ star.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
+ star.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ _stars_row.add_child(star)
+ if i < earned:
+ Motion.pop_in(star, 0.25 + 0.2 * i)
+
+
+func _show_new_items(new_items: Array) -> void:
+ _new_items_box.visible = not new_items.is_empty()
+ if new_items.is_empty():
+ return
+ _new_items_label.text = UIText.get_text("reward_screen.new_items_label")
+ var index := 0
+ for item in ExerciseEngine.get_reward_items():
+ if not new_items.has(item["item_id"]):
+ continue
+ var holder := VBoxContainer.new()
+ var icon := TextureRect.new()
+ icon.texture = ItemIcons.texture_for(item["item_id"])
+ icon.custom_minimum_size = Vector2(ITEM_SIZE, ITEM_SIZE)
+ icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
+ icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ icon.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
+ holder.add_child(icon)
+ var name_label := Label.new()
+ name_label.text = item["title"]
+ name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ name_label.theme_type_variation = &"Small"
+ holder.add_child(name_label)
+ _new_items_row.add_child(holder)
+ Motion.pop_in(holder, 0.8 + 0.15 * index)
+ index += 1
+
+
func _on_collection_pressed() -> void:
GameManager.pending_mission_id = ""
GameManager.goto(GameManager.SCENE_COLLECTION)
diff --git a/scenes/ui/reward_screen.tscn b/scenes/ui/reward_screen.tscn
index b8100cd..833e506 100644
--- a/scenes/ui/reward_screen.tscn
+++ b/scenes/ui/reward_screen.tscn
@@ -1,6 +1,8 @@
-[gd_scene load_steps=2 format=3]
+[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://scenes/ui/reward_screen.gd" id="1"]
+[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"]
+[ext_resource type="Texture2D" path="res://assets/characters/owl_plume.svg" id="3"]
[node name="RewardScreen" type="Control"]
layout_mode = 3
@@ -11,59 +13,104 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
-[node name="CenterContainer" type="CenterContainer" parent="."]
+[node name="Backdrop" parent="." instance=ExtResource("backdrop")]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-
-[node name="VBox" type="VBoxContainer" parent="CenterContainer"]
-layout_mode = 2
-theme_override_constants/separation = 20
-
-[node name="TitleLabel" type="Label" parent="CenterContainer/VBox"]
unique_name_in_owner = true
-layout_mode = 2
-theme_override_font_sizes/font_size = 36
-horizontal_alignment = 1
-[node name="StarsLabel" type="Label" parent="CenterContainer/VBox"]
+[node name="Margin" type="MarginContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 32
+theme_override_constants/margin_top = 24
+theme_override_constants/margin_right = 32
+theme_override_constants/margin_bottom = 24
+
+[node name="Center" type="CenterContainer" parent="Margin"]
+layout_mode = 2
+
+[node name="Card" type="PanelContainer" parent="Margin/Center"]
+layout_mode = 2
+custom_minimum_size = Vector2(720, 0)
+
+[node name="VBox" type="VBoxContainer" parent="Margin/Center/Card"]
+layout_mode = 2
+theme_override_constants/separation = 12
+
+[node name="TitleLabel" type="Label" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
+theme_type_variation = &"CardTitle"
layout_mode = 2
horizontal_alignment = 1
-[node name="MessageLabel" type="Label" parent="CenterContainer/VBox"]
+[node name="StarsRow" type="HBoxContainer" parent="Margin/Center/Card/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+alignment = 1
+theme_override_constants/separation = 10
+
+[node name="StarsLabel" type="Label" parent="Margin/Center/Card/VBox"]
+unique_name_in_owner = true
+theme_type_variation = &"Emphasis"
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="MessageLabel" type="Label" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
horizontal_alignment = 1
autowrap_mode = 2
-custom_minimum_size = Vector2(480, 0)
-[node name="RecordLabel" type="Label" parent="CenterContainer/VBox"]
+[node name="RecordLabel" type="Label" parent="Margin/Center/Card/VBox"]
+unique_name_in_owner = true
+theme_type_variation = &"Emphasis"
+layout_mode = 2
+horizontal_alignment = 1
+
+[node name="TotalLabel" type="Label" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
horizontal_alignment = 1
-[node name="TotalLabel" type="Label" parent="CenterContainer/VBox"]
+[node name="NewItemsBox" type="VBoxContainer" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
layout_mode = 2
-horizontal_alignment = 1
+theme_override_constants/separation = 6
-[node name="NewItemsLabel" type="Label" parent="CenterContainer/VBox"]
+[node name="NewItemsLabel" type="Label" parent="Margin/Center/Card/VBox/NewItemsBox"]
unique_name_in_owner = true
+theme_type_variation = &"Emphasis"
layout_mode = 2
horizontal_alignment = 1
autowrap_mode = 2
-custom_minimum_size = Vector2(480, 0)
-[node name="CollectionButton" type="Button" parent="CenterContainer/VBox"]
+[node name="NewItemsRow" type="HBoxContainer" parent="Margin/Center/Card/VBox/NewItemsBox"]
unique_name_in_owner = true
layout_mode = 2
-custom_minimum_size = Vector2(200, 56)
+alignment = 1
+theme_override_constants/separation = 16
-[node name="ContinueButton" type="Button" parent="CenterContainer/VBox"]
+[node name="Buttons" type="HBoxContainer" parent="Margin/Center/Card/VBox"]
+layout_mode = 2
+theme_override_constants/separation = 16
+
+[node name="CollectionButton" type="Button" parent="Margin/Center/Card/VBox/Buttons"]
+unique_name_in_owner = true
+theme_type_variation = &"SecondaryButton"
+layout_mode = 2
+size_flags_horizontal = 3
+custom_minimum_size = Vector2(0, 56)
+
+[node name="ContinueButton" type="Button" parent="Margin/Center/Card/VBox/Buttons"]
unique_name_in_owner = true
layout_mode = 2
-custom_minimum_size = Vector2(200, 56)
+size_flags_horizontal = 3
+custom_minimum_size = Vector2(0, 56)
diff --git a/scenes/ui/rule_panel.tscn b/scenes/ui/rule_panel.tscn
index 3103131..f883875 100644
--- a/scenes/ui/rule_panel.tscn
+++ b/scenes/ui/rule_panel.tscn
@@ -31,7 +31,7 @@ grow_vertical = 2
[node name="Panel" type="PanelContainer" parent="CenterContainer"]
layout_mode = 2
-custom_minimum_size = Vector2(480, 0)
+custom_minimum_size = Vector2(560, 0)
[node name="Margin" type="MarginContainer" parent="CenterContainer/Panel"]
layout_mode = 2
@@ -46,8 +46,9 @@ theme_override_constants/separation = 16
[node name="TitleLabel" type="Label" parent="CenterContainer/Panel/Margin/VBox"]
unique_name_in_owner = true
+theme_type_variation = &"CardTitle"
layout_mode = 2
-theme_override_font_sizes/font_size = 28
+horizontal_alignment = 1
[node name="StatementLabel" type="Label" parent="CenterContainer/Panel/Margin/VBox"]
unique_name_in_owner = true
diff --git a/scenes/world/mission_intro.gd b/scenes/world/mission_intro.gd
index 52ecc3a..80c3cf7 100644
--- a/scenes/world/mission_intro.gd
+++ b/scenes/world/mission_intro.gd
@@ -4,7 +4,6 @@ extends Control
## (voir "Points durs" du prompt de lancement, Sens de l'aventure).
@onready var _title_label: Label = %TitleLabel
-@onready var _narrative_label: Label = %NarrativeLabel
@onready var _guide: Control = %Guide
@onready var _start_button: Button = %StartButton
@onready var _back_button: Button = %BackButton
@@ -13,8 +12,9 @@ extends Control
func _ready() -> void:
var mission := ExerciseEngine.get_mission(GameManager.pending_mission_id)
_title_label.text = mission.get("title", "")
- _narrative_label.text = mission.get("narrative_context", "")
_guide.say(mission.get("narrative_context", ""))
+ var rule := ExerciseEngine.get_rule(mission.get("rule_id", ""))
+ $Backdrop.kind = "marsh" if rule.get("level", "CE1") == "CE2" else "forest"
_start_button.text = UIText.get_text("mission_intro.start_button")
_back_button.text = UIText.get_text("mission_intro.back_button")
diff --git a/scenes/world/mission_intro.tscn b/scenes/world/mission_intro.tscn
index e655a08..0721bcb 100644
--- a/scenes/world/mission_intro.tscn
+++ b/scenes/world/mission_intro.tscn
@@ -1,7 +1,8 @@
-[gd_scene load_steps=3 format=3]
+[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://scenes/world/mission_intro.gd" id="1"]
[ext_resource type="PackedScene" path="res://scenes/characters/guide_owl.tscn" id="2"]
+[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"]
[node name="MissionIntro" type="Control"]
layout_mode = 3
@@ -12,6 +13,14 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
+[node name="Backdrop" parent="." instance=ExtResource("backdrop")]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
@@ -19,40 +28,45 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-theme_override_constants/margin_left = 32
-theme_override_constants/margin_top = 32
-theme_override_constants/margin_right = 32
-theme_override_constants/margin_bottom = 32
+theme_override_constants/margin_left = 48
+theme_override_constants/margin_top = 40
+theme_override_constants/margin_right = 48
+theme_override_constants/margin_bottom = 40
-[node name="VBox" type="VBoxContainer" parent="Margin"]
+[node name="Center" type="CenterContainer" parent="Margin"]
layout_mode = 2
-theme_override_constants/separation = 16
-[node name="TitleLabel" type="Label" parent="Margin/VBox"]
-unique_name_in_owner = true
-layout_mode = 2
-theme_override_font_sizes/font_size = 32
-
-[node name="NarrativeLabel" type="Label" parent="Margin/VBox"]
+[node name="Card" type="PanelContainer" parent="Margin/Center"]
+layout_mode = 2
+custom_minimum_size = Vector2(760, 0)
+
+[node name="VBox" type="VBoxContainer" parent="Margin/Center/Card"]
+layout_mode = 2
+theme_override_constants/separation = 18
+
+[node name="TitleLabel" type="Label" parent="Margin/Center/Card/VBox"]
unique_name_in_owner = true
+theme_type_variation = &"CardTitle"
layout_mode = 2
+horizontal_alignment = 1
autowrap_mode = 2
-size_flags_vertical = 3
-[node name="Guide" type="Control" parent="Margin/VBox" instance=ExtResource("2")]
+[node name="Guide" type="Control" parent="Margin/Center/Card/VBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
+custom_minimum_size = Vector2(0, 150)
-[node name="Buttons" type="HBoxContainer" parent="Margin/VBox"]
+[node name="Buttons" type="HBoxContainer" parent="Margin/Center/Card/VBox"]
layout_mode = 2
theme_override_constants/separation = 16
-[node name="BackButton" type="Button" parent="Margin/VBox/Buttons"]
+[node name="BackButton" type="Button" parent="Margin/Center/Card/VBox/Buttons"]
unique_name_in_owner = true
+theme_type_variation = &"SecondaryButton"
layout_mode = 2
custom_minimum_size = Vector2(0, 56)
-[node name="StartButton" type="Button" parent="Margin/VBox/Buttons"]
+[node name="StartButton" type="Button" parent="Margin/Center/Card/VBox/Buttons"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
diff --git a/scenes/world/mission_marker.gd b/scenes/world/mission_marker.gd
new file mode 100644
index 0000000..40a5fb2
--- /dev/null
+++ b/scenes/world/mission_marker.gd
@@ -0,0 +1,37 @@
+extends Button
+## Un lieu de la carte : pictogramme du type d'exercice, titre, etoiles
+## gagnees (pleines/vides : la forme porte l'information, pas la couleur) et
+## etat de la competence. Scene autonome : la carte la configure et ecoute
+## `pressed`.
+
+const STAR_FULL := preload("res://assets/interface/star_full.svg")
+const STAR_EMPTY := preload("res://assets/interface/star_empty.svg")
+const TYPE_ICONS := {
+ "multiple_choice": "res://assets/interface/icon_multiple_choice.svg",
+ "sentence_correction": "res://assets/interface/icon_sentence_correction.svg",
+ "dictation": "res://assets/interface/icon_dictation.svg",
+}
+const STAR_SIZE := 24.0
+
+@onready var _type_icon: TextureRect = %TypeIcon
+@onready var _title_label: Label = %TitleLabel
+@onready var _stars_row: HBoxContainer = %StarsRow
+@onready var _status_label: Label = %StatusLabel
+
+
+func setup(mission: Dictionary, stars: int, max_stars: int, status_text: String) -> void:
+ _title_label.text = mission["title"]
+ _status_label.text = status_text
+ _status_label.visible = not status_text.is_empty()
+ _type_icon.texture = load(TYPE_ICONS.get(mission["exercise_type"], TYPE_ICONS["multiple_choice"]))
+
+ for child in _stars_row.get_children():
+ child.queue_free()
+ for i in max_stars:
+ var star := TextureRect.new()
+ star.texture = STAR_FULL if i < stars else STAR_EMPTY
+ star.custom_minimum_size = Vector2(STAR_SIZE, STAR_SIZE)
+ star.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
+ star.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ star.mouse_filter = Control.MOUSE_FILTER_IGNORE
+ _stars_row.add_child(star)
diff --git a/scenes/world/mission_marker.gd.uid b/scenes/world/mission_marker.gd.uid
new file mode 100644
index 0000000..9d044cf
--- /dev/null
+++ b/scenes/world/mission_marker.gd.uid
@@ -0,0 +1 @@
+uid://cd6db5ude6pwq
diff --git a/scenes/world/mission_marker.tscn b/scenes/world/mission_marker.tscn
new file mode 100644
index 0000000..2fd7ade
--- /dev/null
+++ b/scenes/world/mission_marker.tscn
@@ -0,0 +1,59 @@
+[gd_scene load_steps=3 format=3]
+
+[ext_resource type="Script" path="res://scenes/world/mission_marker.gd" id="1"]
+[ext_resource type="Texture2D" path="res://assets/interface/star_empty.svg" id="2"]
+
+[node name="MissionMarker" type="Button"]
+theme_type_variation = &"MarkerButton"
+custom_minimum_size = Vector2(220, 120)
+script = ExtResource("1")
+
+[node name="Content" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_constants/separation = 2
+alignment = 1
+
+[node name="Header" type="HBoxContainer" parent="Content"]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/separation = 8
+alignment = 1
+
+[node name="TypeIcon" type="TextureRect" parent="Content/Header"]
+unique_name_in_owner = true
+layout_mode = 2
+custom_minimum_size = Vector2(36, 36)
+size_flags_vertical = 4
+mouse_filter = 2
+expand_mode = 1
+stretch_mode = 5
+
+[node name="TitleLabel" type="Label" parent="Content/Header"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_filter = 2
+theme_override_font_sizes/font_size = 18
+autowrap_mode = 3
+max_lines_visible = 3
+text_overrun_behavior = 3
+vertical_alignment = 1
+
+[node name="StarsRow" type="HBoxContainer" parent="Content"]
+unique_name_in_owner = true
+layout_mode = 2
+mouse_filter = 2
+alignment = 1
+
+[node name="StatusLabel" type="Label" parent="Content"]
+unique_name_in_owner = true
+layout_mode = 2
+mouse_filter = 2
+theme_type_variation = &"Small"
+horizontal_alignment = 1
diff --git a/scenes/world/world_map.gd b/scenes/world/world_map.gd
index fe9be89..4a9d6b0 100644
--- a/scenes/world/world_map.gd
+++ b/scenes/world/world_map.gd
@@ -3,20 +3,22 @@ extends Control
## (cahier des charges §15) se limitait a trois lieux ; le perimetre a ete
## etendu (docs/specs/decisions.md, points 25 et 27). La position de chaque
## mission vient de data/missions/*.json (fractions 0..1 de la zone de
-## carte), jamais d'une position fixe en dur dans le script. Un profil CE2
-## peut basculer sur la carte CE1 pour revoir les notions precedentes.
+## carte), jamais d'une position fixe en dur dans le script : chaque lieu est
+## replace quand la zone change de taille. Un profil CE2 peut basculer sur la
+## carte CE1 pour revoir les notions precedentes.
-const MARKER_HALF_WIDTH := 80.0
-const MARKER_HALF_HEIGHT := 48.0
+const MARKER_SCENE := preload("res://scenes/world/mission_marker.tscn")
var _viewed_level := "CE1"
+@onready var _backdrop: Control = %Backdrop
@onready var _title_label: Label = %TitleLabel
@onready var _map_area: Control = %MapArea
@onready var _level_toggle_button: Button = %LevelToggleButton
@onready var _total_stars_label: Label = %TotalStarsLabel
@onready var _collection_button: Button = %CollectionButton
@onready var _profile_button: Button = %ProfileButton
+@onready var _guide: Control = %Guide
func _ready() -> void:
@@ -27,19 +29,19 @@ func _ready() -> void:
_collection_button.pressed.connect(_on_collection_pressed)
_profile_button.text = UIText.get_text("world_map.change_profile_button")
_profile_button.pressed.connect(_on_change_profile_pressed)
+ _map_area.resized.connect(_layout_markers)
+ _guide.say(UIText.get_text("world_map.guide_hint"))
_refresh()
func _refresh() -> void:
- var title_key := "world_map.title_ce2" if _viewed_level == "CE2" else "world_map.title"
- _title_label.text = UIText.get_text(title_key)
- var toggle_key := (
- "world_map.review_ce1_button" if _viewed_level == "CE2" else "world_map.back_to_ce2_button"
- )
- _level_toggle_button.text = UIText.get_text(toggle_key)
- _total_stars_label.text = (
- "%s %d" % [UIText.get_text("world_map.total_stars_label"), ProgressManager.stars()]
+ var is_ce2 := _viewed_level == "CE2"
+ _backdrop.kind = "marsh" if is_ce2 else "forest"
+ _title_label.text = UIText.get_text("world_map.title_ce2" if is_ce2 else "world_map.title")
+ _level_toggle_button.text = UIText.get_text(
+ "world_map.review_ce1_button" if is_ce2 else "world_map.back_to_ce2_button"
)
+ _total_stars_label.text = str(ProgressManager.stars())
_populate_missions()
@@ -48,8 +50,64 @@ func _populate_missions() -> void:
_map_area.remove_child(child)
child.queue_free()
+ var index := 0
for mission: Dictionary in ExerciseEngine.get_missions_for_level(_viewed_level):
- _map_area.add_child(_build_marker(mission))
+ var marker: Button = MARKER_SCENE.instantiate()
+ _map_area.add_child(marker)
+ marker.set_meta("map_position", mission["map_position"])
+ marker.setup(
+ mission,
+ ProgressManager.mission_best_stars(mission["mission_id"]),
+ MissionScoring.MAX_STARS,
+ _status_label_for(mission)
+ )
+ marker.pressed.connect(_on_mission_pressed.bind(mission["mission_id"]))
+ Motion.pop_in(marker, 0.04 * index)
+ index += 1
+ _layout_markers()
+
+
+## Replace chaque lieu selon sa position fractionnelle. Les positions du
+## niveau affiche sont etalees sur toute la zone (la plus petite colle au
+## bord, la plus grande au bord oppose) : les lieux restent entierement
+## visibles et ne se chevauchent pas, quelle que soit la taille de la fenetre.
+func _layout_markers() -> void:
+ var markers: Array[Control] = []
+ var low := Vector2(INF, INF)
+ var high := Vector2(-INF, -INF)
+ for child in _map_area.get_children():
+ var control := child as Control
+ if control == null or not control.has_meta("map_position"):
+ continue
+ var fraction: Dictionary = control.get_meta("map_position")
+ var point := Vector2(float(fraction["x"]), float(fraction["y"]))
+ low = Vector2(minf(low.x, point.x), minf(low.y, point.y))
+ high = Vector2(maxf(high.x, point.x), maxf(high.y, point.y))
+ markers.append(control)
+
+ for control in markers:
+ var fraction: Dictionary = control.get_meta("map_position")
+ var free_space := _map_area.size - control.size
+ control.position = Vector2(
+ _spread(float(fraction["x"]), low.x, high.x) * maxf(free_space.x, 0.0),
+ _spread(float(fraction["y"]), low.y, high.y) * maxf(free_space.y, 0.0)
+ )
+
+
+func _spread(value: float, low: float, high: float) -> float:
+ if high - low < 0.0001:
+ return 0.5
+ return (value - low) / (high - low)
+
+
+func _status_label_for(mission: Dictionary) -> String:
+ var rule: Dictionary = ExerciseEngine.get_rule(mission["rule_id"])
+ var skill_id: String = rule.get("skill", "")
+ var skill_state := ProgressManager.get_skill_state(skill_id)
+ var status: String = skill_state["status"]
+ if status == "decouverte":
+ return ""
+ return UIText.get_text("world_map.status_%s" % status)
## Revient au choix du profil sans rien perdre : la progression est ecrite
@@ -68,44 +126,6 @@ func _on_level_toggle_pressed() -> void:
_refresh()
-func _build_marker(mission: Dictionary) -> Control:
- var button := Button.new()
- button.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
- button.text = (
- "%s\n%s\n%s %d/%d"
- % [
- mission["title"],
- _status_label_for(mission),
- UIText.get_text("world_map.mission_stars_label"),
- ProgressManager.mission_best_stars(mission["mission_id"]),
- MissionScoring.MAX_STARS,
- ]
- )
-
- var map_position: Dictionary = mission["map_position"]
- var x: float = map_position["x"]
- var y: float = map_position["y"]
- button.anchor_left = x
- button.anchor_right = x
- button.anchor_top = y
- button.anchor_bottom = y
- button.offset_left = -MARKER_HALF_WIDTH
- button.offset_right = MARKER_HALF_WIDTH
- button.offset_top = -MARKER_HALF_HEIGHT
- button.offset_bottom = MARKER_HALF_HEIGHT
-
- button.pressed.connect(_on_mission_pressed.bind(mission["mission_id"]))
- return button
-
-
-func _status_label_for(mission: Dictionary) -> String:
- var rule: Dictionary = ExerciseEngine.get_rule(mission["rule_id"])
- var skill_id: String = rule.get("skill", "")
- var skill_state := ProgressManager.get_skill_state(skill_id)
- var status: String = skill_state["status"]
- return UIText.get_text("world_map.status_%s" % status)
-
-
func _on_mission_pressed(mission_id: String) -> void:
GameManager.pending_mission_id = mission_id
GameManager.goto(GameManager.SCENE_MISSION_INTRO)
diff --git a/scenes/world/world_map.tscn b/scenes/world/world_map.tscn
index b071b46..7e14e66 100644
--- a/scenes/world/world_map.tscn
+++ b/scenes/world/world_map.tscn
@@ -1,7 +1,9 @@
-[gd_scene load_steps=3 format=3]
+[gd_scene load_steps=5 format=3]
[ext_resource type="Script" path="res://scenes/world/world_map.gd" id="1"]
[ext_resource type="PackedScene" path="res://scenes/characters/guide_owl.tscn" id="2"]
+[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"]
+[ext_resource type="Texture2D" path="res://assets/interface/star_full.svg" id="3"]
[node name="WorldMap" type="Control"]
layout_mode = 3
@@ -12,55 +14,89 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
-[node name="Background" type="ColorRect" parent="."]
+[node name="Backdrop" parent="." instance=ExtResource("backdrop")]
+unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
-color = Color(0.3, 0.52, 0.33, 1)
-[node name="VBox" type="VBoxContainer" parent="."]
+[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
+theme_override_constants/margin_left = 20
+theme_override_constants/margin_top = 14
+theme_override_constants/margin_right = 20
+theme_override_constants/margin_bottom = 10
+
+[node name="VBox" type="VBoxContainer" parent="Margin"]
+layout_mode = 2
+theme_override_constants/separation = 8
+
+[node name="TopBar" type="HBoxContainer" parent="Margin/VBox"]
+layout_mode = 2
theme_override_constants/separation = 12
-[node name="TitleLabel" type="Label" parent="VBox"]
+[node name="ProfileButton" type="Button" parent="Margin/VBox/TopBar"]
unique_name_in_owner = true
+theme_type_variation = &"SecondaryButton"
layout_mode = 2
-theme_override_font_sizes/font_size = 32
+custom_minimum_size = Vector2(0, 52)
+
+[node name="TitleLabel" type="Label" parent="Margin/VBox/TopBar"]
+unique_name_in_owner = true
+theme_type_variation = &"HeaderTitle"
+layout_mode = 2
+size_flags_horizontal = 3
horizontal_alignment = 1
-[node name="TotalStarsLabel" type="Label" parent="VBox"]
+[node name="StarsBadge" type="PanelContainer" parent="Margin/VBox/TopBar"]
+theme_type_variation = &"Pill"
+layout_mode = 2
+
+[node name="Row" type="HBoxContainer" parent="Margin/VBox/TopBar/StarsBadge"]
+layout_mode = 2
+
+[node name="StarIcon" type="TextureRect" parent="Margin/VBox/TopBar/StarsBadge/Row"]
+layout_mode = 2
+custom_minimum_size = Vector2(34, 34)
+size_flags_vertical = 4
+texture = ExtResource("3")
+expand_mode = 1
+stretch_mode = 5
+
+[node name="TotalStarsLabel" type="Label" parent="Margin/VBox/TopBar/StarsBadge/Row"]
+unique_name_in_owner = true
+theme_type_variation = &"Emphasis"
+layout_mode = 2
+
+[node name="CollectionButton" type="Button" parent="Margin/VBox/TopBar"]
unique_name_in_owner = true
layout_mode = 2
-horizontal_alignment = 1
+custom_minimum_size = Vector2(0, 52)
-[node name="MapArea" type="Control" parent="VBox"]
+[node name="MapArea" type="Control" parent="Margin/VBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
-[node name="LevelToggleButton" type="Button" parent="VBox"]
-unique_name_in_owner = true
+[node name="BottomBar" type="HBoxContainer" parent="Margin/VBox"]
layout_mode = 2
-custom_minimum_size = Vector2(0, 48)
+theme_override_constants/separation = 12
-[node name="CollectionButton" type="Button" parent="VBox"]
+[node name="Guide" type="Control" parent="Margin/VBox/BottomBar" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
-custom_minimum_size = Vector2(0, 48)
-[node name="ProfileButton" type="Button" parent="VBox"]
-unique_name_in_owner = true
-layout_mode = 2
-custom_minimum_size = Vector2(0, 48)
-
-[node name="Guide" type="Control" parent="VBox" instance=ExtResource("2")]
+[node name="LevelToggleButton" type="Button" parent="Margin/VBox/BottomBar"]
unique_name_in_owner = true
+theme_type_variation = &"SecondaryButton"
layout_mode = 2
+size_flags_vertical = 4
+custom_minimum_size = Vector2(0, 52)
diff --git a/scripts/data/item_icons.gd b/scripts/data/item_icons.gd
new file mode 100644
index 0000000..c87f4a5
--- /dev/null
+++ b/scripts/data/item_icons.gd
@@ -0,0 +1,20 @@
+class_name ItemIcons
+extends RefCounted
+## Illustration d'un objet de la collection, resolue par identifiant dans un
+## repertoire fixe (CLAUDE.md : jamais de chemin libre dans les donnees). Un
+## objet sans illustration retombe sur un cadenas : jamais d'erreur a l'ecran.
+
+const ITEM_DIR := "res://assets/interface/items"
+const FALLBACK := "res://assets/interface/icon_locked.svg"
+const LOCKED := "res://assets/interface/icon_locked.svg"
+
+
+static func texture_for(item_id: String) -> Texture2D:
+ var path := "%s/%s.svg" % [ITEM_DIR, item_id]
+ if ResourceLoader.exists(path):
+ return load(path)
+ return load(FALLBACK)
+
+
+static func locked_texture() -> Texture2D:
+ return load(LOCKED)
diff --git a/scripts/data/item_icons.gd.uid b/scripts/data/item_icons.gd.uid
new file mode 100644
index 0000000..03ec59a
--- /dev/null
+++ b/scripts/data/item_icons.gd.uid
@@ -0,0 +1 @@
+uid://b4y5au3wsxe00
diff --git a/scripts/ui/motion.gd b/scripts/ui/motion.gd
new file mode 100644
index 0000000..3401e74
--- /dev/null
+++ b/scripts/ui/motion.gd
@@ -0,0 +1,26 @@
+class_name Motion
+extends RefCounted
+## Petites animations d'interface (apparition, rebond). Toutes respectent le
+## reglage d'animations (cahier des charges §11 : possibilite de les
+## desactiver) : elles ne font alors rien et l'element est affiche tel quel.
+
+
+## Fait grossir `node` depuis rien avec un leger rebond.
+static func pop_in(node: Control, delay: float = 0.0) -> void:
+ if not SettingsManager.animations_enabled:
+ return
+ node.scale = Vector2.ZERO
+ await node.get_tree().process_frame
+ node.pivot_offset = node.size / 2.0
+ var tween := node.create_tween()
+ tween.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
+ tween.tween_property(node, "scale", Vector2.ONE, 0.4).set_delay(delay)
+
+
+## Fait apparaitre `node` en fondu.
+static func fade_in(node: CanvasItem, duration: float = 0.25) -> void:
+ if not SettingsManager.animations_enabled:
+ return
+ node.modulate.a = 0.0
+ var tween := node.create_tween()
+ tween.tween_property(node, "modulate:a", 1.0, duration)
diff --git a/scripts/ui/motion.gd.uid b/scripts/ui/motion.gd.uid
new file mode 100644
index 0000000..6840dfe
--- /dev/null
+++ b/scripts/ui/motion.gd.uid
@@ -0,0 +1 @@
+uid://bjlpq2eeaynf2
diff --git a/tests/exercises/test_exercise_host_abandon.gd.uid b/tests/exercises/test_exercise_host_abandon.gd.uid
new file mode 100644
index 0000000..e3d1757
--- /dev/null
+++ b/tests/exercises/test_exercise_host_abandon.gd.uid
@@ -0,0 +1 @@
+uid://bgjhnrqowck7h
diff --git a/tests/world/test_reward_flow.gd b/tests/world/test_reward_flow.gd
index 9c78032..98efd90 100644
--- a/tests/world/test_reward_flow.gd
+++ b/tests/world/test_reward_flow.gd
@@ -83,5 +83,10 @@ func test_collection_lists_owned_and_mysterious_items() -> void:
var rows := collection.get_node("%ItemsBox").get_children()
assert_eq(rows.size(), ExerciseEngine.get_reward_items().size())
- assert_string_contains((rows[0] as Label).text, "Graine magique")
- assert_string_contains((rows[1] as Label).text, UIText.get_text("collection.locked_title"))
+ assert_string_contains(
+ (rows[0].find_child("TextLabel", true, false) as Label).text, "Graine magique"
+ )
+ assert_string_contains(
+ (rows[1].find_child("TextLabel", true, false) as Label).text,
+ UIText.get_text("collection.locked_title")
+ )
diff --git a/tools/screenshot.gd b/tools/screenshot.gd
new file mode 100644
index 0000000..c54052b
--- /dev/null
+++ b/tools/screenshot.gd
@@ -0,0 +1,72 @@
+extends SceneTree
+## Outil de developpement : charge une scene, attend quelques images puis
+## enregistre le rendu en PNG. Usage (fenetre reelle, pas --headless) :
+## godot --path . --resolution 1152x648 -s res://tools/screenshot.gd -- \
+## res://scenes/world/world_map.tscn out.png [CE1|CE2] [mission_id]
+## Cree un profil temporaire pour que les scenes qui lisent la progression
+## fonctionnent, puis le supprime.
+
+
+func _initialize() -> void:
+ var args := OS.get_cmdline_user_args()
+ var scene_path: String = args[0]
+ var out_path: String = args[1]
+ var level: String = args[2] if args.size() > 2 else "CE1"
+
+ var profiles: Node = root.get_node("ProfileManager")
+ var game: Node = root.get_node("GameManager")
+ var profile_id: String = profiles.call("create_profile", "Capture", level)
+ profiles.call("select_profile", profile_id)
+ if args.size() > 3:
+ game.set("pending_mission_id", args[3])
+
+ for arg in args:
+ if arg.begins_with("outcomes:"):
+ var typed: Array[String] = []
+ for part in arg.trim_prefix("outcomes:").split(","):
+ typed.append(part)
+ game.set("last_outcomes", typed)
+
+ var scene: Node = load(scene_path).instantiate()
+ root.add_child(scene)
+ for i in 5:
+ await process_frame
+ # Actions optionnelles sur exercise_host, a partir du 5e argument :
+ # continue_rule | answer: | toggle: | type: | validate
+ # | continue_feedback
+ for action in args.slice(4):
+ if action.begins_with("outcomes:"):
+ continue
+ await _run_action(scene, action)
+ for i in 5:
+ await process_frame
+ for i in 90:
+ await process_frame
+ var image := root.get_texture().get_image()
+ image.save_png(out_path)
+ profiles.call("delete_profile", profile_id)
+ quit(0)
+
+
+func _run_action(scene: Node, action: String) -> void:
+ var overlay: Node = scene.get_node("%OverlayContainer")
+ var exercise: Node = scene.get_node("%ExerciseContainer").get_child(0) if scene.get_node("%ExerciseContainer").get_child_count() > 0 else null
+ var parts := action.split(":", true, 1)
+ match parts[0]:
+ "continue_rule":
+ overlay.get_child(0).emit_signal("continued")
+ "answer":
+ exercise.emit_signal("answer_submitted", int(parts[1]))
+ "toggle":
+ exercise.get_node("%TokensBox").get_child(int(parts[1])).emit_signal("toggled", true)
+ "type":
+ var field: LineEdit = (
+ exercise.get_node("%TextEdit")
+ if exercise.has_node("%TextEdit")
+ else exercise.get_node("%CorrectionsBox").get_child(0)
+ )
+ field.text = parts[1]
+ "validate":
+ exercise.get_node("%ValidateButton").emit_signal("pressed")
+ "continue_feedback":
+ overlay.get_child(overlay.get_child_count() - 1).get_node("%ContinueButton").emit_signal("pressed")
diff --git a/tools/screenshot.gd.uid b/tools/screenshot.gd.uid
new file mode 100644
index 0000000..383f653
--- /dev/null
+++ b/tools/screenshot.gd.uid
@@ -0,0 +1 @@
+uid://su7hm1yepfuw