@@ -12,16 +12,6 @@
< li class = "is-active" > < a > {{ screen.name }}< / a > < / li >
{% endblock %}
{% block content %}
< div class = "fieldRow" style = "align-items:center; justify-content:space-between;" >
< form method = "post" action = "{{ url_for('screen_rename', slug=game.slug, screen_id=screen.id) }}" class = "field has-addons" style = "align-items:center; margin:0;" >
< div class = "control" > < input class = "input" type = "text" name = "name" value = "{{ screen.name }}" required style = "font-size:16px; font-weight:700; width:auto;" > < / div >
< div class = "control" > < button type = "submit" class = "button is-small" > Renommer< / button > < / div >
< / form >
{% if not screen.is_template %}
< a class = "button is-small is-primary" href = "{{ url_for('game_play', slug=game.slug) }}" target = "_blank" > ▶️ Jouer depuis le début< / a >
{% endif %}
< / div >
{% if screen.is_template %}
< p class = "hint" style = "background:#1c2130; border:1px solid #2b3140; border-radius:8px; padding:10px 14px; margin:10px 0;" >
🧩 Contenu d'un élément de jeu réutilisable.
@@ -67,6 +57,25 @@
< button type = "button" class = "button is-small" onclick = "closeLeftPanel()" title = "Fermer" > ✕< / button >
< / div >
< div class = "floatPanelBody" >
<!-- Nom de l'écran, aperçu jouable, et format de l'aperçu (Portrait/
Paysage/Carré) : regroupés ici plutôt qu'au - dessus du canevas —
ce sont des réglages qu'on touche rarement une fois l'écran en
cours de construction, pas besoin qu'ils prennent en permanence de
la place dans la zone centrale. -->
< form method = "post" action = "{{ url_for('screen_rename', slug=game.slug, screen_id=screen.id) }}" class = "field has-addons" style = "align-items:center; margin-bottom:8px;" >
< div class = "control is-expanded" > < input class = "input is-small" type = "text" name = "name" value = "{{ screen.name }}" required > < / div >
< div class = "control" > < button type = "submit" class = "button is-small" > Renommer< / button > < / div >
< / form >
{% if not screen.is_template %}
< a class = "button is-small is-primary is-fullwidth mb-2" href = "{{ url_for('game_play', slug=game.slug) }}" target = "_blank" > ▶️ Jouer depuis le début< / a >
{% endif %}
{% set current_aspect = screen.preview_aspect or '9/16' %}
< div class = "buttons has-addons are-small mb-2" style = "flex-wrap:nowrap;" >
< button type = "button" class = "button smallBtn aspectBtn {{ 'active' if current_aspect == '9/16' else '' }}" data-aspect = "9/16" > 📱 Portrait< / button >
< button type = "button" class = "button smallBtn aspectBtn {{ 'active' if current_aspect == '16/9' else '' }}" data-aspect = "16/9" > 🖥️ Paysage< / button >
< button type = "button" class = "button smallBtn aspectBtn {{ 'active' if current_aspect == '1/1' else '' }}" data-aspect = "1/1" > ◻️ Carré< / button >
< / div >
< hr style = "border-color:var(--border); margin:10px 0;" >
< h2 class = "subtitle is-6" > Éléments de cet écran< / h2 >
{% if top_level_elements %}
< div class = "elementTree" >
@@ -149,27 +158,299 @@
< button type = "button" onclick = "contextMenuDuplicate()" > 📄 Dupliquer< / button >
< / div >
<!-- Centre : l'écran, en très grand -->
<!-- Centre : l'écran (onglet "Écran"), ou la logique de la scène / la
timeline d'animation quand leur onglet est actif — un seul onglet
visible à la fois, chacun occupant tout l'espace disponible (voir
switchBuilderTab() plus bas). Absents sur un écran - modèle (élément
de jeu) : ses déclencheurs référenceraient des éléments dont l'id
change à chaque copie (voir instantiate_template_tree), donc une
logique/animation posée ici ne fonctionnerait plus une fois
l'élément réutilisé — seul l'onglet "Écran" a alors un sens. -->
< div class = "builderCanvasArea" >
{% set current_aspect = screen.preview_aspect or '9/16' %}
< div class = "canvasToolbar" >
< span class = "hint" > Aperçu : < / span >
< button type = "button" class = "smallBtn aspectBtn {{ 'active' if current_aspect == '9/16' else '' }} " data-aspect = "9/16" > 📱 Portrait < / button >
< button type = "button" class = "smallBtn aspectBtn {{ 'active' if current_aspect == '16/9' else '' }} " data-aspect = "16/9" > 🖥️ Paysag e< / button >
< button type = "button" class = "smallBtn aspectBtn {{ 'active' if current_aspect == '1/1' else '' }}" data-aspect = "1/1" > ◻️ Carré < / button >
{% if not screen.is_template %}
< div class = "builderTabs" id = "builderTabs" >
< button type = "button" class = "builderTabBtn active" data-tab = "screen " onclick = "switchBuilderTab('screen')" > 🖥️ Écran < / button >
< button type = "button" class = "builderTabBtn" data-tab = "flow " onclick = "switchBuilderTab('flow')" > 🔀 Logique de la scèn e< / button >
< button type = "button" class = "builderTabBtn" data-tab = "anim" onclick = "switchBuilderTab('anim')" > 🎬 Timeline d'animation < / button >
< / div >
< div class = "canvasFrame" >
< div class = "canvas" id = "canvas" style = "aspect-ratio:{{ current_aspect }};" >
{% for el in top_level_elements %}
< div class = "canvasElement {{ 'selected' if el.id == selected_id else '' }}" id = "el-{{ el.id }}" style = "{{ el|elstyle }}" data-anim-target = "{{ el.id }}"
onmousedown = "onElementMouseDown(event, {{ el.id }}) ">
< div class = "canvasElementInner" style = "{{ el| eltransform }}" > {{ el.rendered_html|safe }} < / div >
< div class = "resizeHandle " onmousedown = "onResizeMouseDown(event, {{ el.id }}) " > < / div >
{% endif %}
< div class = "builderTabPanel active" data-tab-panel = "screen" >
< div class = "canvasFrame" >
< div class = "canvas" id = "canvas" style = "aspect-ratio:{{ current_aspect }}; ">
{% for el in top_lev el_ elements %}
< div class = "canvasElement {{ 'selected' if el.id == selected_id else '' }} " id = "el-{{ el.id }}" style = "{{ el|elstyle }}" data-anim-target = " {{ el.id }}"
onmousedown = "onElementMouseDown(event, {{ el.id }})" >
< div class = "canvasElementInner" style = "{{ el|eltransform }}" > {{ el.rendered_html|safe }}< / div >
< div class = "resizeHandle" onmousedown = "onResizeMouseDown(event, {{ el.id }})" > < / div >
< / div >
{% endfor %}
< / div >
{% endfor %}
< / div >
< p class = "hint" style = "text-align:center;" > Glisse pour déplacer, tire le coin pour redimensionner, clique pour sélectionner.< / p >
< / div >
{% if not screen.is_template %}
< div class = "builderTabPanel" id = "flowTabPanel" data-tab-panel = "flow" >
< div class = "flowToolbar" >
< button type = "button" class = "smallBtn button is-small" onclick = "openNodeForm('trigger')" > ➕ Déclencheur< / button >
< button type = "button" class = "smallBtn button is-small" onclick = "openNodeForm('condition')" > ➕ Condition< / button >
< button type = "button" class = "smallBtn button is-small" onclick = "openNodeForm('action')" > ➕ Action< / button >
< span class = "hint" > Clique un point de sortie d'un nœud, puis le point d'entrée d'un autre nœud, pour les relier. Reclique une connexion pour la supprimer.< / span >
< / div >
< div id = "nodeForm" class = "nodeForm" style = "display:none;" >
< div class = "fieldRow" style = "align-items:center; justify-content:space-between;" >
< strong id = "nodeFormTitle" > Nouveau nœud< / strong >
< button type = "button" class = "smallBtn button is-small" onclick = "closeNodeForm()" > ✕< / button >
< / div >
< div id = "nodeFormTrigger" style = "display:none;" >
< label > Événement< select id = "nf-trigger-event" onchange = "toggleFlowTriggerFields()" >
{% for key, label in trigger_events %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select > < / label >
< label id = "nf-trigger-element-row" > Élément< select id = "nf-trigger-element" >
{% for e2 in elements %}
< option value = "{{ e2.id }}" > {{ e2|elabel }} (elt-{{ e2.id }})< / option >
{% endfor %}
< / select > < / label >
< p class = "hint" id = "nf-trigger-repeater-hint" style = "display:none;" > < / p >
< p class = "hint" id = "nf-trigger-affichage-hint" style = "display:none;" > < / p >
< / div >
< div id = "nodeFormCondition" style = "display:none;" >
{% if not definitions %}
< p class = "hint" > Aucun objet défini dans ce jeu pour l'instant — < a href = "{{ url_for('object_new', slug=game.slug) }}" > crées-en un< / a > .< / p >
{% else %}
< label > Objet< select id = "nf-cond-definition" onchange = "onFlowDefinitionChange('cond')" >
{% for d in definitions %}
< option value = "{{ d.id }}" > {{ d.name }}< / option >
{% endfor %}
< / select > < / label >
< label > Ligne concernée< select id = "nf-cond-row" > < / select > < / label >
< label > Champ< select id = "nf-cond-field" onchange = "onFlowFieldChange()" > < / select > < / label >
< label > Condition< select id = "nf-cond-operator" >
{% for key, label in condition_operators %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select > < / label >
< label > Valeur de comparaison< input type = "text" id = "nf-cond-value" placeholder = "ex. 1, vrai, 10..." > < / label >
< div id = "nf-cond-extra-wrap" >
< label id = "nf-cond-combinator-row" style = "display:none;" > Combiner les conditions avec
< select id = "nf-cond-combinator" >
< option value = "et" > ET (toutes les conditions doivent être vraies)< / option >
< option value = "ou" > OU (au moins une condition doit être vraie)< / option >
< / select >
< / label >
< div id = "nf-cond-extra-clauses" > < / div >
< button type = "button" class = "smallBtn button is-small" onclick = "addFlowConditionClause()" > + Ajouter une condition< / button >
< / div >
{% endif %}
< / div >
< div id = "nodeFormAction" style = "display:none;" >
< label > Type d'action< select id = "nf-action-type" onchange = "toggleFlowActionFields()" >
{% for key, label in action_type_labels.items() %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select > < / label >
< select id = "nf-action-target-screen" style = "display:none;" >
{% for s in all_screens %}
< option value = "{{ s.id }}" > {{ s.name }}< / option >
{% endfor %}
< / select >
< div id = "nf-action-element-fields" style = "display:none;" >
< select id = "nf-action-target-element" >
{% for e2 in elements %}
< option value = "{{ e2.id }}" > {{ e2|elabel }} (elt-{{ e2.id }})< / option >
{% endfor %}
< / select >
< p class = "hint" id = "nf-action-onglet-hint" style = "display:none;" > < / p >
< div id = "nf-action-element-property-wrap" >
< select id = "nf-action-element-property" onchange = "toggleFlowElementPropertyValue()" style = "margin-top:6px;" >
{% for key, label in element_action_properties %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< select id = "nf-action-element-value-visibility" style = "margin-top:6px;" >
{% for key, label in element_visibility_values %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< select id = "nf-action-element-value-surbrillance" style = "display:none; margin-top:6px;" >
{% for key, label in element_surbrillance_values %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< select id = "nf-action-element-value-desactive" style = "display:none; margin-top:6px;" >
{% for key, label in element_desactive_values %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< input type = "color" id = "nf-action-element-value-color" style = "display:none; margin-top:6px;" value = "#5b8cff" >
< input type = "number" id = "nf-action-element-value-number" style = "display:none; margin-top:6px;" value = "50" placeholder = "Nouvelle valeur en %" >
< input type = "text" id = "nf-action-element-value-text" style = "display:none; margin-top:6px;" placeholder = "Nouveau texte affiché" >
< label id = "nf-action-element-toggle-row" style = "display:flex; align-items:center; gap:6px; margin-top:10px; font-size:12px; color:var(--text-dim);" >
< input type = "checkbox" id = "nf-action-element-toggle" onchange = "toggleFlowElementToggleMode()" style = "width:auto; margin:0;" >
Va-et-vient : au 1er clic, passer à cette valeur ; au 2e clic, revenir à la précédente ; etc.
< / label >
< div id = "nf-action-element-value2-wrap" style = "display:none; margin-top:6px;" >
< input type = "color" id = "nf-action-element-value2-color" style = "display:none;" value = "#ff0000" >
< input type = "number" id = "nf-action-element-value2-number" style = "display:none;" placeholder = "Valeur au 2e état en %" >
< / div >
< / div >
< / div >
< div id = "nf-action-wait-fields" style = "display:none;" >
< label > Délai en secondes avant de continuer
< input type = "number" id = "nf-action-wait-seconds" min = "0" step = "0.5" value = "3" placeholder = "ex. 3" >
< / label >
< / div >
< div id = "nf-action-data-fields" style = "display:none;" >
{% if not definitions %}
< p class = "hint" > Aucun objet défini dans ce jeu pour l'instant — < a href = "{{ url_for('object_new', slug=game.slug) }}" > crées-en un< / a > .< / p >
{% else %}
< select id = "nf-action-data-definition" onchange = "onFlowDefinitionChange('action')" >
{% for d in definitions %}
< option value = "{{ d.id }}" > {{ d.name }}< / option >
{% endfor %}
< / select >
< select id = "nf-action-data-row" style = "margin-top:6px;" > < / select >
< select id = "nf-action-data-field" style = "margin-top:6px;" > < / select >
< select id = "nf-action-data-operation" style = "margin-top:6px;" >
{% for key, label in data_operations %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< input type = "text" id = "nf-action-data-value" style = "margin-top:6px;" placeholder = "Valeur / montant" >
{% endif %}
< / div >
< div id = "nf-action-variable-fields" style = "display:none;" >
{% if not global_variables %}
< p class = "hint" > Aucune variable globale pour l'instant — crées-en une depuis "Condition de visibilité" dans les propriétés d'un élément.< / p >
{% else %}
< select id = "nf-action-variable-name" >
{% for v in global_variables %}
< option value = "{{ v.name }}" > {{ v.name }}< / option >
{% endfor %}
< / select >
< select id = "nf-action-variable-operation" style = "margin-top:6px;" >
{% for key, label in data_operations %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< input type = "text" id = "nf-action-variable-value" style = "margin-top:6px;" placeholder = "Valeur / montant" >
{% endif %}
< / div >
< / div >
< div class = "actionsRow" > < button type = "button" class = "smallBtn button is-small is-primary" onclick = "submitNodeForm()" > + Ajouter ce nœud< / button > < / div >
< / div >
< div class = "flowCanvasWrap" >
< svg id = "flowEdges" class = "flowEdgesSvg" > < / svg >
< div id = "flowCanvas" class = "flowCanvas" > < / div >
< / div >
< / div >
< p class = "hint" style = "text-align:center;" > Glisse pour déplacer, tire le coin pour redimensionner, clique pour sélectionner.< / p >
< div class = "builderTabPanel" id = "animTabPanel" data-tab-panel = "anim" >
< div class = "flowToolbar" >
< label style = "display:flex; align-items:center; gap:6px;" >
Piste
< select id = "anim-new-track-element" >
{% for e2 in elements %}
< option value = "{{ e2.id }}" > {{ e2|elabel }}< / option >
{% endfor %}
< / select >
< / label >
< button type = "button" class = "smallBtn button is-small" onclick = "addAnimTrack()" > ➕ Ajouter un clip< / button >
< / div >
< div id = "animClipForm" class = "nodeForm" style = "display:none;" >
< div class = "fieldRow" style = "align-items:center; justify-content:space-between;" >
< strong id = "animClipFormTitle" > Clip d'animation< / strong >
< button type = "button" class = "smallBtn button is-small" onclick = "closeAnimClipForm()" > ✕< / button >
< / div >
< label > Type
< select id = "anim-clip-kind" onchange = "toggleAnimClipKind()" >
< option value = "animate_css" > Animation Animate.css< / option >
< option value = "custom" > Animation personnalisée — images-clés< / option >
< / select >
< / label >
< div id = "anim-clip-animate-css-fields" >
< label > Animation
< select id = "anim-clip-animate-name" >
{% for category, items in animate_css_catalog %}
< optgroup label = "{{ category }}" >
{% for key, label in items %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / optgroup >
{% endfor %}
< / select >
< / label >
< / div >
< div id = "anim-clip-custom-fields" style = "display:none;" >
< div id = "anim-clip-keyframes" > < / div >
< button type = "button" class = "smallBtn button is-small" onclick = "addAnimKeyframeRow()" > + Ajouter une image-clé< / button >
< / div >
< div class = "fieldRow" >
< label style = "flex:1;" > Départ en secondes
< input type = "number" id = "anim-clip-start" min = "0" step = "0.1" value = "0" >
< / label >
< label style = "flex:1;" > Durée en secondes
< input type = "number" id = "anim-clip-duration" min = "0.1" step = "0.1" value = "1" >
< / label >
< / div >
< div class = "fieldRow" >
< label style = "flex:1;" > Délai en secondes
< input type = "number" id = "anim-clip-delay" min = "0" step = "0.1" value = "0" >
< / label >
< label style = "flex:1;" > Vitesse
< select id = "anim-clip-easing" >
< option value = "ease" > Progressive< / option >
< option value = "linear" > Constante< / option >
< option value = "ease-in" > Accélère< / option >
< option value = "ease-out" > Ralentit< / option >
< option value = "ease-in-out" > Accélère puis ralentit< / option >
< / select >
< / label >
< / div >
< label > Répétitions
< select id = "anim-clip-iteration" >
< option value = "1" > Une fois< / option >
< option value = "2" > 2 fois< / option >
< option value = "3" > 3 fois< / option >
< option value = "infinite" > En boucle< / option >
< / select >
< / label >
< div class = "actionsRow" >
< button type = "button" class = "smallBtn button is-small is-primary" onclick = "saveAnimClipForm()" > Enregistrer< / button >
< button type = "button" class = "smallBtn button is-small" onclick = "previewAnimClip()" > ▶ Aperçu< / button >
< button type = "button" class = "smallBtn button is-small is-danger" onclick = "deleteAnimClipForm()" > 🗑️ Supprimer< / button >
< / div >
< / div >
< div class = "animTimelineWrap" >
< div id = "animRuler" class = "animRuler" > < / div >
< div id = "animTracks" class = "animTracks" > < / div >
< / div >
< / div >
{% endif %}
< / div >
<!-- Panneau droit : propriétés de l'élément sélectionné — flottant,
@@ -468,298 +749,6 @@
< / div >
< / div >
{% if not screen.is_template %}
<!-- Poignée de redimensionnement : visible seulement quand le panneau du bas
est ouvert, on la tire à la souris pour régler sa hauteur. -->
< div class = "flowResizeHandle" id = "flowResizeHandle" > < / div >
<!-- Panneau bas, rétractable : logique de la scène (éditeur de flow à nœuds).
Volontairement PAS un <details> natif : sa hauteur ouverte doit pouvoir
être réglée à la souris (voir flowResizeHandle), ce qu'un <details> ne
permet pas nativement. Absent pour un écran - modèle (élément de jeu) :
ses déclencheurs référenceraient des éléments dont l'id change à chaque
copie (voir instantiate_template_tree), donc une logique posée ici ne
fonctionnerait plus une fois l'élément réutilisé. -->
< div class = "flowPanel" id = "flowPanel" >
< div class = "flowPanelHeader" onclick = "toggleFlowPanel()" >
< span class = "flowToggleIcon" > ▸< / span > 🔀 Logique de la scène — nœuds et connexions
< / div >
< div class = "flowPanelBody" id = "flowPanelBody" >
< div class = "flowToolbar" >
< button type = "button" class = "smallBtn button is-small" onclick = "openNodeForm('trigger')" > ➕ Déclencheur< / button >
< button type = "button" class = "smallBtn button is-small" onclick = "openNodeForm('condition')" > ➕ Condition< / button >
< button type = "button" class = "smallBtn button is-small" onclick = "openNodeForm('action')" > ➕ Action< / button >
< span class = "hint" > Clique un point de sortie d'un nœud, puis le point d'entrée d'un autre nœud, pour les relier. Reclique une connexion pour la supprimer.< / span >
< / div >
< div id = "nodeForm" class = "nodeForm" style = "display:none;" >
< div class = "fieldRow" style = "align-items:center; justify-content:space-between;" >
< strong id = "nodeFormTitle" > Nouveau nœud< / strong >
< button type = "button" class = "smallBtn button is-small" onclick = "closeNodeForm()" > ✕< / button >
< / div >
< div id = "nodeFormTrigger" style = "display:none;" >
< label > Événement< select id = "nf-trigger-event" onchange = "toggleFlowTriggerFields()" >
{% for key, label in trigger_events %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select > < / label >
< label id = "nf-trigger-element-row" > Élément< select id = "nf-trigger-element" >
{% for e2 in elements %}
< option value = "{{ e2.id }}" > {{ e2|elabel }} (elt-{{ e2.id }})< / option >
{% endfor %}
< / select > < / label >
< p class = "hint" id = "nf-trigger-repeater-hint" style = "display:none;" > < / p >
< p class = "hint" id = "nf-trigger-affichage-hint" style = "display:none;" > < / p >
< / div >
< div id = "nodeFormCondition" style = "display:none;" >
{% if not definitions %}
< p class = "hint" > Aucun objet défini dans ce jeu pour l'instant — < a href = "{{ url_for('object_new', slug=game.slug) }}" > crées-en un< / a > .< / p >
{% else %}
< label > Objet< select id = "nf-cond-definition" onchange = "onFlowDefinitionChange('cond')" >
{% for d in definitions %}
< option value = "{{ d.id }}" > {{ d.name }}< / option >
{% endfor %}
< / select > < / label >
< label > Ligne concernée< select id = "nf-cond-row" > < / select > < / label >
< label > Champ< select id = "nf-cond-field" onchange = "onFlowFieldChange()" > < / select > < / label >
< label > Condition< select id = "nf-cond-operator" >
{% for key, label in condition_operators %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select > < / label >
< label > Valeur de comparaison< input type = "text" id = "nf-cond-value" placeholder = "ex. 1, vrai, 10..." > < / label >
< div id = "nf-cond-extra-wrap" >
< label id = "nf-cond-combinator-row" style = "display:none;" > Combiner les conditions avec
< select id = "nf-cond-combinator" >
< option value = "et" > ET (toutes les conditions doivent être vraies)< / option >
< option value = "ou" > OU (au moins une condition doit être vraie)< / option >
< / select >
< / label >
< div id = "nf-cond-extra-clauses" > < / div >
< button type = "button" class = "smallBtn button is-small" onclick = "addFlowConditionClause()" > + Ajouter une condition< / button >
< / div >
{% endif %}
< / div >
< div id = "nodeFormAction" style = "display:none;" >
< label > Type d'action< select id = "nf-action-type" onchange = "toggleFlowActionFields()" >
{% for key, label in action_type_labels.items() %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select > < / label >
< select id = "nf-action-target-screen" style = "display:none;" >
{% for s in all_screens %}
< option value = "{{ s.id }}" > {{ s.name }}< / option >
{% endfor %}
< / select >
< div id = "nf-action-element-fields" style = "display:none;" >
< select id = "nf-action-target-element" >
{% for e2 in elements %}
< option value = "{{ e2.id }}" > {{ e2|elabel }} (elt-{{ e2.id }})< / option >
{% endfor %}
< / select >
< p class = "hint" id = "nf-action-onglet-hint" style = "display:none;" > < / p >
< div id = "nf-action-element-property-wrap" >
< select id = "nf-action-element-property" onchange = "toggleFlowElementPropertyValue()" style = "margin-top:6px;" >
{% for key, label in element_action_properties %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< select id = "nf-action-element-value-visibility" style = "margin-top:6px;" >
{% for key, label in element_visibility_values %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< select id = "nf-action-element-value-surbrillance" style = "display:none; margin-top:6px;" >
{% for key, label in element_surbrillance_values %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< select id = "nf-action-element-value-desactive" style = "display:none; margin-top:6px;" >
{% for key, label in element_desactive_values %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< input type = "color" id = "nf-action-element-value-color" style = "display:none; margin-top:6px;" value = "#5b8cff" >
< input type = "number" id = "nf-action-element-value-number" style = "display:none; margin-top:6px;" value = "50" placeholder = "Nouvelle valeur en %" >
< input type = "text" id = "nf-action-element-value-text" style = "display:none; margin-top:6px;" placeholder = "Nouveau texte affiché" >
< label id = "nf-action-element-toggle-row" style = "display:flex; align-items:center; gap:6px; margin-top:10px; font-size:12px; color:var(--text-dim);" >
< input type = "checkbox" id = "nf-action-element-toggle" onchange = "toggleFlowElementToggleMode()" style = "width:auto; margin:0;" >
Va-et-vient : au 1er clic, passer à cette valeur ; au 2e clic, revenir à la précédente ; etc.
< / label >
< div id = "nf-action-element-value2-wrap" style = "display:none; margin-top:6px;" >
< input type = "color" id = "nf-action-element-value2-color" style = "display:none;" value = "#ff0000" >
< input type = "number" id = "nf-action-element-value2-number" style = "display:none;" placeholder = "Valeur au 2e état en %" >
< / div >
< / div >
< / div >
< div id = "nf-action-wait-fields" style = "display:none;" >
< label > Délai en secondes avant de continuer
< input type = "number" id = "nf-action-wait-seconds" min = "0" step = "0.5" value = "3" placeholder = "ex. 3" >
< / label >
< / div >
< div id = "nf-action-data-fields" style = "display:none;" >
{% if not definitions %}
< p class = "hint" > Aucun objet défini dans ce jeu pour l'instant — < a href = "{{ url_for('object_new', slug=game.slug) }}" > crées-en un< / a > .< / p >
{% else %}
< select id = "nf-action-data-definition" onchange = "onFlowDefinitionChange('action')" >
{% for d in definitions %}
< option value = "{{ d.id }}" > {{ d.name }}< / option >
{% endfor %}
< / select >
< select id = "nf-action-data-row" style = "margin-top:6px;" > < / select >
< select id = "nf-action-data-field" style = "margin-top:6px;" > < / select >
< select id = "nf-action-data-operation" style = "margin-top:6px;" >
{% for key, label in data_operations %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< input type = "text" id = "nf-action-data-value" style = "margin-top:6px;" placeholder = "Valeur / montant" >
{% endif %}
< / div >
< div id = "nf-action-variable-fields" style = "display:none;" >
{% if not global_variables %}
< p class = "hint" > Aucune variable globale pour l'instant — crées-en une depuis "Condition de visibilité" dans les propriétés d'un élément.< / p >
{% else %}
< select id = "nf-action-variable-name" >
{% for v in global_variables %}
< option value = "{{ v.name }}" > {{ v.name }}< / option >
{% endfor %}
< / select >
< select id = "nf-action-variable-operation" style = "margin-top:6px;" >
{% for key, label in data_operations %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / select >
< input type = "text" id = "nf-action-variable-value" style = "margin-top:6px;" placeholder = "Valeur / montant" >
{% endif %}
< / div >
< / div >
< div class = "actionsRow" > < button type = "button" class = "smallBtn button is-small is-primary" onclick = "submitNodeForm()" > + Ajouter ce nœud< / button > < / div >
< / div >
< div class = "flowCanvasWrap" >
< svg id = "flowEdges" class = "flowEdgesSvg" > < / svg >
< div id = "flowCanvas" class = "flowCanvas" > < / div >
< / div >
< / div >
< / div >
<!-- Poignée de redimensionnement du panneau timeline, même principe que
flowResizeHandle ci - dessus. -->
< div class = "flowResizeHandle" id = "animResizeHandle" > < / div >
<!-- Panneau bas, rétractable, positionné sous "Logique de la scène" :
timeline d'animation. Une piste par élément animé, des clips posés sur
un axe du temps (secondes depuis l'affichage de l'écran) — glisse un
clip pour changer son départ, tire son bord droit pour changer sa
durée. Chaque clip est soit une animation de la bibliothèque
Animate.css, soit une animation personnalisée (images - clés propres à
ce jeu). -->
< div class = "flowPanel" id = "animPanel" >
< div class = "flowPanelHeader" onclick = "toggleAnimPanel()" >
< span class = "flowToggleIcon" > ▸< / span > 🎬 Timeline d'animation
< / div >
< div class = "flowPanelBody" id = "animPanelBody" >
< div class = "flowToolbar" >
< label style = "display:flex; align-items:center; gap:6px;" >
Piste
< select id = "anim-new-track-element" >
{% for e2 in elements %}
< option value = "{{ e2.id }}" > {{ e2|elabel }}< / option >
{% endfor %}
< / select >
< / label >
< button type = "button" class = "smallBtn button is-small" onclick = "addAnimTrack()" > ➕ Ajouter un clip< / button >
< / div >
< div id = "animClipForm" class = "nodeForm" style = "display:none;" >
< div class = "fieldRow" style = "align-items:center; justify-content:space-between;" >
< strong id = "animClipFormTitle" > Clip d'animation< / strong >
< button type = "button" class = "smallBtn button is-small" onclick = "closeAnimClipForm()" > ✕< / button >
< / div >
< label > Type
< select id = "anim-clip-kind" onchange = "toggleAnimClipKind()" >
< option value = "animate_css" > Animation Animate.css< / option >
< option value = "custom" > Animation personnalisée — images-clés< / option >
< / select >
< / label >
< div id = "anim-clip-animate-css-fields" >
< label > Animation
< select id = "anim-clip-animate-name" >
{% for category, items in animate_css_catalog %}
< optgroup label = "{{ category }}" >
{% for key, label in items %}
< option value = "{{ key }}" > {{ label }}< / option >
{% endfor %}
< / optgroup >
{% endfor %}
< / select >
< / label >
< / div >
< div id = "anim-clip-custom-fields" style = "display:none;" >
< div id = "anim-clip-keyframes" > < / div >
< button type = "button" class = "smallBtn button is-small" onclick = "addAnimKeyframeRow()" > + Ajouter une image-clé< / button >
< / div >
< div class = "fieldRow" >
< label style = "flex:1;" > Départ en secondes
< input type = "number" id = "anim-clip-start" min = "0" step = "0.1" value = "0" >
< / label >
< label style = "flex:1;" > Durée en secondes
< input type = "number" id = "anim-clip-duration" min = "0.1" step = "0.1" value = "1" >
< / label >
< / div >
< div class = "fieldRow" >
< label style = "flex:1;" > Délai en secondes
< input type = "number" id = "anim-clip-delay" min = "0" step = "0.1" value = "0" >
< / label >
< label style = "flex:1;" > Vitesse
< select id = "anim-clip-easing" >
< option value = "ease" > Progressive< / option >
< option value = "linear" > Constante< / option >
< option value = "ease-in" > Accélère< / option >
< option value = "ease-out" > Ralentit< / option >
< option value = "ease-in-out" > Accélère puis ralentit< / option >
< / select >
< / label >
< / div >
< label > Répétitions
< select id = "anim-clip-iteration" >
< option value = "1" > Une fois< / option >
< option value = "2" > 2 fois< / option >
< option value = "3" > 3 fois< / option >
< option value = "infinite" > En boucle< / option >
< / select >
< / label >
< div class = "actionsRow" >
< button type = "button" class = "smallBtn button is-small is-primary" onclick = "saveAnimClipForm()" > Enregistrer< / button >
< button type = "button" class = "smallBtn button is-small" onclick = "previewAnimClip()" > ▶ Aperçu< / button >
< button type = "button" class = "smallBtn button is-small is-danger" onclick = "deleteAnimClipForm()" > 🗑️ Supprimer< / button >
< / div >
< / div >
< div class = "animTimelineWrap" >
< div id = "animRuler" class = "animRuler" > < / div >
< div id = "animTracks" class = "animTracks" > < / div >
< / div >
< / div >
< / div >
{% endif %}
< script >
function saveGeometry ( id , x , y , width , height ) {
@@ -2251,58 +2240,24 @@ function submitNodeForm() {
}
// Panneau de logique de scène absent sur un écran-modèle (élément de jeu) —
// voir le commentaire Jinja au-dessus de la div flowPanel .
var HAS _FLOW _PANEL = ! ! document . getElementById ( 'flowPanel' ) ;
// voir le commentaire Jinja au-dessus de .builderTabs .
var HAS _FLOW _PANEL = ! ! document . getElementById ( 'flowTab Panel' ) ;
if ( HAS _FLOW _PANEL ) { renderFlow ( ) ; }
// ---------- Ouverture / fermeture et redimensionnement du panneau du bas ----------
var FLOW _PANEL _DEFAULT _HEIGHT = 340 ;
var flowPanelLastHeight = FLOW _PANEL _DEFAULT _HEIGHT ;
function toggleFlowPanel ( ) {
var panel = document . getElementById ( 'flowPanel' ) ;
var handle = document . getElementById ( 'flowResizeHandle' ) ;
if ( ! panel || ! handle ) return ;
var isOpen = panel . classList . contains ( 'open' ) ;
if ( isOpen ) {
flowPanelLastHeight = panel . getBoundingClientRect ( ) . height ;
panel . classList . remove ( 'open' ) ;
panel . style . height = '' ;
handle . classList . remove ( 'visible' ) ;
} else {
panel . classList . add ( 'open' ) ;
panel . style . height = flowPanelLastHeight + 'px' ;
handle . classList . add ( 'visible' ) ;
requestAnimationFrame ( drawFlowEdges ) ;
}
}
if ( HAS _FLOW _PANEL ) ( function ( ) {
var handle = document . getElementById ( 'flowResizeHandle' ) ;
var panel = document . getElementById ( 'flowPanel' ) ;
handle . addEventListener ( 'mousedown' , function ( e ) {
e . preventDefault ( ) ;
if ( ! panel . classList . contains ( 'open' ) ) return ;
var startY = e . clientY ;
var startHeight = panel . getBoundingClientRect ( ) . height ;
var wrap = document . querySelector ( '.content-builder' ) ;
var maxHeight = Math . max ( 160 , wrap . getBoundingClientRect ( ) . height - 160 ) ;
function onMove ( ev ) {
var dy = ev . clientY - startY ;
var newHeight = Math . min ( maxHeight , Math . max ( 140 , startHeight - dy ) ) ;
panel . style . height = newHeight + 'px' ;
flowPanelLastHeight = newHeight ;
drawFlowEdges ( ) ;
}
function onUp ( ) {
document . removeEventListener ( 'mousemove' , onMove ) ;
document . removeEventListener ( 'mouseup' , onUp ) ;
}
document . addEventListener ( 'mousemove' , onMove ) ;
document . addEventListener ( 'mouseup' , onUp ) ;
// ---------- Onglets du centre : Écran / Logique de la scène / Timeline
// d'animation — un seul visible à la fois, chacun occupant tout l'espace
// disponible (remplace l'ancien panneau du bas rétractable/redimensionnable
// à la souris : un onglet "prend toute la place" par nature, et en changer
// équivaut à "fermer" celui qu'on quitte). ----------
function switchBuilderTab ( tab ) {
document . querySelectorAll ( '.builderTabBtn' ) . forEach ( function ( btn ) {
btn . classList . toggle ( 'active' , btn . dataset . tab === tab ) ;
} ) ;
} ) ( ) ;
document . querySelectorAll ( '.builderTabPanel' ) . forEach ( function ( panel ) {
panel . classList . toggle ( 'active' , panel . dataset . tabPanel === tab ) ;
} ) ;
if ( tab === 'flow' ) requestAnimationFrame ( drawFlowEdges ) ;
}
// ---------- Timeline d'animation : piste par élément, clips Animate.css ou
// personnalisés (images-clés), glissés/redimensionnés à la souris sur un
@@ -2617,53 +2572,8 @@ function previewAnimClip() {
}
// Panneau timeline absent sur un écran-modèle (élément de jeu), comme le
// panneau de logique — voir le commentaire Jinja au-dessus de la div animPanel .
var HAS _ANIM _PANEL = ! ! document . getElementById ( 'animPanel' ) ;
// panneau de logique — voir le commentaire Jinja au-dessus de .builderTabs .
var HAS _ANIM _PANEL = ! ! document . getElementById ( 'animTab Panel' ) ;
if ( HAS _ANIM _PANEL ) { renderAnimTimeline ( ) ; }
var ANIM _PANEL _DEFAULT _HEIGHT = 260 ;
var animPanelLastHeight = ANIM _PANEL _DEFAULT _HEIGHT ;
function toggleAnimPanel ( ) {
var panel = document . getElementById ( 'animPanel' ) ;
var handle = document . getElementById ( 'animResizeHandle' ) ;
if ( ! panel || ! handle ) return ;
var isOpen = panel . classList . contains ( 'open' ) ;
if ( isOpen ) {
animPanelLastHeight = panel . getBoundingClientRect ( ) . height ;
panel . classList . remove ( 'open' ) ;
panel . style . height = '' ;
handle . classList . remove ( 'visible' ) ;
} else {
panel . classList . add ( 'open' ) ;
panel . style . height = animPanelLastHeight + 'px' ;
handle . classList . add ( 'visible' ) ;
}
}
if ( HAS _ANIM _PANEL ) ( function ( ) {
var handle = document . getElementById ( 'animResizeHandle' ) ;
var panel = document . getElementById ( 'animPanel' ) ;
handle . addEventListener ( 'mousedown' , function ( e ) {
e . preventDefault ( ) ;
if ( ! panel . classList . contains ( 'open' ) ) return ;
var startY = e . clientY ;
var startHeight = panel . getBoundingClientRect ( ) . height ;
var wrap = document . querySelector ( '.content-builder' ) ;
var maxHeight = Math . max ( 160 , wrap . getBoundingClientRect ( ) . height - 160 ) ;
function onMove ( ev ) {
var dy = ev . clientY - startY ;
var newHeight = Math . min ( maxHeight , Math . max ( 140 , startHeight - dy ) ) ;
panel . style . height = newHeight + 'px' ;
animPanelLastHeight = newHeight ;
}
function onUp ( ) {
document . removeEventListener ( 'mousemove' , onMove ) ;
document . removeEventListener ( 'mouseup' , onUp ) ;
}
document . addEventListener ( 'mousemove' , onMove ) ;
document . addEventListener ( 'mouseup' , onUp ) ;
} ) ;
} ) ( ) ;
< / script >
{% endblock %}