Files
Forge-Engine/static/style.css
T
william 25a331830b Reskin the whole admin UI (not just game widgets) with Bulma
Loads Bulma 1.0.2 in base.html (data-theme="dark" for its native dark palette) for every page that extends it, plus play.html directly. Converts every admin template — index, game dashboard, screens list, element types, object new/edit, data form/list — to real Bulma markup: navbar, breadcrumb component, box/card, field/control/input/select, table, notification, buttons, and a native Bulma modal for the data-row detail popup. Forge's own style.css keeps only what Bulma doesn't cover (the fixed-viewport builder/object-edit layouts, the element tree, canvas, flow-graph editor) and now acts as a secondary/override layer rather than a competing design system, matching how per-element inline customization already overrides widget defaults.

screen_edit.html (the 3-panel screen builder) gets the same navbar/breadcrumb/button treatment plus its top rename form, but its flow-graph node editor, canvas and element tree keep their existing custom styling — several of their inputs have JS relying on exact DOM sibling structure (e.g. slider oninput reading nextElementSibling) or Bulma's own select/wrapper requirement, and reskinning them without a browser to verify against risked silently breaking the app's most complex feature. Buttons and headings there are still fully converted (safe, purely additive class changes).

Verified: full test suite green, every route smoke-rendered 200 OK via the test client after the change.
2026-08-24 13:19:02 +02:00

418 lines
24 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); }
.addBtn{ display:inline-block; margin:10px 0; padding:9px 14px; border-radius:6px; border:1px dashed var(--border); color:var(--accent); text-decoration:none; font-size:13px; }
.actionsRow{ margin-top:16px; }
.list{ display:flex; flex-direction:column; gap:6px; }
.listRow{ display:block; padding:10px 12px; border:1px solid var(--border); border-radius:8px; background:var(--panel); text-decoration:none; color:var(--text); }
.listRow:hover{ background:var(--panel2); }
.listRowFlex{ display:flex; align-items:center; justify-content:space-between; gap:10px; }
.listRowFlex .rowLink{ flex:1; text-decoration:none; color:var(--text); }
.listRowFlex form{ margin:0; }
.rowTitle{ font-size:14px; }
.rowSub{ font-size:11px; color:var(--text-dim); margin-top:2px; }
.rowActions{ display:flex; gap:6px; align-items:center; }
.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:10px; padding:14px; margin-top:20px; }
.dangerZone h3{ color:var(--danger); }
.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{ flex:0 0 auto; }
.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{ 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;
}
.content-builder > .fieldRow:first-child{ flex:0 0 auto; }
/* 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; }
.builderPanel{ flex:0 0 260px; min-width:0; height:100%; overflow-y:auto; }
.builderPanelRight{ flex:0 0 320px; min-width:0; height:100%; overflow-y:auto; }
.builderCanvasArea{ flex:1 1 auto; min-width:0; height:100%; overflow-y:auto; display:flex; flex-direction:column; }
.widgetGrid{ display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.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); }
.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) */
.forgeTabBar{ display:flex; flex-wrap:wrap; gap:2px; border-bottom:1px solid var(--border); }
.forgeTabBtn{
background:none; border:none; border-bottom:2px solid transparent; color:var(--text-dim);
padding:8px 14px; font-size:14px; font-family:inherit; cursor:pointer; white-space:nowrap;
}
.forgeTabBtn:hover{ color:var(--text); }
.forgeTabBtn.active{ color:var(--text); border-bottom-color:var(--accent); font-weight:600; }
.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; }
/* Le groupe "Ajouter DANS ce conteneur" doit être visuellement impossible à
confondre avec "Ajouter un élément sur l'écran" (panneau de gauche) —
même icônes de widgets, même mise en page, mais une cible totalement
différente (à l'intérieur du conteneur sélectionné, pas sur l'écran). */
.containerContentGroup{ border-color:#3d6b3d; background:#152015; }
.containerContentGroup > summary{ color:#8fd98f; }
.posGrid{ display:grid; grid-template-columns:1fr 1fr; gap:8px 12px; margin-bottom:14px; }
.posGrid label{ font-size:11px; color:var(--text-dim); display:flex; flex-direction:column; gap:4px; }
.posGrid input{ background:var(--panel2); border:1px solid var(--border); border-radius:6px; color:var(--text); padding:6px 8px; }
.posGrid input:disabled{ opacity:0.4; cursor:not-allowed; }
.sizeField{ display:flex; gap:4px; }
.sizeField input{ flex:1; min-width:0; }
.sizeField select{ width:auto; flex:0 0 auto; padding:6px 4px; }
.controlRow{ margin-bottom:12px; }
.controlRow > label{ display:block; font-size:12px; color:var(--text-dim); margin-bottom:4px; }
.sliderRow{ display:flex; align-items:center; gap:10px; }
.sliderRow input[type=range]{ flex:1; }
.sliderValue{ font-size:12px; color:var(--text-dim); min-width:44px; text-align:right; }
.alignGroup{ display:flex; gap:6px; }
.alignOption{ flex:1; text-align:center; padding:8px 0; border:1px solid var(--border); border-radius:6px; cursor:pointer; }
.alignOption input{ display:none; }
.alignOption.active{ border-color:var(--accent); background:rgba(91,140,255,.15); }
input[type=color]{ width:52px; height:34px; padding:2px; }
.propsPanel .sectionDivider, .builderPanelRight .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; }
.builderPanel, .builderPanelRight, .builderCanvasArea{ flex:none; width:100%; height:auto; max-height:none; overflow:visible; }
}
/* ---------- Logique de la scène : éditeur de flow à nœuds ---------- */
/* Panneau du bas, dans le même flux flexbox que .builder3 (pas une
surcouche/overlay) : replié, il ne prend que la hauteur de son en-tête et
.builder3 récupère tout le reste de l'espace ; ouvert, sa hauteur est
fixée en pixels (réglable à la souris via la poignée juste au-dessus) et
.builder3 rétrécit d'autant automatiquement — jamais l'un par-dessus
l'autre. */
.flowPanel{
flex:0 0 auto; border:1px solid var(--border); border-radius:10px; background:var(--panel);
margin-top:8px; display:flex; flex-direction:column; overflow:hidden;
}
.flowPanelHeader{
flex:0 0 auto; padding:10px 16px; font-weight:700; cursor:pointer; user-select:none;
display:flex; align-items:center; gap:6px;
}
.flowPanelHeader:hover{ background:var(--panel2); }
.flowToggleIcon{ color:var(--text-dim); font-size:11px; transition:transform .15s; display:inline-block; }
.flowPanel.open .flowToggleIcon{ transform:rotate(90deg); }
.flowPanelBody{ flex:1 1 auto; min-height:0; padding:0 16px 16px; display:none; flex-direction:column; }
.flowPanel.open .flowPanelBody{ display:flex; }
.flowToolbar{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:10px; flex:0 0 auto; }
.flowResizeHandle{
flex:0 0 auto; height:10px; margin-top:6px; cursor:row-resize; position:relative; display:none;
}
.flowResizeHandle.visible{ display:block; }
.flowResizeHandle::before{
content:""; position:absolute; left:50%; top:3px; width:60px; height:4px;
background:var(--border); border-radius:2px; transform:translateX(-50%);
}
.flowResizeHandle:hover::before{ background:var(--accent); }
.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; }