Add equipment planner toolbox module
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
|
|
@ -47,15 +47,20 @@
|
|||
{
|
||||
"id": "mb",
|
||||
"type": "imageAnnotation"
|
||||
},
|
||||
{
|
||||
"id": "mc",
|
||||
"type": "equipmentPlanner"
|
||||
}
|
||||
],
|
||||
"updatedAt": "2026-08-03T08:04:53.696Z"
|
||||
"updatedAt": "2026-08-09T07:34:31.425Z",
|
||||
"moduleColumns": 1
|
||||
},
|
||||
"modules": {
|
||||
"m1": {
|
||||
"html": "<h3><span>Obtenir Mew</span> :</h3><div><ul><li>Obtenir la CS <span style=\"color: #f6c453\">Coupe</span> à l'intérieur du bateau le <span style=\"color: #d946ef\">S.S. Anne</span>.</li><li>Faire exprès de perdre un combat <span>(tomber K.O.)</span> pour réapparaître au Centre Pokémon sans faire partir le navire.</li><li>Revenir sur les quais de <span style=\"color: #d946ef\">Carmin-sur-Mer</span> après avoir débloqué et appris la compétence <span style=\"color: #f6c453\">Surf</span>.</li><li>Naviguer sur l'eau pour atteindre la zone cachée où repose le véhicule.</li></ul><div><br></div></div><div><br></div>",
|
||||
"html": "<h3>Obtenir Mew :</h3><div><ul><li>Obtenir la CS <span style=\"color: #f6c453\">Coupe </span>à l'intérieur du bateau le <span style=\"color: #d946ef\">S.S. Anne</span>.</li><li>Faire exprès de perdre un combat (tomber K.O.) pour réapparaître au Centre Pokémon sans faire partir le navire.</li><li>Revenir sur les quais de <span style=\"color: #d946ef\">Carmin-sur-Mer</span> après avoir débloqué et appris la compétence <span style=\"color: #f6c453\">Surf</span>.</li><li>Naviguer sur l'eau pour atteindre la zone cachée où repose le véhicule.</li></ul></div><div><br></div><p></p>",
|
||||
"text": "Obtenir Mew :\n- Obtenir la CS Coupe à l'intérieur du bateau le S.S. Anne.\n- Faire exprès de perdre un combat (tomber K.O.) pour réapparaître au Centre Pokémon sans faire partir le navire.\n- Revenir sur les quais de Carmin-sur-Mer après avoir débloqué et appris la compétence Surf.\n- Naviguer sur l'eau pour atteindre la zone cachée où repose le véhicule.",
|
||||
"updatedAt": "2026-08-03T08:27:16.932Z",
|
||||
"updatedAt": "2026-08-05T14:24:01.662Z",
|
||||
"drawingMode": "permanent",
|
||||
"drawings": {
|
||||
"strokes": [
|
||||
|
|
@ -1295,7 +1300,6 @@
|
|||
"id": "z4",
|
||||
"label": "Boss Mondial",
|
||||
"type": "daily_time",
|
||||
"autoRefresh": true,
|
||||
"time": "14:00:00",
|
||||
"targetAt": 1785758400000
|
||||
},
|
||||
|
|
@ -1304,12 +1308,22 @@
|
|||
"label": "Legion",
|
||||
"type": "interval",
|
||||
"intervalMs": 1800000,
|
||||
"anchorAt": 1785746172680
|
||||
"anchorAt": 1785746172680,
|
||||
"targetAt": 1785747972680,
|
||||
"autoRefresh": true
|
||||
},
|
||||
{
|
||||
"id": "z6",
|
||||
"label": "Durée",
|
||||
"type": "duration",
|
||||
"durationMs": 1800000,
|
||||
"targetAt": 1785767200357
|
||||
}
|
||||
]
|
||||
},
|
||||
"ma": {
|
||||
"lastResetAt": 1785708000000,
|
||||
"lastResetAt": 1786226400000,
|
||||
"hideCompleted": true,
|
||||
"tasks": [
|
||||
{
|
||||
"id": "a1",
|
||||
|
|
@ -1339,8 +1353,6 @@
|
|||
"id": "a5",
|
||||
"title": "Faire les boss du Sanctuaire Noir (Solo)",
|
||||
"type": "weekly",
|
||||
"checked": true,
|
||||
"checkedAt": 1785746891981,
|
||||
"weeklyResetDay": 0
|
||||
},
|
||||
{
|
||||
|
|
@ -1358,7 +1370,9 @@
|
|||
{
|
||||
"id": "a8",
|
||||
"title": "Ameliorer le bateau",
|
||||
"type": "unique"
|
||||
"type": "unique",
|
||||
"checked": true,
|
||||
"checkedAt": 1785789855306
|
||||
}
|
||||
],
|
||||
"categoryOrder": [
|
||||
|
|
@ -4121,6 +4135,178 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"mc": {
|
||||
"types": [
|
||||
{
|
||||
"id": "y1",
|
||||
"title": "Casque",
|
||||
"icon": "helmet"
|
||||
}
|
||||
],
|
||||
"equipments": [
|
||||
{
|
||||
"id": "q1",
|
||||
"typeId": "y1",
|
||||
"name": "Nu Udra γ",
|
||||
"icon": "helmet",
|
||||
"obtain": "Avoir un certificat Alpha supreme",
|
||||
"characteristics": [
|
||||
{
|
||||
"id": "v1",
|
||||
"name": "Défense",
|
||||
"icon": "shield",
|
||||
"value": 68
|
||||
},
|
||||
{
|
||||
"id": "v2",
|
||||
"name": "Resistance au feu",
|
||||
"icon": "shield",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"id": "v3",
|
||||
"name": "Resistance a l'eau",
|
||||
"icon": "shield",
|
||||
"value": -4
|
||||
},
|
||||
{
|
||||
"id": "v4",
|
||||
"name": "Resistance à la foudre",
|
||||
"icon": "shield",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"id": "v5",
|
||||
"name": "Resistance à la glace",
|
||||
"icon": "shield",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"id": "v6",
|
||||
"name": "Resistance au dragon",
|
||||
"icon": "shield",
|
||||
"value": 0
|
||||
}
|
||||
],
|
||||
"socketItems": [
|
||||
{
|
||||
"id": "j1",
|
||||
"name": "Joyau antivirus",
|
||||
"shape": "jewel",
|
||||
"color": "violet",
|
||||
"bonuses": [
|
||||
{
|
||||
"id": "b1",
|
||||
"name": "Antivirus",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "j2",
|
||||
"name": "Joyau antivirus",
|
||||
"shape": "jewel",
|
||||
"color": "violet",
|
||||
"bonuses": [
|
||||
{
|
||||
"id": "b2",
|
||||
"name": "Antivirus",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "j3",
|
||||
"name": "Joyau antivirus",
|
||||
"shape": "jewel",
|
||||
"color": "violet",
|
||||
"bonuses": [
|
||||
{
|
||||
"id": "b3",
|
||||
"name": "Antivirus",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "q2",
|
||||
"typeId": "y1",
|
||||
"name": "Nu Udra γ",
|
||||
"icon": "chest-armor",
|
||||
"characteristics": [
|
||||
{
|
||||
"id": "v7",
|
||||
"name": "Défense",
|
||||
"icon": "shield",
|
||||
"value": 68
|
||||
},
|
||||
{
|
||||
"id": "v8",
|
||||
"name": "Resistance au feu",
|
||||
"icon": "shield",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"id": "v9",
|
||||
"name": "Resistance a l'eau",
|
||||
"icon": "shield",
|
||||
"value": -4
|
||||
},
|
||||
{
|
||||
"id": "va",
|
||||
"name": "Resistance à la foudre",
|
||||
"icon": "shield",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"id": "vb",
|
||||
"name": "Resistance à la glace",
|
||||
"icon": "shield",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"id": "vc",
|
||||
"name": "Resistance au dragon",
|
||||
"icon": "shield",
|
||||
"value": 0
|
||||
}
|
||||
],
|
||||
"socketItems": [
|
||||
{
|
||||
"id": "j4",
|
||||
"name": "Joyau Mise a mort",
|
||||
"shape": "jewel",
|
||||
"color": "indigo",
|
||||
"bonuses": [
|
||||
{
|
||||
"id": "b4",
|
||||
"name": "Mise a mort",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "j5",
|
||||
"name": "Joyau Attaque",
|
||||
"shape": "jewel",
|
||||
"color": "red",
|
||||
"bonuses": [
|
||||
{
|
||||
"id": "b5",
|
||||
"name": "Attaque",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"typeOrder": [
|
||||
"y1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -505,6 +505,75 @@
|
|||
"removeRelationTitle": "Retirer",
|
||||
"emptyTasks": "Aucune tâche planifiée."
|
||||
},
|
||||
"equipmentPlanner": {
|
||||
"equipmentIconLabel": "Type",
|
||||
"typePlaceholder": "Type d'équipement",
|
||||
"equipmentPlaceholder": "Nouvel équipement",
|
||||
"addEquipmentButton": "Ajouter",
|
||||
"summaryTitle": "Résumé",
|
||||
"activeEquipmentsSingular": "équipement actif",
|
||||
"activeEquipmentsPlural": "équipements actifs",
|
||||
"numericTotalsTitle": "Totaux",
|
||||
"equipmentTotalsTitle": "Statistiques de l'équipement",
|
||||
"socketTotalsTitle": "Objets sertis",
|
||||
"mixedTotalsTitle": "Totaux fusionnés",
|
||||
"summaryTooltipTitle": "Provenance",
|
||||
"textBonusesTitle": "Bonus",
|
||||
"emptyTotals": "Aucun total numérique.",
|
||||
"emptyTextBonuses": "Aucun bonus non numérique.",
|
||||
"emptyEquipments": "Aucun équipement planifié.",
|
||||
"activeLabel": "Actif",
|
||||
"includeInSummaryLabel": "Inclure dans le résumé",
|
||||
"reorderTypeTitle": "Déplacer le type",
|
||||
"renameTypeTitle": "Renommer le type",
|
||||
"showTypeTitle": "Afficher ce type",
|
||||
"hideTypeTitle": "Réduire ce type",
|
||||
"reorderEquipmentTitle": "Déplacer",
|
||||
"renameEquipmentTitle": "Renommer",
|
||||
"showDetailsTitle": "Afficher les détails",
|
||||
"hideDetailsTitle": "Masquer les détails",
|
||||
"deleteEquipmentTitle": "Supprimer",
|
||||
"tabsLabel": "Ajout de caractéristiques",
|
||||
"statsTab": "Stats / affixe",
|
||||
"socketsTab": "Objets à sertir",
|
||||
"craftTab": "Craft",
|
||||
"displayStatsTitle": "Statistiques",
|
||||
"displaySocketTitle": "Objets sertis",
|
||||
"editModeTitle": "Mode édition",
|
||||
"viewModeTitle": "Mode affichage",
|
||||
"editModeButton": "Éditer",
|
||||
"viewModeButton": "Affichage",
|
||||
"obtainLabel": "Obtention",
|
||||
"obtainPlaceholder": "Comment obtenir ou créer cet équipement",
|
||||
"emptyObtain": "Aucune obtention renseignée.",
|
||||
"characteristicsTitle": "Caractéristiques",
|
||||
"traitIconLabel": "Icône",
|
||||
"categoryPlaceholder": "Catégorie",
|
||||
"traitNamePlaceholder": "Nom",
|
||||
"traitValuePlaceholder": "Valeur",
|
||||
"addTraitButton": "Ajouter",
|
||||
"reorderTraitTitle": "Déplacer",
|
||||
"reorderCategoryTitle": "Déplacer la catégorie",
|
||||
"deleteTraitTitle": "Supprimer",
|
||||
"emptyCharacteristics": "Aucune caractéristique.",
|
||||
"materialsTitle": "Matériaux",
|
||||
"materialNamePlaceholder": "Matériau",
|
||||
"materialQtyLabel": "Quantité",
|
||||
"addMaterialButton": "Ajouter",
|
||||
"deleteMaterialTitle": "Supprimer",
|
||||
"socketItemsTitle": "Objets sertis",
|
||||
"emptySocketItems": "Aucun objet serti.",
|
||||
"socketNamePlaceholder": "Joyau, materia...",
|
||||
"socketShapeLabel": "Forme",
|
||||
"socketColorLabel": "Couleur",
|
||||
"addSocketButton": "Ajouter",
|
||||
"bonusNamePlaceholder": "Bonus",
|
||||
"addBonusButton": "Ajouter",
|
||||
"deleteSocketTitle": "Supprimer",
|
||||
"deleteBonusTitle": "Supprimer",
|
||||
"addSocketLinkLabel": "Lier à",
|
||||
"deleteSocketLinkTitle": "Retirer le lien"
|
||||
},
|
||||
"imageAnnotation": {
|
||||
"addImage": "Ajouter une image",
|
||||
"replaceImage": "Remplacer l'image",
|
||||
|
|
|
|||
5
website/public/static/icons/ball.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="#323232" stroke-width="2"/>
|
||||
<path d="M8.5 8.5C7.83333 9.16667 7.5 10.1 7.5 11.3C7.5 12.1837 7.85804 13.0316 8.4948 13.6508L10.2 15.3" stroke="#323232" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 497 B |
2
website/public/static/icons/boot.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;stroke:#020202;stroke-miterlimit:10;stroke-width:1.91px;}</style></defs><rect class="cls-1" x="2.45" y="18.68" width="19.09" height="3.82"/><path class="cls-1" d="M7.23,12h1a0,0,0,0,1,0,0v6.68a0,0,0,0,1,0,0H2.45a0,0,0,0,1,0,0V16.77A4.77,4.77,0,0,1,7.23,12Z"/><line class="cls-1" x1="6.27" y1="20.59" x2="6.27" y2="22.5"/><line class="cls-1" x1="10.09" y1="20.59" x2="10.09" y2="22.5"/><line class="cls-1" x1="13.91" y1="20.59" x2="13.91" y2="22.5"/><line class="cls-1" x1="17.73" y1="20.59" x2="17.73" y2="22.5"/><path class="cls-1" d="M21.55,18.68V6.27a1.92,1.92,0,0,0-1.91-1.91H18.59a5.16,5.16,0,0,1-4.31-2.31h0a1.26,1.26,0,0,0-1-.55h0A1.25,1.25,0,0,0,12,2.74V8.18A3.82,3.82,0,0,1,8.18,12h0"/><line class="cls-1" x1="12" y1="7.23" x2="15.82" y2="7.23"/><line class="cls-1" x1="11.09" y1="11.05" x2="15.82" y2="11.05"/><path class="cls-1" d="M16.77,18.68a4.78,4.78,0,0,1,4.78-4.77"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
8
website/public/static/icons/chest-armor.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:serif="http://www.serif.com/" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Icon">
|
||||
<path d="M25,21.5c0,-0.319 -0.152,-0.619 -0.409,-0.807c-0.258,-0.188 -0.589,-0.243 -0.893,-0.146l-7.698,2.44c-0,0 -7.698,-2.44 -7.698,-2.44c-0.304,-0.097 -0.635,-0.042 -0.893,0.146c-0.257,0.188 -0.409,0.488 -0.409,0.807l0,6c0,0.552 0.448,1 1,1l16,0c0.552,0 1,-0.448 1,-1l0,-6Zm-2,1.366l0,3.634l-14,0c0,-0 0,-3.634 0,-3.634c0,0 6.698,2.123 6.698,2.123c0.196,0.063 0.408,0.063 0.604,0l6.698,-2.123Zm-2.002,-14.31c0.02,-0.341 -0.137,-0.668 -0.414,-0.868c-0.278,-0.199 -0.638,-0.243 -0.955,-0.116l-2.5,1c-0.38,0.151 -0.629,0.519 -0.629,0.928l0,11c0,0.317 0.151,0.616 0.406,0.804c0.255,0.189 0.585,0.245 0.888,0.152l6.5,-2c0.42,-0.129 0.706,-0.517 0.706,-0.956l0,-6c0,-0.552 -0.448,-1 -1,-1c-0.892,0 -1.663,-0.246 -2.203,-0.739c-0.516,-0.472 -0.797,-1.166 -0.797,-2.02c0,-0.062 -0.005,-0.124 -0.002,-0.185Zm-8.627,-0.984c-0.317,-0.127 -0.677,-0.083 -0.955,0.116c-0.277,0.2 -0.434,0.527 -0.414,0.868c0.003,0.061 -0.002,0.123 -0.002,0.185c0,0.854 -0.281,1.548 -0.797,2.02c-0.54,0.493 -1.311,0.739 -2.203,0.739c-0.552,0 -1,0.448 -1,1l0,6c0,0.439 0.286,0.827 0.706,0.956l6.5,2c0.303,0.093 0.633,0.037 0.888,-0.152c0.255,-0.188 0.406,-0.487 0.406,-0.804l0,-11c0,-0.409 -0.249,-0.777 -0.629,-0.928l-2.5,-1Zm6.756,2.354c0.21,0.942 0.675,1.72 1.32,2.31c0.666,0.609 1.537,1.023 2.553,1.186c0,0 0,4.339 0,4.339c0,0 -4.5,1.385 -4.5,1.385c0,0 0,-8.969 0,-8.969l0.627,-0.251Zm-6.254,0l0.627,0.251c0,0 0,8.969 0,8.969c-0,0 -4.5,-1.385 -4.5,-1.385c0,0 0,-4.339 0,-4.339c1.016,-0.163 1.887,-0.577 2.553,-1.186c0.645,-0.59 1.11,-1.368 1.32,-2.31Zm-1.892,-5.23c0.058,-0.294 -0.018,-0.598 -0.208,-0.83c-0.19,-0.232 -0.473,-0.366 -0.773,-0.366c-1.611,0 -3.965,1.17 -5.569,2.638c-1.191,1.089 -1.931,2.354 -1.931,3.362c0,0.552 0.448,1 1,1l5.5,0l0.981,-0.804l1,-5Zm11.019,-1.196c-0.3,0 -0.583,0.134 -0.773,0.366c-0.19,0.232 -0.266,0.536 -0.208,0.83l1,5l0.981,0.804l5.5,0c0.552,0 1,-0.448 1,-1c-0,-1.008 -0.74,-2.273 -1.931,-3.362c-1.604,-1.468 -3.958,-2.638 -5.569,-2.638Zm-13.82,5l-3.216,0c0.222,-0.299 0.501,-0.598 0.816,-0.886c0.847,-0.775 1.944,-1.485 2.948,-1.852l-0.548,2.738Zm15.64,0l-0.548,-2.738c1.004,0.367 2.101,1.078 2.948,1.852c0.315,0.288 0.594,0.587 0.816,0.886l-3.216,0Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
29
website/public/static/icons/earring.svg
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M149.299,181.001c-2.202-3.311-5.163-5.931-8.499-7.868V74.445c14.865-5.299,25.6-19.379,25.6-36.045
|
||||
C166.4,17.229,149.171,0,128,0S89.6,17.229,89.6,38.4c0,16.666,10.735,30.737,25.6,36.045v98.697
|
||||
c-3.337,1.937-6.298,4.557-8.499,7.868C97.084,195.413,12.8,323.951,12.8,396.8C12.8,460.322,64.478,512,128,512
|
||||
s115.2-51.678,115.2-115.2C243.2,323.951,158.916,195.413,149.299,181.001z M128,25.6c7.066,0,12.8,5.726,12.8,12.8
|
||||
c0,7.066-5.734,12.8-12.8,12.8s-12.8-5.734-12.8-12.8C115.2,31.326,120.934,25.6,128,25.6z M128,198.076c0,0,64,96,64,144
|
||||
c0,35.354-28.655,64-64,64s-64-28.655-64-64C64,294.076,128,198.076,128,198.076z M128,486.4c-49.485,0-89.6-40.115-89.6-89.6
|
||||
c0-8.713,1.553-18.586,4.181-29.116c11.093,36.881,44.971,63.991,85.419,63.991s74.325-27.11,85.419-63.991
|
||||
c2.62,10.53,4.181,20.403,4.181,29.116C217.6,446.285,177.485,486.4,128,486.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M405.299,181.001c-2.21-3.311-5.163-5.931-8.499-7.868V74.445c14.865-5.299,25.6-19.379,25.6-36.045
|
||||
C422.4,17.229,405.171,0,384,0c-21.171,0-38.4,17.229-38.4,38.4c0,16.666,10.735,30.737,25.6,36.045v98.697
|
||||
c-3.336,1.937-6.298,4.557-8.499,7.868C353.084,195.413,268.8,323.951,268.8,396.8c0,63.522,51.678,115.2,115.2,115.2
|
||||
s115.2-51.678,115.2-115.2C499.2,323.951,414.916,195.413,405.299,181.001z M384,25.6c7.066,0,12.8,5.726,12.8,12.8
|
||||
c0,7.066-5.734,12.8-12.8,12.8c-7.066,0-12.8-5.734-12.8-12.8C371.2,31.326,376.934,25.6,384,25.6z M384,198.076c0,0,64,96,64,144
|
||||
c0,35.345-28.655,64-64,64c-35.345,0-64-28.655-64-64C320,294.076,384,198.076,384,198.076z M384,486.4
|
||||
c-49.485,0-89.6-40.115-89.6-89.6c0-8.713,1.553-18.586,4.181-29.116c11.093,36.881,44.971,63.991,85.419,63.991
|
||||
c40.448,0,74.325-27.119,85.419-63.991c2.62,10.53,4.181,20.403,4.181,29.116C473.6,446.285,433.485,486.4,384,486.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
2
website/public/static/icons/glove.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;stroke:#020202;stroke-miterlimit:10;stroke-width:1.91px;}</style></defs><path class="cls-1" d="M20.55,8.18v10.5H7.18L2.05,13.55a2,2,0,0,1,2.86-2.87l.64.65a1,1,0,0,0,1.63-.68V8.46a6.9,6.9,0,0,1,6.9-7,6.7,6.7,0,0,1,6.47,6.68Z"/><rect class="cls-1" x="7.18" y="18.68" width="13.36" height="3.82"/></svg>
|
||||
|
After Width: | Height: | Size: 571 B |
2
website/public/static/icons/hammer.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;stroke:#020202;stroke-miterlimit:10;stroke-width:1.95px;}</style></defs><rect class="cls-1" x="10.39" y="2.21" width="7.6" height="15.19" transform="translate(-2.78 12.91) rotate(-45)"/><path class="cls-1" d="M13,14,4.79,22.14a2,2,0,0,1-1.47.61,2.07,2.07,0,0,1-2.07-2.07,2,2,0,0,1,.61-1.47L10,11Z"/><path class="cls-1" d="M20.41,5.66a2.05,2.05,0,0,1-.6,1.46L18.34,8.59,15.41,5.66l1.47-1.47a2.07,2.07,0,0,1,3.53,1.47Z"/></svg>
|
||||
|
After Width: | Height: | Size: 696 B |
2
website/public/static/icons/helmet.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.553,16.105A1,1,0,0,0,13,17v3a1,1,0,0,0,.293.707l2,2a1,1,0,0,0,1.154.188l4-2A1,1,0,0,0,21,20V10A9,9,0,0,0,3,10V20a1,1,0,0,0,.553.895l4,2a1,1,0,0,0,1.154-.188l2-2A1,1,0,0,0,11,20V17a1,1,0,0,0-.553-.895L9,15.382V12.618L11.553,13.9a1,1,0,0,0,.894,0L15,12.618v2.764Zm2.972-5.956a1,1,0,0,0-.972-.044L12,11.882,8.447,10.105A1,1,0,0,0,7,11v5a1,1,0,0,0,.553.895L9,17.618v1.968l-1.2,1.2L5,19.382V10a7,7,0,0,1,14,0v9.382l-2.8,1.4-1.2-1.2V17.618l1.447-.723A1,1,0,0,0,17,16V11A1,1,0,0,0,16.525,10.149ZM15,7a1,1,0,0,1-1,1H10a1,1,0,0,1,0-2h4A1,1,0,0,1,15,7Z"/></svg>
|
||||
|
After Width: | Height: | Size: 788 B |
5
website/public/static/icons/jewel.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.8057 5.70615C5.39093 4.87011 5.68354 4.45209 6.11769 4.22604C6.55184 4 7.0621 4 8.08262 4H12H15.9174C16.9379 4 17.4482 4 17.8823 4.22604C18.3165 4.45209 18.6091 4.87011 19.1943 5.70615L19.7915 6.55926C20.6144 7.73493 21.0259 8.32277 21.0064 8.98546C20.9869 9.64815 20.5415 10.2107 19.6507 11.3359L14.375 18V18C13.6417 18.9263 13.275 19.3895 12.8472 19.5895C12.3103 19.8406 11.6897 19.8406 11.1528 19.5895C10.725 19.3895 10.3583 18.9263 9.625 18V18L4.34927 11.3359C3.4585 10.2107 3.01312 9.64815 2.99359 8.98546C2.97407 8.32277 3.38555 7.73493 4.20852 6.55926L4.8057 5.70615Z" stroke="#323232" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M9 7.5L8.5 8.25V8.25C8.20344 8.69484 8.23479 9.28176 8.57706 9.69247L10.5 12" stroke="#323232" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
18
website/public/static/icons/necklace.svg
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M488.515,0c-9.172,0-16.608,7.436-16.608,16.608v9.731c0,119.051-96.856,215.907-215.907,215.907
|
||||
S40.093,145.39,40.093,26.34v-9.731C40.093,7.436,32.657,0,23.485,0S6.877,7.436,6.877,16.608v9.731
|
||||
c0,133.596,104.156,240.171,232.515,248.565v42.686c-25.397-14.658-58.489-11.15-80.188,10.55
|
||||
c-25.901,25.9-25.901,68.043,0,93.943l85.052,85.052c6.486,6.486,17.001,6.486,23.487,0l85.052-85.052
|
||||
c25.901-25.9,25.901-68.043,0-93.943c-21.698-21.698-54.79-25.207-80.188-10.55v-42.686
|
||||
c128.419-8.397,232.516-115.032,232.516-248.565v-9.731C505.123,7.436,497.687,0,488.515,0z M282.34,351.628
|
||||
c12.949-12.949,34.019-12.949,46.968,0c12.949,12.949,12.949,34.019,0,46.968L256,471.904l-73.309-73.309
|
||||
c-12.949-12.949-12.949-34.019,0-46.968c6.475-6.475,14.979-9.711,23.484-9.711c8.504,0,17.009,3.237,23.484,9.711l14.594,14.594
|
||||
c6.411,6.413,16.971,6.524,23.493,0L282.34,351.628z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
2
website/public/static/icons/ring.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M3,14A9,9,0,1,0,14.822,5.46l1.019-1.585a1,1,0,0,0,.031-1.032l-.75-1.333A1,1,0,0,0,14.25,1H9.75a1,1,0,0,0-.872.51l-.75,1.333a1,1,0,0,0,.031,1.032L9.178,5.46A9.011,9.011,0,0,0,3,14Zm8.282-8.966L10.167,3.3l.168-.3h3.33l.168.3L12.718,5.034C12.468,5.011,12.23,5,12,5S11.532,5.011,11.282,5.034ZM12,7a7,7,0,1,1-7,7A7.022,7.022,0,0,1,12,7Z"/></svg>
|
||||
|
After Width: | Height: | Size: 573 B |
4
website/public/static/icons/sword.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" version="1.1" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
|
||||
<path d="m2.75 9.25 1.5 2.5 2 1.5m-4.5 0 1 1m1.5-2.5-1.5 1.5m3-1 8.5-8.5v-2h-2l-8.5 8.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
|
|
@ -4,6 +4,7 @@ import { DrawingControls } from "../features/toolboxes/modules/DrawingControls.j
|
|||
import { DrawingOverlay } from "../features/toolboxes/modules/DrawingOverlay.jsx";
|
||||
import { lockBodyScroll } from "../utils/bodyScrollLock.js";
|
||||
import { Icon } from "./Icon.jsx";
|
||||
import { Tooltip } from "./Tooltip.jsx";
|
||||
|
||||
const DRAWING_COLORS = ["#f5f7ff", "#b4bdd3", "#f6c453", "#22d3ee", "#8b5cf6", "#d946ef"];
|
||||
const DRAWING_WIDTHS = [2, 4, 8, 12];
|
||||
|
|
@ -188,14 +189,16 @@ export function ImageViewer({ image, onClose, createMarkerId = () => globalThis.
|
|||
/>
|
||||
)}
|
||||
{viewerMarkers.map((marker, index) => (
|
||||
<span
|
||||
<Tooltip
|
||||
as="span"
|
||||
className="annotation-marker image-viewer-marker"
|
||||
key={marker.id || `${marker.x}-${marker.y}-${index}`}
|
||||
content={marker.label || `${image.markerPrefix || "Marqueur"} ${index + 1}`}
|
||||
position="top"
|
||||
style={{ "--marker-x": `${marker.x}%`, "--marker-y": `${marker.y}%` }}
|
||||
title={marker.label || `${image.markerPrefix || "Marqueur"} ${index + 1}`}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
70
website/src/components/Tooltip.jsx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Rôle : fournit une infobulle accessible et réutilisable pour les contrôles et données denses.
|
||||
import { useEffect, useId, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
export function Tooltip({ as: Component = "span", children, content, className = "", tooltipClassName = "", position = "top", ...props }) {
|
||||
const tooltipId = useId();
|
||||
const anchorRef = useRef(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [style, setStyle] = useState({});
|
||||
|
||||
function placeTooltip() {
|
||||
const anchor = anchorRef.current;
|
||||
if (!anchor) return;
|
||||
const rect = anchor.getBoundingClientRect();
|
||||
if (position === "top-start") {
|
||||
setStyle({ top: `${Math.max(8, rect.top - 8)}px`, left: `${Math.max(8, rect.left)}px` });
|
||||
return;
|
||||
}
|
||||
setStyle({ top: `${Math.max(8, rect.top - 8)}px`, left: `${Math.min(window.innerWidth - 8, Math.max(8, rect.left + rect.width / 2))}px` });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return undefined;
|
||||
placeTooltip();
|
||||
window.addEventListener("resize", placeTooltip);
|
||||
window.addEventListener("scroll", placeTooltip, true);
|
||||
return () => {
|
||||
window.removeEventListener("resize", placeTooltip);
|
||||
window.removeEventListener("scroll", placeTooltip, true);
|
||||
};
|
||||
}, [open, position]);
|
||||
|
||||
if (!content) return <Component className={className} {...props}>{children}</Component>;
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
className={`ui-tooltip-anchor ${className}`.trim()}
|
||||
ref={anchorRef}
|
||||
tabIndex={props.tabIndex ?? 0}
|
||||
aria-describedby={tooltipId}
|
||||
onMouseEnter={(event) => {
|
||||
placeTooltip();
|
||||
setOpen(true);
|
||||
props.onMouseEnter?.(event);
|
||||
}}
|
||||
onMouseLeave={(event) => {
|
||||
setOpen(false);
|
||||
props.onMouseLeave?.(event);
|
||||
}}
|
||||
onFocus={(event) => {
|
||||
placeTooltip();
|
||||
setOpen(true);
|
||||
props.onFocus?.(event);
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
setOpen(false);
|
||||
props.onBlur?.(event);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{open && createPortal(
|
||||
<span id={tooltipId} className={`ui-tooltip ui-tooltip-${position} ${tooltipClassName}`.trim()} role="tooltip" style={style}>
|
||||
{content}
|
||||
</span>,
|
||||
document.body
|
||||
)}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,12 +17,14 @@ import {
|
|||
normalizeChecklistData,
|
||||
normalizeCombosData,
|
||||
normalizeCountersData,
|
||||
normalizeEquipmentPlannerData,
|
||||
normalizeImageAnnotationData,
|
||||
normalizeLinksData,
|
||||
normalizeNotepadData,
|
||||
normalizeTableData,
|
||||
normalizeTaskPlannerData,
|
||||
normalizeTimerData,
|
||||
summarizeEquipmentPlannerData,
|
||||
normalizeUrl,
|
||||
uid
|
||||
} from "./storage/toolboxStorage.js";
|
||||
|
|
@ -152,12 +154,14 @@ function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, sto
|
|||
normalizeCombosData,
|
||||
normalizeLinksData,
|
||||
normalizeCountersData,
|
||||
normalizeEquipmentPlannerData,
|
||||
normalizeCalculatorData,
|
||||
normalizeImageAnnotationData,
|
||||
normalizeNotepadData,
|
||||
normalizeTableData,
|
||||
normalizeTimerData,
|
||||
normalizeTaskPlannerData,
|
||||
summarizeEquipmentPlannerData,
|
||||
normalizeUrl,
|
||||
hostnameFromUrl,
|
||||
copyText,
|
||||
|
|
|
|||
1057
website/src/features/toolboxes/modules/EquipmentPlannerModule.jsx
Normal file
|
|
@ -1,6 +1,7 @@
|
|||
// Rôle : fournit l'outil annotation d'images avec marqueurs éditables.
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Icon } from "../../../components/Icon.jsx";
|
||||
import { Tooltip } from "../../../components/Tooltip.jsx";
|
||||
import { DrawingOverlay } from "./DrawingOverlay.jsx";
|
||||
|
||||
function markerLabel(index, marker, textContent) {
|
||||
|
|
@ -160,14 +161,16 @@ export function ImageAnnotationModule({ toolboxId, moduleId, context, editing })
|
|||
)}
|
||||
</div>
|
||||
{markers.map((marker, index) => (
|
||||
<span
|
||||
<Tooltip
|
||||
as="span"
|
||||
key={marker.id}
|
||||
className="annotation-marker"
|
||||
content={markerLabel(index, marker, textContent)}
|
||||
position="top"
|
||||
style={{ "--marker-x": `${marker.x}%`, "--marker-y": `${marker.y}%` }}
|
||||
title={markerLabel(index, marker, textContent)}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
</Tooltip>
|
||||
))}
|
||||
<DrawingOverlay
|
||||
active={false}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Icon } from "../../../components/Icon.jsx";
|
||||
import { InlineNotice } from "../../../components/AppOverlays.jsx";
|
||||
import { Tooltip } from "../../../components/Tooltip.jsx";
|
||||
import { useDraftForm } from "../../../hooks/useDraftForm.js";
|
||||
import { applyGroupedReorderOperation, useGroupedReorder } from "../../../hooks/useGroupedReorder.js";
|
||||
import { useInlineEdit } from "../../../hooks/useInlineEdit.js";
|
||||
|
|
@ -867,9 +868,13 @@ function TaskPlannerItem({
|
|||
{TASK_TYPES.map((taskType) => <option key={taskType} value={taskType}>{getTaskTypeLabel(taskType, textContent)}</option>)}
|
||||
</select>
|
||||
{missingPrerequisites.length > 0 && (
|
||||
<span className="task-planner-warning" title={textContent.missingPrerequisiteTitle || "Pré requis non effectué"}>
|
||||
<Tooltip
|
||||
className="task-planner-warning"
|
||||
content={<MissingPrerequisitesTooltip missingPrerequisites={missingPrerequisites} tasks={tasks} textContent={textContent} />}
|
||||
position="top-start"
|
||||
>
|
||||
{textContent.missingPrerequisiteBadge || "Pré requis"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div className="task-planner-actions">
|
||||
<button
|
||||
|
|
@ -947,6 +952,19 @@ function TaskPlannerItem({
|
|||
);
|
||||
}
|
||||
|
||||
function MissingPrerequisitesTooltip({ missingPrerequisites, tasks, textContent }) {
|
||||
return (
|
||||
<span className="task-planner-prerequisite-tooltip">
|
||||
<strong>{textContent.missingPrerequisiteTitle || "Pré requis non effectué"}</strong>
|
||||
<span>
|
||||
{missingPrerequisites.map((relation) => (
|
||||
<span key={relation.id}>{getTaskTitle(tasks, relation.toTaskId)}</span>
|
||||
))}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
|
||||
if (task.type === "daily") {
|
||||
const hasOverride = Boolean(task.dailyResetTime);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { ChecklistModule } from "./ChecklistModule.jsx";
|
|||
import { CombosModule } from "./CombosModule.jsx";
|
||||
import { CountersModule } from "./CountersModule.jsx";
|
||||
import { ImageAnnotationModule } from "./ImageAnnotationModule.jsx";
|
||||
import { EquipmentPlannerModule } from "./EquipmentPlannerModule.jsx";
|
||||
import { LinksModule } from "./LinksModule.jsx";
|
||||
import { NotepadModule } from "./NotepadModule.jsx";
|
||||
import { ImagesModule } from "./ImagesModule.jsx";
|
||||
|
|
@ -29,6 +30,7 @@ const MODULE_COMPONENTS = {
|
|||
table: { label: "Tableau", icon: "table", Component: TableModule, editable: false, scrollable: true },
|
||||
timer: { label: "Timer", icon: "clock", Component: TimerModule, editable: false },
|
||||
taskPlanner: { label: "Planificateur de tâches", icon: "tasklist", Component: TaskPlannerModule, editable: true, scrollable: true },
|
||||
equipmentPlanner: { label: "Planificateur d'équipements", icon: "chest-armor", Component: EquipmentPlannerModule, editable: true, scrollable: true },
|
||||
imageAnnotation: { label: "Annotation d'images", icon: "map", Component: ImageAnnotationModule, editable: true }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,27 @@
|
|||
// Rôle : normalise, compacte et sérialise les données toolbox persistées.
|
||||
const ID_PREFIXES = { tbx: "t", mod: "m", item: "i", section: "g", image: "s", link: "l", counter: "c", combo: "o", calc: "r", marker: "k", timer: "z", task: "a", relation: "e", stroke: "d" };
|
||||
const ID_PREFIXES = {
|
||||
tbx: "t",
|
||||
mod: "m",
|
||||
item: "i",
|
||||
section: "g",
|
||||
image: "s",
|
||||
link: "l",
|
||||
counter: "c",
|
||||
combo: "o",
|
||||
calc: "r",
|
||||
marker: "k",
|
||||
timer: "z",
|
||||
task: "a",
|
||||
relation: "e",
|
||||
stroke: "d",
|
||||
equipmentType: "y",
|
||||
equipment: "q",
|
||||
trait: "v",
|
||||
material: "n",
|
||||
socketItem: "j",
|
||||
socketLink: "u",
|
||||
socketBonus: "b"
|
||||
};
|
||||
const ID_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const NOTEPAD_ALLOWED_COLORS = new Set(["#f5f7ff", "#b4bdd3", "#f6c453", "#22d3ee", "#8b5cf6", "#d946ef", "#101426", "#202745"]);
|
||||
const NOTEPAD_ALLOWED_TAGS = new Set(["p", "div", "br", "strong", "b", "em", "i", "u", "s", "strike", "ul", "ol", "li", "h3", "h4", "span", "mark"]);
|
||||
|
|
@ -21,6 +43,10 @@ const TIMER_MIN_AUTO_REFRESH_MS = 5 * 60 * 1000;
|
|||
const TASK_TYPES = new Set(["unique", "daily", "weekly"]);
|
||||
const COMBO_DEVICES = new Set(["playstation", "xbox", "switch", "n64", "keyboardMouse"]);
|
||||
const COMBO_INPUT_KINDS = new Set(["button", "direction", "key", "mouse"]);
|
||||
const EQUIPMENT_SOCKET_SHAPES = new Set(["ball", "jewel"]);
|
||||
const EQUIPMENT_SOCKET_COLORS = new Set(["red", "orange", "amber", "yellow", "lime", "green", "teal", "cyan", "blue", "indigo", "violet", "purple", "pink", "rose", "white", "gray", "black"]);
|
||||
const EQUIPMENT_TYPE_ICONS = new Set(["boot", "chest-armor", "helmet", "glove", "shield", "sword", "gun", "earring", "necklace", "ring"]);
|
||||
const EQUIPMENT_TRAIT_ICONS = new Set(["sword", "shield"]);
|
||||
const DEFAULT_TASK_PLANNER_WEEKLY_RESET_DAY = 1;
|
||||
const DEFAULT_TASK_PLANNER_RESET_TIME = "00:00";
|
||||
const TOOLBOX_ICON_BASE = "/static/img/toolbox-icons/";
|
||||
|
|
@ -56,6 +82,7 @@ const DEFAULT_MODULE_TITLES = {
|
|||
table: "Tableau",
|
||||
timer: "Timer",
|
||||
taskPlanner: "Planificateur de tâches",
|
||||
equipmentPlanner: "Planificateur d'équipements",
|
||||
imageAnnotation: "Annotation d'images"
|
||||
};
|
||||
|
||||
|
|
@ -678,6 +705,270 @@ export function normalizeTaskPlannerData(data) {
|
|||
};
|
||||
}
|
||||
|
||||
function normalizeEquipmentPlannerType(type) {
|
||||
const title = String(type?.title || "").trim().slice(0, 80);
|
||||
if (!title) return null;
|
||||
const icon = EQUIPMENT_TYPE_ICONS.has(type?.icon) ? type.icon : "shield";
|
||||
return {
|
||||
id: type?.id || uid("equipmentType"),
|
||||
title,
|
||||
icon,
|
||||
collapsed: type?.collapsed === true
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeEquipmentPlannerValue(value) {
|
||||
if (typeof value === "number") return Number.isFinite(value) ? Math.round(value * 1000) / 1000 : "";
|
||||
const text = String(value ?? "").trim().slice(0, 80);
|
||||
if (!text) return "";
|
||||
const numeric = Number(text.replace(",", "."));
|
||||
return Number.isFinite(numeric) && /^[-+]?\d+(?:[,.]\d+)?$/.test(text) ? Math.round(numeric * 1000) / 1000 : text;
|
||||
}
|
||||
|
||||
function normalizeEquipmentPlannerTrait(trait, prefix = "trait") {
|
||||
const name = String(trait?.name || "").trim().slice(0, 80);
|
||||
if (!name) return null;
|
||||
return {
|
||||
id: trait?.id || uid(prefix),
|
||||
category: String(trait?.category || "").trim().slice(0, 80),
|
||||
icon: EQUIPMENT_TRAIT_ICONS.has(trait?.icon) ? trait.icon : "sword",
|
||||
name,
|
||||
value: normalizeEquipmentPlannerValue(trait?.value)
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeEquipmentPlannerMaterial(material) {
|
||||
const name = String(material?.name || "").trim().slice(0, 80);
|
||||
if (!name) return null;
|
||||
return {
|
||||
id: material?.id || uid("material"),
|
||||
name,
|
||||
qty: Math.max(1, parsePositiveInt(material?.qty, 1))
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeEquipmentPlannerSocketItem(socketItem) {
|
||||
const name = String(socketItem?.name || "").trim().slice(0, 80);
|
||||
if (!name) return null;
|
||||
const shape = EQUIPMENT_SOCKET_SHAPES.has(socketItem?.shape) ? socketItem.shape : "jewel";
|
||||
const color = EQUIPMENT_SOCKET_COLORS.has(socketItem?.color) ? socketItem.color : "yellow";
|
||||
return {
|
||||
id: socketItem?.id || uid("socketItem"),
|
||||
name,
|
||||
shape,
|
||||
color,
|
||||
bonuses: (Array.isArray(socketItem?.bonuses) ? socketItem.bonuses : [])
|
||||
.map((bonus) => normalizeEquipmentPlannerTrait(bonus, "socketBonus"))
|
||||
.filter(Boolean)
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeEquipmentPlannerCategoryOrder(value, categories) {
|
||||
const available = new Set(categories.filter(Boolean));
|
||||
const ordered = [];
|
||||
(Array.isArray(value) ? value : []).forEach((category) => {
|
||||
const normalized = String(category || "").trim().slice(0, 80);
|
||||
if (normalized && available.has(normalized) && !ordered.includes(normalized)) ordered.push(normalized);
|
||||
});
|
||||
categories.forEach((category) => {
|
||||
if (category && !ordered.includes(category)) ordered.push(category);
|
||||
});
|
||||
return ordered;
|
||||
}
|
||||
|
||||
function normalizeEquipmentPlannerEquipment(equipment, typeIds) {
|
||||
const name = String(equipment?.name || "").trim().slice(0, 100);
|
||||
const typeId = String(equipment?.typeId || "");
|
||||
if (!name || !typeIds.has(typeId)) return null;
|
||||
const characteristics = (Array.isArray(equipment?.characteristics) ? equipment.characteristics : [])
|
||||
.map((trait) => normalizeEquipmentPlannerTrait(trait, "trait"))
|
||||
.filter(Boolean);
|
||||
const socketItems = (Array.isArray(equipment?.socketItems) ? equipment.socketItems : [])
|
||||
.map(normalizeEquipmentPlannerSocketItem)
|
||||
.filter(Boolean);
|
||||
const socketItemIds = new Set(socketItems.map((socketItem) => socketItem.id));
|
||||
const socketLinks = (Array.isArray(equipment?.socketLinks) ? equipment.socketLinks : [])
|
||||
.map((link) => {
|
||||
const fromSocketItemId = String(link?.fromSocketItemId || "");
|
||||
const toSocketItemId = String(link?.toSocketItemId || "");
|
||||
if (!socketItemIds.has(fromSocketItemId) || !socketItemIds.has(toSocketItemId) || fromSocketItemId === toSocketItemId) return null;
|
||||
const pairKey = [fromSocketItemId, toSocketItemId].sort().join(":");
|
||||
return {
|
||||
id: link?.id || uid("socketLink"),
|
||||
fromSocketItemId,
|
||||
toSocketItemId,
|
||||
pairKey
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
.filter((link, index, links) => links.findIndex((item) => item.pairKey === link.pairKey) === index)
|
||||
.map(({ pairKey, ...link }) => link);
|
||||
const categories = [
|
||||
...characteristics.map((trait) => trait.category),
|
||||
...socketItems.flatMap((socketItem) => socketItem.bonuses.map((bonus) => bonus.category))
|
||||
].filter(Boolean);
|
||||
const categoryOrder = normalizeEquipmentPlannerCategoryOrder(equipment?.categoryOrder, categories);
|
||||
return {
|
||||
id: equipment?.id || uid("equipment"),
|
||||
typeId,
|
||||
name,
|
||||
icon: EQUIPMENT_TYPE_ICONS.has(equipment?.icon) ? equipment.icon : "shield",
|
||||
active: equipment?.active !== false,
|
||||
obtain: String(equipment?.obtain || "").trim().slice(0, 500),
|
||||
characteristics,
|
||||
socketItems,
|
||||
socketLinks,
|
||||
materials: (Array.isArray(equipment?.materials) ? equipment.materials : [])
|
||||
.map(normalizeEquipmentPlannerMaterial)
|
||||
.filter(Boolean),
|
||||
categoryOrder,
|
||||
collapsedCategories: (Array.isArray(equipment?.collapsedCategories) ? equipment.collapsedCategories : [])
|
||||
.map((category) => String(category || "").trim().slice(0, 80))
|
||||
.filter((category, index, collapsedCategories) => category && categoryOrder.includes(category) && collapsedCategories.indexOf(category) === index)
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeEquipmentPlannerData(data) {
|
||||
const rawTypes = (Array.isArray(data?.types) ? data.types : []).map(normalizeEquipmentPlannerType).filter(Boolean);
|
||||
const typeIds = new Set(rawTypes.map((type) => type.id));
|
||||
const equipments = (Array.isArray(data?.equipments) ? data.equipments : [])
|
||||
.map((equipment) => normalizeEquipmentPlannerEquipment(equipment, typeIds))
|
||||
.filter(Boolean);
|
||||
const usedTypeIds = new Set(equipments.map((equipment) => equipment.typeId));
|
||||
const types = rawTypes.filter((type) => usedTypeIds.has(type.id));
|
||||
const orderedTypeIds = (Array.isArray(data?.typeOrder) ? data.typeOrder : [])
|
||||
.map((typeId) => String(typeId || ""))
|
||||
.filter((typeId, index, order) => usedTypeIds.has(typeId) && order.indexOf(typeId) === index);
|
||||
return {
|
||||
types,
|
||||
equipments,
|
||||
typeOrder: [
|
||||
...orderedTypeIds,
|
||||
...types.map((type) => type.id).filter((typeId) => !orderedTypeIds.includes(typeId))
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeEquipmentPlannerData(data) {
|
||||
const normalized = normalizeEquipmentPlannerData(data);
|
||||
const totals = new Map();
|
||||
const textBonuses = [];
|
||||
const activeEquipments = normalized.equipments.filter((equipment) => equipment.active);
|
||||
|
||||
function incrementCount(counts, key) {
|
||||
counts.set(key, (counts.get(key) || 0) + 1);
|
||||
}
|
||||
|
||||
function pickMostUsedKey(counts) {
|
||||
return [...counts.entries()].reduce((picked, [key, count]) => {
|
||||
if (!picked) return { key, count };
|
||||
if (count !== picked.count) return count > picked.count ? { key, count } : picked;
|
||||
return key.localeCompare(picked.key) < 0 ? { key, count } : picked;
|
||||
}, null);
|
||||
}
|
||||
|
||||
function parseIconKey(key) {
|
||||
const [icon, color = ""] = String(key || "").split(":");
|
||||
return { icon: icon || "sword", color };
|
||||
}
|
||||
|
||||
function pickSummaryIcon(iconSources) {
|
||||
return parseIconKey(pickMostUsedKey(iconSources.direct)?.key || pickMostUsedKey(iconSources.socket)?.key || "sword:");
|
||||
}
|
||||
|
||||
function getSummarySourceKind(sourceKinds) {
|
||||
if (sourceKinds.size !== 1) return "mixed";
|
||||
return sourceKinds.has("direct") ? "direct" : "socket";
|
||||
}
|
||||
|
||||
function collectEntry(entry, contribution, icon, sourceKind, color = "") {
|
||||
if (typeof entry.value === "number") {
|
||||
const existing = totals.get(entry.name);
|
||||
const iconKey = icon || "sword";
|
||||
const colorKey = sourceKind === "socket" ? color || "gray" : "";
|
||||
const iconSources = existing?.iconSources || { direct: new Map(), socket: new Map() };
|
||||
const sourceKinds = existing?.sourceKinds || new Set();
|
||||
const contributions = existing?.contributions || [];
|
||||
sourceKinds.add(sourceKind);
|
||||
incrementCount(iconSources[sourceKind], `${iconKey}:${colorKey}`);
|
||||
const pickedIcon = pickSummaryIcon(iconSources);
|
||||
totals.set(entry.name, {
|
||||
name: entry.name,
|
||||
icon: pickedIcon.icon,
|
||||
color: pickedIcon.color,
|
||||
sourceKind: getSummarySourceKind(sourceKinds),
|
||||
iconSources,
|
||||
sourceKinds,
|
||||
contributions: [
|
||||
...contributions,
|
||||
{
|
||||
id: entry.id,
|
||||
sourceKind,
|
||||
equipmentIcon: contribution.equipmentIcon,
|
||||
equipmentName: contribution.equipmentName,
|
||||
sourceName: contribution.sourceName,
|
||||
value: entry.value
|
||||
}
|
||||
],
|
||||
value: (existing?.value || 0) + entry.value
|
||||
});
|
||||
return;
|
||||
}
|
||||
textBonuses.push({
|
||||
id: entry.id,
|
||||
category: entry.category,
|
||||
name: entry.name,
|
||||
value: entry.value,
|
||||
source: contribution.sourceName
|
||||
});
|
||||
}
|
||||
|
||||
activeEquipments.forEach((equipment) => {
|
||||
equipment.characteristics.forEach((trait) => collectEntry(
|
||||
trait,
|
||||
{ equipmentIcon: equipment.icon || "shield", equipmentName: equipment.name, sourceName: equipment.name },
|
||||
trait.icon || "sword",
|
||||
"direct"
|
||||
));
|
||||
equipment.socketItems.forEach((socketItem) => {
|
||||
socketItem.bonuses.forEach((bonus) => collectEntry(
|
||||
bonus,
|
||||
{ equipmentIcon: equipment.icon || "shield", equipmentName: equipment.name, sourceName: `${equipment.name} · ${socketItem.name}` },
|
||||
socketItem.shape || "jewel",
|
||||
"socket",
|
||||
socketItem.color
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
const numericTotals = [...totals.values()]
|
||||
.map(({ iconSources, sourceKinds, color, sourceKind, ...total }) => ({
|
||||
...total,
|
||||
...(color ? { color } : {}),
|
||||
sourceKind
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
return {
|
||||
activeEquipmentCount: activeEquipments.length,
|
||||
totals: numericTotals,
|
||||
equipmentTotals: numericTotals.filter((total) => total.sourceKind === "direct"),
|
||||
socketTotals: numericTotals.filter((total) => total.sourceKind === "socket"),
|
||||
mixedTotals: numericTotals.filter((total) => total.sourceKind === "mixed"),
|
||||
textBonuses,
|
||||
socketItems: activeEquipments.flatMap((equipment) => equipment.socketItems.map((socketItem) => ({
|
||||
...socketItem,
|
||||
equipmentId: equipment.id,
|
||||
equipmentName: equipment.name
|
||||
}))),
|
||||
socketLinks: activeEquipments.flatMap((equipment) => equipment.socketLinks.map((link) => ({
|
||||
...link,
|
||||
equipmentId: equipment.id,
|
||||
equipmentName: equipment.name
|
||||
})))
|
||||
};
|
||||
}
|
||||
|
||||
function clampPercent(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) return 0;
|
||||
|
|
@ -716,6 +1007,64 @@ function compactChecklistSectionForStorage(section) {
|
|||
return compact;
|
||||
}
|
||||
|
||||
function compactEquipmentPlannerTraitForStorage(trait, prefix = "trait") {
|
||||
const normalized = normalizeEquipmentPlannerTrait(trait, prefix);
|
||||
if (!normalized) return null;
|
||||
const compact = { id: normalized.id, name: normalized.name };
|
||||
if (normalized.category) compact.category = normalized.category;
|
||||
if (normalized.icon !== "sword") compact.icon = normalized.icon;
|
||||
if (normalized.value !== "") compact.value = normalized.value;
|
||||
return compact;
|
||||
}
|
||||
|
||||
function compactEquipmentPlannerMaterialForStorage(material) {
|
||||
const normalized = normalizeEquipmentPlannerMaterial(material);
|
||||
return normalized ? normalized : null;
|
||||
}
|
||||
|
||||
function compactEquipmentPlannerSocketItemForStorage(socketItem) {
|
||||
const normalized = normalizeEquipmentPlannerSocketItem(socketItem);
|
||||
if (!normalized) return null;
|
||||
const compact = {
|
||||
id: normalized.id,
|
||||
name: normalized.name,
|
||||
shape: normalized.shape,
|
||||
color: normalized.color
|
||||
};
|
||||
if (normalized.bonuses.length) {
|
||||
compact.bonuses = normalized.bonuses
|
||||
.map((bonus) => compactEquipmentPlannerTraitForStorage(bonus, "socketBonus"))
|
||||
.filter(Boolean);
|
||||
}
|
||||
return compact;
|
||||
}
|
||||
|
||||
function compactEquipmentPlannerEquipmentForStorage(equipment) {
|
||||
const compact = {
|
||||
id: equipment.id,
|
||||
typeId: equipment.typeId,
|
||||
name: equipment.name
|
||||
};
|
||||
if (equipment.icon !== "shield") compact.icon = equipment.icon;
|
||||
if (equipment.active === false) compact.active = false;
|
||||
if (equipment.obtain) compact.obtain = equipment.obtain;
|
||||
if (equipment.characteristics.length) {
|
||||
compact.characteristics = equipment.characteristics
|
||||
.map((trait) => compactEquipmentPlannerTraitForStorage(trait, "trait"))
|
||||
.filter(Boolean);
|
||||
}
|
||||
if (equipment.socketItems.length) {
|
||||
compact.socketItems = equipment.socketItems
|
||||
.map(compactEquipmentPlannerSocketItemForStorage)
|
||||
.filter(Boolean);
|
||||
}
|
||||
if (equipment.socketLinks.length) compact.socketLinks = equipment.socketLinks;
|
||||
if (equipment.materials.length) compact.materials = equipment.materials.map(compactEquipmentPlannerMaterialForStorage).filter(Boolean);
|
||||
if (equipment.categoryOrder.length) compact.categoryOrder = equipment.categoryOrder;
|
||||
if (equipment.collapsedCategories.length) compact.collapsedCategories = equipment.collapsedCategories;
|
||||
return compact;
|
||||
}
|
||||
|
||||
export function compactModuleDataForStorage(type, value) {
|
||||
if (type === "notepad") {
|
||||
const normalized = normalizeNotepadData(value);
|
||||
|
|
@ -889,6 +1238,21 @@ export function compactModuleDataForStorage(type, value) {
|
|||
}
|
||||
return Object.keys(compact).length ? compact : null;
|
||||
}
|
||||
if (type === "equipmentPlanner") {
|
||||
const normalized = normalizeEquipmentPlannerData(value);
|
||||
if (!normalized.equipments.length) return null;
|
||||
const compact = {
|
||||
types: normalized.types.map((type) => {
|
||||
const compactType = { id: type.id, title: type.title };
|
||||
if (type.icon !== "shield") compactType.icon = type.icon;
|
||||
if (type.collapsed) compactType.collapsed = true;
|
||||
return compactType;
|
||||
}),
|
||||
equipments: normalized.equipments.map(compactEquipmentPlannerEquipmentForStorage)
|
||||
};
|
||||
if (normalized.typeOrder.length) compact.typeOrder = normalized.typeOrder;
|
||||
return compact;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
@ -1000,6 +1364,41 @@ function remapModuleDataForExport(type, data, nextId) {
|
|||
return remapped;
|
||||
}
|
||||
|
||||
if (type === "equipmentPlanner") {
|
||||
const typeIdMap = new Map();
|
||||
const equipmentIdMap = new Map();
|
||||
compact.types?.forEach((typeEntry) => typeIdMap.set(typeEntry.id, nextId("equipmentType")));
|
||||
compact.equipments?.forEach((equipment) => equipmentIdMap.set(equipment.id, nextId("equipment")));
|
||||
const remapped = {
|
||||
...compact,
|
||||
types: (compact.types || []).map((typeEntry) => ({ ...typeEntry, id: typeIdMap.get(typeEntry.id) })),
|
||||
equipments: (compact.equipments || []).map((equipment) => {
|
||||
const socketItemIdMap = new Map();
|
||||
equipment.socketItems?.forEach((socketItem) => socketItemIdMap.set(socketItem.id, nextId("socketItem")));
|
||||
return {
|
||||
...equipment,
|
||||
id: equipmentIdMap.get(equipment.id),
|
||||
typeId: typeIdMap.get(equipment.typeId) || "",
|
||||
characteristics: equipment.characteristics?.map((trait) => ({ ...trait, id: nextId("trait") })),
|
||||
materials: equipment.materials?.map((material) => ({ ...material, id: nextId("material") })),
|
||||
socketItems: equipment.socketItems?.map((socketItem) => ({
|
||||
...socketItem,
|
||||
id: socketItemIdMap.get(socketItem.id),
|
||||
bonuses: socketItem.bonuses?.map((bonus) => ({ ...bonus, id: nextId("socketBonus") }))
|
||||
})),
|
||||
socketLinks: equipment.socketLinks?.map((link) => ({
|
||||
...link,
|
||||
id: nextId("socketLink"),
|
||||
fromSocketItemId: socketItemIdMap.get(link.fromSocketItemId),
|
||||
toSocketItemId: socketItemIdMap.get(link.toSocketItemId)
|
||||
})).filter((link) => link.fromSocketItemId && link.toSocketItemId)
|
||||
};
|
||||
}).filter((equipment) => equipment.typeId)
|
||||
};
|
||||
if (compact.typeOrder) remapped.typeOrder = compact.typeOrder.map((typeId) => typeIdMap.get(typeId)).filter(Boolean);
|
||||
return remapped;
|
||||
}
|
||||
|
||||
return compact;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { CalculatorModule } from "../features/toolboxes/modules/CalculatorModule
|
|||
import { ChecklistModule } from "../features/toolboxes/modules/ChecklistModule.jsx";
|
||||
import { CombosModule } from "../features/toolboxes/modules/CombosModule.jsx";
|
||||
import { CountersModule } from "../features/toolboxes/modules/CountersModule.jsx";
|
||||
import { EquipmentPlannerModule } from "../features/toolboxes/modules/EquipmentPlannerModule.jsx";
|
||||
import { ImageAnnotationModule } from "../features/toolboxes/modules/ImageAnnotationModule.jsx";
|
||||
import { ImagesModule } from "../features/toolboxes/modules/ImagesModule.jsx";
|
||||
import { LinksModule } from "../features/toolboxes/modules/LinksModule.jsx";
|
||||
|
|
@ -19,12 +20,14 @@ import {
|
|||
normalizeChecklistData,
|
||||
normalizeCombosData,
|
||||
normalizeCountersData,
|
||||
normalizeEquipmentPlannerData,
|
||||
normalizeImageAnnotationData,
|
||||
normalizeLinksData,
|
||||
normalizeNotepadData,
|
||||
normalizeTableData,
|
||||
normalizeTaskPlannerData,
|
||||
normalizeTimerData,
|
||||
summarizeEquipmentPlannerData,
|
||||
normalizeUrl,
|
||||
uid
|
||||
} from "../features/toolboxes/storage/toolboxStorage.js";
|
||||
|
|
@ -42,6 +45,7 @@ const MODULE_COMPONENTS = {
|
|||
table: { label: "Tableau", mode: "Grille + formules", icon: "table", Component: TableModule, scrollable: true },
|
||||
timer: { label: "Timer", mode: "Chrono + comptes à rebours", icon: "clock", Component: TimerModule },
|
||||
taskPlanner: { label: "Planificateur de tâches", mode: "Catégories + parent/enfant", icon: "tasklist", Component: TaskPlannerModule, editable: true, scrollable: true },
|
||||
equipmentPlanner: { label: "Planificateur d'équipements", mode: "Build + sertissages", icon: "chest-armor", Component: EquipmentPlannerModule, editable: true, scrollable: true },
|
||||
imageAnnotation: { label: "Annotation d'images", mode: "Image + marqueurs", icon: "map", Component: ImageAnnotationModule, editable: true }
|
||||
};
|
||||
|
||||
|
|
@ -128,12 +132,14 @@ export function LibraryPage({ siteContent, actions }) {
|
|||
normalizeCombosData,
|
||||
normalizeLinksData,
|
||||
normalizeCountersData,
|
||||
normalizeEquipmentPlannerData,
|
||||
normalizeCalculatorData,
|
||||
normalizeImageAnnotationData,
|
||||
normalizeNotepadData,
|
||||
normalizeTableData,
|
||||
normalizeTimerData,
|
||||
normalizeTaskPlannerData,
|
||||
summarizeEquipmentPlannerData,
|
||||
normalizeUrl,
|
||||
hostnameFromUrl,
|
||||
clampQty,
|
||||
|
|
|
|||
46
website/src/styles/_components.scss
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Rôle : regroupe les styles de composants UI partagés hors layout global.
|
||||
.ui-tooltip-anchor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ui-tooltip {
|
||||
position: fixed;
|
||||
z-index: 520;
|
||||
width: max-content;
|
||||
max-width: min(280px, calc(100vw - 24px));
|
||||
padding: 8px 10px;
|
||||
border: 1px solid rgba(165, 180, 252, 0.18);
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(7, 10, 24, 0.98);
|
||||
box-shadow: var(--shadow-md);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: 1.35;
|
||||
opacity: 1;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, -100%);
|
||||
transition:
|
||||
opacity var(--duration-fast) var(--ease-standard),
|
||||
transform var(--duration-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.ui-tooltip::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-right: 1px solid rgba(165, 180, 252, 0.18);
|
||||
border-bottom: 1px solid rgba(165, 180, 252, 0.18);
|
||||
background: rgba(7, 10, 24, 0.98);
|
||||
transform: translate(-50%, -4px) rotate(45deg);
|
||||
}
|
||||
|
||||
.ui-tooltip-top-start {
|
||||
transform: translate(0, -100%);
|
||||
}
|
||||
|
||||
.ui-tooltip-top-start::after {
|
||||
left: 16px;
|
||||
}
|
||||
|
|
@ -262,11 +262,36 @@
|
|||
-webkit-mask-image: url("/static/icons/foot.svg");
|
||||
}
|
||||
|
||||
.ui-icon-boot {
|
||||
mask-image: url("/static/icons/boot.svg");
|
||||
-webkit-mask-image: url("/static/icons/boot.svg");
|
||||
}
|
||||
|
||||
.ui-icon-chest-armor {
|
||||
mask-image: url("/static/icons/chest-armor.svg");
|
||||
-webkit-mask-image: url("/static/icons/chest-armor.svg");
|
||||
}
|
||||
|
||||
.ui-icon-helmet {
|
||||
mask-image: url("/static/icons/helmet.svg");
|
||||
-webkit-mask-image: url("/static/icons/helmet.svg");
|
||||
}
|
||||
|
||||
.ui-icon-glove {
|
||||
mask-image: url("/static/icons/glove.svg");
|
||||
-webkit-mask-image: url("/static/icons/glove.svg");
|
||||
}
|
||||
|
||||
.ui-icon-grab {
|
||||
mask-image: url("/static/icons/grab.svg");
|
||||
-webkit-mask-image: url("/static/icons/grab.svg");
|
||||
}
|
||||
|
||||
.ui-icon-sword {
|
||||
mask-image: url("/static/icons/sword.svg");
|
||||
-webkit-mask-image: url("/static/icons/sword.svg");
|
||||
}
|
||||
|
||||
.ui-icon-shield {
|
||||
mask-image: url("/static/icons/shield.svg");
|
||||
-webkit-mask-image: url("/static/icons/shield.svg");
|
||||
|
|
@ -277,6 +302,36 @@
|
|||
-webkit-mask-image: url("/static/icons/gun.svg");
|
||||
}
|
||||
|
||||
.ui-icon-earring {
|
||||
mask-image: url("/static/icons/earring.svg");
|
||||
-webkit-mask-image: url("/static/icons/earring.svg");
|
||||
}
|
||||
|
||||
.ui-icon-necklace {
|
||||
mask-image: url("/static/icons/necklace.svg");
|
||||
-webkit-mask-image: url("/static/icons/necklace.svg");
|
||||
}
|
||||
|
||||
.ui-icon-ring {
|
||||
mask-image: url("/static/icons/ring.svg");
|
||||
-webkit-mask-image: url("/static/icons/ring.svg");
|
||||
}
|
||||
|
||||
.ui-icon-hammer {
|
||||
mask-image: url("/static/icons/hammer.svg");
|
||||
-webkit-mask-image: url("/static/icons/hammer.svg");
|
||||
}
|
||||
|
||||
.ui-icon-jewel {
|
||||
mask-image: url("/static/icons/jewel.svg");
|
||||
-webkit-mask-image: url("/static/icons/jewel.svg");
|
||||
}
|
||||
|
||||
.ui-icon-ball {
|
||||
mask-image: url("/static/icons/ball.svg");
|
||||
-webkit-mask-image: url("/static/icons/ball.svg");
|
||||
}
|
||||
|
||||
.ui-icon-trash {
|
||||
mask-image: url("/static/icons/trashcan.svg");
|
||||
-webkit-mask-image: url("/static/icons/trashcan.svg");
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
@use "shell";
|
||||
@use "home";
|
||||
@use "cards";
|
||||
@use "components";
|
||||
@use "icons";
|
||||
@use "games";
|
||||
@use "toolboxes";
|
||||
|
|
|
|||