Remplace l'ancien panneau du bas rétractable/redimensionnable à la souris (Logique de la scène, Timeline d'animation) par un système d'onglets au centre de l'éditeur - Écran / Logique de la scène / Timeline d'animation - un seul visible à la fois, occupant systématiquement tout l'espace disponible (switchBuilderTab() dans screen_edit.html). Changer d'onglet équivaut à "fermer" celui qu'on quitte ; plus besoin d'une poignée de redimensionnement séparée puisque l'onglet actif prend déjà toute la place. Supprime au passage tout l'ancien mécanisme (toggleFlowPanel/ toggleAnimPanel, poignées flowResizeHandle/animResizeHandle, classe CSS .flowPanel) devenu inutile. Déplace aussi le renommage de l'écran, le bouton "Jouer depuis le début" et le choix du format d'aperçu (Portrait/Paysage/Carré) - jusqu'ici au-dessus du canevas - dans le panneau flottant "Éléments" (celui qui porte déjà ce nom, à gauche) : des réglages qu'on touche rarement une fois l'écran en construction, qui n'ont plus besoin de rester en permanence visibles au-dessus de la zone de travail. Corrige au passage un bug découvert pendant ce tour : sur la page "Nouvel objet" (2 colonnes), le bouton "+ Ajouter un champ" avait disparu - placé APRÈS la zone de liste à défilement (flex-grow:1) dans la colonne de droite, un flex-grow imprévisible dans ce contexte le poussait hors de vue. Déplacé avant la liste (statique, toujours visible), pattern déjà éprouvé ailleurs sur cette même page. Deux tests mis à jour pour refléter intentionnellement la nouvelle structure : la présence de "animTabPanel" (au lieu de l'ancien "animPanel"), et un marqueur plus précis pour distinguer le bloc de propriétés "Onglets" d'une simple occurrence du même texte dans une liste déroulante de la Logique de la scène (qui apparaît désormais plus tôt dans le document, cet éditeur de flow étant maintenant un onglet du centre plutôt qu'un panneau tout en bas de page). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
459 lines
26 KiB
CSS
459 lines
26 KiB
CSS
:root{
|
|
--bg:#12151c; --panel:#191d27; --panel2:#1f2430; --border:#2b3140;
|
|
--text:#e8eaf0; --text-dim:#8b93a7; --accent:#5b8cff; --danger:#e2685f;
|
|
}
|
|
*{ box-sizing:border-box; }
|
|
body{ margin:0; font-family:system-ui,-apple-system,"Segoe UI",sans-serif; background:var(--bg); color:var(--text); }
|
|
h1{ font-size:20px; margin:0 0 6px; }
|
|
h2{ font-size:13px; text-transform:uppercase; letter-spacing:.05em; color:var(--text-dim); margin:24px 0 8px; }
|
|
h3{ font-size:13px; color:var(--accent); margin:14px 0 6px; }
|
|
label{ display:block; font-size:12px; color:var(--text-dim); margin:8px 0 4px; }
|
|
input[type=text], input[type=number], input[type=date], textarea, select{
|
|
width:100%; background:var(--panel2); border:1px solid var(--border); border-radius:6px;
|
|
color:var(--text); padding:8px 10px; font-size:13px; font-family:inherit;
|
|
}
|
|
input[type=checkbox]{ width:auto; margin-top:6px; }
|
|
|
|
/* .topbar/.breadcrumbBar : Bulma fournit déjà la mise en page (navbar,
|
|
breadcrumb) — ces classes ne servent plus qu'à les repérer dans les
|
|
layouts plein-écran (body.builderBody/objectEditBody, voir plus bas) et à
|
|
les rattacher au thème sombre. */
|
|
.topbar{ border-bottom:1px solid var(--border); }
|
|
.breadcrumbBar{ padding:10px 20px; background:transparent; border-bottom:1px solid var(--border); margin-bottom:0; }
|
|
.content{ max-width:760px; margin:0 auto; padding:28px 20px; }
|
|
|
|
.card{ border:1px solid var(--border); border-radius:10px; padding:16px; background:var(--panel); margin:12px 0; }
|
|
.inline-form .fieldRow{ display:flex; gap:8px; align-items:flex-end; }
|
|
.inline-form .fieldRow input{ flex:1; }
|
|
.fieldRow{ display:flex; gap:10px; }
|
|
.fieldRow > div{ flex:1; }
|
|
.formField{ margin-bottom:6px; }
|
|
|
|
button{ background:var(--panel2); border:1px solid var(--border); color:var(--text); border-radius:6px; padding:9px 14px; cursor:pointer; font-size:13px; }
|
|
button.primary{ background:var(--accent); border-color:var(--accent); color:#fff; font-weight:600; }
|
|
button.smallBtn{ padding:5px 10px; font-size:11.5px; }
|
|
button.danger{ color:var(--danger); border-color:rgba(226,104,95,.4); }
|
|
.actionsRow{ margin-top:16px; }
|
|
|
|
/* Nav compacte à une seule ligne (icône + libellé) — pour une liste
|
|
d'actions fixe (ex. colonne "Créer" du tableau de bord d'un jeu) : sobre
|
|
et dense plutôt qu'une carte à deux lignes. */
|
|
.navList{ display:flex; flex-direction:column; gap:3px; }
|
|
.navRow{ display:flex; align-items:center; gap:8px; padding:7px 10px; border:1px solid var(--border); border-radius:6px; background:var(--panel); text-decoration:none; color:var(--text); font-size:12.5px; }
|
|
.navRow:hover{ background:var(--panel2); border-color:var(--accent); }
|
|
|
|
.hint{ font-size:11.5px; color:var(--text-dim); line-height:1.5; }
|
|
.autosaveStatus{ display:inline-flex; align-items:center; gap:6px; transition:color .2s ease; }
|
|
.autosaveStatus.saving{ color:var(--text-dim); }
|
|
.autosaveStatus.saved{ color:#7bd88f; }
|
|
.warningBanner{ border:1px solid var(--danger); background:rgba(226,104,95,.1); color:var(--text);
|
|
border-radius:8px; padding:10px 14px; font-size:12.5px; margin:12px 0; }
|
|
.dangerZone{ border:1px solid rgba(226,104,95,.4); border-radius:8px; padding:10px 12px; margin-top:14px; }
|
|
.dangerZone h3{ color:var(--danger); margin-top:0; }
|
|
.fieldsExistingRow{ display:flex; align-items:center; justify-content:space-between;
|
|
padding:8px 10px; border:1px solid var(--border); border-radius:6px; margin-bottom:6px; font-size:12.5px; }
|
|
.fieldsExistingRow form{ margin:0; }
|
|
|
|
.fieldRowCard{ margin-bottom:10px; }
|
|
.relationTarget{ margin-top:6px; }
|
|
|
|
/* .dataTable : Bulma fournit déjà la mise en page du tableau (classes
|
|
"table is-fullwidth is-hoverable", voir data_list.html) — ceci ne fait
|
|
plus que tronquer les cellules trop longues avec "...", pour garder une
|
|
vue d'ensemble compacte (le détail complet s'ouvre dans la modale Bulma
|
|
ci-dessous via le bouton 👁️). */
|
|
.dataTable td{ max-width:220px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
|
|
/* ---------- Détail d'une entrée (bouton 👁️) ---------- */
|
|
/* Contenu de la modale Bulma (.modal/.modal-card, voir data_list.html) —
|
|
affiche chaque champ en entier, sans troncature. */
|
|
.rowDetailField{ padding:10px 0; border-bottom:1px solid var(--border); }
|
|
.rowDetailField:last-child{ border-bottom:none; }
|
|
.rowDetailLabel{ font-size:11px; color:var(--text-dim); text-transform:uppercase; letter-spacing:.05em; margin-bottom:4px; }
|
|
.rowDetailValue{ font-size:13.5px; white-space:pre-wrap; word-break:break-word; }
|
|
|
|
.content-wide{ max-width:1080px; }
|
|
.twoCol{ display:flex; gap:24px; align-items:flex-start; margin-top:10px; }
|
|
.twoCol .colLeft{ flex:0 0 340px; }
|
|
.twoCol .colRight{ flex:1; min-width:0; }
|
|
@media (max-width: 820px){
|
|
.twoCol{ flex-direction:column; }
|
|
.twoCol .colLeft{ flex:none; width:100%; }
|
|
/* En dessous de 820px, les deux colonnes s'empilent : le défilement
|
|
interne séparé par colonne n'a plus de sens (il n'y a plus qu'une
|
|
colonne) — on revient à une page classique qui défile normalement,
|
|
comme pour l'éditeur d'écran ci-dessous. */
|
|
body.objectEditBody{ height:auto; overflow:visible; }
|
|
body.objectEditBody > main.content{ overflow:visible; }
|
|
.content-objectEdit .colLeft, .content-objectEdit .colRight{ height:auto; overflow:visible; }
|
|
.fieldsTableWrap{ max-height:60vh; }
|
|
}
|
|
|
|
/* ---------- Modifier un objet : tableau des champs existants ---------- */
|
|
/* Comme l'éditeur d'écran (body.builderBody, plus bas) : la page ne défile
|
|
jamais elle-même, seules les zones internes (colonne de gauche, tableau
|
|
des champs) défilent chacune de leur côté — indispensable dès qu'un objet
|
|
a beaucoup de champs, pour garder une vue d'ensemble sans perdre le
|
|
formulaire "Ajouter un champ" hors de l'écran. */
|
|
body.objectEditBody{ display:flex; flex-direction:column; height:100vh; margin:0; overflow:hidden; }
|
|
body.objectEditBody > .topbar, body.objectEditBody > .breadcrumbBar{ flex:0 0 auto; }
|
|
body.objectEditBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; display:flex; flex-direction:column; }
|
|
|
|
.content-objectEdit{ width:100%; display:flex; flex-direction:column; min-height:0; }
|
|
.content-objectEdit > h1, .content-objectEdit > .hint, .content-objectEdit > form, .content-objectEdit > .warningBanner{ flex:0 0 auto; }
|
|
|
|
/* Un <form> qui doit envelopper les DEUX colonnes à la fois (un seul
|
|
formulaire soumis ensemble, ex. object_form.html : nom de l'objet à
|
|
gauche, liste de champs à droite) ne doit pas devenir lui-même une boîte
|
|
dans la mise en page flex — sinon .twoCol à l'intérieur perd la hauteur
|
|
disponible qu'il doit occuper. "display:contents" le rend transparent :
|
|
.twoCol se comporte comme s'il était directement enfant de
|
|
.content-objectEdit. */
|
|
.formPassthrough{ display:contents; }
|
|
.content-objectEdit .twoCol{ flex:1 1 auto; min-height:0; align-items:stretch; }
|
|
.content-objectEdit .colLeft{ overflow-y:auto; height:100%; }
|
|
.content-objectEdit .colRight{ display:flex; flex-direction:column; min-height:0; height:100%; }
|
|
.content-objectEdit .colRight h2{ flex:0 0 auto; }
|
|
.content-objectEdit .colRight > .hint, .content-objectEdit .colRight > button{ flex:0 0 auto; }
|
|
|
|
.fieldsTableWrap{ flex:1 1 auto; min-height:0; overflow:auto; border:1px solid var(--border); border-radius:8px; }
|
|
.fieldsTable{ width:100%; border-collapse:collapse; font-size:12.5px; }
|
|
.fieldsTable th, .fieldsTable td{ border-bottom:1px solid var(--border); padding:8px 10px; text-align:left; white-space:nowrap; }
|
|
.fieldsTable th{ position:sticky; top:0; background:var(--panel2); color:var(--text-dim); z-index:1; }
|
|
.fieldsTable tbody tr:hover{ background:rgba(255,255,255,.02); }
|
|
.fieldsTable tbody tr:last-child td{ border-bottom:none; }
|
|
.fieldsTable td input, .fieldsTable td select{ min-width:120px; width:auto; }
|
|
.fieldsTableActions{ display:flex; gap:6px; }
|
|
|
|
/* ---------- Éditeur d'écrans (façon Elementor/Bricks) ---------- */
|
|
/* La page de l'éditeur d'écran ne doit jamais défiler elle-même : elle
|
|
occupe exactement la hauteur de la fenêtre (topbar + zone de travail), et
|
|
ce sont les zones internes (panneaux, écran, panneau de logique) qui
|
|
défilent chacune de leur côté si besoin. */
|
|
body.builderBody{ display:flex; flex-direction:column; height:100vh; margin:0; overflow:hidden; }
|
|
body.builderBody > .topbar, body.builderBody > .breadcrumbBar{ flex:0 0 auto; }
|
|
body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; display:flex; flex-direction:column; }
|
|
|
|
.content-builder{
|
|
/* .content (ci-dessus) pose "margin:0 auto" pour centrer une page normale
|
|
— mais sur cette page, le body est un flex-container en colonne
|
|
(body.builderBody), et une marge horizontale "auto" y désactive le
|
|
stretch d'un flex-item : sa largeur est alors recalculée en
|
|
"shrink-to-fit" à partir de son contenu, un calcul qui traverse toute
|
|
la chaîne flex/%/aspect-ratio jusqu'au canvas et redonne un résultat
|
|
légèrement différent à chaque changement de contenu du panneau (élément
|
|
sélectionné, grille affichée...) — d'où le canvas qui grandit/rétrécit
|
|
sans raison apparente à chaque interaction. "width:100%" donne une
|
|
taille définie à la place : la marge "auto" ne fait alors que centrer
|
|
l'espace restant une fois "max-width" atteint, comme voulu à l'origine. */
|
|
width:100%;
|
|
max-width:1600px;
|
|
}
|
|
|
|
/* 3 colonnes : ajout d'éléments (gauche) / écran en grand (centre) / propriétés (droite) —
|
|
toutes les trois occupent exactement la même hauteur (celle qui reste
|
|
disponible), avec leur propre défilement interne si leur contenu dépasse. */
|
|
.builder3{ display:flex; gap:20px; align-items:stretch; margin-top:14px; flex:1 1 auto; min-height:0; }
|
|
.builderCanvasArea{ flex:1 1 auto; min-width:0; height:100%; overflow-y:auto; display:flex; flex-direction:column; }
|
|
|
|
/* ---------- Panneaux gauche/droite flottants ---------- */
|
|
/* Les deux panneaux (éléments de l'écran / propriétés) sont sortis du flux
|
|
flex de .builder3 (position:fixed) : .builderCanvasArea (flex:1 1 auto)
|
|
récupère automatiquement tout l'espace libéré, sans réglage
|
|
supplémentaire — les fermer donne donc une prévisualisation aussi large
|
|
que possible, aussi fidèle que possible à ce que "Jouer" affiche
|
|
réellement. Position et état ouvert/fermé mémorisés par écran (voir
|
|
floatPanel*() dans screen_edit.html) pour survivre à un changement de
|
|
sélection (qui remplace tout #builder3) ou un rechargement. */
|
|
.floatPanel{
|
|
position:fixed; top:90px; max-height:calc(100vh - 120px);
|
|
min-width:0; display:flex; flex-direction:column; z-index:60;
|
|
background:var(--panel); border:1px solid var(--border); border-radius:10px;
|
|
box-shadow:0 12px 40px rgba(0,0,0,.55);
|
|
}
|
|
.floatPanel.hidden{ display:none; }
|
|
.floatPanel--left{ left:24px; width:280px; }
|
|
.floatPanel--right{ right:24px; width:340px; }
|
|
.floatPanelHeader{
|
|
display:flex; align-items:center; justify-content:space-between; gap:8px;
|
|
padding:8px 10px; border-bottom:1px solid var(--border); background:var(--panel2);
|
|
border-radius:10px 10px 0 0; cursor:move; flex:0 0 auto; user-select:none;
|
|
}
|
|
.floatPanelHeader .floatPanelTitle{ font-size:11px; color:var(--text-dim); text-transform:uppercase; letter-spacing:.05em; }
|
|
.floatPanelBody{ padding:12px; overflow-y:auto; min-height:0; flex:1 1 auto; }
|
|
.floatPanelReopenBtn{ position:fixed; top:90px; z-index:60; display:none; }
|
|
.floatPanelReopenBtn.visible{ display:inline-flex; }
|
|
#panelLeftReopenBtn{ left:24px; }
|
|
#propsPanelReopenBtn{ right:24px; }
|
|
|
|
.widgetGrid{ display:grid; grid-template-columns:1fr 1fr; gap:8px; }
|
|
|
|
/* Icônes (widget Icône + galerie du panneau de propriétés) — SVG posé en
|
|
silhouette via "mask-image" plutôt qu'une police d'icônes (@font-face) :
|
|
se colore avec "color"/"background-color" comme du texte, MAIS ne dépend
|
|
d'aucun fichier de police externe — certains navigateurs/extensions
|
|
bloquent le chargement de polices web tierces (protection anti-
|
|
fingerprinting), ce qui rendait toutes les icônes Font Awesome invisibles
|
|
quelle que soit leur source (CDN ou même hébergées ici). Un simple SVG
|
|
affiché en masque n'est jamais concerné par ce blocage. Voir
|
|
render_icone.py (widget) et fontawesome_icons.py (liste + chemin des
|
|
fichiers, statiques dans static/icons/). */
|
|
.icon-svg{
|
|
display:inline-block; width:1em; height:1em; flex:0 0 auto;
|
|
background-color:currentColor;
|
|
-webkit-mask-image:var(--icon-url); mask-image:var(--icon-url);
|
|
-webkit-mask-size:contain; mask-size:contain;
|
|
-webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
|
|
-webkit-mask-position:center; mask-position:center;
|
|
}
|
|
|
|
/* Galerie d'icônes (panneau de propriétés) — voir
|
|
fontawesome_icons.py et addIconElement()/filterIconGallery() plus bas. */
|
|
.iconGallery{ display:grid; grid-template-columns:repeat(6, 1fr); gap:6px; max-height:220px; overflow-y:auto; margin-top:8px; }
|
|
.iconGalleryTile{
|
|
aspect-ratio:1; display:flex; align-items:center; justify-content:center;
|
|
background:var(--panel2); border:1px solid var(--border); border-radius:6px;
|
|
color:var(--text); cursor:grab; padding:0;
|
|
}
|
|
.iconGalleryTile .icon-svg{ width:16px; height:16px; }
|
|
.iconGalleryTile:hover{ border-color:var(--accent); color:var(--accent); }
|
|
.iconGalleryTile.dragging{ opacity:0.4; }
|
|
.iconGalleryTile.iconHidden{ display:none; }
|
|
.widgetBtnForm{ margin:0; }
|
|
.widgetBtn{
|
|
width:100%; display:flex; flex-direction:column; align-items:center; gap:4px;
|
|
padding:12px 6px; border:1px solid var(--border); border-radius:10px; background:var(--panel);
|
|
color:var(--text); cursor:pointer;
|
|
}
|
|
.widgetBtn:hover{ border-color:var(--accent); background:var(--panel2); }
|
|
.widgetIcon{ font-size:22px; }
|
|
.widgetLabel{ font-size:11px; text-align:center; line-height:1.2; color:var(--text-dim); }
|
|
|
|
.canvasToolbar{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:10px; }
|
|
.aspectBtn.active{ background:var(--accent); border-color:var(--accent); color:#fff; }
|
|
|
|
.canvasFrame{
|
|
width:100%; padding:10px; border:1px solid var(--border); border-radius:16px; background:var(--panel);
|
|
}
|
|
.canvas{
|
|
position:relative; width:100%; max-width:100%; margin:0 auto; background:#0b0d12; border-radius:8px;
|
|
overflow:hidden; border:1px solid var(--border);
|
|
}
|
|
.canvasElement{
|
|
/* Pas d'overflow:hidden ici : une échelle (transform:scale) ou une
|
|
animation posée sur cet élément (voir la Timeline d'animation) vit sur
|
|
CE cadre (voir filters/element_style_filter.py) — le rogner clippait
|
|
l'effet à la taille d'origine de la boîte au lieu de le laisser
|
|
grossir/bouger avec elle. La zone de l'écran elle-même (.canvas,
|
|
ci-dessus) reste rognée, donc rien ne peut déborder de l'écran final. */
|
|
box-sizing:border-box; cursor:move; user-select:none;
|
|
border:1px dashed transparent;
|
|
}
|
|
.canvasElement:hover{ border-color:rgba(91,140,255,.5); }
|
|
.canvasElement.selected{ border:2px solid var(--accent); }
|
|
.canvasElementInner{ width:100%; height:100%; pointer-events:none; }
|
|
|
|
/* Surbrillance de l'élément sélectionné dans l'arborescence, posée en JS sur
|
|
sa propre balise (voir applySelectionHighlight) — seul moyen de la voir
|
|
sur le canevas quand cet élément est imbriqué dans un conteneur/répéteur/
|
|
groupe de champs, qui n'a pas de cadre ".canvasElement" séparé comme un
|
|
élément posé directement sur l'écran. Un "outline" qui dépasse la boîte
|
|
(offset positif) se fait couper par le premier ancêtre en overflow:hidden
|
|
(le canevas lui-même, ou un conteneur aux coins arrondis) dès que
|
|
l'élément touche un de ses bords — très fréquent pour une rangée de
|
|
jauges collée en haut d'un conteneur. Un box-shadow EN CREUX (inset)
|
|
reste toujours à l'intérieur de la boîte, jamais rogné, et épouse
|
|
naturellement les coins arrondis de l'élément lui-même. */
|
|
.elementHighlighted{
|
|
box-shadow: inset 0 0 0 2px var(--accent), inset 0 0 0 6px rgba(91,140,255,.2);
|
|
}
|
|
.resizeHandle{
|
|
position:absolute; right:0; bottom:0; width:14px; height:14px; cursor:nwse-resize;
|
|
background:var(--accent); border-radius:3px 0 6px 0; opacity:.85;
|
|
}
|
|
|
|
.listRow.selected{ border-color:var(--accent); background:var(--panel2); }
|
|
|
|
.childList{ display:flex; flex-direction:column; gap:6px; margin:2px 0 10px 18px; padding-left:10px; border-left:2px dashed var(--border); }
|
|
.childRow{ padding:7px 10px; font-size:12px; }
|
|
.childRow .rowTitle{ font-size:12px; }
|
|
|
|
/* Arborescence compacte des éléments de l'écran (panneau gauche) — une
|
|
ligne par élément, indentation par imbrication réelle (profondeur
|
|
illimitée) plutôt qu'un seul niveau d'enfants comme l'ancienne .childList,
|
|
pour rester lisible même avec beaucoup d'éléments. */
|
|
.elementTree{ display:flex; flex-direction:column; gap:1px; max-height:55vh; overflow-y:auto; margin-bottom:14px; }
|
|
.treeNode{ display:flex; flex-direction:column; }
|
|
.treeRow{ display:flex; align-items:center; gap:2px; }
|
|
.treeToggle{
|
|
flex:0 0 auto; width:16px; height:16px; padding:0; border:none; background:none; cursor:pointer;
|
|
color:var(--text-dim); font-size:10px; display:flex; align-items:center; justify-content:center;
|
|
transition:transform .12s ease;
|
|
}
|
|
.treeToggle:hover{ color:var(--text); }
|
|
.treeToggleSpacer{ flex:0 0 auto; width:16px; height:16px; }
|
|
.treeNode.collapsed > .treeRow > .treeToggle{ transform:rotate(-90deg); }
|
|
.treeNode.collapsed > .treeChildren{ display:none; }
|
|
.treeRowLink{
|
|
flex:1; display:flex; align-items:center; gap:6px; padding:4px 6px; border-radius:5px;
|
|
text-decoration:none; color:var(--text); font-size:12.5px; line-height:1.5;
|
|
white-space:nowrap; overflow:hidden; min-width:0;
|
|
}
|
|
.treeRowLink:hover{ background:var(--panel2); }
|
|
.treeRowLink.selected{ background:var(--accent); color:#0b0e14; font-weight:600; }
|
|
.treeIcon{ flex:0 0 auto; font-size:12px; }
|
|
.treeLabel{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
.treeChildren{ display:flex; flex-direction:column; margin-left:10px; padding-left:8px; border-left:1px dashed var(--border); }
|
|
.treeRow.dragging{ opacity:0.4; }
|
|
.treeRow.dropBefore{ box-shadow: inset 0 2px 0 0 var(--accent); }
|
|
.treeRow.dropAfter{ box-shadow: inset 0 -2px 0 0 var(--accent); }
|
|
.treeRow.dropInside{ background:rgba(91,140,255,.25); box-shadow: inset 0 0 0 1px var(--accent); border-radius:4px; }
|
|
|
|
.contextMenu{
|
|
position:fixed; z-index:500; background:var(--panel); border:1px solid var(--border);
|
|
border-radius:8px; box-shadow:0 8px 24px rgba(0,0,0,.4); padding:4px; min-width:160px;
|
|
}
|
|
.contextMenu button{
|
|
display:block; width:100%; text-align:left; background:none; border:none; color:var(--text);
|
|
padding:7px 10px; border-radius:5px; font-size:13px; cursor:pointer;
|
|
}
|
|
.contextMenu button:hover{ background:var(--panel2); }
|
|
|
|
/* Élément à onglets (voir render_onglets.py) : la barre d'onglets elle-même
|
|
est un vrai composant Bulma ("tabs" > "ul" > "li", voir render_onglets.py)
|
|
— il ne reste ici que les panneaux, que Bulma n'a pas de composant pour. */
|
|
.forgeTabPanels{ min-width:0; }
|
|
.forgeTabPanel{ padding-top:10px; }
|
|
|
|
.propGroup{ border:1px solid var(--border); border-radius:8px; margin-bottom:8px; background:var(--panel); }
|
|
.propGroup > summary{
|
|
list-style:none; cursor:pointer; padding:9px 12px; font-size:13px; color:var(--accent);
|
|
font-weight:600; user-select:none; display:flex; align-items:center; justify-content:space-between;
|
|
}
|
|
.propGroup > summary::-webkit-details-marker{ display:none; }
|
|
.propGroup > summary::after{ content:"▸"; color:var(--text-dim); font-size:11px; transition:transform .15s; }
|
|
.propGroup[open] > summary::after{ transform:rotate(90deg); }
|
|
.propGroup > summary:hover{ background:var(--panel2); }
|
|
.propGroupBody{ padding:2px 12px 12px; }
|
|
.propGroupBody h3{ margin-top:0; }
|
|
|
|
|
|
/* .posGrid/.controlRow/.sizeField/.sliderRow/.alignGroup : Bulma fournit
|
|
déjà la mise en page (columns/field/control/buttons has-addons, voir
|
|
screen_edit.html) — ce qui reste ici n'est que ce que Bulma ne fournit
|
|
pas nativement : l'espacement propre à un curseur (slider) et son
|
|
pastille de valeur affichée à côté. */
|
|
.posGrid input:disabled{ opacity:0.4; cursor:not-allowed; }
|
|
.sliderRow{ display:flex; align-items:center; gap:10px; }
|
|
.sliderRow input[type=range]{ flex:1; }
|
|
.sliderValue{ min-width:44px; text-align:right; }
|
|
input[type=color]{ width:52px; height:34px; padding:2px; }
|
|
|
|
.propsPanel .sectionDivider, .floatPanel .sectionDivider{ border:none; border-top:1px solid var(--border); margin:14px 0; }
|
|
.actionAddForm{ margin-top:10px; }
|
|
|
|
.uploadRow{ margin-top:6px; }
|
|
.uploadInput{ font-size:11px; color:var(--text-dim); }
|
|
.uploadStatus{ display:block; margin-top:4px; }
|
|
code{ background:var(--panel2); border:1px solid var(--border); border-radius:4px; padding:1px 6px; font-size:11.5px; }
|
|
|
|
@media (max-width: 1300px){
|
|
/* Sous cette largeur, les 3 colonnes s'empilent : le principe "tout tient
|
|
dans la fenêtre, chaque zone défile pour elle-même" n'a plus de sens
|
|
(il n'y a plus qu'une seule colonne) — on revient à une page classique
|
|
qui défile normalement. */
|
|
body.builderBody{ height:auto; overflow:visible; }
|
|
body.builderBody > main.content{ overflow:visible; }
|
|
.builder3{ flex-direction:column; flex:none; }
|
|
.builderCanvasArea{ flex:none; width:100%; height:auto; max-height:none; overflow:visible; }
|
|
/* Trop peu de largeur pour qu'un panneau flottant ait un sens (il
|
|
couvrirait tout l'écran) : les deux rejoignent la pile normale et
|
|
restent toujours ouverts/visibles, quel que soit l'état mémorisé —
|
|
.hidden est ignoré grâce au "!important" sur "display" ci-dessous. */
|
|
.floatPanel{ position:static; flex:none; width:100%; height:auto; max-height:none; box-shadow:none; top:auto; left:auto; right:auto; display:flex !important; }
|
|
.floatPanelReopenBtn{ display:none !important; }
|
|
}
|
|
|
|
/* ---------- Onglets du centre : Écran / Logique de la scène / Timeline
|
|
d'animation ---------- */
|
|
/* Un seul onglet visible à la fois (voir switchBuilderTab() dans
|
|
screen_edit.html), chacun occupant tout l'espace disponible dans
|
|
.builderCanvasArea — remplace l'ancien panneau du bas rétractable/
|
|
redimensionnable à la souris : un onglet "prend toute la place" par
|
|
nature, pas besoin d'une poignée de redimensionnement séparée, et en
|
|
changer d'onglet équivaut à "fermer" celui qu'on quitte. */
|
|
.builderTabs{ display:flex; gap:2px; flex:0 0 auto; border-bottom:1px solid var(--border); margin-bottom:10px; }
|
|
.builderTabBtn{
|
|
background:transparent; border:none; border-bottom:2px solid transparent; border-radius:0;
|
|
padding:8px 12px; color:var(--text-dim); font-size:12.5px; cursor:pointer;
|
|
}
|
|
.builderTabBtn:hover{ color:var(--text); }
|
|
.builderTabBtn.active{ color:var(--accent); border-bottom-color:var(--accent); font-weight:600; }
|
|
|
|
.builderTabPanel{ flex:1 1 auto; min-height:0; display:none; flex-direction:column; }
|
|
.builderTabPanel.active{ display:flex; }
|
|
|
|
.flowToolbar{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:10px; flex:0 0 auto; }
|
|
|
|
.nodeForm{ border:1px solid var(--border); border-radius:8px; padding:12px; margin-bottom:12px; background:var(--panel2); max-width:420px; flex:0 0 auto; }
|
|
.nodeForm label{ margin-top:8px; }
|
|
|
|
.flowCanvasWrap{ position:relative; width:100%; flex:1 1 auto; min-height:120px; overflow:auto; border:1px dashed var(--border); border-radius:8px; background:#0d0f15; }
|
|
.flowCanvas{ position:relative; width:2200px; height:1400px; }
|
|
.flowEdgesSvg{ position:absolute; top:0; left:0; width:2200px; height:1400px; pointer-events:none; z-index:0; }
|
|
.flowEdgeLine{ stroke:var(--accent); stroke-width:2; pointer-events:stroke; cursor:pointer; }
|
|
.flowEdgeLine.flowEdgeTrue{ stroke:#4caf7d; }
|
|
.flowEdgeLine.flowEdgeFalse{ stroke:var(--danger); }
|
|
|
|
.flowNode{
|
|
position:absolute; width:220px; min-height:52px; border-radius:8px; border:1px solid var(--border);
|
|
background:var(--panel2); padding:10px 12px; font-size:12.5px; cursor:grab; z-index:1; user-select:none;
|
|
}
|
|
.flowNode-trigger{ border-color:#5b8cff; }
|
|
.flowNode-condition{ border-color:#e0b23c; }
|
|
.flowNode-action{ border-color:#4caf7d; }
|
|
.flowNodeBody{ padding-right:16px; line-height:1.4; word-break:break-word; }
|
|
.flowNodeDelete{
|
|
position:absolute; top:4px; right:4px; width:18px; height:18px; padding:0; line-height:1;
|
|
font-size:11px; border-radius:4px; background:transparent; border:none; color:var(--text-dim);
|
|
}
|
|
.flowNodeDelete:hover{ color:var(--danger); }
|
|
.flowPort{
|
|
position:absolute; width:14px; height:14px; border-radius:50%; background:var(--panel);
|
|
border:2px solid var(--accent); top:50%; transform:translateY(-50%); cursor:crosshair; z-index:2;
|
|
}
|
|
.flowPortIn{ left:-8px; }
|
|
.flowPortOut{ right:-8px; }
|
|
.flowPortTrue{ right:-8px; top:35%; border-color:#4caf7d; }
|
|
.flowPortFalse{ right:-8px; top:65%; border-color:var(--danger); }
|
|
|
|
/* ---------- Timeline d'animation : panneau sous "Logique de la scène" ---------- */
|
|
.animTimelineWrap{
|
|
position:relative; width:100%; flex:1 1 auto; min-height:120px; overflow:auto;
|
|
border:1px dashed var(--border); border-radius:8px; background:#0d0f15;
|
|
}
|
|
.animRuler{ display:flex; height:22px; border-bottom:1px solid var(--border); position:sticky; top:0; background:#0d0f15; z-index:3; }
|
|
.animRulerSpacer{ position:sticky; left:0; width:140px; flex:0 0 140px; background:var(--panel); z-index:2; border-right:1px solid var(--border); }
|
|
.animRulerTicksWrap{ position:relative; flex:0 0 auto; }
|
|
.animRulerTick{
|
|
position:absolute; top:0; bottom:0; font-size:10px; color:var(--text-dim);
|
|
border-left:1px solid var(--border); padding-left:3px;
|
|
}
|
|
.animTracks{ position:relative; }
|
|
.animTrack{ position:relative; height:38px; border-bottom:1px solid var(--border); display:flex; align-items:stretch; }
|
|
.animTrackLabel{
|
|
position:sticky; left:0; width:140px; flex:0 0 140px; background:var(--panel); z-index:2;
|
|
padding:0 8px; font-size:12px; display:flex; align-items:center; border-right:1px solid var(--border);
|
|
overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
|
|
}
|
|
.animTrackLane{ position:relative; flex:0 0 auto; }
|
|
.animClip{
|
|
position:absolute; top:5px; height:28px; border-radius:6px; background:var(--accent); color:#fff;
|
|
font-size:11px; padding:5px 8px; cursor:grab; overflow:hidden; white-space:nowrap; user-select:none;
|
|
box-sizing:border-box;
|
|
}
|
|
.animClip.selected{ outline:2px solid #fff; }
|
|
.animClipResizeHandle{ position:absolute; right:0; top:0; width:8px; height:100%; cursor:ew-resize; }
|
|
.animKeyframeRow{ align-items:flex-start; }
|
|
.animKeyframeRow textarea{ font-family:monospace; font-size:11px; }
|