diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e37b69d --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +PORT=5173 diff --git a/.forgejo/workflows/deploy-prod.yml b/.forgejo/workflows/deploy-prod.yml new file mode 100644 index 0000000..c7eb5be --- /dev/null +++ b/.forgejo/workflows/deploy-prod.yml @@ -0,0 +1,28 @@ +name: Deploy Sokko G + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: docker + + steps: + - name: Deploy on WebApps + env: + DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + run: | + apt-get update + apt-get install -y openssh-client + + mkdir -p ~/.ssh + printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + + ssh -o StrictHostKeyChecking=no \ + -i ~/.ssh/id_ed25519 \ + -p 2911 \ + shinuwa@192.168.0.100 \ + "sudo /usr/local/bin/deploy-sokko-g" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a00c13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.env +.DS_Store diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md new file mode 100644 index 0000000..4286d06 --- /dev/null +++ b/DESIGN_SYSTEM.md @@ -0,0 +1,921 @@ +# 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**. + +## 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. + +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. + +Eviter le style RGB agressif. L'ambiance recherchee est celle d'une galaxie calme, profonde et lumineuse. + +## 2. Direction Artistique + +Nom du theme : **Nebula Command** + +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. + +```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-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-secondary: #3b82f6; + --color-secondary-hover: #60a5fa; + --color-secondary-active: #2563eb; + --color-secondary-soft: rgba(59, 130, 246, 0.14); + + --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; +} +``` + +## 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; +``` + +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. + +## 5. Layout Application + +Desktop : + +- sidebar gauche : `248px`, reductible a environ `72px` ; +- zone centrale : flexible, `max-width: 1600px` ; +- panneau contextuel droit facultatif : `320px`, repliable. + +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. + +La zone centrale contient le titre, les actions principales, les outils, les resultats et les contenus. + +Le panneau droit peut afficher membres, activite recente, details, filtres, historique ou options de partage. + +Mobile : + +- 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`. + +## 6. Navigation + +Element de navigation : + +```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; +} + +.nav-item:hover { + color: var(--color-text-primary); + background: var(--color-bg-hover); +} + +.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); +} +``` + +L'etat actif doit etre clair sans fond violet trop sature. + +## 7. Topbar + +La barre superieure de la zone centrale peut contenir titre de page, toolbox active, recherche globale, avatars, notifications, partage et menu utilisateur. + +```css +.topbar { + min-height: 68px; + position: sticky; + top: 0; + z-index: 20; + backdrop-filter: blur(18px); + background: rgba(7, 9, 19, 0.78); +} +``` + +## 8. Cartes + +```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. + +Style : + +- contour ; +- epaisseur reguliere ; +- taille 18 a 22 px ; +- coins legerement arrondis. + +Mappings recommandes : + +- 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`. + +## 19. Animations + +Animations rapides et discretes uniquement : + +- 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. + +Eviter animations permanentes, rotations gratuites, zooms importants, flashes et effets neon agressifs. + +```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; + width: 6px; + height: 6px; + border-radius: 0; +} + +.legacy-scrollbar::-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))); + border-radius: 10px; +} +``` + +Dimensions recommandees : + +- 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. + +Regles : + +- 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. + +```css +:focus-visible { + outline: 2px solid var(--color-accent-cyan); + outline-offset: 3px; +} +``` + +## 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. diff --git a/README.md b/README.md index e69de29..8d8b827 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,95 @@ +# Sokko G + +Webapp locale pour préparer et accompagner des sessions gaming sur second écran. + +Sokko G propose des toolboxes modulaires stockées dans le navigateur, ainsi que des pages de guides de jeu maintenues dans le dépôt. Aucune donnée utilisateur n’est envoyée côté serveur. + +## Fonctionnalités + +- Toolboxes locales stockées dans `localStorage`. +- Outils disponibles : bloc notes, checklist avec quantités, screenshots. +- Import/export d’une toolbox. +- Import/export global de toutes les toolboxes. +- Association locale entre une page jeu et une toolbox. +- Panneau latéral redimensionnable sur les pages jeux. +- Suivi et contrôle du quota localStorage. +- Pages de guides de jeu éditables via JSON. + +## Démarrage + +```bash +npm run dev +``` + +Par défaut, le site démarre sur `http://localhost:5173`. + +## Configuration + +Le serveur lit automatiquement un fichier `.env` à la racine du projet. + +```bash +cp .env.example .env +``` + +Exemple : + +```env +PORT=5173 +``` + +Le fichier `.env` est ignoré par git. Pour changer le port localement, modifier `PORT`. + +## Vérifications + +Avant un push ou une mise en ligne : + +```bash +npm run check +``` + +Cette commande vérifie la syntaxe de `website/src/app.js`, celle de `server.mjs`, puis lance les tests. + +## Contenu éditable + +Les contenus maintenus à la main sont regroupés dans `website/public/data`. + +| Fichier | Rôle | +| --- | --- | +| `website/public/data/site.json` | Textes généraux du site : home, navigation, sidebar, page toolboxes. | +| `website/public/data/games.json` | Liste des jeux affichés sur `/games` et contenus des cards jeux. | +| `website/public/data/mhwilds/monsters.json` | Données des monstres Monster Hunter Wilds. | +| `website/public/data/mhwilds/endemic_life.json` | Données de la faune Monster Hunter Wilds. | +| `website/public/data/mhwilds/i18n/fr.json` | Traductions françaises des données MHWilds. | +| `website/public/data/mhwilds/i18n/en.json` | Traductions anglaises des données MHWilds. | +| `website/public/data/toolbox-icons.json` | Manifeste des icônes SVG réutilisables. | + +Les images publiques sont dans `website/public/static`. + +## Structure + +```text +. +├── DESIGN_SYSTEM.md +├── package.json +├── server.mjs +├── tests/ +└── website/ + ├── public/ + │ ├── data/ + │ ├── static/ + │ └── index.html + └── src/ + ├── app.js + └── styles.css +``` + +## Stockage local + +Clés principales : + +- `sokkog:toolboxes` : registre local des toolboxes. +- `sokkog:game-toolbox-links` : associations locales jeu -> toolbox. +- `sokkog:toolbox:{toolboxId}:module:{moduleId}` : données de chaque outil. +- `sokkog:drawer-width` : largeur du panneau latéral. + +Le quota applicatif est contrôlé côté navigateur avant écriture. Les imports globaux simulent l’espace nécessaire avant de modifier les données locales. diff --git a/package.json b/package.json new file mode 100644 index 0000000..04b5cb6 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "sokko-g", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start": "node server.mjs", + "dev": "node server.mjs", + "check": "node --check website/src/app.js && node --check server.mjs && npm test", + "test": "node --test" + } +} diff --git a/server.mjs b/server.mjs new file mode 100644 index 0000000..35bbafb --- /dev/null +++ b/server.mjs @@ -0,0 +1,96 @@ +import { createReadStream, existsSync, readFileSync, statSync } from "node:fs"; +import { createServer } from "node:http"; +import { extname, join, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = resolve(fileURLToPath(new URL(".", import.meta.url)), "website"); +const publicRoot = join(root, "public"); + +function loadEnvFile(file = ".env") { + if (!existsSync(file)) return; + + const lines = readFileSync(file, "utf8").split(/\r?\n/); + lines.forEach((line) => { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) return; + + const separatorIndex = trimmed.indexOf("="); + if (separatorIndex < 1) return; + + const key = trimmed.slice(0, separatorIndex).trim(); + const value = trimmed.slice(separatorIndex + 1).trim().replace(/^["']|["']$/g, ""); + if (!process.env[key]) process.env[key] = value; + }); +} + +loadEnvFile(); + +const port = Number(process.env.PORT || 5173); + +const types = { + ".html": "text/html; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".webp": "image/webp" +}; + +function isInsideRoot(parent, file) { + const relativePath = relative(parent, file); + return Boolean(relativePath) && !relativePath.startsWith("..") && !relativePath.startsWith("/"); +} + +function fileIfReadable(file) { + return isInsideRoot(root, file) && existsSync(file) && statSync(file).isFile() ? file : null; +} + +function resolvePath(url) { + let requested; + try { + requested = decodeURIComponent(new URL(url, "http://localhost").pathname); + } catch { + return null; + } + + const candidate = fileIfReadable(resolve(root, `.${requested}`)); + if (candidate) return candidate; + + const publicCandidate = fileIfReadable(resolve(publicRoot, `.${requested}`)); + if (publicCandidate && isInsideRoot(publicRoot, publicCandidate)) return publicCandidate; + + return join(publicRoot, "index.html"); +} + +const server = createServer((req, res) => { + const file = resolvePath(req.url || "/"); + if (!file || !existsSync(file)) { + res.writeHead(404); + res.end("Not found"); + return; + } + + res.writeHead(200, { + "Content-Type": types[extname(file)] || "application/octet-stream", + "Cache-Control": "no-store" + }); + createReadStream(file).pipe(res); +}); + +server.on("error", (error) => { + if (error.code === "EADDRINUSE") { + console.error(`Port ${port} is already in use. Set PORT to another value.`); + process.exit(1); + } + + console.error(error); + process.exit(1); +}); + +server.listen(port, () => { + console.log(`Sokko G dev server running at http://localhost:${port}`); +}); diff --git a/tests/static-smoke.test.mjs b/tests/static-smoke.test.mjs new file mode 100644 index 0000000..b59ae26 --- /dev/null +++ b/tests/static-smoke.test.mjs @@ -0,0 +1,108 @@ +import { readFile } from "node:fs/promises"; +import { test } from "node:test"; +import assert from "node:assert/strict"; + +test("static entrypoint loads the application assets", async () => { + const html = await readFile("website/public/index.html", "utf8"); + + assert.match(html, /
<\/div>/); + assert.match(html, /\/favicon\.ico/); + assert.match(html, /\/src\/app\.js/); + assert.match(html, /\/src\/styles\.css/); +}); + +test("application defines the expected local toolbox primitives", async () => { + const source = await readFile("website/src/app.js", "utf8"); + + assert.match(source, /sokkog:toolboxes/); + assert.match(source, /sokkog:game-toolbox-links/); + assert.match(source, /DEFAULT_SITE_CONTENT/); + assert.match(source, /function loadSiteContent/); + assert.match(source, /\/data\/site\.json/); + assert.match(source, /APP_STORAGE_LIMIT_BYTES/); + assert.match(source, /function renderStorageQuota/); + assert.match(source, /function writeStorageValue/); + assert.match(source, /role="progressbar"/); + assert.match(source, /export-all-toolboxes/); + assert.match(source, /import-all-toolboxes/); + assert.match(source, /function createGlobalExportPayload/); + assert.match(source, /function importAllToolboxes/); + assert.match(source, /open-link-toolbox-modal/); + assert.match(source, /function showToolboxLinkModal/); + assert.match(source, /data-game-id/); + assert.match(source, /notepad/); + assert.match(source, /checklist/); + assert.match(source, /screenshots/); + assert.doesNotMatch(source, /description: _description/); + assert.match(source, /function showToolboxCreateModal/); + assert.match(source, /data-action="toolbox-create-form"/); + assert.match(source, /function getToolboxGameId/); + assert.match(source, /dragon\.png/); + assert.match(source, /const game = getToolboxGame\(toolbox\)/); + assert.match(source, /moduleColumns/); + assert.match(source, /data-action="set-module-layout"/); + assert.match(source, /data-action="edit-toolbox-title"/); + assert.match(source, /function renderToolboxModules/); + assert.match(source, /function moveToolboxModule/); + assert.match(source, /dragstart/); + assert.match(source, /qtyTarget/); + assert.match(source, /qtyCurrent/); + assert.match(source, /adjust-check-qty/); + assert.match(source, /paste-screenshot/); + assert.match(source, /clipboardData/); + assert.match(source, /function addScreenshotFiles/); + assert.match(source, /view-screenshot/); + assert.match(source, /function showScreenshotViewer/); + assert.match(source, /drop-screenshot/); + assert.match(source, /dataTransfer\.files/); +}); + +test("mhwilds data and assets are available", async () => { + const site = JSON.parse(await readFile("website/public/data/site.json", "utf8")); + const games = JSON.parse(await readFile("website/public/data/games.json", "utf8")); + const monsters = JSON.parse(await readFile("website/public/data/mhwilds/monsters.json", "utf8")); + const endemicLife = JSON.parse(await readFile("website/public/data/mhwilds/endemic_life.json", "utf8")); + const translations = JSON.parse(await readFile("website/public/data/mhwilds/i18n/fr.json", "utf8")); + const appSource = await readFile("website/src/app.js", "utf8"); + + assert.equal(site.brand.name, "Sokko G"); + assert.equal(site.home.hero.title, "Sokko G"); + 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.ok(monsters.monsters.length > 0); + assert.ok(endemicLife.endemicLife.length > 0); + assert.equal(translations.monsters, "monstres"); + assert.match(appSource, /renderMhwildsListing/); + assert.match(appSource, /flip-monster-card/); + await readFile("website/public/static/img/mhwilds/chatacabra.png"); +}); + +test("server supports local env configuration", async () => { + const server = await readFile("server.mjs", "utf8"); + const envExample = await readFile(".env.example", "utf8"); + + assert.match(server, /function loadEnvFile/); + assert.match(server, /process\.env\.PORT/); + assert.match(envExample, /PORT=5173/); +}); + +test("legacy svg icons are available for reuse", async () => { + const manifest = JSON.parse(await readFile("website/public/data/toolbox-icons.json", "utf8")); + + assert.ok(manifest.icons.length >= 4); + await readFile("website/public/static/icons/home.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"); + await readFile("website/public/static/icons/export.svg", "utf8"); + await readFile("website/public/static/icons/notepad.svg", "utf8"); + await readFile("website/public/static/icons/picture.svg", "utf8"); + await readFile("website/public/static/icons/rows.svg", "utf8"); + await readFile("website/public/static/icons/rubber.svg", "utf8"); + await readFile("website/public/static/icons/trashcan.svg", "utf8"); + await readFile("website/public/favicon.ico"); + await readFile("website/public/static/img/dragon.png"); + await readFile("website/public/static/img/toolbox-icons/controller.svg", "utf8"); +}); diff --git a/website/public/data/games.json b/website/public/data/games.json new file mode 100644 index 0000000..1de6d7e --- /dev/null +++ b/website/public/data/games.json @@ -0,0 +1,38 @@ +{ + "games": [ + { + "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", + "summary": "Repères rapides pour préparer une chasse, suivre les priorités et garder les infos utiles sur un second écran.", + "sections": [ + { + "title": "Préparation", + "items": [ + "Choisir un repas adapté", + "Vérifier les pièges et consommables", + "Préparer un set anti-statuts" + ] + }, + { + "title": "Pendant la chasse", + "items": [ + "Marquer les parties cassables", + "Noter les timings de rage", + "Garder un oeil sur les objectifs secondaires" + ] + }, + { + "title": "Après mission", + "items": [ + "Capturer les drops rares", + "Mettre à jour les crafts suivis", + "Archiver les screenshots importants" + ] + } + ] + } + ] +} diff --git a/website/public/data/mhwilds/endemic_life.json b/website/public/data/mhwilds/endemic_life.json new file mode 100644 index 0000000..1040c74 --- /dev/null +++ b/website/public/data/mhwilds/endemic_life.json @@ -0,0 +1,1129 @@ +{ + "endemicLife": [ + { + "name": "vigorwasp", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "giant vigorwasp", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "purewasp", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "vigormantle bug", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "chillmantle bug", + "locations": [ + { + "condition": "default", + "values": [ + "iceshard cliffs" + ] + } + ], + "description": "to be completed" + }, + { + "name": "heatmantle bug", + "locations": [ + { + "condition": "default", + "values": [ + "iceshard cliffs" + ] + } + ], + "description": "to be completed" + }, + { + "name": "wiggly litchi", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "paratoad", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "nitrotoad", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "sleeptoad", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "flashflies", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "dung beetle", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "bomb beetle", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "rime beetle", + "locations": [ + { + "condition": "default", + "values": [ + "iceshard cliffs" + ] + } + ], + "description": "to be completed" + }, + { + "name": "thunderbug", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "great thunderbugs", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "flashbug", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "godbug", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "bitterbug", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "dark hornet", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "wedge beetle", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "iceshard cliffs", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "windrustler", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains" + ] + } + ], + "description": "to be completed" + }, + { + "name": "black windrustler", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains" + ] + } + ], + "description": "to be completed" + }, + { + "name": "nothpecker", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains" + ] + } + ], + "description": "to be completed" + }, + { + "name": "landlight shrimp", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains" + ] + } + ], + "description": "to be completed" + }, + { + "name": "amstrigian", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "hymstrigian", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "rufflizard", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains" + ] + } + ], + "description": "to be completed" + }, + { + "name": "tracktail lizard", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains" + ] + } + ], + "description": "to be completed" + }, + { + "name": "pallbug", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "quatreflies", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "emperor hopper", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "tyrant hopper", + "locations": [ + { + "condition": "default", + "values": [ + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "sandstar", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains" + ] + } + ], + "description": "to be completed" + }, + { + "name": "leaflugger ant", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "florashell crab", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "crudeshell crab", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "curioshell crab", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "smokeshroud gekko", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "dapperwing", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "dazzlewing", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "mimiphyta", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "scarlet joyperch", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "starduster", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "vaporonid", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "omenfly", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "myriads", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "jewel scarab", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "regal jewel scarab", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "pink landlight shrimp", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "ember rufflizard", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "azure rufflizard", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "gloom gekko", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "scalebat", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "blink angler", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "sparklerjelly", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "peony sparklerjelly", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "nu yaya", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "gawkie", + "locations": [ + { + "condition": "default", + "values": [ + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "solanaria", + "locations": [ + { + "condition": "default", + "values": [ + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "pillopod", + "locations": [ + { + "condition": "default", + "values": [ + "oilwell basin", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "gillopod", + "locations": [ + { + "condition": "default", + "values": [ + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "petalfly", + "locations": [ + { + "condition": "default", + "values": [ + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "xu yo", + "locations": [ + { + "condition": "default", + "values": [ + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "downy crake", + "locations": [ + { + "condition": "default", + "values": [ + "iceshard cliffs" + ] + } + ], + "description": "to be completed" + }, + { + "name": "hercudrome", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "gold hercudrome", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "prism hercudrome", + "locations": [ + { + "condition": "default", + "values": [ + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + } + ], + "aquaticLife": [ + { + "name": "virid bowfin", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "sushifish", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "whetfish", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "goldenfish", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "iceshard cliffs" + ] + } + ], + "description": "to be completed" + }, + { + "name": "platinumfish", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "iceshard cliffs" + ] + } + ], + "description": "to be completed" + }, + { + "name": "burst arowana", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest", + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "bomb arowana", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest", + "oilwell basin" + ] + } + ], + "description": "to be completed" + }, + { + "name": "goldenfry", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest", + "oilwell basin", + "iceshard cliffs", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "gunpowderfish", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest", + "oilwell basin", + "iceshard cliffs", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "gravid bowfin", + "locations": [ + { + "condition": "default", + "values": [ + "windward plains", + "scarlet forest", + "oilwell basin", + "iceshard cliffs", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "escunite", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "grand escunite", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "glass parexus", + "locations": [ + { + "condition": "default", + "values": [ + "iceshard cliffs", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "blind perch", + "locations": [ + { + "condition": "default", + "values": [ + "iceshard cliffs", + "ruins of wyveria" + ] + } + ], + "description": "to be completed" + }, + { + "name": "gastronome tuna", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "goliath squid", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "great trevally", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "gajau", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + }, + { + "name": "speartuna", + "locations": [ + { + "condition": "default", + "values": [ + "scarlet forest" + ] + } + ], + "description": "to be completed" + } + ] +} diff --git a/website/public/data/mhwilds/i18n/en.json b/website/public/data/mhwilds/i18n/en.json new file mode 100644 index 0000000..9790e7e --- /dev/null +++ b/website/public/data/mhwilds/i18n/en.json @@ -0,0 +1,217 @@ +{ + "and": "and", + "or": "or", + "reset": "reset", + "create checklist": "create checklist", + "to be completed": "to be completed", + "monsters": "monsters", + "endemic life": "endemic life", + "name": "name", + "weaknesses": "weaknesses", + "locations": "locations", + "ailments": "ailments", + "cut": "cut", + "blunt": "blunt", + "ammo": "ammo", + "windward plains": "windward plains", + "scarlet forest": "scarlet forest", + "ruins of wyveria": "ruins of wyveria", + "oilwell basin": "oilwell basin", + "iceshard cliffs": "iceshard cliffs", + "dragon": "dragon", + "fire": "fire", + "ice": "ice", + "thunder": "thunder", + "water": "water", + "dragonblight": "dragonblight", + "fireblight": "fireblight", + "iceblight": "iceblight", + "thunderblight": "thunderblight", + "waterblight": "waterblight", + "frostblight": "frostblight", + "bubbleblight": "bubbleblight", + "stench": "stench", + "sleep": "sleep", + "paralysis": "paralysis", + "blast": "blast", + "poison": "poison", + "webbed": "webbed", + "bleeding": "bleeding", + "stun": "stun", + "frenzy virus": "frenzy virus", + "defense down": "defense down", + "minor wind pressure": "minor wind pressure", + "dragon wind pressure": "dragon wind pressure", + "minor tremor": "minor tremor", + "weak roar": "weak roar", + "strong roar": "strong roar", + "mantle broken": "mantle broken", + "head": "head", + "nose": "nose", + "antenna": "antenna", + "neck": "neck", + "mouth": "mouth", + "torso": "torso", + "membrane": "membrane", + "tongue": "tongue", + "chest": "chest", + "belly": "belly", + "tentacle": "tentacle", + "back": "back", + "left wingarm": "left wingarm", + "right wingarm": "right wingarm", + "front left arm": "front left arm", + "front right arm": "front right arm", + "middle left arm": "middle left arm", + "middle right arm": "middle right arm", + "rear left arm": "rear left arm", + "rear right arm": "rear right arm", + "left wing": "left wing", + "right wing": "right wing", + "left leg": "left leg", + "right leg": "right leg", + "forelegs": "forelegs", + "left foreleg": "left foreleg", + "right foreleg": "right foreleg", + "hindlegs": "hindlegs", + "left hindleg": "left hindleg", + "right hindleg": "right hindleg", + "left chainblade": "left chainblade", + "right chainblade": "right chainblade", + "left claw": "left claw", + "right claw": "right claw", + "abdominal iceplate": "abdominal iceplate", + "large iceplate(hidden)": "large iceplate(hidden)", + "large iceplate(exposed)": "large iceplate(exposed)", + "head (crystallized)": "head (crystallized)", + "left wingarm (crystallized)": "left wingarm (crystallized)", + "right wingarm (crystallized)": "right wingarm (crystallized)", + "back fin": "back fin", + "stinger": "stinger", + "mantle": "mantle", + "petals": "petals", + "tail": "tail", + "tail hair": "tail hair", + "rear": "rear", + "tail tip": "tail tip", + "flying wyvern": "flying wyvern", + "fanged beast": "fanged beast", + "construct": "construct", + "chatacabra": "chatacabra", + "quematrice": "quematrice", + "lala barina": "lala barina", + "congalala": "congalala", + "balahara": "balahara", + "doshaguma": "doshaguma", + "uth duna": "uth duna", + "rompopolo": "rompopolo", + "rey dau": "rey dau", + "nerscylla": "nerscylla", + "hirabami": "hirabami", + "ajarakan": "ajarakan", + "nu udra": "nu udra", + "jin dahaad": "jin dahaad", + "xu wu": "xu wu", + "zoh shia": "zoh shia", + "yian kut-ku": "yian kut-ku", + "gypceros": "gypceros", + "rathian": "rathian", + "rathalos": "rathalos", + "gravios": "gravios", + "blangonga": "blangonga", + "gore magala": "gore magala", + "arkveld": "arkveld", + "mizutsune": "mizutsune", + "guardian doshaguma": "guardian doshaguma", + "guardian rathalos": "guardian rathalos", + "guardian ebony odogaron": "guardian ebony odogaron", + "guardian arkveld": "guardian arkveld", + "guardian fulgur anjanath": "guardian fulgur anjanath", + "vigorwasp": "vigorwasp", + "giant vigorwasp": "giant vigorwasp", + "purewasp": "purewasp", + "vigormantle bug": "vigormantle bug", + "chillmantle bug": "chillmantle bug", + "heatmantle bug": "heatmantle bug", + "wiggly litchi": "wiggly litchi", + "paratoad": "paratoad", + "nitrotoad": "nitrotoad", + "sleeptoad": "sleeptoad", + "flashflies": "flashflies", + "dung beetle": "dung beetle", + "bomb beetle": "bomb beetle", + "rime beetle": "rime beetle", + "thunderbug": "thunderbug", + "great thunderbugs": "great thunderbugs", + "flashbug": "flashbug", + "godbug": "godbug", + "bitterbug": "bitterbug", + "dark hornet": "dark hornet", + "wedge beetle": "wedge beetle", + "windrustler": "windrustler", + "black windrustler": "black windrustler", + "nothpecker": "nothpecker", + "landlight shrimp": "landlight shrimp", + "amstrigian": "amstrigian", + "hymstrigian": "hymstrigian", + "rufflizard": "rufflizard", + "tracktail lizard": "tracktail lizard", + "pallbug": "pallbug", + "quatreflies": "quatreflies", + "emperor hopper": "emperor hopper", + "tyrant hopper": "tyrant hopper", + "sandstar": "sandstar", + "leaflugger ant": "leaflugger ant", + "florashell crab": "florashell crab", + "crudeshell crab": "crudeshell crab", + "curioshell crab": "curioshell crab", + "smokeshroud gekko": "smokeshroud gekko", + "dapperwing": "dapperwing", + "dazzlewing": "dazzlewing", + "mimiphyta": "mimiphyta", + "scarlet joyperch": "scarlet joyperch", + "starduster": "starduster", + "vaporonid": "vaporonid", + "omenfly": "omenfly", + "myriads": "myriads", + "jewel scarab": "jewel scarab", + "regal jewel scarab": "regal jewel scarab", + "pink landlight shrimp": "pink landlight shrimp", + "ember rufflizard": "ember rufflizard", + "azure rufflizard": "azure rufflizard", + "gloom gekko": "gloom gekko", + "scalebat": "scalebat", + "blink angler": "blink angler", + "sparklerjelly": "sparklerjelly", + "peony sparklerjelly": "peony sparklerjelly", + "nu yaya": "nu yaya", + "gawkie": "gawkie", + "solanaria": "solanaria", + "pillopod": "pillopod", + "gillopod": "gillopod", + "petalfly": "petalfly", + "xu yo": "xu yo", + "downy crake": "downy crake", + "hercudrome": "hercudrome", + "gold hercudrome": "gold hercudrome", + "prism hercudrome": "prism hercudrome", + "virid bowfin": "virid bowfin", + "sushifish": "sushifish", + "whetfish": "whetfish", + "goldenfish": "goldenfish", + "platinumfish": "platinumfish", + "burst arowana": "burst arowana", + "bomb arowana": "bomb arowana", + "goldenfry": "goldenfry", + "gunpowderfish": "gunpowderfish", + "gravid bowfin": "gravid bowfin", + "escunite": "escunite", + "grand escunite": "grand escunite", + "glass parexus": "glass parexus", + "blind perch": "blind perch", + "gastronome tuna": "gastronome tuna", + "goliath squid": "goliath squid", + "great trevally": "great trevally", + "gajau": "gajau", + "speartuna": "speartuna" +} diff --git a/website/public/data/mhwilds/i18n/fr.json b/website/public/data/mhwilds/i18n/fr.json new file mode 100644 index 0000000..3426394 --- /dev/null +++ b/website/public/data/mhwilds/i18n/fr.json @@ -0,0 +1,217 @@ +{ + "and": "et", + "or": "ou", + "reset": "réinitialiser", + "create checklist": "créer checklist", + "to be completed": "à compléter", + "monsters": "monstres", + "endemic life": "faune endémique", + "name": "nom", + "weaknesses": "faiblesses", + "locations": "localisations", + "ailments": "afflictions", + "cut": "tranchant", + "blunt": "contondant", + "ammo": "projectiles", + "windward plains": "plaines venteuses", + "scarlet forest": "forêt écarlate", + "ruins of wyveria": "ruines de wyveria", + "oilwell basin": "bassin pétrolier", + "iceshard cliffs": "falaises de glaces", + "dragon": "dragon", + "fire": "feu", + "ice": "glace", + "thunder": "foudre", + "water": "eau", + "dragonblight": "fléau-dragon", + "fireblight": "fléau-feu", + "iceblight": "fléau-glace", + "thunderblight": "fléau-foudre", + "waterblight": "fléau-eau", + "frostblight": "fléau-givre", + "bubbleblight": "fléau-bulles", + "stench": "puanteur", + "sleep": "sommeil", + "paralysis": "paralysie", + "blast": "explosion", + "poison": "poison", + "webbed": "toile", + "bleeding": "hémorragie", + "stun": "flash", + "frenzy virus": "furie", + "defense down": "baisse de défense", + "minor wind pressure": "bourrasque mineure", + "dragon wind pressure": "bourrasque draconique", + "minor tremor": "secousse mineure", + "weak roar": "rugissement faible", + "strong roar": "rugissement fort", + "mantle broken": "manteau brisé", + "head": "tête", + "nose": "nez", + "antenna": "antenne", + "neck": "cou", + "mouth": "bouche", + "torso": "torse", + "membrane": "membrane", + "tongue": "langue", + "chest": "poitrine", + "belly": "ventre", + "tentacle": "tentacule", + "back": "dos", + "left wingarm": "patte ailée G", + "right wingarm": "patte ailée D", + "front left arm": "patte av. G", + "front right arm": "patte av. D", + "middle left arm": "patte mid. G", + "middle right arm": "patte mid. D", + "rear left arm": "patte ar. G", + "rear right arm": "patte ar. D", + "left wing": "aile gauche", + "right wing": "aile droite", + "left leg": "patte gauche", + "right leg": "patte droite", + "forelegs": "pattes av.", + "left foreleg": "patte av. G", + "right foreleg": "patte av. D", + "hindlegs": "pattes ar.", + "left hindleg": "patte ar. G", + "right hindleg": "patte ar. D", + "left chainblade": "chaines G", + "right chainblade": "chaines D", + "left claw": "griffes G", + "right claw": "griffes D", + "abdominal iceplate": "plaque gelée(flancs)", + "large iceplate(hidden)": "gde plaque gelée (cachée)", + "large iceplate(exposed)": "gde plaque gelée (visible)", + "head (crystallized)": "tête (cristallisée)", + "left wingarm (crystallized)": "patte ailée G (cristallisée)", + "right wingarm (crystallized)": "patte ailée D (cristallisée)", + "back fin": "nageoire dorsale", + "stinger": "dard", + "mantle": "manteau", + "petals": "petales", + "tail": "queue", + "tail hair": "poils de queue", + "rear": "arrière", + "tail tip": "Bout de queue", + "flying wyvern": "wyverne volante", + "fanged beast": "bête à crocs", + "construct": "artificiel", + "chatacabra": "chatacabra", + "quematrice": "quematrice", + "lala barina": "lala barina", + "congalala": "congalala", + "balahara": "balahara", + "doshaguma": "doshaguma", + "uth duna": "uth duna", + "rompopolo": "rompopolo", + "rey dau": "rey dau", + "nerscylla": "nerscylla", + "hirabami": "hirabami", + "ajarakan": "ajarakan", + "nu udra": "nu udra", + "jin dahaad": "jin dahaad", + "xu wu": "xu wu", + "zoh shia": "zoh shia", + "yian kut-ku": "yian kut-ku", + "gypceros": "gypceros", + "rathian": "rathian", + "rathalos": "rathalos", + "gravios": "gravios", + "blangonga": "blangonga", + "gore magala": "gore magala", + "arkveld": "arkveld", + "mizutsune": "mizutsune", + "guardian doshaguma": "doshaguma gardien", + "guardian rathalos": "rathalos gardien", + "guardian ebony odogaron": "odogaron désastre gardien", + "guardian arkveld": "arkveld gardien", + "guardian fulgur anjanath": "anjanath fulgurant gardien", + "vigorwasp": "vitaguêpe", + "giant vigorwasp": "grande vitaguêpe", + "purewasp": "curaguêpe", + "vigormantle bug": "vitanille", + "chillmantle bug": "glacianille", + "heatmantle bug": "calonille", + "wiggly litchi": "chenille-litchi", + "paratoad": "paralycrapaud", + "nitrotoad": "nitrocrapaud", + "sleeptoad": "somnicrapaud", + "flashflies": "flashinsectes", + "dung beetle": "", + "bomb beetle": "scarabombe", + "rime beetle": "scarabée des neiges", + "thunderbug": "foudrinsectes", + "great thunderbugs": "grands foudrinsectes", + "flashbug": "scaralux", + "godbug": "insecte immortel", + "bitterbug": "amerinsecte", + "dark hornet": "frelon clair-obscur", + "wedge beetle": "scaragrappin", + "windrustler": "filovent", + "black windrustler": "filovent noir", + "nothpecker": "", + "landlight shrimp": "crevette terralux", + "amstrigian": "amstrigien", + "hymstrigian": "", + "rufflizard": "gonflézard", + "tracktail lizard": "lézard markoda", + "pallbug": "charoptère", + "quatreflies": "trèflémères", + "emperor hopper": "criquet empereur", + "tyrant hopper": "criquet tyran", + "sandstar": "étoile des sables", + "leaflugger ant": "fourmifolia", + "florashell crab": "floracrabe", + "crudeshell crab": "pétrocrabe", + "curioshell crab": "curiocrabe", + "smokeshroud gekko": "", + "dapperwing": "bellaile", + "dazzlewing": "", + "mimiphyta": "mimiflore", + "scarlet joyperch": "perche rampante", + "starduster": "stellardine", + "vaporonid": "", + "omenfly": "libellule divinatoire", + "myriads": "myriades", + "jewel scarab": "scarajoyau", + "regal jewel scarab": "scarajoyau royal", + "pink landlight shrimp": "crevette terralux rose", + "ember rufflizard": "gonflézard incandescent", + "azure rufflizard": "", + "gloom gekko": "gecko nocturne", + "scalebat": "chauve-souris à écailles", + "blink angler": "poisson cillant", + "sparklerjelly": "flaméduse", + "peony sparklerjelly": "", + "nu yaya": "nu yaya", + "gawkie": "zieuteur", + "solanaria": "solanaria", + "pillopod": "plalimace", + "gillopod": "glalimace", + "petalfly": "pétaluciole", + "xu yo": "", + "downy crake": "", + "hercudrome": "", + "gold hercudrome": "", + "prism hercudrome": "", + "virid bowfin": "amie émeraude", + "sushifish": "sushipoisson", + "whetfish": "poissaiguiseur", + "goldenfish": "poisson d'or", + "platinumfish": "poisson platine", + "burst arowana": "arowana explosif", + "bomb arowana": "arowana bombe", + "goldenfry": "poisson rouge", + "gunpowderfish": "poisson-poudre", + "gravid bowfin": "amie gravide", + "escunite": "escunite", + "grand escunite": "grande escunite", + "glass parexus": "parexus de verre", + "blind perch": "perche aveugle", + "gastronome tuna": "thon glouton", + "goliath squid": "calmar titan", + "great trevally": "", + "gajau": "gajau", + "speartuna": "" +} diff --git a/website/public/data/mhwilds/monsters.json b/website/public/data/mhwilds/monsters.json new file mode 100644 index 0000000..e552fd0 --- /dev/null +++ b/website/public/data/mhwilds/monsters.json @@ -0,0 +1,4437 @@ +{ + "monsters": [ + { + "name": "chatacabra", + "type": "amphibian", + "ailments": [ + { + "condition": "default", + "values": [ + "none" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "ice", + "thunder" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "tongue" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 0 + }, + { + "name": "tongue", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 0 + }, + { + "name": "torso", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 0 + }, + { + "name": "left foreleg", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "right foreleg", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "left hindleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 0, + "thunder": 2, + "ice": 1, + "dragon": 0 + }, + { + "name": "right hindleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 0, + "thunder": 2, + "ice": 1, + "dragon": 0 + }, + { + "name": "rear", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 0 + } + ] + }, + { + "name": "quematrice", + "type": "brute wyvern", + "ailments": [ + { + "condition": "default", + "values": [ + "fireblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "water" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "neck" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "lala barina", + "type": "temnoceran", + "ailments": [ + { + "condition": "default", + "values": [ + "paralysis" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "tail" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 4, + "ammo": 3, + "fire": 2, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "torso", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "left claw", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "right claw", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "left hindleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "right hindleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "petals", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 2, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "tail", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 2, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + } + ] + }, + { + "name": "congalala", + "type": "fanged beast", + "ailments": [ + { + "condition": "default", + "values": [ + "minor tremor", + "poison", + "paralysis", + "blast", + "stench" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire", + "ice" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "back" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 2, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "balahara", + "type": "leviathan", + "ailments": [ + { + "condition": "default", + "values": [ + "waterblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "thunder" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 2, + "ice": 1, + "dragon": 1 + }, + { + "name": "neck", + "cut": 3, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 2, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "doshaguma", + "type": "fanged beast", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "minor tremor" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire", + "thunder" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "belly" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 2, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail hair", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 0 + } + ] + }, + { + "name": "uth duna", + "type": "leviathan", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "waterblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "thunder" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 2, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "rompopolo", + "type": "brute wyvern", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "poison" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "water" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "tongue" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "back", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "rey dau", + "type": "flying wyvern", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "thunderblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "water", + "ice" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "head" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 2, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "left wing", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "right wing", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "left leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "right leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "nerscylla", + "type": "temnoceran", + "ailments": [ + { + "condition": "default", + "values": [ + "poison", + "sleep", + "webbed" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire" + ] + }, + { + "condition": "mantle broken", + "values": [ + "thunder" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "claw" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 0 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 3, + "fire": 2, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 0 + }, + { + "name": "left foreleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 0 + }, + { + "name": "right foreleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 0 + }, + { + "name": "left claw", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "right claw", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "belly", + "cut": 4, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "stinger", + "cut": 2, + "blunt": 2, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 0 + }, + { + "name": "left hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "right hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "mantle", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 2, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 0 + } + ] + }, + { + "name": "hirabami", + "type": "leviathan", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "frostblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire", + "thunder" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 4, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 2, + "ice": 0, + "dragon": 1 + }, + { + "name": "neck", + "cut": 3, + "blunt": 4, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 2, + "ice": 0, + "dragon": 1 + }, + { + "name": "torso", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + } + ] + }, + { + "name": "ajarakan", + "type": "fanged beast", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "minor tremor", + "fireblight", + "blast" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "water", + "ice" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "leg" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "belly", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "back", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail tip", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "nu udra", + "type": "cephalopod", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "fireblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "water" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "membrane", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "mouth", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 3, + "blunt": 4, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tentacle", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "front left arm", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "front right arm", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "middle left arm", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "middle right arm", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "rear left arm", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "rear right arm", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "guardian doshaguma", + "type": "construct", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "minor tremor", + "defense down" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire", + "ice", + "thunder", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "foreleg" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 4, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 2, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 2 + }, + { + "name": "right foreleg", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 2, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 2 + }, + { + "name": "left hindleg", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 1 + }, + { + "name": "tail hair", + "cut": 2, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 1 + } + ] + }, + { + "name": "guardian rathalos", + "type": "construct", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "minor wind pressure", + "fireblight", + "poison" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "thunder", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "leg", + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "neck", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "left wing", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "right wing", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "left leg", + "cut": 4, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 2 + }, + { + "name": "right leg", + "cut": 4, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 2 + }, + { + "name": "tail", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 0, + "thunder": 1, + "ice": 0, + "dragon": 1 + } + ] + }, + { + "name": "jin dahaad", + "type": "leviathan", + "ailments": [ + { + "condition": "default", + "values": [ + "strong roar", + "iceblight", + "frostblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth", + "torso" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 2, + "ice": 0, + "dragon": 1 + }, + { + "name": "neck", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 2, + "ice": 0, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "abdominal iceplate", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "large iceplate(hidden)", + "cut": 1, + "blunt": 1, + "ammo": 1, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "large iceplate(exposed)", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 2, + "ice": 0, + "dragon": 1 + } + ] + }, + { + "name": "guardian ebony odogaron", + "type": "construct", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "dragonblight", + "bleeding" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "water" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "foreleg", + "tail" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 2, + "water": 3, + "thunder": 2, + "ice": 2, + "dragon": 1 + }, + { + "name": "neck", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 2, + "thunder": 2, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 2, + "thunder": 2, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 2, + "water": 2, + "thunder": 2, + "ice": 2, + "dragon": 2 + }, + { + "name": "right foreleg", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 2, + "water": 2, + "thunder": 2, + "ice": 2, + "dragon": 2 + }, + { + "name": "back", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 2, + "thunder": 2, + "ice": 2, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 2, + "thunder": 2, + "ice": 2, + "dragon": 1 + }, + { + "name": "tail", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 2, + "thunder": 2, + "ice": 2, + "dragon": 2 + }, + { + "name": "tail tip", + "cut": 3, + "blunt": 2, + "ammo": 3, + "fire": 1, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 2 + } + ] + }, + { + "name": "xu wu", + "type": "cephalopod", + "ailments": [ + { + "condition": "default", + "values": [ + "none" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "ice" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + }, + { + "name": "membrane", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + }, + { + "name": "mouth", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 3, + "dragon": 0 + }, + { + "name": "torso", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + }, + { + "name": "tentacle", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + }, + { + "name": "front left arm", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + }, + { + "name": "front right arm", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + }, + { + "name": "middle left arm", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + }, + { + "name": "middle right arm", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + }, + { + "name": "rear left arm", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + }, + { + "name": "rear right arm", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + } + ] + }, + { + "name": "guardian arkveld", + "type": "construct", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "minor wind pressure", + "dragonblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "chainblade" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left wing", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right wing", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left chainblade", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 0 + }, + { + "name": "right chainblade", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 0 + }, + { + "name": "left leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "zoh shia", + "type": "construct", + "ailments": [ + { + "condition": "default", + "values": [ + "strong roar", + "dragon wind pressure", + "fireblight", + "thunderblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "head (crystallized)", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "neck", + "cut": 2, + "blunt": 2, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left wingarm", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "right wingarm", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "left wingarm (crystallized)", + "cut": 3, + "blunt": 3, + "ammo": 1, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right wingarm (crystallized)", + "cut": 3, + "blunt": 3, + "ammo": 1, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 2, + "blunt": 2, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "yian kut-ku", + "type": "flying wyvern", + "ailments": [ + { + "condition": "default", + "values": [ + "minor wind pressure", + "fireblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "water", + "ice", + "thunder" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 2, + "ice": 2, + "dragon": 0 + }, + { + "name": "neck", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 2, + "thunder": 2, + "ice": 1, + "dragon": 2 + }, + { + "name": "torso", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "left wing", + "cut": 4, + "blunt": 3, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 0 + }, + { + "name": "right wing", + "cut": 4, + "blunt": 3, + "ammo": 4, + "fire": 1, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 0 + }, + { + "name": "left leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "right leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 0 + }, + { + "name": "tail", + "cut": 4, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 2, + "dragon": 0 + } + ] + }, + { + "name": "gypceros", + "type": "flying wyvern", + "ailments": [ + { + "condition": "default", + "values": [ + "minor wind pressure", + "poison", + "stun" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire", + "ice" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "head" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 0, + "ice": 2, + "dragon": 1 + }, + { + "name": "neck", + "cut": 3, + "blunt": 2, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "left wing", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 1, + "thunder": 0, + "ice": 2, + "dragon": 1 + }, + { + "name": "right wing", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 1, + "thunder": 0, + "ice": 2, + "dragon": 1 + }, + { + "name": "left leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "right leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 4, + "blunt": 3, + "ammo": 4, + "fire": 2, + "water": 1, + "thunder": 0, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "rathian", + "type": "flying wyvern", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "minor wind pressure", + "fireblight", + "poison" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "thunder", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 0, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 2 + }, + { + "name": "neck", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "left wing", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "right wing", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "left leg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 0, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "right leg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 0, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + } + ] + }, + { + "name": "guardian fulgur anjanath", + "type": "construct", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "minor wind pressure", + "thunderblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "water", + "ice", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 2, + "thunder": 0, + "ice": 2, + "dragon": 2 + }, + { + "name": "nose", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 2, + "thunder": 0, + "ice": 2, + "dragon": 2 + }, + { + "name": "neck", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 2, + "thunder": 0, + "ice": 2, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 2, + "dragon": 0 + }, + { + "name": "left wing", + "cut": 4, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 2, + "thunder": 0, + "ice": 2, + "dragon": 1 + }, + { + "name": "right wing", + "cut": 4, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 2, + "thunder": 0, + "ice": 2, + "dragon": 1 + }, + { + "name": "left leg", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 2, + "dragon": 0 + }, + { + "name": "right leg", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 1, + "thunder": 0, + "ice": 2, + "dragon": 0 + }, + { + "name": "tail", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 1, + "water": 2, + "thunder": 0, + "ice": 2, + "dragon": 1 + } + ] + }, + { + "name": "rathalos", + "type": "flying wyvern", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "minor wind pressure", + "fireblight", + "poison" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "thunder", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 0, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 2 + }, + { + "name": "neck", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "left wing", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "right wing", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "left leg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 0, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "right leg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 0, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + } + ] + }, + { + "name": "gravios", + "type": "flying wyvern", + "ailments": [ + { + "condition": "default", + "values": [ + "strong roar", + "minor tremor", + "fireblight", + "poison", + "sleep" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "water", + "ice", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "head", + "back", + "belly", + "tail" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "neck", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "belly", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "back", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left wing", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right wing", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 2, + "thunder": 1, + "ice": 1, + "dragon": 2 + } + ] + }, + { + "name": "blangonga", + "type": "fanged beast", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "iceblight", + "frostblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire", + "thunder" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "back" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 4, + "fire": 2, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 0 + }, + { + "name": "torso", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 2, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 0 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 0 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 0 + }, + { + "name": "left hindleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 0 + }, + { + "name": "right hindleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 0 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 0 + } + ] + }, + { + "name": "gore magala", + "type": "demi elder", + "ailments": [ + { + "condition": "default", + "values": [ + "strong roar", + "frenzy virus" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire", + "thunder", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "antenna", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 2, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 2 + }, + { + "name": "neck", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left wing", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right wing", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left wingarm", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right wingarm", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 2, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "arkveld", + "type": "flying wyvern", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "minor wind pressure", + "dragonblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "chainblade" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left wing", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right wing", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left chainblade", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 0 + }, + { + "name": "right chainblade", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 0 + }, + { + "name": "left leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right leg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "mizutsune", + "type": "leviathan", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "fireblight", + "waterblight", + "bubbleblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "ice", + "thunder", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "mouth" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 4, + "blunt": 4, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left claw", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 2, + "ice": 1, + "dragon": 2 + }, + { + "name": "right claw", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 2, + "ice": 1, + "dragon": 2 + }, + { + "name": "back fin", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 2, + "ice": 1, + "dragon": 2 + }, + { + "name": "right foreleg", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 2, + "ice": 1, + "dragon": 2 + }, + { + "name": "left hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 2, + "ammo": 3, + "fire": 1, + "water": 0, + "thunder": 1, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "seregios", + "type": "flying wyvern", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "bleeding" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "ice", + "thunder" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "legs" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 1 + }, + { + "name": "neck", + "cut": 2, + "blunt": 3, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left wing", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "right wing", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 1, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 3, + "ice": 2, + "dragon": 1 + }, + { + "name": "right hindleg", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 3, + "ice": 2, + "dragon": 1 + }, + { + "name": "tail", + "cut": 3, + "blunt": 2, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "lagiacrus", + "type": "leviathan", + "ailments": [ + { + "condition": "default", + "values": [ + "weak roar", + "thunderblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire", + "ice", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [ + "head", + "back" + ] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 2, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "neck", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "forelegs", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "back", + "cut": 3, + "blunt": 3, + "ammo": 1, + "fire": 3, + "water": 0, + "thunder": 0, + "ice": 2, + "dragon": 2 + }, + { + "name": "hindlegs", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 1 + }, + { + "name": "tail", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 3, + "water": 0, + "thunder": 0, + "ice": 1, + "dragon": 1 + } + ] + }, + { + "name": "omega planetes", + "type": "???", + "ailments": [ + { + "condition": "default", + "values": [ + "fireblight", + "hp penalty" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "thunder", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 3, + "ice": 1, + "dragon": 3 + }, + { + "name": "torso", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 0, + "water": 1, + "thunder": 1, + "ice": 0, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 3, + "blunt": 4, + "ammo": 4, + "fire": 0, + "water": 1, + "thunder": 3, + "ice": 1, + "dragon": 3 + }, + { + "name": "right foreleg", + "cut": 3, + "blunt": 4, + "ammo": 4, + "fire": 0, + "water": 1, + "thunder": 3, + "ice": 1, + "dragon": 3 + }, + { + "name": "back", + "cut": 3, + "blunt": 2, + "ammo": 2, + "fire": 0, + "water": 1, + "thunder": 3, + "ice": 1, + "dragon": 2 + }, + { + "name": "left hindleg", + "cut": 4, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 3, + "ice": 1, + "dragon": 2 + }, + { + "name": "right hindleg", + "cut": 4, + "blunt": 3, + "ammo": 3, + "fire": 0, + "water": 1, + "thunder": 3, + "ice": 1, + "dragon": 2 + }, + { + "name": "back unity", + "cut": 4, + "blunt": 4, + "ammo": 1, + "fire": 0, + "water": 1, + "thunder": 2, + "ice": 1, + "dragon": 4 + } + ] + }, + { + "name": "gogmazios", + "type": "Elder Dragon", + "ailments": [ + { + "condition": "default", + "values": [ + "minor tremor", + "strong roar", + "fireblight" + ] + } + ], + "weaknesses": [ + { + "condition": "default", + "values": [ + "fire", + "dragon" + ] + } + ], + "weakpoints": [ + { + "condition": "default", + "values": [] + } + ], + "damage": [ + { + "name": "head", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 2, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 1 + }, + { + "name": "chest", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 1 + }, + { + "name": "left foreleg", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 0, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 0 + }, + { + "name": "right foreleg", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 0, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 0 + }, + { + "name": "belly", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 0, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 0 + }, + { + "name": "back", + "cut": 2, + "blunt": 2, + "ammo": 2, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 1 + }, + { + "name": "left wingarm", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 2, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 1 + }, + { + "name": "right wingarm", + "cut": 3, + "blunt": 3, + "ammo": 2, + "fire": 2, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 1 + }, + { + "name": "left hindleg", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 0 + }, + { + "name": "right hindleg", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 0 + }, + { + "name": "tail", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 1 + }, + { + "name": "tail tip", + "cut": 2, + "blunt": 2, + "ammo": 1, + "fire": 1, + "water": 0, + "thunder": 0, + "ice": 0, + "dragon": 1 + } + ] + } + ] +} diff --git a/website/public/data/site.json b/website/public/data/site.json new file mode 100644 index 0000000..3a0133f --- /dev/null +++ b/website/public/data/site.json @@ -0,0 +1,92 @@ +{ + "brand": { + "name": "Sokko G", + "homeAriaLabel": "Accueil Sokko G" + }, + "navigation": { + "home": "Accueil", + "toolboxes": "Toolboxes", + "games": "Pages jeux", + "mobileGames": "Infos" + }, + "sidebar": { + "badge": "Local only", + "note": "Données stockées dans ce navigateur." + }, + "topbar": { + "dashboard": "Dashboard", + "toolbox": "Toolbox active", + "games": "Informations jeu" + }, + "home": { + "hero": { + "eyebrow": "Session gaming efficace", + "title": "Sokko G", + "description": "Centralisez vos outils et repères de jeu dans une interface locale, rapide à consulter, pensée pour accompagner vos sessions sans interrompre l’action.", + "primaryAction": "Voir les toolboxes", + "secondaryAction": "Voir les jeux" + }, + "stats": { + "toolboxSingular": "toolbox créée", + "toolboxPlural": "toolboxs créées", + "toolSingular": "outil disponible", + "toolPlural": "outils disponibles" + }, + "origin": { + "eyebrow": "Origine du nom", + "title": "Pourquoi Sokko G ?", + "visualAlt": "Dragon Sokko G", + "dialogueAriaLabel": "Dialogue d’origine du nom Sokko G", + "caption": "\"G\" c'est pour Gaming", + "lines": [ + { + "speaker": "user", + "text": "Yo !\nComment peut on traduire en un minimum de mots :\n\"faire qqch de manière efficace et rapide\" en japonais ?" + }, + { + "speaker": "app", + "text": "Une traduction concise en japonais serait :\n効率よく素早くやる (Kōritsu yoku subayaku yaru)\nCela signifie littéralement \"le faire efficacement et rapidement.\"" + }, + { + "speaker": "user", + "text": "En plus court stp" + }, + { + "speaker": "app", + "text": "迅速効率 (Jinsoku Kōritsu)\nCela signifie \"rapidité et efficacité\" en un minimum de mots." + }, + { + "speaker": "user", + "text": "Encore plus court !" + }, + { + "speaker": "app", + "text": "速効 (Sokkō)\nCela signifie \"efficacité rapide\" ou \"effet immédiat,\" selon le contexte." + }, + { + "speaker": "user", + "text": "Merci ! :)" + } + ] + } + }, + "toolboxes": { + "eyebrow": "Données locales", + "title": "Toolboxes", + "newButton": "Nouvelle toolbox", + "importAll": "Importer tout", + "exportAll": "Exporter tout", + "importOne": "Importer une toolbox", + "storageHelp": { + "title": "Sauvegardes locales", + "text": "Vos toolboxes restent sur cet appareil, dans ce navigateur. Rien n’est envoyé sur un serveur.", + "items": [ + "Un nettoyage du navigateur ou un changement d’appareil peut supprimer les données.", + "Surveillez le quota, surtout si vous ajoutez des screenshots.", + "Faites un export global régulier pour garder une sauvegarde." + ] + }, + "emptyTitle": "Aucune toolbox", + "emptyText": "Créez une première toolbox pour stocker vos outils dans ce navigateur." + } +} diff --git a/website/public/data/toolbox-icons.json b/website/public/data/toolbox-icons.json new file mode 100644 index 0000000..d239194 --- /dev/null +++ b/website/public/data/toolbox-icons.json @@ -0,0 +1,24 @@ +{ + "icons": [ + { + "id": "default", + "label": "Toolbox", + "src": "/static/img/toolbox-icons/default.svg" + }, + { + "id": "controller", + "label": "Controller", + "src": "/static/img/toolbox-icons/controller.svg" + }, + { + "id": "quest", + "label": "Quest", + "src": "/static/img/toolbox-icons/quest.svg" + }, + { + "id": "target", + "label": "Target", + "src": "/static/img/toolbox-icons/target.svg" + } + ] +} diff --git a/website/public/favicon.ico b/website/public/favicon.ico new file mode 100644 index 0000000..e0bfd7d Binary files /dev/null and b/website/public/favicon.ico differ diff --git a/website/public/index.html b/website/public/index.html new file mode 100644 index 0000000..c0c10ee --- /dev/null +++ b/website/public/index.html @@ -0,0 +1,21 @@ + + + + + + + + Sokko G + + + + + + +
+ + + diff --git a/website/public/static/icons/checklist.svg b/website/public/static/icons/checklist.svg new file mode 100644 index 0000000..071c68f --- /dev/null +++ b/website/public/static/icons/checklist.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/website/public/static/icons/close.svg b/website/public/static/icons/close.svg new file mode 100644 index 0000000..ef3f3eb --- /dev/null +++ b/website/public/static/icons/close.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/website/public/static/icons/columns.svg b/website/public/static/icons/columns.svg new file mode 100644 index 0000000..f12620f --- /dev/null +++ b/website/public/static/icons/columns.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/website/public/static/icons/controller.svg b/website/public/static/icons/controller.svg new file mode 100644 index 0000000..a93c9cd --- /dev/null +++ b/website/public/static/icons/controller.svg @@ -0,0 +1,3 @@ + + + diff --git a/website/public/static/icons/copy.svg b/website/public/static/icons/copy.svg new file mode 100644 index 0000000..3bc3d0d --- /dev/null +++ b/website/public/static/icons/copy.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/website/public/static/icons/edit.svg b/website/public/static/icons/edit.svg new file mode 100644 index 0000000..54bc424 --- /dev/null +++ b/website/public/static/icons/edit.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/website/public/static/icons/export.svg b/website/public/static/icons/export.svg new file mode 100644 index 0000000..912797c --- /dev/null +++ b/website/public/static/icons/export.svg @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/website/public/static/icons/home.svg b/website/public/static/icons/home.svg new file mode 100644 index 0000000..b658535 --- /dev/null +++ b/website/public/static/icons/home.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/website/public/static/icons/import.svg b/website/public/static/icons/import.svg new file mode 100644 index 0000000..a79ee9d --- /dev/null +++ b/website/public/static/icons/import.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/website/public/static/icons/link.svg b/website/public/static/icons/link.svg new file mode 100644 index 0000000..ff8dac1 --- /dev/null +++ b/website/public/static/icons/link.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/website/public/static/icons/notepad.svg b/website/public/static/icons/notepad.svg new file mode 100644 index 0000000..91a45d5 --- /dev/null +++ b/website/public/static/icons/notepad.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/website/public/static/icons/open.svg b/website/public/static/icons/open.svg new file mode 100644 index 0000000..86573cf --- /dev/null +++ b/website/public/static/icons/open.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/website/public/static/icons/picture.svg b/website/public/static/icons/picture.svg new file mode 100644 index 0000000..7c6810c --- /dev/null +++ b/website/public/static/icons/picture.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/website/public/static/icons/rows.svg b/website/public/static/icons/rows.svg new file mode 100644 index 0000000..5fb7975 --- /dev/null +++ b/website/public/static/icons/rows.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/website/public/static/icons/rubber.svg b/website/public/static/icons/rubber.svg new file mode 100644 index 0000000..645d2f4 --- /dev/null +++ b/website/public/static/icons/rubber.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/website/public/static/icons/save.svg b/website/public/static/icons/save.svg new file mode 100644 index 0000000..583bdb5 --- /dev/null +++ b/website/public/static/icons/save.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/website/public/static/icons/toolbox.svg b/website/public/static/icons/toolbox.svg new file mode 100644 index 0000000..536d39c --- /dev/null +++ b/website/public/static/icons/toolbox.svg @@ -0,0 +1,4 @@ + + + + diff --git a/website/public/static/icons/trashcan.svg b/website/public/static/icons/trashcan.svg new file mode 100644 index 0000000..0df6d81 --- /dev/null +++ b/website/public/static/icons/trashcan.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/website/public/static/img/dragon.png b/website/public/static/img/dragon.png new file mode 100644 index 0000000..160cc68 Binary files /dev/null and b/website/public/static/img/dragon.png differ diff --git a/website/public/static/img/mhwilds.png b/website/public/static/img/mhwilds.png new file mode 100644 index 0000000..a79e300 Binary files /dev/null and b/website/public/static/img/mhwilds.png differ diff --git a/website/public/static/img/mhwilds/0-stars.png b/website/public/static/img/mhwilds/0-stars.png new file mode 100644 index 0000000..11dbab2 Binary files /dev/null and b/website/public/static/img/mhwilds/0-stars.png differ diff --git a/website/public/static/img/mhwilds/1-stars.png b/website/public/static/img/mhwilds/1-stars.png new file mode 100644 index 0000000..39cb4b4 Binary files /dev/null and b/website/public/static/img/mhwilds/1-stars.png differ diff --git a/website/public/static/img/mhwilds/2-stars.png b/website/public/static/img/mhwilds/2-stars.png new file mode 100644 index 0000000..df92a3d Binary files /dev/null and b/website/public/static/img/mhwilds/2-stars.png differ diff --git a/website/public/static/img/mhwilds/3-stars.png b/website/public/static/img/mhwilds/3-stars.png new file mode 100644 index 0000000..6f17c63 Binary files /dev/null and b/website/public/static/img/mhwilds/3-stars.png differ diff --git a/website/public/static/img/mhwilds/4-stars.png b/website/public/static/img/mhwilds/4-stars.png new file mode 100644 index 0000000..95e3bfc Binary files /dev/null and b/website/public/static/img/mhwilds/4-stars.png differ diff --git a/website/public/static/img/mhwilds/ajarakan.png b/website/public/static/img/mhwilds/ajarakan.png new file mode 100644 index 0000000..5a728d9 Binary files /dev/null and b/website/public/static/img/mhwilds/ajarakan.png differ diff --git a/website/public/static/img/mhwilds/ammo.png b/website/public/static/img/mhwilds/ammo.png new file mode 100644 index 0000000..1bf0f9d Binary files /dev/null and b/website/public/static/img/mhwilds/ammo.png differ diff --git a/website/public/static/img/mhwilds/amstrigian.png b/website/public/static/img/mhwilds/amstrigian.png new file mode 100644 index 0000000..da92e9e Binary files /dev/null and b/website/public/static/img/mhwilds/amstrigian.png differ diff --git a/website/public/static/img/mhwilds/arkveld.png b/website/public/static/img/mhwilds/arkveld.png new file mode 100644 index 0000000..07e9395 Binary files /dev/null and b/website/public/static/img/mhwilds/arkveld.png differ diff --git a/website/public/static/img/mhwilds/azure rufflizard.png b/website/public/static/img/mhwilds/azure rufflizard.png new file mode 100644 index 0000000..38b5d1e Binary files /dev/null and b/website/public/static/img/mhwilds/azure rufflizard.png differ diff --git a/website/public/static/img/mhwilds/balahara.png b/website/public/static/img/mhwilds/balahara.png new file mode 100644 index 0000000..6c236bb Binary files /dev/null and b/website/public/static/img/mhwilds/balahara.png differ diff --git a/website/public/static/img/mhwilds/bitterbug.png b/website/public/static/img/mhwilds/bitterbug.png new file mode 100644 index 0000000..2a125e0 Binary files /dev/null and b/website/public/static/img/mhwilds/bitterbug.png differ diff --git a/website/public/static/img/mhwilds/black windrustler.png b/website/public/static/img/mhwilds/black windrustler.png new file mode 100644 index 0000000..b86c5c1 Binary files /dev/null and b/website/public/static/img/mhwilds/black windrustler.png differ diff --git a/website/public/static/img/mhwilds/blangonga.png b/website/public/static/img/mhwilds/blangonga.png new file mode 100644 index 0000000..1575241 Binary files /dev/null and b/website/public/static/img/mhwilds/blangonga.png differ diff --git a/website/public/static/img/mhwilds/blast.png b/website/public/static/img/mhwilds/blast.png new file mode 100644 index 0000000..4d2ddfb Binary files /dev/null and b/website/public/static/img/mhwilds/blast.png differ diff --git a/website/public/static/img/mhwilds/bleeding.png b/website/public/static/img/mhwilds/bleeding.png new file mode 100644 index 0000000..0b4dd4d Binary files /dev/null and b/website/public/static/img/mhwilds/bleeding.png differ diff --git a/website/public/static/img/mhwilds/blind perch.png b/website/public/static/img/mhwilds/blind perch.png new file mode 100644 index 0000000..4cc1dfb Binary files /dev/null and b/website/public/static/img/mhwilds/blind perch.png differ diff --git a/website/public/static/img/mhwilds/blink angler.png b/website/public/static/img/mhwilds/blink angler.png new file mode 100644 index 0000000..e0999df Binary files /dev/null and b/website/public/static/img/mhwilds/blink angler.png differ diff --git a/website/public/static/img/mhwilds/blunt.png b/website/public/static/img/mhwilds/blunt.png new file mode 100644 index 0000000..66a20c6 Binary files /dev/null and b/website/public/static/img/mhwilds/blunt.png differ diff --git a/website/public/static/img/mhwilds/bomb arowana.png b/website/public/static/img/mhwilds/bomb arowana.png new file mode 100644 index 0000000..3c16dba Binary files /dev/null and b/website/public/static/img/mhwilds/bomb arowana.png differ diff --git a/website/public/static/img/mhwilds/bomb beetle.png b/website/public/static/img/mhwilds/bomb beetle.png new file mode 100644 index 0000000..636e9fe Binary files /dev/null and b/website/public/static/img/mhwilds/bomb beetle.png differ diff --git a/website/public/static/img/mhwilds/bubbleblight.png b/website/public/static/img/mhwilds/bubbleblight.png new file mode 100644 index 0000000..6f89e73 Binary files /dev/null and b/website/public/static/img/mhwilds/bubbleblight.png differ diff --git a/website/public/static/img/mhwilds/burst arowana.png b/website/public/static/img/mhwilds/burst arowana.png new file mode 100644 index 0000000..8ef7146 Binary files /dev/null and b/website/public/static/img/mhwilds/burst arowana.png differ diff --git a/website/public/static/img/mhwilds/chatacabra.png b/website/public/static/img/mhwilds/chatacabra.png new file mode 100644 index 0000000..49be268 Binary files /dev/null and b/website/public/static/img/mhwilds/chatacabra.png differ diff --git a/website/public/static/img/mhwilds/chillmantle bug.png b/website/public/static/img/mhwilds/chillmantle bug.png new file mode 100644 index 0000000..ffc1e69 Binary files /dev/null and b/website/public/static/img/mhwilds/chillmantle bug.png differ diff --git a/website/public/static/img/mhwilds/congalala.png b/website/public/static/img/mhwilds/congalala.png new file mode 100644 index 0000000..a9bffba Binary files /dev/null and b/website/public/static/img/mhwilds/congalala.png differ diff --git a/website/public/static/img/mhwilds/crudeshell crab.png b/website/public/static/img/mhwilds/crudeshell crab.png new file mode 100644 index 0000000..62a324f Binary files /dev/null and b/website/public/static/img/mhwilds/crudeshell crab.png differ diff --git a/website/public/static/img/mhwilds/curioshell crab.png b/website/public/static/img/mhwilds/curioshell crab.png new file mode 100644 index 0000000..033cd9e Binary files /dev/null and b/website/public/static/img/mhwilds/curioshell crab.png differ diff --git a/website/public/static/img/mhwilds/cut.png b/website/public/static/img/mhwilds/cut.png new file mode 100644 index 0000000..0314727 Binary files /dev/null and b/website/public/static/img/mhwilds/cut.png differ diff --git a/website/public/static/img/mhwilds/dapperwing.png b/website/public/static/img/mhwilds/dapperwing.png new file mode 100644 index 0000000..c18c058 Binary files /dev/null and b/website/public/static/img/mhwilds/dapperwing.png differ diff --git a/website/public/static/img/mhwilds/dark hornet.png b/website/public/static/img/mhwilds/dark hornet.png new file mode 100644 index 0000000..4547ad8 Binary files /dev/null and b/website/public/static/img/mhwilds/dark hornet.png differ diff --git a/website/public/static/img/mhwilds/dazzlewing.png b/website/public/static/img/mhwilds/dazzlewing.png new file mode 100644 index 0000000..02fd79b Binary files /dev/null and b/website/public/static/img/mhwilds/dazzlewing.png differ diff --git a/website/public/static/img/mhwilds/defense down.png b/website/public/static/img/mhwilds/defense down.png new file mode 100644 index 0000000..6e51f9c Binary files /dev/null and b/website/public/static/img/mhwilds/defense down.png differ diff --git a/website/public/static/img/mhwilds/doshaguma.png b/website/public/static/img/mhwilds/doshaguma.png new file mode 100644 index 0000000..c20a2de Binary files /dev/null and b/website/public/static/img/mhwilds/doshaguma.png differ diff --git a/website/public/static/img/mhwilds/downy crake.png b/website/public/static/img/mhwilds/downy crake.png new file mode 100644 index 0000000..94ea76a Binary files /dev/null and b/website/public/static/img/mhwilds/downy crake.png differ diff --git a/website/public/static/img/mhwilds/dragon wind pressure.png b/website/public/static/img/mhwilds/dragon wind pressure.png new file mode 100644 index 0000000..bce37b2 Binary files /dev/null and b/website/public/static/img/mhwilds/dragon wind pressure.png differ diff --git a/website/public/static/img/mhwilds/dragon.png b/website/public/static/img/mhwilds/dragon.png new file mode 100644 index 0000000..3072357 Binary files /dev/null and b/website/public/static/img/mhwilds/dragon.png differ diff --git a/website/public/static/img/mhwilds/dragonblight.png b/website/public/static/img/mhwilds/dragonblight.png new file mode 100644 index 0000000..3072357 Binary files /dev/null and b/website/public/static/img/mhwilds/dragonblight.png differ diff --git a/website/public/static/img/mhwilds/dung beetle.png b/website/public/static/img/mhwilds/dung beetle.png new file mode 100644 index 0000000..d378df2 Binary files /dev/null and b/website/public/static/img/mhwilds/dung beetle.png differ diff --git a/website/public/static/img/mhwilds/ember rufflizard.png b/website/public/static/img/mhwilds/ember rufflizard.png new file mode 100644 index 0000000..a831048 Binary files /dev/null and b/website/public/static/img/mhwilds/ember rufflizard.png differ diff --git a/website/public/static/img/mhwilds/emperor hopper.png b/website/public/static/img/mhwilds/emperor hopper.png new file mode 100644 index 0000000..87c3c86 Binary files /dev/null and b/website/public/static/img/mhwilds/emperor hopper.png differ diff --git a/website/public/static/img/mhwilds/escunite.png b/website/public/static/img/mhwilds/escunite.png new file mode 100644 index 0000000..da44728 Binary files /dev/null and b/website/public/static/img/mhwilds/escunite.png differ diff --git a/website/public/static/img/mhwilds/fire.png b/website/public/static/img/mhwilds/fire.png new file mode 100644 index 0000000..1de9397 Binary files /dev/null and b/website/public/static/img/mhwilds/fire.png differ diff --git a/website/public/static/img/mhwilds/fireblight.png b/website/public/static/img/mhwilds/fireblight.png new file mode 100644 index 0000000..1de9397 Binary files /dev/null and b/website/public/static/img/mhwilds/fireblight.png differ diff --git a/website/public/static/img/mhwilds/flashbug.png b/website/public/static/img/mhwilds/flashbug.png new file mode 100644 index 0000000..3342a9f Binary files /dev/null and b/website/public/static/img/mhwilds/flashbug.png differ diff --git a/website/public/static/img/mhwilds/flashflies.png b/website/public/static/img/mhwilds/flashflies.png new file mode 100644 index 0000000..7d7a071 Binary files /dev/null and b/website/public/static/img/mhwilds/flashflies.png differ diff --git a/website/public/static/img/mhwilds/florashell crab.png b/website/public/static/img/mhwilds/florashell crab.png new file mode 100644 index 0000000..c547401 Binary files /dev/null and b/website/public/static/img/mhwilds/florashell crab.png differ diff --git a/website/public/static/img/mhwilds/frenzy virus.png b/website/public/static/img/mhwilds/frenzy virus.png new file mode 100644 index 0000000..006a00b Binary files /dev/null and b/website/public/static/img/mhwilds/frenzy virus.png differ diff --git a/website/public/static/img/mhwilds/frostblight.png b/website/public/static/img/mhwilds/frostblight.png new file mode 100644 index 0000000..5f0aa69 Binary files /dev/null and b/website/public/static/img/mhwilds/frostblight.png differ diff --git a/website/public/static/img/mhwilds/gajau.png b/website/public/static/img/mhwilds/gajau.png new file mode 100644 index 0000000..e97ab60 Binary files /dev/null and b/website/public/static/img/mhwilds/gajau.png differ diff --git a/website/public/static/img/mhwilds/gastronome tuna.png b/website/public/static/img/mhwilds/gastronome tuna.png new file mode 100644 index 0000000..5688a6c Binary files /dev/null and b/website/public/static/img/mhwilds/gastronome tuna.png differ diff --git a/website/public/static/img/mhwilds/gawkie.png b/website/public/static/img/mhwilds/gawkie.png new file mode 100644 index 0000000..f574bdc Binary files /dev/null and b/website/public/static/img/mhwilds/gawkie.png differ diff --git a/website/public/static/img/mhwilds/giant vigorwasp.png b/website/public/static/img/mhwilds/giant vigorwasp.png new file mode 100644 index 0000000..a4ad52a Binary files /dev/null and b/website/public/static/img/mhwilds/giant vigorwasp.png differ diff --git a/website/public/static/img/mhwilds/gillopod.png b/website/public/static/img/mhwilds/gillopod.png new file mode 100644 index 0000000..b906829 Binary files /dev/null and b/website/public/static/img/mhwilds/gillopod.png differ diff --git a/website/public/static/img/mhwilds/glass parexus.png b/website/public/static/img/mhwilds/glass parexus.png new file mode 100644 index 0000000..5133d72 Binary files /dev/null and b/website/public/static/img/mhwilds/glass parexus.png differ diff --git a/website/public/static/img/mhwilds/gloom gekko.png b/website/public/static/img/mhwilds/gloom gekko.png new file mode 100644 index 0000000..6299b75 Binary files /dev/null and b/website/public/static/img/mhwilds/gloom gekko.png differ diff --git a/website/public/static/img/mhwilds/godbug.png b/website/public/static/img/mhwilds/godbug.png new file mode 100644 index 0000000..9da33ee Binary files /dev/null and b/website/public/static/img/mhwilds/godbug.png differ diff --git a/website/public/static/img/mhwilds/gogmazios.png b/website/public/static/img/mhwilds/gogmazios.png new file mode 100644 index 0000000..1e5af82 Binary files /dev/null and b/website/public/static/img/mhwilds/gogmazios.png differ diff --git a/website/public/static/img/mhwilds/gold hercudrome.png b/website/public/static/img/mhwilds/gold hercudrome.png new file mode 100644 index 0000000..56faa47 Binary files /dev/null and b/website/public/static/img/mhwilds/gold hercudrome.png differ diff --git a/website/public/static/img/mhwilds/goldenfish.png b/website/public/static/img/mhwilds/goldenfish.png new file mode 100644 index 0000000..947d034 Binary files /dev/null and b/website/public/static/img/mhwilds/goldenfish.png differ diff --git a/website/public/static/img/mhwilds/goldenfry.png b/website/public/static/img/mhwilds/goldenfry.png new file mode 100644 index 0000000..80440b8 Binary files /dev/null and b/website/public/static/img/mhwilds/goldenfry.png differ diff --git a/website/public/static/img/mhwilds/goliath squid.png b/website/public/static/img/mhwilds/goliath squid.png new file mode 100644 index 0000000..993b612 Binary files /dev/null and b/website/public/static/img/mhwilds/goliath squid.png differ diff --git a/website/public/static/img/mhwilds/gore magala.png b/website/public/static/img/mhwilds/gore magala.png new file mode 100644 index 0000000..e43cd48 Binary files /dev/null and b/website/public/static/img/mhwilds/gore magala.png differ diff --git a/website/public/static/img/mhwilds/grand escunite.png b/website/public/static/img/mhwilds/grand escunite.png new file mode 100644 index 0000000..7e01459 Binary files /dev/null and b/website/public/static/img/mhwilds/grand escunite.png differ diff --git a/website/public/static/img/mhwilds/gravid bowfin.png b/website/public/static/img/mhwilds/gravid bowfin.png new file mode 100644 index 0000000..58f4463 Binary files /dev/null and b/website/public/static/img/mhwilds/gravid bowfin.png differ diff --git a/website/public/static/img/mhwilds/gravios.png b/website/public/static/img/mhwilds/gravios.png new file mode 100644 index 0000000..4c138a1 Binary files /dev/null and b/website/public/static/img/mhwilds/gravios.png differ diff --git a/website/public/static/img/mhwilds/great thunderbugs.png b/website/public/static/img/mhwilds/great thunderbugs.png new file mode 100644 index 0000000..8c8277a Binary files /dev/null and b/website/public/static/img/mhwilds/great thunderbugs.png differ diff --git a/website/public/static/img/mhwilds/great trevally.png b/website/public/static/img/mhwilds/great trevally.png new file mode 100644 index 0000000..8808e1b Binary files /dev/null and b/website/public/static/img/mhwilds/great trevally.png differ diff --git a/website/public/static/img/mhwilds/guardian arkveld.png b/website/public/static/img/mhwilds/guardian arkveld.png new file mode 100644 index 0000000..ab86324 Binary files /dev/null and b/website/public/static/img/mhwilds/guardian arkveld.png differ diff --git a/website/public/static/img/mhwilds/guardian doshaguma.png b/website/public/static/img/mhwilds/guardian doshaguma.png new file mode 100644 index 0000000..7d78ac6 Binary files /dev/null and b/website/public/static/img/mhwilds/guardian doshaguma.png differ diff --git a/website/public/static/img/mhwilds/guardian ebony odogaron.png b/website/public/static/img/mhwilds/guardian ebony odogaron.png new file mode 100644 index 0000000..412d166 Binary files /dev/null and b/website/public/static/img/mhwilds/guardian ebony odogaron.png differ diff --git a/website/public/static/img/mhwilds/guardian fulgur anjanath.png b/website/public/static/img/mhwilds/guardian fulgur anjanath.png new file mode 100644 index 0000000..119572b Binary files /dev/null and b/website/public/static/img/mhwilds/guardian fulgur anjanath.png differ diff --git a/website/public/static/img/mhwilds/guardian rathalos.png b/website/public/static/img/mhwilds/guardian rathalos.png new file mode 100644 index 0000000..53ecf04 Binary files /dev/null and b/website/public/static/img/mhwilds/guardian rathalos.png differ diff --git a/website/public/static/img/mhwilds/gunpowderfish.png b/website/public/static/img/mhwilds/gunpowderfish.png new file mode 100644 index 0000000..65ce69a Binary files /dev/null and b/website/public/static/img/mhwilds/gunpowderfish.png differ diff --git a/website/public/static/img/mhwilds/gypceros.png b/website/public/static/img/mhwilds/gypceros.png new file mode 100644 index 0000000..bb1ec6c Binary files /dev/null and b/website/public/static/img/mhwilds/gypceros.png differ diff --git a/website/public/static/img/mhwilds/heatmantle bug.png b/website/public/static/img/mhwilds/heatmantle bug.png new file mode 100644 index 0000000..fee8ed8 Binary files /dev/null and b/website/public/static/img/mhwilds/heatmantle bug.png differ diff --git a/website/public/static/img/mhwilds/hercudrome.png b/website/public/static/img/mhwilds/hercudrome.png new file mode 100644 index 0000000..21d5043 Binary files /dev/null and b/website/public/static/img/mhwilds/hercudrome.png differ diff --git a/website/public/static/img/mhwilds/hirabami.png b/website/public/static/img/mhwilds/hirabami.png new file mode 100644 index 0000000..39921fd Binary files /dev/null and b/website/public/static/img/mhwilds/hirabami.png differ diff --git a/website/public/static/img/mhwilds/hp penalty.png b/website/public/static/img/mhwilds/hp penalty.png new file mode 100644 index 0000000..8f96a10 Binary files /dev/null and b/website/public/static/img/mhwilds/hp penalty.png differ diff --git a/website/public/static/img/mhwilds/hymstrigian.png b/website/public/static/img/mhwilds/hymstrigian.png new file mode 100644 index 0000000..0c71509 Binary files /dev/null and b/website/public/static/img/mhwilds/hymstrigian.png differ diff --git a/website/public/static/img/mhwilds/ice.png b/website/public/static/img/mhwilds/ice.png new file mode 100644 index 0000000..bc82dfe Binary files /dev/null and b/website/public/static/img/mhwilds/ice.png differ diff --git a/website/public/static/img/mhwilds/iceblight.png b/website/public/static/img/mhwilds/iceblight.png new file mode 100644 index 0000000..bc82dfe Binary files /dev/null and b/website/public/static/img/mhwilds/iceblight.png differ diff --git a/website/public/static/img/mhwilds/iceshard cliffs.png b/website/public/static/img/mhwilds/iceshard cliffs.png new file mode 100644 index 0000000..edf4640 Binary files /dev/null and b/website/public/static/img/mhwilds/iceshard cliffs.png differ diff --git a/website/public/static/img/mhwilds/jewel scarab.png b/website/public/static/img/mhwilds/jewel scarab.png new file mode 100644 index 0000000..d24ab85 Binary files /dev/null and b/website/public/static/img/mhwilds/jewel scarab.png differ diff --git a/website/public/static/img/mhwilds/jin dahaad.png b/website/public/static/img/mhwilds/jin dahaad.png new file mode 100644 index 0000000..9e72088 Binary files /dev/null and b/website/public/static/img/mhwilds/jin dahaad.png differ diff --git a/website/public/static/img/mhwilds/lagiacrus.png b/website/public/static/img/mhwilds/lagiacrus.png new file mode 100644 index 0000000..a63f4dd Binary files /dev/null and b/website/public/static/img/mhwilds/lagiacrus.png differ diff --git a/website/public/static/img/mhwilds/lala barina.png b/website/public/static/img/mhwilds/lala barina.png new file mode 100644 index 0000000..0dcf326 Binary files /dev/null and b/website/public/static/img/mhwilds/lala barina.png differ diff --git a/website/public/static/img/mhwilds/landlight shrimp.png b/website/public/static/img/mhwilds/landlight shrimp.png new file mode 100644 index 0000000..072dade Binary files /dev/null and b/website/public/static/img/mhwilds/landlight shrimp.png differ diff --git a/website/public/static/img/mhwilds/leaflugger ant.png b/website/public/static/img/mhwilds/leaflugger ant.png new file mode 100644 index 0000000..5a9409d Binary files /dev/null and b/website/public/static/img/mhwilds/leaflugger ant.png differ diff --git a/website/public/static/img/mhwilds/mimiphyta.png b/website/public/static/img/mhwilds/mimiphyta.png new file mode 100644 index 0000000..0a4400a Binary files /dev/null and b/website/public/static/img/mhwilds/mimiphyta.png differ diff --git a/website/public/static/img/mhwilds/minor tremor.png b/website/public/static/img/mhwilds/minor tremor.png new file mode 100644 index 0000000..3356091 Binary files /dev/null and b/website/public/static/img/mhwilds/minor tremor.png differ diff --git a/website/public/static/img/mhwilds/minor wind pressure.png b/website/public/static/img/mhwilds/minor wind pressure.png new file mode 100644 index 0000000..9bee2e7 Binary files /dev/null and b/website/public/static/img/mhwilds/minor wind pressure.png differ diff --git a/website/public/static/img/mhwilds/mizutsune.png b/website/public/static/img/mhwilds/mizutsune.png new file mode 100644 index 0000000..b36b2f9 Binary files /dev/null and b/website/public/static/img/mhwilds/mizutsune.png differ diff --git a/website/public/static/img/mhwilds/myriads.png b/website/public/static/img/mhwilds/myriads.png new file mode 100644 index 0000000..032c832 Binary files /dev/null and b/website/public/static/img/mhwilds/myriads.png differ diff --git a/website/public/static/img/mhwilds/nerscylla.png b/website/public/static/img/mhwilds/nerscylla.png new file mode 100644 index 0000000..06f16da Binary files /dev/null and b/website/public/static/img/mhwilds/nerscylla.png differ diff --git a/website/public/static/img/mhwilds/neutral.png b/website/public/static/img/mhwilds/neutral.png new file mode 100644 index 0000000..09df12e Binary files /dev/null and b/website/public/static/img/mhwilds/neutral.png differ diff --git a/website/public/static/img/mhwilds/nitrotoad.png b/website/public/static/img/mhwilds/nitrotoad.png new file mode 100644 index 0000000..18ee96b Binary files /dev/null and b/website/public/static/img/mhwilds/nitrotoad.png differ diff --git a/website/public/static/img/mhwilds/nothpecker.png b/website/public/static/img/mhwilds/nothpecker.png new file mode 100644 index 0000000..369ed7f Binary files /dev/null and b/website/public/static/img/mhwilds/nothpecker.png differ diff --git a/website/public/static/img/mhwilds/nu udra.png b/website/public/static/img/mhwilds/nu udra.png new file mode 100644 index 0000000..44ffc55 Binary files /dev/null and b/website/public/static/img/mhwilds/nu udra.png differ diff --git a/website/public/static/img/mhwilds/nu yaya.png b/website/public/static/img/mhwilds/nu yaya.png new file mode 100644 index 0000000..928bc4f Binary files /dev/null and b/website/public/static/img/mhwilds/nu yaya.png differ diff --git a/website/public/static/img/mhwilds/oilwell basin.png b/website/public/static/img/mhwilds/oilwell basin.png new file mode 100644 index 0000000..173111e Binary files /dev/null and b/website/public/static/img/mhwilds/oilwell basin.png differ diff --git a/website/public/static/img/mhwilds/omega planetes.png b/website/public/static/img/mhwilds/omega planetes.png new file mode 100644 index 0000000..6b857ed Binary files /dev/null and b/website/public/static/img/mhwilds/omega planetes.png differ diff --git a/website/public/static/img/mhwilds/omenfly.png b/website/public/static/img/mhwilds/omenfly.png new file mode 100644 index 0000000..e4a9c24 Binary files /dev/null and b/website/public/static/img/mhwilds/omenfly.png differ diff --git a/website/public/static/img/mhwilds/pallbug.png b/website/public/static/img/mhwilds/pallbug.png new file mode 100644 index 0000000..2561cbe Binary files /dev/null and b/website/public/static/img/mhwilds/pallbug.png differ diff --git a/website/public/static/img/mhwilds/paralysis.png b/website/public/static/img/mhwilds/paralysis.png new file mode 100644 index 0000000..cb424c1 Binary files /dev/null and b/website/public/static/img/mhwilds/paralysis.png differ diff --git a/website/public/static/img/mhwilds/paratoad.png b/website/public/static/img/mhwilds/paratoad.png new file mode 100644 index 0000000..dcdf5a7 Binary files /dev/null and b/website/public/static/img/mhwilds/paratoad.png differ diff --git a/website/public/static/img/mhwilds/peony sparklerjelly.png b/website/public/static/img/mhwilds/peony sparklerjelly.png new file mode 100644 index 0000000..be882ae Binary files /dev/null and b/website/public/static/img/mhwilds/peony sparklerjelly.png differ diff --git a/website/public/static/img/mhwilds/petalfly.png b/website/public/static/img/mhwilds/petalfly.png new file mode 100644 index 0000000..7d29cbc Binary files /dev/null and b/website/public/static/img/mhwilds/petalfly.png differ diff --git a/website/public/static/img/mhwilds/pillopod.png b/website/public/static/img/mhwilds/pillopod.png new file mode 100644 index 0000000..a2389a6 Binary files /dev/null and b/website/public/static/img/mhwilds/pillopod.png differ diff --git a/website/public/static/img/mhwilds/pink landlight shrimp.png b/website/public/static/img/mhwilds/pink landlight shrimp.png new file mode 100644 index 0000000..b06cb7e Binary files /dev/null and b/website/public/static/img/mhwilds/pink landlight shrimp.png differ diff --git a/website/public/static/img/mhwilds/platinumfish.png b/website/public/static/img/mhwilds/platinumfish.png new file mode 100644 index 0000000..c43fd4a Binary files /dev/null and b/website/public/static/img/mhwilds/platinumfish.png differ diff --git a/website/public/static/img/mhwilds/poison.png b/website/public/static/img/mhwilds/poison.png new file mode 100644 index 0000000..aef6039 Binary files /dev/null and b/website/public/static/img/mhwilds/poison.png differ diff --git a/website/public/static/img/mhwilds/prism hercudrome.png b/website/public/static/img/mhwilds/prism hercudrome.png new file mode 100644 index 0000000..0adeafc Binary files /dev/null and b/website/public/static/img/mhwilds/prism hercudrome.png differ diff --git a/website/public/static/img/mhwilds/purewasp.png b/website/public/static/img/mhwilds/purewasp.png new file mode 100644 index 0000000..a2710fc Binary files /dev/null and b/website/public/static/img/mhwilds/purewasp.png differ diff --git a/website/public/static/img/mhwilds/quatreflies.png b/website/public/static/img/mhwilds/quatreflies.png new file mode 100644 index 0000000..b25e0b7 Binary files /dev/null and b/website/public/static/img/mhwilds/quatreflies.png differ diff --git a/website/public/static/img/mhwilds/quematrice.png b/website/public/static/img/mhwilds/quematrice.png new file mode 100644 index 0000000..ecdbae9 Binary files /dev/null and b/website/public/static/img/mhwilds/quematrice.png differ diff --git a/website/public/static/img/mhwilds/rathalos.png b/website/public/static/img/mhwilds/rathalos.png new file mode 100644 index 0000000..2b71bdf Binary files /dev/null and b/website/public/static/img/mhwilds/rathalos.png differ diff --git a/website/public/static/img/mhwilds/rathian.png b/website/public/static/img/mhwilds/rathian.png new file mode 100644 index 0000000..1bfef8d Binary files /dev/null and b/website/public/static/img/mhwilds/rathian.png differ diff --git a/website/public/static/img/mhwilds/regal jewel scarab.png b/website/public/static/img/mhwilds/regal jewel scarab.png new file mode 100644 index 0000000..1febdc2 Binary files /dev/null and b/website/public/static/img/mhwilds/regal jewel scarab.png differ diff --git a/website/public/static/img/mhwilds/rey dau.png b/website/public/static/img/mhwilds/rey dau.png new file mode 100644 index 0000000..983784f Binary files /dev/null and b/website/public/static/img/mhwilds/rey dau.png differ diff --git a/website/public/static/img/mhwilds/rime beetle.png b/website/public/static/img/mhwilds/rime beetle.png new file mode 100644 index 0000000..9d38e15 Binary files /dev/null and b/website/public/static/img/mhwilds/rime beetle.png differ diff --git a/website/public/static/img/mhwilds/rompopolo.png b/website/public/static/img/mhwilds/rompopolo.png new file mode 100644 index 0000000..24f1b6c Binary files /dev/null and b/website/public/static/img/mhwilds/rompopolo.png differ diff --git a/website/public/static/img/mhwilds/rufflizard.png b/website/public/static/img/mhwilds/rufflizard.png new file mode 100644 index 0000000..996f485 Binary files /dev/null and b/website/public/static/img/mhwilds/rufflizard.png differ diff --git a/website/public/static/img/mhwilds/ruins of wyveria.png b/website/public/static/img/mhwilds/ruins of wyveria.png new file mode 100644 index 0000000..3ba6598 Binary files /dev/null and b/website/public/static/img/mhwilds/ruins of wyveria.png differ diff --git a/website/public/static/img/mhwilds/sandstar.png b/website/public/static/img/mhwilds/sandstar.png new file mode 100644 index 0000000..280ae0f Binary files /dev/null and b/website/public/static/img/mhwilds/sandstar.png differ diff --git a/website/public/static/img/mhwilds/scalebat.png b/website/public/static/img/mhwilds/scalebat.png new file mode 100644 index 0000000..3821d9b Binary files /dev/null and b/website/public/static/img/mhwilds/scalebat.png differ diff --git a/website/public/static/img/mhwilds/scarlet forest.png b/website/public/static/img/mhwilds/scarlet forest.png new file mode 100644 index 0000000..996ddc3 Binary files /dev/null and b/website/public/static/img/mhwilds/scarlet forest.png differ diff --git a/website/public/static/img/mhwilds/scarlet joyperch.png b/website/public/static/img/mhwilds/scarlet joyperch.png new file mode 100644 index 0000000..4fdd946 Binary files /dev/null and b/website/public/static/img/mhwilds/scarlet joyperch.png differ diff --git a/website/public/static/img/mhwilds/seregios.png b/website/public/static/img/mhwilds/seregios.png new file mode 100644 index 0000000..eef3fad Binary files /dev/null and b/website/public/static/img/mhwilds/seregios.png differ diff --git a/website/public/static/img/mhwilds/sleep.png b/website/public/static/img/mhwilds/sleep.png new file mode 100644 index 0000000..bf363b2 Binary files /dev/null and b/website/public/static/img/mhwilds/sleep.png differ diff --git a/website/public/static/img/mhwilds/sleeptoad.png b/website/public/static/img/mhwilds/sleeptoad.png new file mode 100644 index 0000000..c8e77e2 Binary files /dev/null and b/website/public/static/img/mhwilds/sleeptoad.png differ diff --git a/website/public/static/img/mhwilds/smokeshroud gekko.png b/website/public/static/img/mhwilds/smokeshroud gekko.png new file mode 100644 index 0000000..5b1db43 Binary files /dev/null and b/website/public/static/img/mhwilds/smokeshroud gekko.png differ diff --git a/website/public/static/img/mhwilds/solanaria.png b/website/public/static/img/mhwilds/solanaria.png new file mode 100644 index 0000000..db0c6bf Binary files /dev/null and b/website/public/static/img/mhwilds/solanaria.png differ diff --git a/website/public/static/img/mhwilds/sparklerjelly.png b/website/public/static/img/mhwilds/sparklerjelly.png new file mode 100644 index 0000000..3c68659 Binary files /dev/null and b/website/public/static/img/mhwilds/sparklerjelly.png differ diff --git a/website/public/static/img/mhwilds/speartuna.png b/website/public/static/img/mhwilds/speartuna.png new file mode 100644 index 0000000..276e99e Binary files /dev/null and b/website/public/static/img/mhwilds/speartuna.png differ diff --git a/website/public/static/img/mhwilds/starduster.png b/website/public/static/img/mhwilds/starduster.png new file mode 100644 index 0000000..62bf3e8 Binary files /dev/null and b/website/public/static/img/mhwilds/starduster.png differ diff --git a/website/public/static/img/mhwilds/stench.png b/website/public/static/img/mhwilds/stench.png new file mode 100644 index 0000000..eeb9c9f Binary files /dev/null and b/website/public/static/img/mhwilds/stench.png differ diff --git a/website/public/static/img/mhwilds/strong roar.png b/website/public/static/img/mhwilds/strong roar.png new file mode 100644 index 0000000..4b81586 Binary files /dev/null and b/website/public/static/img/mhwilds/strong roar.png differ diff --git a/website/public/static/img/mhwilds/stun.png b/website/public/static/img/mhwilds/stun.png new file mode 100644 index 0000000..1b5d483 Binary files /dev/null and b/website/public/static/img/mhwilds/stun.png differ diff --git a/website/public/static/img/mhwilds/sushifish.png b/website/public/static/img/mhwilds/sushifish.png new file mode 100644 index 0000000..bd59deb Binary files /dev/null and b/website/public/static/img/mhwilds/sushifish.png differ diff --git a/website/public/static/img/mhwilds/thunder.png b/website/public/static/img/mhwilds/thunder.png new file mode 100644 index 0000000..6193a6b Binary files /dev/null and b/website/public/static/img/mhwilds/thunder.png differ diff --git a/website/public/static/img/mhwilds/thunderblight.png b/website/public/static/img/mhwilds/thunderblight.png new file mode 100644 index 0000000..6193a6b Binary files /dev/null and b/website/public/static/img/mhwilds/thunderblight.png differ diff --git a/website/public/static/img/mhwilds/thunderbug.png b/website/public/static/img/mhwilds/thunderbug.png new file mode 100644 index 0000000..0b1cef1 Binary files /dev/null and b/website/public/static/img/mhwilds/thunderbug.png differ diff --git a/website/public/static/img/mhwilds/tracktail lizard.png b/website/public/static/img/mhwilds/tracktail lizard.png new file mode 100644 index 0000000..7e377ac Binary files /dev/null and b/website/public/static/img/mhwilds/tracktail lizard.png differ diff --git a/website/public/static/img/mhwilds/tyrant hopper.png b/website/public/static/img/mhwilds/tyrant hopper.png new file mode 100644 index 0000000..789c853 Binary files /dev/null and b/website/public/static/img/mhwilds/tyrant hopper.png differ diff --git a/website/public/static/img/mhwilds/unknown.png b/website/public/static/img/mhwilds/unknown.png new file mode 100644 index 0000000..f2d789f Binary files /dev/null and b/website/public/static/img/mhwilds/unknown.png differ diff --git a/website/public/static/img/mhwilds/uth duna.png b/website/public/static/img/mhwilds/uth duna.png new file mode 100644 index 0000000..83038ac Binary files /dev/null and b/website/public/static/img/mhwilds/uth duna.png differ diff --git a/website/public/static/img/mhwilds/vaporonid.png b/website/public/static/img/mhwilds/vaporonid.png new file mode 100644 index 0000000..6ea7fb3 Binary files /dev/null and b/website/public/static/img/mhwilds/vaporonid.png differ diff --git a/website/public/static/img/mhwilds/vigormantle bug.png b/website/public/static/img/mhwilds/vigormantle bug.png new file mode 100644 index 0000000..d87c849 Binary files /dev/null and b/website/public/static/img/mhwilds/vigormantle bug.png differ diff --git a/website/public/static/img/mhwilds/vigorwasp.png b/website/public/static/img/mhwilds/vigorwasp.png new file mode 100644 index 0000000..c9944c4 Binary files /dev/null and b/website/public/static/img/mhwilds/vigorwasp.png differ diff --git a/website/public/static/img/mhwilds/virid bowfin.png b/website/public/static/img/mhwilds/virid bowfin.png new file mode 100644 index 0000000..6db6cf6 Binary files /dev/null and b/website/public/static/img/mhwilds/virid bowfin.png differ diff --git a/website/public/static/img/mhwilds/water.png b/website/public/static/img/mhwilds/water.png new file mode 100644 index 0000000..71fe658 Binary files /dev/null and b/website/public/static/img/mhwilds/water.png differ diff --git a/website/public/static/img/mhwilds/waterblight.png b/website/public/static/img/mhwilds/waterblight.png new file mode 100644 index 0000000..71fe658 Binary files /dev/null and b/website/public/static/img/mhwilds/waterblight.png differ diff --git a/website/public/static/img/mhwilds/weak roar.png b/website/public/static/img/mhwilds/weak roar.png new file mode 100644 index 0000000..ec6f398 Binary files /dev/null and b/website/public/static/img/mhwilds/weak roar.png differ diff --git a/website/public/static/img/mhwilds/webbed.png b/website/public/static/img/mhwilds/webbed.png new file mode 100644 index 0000000..3d6972e Binary files /dev/null and b/website/public/static/img/mhwilds/webbed.png differ diff --git a/website/public/static/img/mhwilds/wedge beetle.png b/website/public/static/img/mhwilds/wedge beetle.png new file mode 100644 index 0000000..336b7a9 Binary files /dev/null and b/website/public/static/img/mhwilds/wedge beetle.png differ diff --git a/website/public/static/img/mhwilds/whetfish.png b/website/public/static/img/mhwilds/whetfish.png new file mode 100644 index 0000000..f2a9c7f Binary files /dev/null and b/website/public/static/img/mhwilds/whetfish.png differ diff --git a/website/public/static/img/mhwilds/wiggly litchi.png b/website/public/static/img/mhwilds/wiggly litchi.png new file mode 100644 index 0000000..9ddf9fa Binary files /dev/null and b/website/public/static/img/mhwilds/wiggly litchi.png differ diff --git a/website/public/static/img/mhwilds/windrustler.png b/website/public/static/img/mhwilds/windrustler.png new file mode 100644 index 0000000..a475696 Binary files /dev/null and b/website/public/static/img/mhwilds/windrustler.png differ diff --git a/website/public/static/img/mhwilds/windward plains.png b/website/public/static/img/mhwilds/windward plains.png new file mode 100644 index 0000000..723e24f Binary files /dev/null and b/website/public/static/img/mhwilds/windward plains.png differ diff --git a/website/public/static/img/mhwilds/xu wu.png b/website/public/static/img/mhwilds/xu wu.png new file mode 100644 index 0000000..253fa47 Binary files /dev/null and b/website/public/static/img/mhwilds/xu wu.png differ diff --git a/website/public/static/img/mhwilds/xu yo.png b/website/public/static/img/mhwilds/xu yo.png new file mode 100644 index 0000000..81b75bb Binary files /dev/null and b/website/public/static/img/mhwilds/xu yo.png differ diff --git a/website/public/static/img/mhwilds/yian kut-ku.png b/website/public/static/img/mhwilds/yian kut-ku.png new file mode 100644 index 0000000..eda14f5 Binary files /dev/null and b/website/public/static/img/mhwilds/yian kut-ku.png differ diff --git a/website/public/static/img/mhwilds/zoh shia.png b/website/public/static/img/mhwilds/zoh shia.png new file mode 100644 index 0000000..10f226e Binary files /dev/null and b/website/public/static/img/mhwilds/zoh shia.png differ diff --git a/website/public/static/img/toolbox-icons/controller.svg b/website/public/static/img/toolbox-icons/controller.svg new file mode 100644 index 0000000..de7a7aa --- /dev/null +++ b/website/public/static/img/toolbox-icons/controller.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/website/public/static/img/toolbox-icons/default.svg b/website/public/static/img/toolbox-icons/default.svg new file mode 100644 index 0000000..5b6da01 --- /dev/null +++ b/website/public/static/img/toolbox-icons/default.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/website/public/static/img/toolbox-icons/quest.svg b/website/public/static/img/toolbox-icons/quest.svg new file mode 100644 index 0000000..e11806c --- /dev/null +++ b/website/public/static/img/toolbox-icons/quest.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/website/public/static/img/toolbox-icons/target.svg b/website/public/static/img/toolbox-icons/target.svg new file mode 100644 index 0000000..40f378f --- /dev/null +++ b/website/public/static/img/toolbox-icons/target.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/website/src/app.js b/website/src/app.js new file mode 100644 index 0000000..1f282b3 --- /dev/null +++ b/website/src/app.js @@ -0,0 +1,2300 @@ +const STORAGE_KEYS = { + registry: "sokkog:toolboxes", + links: "sokkog:game-toolbox-links", + drawerWidth: "sokkog:drawer-width" +}; +const APP_STORAGE_PREFIX = "sokkog:"; +const APP_STORAGE_LIMIT_BYTES = 5 * 1024 * 1024; +const APP_STORAGE_WARNING_RATIO = 0.85; + +const MODULES = { + notepad: { label: "Bloc notes", icon: "notepad" }, + checklist: { label: "Checklist", icon: "checklist" }, + screenshots: { label: "Screenshots", icon: "picture" } +}; + +const DEFAULT_SITE_CONTENT = { + brand: { name: "Sokko G", homeAriaLabel: "Accueil Sokko G" }, + navigation: { home: "Accueil", toolboxes: "Toolboxes", games: "Pages jeux", mobileGames: "Infos" }, + sidebar: { badge: "Local only", note: "Données stockées dans ce navigateur." }, + topbar: { dashboard: "Dashboard", toolbox: "Toolbox active", games: "Informations jeu" }, + home: { + hero: { + eyebrow: "Session gaming efficace", + title: "Sokko G", + description: "Centralisez vos outils et repères de jeu dans une interface locale, rapide à consulter, pensée pour accompagner vos sessions sans interrompre l’action.", + primaryAction: "Voir les toolboxes", + secondaryAction: "Voir les jeux" + }, + stats: { + toolboxSingular: "toolbox créée", + toolboxPlural: "toolboxs créées", + toolSingular: "outil disponible", + toolPlural: "outils disponibles" + }, + origin: { + eyebrow: "Origine du nom", + title: "Pourquoi Sokko G ?", + visualAlt: "Dragon Sokko G", + dialogueAriaLabel: "Dialogue d’origine du nom Sokko G", + caption: "\"G\" c'est pour Gaming", + lines: [] + } + }, + toolboxes: { + eyebrow: "Données locales", + title: "Toolboxes", + newButton: "Nouvelle toolbox", + importAll: "Importer tout", + exportAll: "Exporter tout", + importOne: "Importer une toolbox", + storageHelp: { + title: "Sauvegardes locales", + text: "Vos toolboxes restent sur cet appareil, dans ce navigateur. Rien n’est envoyé sur un serveur.", + items: [ + "Un nettoyage du navigateur ou un changement d’appareil peut supprimer les données.", + "Surveillez le quota, surtout si vous ajoutez des screenshots.", + "Faites un export global régulier pour garder une sauvegarde." + ] + }, + emptyTitle: "Aucune toolbox", + emptyText: "Créez une première toolbox pour stocker vos outils dans ce navigateur." + } +}; + +const app = document.querySelector("#app"); +const MHWILDS_IMG_PATH = "/static/img/mhwilds"; +const siteState = { + loaded: false, + loading: false, + content: DEFAULT_SITE_CONTENT +}; +const gamesState = { + loaded: false, + loading: false, + error: "", + games: [] +}; +const mhwildsState = { + loaded: false, + loading: false, + error: "", + translations: {}, + monsters: [], + endemic: [], + filters: { + monsters: { name: "", weaknesses: [], logic: "and" }, + endemic: { name: "", locations: [], logic: "and" } + } +}; +let draggedModule = null; + +const ID_PREFIXES = { + tbx: "t", + mod: "m", + item: "i", + shot: "s" +}; +const ID_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789"; + +function randomToken(length = 6) { + const bytes = new Uint8Array(length); + if (globalThis.crypto?.getRandomValues) { + globalThis.crypto.getRandomValues(bytes); + return [...bytes].map((byte) => ID_ALPHABET[byte % ID_ALPHABET.length]).join(""); + } + + return Math.random().toString(36).slice(2, 2 + length).padEnd(length, "0"); +} + +function uid(prefix) { + return `${ID_PREFIXES[prefix] || "x"}${randomToken()}`; +} + +function readJson(key, fallback) { + try { + const value = localStorage.getItem(key); + return value ? JSON.parse(value) : fallback; + } catch { + return fallback; + } +} + +function writeJson(key, value) { + return writeStorageValue(key, JSON.stringify(value)); +} + +function stringStorageBytes(value) { + return String(value || "").length * 2; +} + +function getAppStorageUsage(projected = {}) { + let used = 0; + for (let index = 0; index < localStorage.length; index += 1) { + const key = localStorage.key(index); + if (!key?.startsWith(APP_STORAGE_PREFIX) || Object.prototype.hasOwnProperty.call(projected, key)) continue; + used += stringStorageBytes(key) + stringStorageBytes(localStorage.getItem(key)); + } + + Object.entries(projected).forEach(([key, value]) => { + if (!key.startsWith(APP_STORAGE_PREFIX) || value == null) return; + used += stringStorageBytes(key) + stringStorageBytes(value); + }); + + return { + used, + limit: APP_STORAGE_LIMIT_BYTES, + ratio: Math.min(1, used / APP_STORAGE_LIMIT_BYTES) + }; +} + +function formatBytes(bytes) { + if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1).replace(".", ",")} Mio`; + if (bytes >= 1024) return `${Math.ceil(bytes / 1024)} Kio`; + return `${bytes} o`; +} + +function renderStorageQuota() { + const usage = getAppStorageUsage(); + const percent = Math.round(usage.ratio * 100); + const state = usage.ratio >= 1 ? "danger" : usage.ratio >= APP_STORAGE_WARNING_RATIO ? "warning" : "ok"; + + return ` +
+
+ Stockage local + ${formatBytes(usage.used)} / ${formatBytes(usage.limit)} +
+
+ +
+
+ `; +} + +function refreshStorageQuotaIndicators() { + document.querySelectorAll(".storage-quota").forEach((quota) => { + quota.outerHTML = renderStorageQuota(); + }); +} + +function showStorageQuotaModal(usage) { + if (document.querySelector(".storage-quota-modal")) return; + queueMicrotask(() => { + showConfirmModal({ + title: "Quota local atteint", + message: `Sokko G utilise ${formatBytes(usage.used)} sur ${formatBytes(usage.limit)}. Supprimez des outils, des screenshots ou des toolboxes avant d'ajouter de nouvelles données.`, + confirmLabel: "Compris", + cancelLabel: "Fermer", + danger: true, + className: "storage-quota-modal" + }); + }); +} + +function writeStorageValue(key, value) { + const projectedUsage = getAppStorageUsage({ [key]: value }); + if (projectedUsage.used > APP_STORAGE_LIMIT_BYTES) { + showStorageQuotaModal(projectedUsage); + return false; + } + + try { + localStorage.setItem(key, value); + if (key !== STORAGE_KEYS.drawerWidth) refreshStorageQuotaIndicators(); + return true; + } catch (error) { + if (error?.name === "QuotaExceededError" || error?.code === 22) { + showStorageQuotaModal(projectedUsage); + return false; + } + throw error; + } +} + +function getDefaultModuleTitle(type) { + return MODULES[type]?.label || "Outil"; +} + +function normalizeToolboxModule(module) { + if (!module || typeof module !== "object" || !module.type) return null; + const title = String(module.title || "").trim(); + const normalized = { + id: module.id || uid("mod"), + type: module.type + }; + + if (title && title !== getDefaultModuleTitle(module.type)) { + normalized.title = title; + } + + return normalized; +} + +function normalizeToolbox(toolbox) { + if (!toolbox || typeof toolbox !== "object") return null; + return { + id: toolbox.id || uid("tbx"), + name: String(toolbox.name || "Nouvelle toolbox").trim() || "Nouvelle toolbox", + moduleColumns: Number(toolbox.moduleColumns) === 1 ? 1 : 2, + modules: (Array.isArray(toolbox.modules) ? toolbox.modules : []).map(normalizeToolboxModule).filter(Boolean), + updatedAt: toolbox.updatedAt || new Date().toISOString() + }; +} + +function normalizeToolboxes(toolboxes) { + return (Array.isArray(toolboxes) ? toolboxes : []).map(normalizeToolbox).filter(Boolean); +} + +function compactToolboxForStorage(toolbox) { + const normalized = normalizeToolbox(toolbox); + if (!normalized) return null; + + const compact = { + id: normalized.id, + name: normalized.name, + modules: normalized.modules, + updatedAt: normalized.updatedAt + }; + + if (normalized.moduleColumns === 1) compact.moduleColumns = 1; + + return compact; +} + +function compactToolboxesForStorage(toolboxes) { + return (Array.isArray(toolboxes) ? toolboxes : []).map(compactToolboxForStorage).filter(Boolean); +} + +function getToolboxes() { + return normalizeToolboxes(readJson(STORAGE_KEYS.registry, [])); +} + +function saveToolboxes(toolboxes) { + return writeJson(STORAGE_KEYS.registry, compactToolboxesForStorage(toolboxes)); +} + +function getLinks() { + return readJson(STORAGE_KEYS.links, {}); +} + +function saveLinks(links) { + return writeJson(STORAGE_KEYS.links, links); +} + +function moduleStorageKey(toolboxId, moduleId) { + return `sokkog:toolbox:${toolboxId}:module:${moduleId}`; +} + +function globalModuleKey(toolboxId, moduleId) { + return `${toolboxId}:${moduleId}`; +} + +function downloadJson(payload, filename) { + const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" }); + const link = document.createElement("a"); + link.href = URL.createObjectURL(blob); + link.download = filename; + link.click(); + URL.revokeObjectURL(link.href); +} + +function getModuleData(toolboxId, moduleId, fallback) { + const value = readJson(moduleStorageKey(toolboxId, moduleId), undefined); + return value == null ? fallback : value; +} + +function getToolboxModule(toolboxId, moduleId) { + return getToolboxes() + .find((toolbox) => toolbox.id === toolboxId) + ?.modules.find((module) => module.id === moduleId); +} + +function compactChecklistItemForStorage(item) { + const normalized = normalizeChecklistItem(item); + const compact = { + id: normalized.id, + label: normalized.label + }; + + if (normalized.qtyTarget !== 1) compact.qtyTarget = normalized.qtyTarget; + if (normalized.qtyCurrent !== 0) compact.qtyCurrent = normalized.qtyCurrent; + + return compact; +} + +function compactModuleDataForStorage(type, value) { + if (type === "notepad") { + const text = String(value?.text || ""); + return text ? { text } : null; + } + + if (type === "checklist") { + const items = normalizeChecklistData(value).items.map(compactChecklistItemForStorage); + return items.length ? { items } : null; + } + + if (type === "screenshots") { + const shots = (Array.isArray(value?.shots) ? value.shots : []) + .filter((shot) => shot?.dataUrl) + .map((shot) => ({ + id: shot.id || uid("shot"), + dataUrl: shot.dataUrl + })); + return shots.length ? { shots } : null; + } + + return value; +} + +function createExportIdFactory() { + const counts = {}; + return (prefix) => { + counts[prefix] = (counts[prefix] || 0) + 1; + return `${ID_PREFIXES[prefix] || "x"}${counts[prefix].toString(36)}`; + }; +} + +function remapModuleDataForExport(type, data, nextId) { + const compact = compactModuleDataForStorage(type, data); + if (!compact) return null; + + if (type === "checklist") { + return { + items: compact.items.map((item) => ({ + ...item, + id: nextId("item") + })) + }; + } + + if (type === "screenshots") { + return { + shots: compact.shots.map((shot) => ({ + ...shot, + id: nextId("shot") + })) + }; + } + + return compact; +} + +function createToolboxExportPayload(toolbox) { + const source = normalizeToolbox(toolbox); + const nextId = createExportIdFactory(); + const moduleIdMap = new Map(); + const exportedToolbox = compactToolboxForStorage({ + ...source, + id: nextId("tbx"), + modules: source.modules.map((module) => { + const id = nextId("mod"); + moduleIdMap.set(module.id, id); + return { ...module, id }; + }) + }); + const modules = Object.fromEntries(source.modules + .map((module) => [ + moduleIdMap.get(module.id), + remapModuleDataForExport(module.type, getModuleData(source.id, module.id, null), nextId) + ]) + .filter(([, data]) => data != null)); + + return { toolbox: exportedToolbox, modules }; +} + +function createGlobalExportPayload() { + const toolboxes = getToolboxes(); + const modules = {}; + + toolboxes.forEach((toolbox) => { + toolbox.modules.forEach((module) => { + const data = compactModuleDataForStorage(module.type, getModuleData(toolbox.id, module.id, null)); + if (data) modules[globalModuleKey(toolbox.id, module.id)] = data; + }); + }); + + return { + version: 1, + exportedAt: new Date().toISOString(), + toolboxes: compactToolboxesForStorage(toolboxes), + modules, + links: getLinks() + }; +} + +function setModuleData(toolboxId, moduleId, value) { + const module = getToolboxModule(toolboxId, moduleId); + const compact = compactModuleDataForStorage(module?.type, value); + const key = moduleStorageKey(toolboxId, moduleId); + + if (compact == null) { + localStorage.removeItem(key); + refreshStorageQuotaIndicators(); + return true; + } + + return writeJson(key, compact); +} + +function parsePositiveInt(value, fallback = 1) { + const parsed = Number.parseInt(value, 10); + return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; +} + +function clampQty(value, target) { + const parsed = Number.parseInt(value, 10); + const safeValue = Number.isFinite(parsed) ? parsed : 0; + return Math.min(Math.max(safeValue, 0), Math.max(1, target)); +} + +function normalizeChecklistItem(item) { + const qtyTarget = Math.max(1, parsePositiveInt(item?.qtyTarget, 1)); + const qtyCurrent = clampQty(item?.qtyCurrent, qtyTarget); + + return { + id: item?.id || uid("item"), + label: String(item?.label || "").trim(), + qtyTarget, + qtyCurrent + }; +} + +function normalizeChecklistData(data) { + return { + items: (data?.items || []) + .map(normalizeChecklistItem) + .filter((item) => item.label) + }; +} + +function isChecklistItemDone(item) { + return clampQty(item.qtyCurrent, item.qtyTarget) >= item.qtyTarget; +} + +function getGames() { + return gamesState.games; +} + +function getGame(gameId) { + return getGames().find((item) => item.id === gameId); +} + +function getToolboxGameId(toolbox) { + const links = getLinks(); + return Object.entries(links).find(([, toolboxId]) => toolboxId === toolbox?.id)?.[0] || ""; +} + +function getToolboxGame(toolbox) { + return getGame(getToolboxGameId(toolbox)); +} + +async function loadGames() { + if (gamesState.loaded || gamesState.loading) return; + gamesState.loading = true; + gamesState.error = ""; + + try { + const response = await fetch("/data/games.json"); + if (!response.ok) throw new Error("Impossible de charger la liste des jeux."); + + const payload = await response.json(); + gamesState.games = Array.isArray(payload.games) ? payload.games : []; + gamesState.loaded = true; + } catch (error) { + gamesState.error = error.message; + gamesState.games = []; + gamesState.loaded = true; + } finally { + gamesState.loading = false; + } +} + +async function loadSiteContent() { + if (siteState.loaded || siteState.loading) return; + siteState.loading = true; + + try { + const response = await fetch("/data/site.json"); + if (!response.ok) throw new Error("Impossible de charger le contenu du site."); + + const payload = await response.json(); + siteState.content = mergeContent(DEFAULT_SITE_CONTENT, payload); + } catch { + siteState.content = DEFAULT_SITE_CONTENT; + } finally { + siteState.loaded = true; + siteState.loading = false; + } +} + +function getDrawerWidth() { + const value = Number(localStorage.getItem(STORAGE_KEYS.drawerWidth)); + if (!Number.isFinite(value) || value <= 0) return ""; + + const maxWidth = Math.floor(window.innerWidth * 0.94); + return Math.min(Math.max(value, 360), maxWidth); +} + +function escapeHtml(value) { + return String(value).replace(/[&<>"']/g, (char) => ({ + "&": "&", + "<": "<", + ">": ">", + "\"": """, + "'": "'" + })[char]); +} + +function escapeAttr(value) { + return escapeHtml(value).replace(/`/g, "`"); +} + +function assetPath(name) { + return encodeURI(`${MHWILDS_IMG_PATH}/${name}.png`); +} + +function t(key, { capitalize = false } = {}) { + const value = mhwildsState.translations[key] || key; + if (!capitalize) return value; + return value.charAt(0).toUpperCase() + value.slice(1); +} + +function mergeContent(defaults, overrides) { + if (!overrides || typeof overrides !== "object" || Array.isArray(overrides)) return defaults; + return Object.fromEntries(Object.entries(defaults).map(([key, value]) => { + const override = overrides[key]; + if (Array.isArray(value)) return [key, Array.isArray(override) ? override : value]; + if (value && typeof value === "object") return [key, mergeContent(value, override)]; + return [key, override ?? value]; + })); +} + +function siteContent() { + return siteState.content; +} + +function formatContentText(value) { + return escapeHtml(value).replace(/<i>(.+?)<\/i>/g, "$1").replace(/\n/g, "
"); +} + +function formatDate(value) { + return new Intl.DateTimeFormat("fr-FR", { dateStyle: "medium", timeStyle: "short" }).format(new Date(value)); +} + +function navigate(path) { + location.hash = path; +} + +function currentRoute() { + const hashRoute = location.hash.replace(/^#/, ""); + if (hashRoute) return hashRoute; + + const path = location.pathname.replace(/\/+$/, "") || "/"; + if (path === "/mhwilds") return "/games/mhwilds"; + if (path === "/mhwilds/monsters") return "/games/mhwilds/monsters"; + if (path === "/mhwilds/endemic") return "/games/mhwilds/endemic"; + if (path === "/toolboxes") return "/toolboxes"; + if (path === "/games") return "/games"; + if (path === "/games/mhwilds") return "/games/mhwilds"; + if (path === "/games/mhwilds/monsters") return "/games/mhwilds/monsters"; + if (path === "/games/mhwilds/endemic") return "/games/mhwilds/endemic"; + + return "/"; +} + +function getRouteGameId(route) { + const [, section, gameId] = route.split("/"); + return section === "games" ? gameId || "" : ""; +} + +function renderTopbarActions(route) { + const gameId = getRouteGameId(route); + const game = getGame(gameId); + if (!game) return ""; + + const links = getLinks(); + const linkedToolbox = getToolboxes().find((item) => item.id === links[game.id]); + const label = linkedToolbox + ? `Ouvrir la toolbox ${linkedToolbox.name}` + : "Associer une toolbox"; + + return ` +
+ +
+ `; +} + +function shell(content) { + const route = currentRoute(); + const contentConfig = siteContent(); + const topbarLabel = route.startsWith("/toolbox") + ? contentConfig.topbar.toolbox + : route.startsWith("/games") + ? contentConfig.topbar.games + : contentConfig.topbar.dashboard; + app.innerHTML = ` +
+ +
+
+
+ ${escapeHtml(topbarLabel)} +
+ ${renderTopbarActions(route)} +
+
${content}
+
+ +
+ `; +} + +function createToolbox({ name }) { + const now = new Date().toISOString(); + const toolbox = { + id: uid("tbx"), + name: name.trim() || "Nouvelle toolbox", + updatedAt: now, + modules: [ + { id: uid("mod"), type: "notepad", title: "Notes rapides" }, + { id: uid("mod"), type: "checklist" } + ] + }; + return saveToolboxes([toolbox, ...getToolboxes()]) ? toolbox : null; +} + +function updateToolbox(toolbox) { + toolbox.updatedAt = new Date().toISOString(); + return saveToolboxes(getToolboxes().map((item) => item.id === toolbox.id ? toolbox : item)); +} + +function moveToolboxModule(toolboxId, fromModuleId, toModuleId, placement = "before") { + if (!fromModuleId || !toModuleId || fromModuleId === toModuleId) return false; + + const toolbox = getToolboxes().find((item) => item.id === toolboxId); + if (!toolbox) return false; + + const modules = [...toolbox.modules]; + const fromIndex = modules.findIndex((module) => module.id === fromModuleId); + const toIndex = modules.findIndex((module) => module.id === toModuleId); + if (fromIndex < 0 || toIndex < 0) return false; + + const [moved] = modules.splice(fromIndex, 1); + const targetIndex = modules.findIndex((module) => module.id === toModuleId); + const insertIndex = placement === "after" ? targetIndex + 1 : targetIndex; + modules.splice(insertIndex, 0, moved); + toolbox.modules = modules; + updateToolbox(toolbox); + return true; +} + +function deleteToolbox(id) { + const toolbox = getToolboxes().find((item) => item.id === id); + if (toolbox) { + toolbox.modules.forEach((module) => localStorage.removeItem(moduleStorageKey(id, module.id))); + } + saveToolboxes(getToolboxes().filter((item) => item.id !== id)); + const links = getLinks(); + Object.entries(links).forEach(([gameId, toolboxId]) => { + if (toolboxId === id) delete links[gameId]; + }); + saveLinks(links); +} + +function linkToolboxToGame(gameId, toolboxId) { + const previousLinks = getLinks(); + const previousToolboxId = previousLinks[gameId] || ""; + const links = getLinks(); + if (toolboxId) links[gameId] = toolboxId; + else delete links[gameId]; + saveLinks(links); + + const touchedToolboxIds = new Set([previousToolboxId, toolboxId].filter(Boolean)); + const now = new Date().toISOString(); + const nextToolboxes = getToolboxes().map((toolbox) => { + return touchedToolboxIds.has(toolbox.id) ? { ...toolbox, updatedAt: now } : toolbox; + }); + saveToolboxes(nextToolboxes); +} + +function renderHome() { + const toolboxes = getToolboxes(); + const toolCount = Object.keys(MODULES).length; + const content = siteContent().home; + const dialogue = content.origin.lines.map((line) => ` +

${formatContentText(line.text || "")}

+ `).join(""); + + shell(` +
+
+

${escapeHtml(content.hero.eyebrow)}

+

${escapeHtml(content.hero.title)}

+

${escapeHtml(content.hero.description)}

+ +
+
+
+ ${toolboxes.length} + ${escapeHtml(toolboxes.length > 1 ? content.stats.toolboxPlural : content.stats.toolboxSingular)} +
+
+ ${toolCount} + ${escapeHtml(toolCount > 1 ? content.stats.toolPlural : content.stats.toolSingular)} +
+
+
+
+
+

${escapeHtml(content.origin.eyebrow)}

+

${escapeHtml(content.origin.title)}

+
+ ${dialogue} + ${escapeHtml(content.origin.caption)} +
+
+
+ ${escapeAttr(content.origin.visualAlt)} +
+
+ `); +} + +function renderToolboxes() { + const toolboxes = getToolboxes(); + const content = siteContent().toolboxes; + shell(` +
+
+
+

${escapeHtml(content.eyebrow)}

+

${escapeHtml(content.title)}

+
+
+ + +
+
+
+
+ + +
+
+
+ ${storageHelpCard(content.storageHelp)} + ${toolboxes.length ? toolboxes.map(toolboxCard).join("") : ` +
+

${escapeHtml(content.emptyTitle)}

+

${escapeHtml(content.emptyText)}

+
+ `} +
+ ${renderStorageQuota()} +
+ `); +} + +function storageHelpCard(help) { + return ` +
+
+
+ +
+

LocalStorage

+

${escapeHtml(help.title)}

+
+
+

${escapeHtml(help.text)}

+
    + ${help.items.map((item, index) => ` +
  • + ${index + 1} +

    ${escapeHtml(item)}

    +
  • + `).join("")} +
+
+
+ `; +} + +function toolboxCard(toolbox) { + const game = getToolboxGame(toolbox); + return ` +
+
+ ${game?.image ? `${escapeAttr(game.title)}` : ""} +
+
+

${game ? escapeHtml(game.title) : "Toolbox libre"}

+

${escapeHtml(toolbox.name)}

+ Modifiée le ${formatDate(toolbox.updatedAt)} +
+ + + + + +
+
+
+ `; +} + +function renderToolbox(id, { embedded = false } = {}) { + const toolbox = getToolboxes().find((item) => item.id === id); + if (!toolbox) { + if (embedded) return `

Toolbox introuvable.

`; + shell(`

Toolbox introuvable

Retour
`); + return; + } + + const moduleColumns = Number(toolbox.moduleColumns) === 1 ? 1 : 2; + const content = ` +
+
+
+

Toolbox

+ ${embedded ? `

${escapeHtml(toolbox.name)}

` : ` +

${escapeHtml(toolbox.name)}

+ `} +
+
+ +
+
+ ${embedded ? "" : ` +
+ ${renderModuleLayoutSwitch(toolbox, moduleColumns)} +
+ `} +
+ ${renderToolboxModules(toolbox, moduleColumns)} +
+ ${embedded ? "" : renderStorageQuota()} +
+ `; + + if (embedded) return content; + shell(content); +} + +function renderModuleLayoutSwitch(toolbox, moduleColumns) { + return ` +
+ + +
+ `; +} + +function renderToolboxModules(toolbox, moduleColumns) { + if (moduleColumns === 1) { + return toolbox.modules.map((module) => renderModule(toolbox, module)).join(""); + } + + const columns = [[], []]; + toolbox.modules.forEach((module, index) => { + columns[index % 2].push(module); + }); + + return columns.map((modules) => ` +
+ ${modules.map((module) => renderModule(toolbox, module)).join("")} +
+ `).join(""); +} + +function renderModule(toolbox, module) { + const label = MODULES[module.type]?.label || module.type; + return ` +
+
+
+ + +

${escapeHtml(module.title || label)}

+
+
+ +
+
+ ${module.type === "notepad" ? renderNotepad(toolbox.id, module.id) : ""} + ${module.type === "checklist" ? renderChecklist(toolbox.id, module.id) : ""} + ${module.type === "screenshots" ? renderScreenshots(toolbox.id, module.id) : ""} +
+ `; +} + +function renderNotepad(toolboxId, moduleId) { + const data = getModuleData(toolboxId, moduleId, { text: "" }); + return ``; +} + +function renderChecklist(toolboxId, moduleId) { + const data = normalizeChecklistData(getModuleData(toolboxId, moduleId, { items: [] })); + return ` +
+ + + +
+ + `; +} + +function renderChecklistItem(toolboxId, moduleId, item) { + const done = isChecklistItemDone(item); + return ` +
  • +
    + ${item.qtyTarget === 1 ? ` + + ` : ` +
    + + ${clampQty(item.qtyCurrent, item.qtyTarget)}/${item.qtyTarget} + +
    + `} + ${escapeHtml(item.label)} +
    + +
  • + `; +} + +function renderScreenshots(toolboxId, moduleId) { + const data = getModuleData(toolboxId, moduleId, { shots: [] }); + return ` + +
    Coller une image ici
    +
    + ${data.shots.map((shot) => ` +
    + + +
    + `).join("")} +
    + `; +} + +function renderGames() { + const games = getGames(); + + shell(` +
    +
    +

    Pages informatives

    +

    Jeux disponibles

    +

    Choisissez un jeu pour consulter ses données maintenues et associer une toolbox locale.

    +
    +
    +
    + ${games.length ? games.map((game) => ` +
    +
    + ${escapeAttr(game.title)} +
    +
    +

    ${escapeHtml(game.eyebrow || "Guide de jeu")}

    +

    ${escapeHtml(game.title)}

    +

    ${escapeHtml(game.summary)}

    +
    + Ouvrir +
    +
    +
    + `).join("") : ` +
    +

    Aucun jeu disponible

    +

    ${escapeHtml(gamesState.error || "Ajoutez des entrées dans /data/games.json.")}

    +
    + `} +
    + `); +} + +function renderGame(gameId) { + const game = getGame(gameId); + if (!game) { + renderGames(); + return; + } + + if (game.id === "mhwilds") { + renderMhwilds(currentRoute().split("/")[3] || ""); + return; + } + + shell(` +
    +
    +

    ${escapeHtml(game.eyebrow || "Guide de jeu")}

    +

    ${escapeHtml(game.title)}

    +

    ${escapeHtml(game.summary)}

    +
    +
    +
    + ${game.sections.map((section) => ` +
    +

    ${escapeHtml(section.title)}

    +
      ${section.items.map((item) => `
    • ${escapeHtml(item)}
    • `).join("")}
    +
    + `).join("")} +
    + + `); +} + +async function loadMhwildsData() { + if (mhwildsState.loaded || mhwildsState.loading) return; + mhwildsState.loading = true; + mhwildsState.error = ""; + + try { + const [monstersResponse, endemicResponse, translationsResponse] = await Promise.all([ + fetch("/data/mhwilds/monsters.json"), + fetch("/data/mhwilds/endemic_life.json"), + fetch("/data/mhwilds/i18n/fr.json") + ]); + + if (!monstersResponse.ok || !endemicResponse.ok || !translationsResponse.ok) { + throw new Error("Impossible de charger les données Monster Hunter Wilds."); + } + + const [monstersJson, endemicJson, translations] = await Promise.all([ + monstersResponse.json(), + endemicResponse.json(), + translationsResponse.json() + ]); + + mhwildsState.monsters = monstersJson.monsters || []; + mhwildsState.endemic = [ + ...(endemicJson.endemicLife || []), + ...(endemicJson.aquaticLife || []) + ]; + mhwildsState.translations = translations; + mhwildsState.loaded = true; + } catch (error) { + mhwildsState.error = error.message; + } finally { + mhwildsState.loading = false; + } +} + +function renderMhwilds(category) { + const activeCategory = category === "monsters" || category === "endemic" ? category : ""; + + if (!mhwildsState.loaded) { + shell(` +
    +
    +

    Monster Hunter Wilds

    +

    Monster Hunter: Wilds

    +

    Chargement des données de chasse, faune endémique et filtres associés.

    +
    +
    +

    Chargement

    Préparation des données locales...

    + `); + loadMhwildsData().then(rerender); + return; + } + + if (mhwildsState.error) { + shell(` +
    +

    Impossible de charger MH Wilds

    +

    ${escapeHtml(mhwildsState.error)}

    + +
    + `); + return; + } + + if (!activeCategory) { + renderMhwildsOverview(); + return; + } + + renderMhwildsListing(activeCategory); +} + +function renderMhwildsOverview() { + shell(` +
    +
    +

    Guide de jeu

    +

    Monster Hunter: Wilds

    +

    Consultez rapidement les monstres, faiblesses, afflictions, hitzones et emplacements de faune endémique.

    +
    +
    +
    + + + Monstres + Recherche, filtres par faiblesse et tableau de dégâts par partie. + + + + Faune endémique + Faune endémique et aquatique filtrable par localisation. + +
    + + `); +} + +function renderMhwildsListing(category) { + const isMonsters = category === "monsters"; + const label = isMonsters ? t("monsters", { capitalize: true }) : t("endemic life", { capitalize: true }); + const filterKey = isMonsters ? "weaknesses" : "locations"; + const options = getUniqueConditionValues(isMonsters ? mhwildsState.monsters : mhwildsState.endemic, filterKey); + const total = isMonsters ? mhwildsState.monsters.length : mhwildsState.endemic.length; + const visible = getFilteredMhwildsItems(category).length; + + shell(` +
    +
    +

    Monster Hunter Wilds

    +
    +

    ${escapeHtml(label)}

    + ${visible} / ${total} +
    +

    ${isMonsters ? "Filtrez les monstres par nom et faiblesses, puis consultez leurs dégâts détaillés." : "Filtrez la faune par nom et zones d'apparition."}

    +
    +
    + Monstres + Faune +
    +
    +
    + +
    + ${renderMhwildsResults(category)} +
    +
    + + `); +} + +function renderMhwildsFilters(category, filterKey, options) { + const filters = mhwildsState.filters[category]; + const selected = new Set(filters[filterKey]); + const logicLabel = filters.logic === "or" ? t("or", { capitalize: true }) : t("and", { capitalize: true }); + + return ` +
    +
    +
    +

    Filtres

    +

    ${category === "monsters" ? t("weaknesses", { capitalize: true }) : t("locations", { capitalize: true })}

    +
    + +
    + +
    + Correspondance + +
    +
    + ${options.map((option) => ` + + `).join("")} +
    +
    + `; +} + +function renderMhwildsResults(category) { + const items = getFilteredMhwildsItems(category); + + return ` +
    + ${items.length ? items.map((item) => category === "monsters" ? renderMonsterCard(item) : renderEndemicCard(item)).join("") : ` +
    +

    Aucun résultat

    +

    Ajustez la recherche ou réinitialisez les filtres actifs.

    +
    + `} +
    + `; +} + +function refreshMhwildsResults(category) { + const results = document.querySelector(".mhwilds-results"); + if (results) results.innerHTML = renderMhwildsResults(category); + const count = document.querySelector("[data-mhwilds-count]"); + if (count) { + const total = category === "monsters" ? mhwildsState.monsters.length : mhwildsState.endemic.length; + count.textContent = `${getFilteredMhwildsItems(category).length} / ${total}`; + } +} + +function renderMonsterCard(monster) { + const weaknesses = getConditionValues(monster.weaknesses); + const ailments = getConditionValues(monster.ailments).filter((value) => value !== "none"); + + return ` +
    +
    +
    +
    + ${escapeAttr(t(monster.name, { capitalize: true }))} +
    +
    +

    ${escapeHtml(t(monster.type, { capitalize: true }))}

    +

    ${escapeHtml(t(monster.name, { capitalize: true }))}

    + ${renderIconRow(t("weaknesses", { capitalize: true }), weaknesses)} + ${renderIconRow(t("ailments", { capitalize: true }), ailments.length ? ailments : ["none"])} +
    +
    +
    +
    +

    Détails

    +

    ${escapeHtml(t(monster.name, { capitalize: true }))}

    +
    + ${renderDamageTable(monster.damage || [])} +
    +
    +
    + `; +} + +function renderEndemicCard(item) { + const locations = getConditionValues(item.locations); + const description = t(item.description); + const hasDescription = normalizeText(description) && normalizeText(description) !== normalizeText("à compléter"); + + return ` +
    +
    + ${escapeAttr(t(item.name, { capitalize: true }))} +
    +
    +

    ${escapeHtml(t(item.name, { capitalize: true }))}

    + ${renderIconRow(t("locations", { capitalize: true }), locations)} + ${hasDescription ? `

    ${escapeHtml(description)}

    ` : ""} +
    +
    + `; +} + +function renderIconRow(label, values) { + return ` +
    + ${escapeHtml(label)} +
    + ${values.map((value) => value === "none" ? `-` : ` + ${escapeAttr(t(value, { capitalize: true }))} + `).join("")} +
    +
    + `; +} + +function renderDamageTable(rows) { + if (!rows.length) return ""; + const columns = Object.keys(rows[0]); + return ` +
    + + + + ${columns.map((column) => ` + + `).join("")} + + + + ${rows.map((row) => ` + + ${columns.map((column) => column === "name" ? ` + + ` : ` + + `).join("")} + + `).join("")} + +
    ${column === "name" ? "" : `${escapeAttr(t(column, { capitalize: true }))}`}
    ${escapeHtml(t(row[column], { capitalize: true }))}${row[column]} étoiles
    +
    + `; +} + +function getFilteredMhwildsItems(category) { + const items = category === "monsters" ? mhwildsState.monsters : mhwildsState.endemic; + const filters = mhwildsState.filters[category]; + const filterKey = category === "monsters" ? "weaknesses" : "locations"; + const selected = filters[filterKey]; + const search = normalizeText(filters.name); + + return items.filter((item) => { + const nameMatches = !search || normalizeText(t(item.name)).includes(search) || normalizeText(item.name).includes(search); + if (!nameMatches) return false; + if (!selected.length) return true; + + const values = getConditionValues(item[filterKey]); + if (filters.logic === "or" && selected.length >= 2) { + return selected.some((value) => values.includes(value)); + } + + return selected.every((value) => values.includes(value)); + }); +} + +function getConditionValues(conditions = []) { + return [...new Set(conditions.flatMap((condition) => condition.values || []))]; +} + +function getUniqueConditionValues(items, property) { + return [...new Set(items.flatMap((item) => getConditionValues(item[property])))] + .filter((value) => value !== "none") + .sort((a, b) => t(a).localeCompare(t(b), "fr")); +} + +function normalizeText(value) { + return String(value || "").trim().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\s+/g, " "); +} + +function openDrawer(gameId) { + const drawer = document.querySelector("#toolbox-drawer"); + const toolboxes = getToolboxes(); + const links = getLinks(); + const selectedId = links[gameId] || ""; + const drawerWidth = getDrawerWidth(); + drawer.innerHTML = ` +
    +
    +
    + +
    +
    +

    Toolbox liée

    +
    + + + +
    +
    + +
    + ${selectedId ? renderToolbox(selectedId, { embedded: true }) : ` +
    +

    Aucune toolbox associée à cette page jeu.

    + +
    + `} + ${renderStorageQuota()} +
    +
    + `; + drawer.dataset.gameId = gameId; + drawer.setAttribute("aria-hidden", "false"); +} + +function closeDrawer() { + const drawer = document.querySelector("#toolbox-drawer"); + if (drawer) { + drawer.setAttribute("aria-hidden", "true"); + drawer.innerHTML = ""; + delete drawer.dataset.gameId; + } +} + +function refreshToolboxView(sourceElement = null) { + const drawer = sourceElement?.closest?.("#toolbox-drawer"); + if (drawer?.dataset.gameId) { + openDrawer(drawer.dataset.gameId); + return; + } + + rerender(); +} + +function showToolboxCreateModal({ gameId = "" } = {}) { + return new Promise((resolve) => { + const host = document.createElement("div"); + host.className = "confirm-modal-root"; + host.innerHTML = ` +
    + + `; + + const previousFocus = document.activeElement; + document.body.appendChild(host); + const input = host.querySelector("input[name='name']"); + + function close(result) { + host.remove(); + document.removeEventListener("keydown", onKeydown); + previousFocus?.focus?.(); + resolve(result); + } + + function onKeydown(event) { + if (event.key === "Escape") close(null); + } + + host.addEventListener("click", (event) => { + if (event.target.closest("[data-toolbox-create-result='cancel']")) close(null); + }); + + host.querySelector("form").addEventListener("submit", (event) => { + event.preventDefault(); + const name = new FormData(event.currentTarget).get("name")?.trim(); + if (!name) return; + + const toolbox = createToolbox({ name }); + if (!toolbox) return; + if (gameId) { + const links = getLinks(); + links[gameId] = toolbox.id; + saveLinks(links); + } + close(toolbox); + }); + + document.addEventListener("keydown", onKeydown); + input.focus(); + }); +} + +function showToolboxLinkModal(gameId) { + return new Promise((resolve) => { + const toolboxes = getToolboxes(); + const selectedId = getLinks()[gameId] || ""; + const host = document.createElement("div"); + host.className = "confirm-modal-root"; + host.innerHTML = ` +
    + + `; + + const previousFocus = document.activeElement; + document.body.appendChild(host); + const select = host.querySelector("select"); + + function close(result) { + host.remove(); + document.removeEventListener("keydown", onKeydown); + previousFocus?.focus?.(); + resolve(result); + } + + function onKeydown(event) { + if (event.key === "Escape") close(null); + } + + host.addEventListener("click", (event) => { + if (event.target.closest("[data-toolbox-link-result='cancel']")) close(null); + }); + + host.querySelector("form").addEventListener("submit", (event) => { + event.preventDefault(); + close(new FormData(event.currentTarget).get("toolboxId") || ""); + }); + + document.addEventListener("keydown", onKeydown); + select.focus(); + }); +} + +async function compressImage(file) { + const bitmap = await createImageBitmap(file); + const maxSide = 1400; + const ratio = Math.min(1, maxSide / Math.max(bitmap.width, bitmap.height)); + const canvas = document.createElement("canvas"); + canvas.width = Math.round(bitmap.width * ratio); + canvas.height = Math.round(bitmap.height * ratio); + canvas.getContext("2d").drawImage(bitmap, 0, 0, canvas.width, canvas.height); + return canvas.toDataURL("image/jpeg", 0.78); +} + +async function addScreenshotFiles(toolboxId, moduleId, files) { + const imageFiles = [...files].filter((file) => file?.type?.startsWith("image/")); + if (!imageFiles.length) return false; + + const data = getModuleData(toolboxId, moduleId, { shots: [] }); + for (const file of imageFiles) { + data.shots.unshift({ + id: uid("shot"), + dataUrl: await compressImage(file) + }); + } + return setModuleData(toolboxId, moduleId, data); +} + +function exportToolbox(id) { + const toolbox = normalizeToolbox(getToolboxes().find((item) => item.id === id)); + if (!toolbox) return; + const payload = createToolboxExportPayload(toolbox); + downloadJson(payload, `${toolbox.name.toLowerCase().replace(/[^a-z0-9]+/gi, "-")}.sokkog.json`); +} + +function exportAllToolboxes() { + downloadJson(createGlobalExportPayload(), `sokkog-global-${new Date().toISOString().slice(0, 10)}.json`); +} + +async function importToolbox(file) { + const payload = JSON.parse(await file.text()); + if (!payload.toolbox?.modules) throw new Error("Format d'outils invalide"); + const imported = normalizeToolbox({ ...payload.toolbox, id: uid("tbx"), updatedAt: new Date().toISOString() }); + const moduleIdMap = new Map(); + imported.modules = imported.modules.map((module) => { + const nextId = uid("mod"); + moduleIdMap.set(module.id, nextId); + return { ...module, id: nextId }; + }); + saveToolboxes([imported, ...getToolboxes()]); + Object.entries(payload.modules || {}).forEach(([oldId, data]) => { + const nextId = moduleIdMap.get(oldId); + if (nextId) setModuleData(imported.id, nextId, data); + }); + return imported; +} + +async function importAllToolboxes(file) { + const payload = JSON.parse(await file.text()); + if (!Array.isArray(payload.toolboxes) || !payload.modules || typeof payload.modules !== "object") { + throw new Error("Format d'import global invalide"); + } + + const toolboxIdMap = new Map(); + const moduleIdMap = new Map(); + const projected = {}; + const importedToolboxes = payload.toolboxes.map((toolbox) => { + const nextToolboxId = uid("tbx"); + toolboxIdMap.set(toolbox.id, nextToolboxId); + const modules = (toolbox.modules || []).map((module) => { + const nextModuleId = uid("mod"); + moduleIdMap.set(globalModuleKey(toolbox.id, module.id), nextModuleId); + return { ...module, id: nextModuleId }; + }); + + return normalizeToolbox({ + ...toolbox, + id: nextToolboxId, + name: `${toolbox.name || "Toolbox"} (import)`, + modules, + updatedAt: new Date().toISOString() + }); + }); + + const nextToolboxes = [...importedToolboxes, ...getToolboxes()]; + const importedToolboxMap = new Map(importedToolboxes.map((toolbox) => [toolbox.id, toolbox])); + projected[STORAGE_KEYS.registry] = JSON.stringify(compactToolboxesForStorage(nextToolboxes)); + + Object.entries(payload.modules).forEach(([key, data]) => { + const [oldToolboxId] = key.split(":"); + const nextToolboxId = toolboxIdMap.get(oldToolboxId); + const nextModuleId = moduleIdMap.get(key); + const toolbox = importedToolboxMap.get(nextToolboxId); + const module = toolbox?.modules.find((item) => item.id === nextModuleId); + const compact = compactModuleDataForStorage(module?.type, data); + if (nextToolboxId && nextModuleId && compact) { + projected[moduleStorageKey(nextToolboxId, nextModuleId)] = JSON.stringify(compact); + } + }); + + const links = getLinks(); + Object.entries(payload.links || {}).forEach(([gameId, oldToolboxId]) => { + const nextToolboxId = toolboxIdMap.get(oldToolboxId); + if (nextToolboxId) links[gameId] = nextToolboxId; + }); + projected[STORAGE_KEYS.links] = JSON.stringify(links); + + const projectedUsage = getAppStorageUsage(projected); + if (projectedUsage.used > APP_STORAGE_LIMIT_BYTES) { + showStorageQuotaModal(projectedUsage); + return false; + } + + Object.entries(projected).forEach(([key, value]) => writeStorageValue(key, value)); + return true; +} + +function showConfirmModal({ title, message, confirmLabel = "Confirmer", cancelLabel = "Annuler", danger = false, className = "" }) { + return new Promise((resolve) => { + const host = document.createElement("div"); + host.className = ["confirm-modal-root", className].filter(Boolean).join(" "); + host.innerHTML = ` +
    + + `; + + const previousFocus = document.activeElement; + document.body.appendChild(host); + + const cancelButton = host.querySelector('[data-confirm-result="cancel"]'); + const confirmButton = host.querySelector('[data-confirm-result="confirm"]'); + + function close(result) { + host.remove(); + document.removeEventListener("keydown", onKeydown); + previousFocus?.focus?.(); + resolve(result); + } + + function onKeydown(event) { + if (event.key === "Escape") close(false); + if (event.key !== "Tab") return; + + const focusable = [cancelButton, confirmButton]; + const currentIndex = focusable.indexOf(document.activeElement); + const nextIndex = event.shiftKey + ? (currentIndex <= 0 ? focusable.length - 1 : currentIndex - 1) + : (currentIndex === focusable.length - 1 ? 0 : currentIndex + 1); + + event.preventDefault(); + focusable[nextIndex].focus(); + } + + host.addEventListener("click", (event) => { + const result = event.target.closest("[data-confirm-result]")?.dataset.confirmResult; + if (!result) return; + close(result === "confirm"); + }); + + document.addEventListener("keydown", onKeydown); + confirmButton.focus(); + }); +} + +function showScreenshotViewer(shot) { + const host = document.createElement("div"); + host.className = "screenshot-viewer-root"; + host.innerHTML = ` +
    + + `; + + document.body.appendChild(host); + + function close() { + host.remove(); + document.removeEventListener("keydown", onKeydown); + } + + function onKeydown(event) { + if (event.key === "Escape") close(); + } + + host.addEventListener("click", (event) => { + if (event.target.closest("[data-action='close-screenshot-viewer']")) close(); + }); + document.addEventListener("keydown", onKeydown); +} + +function rerender() { + if (!siteState.loaded) { + shell(` +
    +

    Chargement

    +

    Préparation du contenu...

    +
    + `); + loadSiteContent().then(rerender); + return; + } + + if (!gamesState.loaded) { + shell(` +
    +

    Chargement

    +

    Préparation des jeux disponibles...

    +
    + `); + loadGames().then(rerender); + return; + } + + const route = currentRoute(); + if (route === "/") renderHome(); + else if (route === "/toolboxes") renderToolboxes(); + else if (route.startsWith("/toolbox/")) renderToolbox(route.split("/")[2]); + else if (route === "/games") renderGames(); + else if (route.startsWith("/games/")) renderGame(route.split("/")[2]); + else navigate("/"); +} + +document.addEventListener("click", async (event) => { + const target = event.target.closest("[data-action]"); + if (!target) return; + const action = target.dataset.action; + + if (action === "flip-monster-card") { + target.classList.toggle("is-flipped"); + target.setAttribute("aria-pressed", target.classList.contains("is-flipped") ? "true" : "false"); + return; + } + + if (action === "new-toolbox") { + const toolbox = await showToolboxCreateModal(); + if (toolbox) navigate(`/toolbox/${toolbox.id}`); + } + if (action === "delete-toolbox") { + const toolbox = getToolboxes().find((item) => item.id === target.dataset.id); + const confirmed = await showConfirmModal({ + title: "Supprimer la toolbox", + message: `Supprimer "${toolbox?.name || "cette toolbox"}" et ses données locales ?`, + confirmLabel: "Supprimer", + danger: true + }); + + if (confirmed) { + deleteToolbox(target.dataset.id); + rerender(); + } + } + if (action === "export-toolbox") exportToolbox(target.dataset.id); + if (action === "export-all-toolboxes") exportAllToolboxes(); + if (action === "open-link-toolbox-modal") { + const toolboxId = await showToolboxLinkModal(target.dataset.gameId); + if (toolboxId == null) return; + linkToolboxToGame(target.dataset.gameId, toolboxId); + openDrawer(target.dataset.gameId); + } + if (action === "set-module-layout") { + const toolbox = getToolboxes().find((item) => item.id === target.dataset.id); + if (toolbox) { + toolbox.moduleColumns = Number(target.dataset.columns) === 1 ? 1 : 2; + updateToolbox(toolbox); + refreshToolboxView(target); + } + } + if (action === "view-screenshot") { + const data = getModuleData(target.dataset.toolboxId, target.dataset.moduleId, { shots: [] }); + const shot = data.shots.find((item) => item.id === target.dataset.shotId); + if (shot) showScreenshotViewer(shot); + } + if (action === "close-screenshot-viewer") { + target.closest(".screenshot-viewer-root")?.remove(); + } + if (action === "delete-module") { + const toolbox = getToolboxes().find((item) => item.id === target.dataset.toolboxId); + const module = toolbox?.modules.find((item) => item.id === target.dataset.moduleId); + const confirmed = await showConfirmModal({ + title: "Retirer l'outil", + message: `Retirer "${module?.title || "cet outil"}" de la toolbox ?`, + confirmLabel: "Retirer", + danger: true + }); + + if (confirmed && toolbox) { + toolbox.modules = toolbox.modules.filter((item) => item.id !== target.dataset.moduleId); + localStorage.removeItem(moduleStorageKey(toolbox.id, target.dataset.moduleId)); + updateToolbox(toolbox); + refreshToolboxView(target); + } + } + if (action === "open-drawer") openDrawer(target.dataset.gameId); + if (action === "close-drawer") closeDrawer(); + if (action === "new-toolbox-for-game") { + const toolbox = await showToolboxCreateModal({ gameId: target.dataset.gameId }); + if (toolbox) openDrawer(target.dataset.gameId); + } + if (action === "retry-mhwilds") { + mhwildsState.loaded = false; + mhwildsState.loading = false; + mhwildsState.error = ""; + rerender(); + } + if (action === "reset-mhwilds-filters") { + const category = target.dataset.category; + const filterKey = category === "monsters" ? "weaknesses" : "locations"; + mhwildsState.filters[category] = { name: "", [filterKey]: [], logic: "and" }; + rerender(); + } + if (action === "toggle-mhwilds-logic") { + const category = target.dataset.category; + const filters = mhwildsState.filters[category]; + filters.logic = filters.logic === "and" ? "or" : "and"; + rerender(); + } + if (action === "delete-check-item") { + const data = normalizeChecklistData(getModuleData(target.dataset.toolboxId, target.dataset.moduleId, { items: [] })); + data.items = data.items.filter((item) => item.id !== target.dataset.itemId); + setModuleData(target.dataset.toolboxId, target.dataset.moduleId, data); + refreshToolboxView(target); + } + if (action === "adjust-check-qty") { + const data = normalizeChecklistData(getModuleData(target.dataset.toolboxId, target.dataset.moduleId, { items: [] })); + const item = data.items.find((entry) => entry.id === target.dataset.itemId); + if (item) { + const delta = Number.parseInt(target.dataset.delta, 10) || 0; + item.qtyCurrent = clampQty(item.qtyCurrent + delta, item.qtyTarget); + setModuleData(target.dataset.toolboxId, target.dataset.moduleId, data); + refreshToolboxView(target); + } + } + if (action === "delete-screenshot") { + const data = getModuleData(target.dataset.toolboxId, target.dataset.moduleId, { shots: [] }); + data.shots = data.shots.filter((shot) => shot.id !== target.dataset.shotId); + setModuleData(target.dataset.toolboxId, target.dataset.moduleId, data); + refreshToolboxView(target); + } +}); + +document.addEventListener("pointerdown", (event) => { + const handle = event.target.closest(".drawer-resize-handle"); + if (!handle) return; + + const panel = handle.closest(".drawer-panel"); + if (!panel) return; + + event.preventDefault(); + const startX = event.clientX; + const startWidth = panel.getBoundingClientRect().width; + const minWidth = 360; + const maxWidth = Math.floor(window.innerWidth * 0.94); + + document.body.classList.add("is-resizing-drawer"); + + function resizeDrawer(moveEvent) { + const nextWidth = Math.min(Math.max(startWidth + startX - moveEvent.clientX, minWidth), maxWidth); + panel.style.width = `${nextWidth}px`; + writeStorageValue(STORAGE_KEYS.drawerWidth, String(Math.round(nextWidth))); + } + + function stopResize() { + document.body.classList.remove("is-resizing-drawer"); + window.removeEventListener("pointermove", resizeDrawer); + window.removeEventListener("pointerup", stopResize); + window.removeEventListener("pointercancel", stopResize); + } + + window.addEventListener("pointermove", resizeDrawer); + window.addEventListener("pointerup", stopResize); + window.addEventListener("pointercancel", stopResize); +}); + +document.addEventListener("focusin", (event) => { + const pasteTarget = event.target.closest("[data-action='paste-screenshot']"); + if (pasteTarget && pasteTarget.textContent.trim() === "Coller une image ici") { + pasteTarget.textContent = ""; + return; + } + + const title = event.target.closest("[data-action='edit-module-title'], [data-action='edit-toolbox-title']"); + if (!title) return; + + title.dataset.previousTitle = title.textContent.trim(); +}); + +document.addEventListener("focusout", (event) => { + const pasteTarget = event.target.closest("[data-action='paste-screenshot']"); + if (pasteTarget && !pasteTarget.textContent.trim()) { + pasteTarget.textContent = "Coller une image ici"; + return; + } + + const title = event.target.closest("[data-action='edit-module-title'], [data-action='edit-toolbox-title']"); + if (!title) return; + + if (title.dataset.action === "edit-toolbox-title") { + const toolbox = getToolboxes().find((item) => item.id === title.dataset.id); + if (!toolbox) return; + + const nextTitle = title.textContent.trim() || "Nouvelle toolbox"; + title.textContent = nextTitle; + + if (nextTitle !== toolbox.name) { + toolbox.name = nextTitle; + updateToolbox(toolbox); + } + return; + } + + const toolbox = getToolboxes().find((item) => item.id === title.dataset.toolboxId); + const module = toolbox?.modules.find((item) => item.id === title.dataset.moduleId); + if (!toolbox || !module) return; + + const fallback = MODULES[module.type]?.label || "Outil"; + const nextTitle = title.textContent.trim() || fallback; + title.textContent = nextTitle; + + if (nextTitle !== module.title) { + module.title = nextTitle; + updateToolbox(toolbox); + } +}); + +document.addEventListener("dragstart", (event) => { + const moduleElement = event.target.closest(".module[draggable='true']"); + if (!moduleElement) return; + + if (event.target.closest("button, input, select, textarea, [contenteditable='true']")) { + event.preventDefault(); + return; + } + + draggedModule = { + toolboxId: moduleElement.dataset.toolboxId, + moduleId: moduleElement.dataset.moduleId + }; + moduleElement.classList.add("is-dragging"); + event.dataTransfer.effectAllowed = "move"; + event.dataTransfer.setData("text/plain", draggedModule.moduleId); +}); + +document.addEventListener("dragover", (event) => { + const dropzone = event.target.closest("[data-action='drop-screenshot']"); + if (dropzone) { + event.preventDefault(); + event.dataTransfer.dropEffect = "copy"; + dropzone.classList.add("is-drag-over"); + return; + } + + const moduleElement = event.target.closest(".module[draggable='true']"); + if (!moduleElement || !draggedModule || moduleElement.dataset.toolboxId !== draggedModule.toolboxId) return; + + event.preventDefault(); + event.dataTransfer.dropEffect = "move"; + document.querySelectorAll(".module.is-drop-target").forEach((item) => item.classList.remove("is-drop-target", "drop-after")); + const rect = moduleElement.getBoundingClientRect(); + moduleElement.classList.add("is-drop-target"); + moduleElement.classList.toggle("drop-after", event.clientY > rect.top + rect.height / 2); +}); + +document.addEventListener("dragleave", (event) => { + const dropzone = event.target.closest("[data-action='drop-screenshot']"); + if (!dropzone || dropzone.contains(event.relatedTarget)) return; + dropzone.classList.remove("is-drag-over"); +}); + +document.addEventListener("drop", async (event) => { + const dropzone = event.target.closest("[data-action='drop-screenshot']"); + if (dropzone) { + event.preventDefault(); + dropzone.classList.remove("is-drag-over"); + if (await addScreenshotFiles(dropzone.dataset.toolboxId, dropzone.dataset.moduleId, event.dataTransfer.files)) { + refreshToolboxView(dropzone); + } + return; + } + + const moduleElement = event.target.closest(".module[draggable='true']"); + if (!moduleElement || !draggedModule || moduleElement.dataset.toolboxId !== draggedModule.toolboxId) return; + + event.preventDefault(); + const rect = moduleElement.getBoundingClientRect(); + const placement = event.clientY > rect.top + rect.height / 2 ? "after" : "before"; + if (moveToolboxModule(draggedModule.toolboxId, draggedModule.moduleId, moduleElement.dataset.moduleId, placement)) { + refreshToolboxView(moduleElement); + } +}); + +document.addEventListener("dragend", () => { + draggedModule = null; + document.querySelectorAll(".module.is-dragging, .module.is-drop-target").forEach((item) => { + item.classList.remove("is-dragging", "is-drop-target", "drop-after"); + }); +}); + +document.addEventListener("keydown", (event) => { + const editableTitle = event.target.closest("[data-action='edit-module-title'], [data-action='edit-toolbox-title']"); + if (editableTitle && event.key === "Enter") { + event.preventDefault(); + editableTitle.blur(); + return; + } + + if (editableTitle && event.key === "Escape") { + event.preventDefault(); + editableTitle.textContent = editableTitle.dataset.previousTitle || editableTitle.textContent; + editableTitle.blur(); + return; + } + + const target = event.target.closest("[data-action='flip-monster-card']"); + if (!target || (event.key !== "Enter" && event.key !== " ")) return; + + event.preventDefault(); + target.classList.toggle("is-flipped"); + target.setAttribute("aria-pressed", target.classList.contains("is-flipped") ? "true" : "false"); +}); + +document.addEventListener("change", async (event) => { + const target = event.target.closest("[data-action]"); + if (!target) return; + if (target.dataset.action === "add-module" && target.value) { + const toolbox = getToolboxes().find((item) => item.id === target.dataset.id); + toolbox.modules.push({ id: uid("mod"), type: target.value }); + updateToolbox(toolbox); + refreshToolboxView(target); + } + if (target.dataset.action === "toggle-check-item") { + const data = normalizeChecklistData(getModuleData(target.dataset.toolboxId, target.dataset.moduleId, { items: [] })); + const item = data.items.find((entry) => entry.id === target.dataset.itemId); + if (item) { + item.qtyTarget = 1; + item.qtyCurrent = target.checked ? 1 : 0; + } + setModuleData(target.dataset.toolboxId, target.dataset.moduleId, data); + target.closest(".checklist-item")?.classList.toggle("is-complete", target.checked); + } + if (target.dataset.action === "add-screenshot") { + if (await addScreenshotFiles(target.dataset.toolboxId, target.dataset.moduleId, target.files)) { + refreshToolboxView(target); + } + } + if (target.dataset.action === "link-toolbox") { + linkToolboxToGame(target.dataset.gameId, target.value); + openDrawer(target.dataset.gameId); + } + if (target.dataset.action === "toggle-mhwilds-option") { + const { category, filterKey, value } = target.dataset; + const values = new Set(mhwildsState.filters[category][filterKey]); + if (target.checked) values.add(value); + else values.delete(value); + mhwildsState.filters[category][filterKey] = [...values]; + target.closest(".filter-chip")?.classList.toggle("active", target.checked); + refreshMhwildsResults(category); + } + if (target.dataset.action === "import-toolbox" && target.files[0]) { + try { + const imported = await importToolbox(target.files[0]); + if (target.dataset.gameId && imported) { + linkToolboxToGame(target.dataset.gameId, imported.id); + openDrawer(target.dataset.gameId); + } else { + rerender(); + } + } catch (error) { + showConfirmModal({ + title: "Import impossible", + message: error.message, + confirmLabel: "Compris", + cancelLabel: "Fermer", + danger: true + }); + } + } + if (target.dataset.action === "import-all-toolboxes" && target.files[0]) { + try { + if (await importAllToolboxes(target.files[0])) rerender(); + } catch (error) { + showConfirmModal({ + title: "Import global impossible", + message: error.message, + confirmLabel: "Compris", + cancelLabel: "Fermer", + danger: true + }); + } + } +}); + +document.addEventListener("paste", async (event) => { + const target = event.target.closest("[data-action='paste-screenshot']"); + if (!target) return; + + const files = [...(event.clipboardData?.items || [])] + .filter((item) => item.type.startsWith("image/")) + .map((item) => item.getAsFile()) + .filter(Boolean); + + if (!files.length) return; + + event.preventDefault(); + target.textContent = "Coller une image ici"; + if (await addScreenshotFiles(target.dataset.toolboxId, target.dataset.moduleId, files)) { + refreshToolboxView(target); + } +}); + +document.addEventListener("submit", (event) => { + const form = event.target.closest("[data-action='add-check-item']"); + if (!form) return; + event.preventDefault(); + const label = new FormData(form).get("label")?.trim(); + if (!label) return; + const qtyTarget = parsePositiveInt(new FormData(form).get("qty"), 1); + const data = normalizeChecklistData(getModuleData(form.dataset.toolboxId, form.dataset.moduleId, { items: [] })); + data.items.push({ id: uid("item"), label, qtyTarget, qtyCurrent: 0 }); + setModuleData(form.dataset.toolboxId, form.dataset.moduleId, data); + form.reset(); + refreshToolboxView(form); +}); + +document.addEventListener("input", (event) => { + const mhwildsNameInput = event.target.closest("[data-action='set-mhwilds-name']"); + if (mhwildsNameInput) { + const category = mhwildsNameInput.dataset.category; + mhwildsState.filters[category].name = mhwildsNameInput.value; + refreshMhwildsResults(category); + return; + } + + const target = event.target.closest("[data-action='save-note']"); + if (!target) return; + setModuleData(target.dataset.toolboxId, target.dataset.moduleId, { text: target.value }); +}); + +window.addEventListener("hashchange", rerender); +rerender(); diff --git a/website/src/styles.css b/website/src/styles.css new file mode 100644 index 0000000..7c6ef23 --- /dev/null +++ b/website/src/styles.css @@ -0,0 +1,2345 @@ +: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-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-secondary: #3b82f6; + --color-secondary-soft: rgba(59, 130, 246, 0.14); + --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; + + --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; + + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --radius-xl: 18px; + --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; + --ease-standard: cubic-bezier(0.2, 0.8, 0.2, 1); +} + +* { + box-sizing: border-box; +} + +html { + min-height: 100%; + background: var(--color-bg-page); +} + +body { + min-height: 100vh; + margin: 0; + color: var(--color-text-primary); + background: var(--gradient-page); + font-family: Inter, "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif; + line-height: 1.55; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + opacity: 0.23; + background-image: + radial-gradient(circle, rgba(255, 255, 255, 0.52) 1px, transparent 1px), + radial-gradient(circle, rgba(139, 92, 246, 0.38) 1px, transparent 1px); + background-position: 0 0, 35px 60px; + background-size: 90px 90px, 140px 140px; +} + +a { + color: inherit; + text-decoration: none; +} + +button, +.button, +select, +input, +textarea, +.import-button { + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: rgba(21, 26, 48, 0.92); + color: var(--color-text-primary); + font: inherit; + transition: + transform var(--duration-fast) var(--ease-standard), + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard), + box-shadow var(--duration-fast) var(--ease-standard); +} + +button, +.button, +.import-button { + display: inline-flex; + min-height: 42px; + align-items: center; + justify-content: center; + gap: 8px; + padding: 0 18px; + cursor: pointer; +} + +button:hover, +.button:hover, +.import-button:hover { + border-color: var(--color-border-hover); + background: var(--color-bg-hover); +} + +button:active, +.button:active, +.import-button:active { + transform: translateY(0); +} + +:focus-visible { + outline: 2px solid var(--color-accent-cyan); + outline-offset: 3px; +} + +.filter-options::-webkit-scrollbar, +.drawer-panel::-webkit-scrollbar, +.damage-table-wrap::-webkit-scrollbar, +.legacy-scrollbar::-webkit-scrollbar { + background-color: transparent; + width: 6px; + height: 6px; + border-radius: 0; +} + +.filter-options::-webkit-scrollbar-track, +.drawer-panel::-webkit-scrollbar-track, +.damage-table-wrap::-webkit-scrollbar-track, +.legacy-scrollbar::-webkit-scrollbar-track { + background-color: transparent; + border-radius: 0; + box-shadow: none; +} + +.filter-options::-webkit-scrollbar-thumb, +.drawer-panel::-webkit-scrollbar-thumb, +.damage-table-wrap::-webkit-scrollbar-thumb, +.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))); + border-radius: 10px; +} + +.primary { + border-color: transparent; + background: var(--gradient-brand); + box-shadow: 0 8px 22px rgba(99, 102, 241, 0.22); + color: #fff; + font-weight: 700; +} + +.primary:hover { + border-color: transparent; + filter: brightness(1.08); + transform: translateY(-1px); + box-shadow: 0 10px 28px rgba(139, 92, 246, 0.3); +} + +.ghost { + border-color: transparent; + background: transparent; + color: var(--color-text-secondary); +} + +.danger { + color: var(--color-danger); +} + +.danger:hover { + border-color: rgba(251, 113, 133, 0.32); + background: var(--color-danger-soft); + color: #fff; +} + +.app-shell { + display: grid; + grid-template-columns: 248px minmax(0, 1fr); + min-height: 100vh; +} + +.sidebar { + position: sticky; + top: 0; + display: flex; + height: 100vh; + flex-direction: column; + gap: var(--space-5); + padding: var(--space-5); + border-right: 1px solid var(--color-border); + background: rgba(11, 14, 27, 0.82); + backdrop-filter: blur(18px); + box-shadow: var(--shadow-md); +} + +.brand { + display: flex; + align-items: center; + gap: 12px; + font-size: var(--font-size-lg); + font-weight: 800; +} + +.brand-mark { + display: block; + width: 34px; + height: 34px; + flex: 0 0 auto; + background: var(--gradient-brand); + filter: drop-shadow(0 0 16px rgba(139, 92, 246, 0.28)); + mask: url("/static/icons/controller.svg") center / contain no-repeat; + -webkit-mask: url("/static/icons/controller.svg") center / contain no-repeat; + transition: transform 0.8s var(--ease-standard), filter var(--duration-normal) var(--ease-standard); +} + +.brand:hover .brand-mark { + filter: drop-shadow(0 0 22px rgba(139, 92, 246, 0.42)); + transform: rotate(360deg); +} + +.nav { + display: grid; + gap: 8px; +} + +.nav-item { + display: flex; + min-height: 42px; + align-items: center; + gap: 12px; + padding: 10px 12px; + border: 1px solid transparent; + border-radius: var(--radius-md); + color: var(--color-text-secondary); + transition: var(--duration-normal) var(--ease-standard); +} + +.nav-icon { + display: block; + width: 24px; + height: 24px; + flex: 0 0 auto; + background: var(--color-accent-cyan); + mask-position: center; + mask-repeat: no-repeat; + mask-size: 20px 20px; + -webkit-mask-position: center; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: 20px 20px; +} + +.nav-icon-home { + mask-image: url("/static/icons/home.svg"); + -webkit-mask-image: url("/static/icons/home.svg"); +} + +.nav-icon-toolbox { + mask-image: url("/static/icons/toolbox.svg"); + -webkit-mask-image: url("/static/icons/toolbox.svg"); +} + +.nav-icon-controller { + mask-image: url("/static/icons/controller.svg"); + -webkit-mask-image: url("/static/icons/controller.svg"); +} + +.nav-item strong { + color: inherit; + font-size: var(--font-size-sm); + font-weight: 700; +} + +.nav-item:hover { + background: var(--color-bg-hover); + color: var(--color-text-primary); +} + +.nav-item.active { + border-color: var(--color-primary-border); + background: var(--color-primary-soft); + box-shadow: inset 3px 0 0 var(--color-primary); + color: var(--color-text-primary); +} + +.sidebar-note { + margin-top: auto; + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + background: rgba(16, 20, 38, 0.62); +} + +.sidebar-note p { + margin: 10px 0 0; + font-size: var(--font-size-sm); +} + +.sidebar-storage-actions { + display: flex; + gap: var(--space-2); + margin-top: var(--space-4); +} + +.sidebar-action-button { + display: inline-flex; + width: 40px; + min-width: 40px; + min-height: 40px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: rgba(21, 26, 48, 0.92); + color: var(--color-text-primary); + cursor: pointer; +} + +.sidebar-action-button:hover { + border-color: var(--color-border-hover); + background: var(--color-bg-hover); +} + +.content-shell { + display: flex; + min-height: 100vh; + min-width: 0; + flex-direction: column; +} + +.topbar { + position: sticky; + top: 0; + z-index: 20; + display: flex; + min-height: 68px; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + padding: 0 var(--space-8); + border-bottom: 1px solid var(--color-border); + background: rgba(7, 9, 19, 0.78); + backdrop-filter: blur(18px); +} + +.topbar strong { + font-size: var(--font-size-lg); +} + +.topbar .eyebrow { + margin-bottom: 2px; +} + +.topbar-actions { + display: flex; + gap: 10px; +} + +.toolbox-icon-button { + width: 42px; + min-width: 42px; + padding: 0; +} + +.toolbox-icon-button img { + width: 21px; + height: 21px; + object-fit: contain; +} + +main { + display: flex; + flex: 1; + flex-direction: column; + width: min(1600px, calc(100% - 64px)); + margin: 0 auto; + padding: var(--space-8) 0 var(--space-12); +} + +.hero, +.game-hero { + position: relative; + display: grid; + min-height: 360px; + align-items: end; + gap: var(--space-6); + overflow: hidden; + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-md); + padding: var(--space-10); +} + +.hero::after, +.game-hero::after { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: + linear-gradient(135deg, rgba(5, 7, 17, 0.1), rgba(5, 7, 17, 0.52)), + radial-gradient(circle at 82% 16%, rgba(34, 211, 238, 0.18), transparent 30%); +} + +.hero > *, +.game-hero > * { + position: relative; + z-index: 1; +} + +.hero { + grid-template-columns: minmax(0, 1fr) minmax(360px, 480px); + background: + var(--gradient-nebula), + linear-gradient(135deg, rgba(16, 20, 38, 0.98), rgba(5, 7, 17, 0.96)); +} + +.game-hero { + grid-template-columns: 1fr auto; + color: white; +} + +h1, +h2, +p { + margin-top: 0; +} + +h1 { + max-width: 820px; + margin-bottom: 14px; + font-size: clamp(2rem, 4vw, var(--font-size-4xl)); + line-height: 1.05; +} + +h2 { + margin-bottom: 10px; + font-size: var(--font-size-xl); +} + +p, +li, +small, +span { + color: var(--color-text-secondary); +} + +.eyebrow { + margin-bottom: 10px; + color: var(--color-accent-cyan); + font-size: var(--font-size-xs); + font-weight: 800; + letter-spacing: 0; + text-transform: uppercase; +} + +.actions, +.card-actions, +.toolbar { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.toolbar-stack { + align-items: flex-start; + flex-direction: column; +} + +.toolbar-stack > div { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.hero-panel { + padding: var(--space-5); + border: 1px solid rgba(165, 180, 252, 0.14); + border-radius: var(--radius-lg); + background: rgba(16, 20, 38, 0.72); + backdrop-filter: blur(18px); +} + +.hero-panel span { + display: block; + background: var(--gradient-brand); + background-clip: text; + color: transparent; + font-size: 56px; + font-weight: 800; + line-height: 1; +} + +.home-visual { + display: grid; + align-self: center; + justify-self: stretch; + width: 100%; + height: 100%; + min-height: 320px; + place-items: center; +} + +.home-visual img { + display: block; + width: 100%; + height: 100%; + max-height: 520px; + object-fit: cover; + border-radius: var(--radius-lg); + filter: drop-shadow(0 22px 36px rgba(0, 0, 0, 0.34)); +} + +.origin-section { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(300px, 0.65fr); + gap: var(--space-6); + margin-top: var(--space-6); + padding: var(--space-6); + border: 1px solid rgba(165, 180, 252, 0.14); + border-radius: var(--radius-lg); + background: rgba(16, 20, 38, 0.42); + box-shadow: var(--shadow-sm); +} + +.origin-copy { + min-width: 0; + padding-right: var(--space-6); + border-right: 2px solid rgba(34, 211, 238, 0.66); +} + +.home-dialogue { + position: relative; + display: grid; + gap: 8px; + margin-top: var(--space-4); +} + +.dialogue-line { + width: fit-content; + max-width: 100%; + margin: 0; + padding: 8px 11px; + border: 1px solid rgba(165, 180, 252, 0.14); + border-radius: 8px; + color: var(--color-text-primary); + font-size: var(--font-size-sm); + line-height: 1.35; +} + +.dialogue-line-user { + justify-self: end; + background: linear-gradient(135deg, rgba(124, 58, 237, 0.28), rgba(37, 99, 235, 0.18)); +} + +.dialogue-line-app { + justify-self: start; + background: rgba(7, 10, 24, 0.48); +} + +.dialogue-line i { + color: var(--color-text-muted); + font-style: normal; +} + +.dialogue-caption { + display: block; + margin-top: 4px; + color: var(--color-text-muted); + font-size: var(--font-size-xs); + font-style: italic; + text-align: right; +} + +.home-stats { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; + align-self: start; +} + +.hero-stats { + align-self: center; + justify-self: end; + width: min(420px, 100%); +} + +.home-stats article { + min-height: 118px; + padding: var(--space-4); + border: 1px solid rgba(165, 180, 252, 0.14); + border-radius: var(--radius-md); + background: + linear-gradient(145deg, rgba(34, 211, 238, 0.08), transparent 52%), + rgba(7, 10, 24, 0.52); +} + +.home-stats strong { + display: block; + margin-bottom: 10px; + background: var(--gradient-brand); + background-clip: text; + color: transparent; + font-size: 44px; + font-weight: 900; + line-height: 0.95; +} + +.home-stats span { + display: block; + color: var(--color-text-secondary); + font-size: var(--font-size-sm); + font-weight: 700; + line-height: 1.35; +} + +.section-grid, +.cards, +.info-grid, +.modules { + display: grid; + gap: var(--space-4); + margin-top: var(--space-6); +} + +.section-grid, +.info-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.cards { + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); +} + +.feature, +.info-panel, +.module, +.empty, +.card { + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + background: rgba(16, 20, 38, 0.88); + box-shadow: var(--shadow-sm); +} + +.feature, +.info-panel, +.empty { + padding: var(--space-5); +} + +.feature, +.card, +.module, +.info-panel { + transition: + transform var(--duration-fast) var(--ease-standard), + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard); +} + +.feature:hover, +.card:hover, +.module:hover, +.info-panel:hover { + transform: translateY(-2px); + border-color: var(--color-border-hover); + background: rgba(21, 26, 48, 0.96); +} + +.feature { + display: grid; + gap: 8px; +} + +.feature strong { + color: var(--color-text-primary); +} + +.page-heading, +.toolbox-head { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: var(--space-5); +} + +.card { + overflow: hidden; +} + +.card-cover { + min-height: 124px; + border-bottom: 1px solid var(--color-border); +} + +.card-body { + padding: var(--space-5); +} + +.card-actions { + margin-top: var(--space-5); +} + +.toolbox-card { + display: flex; + min-height: 292px; + flex-direction: column; +} + +.toolbox-card .card-cover { + flex: 0 0 124px; + height: 124px; + min-height: 124px; +} + +.toolbox-card .card-body { + display: flex; + flex: 1; + flex-direction: column; +} + +.toolbox-card .card-actions { + margin-top: auto; + padding-top: var(--space-5); +} + +.toolbox-page { + display: flex; + min-height: calc(100vh - 68px - var(--space-8) - var(--space-12)); + flex: 1; + flex-direction: column; + gap: var(--space-4); +} + +.toolbox-embedded { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.storage-help-card { + position: relative; + min-height: 292px; + overflow: hidden; + border-color: rgba(34, 211, 238, 0.22); + background: + linear-gradient(135deg, rgba(34, 211, 238, 0.1), transparent 38%), + rgba(10, 13, 29, 0.84); +} + +.storage-help-card::after { + content: ""; + position: absolute; + inset: 0 auto 0 0; + width: 3px; + background: var(--gradient-brand); +} + +.storage-help-card .card-body { + display: flex; + min-height: 100%; + flex-direction: column; + padding: 18px; +} + +.storage-help-heading { + display: grid; + grid-template-columns: 36px minmax(0, 1fr); + gap: 10px; + align-items: center; + margin-bottom: 12px; +} + +.storage-help-mark { + display: grid; + width: 36px; + height: 36px; + place-items: center; + border: 1px solid rgba(34, 211, 238, 0.32); + border-radius: var(--radius-md); + background: rgba(34, 211, 238, 0.1); + color: var(--color-accent-cyan); + box-shadow: 0 0 22px rgba(34, 211, 238, 0.12); +} + +.storage-help-mark::before { + content: ""; + width: 19px; + height: 19px; + background: currentColor; + mask: url("/static/icons/save.svg") center / contain no-repeat; + -webkit-mask: url("/static/icons/save.svg") center / contain no-repeat; +} + +.storage-help-card h2 { + margin: 0; + font-size: var(--font-size-lg); + line-height: 1.2; +} + +.storage-help-card .eyebrow { + color: var(--color-accent-cyan); +} + +.storage-help-intro { + color: var(--color-text-secondary); + font-size: var(--font-size-xs); + line-height: 1.45; +} + +.storage-help-card p, +.storage-help-card ul { + margin: 0; +} + +.storage-help-list { + display: grid; + gap: 7px; + margin-top: 18px; + padding: 10px 0 0; + list-style: none; +} + +.storage-help-list li { + display: grid; + grid-template-columns: 22px minmax(0, 1fr); + gap: 8px; + align-items: start; + padding: 8px 9px; + border: 1px solid rgba(150, 165, 205, 0.12); + border-radius: var(--radius-md); + background: rgba(7, 10, 24, 0.45); +} + +.storage-help-list span { + display: grid; + width: 18px; + height: 18px; + place-items: center; + border-radius: var(--radius-sm); + background: rgba(139, 92, 246, 0.18); + color: var(--color-text-primary); + font-size: var(--font-size-xs); + font-weight: 800; +} + +.storage-help-list p { + align-self: center; + color: var(--color-text-secondary); + font-size: var(--font-size-xs); + line-height: 1.32; +} + +.storage-quota { + display: grid; + gap: var(--space-3); + margin-top: auto; + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + background: rgba(10, 13, 29, 0.78); + box-shadow: var(--shadow-sm); +} + +.toolbox-page > .storage-quota, +.drawer-content > .storage-quota { + margin-top: auto; +} + +.storage-quota > div:first-child { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + color: var(--color-text-secondary); + font-size: var(--font-size-sm); +} + +.storage-quota strong { + color: var(--color-text-primary); + font-size: var(--font-size-sm); +} + +.storage-quota-track { + width: 100%; + height: 9px; + overflow: hidden; + border: 1px solid rgba(150, 165, 205, 0.14); + border-radius: var(--radius-pill); + background: rgba(5, 7, 17, 0.82); +} + +.storage-quota-track span { + display: block; + height: 100%; + border-radius: inherit; + background: var(--gradient-brand); + box-shadow: 0 0 18px rgba(99, 102, 241, 0.32); + transition: width var(--duration-normal) var(--ease-standard); +} + +.storage-quota-warning { + border-color: rgba(251, 191, 36, 0.34); +} + +.storage-quota-warning .storage-quota-track span { + background: linear-gradient(135deg, #fbbf24, #f97316); +} + +.storage-quota-danger { + border-color: rgba(251, 113, 133, 0.42); +} + +.storage-quota-danger .storage-quota-track span { + background: linear-gradient(135deg, #fb7185, #d946ef); +} + +.card-icon-button { + width: 42px; + min-width: 42px; + padding: 0; +} + +.ui-icon { + display: block; + width: 20px; + height: 20px; + background: currentColor; + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-position: center; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; +} + +.ui-icon-open { + mask-image: url("/static/icons/open.svg"); + -webkit-mask-image: url("/static/icons/open.svg"); +} + +.ui-icon-save { + mask-image: url("/static/icons/save.svg"); + -webkit-mask-image: url("/static/icons/save.svg"); +} + +.ui-icon-export { + mask-image: url("/static/icons/export.svg"); + -webkit-mask-image: url("/static/icons/export.svg"); +} + +.ui-icon-import { + mask-image: url("/static/icons/import.svg"); + -webkit-mask-image: url("/static/icons/import.svg"); +} + +.ui-icon-link { + mask-image: url("/static/icons/link.svg"); + -webkit-mask-image: url("/static/icons/link.svg"); +} + +.ui-icon-rows { + mask-image: url("/static/icons/rows.svg"); + -webkit-mask-image: url("/static/icons/rows.svg"); +} + +.ui-icon-columns { + mask-image: url("/static/icons/columns.svg"); + -webkit-mask-image: url("/static/icons/columns.svg"); +} + +.ui-icon-close { + mask-image: url("/static/icons/close.svg"); + -webkit-mask-image: url("/static/icons/close.svg"); +} + +.ui-icon-rubber { + mask-image: url("/static/icons/rubber.svg"); + -webkit-mask-image: url("/static/icons/rubber.svg"); +} + +.ui-icon-trash { + mask-image: url("/static/icons/trashcan.svg"); + -webkit-mask-image: url("/static/icons/trashcan.svg"); +} + +.game-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: var(--space-4); + margin-top: var(--space-6); +} + +.game-card .card-cover { + min-height: 170px; +} + +.game-card-cover { + position: relative; + overflow: hidden; + background: + var(--game-cover), + rgba(5, 7, 17, 0.72); +} + +.toolbox-card-cover { + position: relative; + overflow: hidden; + background: + var(--game-cover), + rgba(5, 7, 17, 0.72); +} + +.game-card-cover img, +.toolbox-card-cover img { + display: block; + width: 100%; + height: 100%; + min-height: inherit; + object-fit: cover; +} + +.badge { + display: inline-flex; + min-height: 24px; + align-items: center; + gap: 6px; + border-radius: var(--radius-pill); + background: var(--color-primary-soft); + color: #b69cff; + font-size: var(--font-size-xs); + font-weight: 700; + padding: 3px 9px; +} + +.toolbox-head { + padding: var(--space-6); + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + background: rgba(16, 20, 38, 0.76); + backdrop-filter: blur(18px); +} + +.toolbox-title { + width: fit-content; + max-width: 100%; + padding: 3px 6px; + border-radius: var(--radius-sm); + cursor: text; +} + +.toolbox-title:focus { + background: rgba(7, 10, 24, 0.72); + outline: 1px solid rgba(34, 211, 238, 0.72); + outline-offset: 3px; +} + +.modules-toolbar { + display: flex; + justify-content: flex-end; + margin-top: var(--space-4); +} + +.modules { + align-items: start; +} + +.modules[data-layout-cols="1"] { + grid-template-columns: 1fr; +} + +.modules[data-layout-cols="2"] { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.module-column { + display: grid; + gap: var(--space-4); + align-content: start; +} + +.module { + overflow: hidden; +} + +.module[draggable="true"] { + cursor: grab; +} + +.module.is-dragging { + cursor: grabbing; + opacity: 0.58; + transform: scale(0.99); +} + +.module.is-drop-target { + border-color: rgba(34, 211, 238, 0.72); + box-shadow: + var(--shadow-sm), + inset 0 3px 0 rgba(34, 211, 238, 0.8); +} + +.module.is-drop-target.drop-after { + box-shadow: + var(--shadow-sm), + inset 0 -3px 0 rgba(34, 211, 238, 0.8); +} + +.module header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + padding: var(--space-4); + border-bottom: 1px solid var(--color-border); +} + +.module header > div { + display: flex; + align-items: center; + gap: 10px; +} + +.module header h2 { + margin: 0; + font-size: 17px; +} + +.layout-switch { + display: inline-grid; + grid-template-columns: repeat(2, 38px); + gap: 4px; + padding: 4px; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: rgba(5, 7, 17, 0.48); +} + +.layout-switch button { + min-height: 34px; + align-items: center; + justify-content: center; + padding: 0; + border-color: transparent; + background: transparent; + color: var(--color-text-muted); + font-weight: 900; +} + +.layout-switch button.active { + background: var(--gradient-brand); + color: white; + box-shadow: var(--shadow-primary); +} + +.module-drag-handle { + display: grid; + width: 18px; + height: 34px; + flex: 0 0 18px; + place-items: center; + cursor: grab; + opacity: 0.62; +} + +.module-drag-handle::before { + content: ""; + width: 4px; + height: 20px; + background: + radial-gradient(circle, currentColor 1.6px, transparent 1.8px) 0 0 / 4px 6px, + radial-gradient(circle, currentColor 1.6px, transparent 1.8px) 8px 0 / 4px 6px; + color: var(--color-text-muted); +} + +.module:hover .module-drag-handle, +.module:focus-within .module-drag-handle { + opacity: 1; +} + +.module-title { + min-width: 80px; + max-width: 100%; + padding: 4px 6px; + border-radius: var(--radius-sm); + cursor: text; +} + +.module-title:focus { + background: rgba(7, 10, 24, 0.72); + outline: 1px solid rgba(34, 211, 238, 0.72); + outline-offset: 2px; +} + +.module-delete-button { + width: 38px; + min-width: 38px; + padding: 0; +} + +.module-icon { + display: grid; + width: 34px; + height: 34px; + place-items: center; + border-radius: var(--radius-md); + background: var(--gradient-brand); + box-shadow: var(--shadow-primary); + color: white; +} + +.module-icon-svg { + display: block; + width: 19px; + height: 19px; + background: white; + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-position: center; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; +} + +.module-icon-notepad { + mask-image: url("/static/icons/notepad.svg"); + -webkit-mask-image: url("/static/icons/notepad.svg"); +} + +.module-icon-checklist { + mask-image: url("/static/icons/checklist.svg"); + -webkit-mask-image: url("/static/icons/checklist.svg"); +} + +.module-icon-picture { + mask-image: url("/static/icons/picture.svg"); + -webkit-mask-image: url("/static/icons/picture.svg"); +} + +.notepad { + display: block; + width: calc(100% - 32px); + min-height: 240px; + margin: var(--space-4); + padding: var(--space-4); + resize: vertical; + line-height: 1.6; + background: rgba(5, 7, 17, 0.56); +} + +.inline-form, +.field { + display: grid; + gap: 10px; + padding: var(--space-4); +} + +.inline-form { + grid-template-columns: 1fr auto; +} + +.checklist-add-form { + grid-template-columns: minmax(0, 1fr) 76px auto; +} + +.checklist-qty-input { + padding-inline: 10px; + text-align: center; +} + +input, +select, +textarea { + min-height: 44px; + padding: 0 14px; + outline: none; +} + +textarea { + padding-block: 14px; +} + +input::placeholder, +textarea::placeholder { + color: var(--color-text-muted); +} + +input:focus, +select:focus, +textarea:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.16); +} + +.checklist { + display: grid; + gap: 8px; + margin: 0; + padding: 0 var(--space-4) var(--space-4); + list-style: none; +} + +.checklist-item { + display: grid; + grid-template-columns: 1fr 42px; + gap: 8px; + align-items: center; +} + +.checklist-item-main { + display: flex; + min-height: 42px; + align-items: center; + gap: 10px; + padding: 0 12px; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: rgba(5, 7, 17, 0.44); +} + +.checklist input[type="checkbox"] { + flex: 0 0 auto; + width: 20px; + min-height: 20px; + height: 20px; + accent-color: var(--color-primary); +} + +.checklist-item.is-complete .checklist-item-main > span { + color: var(--color-text-muted); + text-decoration: line-through; +} + +.checklist-qty-controls { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + gap: 6px; +} + +.checklist-qty-controls button { + width: 28px; + min-width: 28px; + min-height: 28px; + padding: 0; + line-height: 1; +} + +.checklist-qty-controls small { + min-width: 42px; + color: var(--color-text-secondary); + text-align: center; +} + +.dropzone { + display: grid; + min-height: 74px; + margin: var(--space-4); + place-items: center; + border: 1px dashed rgba(139, 92, 246, 0.52); + border-radius: var(--radius-lg); + background: var(--color-primary-soft); + color: var(--color-text-secondary); + cursor: pointer; +} + +.dropzone:hover { + border-color: var(--color-accent-cyan); + background: rgba(34, 211, 238, 0.1); + box-shadow: 0 0 30px rgba(34, 211, 238, 0.12); +} + +.dropzone.is-drag-over { + border-color: var(--color-accent-cyan); + background: rgba(34, 211, 238, 0.14); + box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.12); +} + +.paste-target { + min-height: 46px; + margin: 0 var(--space-4) var(--space-4); + padding: 12px 14px; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: rgba(5, 7, 17, 0.44); + color: var(--color-text-muted); +} + +.paste-target:focus { + border-color: var(--color-primary); + color: var(--color-text-secondary); + outline: none; + box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.16); +} + +.shots { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: var(--space-3); + padding: 0 var(--space-4) var(--space-4); +} + +.shots figure { + position: relative; + margin: 0; + padding: 8px; + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + background: rgba(5, 7, 17, 0.38); +} + +.shot-preview { + display: block; + width: 100%; + min-height: 0; + padding: 0; + border: 0; + border-radius: var(--radius-md); + background: transparent; +} + +.shot-preview:hover { + transform: none; + box-shadow: 0 0 0 2px rgba(34, 211, 238, 0.28); +} + +.shots img { + display: block; + width: 100%; + aspect-ratio: 16 / 9; + object-fit: cover; + border-radius: var(--radius-md); + background: var(--color-bg-deep); +} + +.shot-delete-button { + width: 38px; + min-width: 38px; + padding: 0; +} + +.screenshot-viewer-root { + position: fixed; + inset: 0; + z-index: 110; + display: grid; + place-items: center; + padding: var(--space-5); +} + +.screenshot-viewer-backdrop { + position: absolute; + inset: 0; + background: rgba(2, 3, 9, 0.64); + backdrop-filter: blur(4px); +} + +.screenshot-viewer { + position: relative; + display: grid; + width: min(1120px, calc(100vw - 32px)); + max-height: calc(100vh - 48px); + gap: var(--space-4); + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + background: rgba(16, 20, 38, 0.96); + box-shadow: var(--shadow-lg); +} + +.screenshot-viewer header { + display: flex; + align-items: center; + justify-content: flex-end; + gap: var(--space-4); +} + +.screenshot-viewer img { + display: block; + max-width: 100%; + max-height: calc(100vh - 170px); + justify-self: center; + border-radius: var(--radius-md); + object-fit: contain; +} + +.drawer[aria-hidden="true"] { + display: none; +} + +.drawer-backdrop { + position: fixed; + inset: 0; + z-index: 40; + background: rgba(2, 3, 9, 0.42); + backdrop-filter: blur(2px); +} + +.drawer-panel { + position: fixed; + inset: 0 0 0 auto; + z-index: 50; + width: min(720px, 94vw); + min-width: 360px; + max-width: 94vw; + overflow: auto; + border-left: 1px solid var(--color-border); + background: rgba(7, 9, 19, 0.96); + box-shadow: var(--shadow-lg); + padding: var(--space-5); +} + +.drawer-content { + position: relative; + display: flex; + min-height: 100%; + flex-direction: column; + gap: var(--space-4); +} + +.drawer-resize-handle { + position: absolute; + top: 50%; + left: -18px; + width: 14px; + height: 72px; + border-radius: var(--radius-pill); + cursor: ew-resize; + transform: translateY(-50%); +} + +.drawer-resize-handle::before { + content: ""; + position: absolute; + top: 0; + left: 5px; + width: 4px; + height: 100%; + border-radius: inherit; + background: var(--gradient-brand); + opacity: 0.72; +} + +body.is-resizing-drawer, +body.is-resizing-drawer * { + cursor: ew-resize !important; + user-select: none; +} + +.drawer-panel > .drawer-content > header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-4); + margin-bottom: var(--space-3); +} + +.drawer-toolbox-actions { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-top: var(--space-3); +} + +.drawer-action-button { + display: inline-flex; + width: 40px; + min-width: 40px; + min-height: 40px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: rgba(21, 26, 48, 0.92); + color: var(--color-text-primary); + cursor: pointer; +} + +.drawer-action-button:hover { + border-color: var(--color-border-hover); + background: var(--color-bg-hover); +} + +.drawer-action-button:disabled { + color: var(--color-text-disabled); + cursor: not-allowed; + opacity: 0.56; +} + +.drawer-close-button { + width: 40px; + min-width: 40px; + padding: 0; +} + +.drawer-panel .modules { + grid-template-columns: 1fr; +} + +.confirm-modal-root { + position: fixed; + inset: 0; + z-index: 100; + display: grid; + place-items: center; + padding: var(--space-5); +} + +.confirm-backdrop { + position: absolute; + inset: 0; + background: rgba(2, 3, 9, 0.62); + backdrop-filter: blur(6px); +} + +.confirm-modal { + position: relative; + width: min(520px, calc(100vw - 32px)); + max-height: calc(100vh - 48px); + overflow: auto; + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + background: rgba(16, 20, 38, 0.96); + box-shadow: var(--shadow-lg); + padding: var(--space-5); +} + +.confirm-modal h2 { + margin-bottom: var(--space-3); +} + +.confirm-modal p { + margin-bottom: var(--space-6); +} + +.confirm-modal footer { + display: flex; + justify-content: flex-end; + gap: 10px; +} + +.toolbox-create-modal form, +.toolbox-link-modal form { + display: grid; + gap: var(--space-4); +} + +.toolbox-create-modal .field, +.toolbox-link-modal .field { + padding: 0; +} + +.confirm-danger { + border-color: rgba(251, 113, 133, 0.34); + background: var(--color-danger); + color: #fff; + font-weight: 700; +} + +.mhwilds-hero { + background: + linear-gradient(135deg, rgba(139, 92, 246, 0.86), rgba(99, 102, 241, 0.76), rgba(34, 211, 238, 0.58)), + var(--color-bg-surface); +} + +.mhwilds-home-grid { + display: grid; + grid-template-columns: repeat(2, minmax(240px, 380px)); + justify-content: center; + gap: var(--space-4); + margin-top: var(--space-6); +} + +.mhwilds-home-card { + overflow: hidden; + padding: 0; + min-height: 230px; +} + +.mhwilds-home-card img { + display: block; + width: 100%; + height: 120px; + object-fit: contain; + padding: var(--space-4); + border-bottom: 1px solid var(--color-border); + background: rgba(5, 7, 17, 0.28); +} + +.mhwilds-home-card strong, +.mhwilds-home-card span { + margin-inline: var(--space-5); +} + +.mhwilds-home-card strong { + margin-top: var(--space-4); +} + +.mhwilds-home-card span { + margin-bottom: var(--space-5); +} + +.mhwilds-heading p { + max-width: 70ch; +} + +.mhwilds-title-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px; +} + +.mhwilds-title-row h1 { + margin-bottom: 0; +} + +.results-count { + display: inline-flex; + min-height: 28px; + align-items: center; + padding: 4px 10px; + border: 1px solid rgba(165, 180, 252, 0.16); + border-radius: var(--radius-pill); + background: rgba(5, 7, 17, 0.34); + color: var(--color-text-secondary); + font-size: var(--font-size-sm); + font-weight: 800; +} + +.mhwilds-layout { + display: grid; + grid-template-columns: minmax(240px, 300px) minmax(0, 1fr); + gap: var(--space-5); + margin-top: var(--space-6); + align-items: start; +} + +.mhwilds-filters { + position: sticky; + top: 92px; +} + +.filter-panel { + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + background: rgba(16, 20, 38, 0.72); + box-shadow: var(--shadow-sm); + backdrop-filter: blur(18px); + padding: var(--space-4); +} + +.filter-panel-head, +.filter-logic { + display: flex; + justify-content: space-between; + gap: var(--space-3); +} + +.filter-panel-head { + align-items: flex-start; +} + +.filter-logic { + align-items: center; +} + +.filter-panel-head h2 { + margin-bottom: 0; + font-size: var(--font-size-lg); +} + +.filter-reset-button { + width: 40px; + min-width: 40px; + min-height: 40px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: rgba(16, 20, 38, 0.78); + color: var(--color-text-secondary); + box-shadow: var(--shadow-sm); +} + +.filter-reset-button:hover { + border-color: var(--color-border-hover); + background: rgba(21, 26, 48, 0.96); + color: var(--color-text-primary); +} + +.field.compact { + padding: var(--space-4) 0; +} + +.filter-logic { + min-height: 42px; + margin-bottom: var(--space-4); + padding: 8px 10px; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: rgba(5, 7, 17, 0.36); +} + +.filter-logic button { + min-height: 30px; + padding: 0 12px; + background: var(--color-primary-soft); + color: var(--color-text-primary); + font-size: var(--font-size-sm); + font-weight: 800; + text-transform: uppercase; +} + +.filter-options { + display: grid; + gap: 8px; + max-height: calc(100vh - 360px); + overflow: auto; + margin-right: -6px; + padding-right: 12px; +} + +.filter-chip { + display: grid; + grid-template-columns: 22px 24px 1fr; + min-height: 40px; + align-items: center; + gap: 8px; + padding: 7px 9px; + border: 1px solid var(--color-border); + border-radius: var(--radius-pill); + background: rgba(5, 7, 17, 0.32); + color: var(--color-text-secondary); + cursor: pointer; +} + +.filter-chip:hover, +.filter-chip.active { + border-color: var(--color-primary-border); + background: var(--color-primary-soft); + color: var(--color-text-primary); +} + +.filter-chip input { + width: 18px; + height: 18px; + min-height: 18px; + accent-color: var(--color-primary); +} + +.filter-chip img { + width: 22px; + height: 22px; + object-fit: contain; +} + +.filter-chip span { + overflow: hidden; + color: inherit; + font-size: var(--font-size-sm); + text-overflow: ellipsis; + white-space: nowrap; +} + +.mhwilds-grid { + display: grid; + gap: var(--space-4); +} + +.monster-grid { + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); +} + +.endemic-grid { + grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); +} + +.mhwilds-card { + overflow: hidden; + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + background: rgba(16, 20, 38, 0.88); + box-shadow: var(--shadow-sm); + transition: + transform var(--duration-fast) var(--ease-standard), + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard); +} + +.mhwilds-card:hover { + transform: translateY(-2px); + border-color: var(--color-border-hover); + background: rgba(21, 26, 48, 0.96); +} + +.monster-card { + height: 285px; + overflow: visible; + cursor: pointer; + perspective: 1200px; +} + +.monster-card:hover, +.monster-card:focus-visible { + background: transparent; +} + +.mhwilds-card-inner { + position: relative; + width: 100%; + height: 100%; + transform-style: preserve-3d; + transition: transform 420ms var(--ease-standard); +} + +.monster-card.is-flipped .mhwilds-card-inner { + transform: rotateY(180deg); +} + +.mhwilds-card-face { + position: absolute; + inset: 0; + display: flex; + overflow: hidden; + flex-direction: column; + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + background: rgba(16, 20, 38, 0.92); + box-shadow: var(--shadow-sm); + backface-visibility: hidden; +} + +.mhwilds-card-front { + transform: rotateY(0deg); +} + +.mhwilds-card-back { + transform: rotateY(180deg); +} + +.monster-card:hover .mhwilds-card-face { + border-color: var(--color-border-hover); + background: rgba(21, 26, 48, 0.98); +} + +.mhwilds-card-art { + display: grid; + min-height: 112px; + place-items: center; + border-bottom: 1px solid var(--color-border); + background: + radial-gradient(circle at 50% 40%, rgba(139, 92, 246, 0.16), transparent 46%), + rgba(5, 7, 17, 0.36); +} + +.endemic-card .mhwilds-card-art { + min-height: 104px; +} + +.mhwilds-card-art img { + max-width: min(78%, 230px); + max-height: 92px; + object-fit: contain; + filter: drop-shadow(0 16px 24px rgba(0, 0, 0, 0.44)); +} + +.endemic-card .mhwilds-card-art img { + max-height: 78px; +} + +.mhwilds-card-body { + padding: 12px; +} + +.mhwilds-card-body h2 { + margin-bottom: 8px; + font-size: 1rem; +} + +.monster-card .eyebrow { + margin-bottom: 6px; +} + +.mhwilds-card-back .mhwilds-card-body { + padding: 10px 12px 6px; +} + +.mhwilds-card-back .eyebrow { + margin-bottom: 4px; +} + +.mhwilds-icon-row { + display: grid; + grid-template-columns: 92px 1fr; + gap: var(--space-2); + align-items: start; + margin-top: 4px; +} + +.mhwilds-icon-row > span { + color: var(--color-text-muted); + font-size: 0.82rem; + line-height: 1.25; +} + +.mhwilds-icon-row > div { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.mhwilds-icon-row img { + width: 18px; + height: 18px; + object-fit: contain; +} + +.mhwilds-icon-row em { + display: inline-grid; + width: 18px; + height: 18px; + place-items: center; + color: var(--color-text-muted); + font-style: normal; +} + +.damage-table-wrap { + min-height: 0; + max-height: 218px; + overflow-x: hidden; + overflow-y: auto; + margin: 0 10px 10px; + padding: 0; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); +} + +.damage-table { + width: 100%; + min-width: 0; + border-collapse: separate; + border-spacing: 0; + table-layout: fixed; + font-size: 0.62rem; +} + +.damage-table th, +.damage-table td { + width: 9%; + padding: 2px 1px; + border-right: 1px solid var(--color-border); + border-bottom: 1px solid var(--color-border); + text-align: center; +} + +.damage-table th:first-child, +.damage-table td:first-child { + position: sticky; + left: 0; + width: 26%; + max-width: 26%; + overflow: hidden; + border-left: 1px solid var(--color-border); + background: var(--color-bg-surface-alt); + color: var(--color-text-secondary); + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} + +.damage-table th { + position: sticky; + top: 0; + z-index: 2; + background: rgba(21, 26, 48, 0.96); +} + +.damage-table th:first-child { + z-index: 3; +} + +.damage-table img { + width: 14px; + height: 14px; + object-fit: contain; +} + +.mobile-nav { + display: none; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +@media (max-width: 1024px) { + .app-shell { + grid-template-columns: 72px minmax(0, 1fr); + } + + .sidebar { + padding: var(--space-4) 10px; + } + + .brand span:last-child, + .nav-item strong, + .sidebar-note { + display: none; + } + + .brand, + .nav-item { + justify-content: center; + } +} + +@media (max-width: 760px) { + .app-shell { + display: block; + padding-bottom: 76px; + } + + .sidebar { + display: none; + } + + .topbar, + .page-heading, + .toolbox-head, + .module header { + align-items: stretch; + flex-direction: column; + } + + .topbar { + padding: var(--space-4); + } + + .topbar-actions { + display: flex; + } + + .topbar-actions > * { + width: 100%; + } + + .hero, + .game-hero, + .origin-section, + .section-grid, + .info-grid { + grid-template-columns: 1fr; + } + + .hero, + .game-hero { + min-height: auto; + padding: var(--space-6); + } + + .home-visual { + justify-self: center; + width: min(320px, 100%); + min-height: 240px; + } + + .home-visual img { + object-fit: contain; + } + + .origin-section { + padding: var(--space-5); + } + + .home-dialogue { + padding-left: 0; + } + + .origin-copy { + padding-right: 0; + padding-bottom: var(--space-5); + border-right: 0; + border-bottom: 2px solid rgba(34, 211, 238, 0.66); + } + + .dialogue-line { + max-width: 92%; + } + + .home-stats { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + main { + width: min(100% - 24px, 1180px); + padding-top: var(--space-5); + } + + .modules, + .modules[data-layout-cols="1"], + .modules[data-layout-cols="2"] { + grid-template-columns: 1fr; + } + + .module-column { + display: contents; + } + + .mhwilds-home-grid, + .mhwilds-layout { + grid-template-columns: 1fr; + } + + .mhwilds-filters { + position: static; + } + + .filter-options { + max-height: none; + } + + .inline-form { + grid-template-columns: 1fr; + } + + .mobile-nav { + position: fixed; + right: 12px; + bottom: 12px; + left: 12px; + z-index: 60; + display: grid; + grid-template-columns: 1fr 1fr 52px 1fr; + gap: 8px; + align-items: center; + padding: 8px; + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + background: rgba(11, 14, 27, 0.9); + backdrop-filter: blur(18px); + box-shadow: var(--shadow-lg); + } + + .mobile-nav a, + .mobile-nav button { + min-height: 44px; + padding: 0 8px; + border-radius: var(--radius-md); + font-size: var(--font-size-sm); + } + + .mobile-nav a { + display: grid; + place-items: center; + color: var(--color-text-secondary); + } + + .mobile-nav a.active { + background: var(--color-primary-soft); + color: var(--color-text-primary); + } + + .drawer-panel { + min-width: 0; + width: min(720px, 94vw); + } + + .drawer-resize-handle { + display: none; + } +} + +@media (max-width: 480px) { + .home-stats { + grid-template-columns: 1fr; + } +}