diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md index 4286d06..6eeb34e 100644 --- a/DESIGN_SYSTEM.md +++ b/DESIGN_SYSTEM.md @@ -1,921 +1,341 @@ # Design System - Sokko G -Ce document est la source de verite visuelle du projet Sokko G. Toute nouvelle interface doit respecter le theme **Nebula Command**. +Ce document est la reference visuelle du projet Sokko G. Toute nouvelle interface doit rester coherente avec le theme **Nebula Command** actuel. ## 1. Vision -Sokko G est une webapp gaming pensee pour des groupes d'amis pendant leurs sessions de jeu. Elle doit permettre de consulter rapidement des informations, gerer des toolboxes, notes, checklists, captures d'ecran, liens, ressources et pages informatives. +Sokko G est une webapp gaming locale, pensee pour rester ouverte sur un second ecran pendant une session. L'interface doit etre sombre, lisible, dense sans etre surchargee, et suffisamment immersive pour evoquer une ambiance nebuleuse premium. -L'interface doit etre sombre, moderne, immersive, lisible sur desktop et mobile, agreable pendant de longues sessions, inspiree par l'espace, les nebuleuses et les interfaces gaming premium. +Principes directeurs : -Eviter le style RGB agressif. L'ambiance recherchee est celle d'une galaxie calme, profonde et lumineuse. +- priorite a la lisibilite pendant de longues sessions ; +- pas de style RGB agressif ; +- violet et indigo pour la profondeur et les interactions ; +- dore pour les accents de hierarchie, les eyebrows, les focus et certaines bordures ; +- cyan conserve comme token secondaire, mais non dominant ; +- les effets lumineux doivent rester contenus. -## 2. Direction Artistique +## 2. Tokens -Nom du theme : **Nebula Command** +Les tokens source sont dans `website/src/styles/_tokens.scss`. Ne pas dupliquer les couleurs dans les composants. -Mots-cles : - -- galaxie ; -- nebuleuse ; -- espace profond ; -- violet electrique ; -- bleu cosmique ; -- verre teinte ; -- lumiere diffuse ; -- interface futuriste ; -- collaboration ; -- gaming premium. - -Principes : - -- l'arriere-plan principal est tres sombre, legerement bleute ; -- le violet et le bleu servent aux actions, etats actifs, informations importantes et effets de profondeur ; -- les effets lumineux restent subtils et ne doivent jamais reduire la lisibilite ; -- l'interface doit etre elegante, dense et exploitable pendant une session de jeu. - -## 3. Tokens CSS - -Utiliser ces variables comme base. Ne pas coder les couleurs en dur dans les composants. +Couleurs principales : ```css -:root { - color-scheme: dark; +--color-bg-page: #070913; +--color-bg-deep: #050711; +--color-bg-sidebar: #0b0e1b; +--color-bg-surface: #101426; +--color-bg-surface-alt: #151a30; +--color-bg-elevated: #1a2038; +--color-bg-hover: #202745; - --color-bg-page: #070913; - --color-bg-deep: #050711; - --color-bg-sidebar: #0b0e1b; - --color-bg-surface: #101426; - --color-bg-surface-alt: #151a30; - --color-bg-elevated: #1a2038; - --color-bg-hover: #202745; +--color-primary: #8b5cf6; +--color-primary-hover: #9f7aea; +--color-primary-active: #7347dc; +--color-primary-soft: rgba(139, 92, 246, 0.14); +--color-primary-border: rgba(139, 92, 246, 0.45); - --color-primary: #8b5cf6; - --color-primary-hover: #9f7aea; - --color-primary-active: #7347dc; - --color-primary-soft: rgba(139, 92, 246, 0.14); - --color-primary-border: rgba(139, 92, 246, 0.45); +--color-accent-gold: #f6c453; +--color-accent-cyan: #22d3ee; +--color-accent-pink: #d946ef; +--color-accent-indigo: #6366f1; - --color-secondary: #3b82f6; - --color-secondary-hover: #60a5fa; - --color-secondary-active: #2563eb; - --color-secondary-soft: rgba(59, 130, 246, 0.14); +--color-text-primary: #f5f7ff; +--color-text-secondary: #b4bdd3; +--color-text-muted: #7d879f; - --color-accent-cyan: #22d3ee; - --color-accent-pink: #d946ef; - --color-accent-indigo: #6366f1; - - --color-text-primary: #f5f7ff; - --color-text-secondary: #b4bdd3; - --color-text-muted: #7d879f; - --color-text-disabled: #555e73; - --color-text-inverse: #080a13; - - --color-border: rgba(150, 165, 205, 0.14); - --color-border-hover: rgba(150, 165, 205, 0.28); - --color-border-strong: rgba(167, 139, 250, 0.42); - - --color-success: #34d399; - --color-success-soft: rgba(52, 211, 153, 0.13); - --color-warning: #fbbf24; - --color-warning-soft: rgba(251, 191, 36, 0.13); - --color-danger: #fb7185; - --color-danger-soft: rgba(251, 113, 133, 0.13); - --color-info: #38bdf8; - --color-info-soft: rgba(56, 189, 248, 0.13); - - --gradient-brand: linear-gradient(135deg, #8b5cf6 0%, #6366f1 45%, #3b82f6 100%); - --gradient-nebula: linear-gradient( - 135deg, - rgba(139, 92, 246, 0.28), - rgba(59, 130, 246, 0.18), - rgba(217, 70, 239, 0.12) - ); - --gradient-page: - radial-gradient(circle at 20% 10%, rgba(124, 58, 237, 0.16), transparent 34%), - radial-gradient(circle at 85% 20%, rgba(37, 99, 235, 0.12), transparent 30%), - radial-gradient(circle at 55% 90%, rgba(217, 70, 239, 0.08), transparent 35%), - #070913; - - --font-size-xs: 0.75rem; - --font-size-sm: 0.875rem; - --font-size-base: 1rem; - --font-size-lg: 1.125rem; - --font-size-xl: 1.25rem; - --font-size-2xl: 1.5rem; - --font-size-3xl: 2rem; - --font-size-4xl: 2.5rem; - - --font-weight-regular: 400; - --font-weight-medium: 500; - --font-weight-semibold: 600; - --font-weight-bold: 700; - - --space-1: 0.25rem; - --space-2: 0.5rem; - --space-3: 0.75rem; - --space-4: 1rem; - --space-5: 1.25rem; - --space-6: 1.5rem; - --space-8: 2rem; - --space-10: 2.5rem; - --space-12: 3rem; - --space-16: 4rem; - - --radius-sm: 6px; - --radius-md: 10px; - --radius-lg: 14px; - --radius-xl: 18px; - --radius-2xl: 24px; - --radius-pill: 999px; - - --shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.22); - --shadow-md: 0 12px 30px rgba(0, 0, 0, 0.3); - --shadow-lg: 0 24px 60px rgba(0, 0, 0, 0.42); - --shadow-primary: 0 0 28px rgba(139, 92, 246, 0.22); - --shadow-secondary: 0 0 28px rgba(59, 130, 246, 0.18); - - --duration-fast: 120ms; - --duration-normal: 180ms; - --duration-slow: 260ms; - --ease-standard: cubic-bezier(0.2, 0.8, 0.2, 1); - - --breakpoint-sm: 640px; - --breakpoint-md: 768px; - --breakpoint-lg: 1024px; - --breakpoint-xl: 1280px; - --breakpoint-2xl: 1536px; -} +--color-border: rgba(150, 165, 205, 0.14); +--color-border-hover: rgba(150, 165, 205, 0.28); ``` +Rayons : + +- `--radius-sm: 6px` +- `--radius-md: 10px` +- `--radius-lg: 14px` +- `--radius-xl: 18px` +- `--radius-pill: 999px` + +Animations : + +- `--duration-fast: 120ms` +- `--duration-normal: 180ms` +- `--ease-standard: cubic-bezier(0.2, 0.8, 0.2, 1)` + +## 3. Fond Nebula + +Le fond global est defini dans `website/src/styles/_nebula.scss`. + +Regles : + +- fond de page tres sombre avec nebuleuses violettes, indigo, rose et leger accent dore ; +- etoiles statiques, reparties de facon non alignee ; +- pas d'animation permanente de fond ; +- le contenu reste prioritaire sur l'ambiance. + +Ne pas ajouter d'orbes decoratifs supplementaires. Si une section a besoin de profondeur, utiliser les fonds semi-transparents et les bordures existantes. + ## 4. Typographie Police principale : ```css -font-family: "Inter", "Segoe UI", system-ui, sans-serif; -``` - -Police d'accent optionnelle pour logo, grands titres, compteurs et titres de toolboxes : - -```css -font-family: "Space Grotesk", "Inter", sans-serif; +font-family: Inter, "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif; ``` Regles : -- corps de texte : 14 a 16 px ; -- titres de cartes : 15 a 18 px ; -- titres de page : 28 a 36 px ; -- hauteur de ligne : 1.5 a 1.65 ; -- paragraphes longs limites a environ 70 caracteres ; -- eviter les gris trop sombres sur fond noir. +- pas de letter-spacing negatif ; +- corps de texte entre 14 et 16 px ; +- titres de cartes/modules entre 15 et 18 px ; +- titres de page avec `clamp` deja defini dans `_home.scss` ; +- paragraphs limites visuellement, pas de longs blocs sans respiration ; +- les `` doivent etre en italique, pas utilises comme gris decoratif. -## 5. Layout Application +## 5. Heroes -Desktop : +Les heroes de la page d'accueil et des pages jeux utilisent le meme style via `.hero` et `.game-hero`. -- sidebar gauche : `248px`, reductible a environ `72px` ; -- zone centrale : flexible, `max-width: 1600px` ; -- panneau contextuel droit facultatif : `320px`, repliable. +Caracteristiques : -La sidebar contient le logo, le nom de l'application, la creation de toolbox, la liste des toolboxes, les raccourcis vers les pages informatives, les parametres et le profil. +- fond commun nebula sombre ; +- overlay sombre pour garder le texte lisible ; +- bordure gradient subtile dore/violet clair ; +- `border-radius: var(--radius-xl)` ; +- `box-shadow: var(--shadow-md)` ; +- pas de background inline sur les heroes de jeux. -La zone centrale contient le titre, les actions principales, les outils, les resultats et les contenus. +Les pages jeux doivent heriter du hero commun. Si un jeu a une image, elle doit etre utilisee dans les cards ou les covers, pas comme remplacement complet du hero commun sauf decision explicite. -Le panneau droit peut afficher membres, activite recente, details, filtres, historique ou options de partage. +## 6. Cards -Mobile : +Les cards representent les elements repetes : toolboxes, jeux, categories, monstres, faune. -- navigation inferieure ou menu lateral ; -- contenu en une colonne ; -- boutons importants pleine largeur ; -- modales quasi plein ecran ; -- filtres dans un panneau bas ; -- cibles tactiles d'au moins `44px`. +Style courant : -## 6. Navigation +- fond sombre `rgba(16, 20, 38, 0.88)` ; +- bordure gradient fine, majoritairement doree avec une petite presence cyan ; +- support optionnel de `corner-shape: scoop` quand le navigateur le supporte ; +- pas de translation au hover pour les cards principales ; +- hover en violet/violet clair avec glow contenu. -Element de navigation : +La bordure haute/basse interne d'une cover doit rester doree quand elle separe une image du contenu. -```css -.nav-item { - display: flex; - align-items: center; - gap: 12px; - min-height: 42px; - padding: 10px 12px; - border-radius: 10px; - color: var(--color-text-secondary); - transition: 160ms ease; -} +Eviter les cards dans des cards. Les sections de page doivent rester des layouts ou panneaux, pas des empilements de cartes decoratives. -.nav-item:hover { - color: var(--color-text-primary); - background: var(--color-bg-hover); -} +## 7. Modules Toolbox -.nav-item.active { - color: var(--color-text-primary); - background: var(--color-primary-soft); - border: 1px solid var(--color-primary-border); - box-shadow: inset 3px 0 0 var(--color-primary); -} -``` +Les modules sont des panneaux de travail, pas des cards classiques. -L'etat actif doit etre clair sans fond violet trop sature. +Style courant : -## 7. Topbar +- fond semi-transparent `rgba(16, 20, 38, 0.42)` ; +- bordure neutre tres discrete ; +- accent dore leger en bas/droite ; +- hover violet/violet clair moins intense que les cards ; +- pas de mouvement au hover ; +- contenu interne au-dessus des pseudo-bordures via `z-index`. -La barre superieure de la zone centrale peut contenir titre de page, toolbox active, recherche globale, avatars, notifications, partage et menu utilisateur. +Le mot visible cote utilisateur est **outil**, pas **module**. Le terme module reste acceptable uniquement dans le code. -```css -.topbar { - min-height: 68px; - position: sticky; - top: 0; - z-index: 20; - backdrop-filter: blur(18px); - background: rgba(7, 9, 19, 0.78); -} -``` +## 8. Boutons -## 8. Cartes +### Primary -```css -.card { - background: rgba(16, 20, 38, 0.88); - border: 1px solid var(--color-border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-sm); - padding: 20px; - transition: - transform var(--duration-fast) var(--ease-standard), - border-color var(--duration-fast) var(--ease-standard), - background-color var(--duration-fast) var(--ease-standard); -} - -.card:hover { - transform: translateY(-2px); - border-color: var(--color-border-hover); - background: rgba(21, 26, 48, 0.96); -} - -.card.selected { - border-color: var(--color-primary-border); - box-shadow: var(--shadow-md), var(--shadow-primary); -} -``` - -Une carte peut contenir icone, titre, description courte, statut, date/auteur, actions secondaires, contenu et pied de carte. - -## 9. Boutons - -Bouton principal : - -```css -.button-primary { - min-height: 42px; - padding: 0 18px; - border: none; - border-radius: var(--radius-md); - color: white; - font-weight: 600; - background: var(--gradient-brand); - box-shadow: 0 8px 22px rgba(99, 102, 241, 0.22); -} - -.button-primary:hover { - filter: brightness(1.08); - transform: translateY(-1px); - box-shadow: 0 10px 28px rgba(139, 92, 246, 0.3); -} -``` - -Bouton secondaire : - -```css -.button-secondary { - min-height: 42px; - padding: 0 18px; - border: 1px solid var(--color-border); - border-radius: var(--radius-md); - color: var(--color-text-primary); - background: var(--color-bg-surface-alt); -} -``` - -Bouton fantome : - -```css -.button-ghost { - min-height: 38px; - padding: 0 12px; - border: 1px solid transparent; - color: var(--color-text-secondary); - background: transparent; -} - -.button-ghost:hover { - color: var(--color-text-primary); - background: var(--color-bg-hover); -} -``` - -Bouton danger : reserve aux actions destructrices, avec confirmation si l'action est irreversible. - -Bouton icone : - -```css -.icon-button { - width: 40px; - height: 40px; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 10px; - color: var(--color-text-secondary); - background: transparent; -} -``` - -## 10. Formulaires - -```css -.input { - width: 100%; - min-height: 44px; - padding: 0 14px; - border-radius: var(--radius-md); - border: 1px solid var(--color-border); - background: #0c1020; - color: var(--color-text-primary); - outline: none; -} - -.input::placeholder { - color: var(--color-text-muted); -} - -.input:focus { - border-color: var(--color-primary); - box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.16); -} - -.input.error { - border-color: var(--color-danger); - box-shadow: 0 0 0 3px var(--color-danger-soft); -} -``` - -Textarea : - -- hauteur minimale `180px` ; -- `resize: vertical` ; -- `line-height: 1.6` ; -- style coherent avec les autres champs. - -## 11. Outils Toolbox - -### Bloc-note - -Prevoir titre, contenu, auteur, date de derniere modification, autosauvegarde, statut de sauvegarde, epinglage, partage, suppression, lecture/edition et historique eventuel. - -```css -.note-editor { - background: rgba(5, 7, 17, 0.56); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); - padding: 16px; -} -``` - -### Checklist - -Chaque tache peut comprendre case a cocher, titre, description, responsable, priorite, echeance et menu d'actions. - -```css -.checkbox { - width: 20px; - height: 20px; - border-radius: 6px; - border: 1px solid var(--color-border-hover); - background: var(--color-bg-deep); -} - -.checkbox.checked { - border-color: transparent; - background: var(--gradient-brand); -} -``` - -Priorites : - -- faible : bleu ; -- normale : violet ; -- elevee : orange ; -- critique : rouge. - -Ne jamais utiliser uniquement la couleur pour communiquer la priorite. - -### Captures d'ecran - -La galerie doit etre responsive : - -- mobile : 1 colonne ; -- tablette : 2 colonnes ; -- desktop : 3 ou 4 colonnes. - -Carte de capture : apercu, nom, auteur, date, tags, copier le lien, telecharger, ouvrir en grand, supprimer selon permissions. - -Dropzone : - -```css -.dropzone { - min-height: 160px; - border: 1px dashed rgba(139, 92, 246, 0.52); - border-radius: var(--radius-lg); - background: var(--color-primary-soft); -} - -.dropzone.drag-active { - border-color: var(--color-accent-cyan); - background: rgba(34, 211, 238, 0.1); - box-shadow: 0 0 30px rgba(34, 211, 238, 0.12); -} -``` - -Visionneuse : fond presque noir, image centree, zoom, navigation entre images, metadonnees en panneau lateral, fermeture avec `Escape`. - -## 12. Toolboxes - -Une toolbox represente un espace de jeu. Une carte de toolbox affiche nom, icone/image, couleur, nombre de membres, nombre d'outils, derniere activite, avatars et statut prive/public. Les toolboxes ne portent pas de champ description. - -La toolbox active peut utiliser une bordure en degrade, une lueur violette subtile, une pastille active et un fond legerement plus lumineux. - -Creation d'une toolbox : - -- nom ; -- icone ; -- couleur d'accent ; -- confidentialite ; -- membres ; -- outils ajoutes immediatement. - -Couleurs d'accent proposees : violet, bleu, cyan, indigo, rose cosmique, vert emeraude. - -## 13. Pages Informatives - -Les pages informatives presentent des donnees structurees : objets, cartes, ressources, personnages, statistiques, strategies, equipements, recettes, quetes, evenements et liens utiles. - -Structure recommandee : - -1. titre ; -2. description ; -3. recherche ; -4. filtres ; -5. selecteur d'affichage ; -6. grille ou tableau ; -7. pagination ; -8. panneau de detail. - -La structure visuelle doit rester independante de la source de donnees JSON ou API. - -Exemple : - -```json -{ - "id": "item-001", - "name": "Nom de l'element", - "category": "Categorie", - "rarity": "epic", - "tags": ["support", "coop"], - "image": "/images/item-001.webp", - "description": "Description de l'element", - "stats": { - "power": 85, - "speed": 42 - } -} -``` - -## 14. Filtres Et Tableaux - -Filtres possibles : recherche textuelle, categorie, rarete, jeu, type, auteur, date, tags, statut et favoris. - -Desktop : barre horizontale, panneau lateral droit ou zone repliable au-dessus du contenu. - -Mobile : panneau venant du bas. - -```css -.filter-chip.active { - color: #fff; - background: var(--color-primary-soft); - border-color: var(--color-primary-border); -} -``` - -Afficher le nombre de filtres actifs et prevoir une action de reinitialisation. - -Tableaux : - -- en-tete lisible en dark mode ; -- ligne separee par `var(--color-border)` ; -- hover discret `rgba(139, 92, 246, 0.06)` ; -- en-tete fixe pour les longs tableaux ; -- tri indique par une fleche ; -- valeurs numeriques alignees a droite ; -- mobile : cartes ou scroll horizontal. - -## 15. Badges, Avatars, Presence - -Badge : - -```css -.badge { - display: inline-flex; - align-items: center; - gap: 6px; - min-height: 24px; - padding: 3px 9px; - border-radius: var(--radius-pill); - font-size: 12px; - font-weight: 600; -} -``` - -Exemples : - -- en ligne : `background: var(--color-success-soft); color: var(--color-success);` -- prive : `background: var(--color-primary-soft); color: #b69cff;` -- nouveau : `background: var(--color-info-soft); color: var(--color-info);` -- attention : `background: var(--color-warning-soft); color: var(--color-warning);` - -Tailles avatar : - -```css ---avatar-xs: 24px; ---avatar-sm: 32px; ---avatar-md: 40px; ---avatar-lg: 52px; -``` - -Presence : - -- vert : en ligne ; -- bleu : en jeu ; -- jaune : absent ; -- gris : hors ligne ; -- rouge : ne pas deranger. - -Ne pas reposer uniquement sur la couleur. Ajouter un tooltip ou un libelle accessible. - -## 16. Notifications, Modales, Menus - -Toasts : - -- position : `right: 20px; bottom: 20px;` -- information : 4 s ; -- succes : 3 a 4 s ; -- avertissement : 6 s ; -- erreur : persistante ou 8 s. - -Les notifications ne doivent pas masquer les actions principales. - -Modales : - -```css -.modal-backdrop { - background: rgba(2, 3, 9, 0.76); - backdrop-filter: blur(8px); -} - -.modal { - width: min(560px, calc(100vw - 32px)); - max-height: calc(100vh - 48px); - overflow-y: auto; - border: 1px solid var(--color-border); - border-radius: var(--radius-xl); - background: var(--color-bg-surface); - box-shadow: var(--shadow-lg); -} -``` - -Les actions de modale sont regroupees en bas a droite. Sur mobile, une modale peut devenir un panneau plein ecran. - -Menus contextuels : - -- fond eleve ; -- ombre forte ; -- separateurs fins ; -- rayon 10 a 12 px ; -- largeur minimale 180 px ; -- action dangereuse isolee visuellement. - -## 17. Etats UI - -Etats vides : - -- icone ou illustration simple ; -- titre ; -- explication courte ; -- action claire ; -- jamais de section completement vide. - -Chargement : - -- skeletons pour cartes, listes, tableaux, galeries, profils ; -- spinner reserve aux petites actions ; -- bouton en chargement conserve sa largeur. - -Erreur : - -- icone ; -- titre clair ; -- explication comprehensible ; -- detail technique secondaire si utile ; -- bouton pour reessayer. - -## 18. Icones - -Bibliotheque recommandee : Lucide Icons. Ne pas melanger plusieurs bibliotheques. +Les boutons primary sont plus contrastes que les boutons standards. Style : -- contour ; -- epaisseur reguliere ; -- taille 18 a 22 px ; -- coins legerement arrondis. +- fond violet/indigo sombre ; +- bordure doree ; +- hover plus lumineux avec bordure doree plus nette ; +- glow violet/dore contenu ; +- pas de `brightness()` global ; +- pas de translation verticale. -Mappings recommandes : +### Boutons standards -- toolbox : `Package`, `Boxes`, `Briefcase` ; -- bloc-note : `NotebookPen` ; -- checklist : `ListChecks` ; -- screenshot : `Image` ; -- membres : `Users` ; -- informations : `Database`, `BookOpen` ; -- parametres : `Settings` ; -- filtre : `SlidersHorizontal` ; -- recherche : `Search` ; -- partager : `Share2` ; -- ajouter : `Plus`. +Hover : -## 19. Animations +- fond nebula plus lumineux ; +- bordure violet clair ; +- glow violet tres discret ; +- pas d'accent dore systematique. -Animations rapides et discretes uniquement : +### Ghost -- leger deplacement vertical au survol ; -- fondu ; -- changement de couleur ; -- ouverture fluide d'un panneau ; -- apparition d'un menu ; -- legere pulsation d'un statut en ligne ; -- transition entre vues grille/liste. +Hover minimal : -Eviter animations permanentes, rotations gratuites, zooms importants, flashes et effets neon agressifs. +- leger fond violet transparent ; +- texte plus clair ; +- pas de glow. + +### Danger + +Reserve aux actions destructrices. + +- couleur rouge au repos ; +- hover rouge sombre, bordure rouge, texte/icones blancs ; +- confirmation en modale pour les actions irreversibles. + +### Boutons icones + +Les icones utilisent `currentColor` via `.ui-icon`. + +- taille habituelle : 20 px ; +- bouton : 40 x 40 px ; +- hover neutre : icone violet clair `#c4b5fd` + leger glow ; +- hover danger : icone blanche + glow rouge discret ; +- utiliser les SVG de `website/public/static/icons`. + +## 9. Formulaires Et Checkbox + +Inputs et textareas : + +- fond sombre ; +- bordure fine ; +- focus dore via `:focus-visible` ; +- placeholders en `--color-text-muted`. + +Checkbox : + +- style custom global ; +- format carre strict ; +- fond non coche legerement eclaire ; +- bordure dore/violet ; +- etat coche avec gradient dore/violet et coche blanche ; +- spacing adapte dans les filtres. + +## 10. Navigation + +Sidebar : + +- fond sombre, stable ; +- icones SVG pour Accueil, Toolboxes et Jeux ; +- navigation active en violet soft ; +- le logo controller garde le gradient de l'ancien badge et une rotation au hover. + +Le nom de marque affiche `Sokko` en blanc et `G` en dore. Au hover, `Sokko` anime vers le dore de droite a gauche puis revient au hover out. + +## 11. Filtres Et Tableaux + +Filtres : + +- chips compactes ; +- checkbox, icone puis texte ; +- spacing lisible : checkbox pas collee au bord, icone proche du texte ; +- reset avec bouton icone `rubber`. + +Tableaux : + +- pas de scroll horizontal pour les tableaux de degats MHWilds ; +- cellules compactes ; +- bordures fines ; +- lisibilite prioritaire sur la decoration. + +## 12. Toolboxes + +Les toolboxes sont locales et stockees dans le navigateur. + +Regles UI : + +- pas de champ description ; +- image de jeu si associee a un jeu ; +- icone editable si toolbox libre ; +- action importer/exporter clairement identifiee ; +- quota localStorage visible en bas de page/panneau ; +- import/export global disponibles. + +Les cards toolbox doivent garder une taille compacte. L'image/icone de cover est cliquable. + +## 13. Donnees Jeux + +Le contenu editorial editable doit rester dans : + +- `website/public/data/site.json` pour les textes generaux ; +- `website/public/data/games.json` pour la liste des jeux ; +- `website/public/data/mhwilds/*.json` pour les donnees MHWilds. + +Les retours a la ligne `\n` du JSON doivent etre rendus correctement. Le JSX ne doit pas contenir de contenu editorial long si une entree JSON existe deja. + +## 14. Modales Et Drawer + +Modales : + +- fond floute mais pas trop opaque ; +- panneau centre ; +- fermeture au clic exterieur quand l'action le permet ; +- boutons icones pour fermer/zoomer quand approprie. + +Drawer toolbox : + +- panneau lateral redimensionnable ; +- overlay floute leger ; +- les actions internes ne doivent pas fermer le drawer sauf action explicite. + +## 15. Scrollbars + +Les scrollbars reprennent l'ancien rendu : ```css -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } -} -``` - -## 20. Scrollbars - -Les zones scrollables doivent reprendre le style de l'ancien site : piste sombre, ombre interne claire et pouce avec degrade violet/indigo. Pour l'integration **Nebula Command**, le rail noir historique est remplace par un bleu-noir profond. - -Version globale recommandee : - -```css -.legacy-scrollbar::-webkit-scrollbar { - background-color: transparent; +::-webkit-scrollbar { width: 6px; height: 6px; - border-radius: 0; + background-color: transparent; } -.legacy-scrollbar::-webkit-scrollbar-track { +::-webkit-scrollbar-track { background-color: transparent; - border-radius: 0; box-shadow: none; } -.legacy-scrollbar::-webkit-scrollbar-thumb { - background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.5, rgb(79, 26, 114)), color-stop(1, rgb(44, 1, 135))); +::-webkit-scrollbar-thumb { + background-image: -webkit-gradient( + linear, + left bottom, + left top, + color-stop(0.5, rgb(79, 26, 114)), + color-stop(1, rgb(44, 1, 135)) + ); border-radius: 10px; } ``` -Dimensions recommandees : +Ne pas ajouter de rail noir visible ni de double bordure pres des panneaux. -- scrollbar principale, panneau lateral, drawer, liste de filtres ou tableau : `6px` ; -- rail transparent pour eviter les doubles bordures avec les panneaux ; -- thumb arrondi sans bordure interne ; -- sur un panneau borde, ajouter un leger `padding-right` au contenu scrollable pour separer le thumb de la bordure du panneau ; -- ne pas utiliser de scrollbar trop lumineuse sur les zones de lecture longues. - -Les scrollbars doivent etre appliquees aux panneaux, menus, drawers, listes de filtres, tableaux et zones de details. Utiliser la classe `.legacy-scrollbar` sur les zones internes qui doivent reprendre explicitement l'ancien rendu. Eviter les overrides globaux comme `scrollbar-width` ou `scrollbar-color`, car ils changent l'aspect par rapport a l'ancien site. - -## 21. Accessibilite - -Minimum WCAG AA. +## 16. Images Et Assets Regles : +- les pages jeux et toolboxes associees utilisent les images de jeu en cover ; +- les images de jeu doivent etre rangees par jeu dans `website/public/static/img/games//` ; +- utiliser `card-cover.png` pour les cards et `hero-bg.png` pour le futur fond de hero ; +- declarer ces images dans `games.json` via `images.cardCover` et `images.heroBg` ; +- les toolboxes libres utilisent une icone de `website/public/static/img/toolbox-icons` ; +- `toolbox.png` est l'icone par defaut ; +- les categories de jeu utilisent des images representatives, entieres et bien cadrees ; +- eviter les images trop grandes dans les cards. + +## 17. Accessibilite + +Minimum attendu : + +- focus visible dore ; +- boutons reels pour les actions ; +- liens reels pour la navigation ; +- labels explicites ou `aria-label` pour les boutons icones ; - contraste suffisant ; -- navigation clavier complete ; -- focus visible ; -- labels de champs ; -- textes alternatifs aux images ; -- ne jamais utiliser uniquement la couleur pour transmettre une information ; -- boutons reels pour actions ; -- liens reels pour navigation ; -- hierarchie correcte `h1`, `h2`, `h3` ; -- notifications importantes annoncees aux lecteurs d'ecran ; -- modales correctement gerees au clavier. +- modales avec `role="dialog"` et `aria-modal="true"` ; +- ne pas transmettre une information uniquement par couleur. -```css -:focus-visible { - outline: 2px solid var(--color-accent-cyan); - outline-offset: 3px; -} -``` +## 18. A Eviter -## 22. Fond Galaxie Et Effet Verre - -Fond recommande : - -- gradient CSS ; -- pseudo-element avec motif discret ; -- particules statiques seulement ; -- aucun canvas anime en permanence par defaut. - -```css -body { - min-height: 100vh; - color: var(--color-text-primary); - background: var(--gradient-page); -} - -body::before { - content: ""; - position: fixed; - inset: 0; - pointer-events: none; - opacity: 0.25; - background-image: - radial-gradient(circle, rgba(255, 255, 255, 0.5) 1px, transparent 1px), - radial-gradient(circle, rgba(139, 92, 246, 0.35) 1px, transparent 1px); - background-size: 90px 90px, 140px 140px; - background-position: 0 0, 35px 60px; -} -``` - -Effet verre autorise sur topbar, modales, panneaux flottants, barre de filtres et menus. Ne pas l'appliquer a toutes les cartes. - -```css -.glass-panel { - background: rgba(16, 20, 38, 0.72); - border: 1px solid rgba(165, 180, 252, 0.14); - backdrop-filter: blur(18px); - -webkit-backdrop-filter: blur(18px); -} -``` - -## 23. Ecrans Cibles - -Accueil : - -- message de bienvenue ; -- toolbox recemment utilisee ; -- bouton creer une toolbox ; -- bouton rejoindre une toolbox si collaboration activee ; -- section Mes toolboxes ; -- activite recente ; -- outils rapides. - -Page toolbox : - -- nom, icone ; -- membres ; -- inviter, partager, options ; -- onglets : vue d'ensemble, notes, checklists, captures, fichiers, informations, activite ; -- vue d'ensemble avec outils epingles, notes recentes, taches en cours, dernieres captures et activite. - -Pages informatives : - -- recherche ; -- filtres ; -- vue grille/table ; -- detail ; -- association rapide a une toolbox. - -## 24. Permissions Et Themes Par Toolbox - -Roles visuels a prevoir : - -- proprietaire ; -- administrateur ; -- membre ; -- lecteur. - -Actions indisponibles : - -- masquer si cela simplifie l'interface ; -- sinon desactiver avec tooltip explicatif. - -Chaque toolbox peut definir une couleur d'accent : - -```css -.toolbox-theme { - --toolbox-accent: #8b5cf6; - --toolbox-accent-soft: rgba(139, 92, 246, 0.14); -} -``` - -Cette couleur peut etre utilisee pour icone, bordure active, badges, graphiques, titre et certains boutons secondaires. La couleur principale de l'application doit rester coherente. - -## 25. Recommandations Techniques - -Stack suggeree pour une refonte propre : - -- React ou Next.js ; -- TypeScript strict ; -- Tailwind CSS ou CSS Modules ; -- Lucide React pour les icones ; -- Radix UI ou shadcn/ui pour les composants accessibles ; -- Framer Motion uniquement pour quelques transitions utiles ; -- React Hook Form pour les formulaires ; -- Zod pour la validation ; -- TanStack Query pour les donnees distantes ; -- TanStack Table pour les tableaux ; -- Zustand ou Context API pour un etat global leger. - -Organisation cible : - -```text -src/ - components/ - ui/ - Button - Input - Textarea - Modal - Dropdown - Badge - Avatar - Tooltip - Tabs - Card - Toast - Skeleton - layout/ - AppShell - Sidebar - Topbar - MobileNavigation - ContextPanel - toolbox/ - ToolboxCard - ToolboxHeader - ToolboxMembers - ToolboxSettings - CreateToolboxModal - tools/ - NoteTool - ChecklistTool - ScreenshotTool - FileTool - information/ - FilterBar - DataCard - DataTable - DataDetailsPanel - EmptyResults - pages/ - hooks/ - services/ - stores/ - styles/ - types/ -``` - -Regles de developpement : - -- creer les composants UI de base avant les pages ; -- centraliser les couleurs en variables CSS ; -- gerer les etats normal, hover, active, focus, disabled, loading et error ; -- concevoir mobile-first ; -- ajouter des donnees fictives JSON pour tester les vues ; -- separer les composants visuels de la recuperation des donnees ; -- prevoir les permissions ; -- traiter l'accessibilite des le debut ; -- eviter les composants de plus de 250 a 300 lignes ; -- ajouter des tests sur les composants critiques. +- accents dores partout ; +- hover trop lumineux sur tous les elements ; +- mouvements verticaux sur les cards/modules principaux ; +- background inline qui remplace le theme commun ; +- descriptions de toolbox ; +- alertes navigateur pour confirmations ou creation ; +- gros blocs explicatifs visibles dans l'app ; +- scroll horizontal sur les tableaux de donnees critiques. diff --git a/tests/static-smoke.test.mjs b/tests/static-smoke.test.mjs index 37e1348..a617677 100644 --- a/tests/static-smoke.test.mjs +++ b/tests/static-smoke.test.mjs @@ -13,6 +13,7 @@ test("vite entrypoint loads the react application", async () => { test("react application defines the expected local toolbox primitives", async () => { const source = await readFile("website/src/main.jsx", "utf8"); const styles = await readFile("website/src/styles/main.scss", "utf8"); + const styleNebula = await readFile("website/src/styles/_nebula.scss", "utf8"); const styleTokens = await readFile("website/src/styles/_tokens.scss", "utf8"); const styleBase = await readFile("website/src/styles/_base.scss", "utf8"); const styleHome = await readFile("website/src/styles/_home.scss", "utf8"); @@ -40,10 +41,13 @@ test("react application defines the expected local toolbox primitives", async () assert.match(source, /sokkog:game-toolbox-links/); assert.match(source, /styles\/main\.scss/); assert.match(styles, /@use "tokens"/); + assert.match(styles, /@use "nebula"/); assert.match(styles, /@use "toolboxes"/); assert.match(styles, /@use "mhwilds"/); assert.match(styleTokens, /--gradient-brand/); assert.match(styleBase, /\.card-icon-button,\n\.drawer-action-button/); + assert.match(styleNebula, /@function star-field/); + assert.match(styleNebula, /body::after/); assert.match(styleBase, /\.import-icon-button/); assert.match(styleHome, /\.dialogue-line i/); assert.match(styleHome, /font-style: italic/); @@ -54,6 +58,7 @@ test("react application defines the expected local toolbox primitives", async () assert.match(styleIcons, /ui-icon-drag/); assert.match(styleIcons, /ui-icon-dropdown/); assert.match(styleIcons, /ui-icon-flip/); + assert.match(styleIcons, /ui-icon-add/); assert.match(styleMhwilds, /\.mhwilds-card/); assert.match(styleMhwilds, /\.mhwilds-flip-indicator/); assert.match(styleResponsive, /@media \(max-width: 760px\)/); @@ -160,7 +165,8 @@ test("mhwilds data and assets are available", async () => { assert.ok(site.toolboxes.storageHelp.items.length >= 3); assert.equal(games.games[0].id, "mhwilds"); assert.equal(games.games[0].eyebrow, "Guide de jeu"); - assert.match(games.games[0].image, /mhwilds\.png/); + assert.match(games.games[0].images.cardCover, /games\/mhwilds\/card-cover\.png/); + assert.match(games.games[0].images.heroBg, /games\/mhwilds\/hero-bg\.png/); assert.ok(monsters.monsters.length > 0); assert.ok(endemicLife.endemicLife.length > 0); assert.equal(translations.monsters, "monstres"); @@ -168,6 +174,8 @@ test("mhwilds data and assets are available", async () => { const monsterCardSource = await readFile("website/src/features/games/mhwilds/cards/MonsterCard.jsx", "utf8"); assert.match(monsterCardSource, /monster-card/); await readFile("website/public/static/img/mhwilds/chatacabra.png"); + await readFile("website/public/static/img/games/mhwilds/card-cover.png"); + await readFile("website/public/static/img/games/mhwilds/hero-bg.png"); }); test("server and vite support local env configuration", async () => { @@ -187,6 +195,7 @@ test("legacy svg icons are available for reuse", async () => { assert.ok(manifest.icons.length >= 4); await readFile("website/public/static/icons/home.svg", "utf8"); + await readFile("website/public/static/icons/add.svg", "utf8"); await readFile("website/public/static/icons/checklist.svg", "utf8"); await readFile("website/public/static/icons/close.svg", "utf8"); await readFile("website/public/static/icons/columns.svg", "utf8"); diff --git a/website/public/data/games.json b/website/public/data/games.json index 1de6d7e..e4c2e62 100644 --- a/website/public/data/games.json +++ b/website/public/data/games.json @@ -4,8 +4,10 @@ "id": "mhwilds", "title": "Monster Hunter Wilds", "eyebrow": "Guide de jeu", - "cover": "linear-gradient(135deg, rgba(139, 92, 246, 0.88) 0%, rgba(99, 102, 241, 0.82) 48%, rgba(34, 211, 238, 0.72) 100%)", - "image": "/static/img/mhwilds.png", + "images": { + "cardCover": "/static/img/games/mhwilds/card-cover.png", + "heroBg": "/static/img/games/mhwilds/hero-bg.png" + }, "summary": "Repères rapides pour préparer une chasse, suivre les priorités et garder les infos utiles sur un second écran.", "sections": [ { diff --git a/website/public/static/icons/add.svg b/website/public/static/icons/add.svg new file mode 100644 index 0000000..fc03b43 --- /dev/null +++ b/website/public/static/icons/add.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/website/public/static/img/mhwilds.png b/website/public/static/img/games/mhwilds/card-cover.png similarity index 100% rename from website/public/static/img/mhwilds.png rename to website/public/static/img/games/mhwilds/card-cover.png diff --git a/website/public/static/img/games/mhwilds/hero-bg.png b/website/public/static/img/games/mhwilds/hero-bg.png new file mode 100644 index 0000000..cc5ac60 Binary files /dev/null and b/website/public/static/img/games/mhwilds/hero-bg.png differ diff --git a/website/src/features/games/GameRoute.jsx b/website/src/features/games/GameRoute.jsx index b58d554..e04520a 100644 --- a/website/src/features/games/GameRoute.jsx +++ b/website/src/features/games/GameRoute.jsx @@ -4,13 +4,14 @@ import { MhwildsPage } from "./mhwilds/MhwildsPage.jsx"; export function GameRoute(props) { const { gameId, games } = props; const game = games.find((item) => item.id === gameId); + const heroStyle = game?.images?.heroBg ? { "--game-hero-image": `url("${game.images.heroBg}")` } : undefined; if (!game) return ; - if (game.id === "mhwilds") return ; + if (game.id === "mhwilds") return ; return ( <> -
+

