Éditeur de collision : cartes "pièces qui s'emboîtent", animées
Refonte visuelle de l'onglet "🧩 Collision" — l'ancien rendu (pastilles plates + flèches) ne rendait pas la métaphore "pièces de puzzle qui s'emboîtent" demandée. Chaque maillon (déclencheur/action/sous-action) est maintenant une pièce chevronnée colorée par type, imbriquée dans la suivante via clip-path + marge négative, avec une entrée animée en cascade. Cartes objet et choix de l'assistant modal redessinés en grille de cartes avec icône + libellé, hover/entrée animés. Purement visuel — aucun changement de logique/données (déjà couvert par tests/test_collision_rules.py et static/js/play/__tests__/collision-rules-controller.test.js). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ee270d0286
commit
b64541856f
@@ -37,22 +37,28 @@ function collisionRuleActionSummary(action) {
|
||||
return COLLISION_ACTION_LABELS[action.type] || action.type;
|
||||
}
|
||||
|
||||
// Une règle = une chaîne de "maillons" (déclencheur -> action -> sous-action
|
||||
// éventuelle) — chacun sa propre petite carte, visuellement emboîtées.
|
||||
// Une règle = une chaîne de "maillons" en forme de pièces qui s'emboîtent
|
||||
// (déclencheur -> action -> sous-action éventuelle), pas une liste plate —
|
||||
// chaque maillon porte un data-kind (couleur en CSS) et un délai d'entrée
|
||||
// en cascade (voir @keyframes collisionLinkIn, static/style.css).
|
||||
function collisionRuleChainHtml(rule, ruleIndex, objectId) {
|
||||
var links = [collisionRuleTriggerSummary(rule), collisionRuleActionSummary(rule.action)];
|
||||
var links = [
|
||||
{ kind: 'trigger', text: collisionRuleTriggerSummary(rule) },
|
||||
{ kind: rule.action.type, text: collisionRuleActionSummary(rule.action) },
|
||||
];
|
||||
var action = rule.action;
|
||||
while (action.type === 'interagir' && action.sub_action) {
|
||||
links.push(collisionRuleActionSummary(action.sub_action));
|
||||
links.push({ kind: action.sub_action.type, text: collisionRuleActionSummary(action.sub_action) });
|
||||
action = action.sub_action;
|
||||
}
|
||||
var linksHtml = links.map(function (text) {
|
||||
return '<span class="collisionRuleLink">' + text.replace(/</g, '<') + '</span>';
|
||||
}).join('<span class="collisionRuleArrow">→</span>');
|
||||
var linksHtml = links.map(function (link, i) {
|
||||
return '<span class="collisionRuleLink" data-kind="' + link.kind + '" style="animation-delay:' + (i * 60) + 'ms">' +
|
||||
link.text.replace(/</g, '<') + '</span>';
|
||||
}).join('');
|
||||
return (
|
||||
'<div class="collisionRuleChain">' +
|
||||
'<div class="collisionRuleChainLinks">' + linksHtml + '</div>' +
|
||||
'<button type="button" class="button is-small is-danger" title="Supprimer cette règle" ' +
|
||||
'<button type="button" class="collisionRuleChainDelete" title="Supprimer cette règle" ' +
|
||||
'onclick="deleteCollisionRule(' + objectId + ', ' + ruleIndex + ')">🗑️</button>' +
|
||||
'</div>'
|
||||
);
|
||||
@@ -69,7 +75,7 @@ function collisionRuleCardHtml(obj) {
|
||||
'<h3 class="collisionRuleCardTitle">' + collisionRuleObjectLabel(obj).replace(/</g, '<') + '</h3>' +
|
||||
'</div>' +
|
||||
'<div class="collisionRuleCardRules">' + rulesHtml + '</div>' +
|
||||
'<button type="button" class="button is-small primary" onclick="openCollisionRuleModal(' + obj.id + ')">+ Action</button>' +
|
||||
'<button type="button" class="collisionRuleAddBtn" onclick="openCollisionRuleModal(' + obj.id + ')">✚ Action</button>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
@@ -81,7 +87,11 @@ function renderCollisionRuleCards() {
|
||||
container.innerHTML = '<p class="hint">Aucun objet éligible pour l\'instant — un décor, un ennemi ou un PNJ (jamais le joueur lui-même).</p>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = COLLISION_RULE_OBJECTS.map(collisionRuleCardHtml).join('');
|
||||
// Chaque carte entre en cascade (voir @keyframes collisionCardIn,
|
||||
// static/style.css) plutôt que toutes en même temps.
|
||||
container.innerHTML = COLLISION_RULE_OBJECTS.map(function (obj, i) {
|
||||
return collisionRuleCardHtml(obj).replace('<div class="collisionRuleCard"', '<div style="animation-delay:' + (i * 50) + 'ms" class="collisionRuleCard"');
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function collisionRuleObjectById(objectId) {
|
||||
@@ -136,16 +146,29 @@ function collisionWizardBodyEl() {
|
||||
|
||||
// Étape 1 — déclencheur : collision directe, ou périmètre (px réglable,
|
||||
// valeur par défaut COLLISION_DEFAULT_PERIMETRE_PX).
|
||||
// Sépare "emoji" et "libellé" d'un texte "🗺️ Déclencher une quête" pour
|
||||
// les afficher sur deux lignes dans une carte-choix (voir .collisionWizardChoice).
|
||||
function collisionSplitEmojiLabel(text) {
|
||||
var m = text.match(/^(\S+)\s+(.*)$/);
|
||||
return m ? { emoji: m[1], label: m[2] } : { emoji: '', label: text };
|
||||
}
|
||||
|
||||
function renderCollisionWizardTriggerStep() {
|
||||
var collision = collisionSplitEmojiLabel(COLLISION_TRIGGER_LABELS.collision);
|
||||
var perimetre = collisionSplitEmojiLabel(COLLISION_TRIGGER_LABELS.perimetre);
|
||||
collisionWizardBodyEl().innerHTML =
|
||||
'<p class="hint mb-2">Cette règle se déclenche...</p>' +
|
||||
'<div class="collisionWizardChoices">' +
|
||||
'<button type="button" class="button is-fullwidth mb-2" onclick="collisionWizardChooseTrigger(\'collision\')">' + COLLISION_TRIGGER_LABELS.collision + '</button>' +
|
||||
'<div class="fieldRow" style="align-items:center; gap:8px;">' +
|
||||
'<button type="button" class="button is-fullwidth" onclick="collisionWizardChooseTrigger(\'perimetre\')">' + COLLISION_TRIGGER_LABELS.perimetre + '</button>' +
|
||||
'<input type="number" min="1" id="collisionWizardPerimetrePx" value="' + COLLISION_DEFAULT_PERIMETRE_PX + '" style="width:80px;" class="input is-small">' +
|
||||
'<span class="hint">px</span>' +
|
||||
'</div>' +
|
||||
'<button type="button" class="collisionWizardChoice" style="animation-delay:0ms" onclick="collisionWizardChooseTrigger(\'collision\')">' +
|
||||
'<span class="emoji">' + collision.emoji + '</span><span>' + collision.label + '</span>' +
|
||||
'</button>' +
|
||||
'<button type="button" class="collisionWizardChoice" style="animation-delay:40ms" onclick="collisionWizardChooseTrigger(\'perimetre\')">' +
|
||||
'<span class="emoji">' + perimetre.emoji + '</span><span>' + perimetre.label + '</span>' +
|
||||
'<span class="fieldRow" style="align-items:center; gap:6px;" onclick="event.stopPropagation()">' +
|
||||
'<input type="number" min="1" id="collisionWizardPerimetrePx" value="' + COLLISION_DEFAULT_PERIMETRE_PX + '" style="width:64px;" class="input is-small">' +
|
||||
'<span class="hint">px</span>' +
|
||||
'</span>' +
|
||||
'</button>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
@@ -164,8 +187,11 @@ function collisionWizardChooseTrigger(trigger) {
|
||||
// quelle action exécuter.
|
||||
function renderCollisionWizardActionStep(nested) {
|
||||
var types = nested ? COLLISION_ACTION_TYPES.filter(function (t) { return t !== 'interagir'; }) : COLLISION_ACTION_TYPES;
|
||||
var buttons = types.map(function (type) {
|
||||
return '<button type="button" class="button is-fullwidth mb-2" onclick="collisionWizardChooseAction(\'' + type + '\', ' + (nested ? 'true' : 'false') + ')">' + COLLISION_ACTION_LABELS[type] + '</button>';
|
||||
var buttons = types.map(function (type, i) {
|
||||
var split = collisionSplitEmojiLabel(COLLISION_ACTION_LABELS[type]);
|
||||
return '<button type="button" class="collisionWizardChoice" style="animation-delay:' + (i * 40) + 'ms" onclick="collisionWizardChooseAction(\'' + type + '\', ' + (nested ? 'true' : 'false') + ')">' +
|
||||
'<span class="emoji">' + split.emoji + '</span><span>' + split.label + '</span>' +
|
||||
'</button>';
|
||||
}).join('');
|
||||
collisionWizardBodyEl().innerHTML =
|
||||
'<p class="hint mb-2">' + (nested ? 'Une fois la touche pressée...' : 'Alors...') + '</p>' +
|
||||
@@ -223,8 +249,10 @@ function collisionWizardChooseAction(type, nested) {
|
||||
collisionWizardBodyEl().innerHTML = '<p class="hint">Aucun événement personnalisé pour l\'instant — crée-en un dans l\'onglet "📣 Événements", puis reviens ici.</p>';
|
||||
return;
|
||||
}
|
||||
var buttons = eventIds.map(function (id) {
|
||||
return '<button type="button" class="button is-fullwidth mb-2" onclick="collisionWizardFinalizeAction({type:\'evenement\', evenement_id:' + id + '}, ' + (nested ? 'true' : 'false') + ')">📣 ' + CUSTOM_EVENTS_MAP[id].name + '</button>';
|
||||
var buttons = eventIds.map(function (id, i) {
|
||||
return '<button type="button" class="collisionWizardChoice" style="animation-delay:' + (i * 40) + 'ms" onclick="collisionWizardFinalizeAction({type:\'evenement\', evenement_id:' + id + '}, ' + (nested ? 'true' : 'false') + ')">' +
|
||||
'<span class="emoji">📣</span><span>' + CUSTOM_EVENTS_MAP[id].name.replace(/</g, '<') + '</span>' +
|
||||
'</button>';
|
||||
}).join('');
|
||||
collisionWizardBodyEl().innerHTML = '<p class="hint mb-2">Quel événement ?</p><div class="collisionWizardChoices">' + buttons + '</div>';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user