{game.eyebrow || "Guide de jeu"}

{game.title}

diff --git a/website/src/features/games/GamesPage.jsx b/website/src/features/games/GamesPage.jsx index 795c7d4..5c153eb 100644 --- a/website/src/features/games/GamesPage.jsx +++ b/website/src/features/games/GamesPage.jsx @@ -3,7 +3,7 @@ export function GamesPage({ siteContent, games, gamesError }) { return ( <> -
+

{content.eyebrow}

{content.title}

@@ -20,7 +20,7 @@ export function GamesPage({ siteContent, games, gamesError }) { title={`Ouvrir ${game.title}`} style={{ "--game-cover": game.cover || "var(--gradient-nebula)" }} > - {game.title} + {game.title}

{game.eyebrow || "Guide de jeu"}

diff --git a/website/src/features/games/mhwilds/MhwildsOverview.jsx b/website/src/features/games/mhwilds/MhwildsOverview.jsx index be050cd..10fc0f0 100644 --- a/website/src/features/games/mhwilds/MhwildsOverview.jsx +++ b/website/src/features/games/mhwilds/MhwildsOverview.jsx @@ -1,9 +1,11 @@ import { assetPath } from "./utils.js"; -export function MhwildsOverview() { +export function MhwildsOverview({ game }) { + const heroStyle = game?.images?.heroBg ? { "--game-hero-image": `url("${game.images.heroBg}")` } : undefined; + return ( <> -
+

Guide de jeu

Monster Hunter: Wilds

diff --git a/website/src/features/games/mhwilds/MhwildsPage.jsx b/website/src/features/games/mhwilds/MhwildsPage.jsx index 6852e8f..247d8dd 100644 --- a/website/src/features/games/mhwilds/MhwildsPage.jsx +++ b/website/src/features/games/mhwilds/MhwildsPage.jsx @@ -1,13 +1,14 @@ import { MhwildsListing } from "./MhwildsListing.jsx"; import { MhwildsOverview } from "./MhwildsOverview.jsx"; -export function MhwildsPage({ category, mhwilds, filters, setFilters, t }) { +export function MhwildsPage({ category, game, mhwilds, filters, setFilters, t }) { const activeCategory = category === "monsters" || category === "endemic" ? category : ""; + const heroStyle = game?.images?.heroBg ? { "--game-hero-image": `url("${game.images.heroBg}")` } : undefined; if (!mhwilds.loaded) { return ( <> -
+

Monster Hunter Wilds

Monster Hunter: Wilds

@@ -31,7 +32,7 @@ export function MhwildsPage({ category, mhwilds, filters, setFilters, t }) { ); } - if (!activeCategory) return ; + if (!activeCategory) return ; return ( [ type, { label: module.label, icon: module.icon } ])); +function getSequentialModuleColumns(modules, splitIndex) { + return [ + modules.slice(0, splitIndex), + modules.slice(splitIndex) + ]; +} + +function getColumnHeight(heights) { + if (!heights.length) return 0; + return heights.reduce((total, height) => total + height, 0) + MODULE_COLUMN_GAP_PX * (heights.length - 1); +} + +function chooseMeasuredSplitIndex(modules, moduleHeights, fallbackSplitIndex) { + if (modules.length <= 1) return modules.length; + if (modules.some((module) => !moduleHeights.get(module.id))) return fallbackSplitIndex; + + let bestSplitIndex = fallbackSplitIndex; + let bestDiff = Number.POSITIVE_INFINITY; + let bestLeftDominantSplitIndex = 0; + let bestLeftDominantDiff = Number.POSITIVE_INFINITY; + + for (let splitIndex = 1; splitIndex < modules.length; splitIndex += 1) { + const leftHeight = getColumnHeight(modules.slice(0, splitIndex).map((module) => moduleHeights.get(module.id))); + const rightHeight = getColumnHeight(modules.slice(splitIndex).map((module) => moduleHeights.get(module.id))); + const diff = Math.abs(leftHeight - rightHeight); + + if (leftHeight >= rightHeight && diff < bestLeftDominantDiff) { + bestLeftDominantDiff = diff; + bestLeftDominantSplitIndex = splitIndex; + } + + if (diff < bestDiff) { + bestDiff = diff; + bestSplitIndex = splitIndex; + } + } + + return bestLeftDominantSplitIndex || bestSplitIndex; +} + export function AddToolControls({ onAdd }) { const [open, setOpen] = useState(false); const [quickOpen, setQuickOpen] = useState(false); @@ -134,6 +176,48 @@ export function AddToolControls({ onAdd }) { export function ToolboxModules({ toolbox, moduleColumns, context, onRename, onDelete, onMove }) { const [draggingModuleId, setDraggingModuleId] = useState(""); const [dropTarget, setDropTarget] = useState({ id: "", placement: "before" }); + const [measuredSplitIndex, setMeasuredSplitIndex] = useState(() => Math.ceil(toolbox.modules.length / 2)); + const moduleElementsRef = useRef(new Map()); + const moduleIdSignature = useMemo(() => toolbox.modules.map((module) => module.id).join("|"), [toolbox.modules]); + + useEffect(() => { + setMeasuredSplitIndex(Math.ceil(toolbox.modules.length / 2)); + }, [toolbox.id, moduleIdSignature, toolbox.modules.length]); + + useLayoutEffect(() => { + if (moduleColumns !== 2 || toolbox.modules.length <= 1) return undefined; + + let frameId = 0; + function rebalanceColumns() { + cancelAnimationFrame(frameId); + frameId = requestAnimationFrame(() => { + const moduleHeights = new Map(); + toolbox.modules.forEach((module) => { + const element = moduleElementsRef.current.get(module.id); + if (element) moduleHeights.set(module.id, element.getBoundingClientRect().height); + }); + + setMeasuredSplitIndex((currentSplitIndex) => { + const fallbackSplitIndex = Math.min(Math.max(currentSplitIndex, 1), toolbox.modules.length - 1); + const nextSplitIndex = chooseMeasuredSplitIndex(toolbox.modules, moduleHeights, fallbackSplitIndex); + return nextSplitIndex === currentSplitIndex ? currentSplitIndex : nextSplitIndex; + }); + }); + } + + const observer = new ResizeObserver(rebalanceColumns); + + toolbox.modules.forEach((module) => { + const element = moduleElementsRef.current.get(module.id); + if (element) observer.observe(element); + }); + rebalanceColumns(); + + return () => { + cancelAnimationFrame(frameId); + observer.disconnect(); + }; + }, [moduleColumns, toolbox.id, moduleIdSignature, toolbox.modules.length]); useEffect(() => { if (!draggingModuleId) return undefined; @@ -180,6 +264,14 @@ export function ToolboxModules({ toolbox, moduleColumns, context, onRename, onDe setDropTarget({ id: "", placement: "before" }); } + function registerModuleElement(moduleId, element) { + if (element) { + moduleElementsRef.current.set(moduleId, element); + } else { + moduleElementsRef.current.delete(moduleId); + } + } + if (moduleColumns === 1) { return (
@@ -195,14 +287,15 @@ export function ToolboxModules({ toolbox, moduleColumns, context, onRename, onDe onRename={onRename} onDelete={onDelete} onMove={onMove} + registerModuleElement={registerModuleElement} /> ))}
); } - const columns = [[], []]; - toolbox.modules.forEach((module, index) => columns[index % 2].push(module)); + const splitIndex = Math.min(Math.max(measuredSplitIndex, 1), Math.max(1, toolbox.modules.length - 1)); + const columns = getSequentialModuleColumns(toolbox.modules, splitIndex); return (
@@ -220,6 +313,7 @@ export function ToolboxModules({ toolbox, moduleColumns, context, onRename, onDe onRename={onRename} onDelete={onDelete} onMove={onMove} + registerModuleElement={registerModuleElement} /> ))}
@@ -228,7 +322,7 @@ export function ToolboxModules({ toolbox, moduleColumns, context, onRename, onDe ); } -function ModuleShell({ toolbox, module, context, draggingModuleId, dropTarget, onDragStart, onRename, onDelete }) { +function ModuleShell({ toolbox, module, context, draggingModuleId, dropTarget, onDragStart, onRename, onDelete, registerModuleElement }) { const definition = MODULE_COMPONENTS[module.type] || MODULE_COMPONENTS.notepad; const Component = definition.Component; const label = definition.label || module.type; @@ -247,6 +341,7 @@ function ModuleShell({ toolbox, module, context, draggingModuleId, dropTarget, o className={className} data-toolbox-id={toolbox.id} data-module-id={module.id} + ref={(element) => registerModuleElement?.(module.id, element)} >
@@ -273,7 +368,7 @@ function ModuleShell({ toolbox, module, context, draggingModuleId, dropTarget, o aria-pressed={editing} title={editing ? "Masquer l'ajout" : "Ajouter"} > - + )} - -
-
- - -
+
+
+ + +
+
+ +
@@ -1039,15 +1045,16 @@ function StorageHelpCard({ help }) { } function ToolboxCard({ toolbox, game, actions }) { - const coverImage = game?.image || toolbox.icon || DEFAULT_TOOLBOX_ICON; + const gameCoverImage = getGameCardCover(game); + const coverImage = gameCoverImage || toolbox.icon || DEFAULT_TOOLBOX_ICON; return (
{game @@ -1141,15 +1148,17 @@ function ToolboxIconPicker({ toolbox, onChange }) { } function ToolboxGameIcon({ game }) { + const gameCoverImage = getGameCardCover(game); return (
- +
); } function ToolboxView({ toolbox, toolboxGame, embedded, actions, updateToolbox, updateModuleData, addScreenshotFiles }) { const moduleColumns = Number(toolbox.moduleColumns) === 1 ? 1 : 2; + const toolboxGameCover = getGameCardCover(toolboxGame); const moduleContext = { getModuleData, normalizeChecklistData, @@ -1185,12 +1194,12 @@ function ToolboxView({ toolbox, toolboxGame, embedded, actions, updateToolbox, u return (
-
+

Toolbox

{!embedded && !toolboxGame && updateToolbox({ ...toolbox, icon })} />} - {!embedded && toolboxGame?.image && } + {!embedded && toolboxGameCover && } {embedded ?

{toolbox.name}

: ( name !== toolbox.name && updateToolbox({ ...toolbox, name })} /> )} @@ -1267,17 +1276,17 @@ function ToolboxDrawer({ gameId, toolboxes, links, actions, updateToolbox, updat } return ( -