﻿/* Radbit */
.topbar{display:flex;align-items:center;gap:10px;padding:6px 14px;background:var(--r-surface2);border-bottom:1px solid var(--r-border);z-index:100;flex-shrink:0;height:var(--r-header-h)}
.logo{display:flex;align-items:center;gap:8px;font-weight:800;font-size:14px;color:var(--r-accent);cursor:pointer;white-space:nowrap}
.logo .dot{width:26px;height:26px;border-radius:7px;background:linear-gradient(135deg,var(--r-accent),var(--r-accent-deep));display:flex;align-items:center;justify-content:center;color:#000;font-size:11px;font-weight:900}
.nav-tabs{display:flex;gap:2px;background:var(--r-input-bg);border-radius:9px;padding:2px;flex:1;justify-content:center}
.nav-tab{display:flex;align-items:center;gap:5px;padding:7px 16px;border-radius:7px;border:none;background:transparent;color:var(--r-ink3);font-size:11.5px;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap;transition:all .12s}
.nav-tab:hover{color:var(--r-ink);background:var(--r-item-bg)}
.nav-tab.active{background:var(--r-accent);color:var(--r-accent-ink);box-shadow:0 2px 10px var(--r-accent-soft)}
.topbar-right{display:flex;align-items:center;gap:8px}
.tb-btn{width:30px;height:30px;border-radius:7px;border:1px solid var(--r-item-border);background:var(--r-item-bg);color:var(--r-ink3);cursor:pointer;font-size:13px;display:flex;align-items:center;justify-content:center;transition:.1s}
.tb-btn:hover{background:var(--r-accent-faint);color:var(--r-accent)}
.search-box{display:flex;align-items:center;gap:5px;background:var(--r-input-bg);border:1px solid var(--r-input-border);border-radius:8px;padding:0 9px;width:180px}
.search-box:focus-within{box-shadow:0 0 0 2px var(--r-accent-soft)}
.search-box input{flex:1;border:none;background:transparent;font-size:11px;font-family:inherit;color:var(--r-ink);padding:6px 0}
.search-box input::placeholder{color:var(--r-ink-faint)}
.search-box input:focus{outline:none}
.acct{display:flex;align-items:center;gap:6px;color:var(--r-ink);font-size:11px;font-weight:700}
.acct .av{width:26px;height:26px;border-radius:50%;background:linear-gradient(135deg,var(--r-accent),var(--r-accent-deep));color:#000;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:800}
/* overflow-x:auto — the toolbar has grown a few buttons at a time (Rectangle/Pen/Eraser/Undo/
   Redo on top of the original set) and nothing here wraps (every button is white-space:nowrap),
   so on a narrower window or a zoomed-in browser the later buttons — Undo/Redo among them —
   could run past the edge and sit behind body's overflow:hidden, unreachable and looking
   "broken" rather than just scrolled off. Scrolling keeps every control reachable regardless
   of viewport width instead of silently clipping them. */
.toolbar{display:flex;align-items:center;gap:6px;padding:4px 14px;background:var(--r-surface);border-bottom:1px solid var(--r-border);flex-shrink:0;height:var(--r-toolbar-h);overflow-x:auto;overflow-y:hidden}
.tool-group{display:flex;gap:1px;background:var(--r-input-bg);border:1px solid var(--r-item-border);border-radius:7px;padding:2px;flex-shrink:0}
.tool-btn{padding:5px 12px;border:none;background:transparent;color:var(--r-ink3);font-size:10.5px;font-weight:600;cursor:pointer;border-radius:5px;font-family:inherit;white-space:nowrap;transition:all .1s}
.tool-btn:hover{color:var(--r-ink);background:var(--r-item-bg)}
.tool-btn.active{background:var(--r-accent);color:var(--r-accent-ink)}
.tool-btn:disabled{opacity:.35;cursor:not-allowed;pointer-events:none}
.tool-sep{width:1px;height:20px;background:var(--r-border);margin:0 3px}
.zoom-info{font-size:10.5px;color:var(--r-ink3);min-width:40px;text-align:center;font-weight:600}
.spacer{flex:1}
.act-btn{display:inline-flex;align-items:center;gap:4px;padding:5px 12px;border-radius:6px;font-size:10.5px;font-weight:600;cursor:pointer;border:none;font-family:inherit;white-space:nowrap;transition:all .1s}
.act-btn.primary{background:var(--r-accent);color:var(--r-accent-ink)}
.act-btn.primary:hover{opacity:.9}
.act-btn.outline{background:transparent;border:1px solid var(--r-item-border);color:var(--r-ink3)}
.act-btn.outline:hover{border-color:var(--r-accent);color:var(--r-accent)}
.act-btn.danger{background:transparent;border:1px solid var(--r-red-bg);color:var(--r-red)}
.act-btn.danger:hover{background:var(--r-red-bg);border-color:var(--r-red)}
.main{flex:1;display:flex;overflow:hidden;position:relative}
/* Floating sidebar — matches launcher.html's .iso-sidebar/.sb-toggle glass-panel pattern
   exactly, instead of the old docked full-height panel that permanently ate canvas width. */
.sb-toggle{position:absolute;top:14px;left:14px;z-index:20;width:34px;height:34px;border-radius:9px;background:var(--r-accent);border:1px solid var(--r-accent);color:#fff;font-size:16px;font-weight:700;cursor:pointer;box-shadow:0 4px 12px -4px var(--r-accent);display:flex;align-items:center;justify-content:center;transition:transform .15s,box-shadow .15s}
.sb-toggle:hover{transform:scale(1.05)}
.sidebar{position:absolute;top:14px;left:14px;z-index:15;width:340px;max-height:calc(100% - 28px);background:var(--r-glass);backdrop-filter:blur(16px) saturate(160%);-webkit-backdrop-filter:blur(16px) saturate(160%);border:1px solid var(--r-glass-border);border-radius:16px;box-shadow:0 20px 44px -16px var(--r-shadow-lg);display:flex;flex-direction:row;box-sizing:border-box;overflow:hidden;transform:translateY(-8px) scale(.97);opacity:0;pointer-events:none;transition:transform .18s,opacity .18s}
.sidebar.open{transform:translateY(0) scale(1);opacity:1;pointer-events:auto}
/* Redesign (icon rail replaces the old wrapping text-tab strip): 5 tabs
   (Devices/Cables/Scripts/My VMs/Security) used to fight for room as text labels in a fixed-
   width header -- first squeezed into one row (clipping/overlap), then wrapped to 2 rows as
   a quick fix. Neither scales if more categories get added later, and both eat width the
   item grid could use. An icon-only vertical rail (VS Code-style) takes a fixed 48px
   regardless of how many categories exist, so it never needs to re-squeeze again; the
   current section's name shows once, above the search box, instead of being repeated on
   every tab. */
.sb-rail{width:48px;flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:4px;padding:12px 0;background:var(--r-item-bg)}
.sb-rail-item{width:34px;height:34px;border:none;border-radius:9px;background:transparent;color:var(--r-ink3);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:.12s;flex-shrink:0}
.sb-rail-item:hover{background:var(--r-accent-tint)}
.sb-rail-item.active{background:var(--r-accent);color:#fff}
.sb-panel{flex:1;min-width:0;display:flex;flex-direction:column}
.sb-panel-head{padding:12px 10px 0;display:flex;align-items:center;justify-content:space-between;gap:6px}
.sb-panel-title{font-size:13px;font-weight:700;color:var(--r-ink)}
.sb-close{width:22px;height:22px;border-radius:6px;border:none;background:var(--r-item-bg);color:var(--r-ink3);font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.sb-search{box-sizing:border-box;margin:9px 10px 0;width:calc(100% - 20px);padding:8px 10px;border-radius:9px;border:1.5px solid var(--r-input-border);font-size:12px;font-family:inherit;background:var(--r-input-bg);color:var(--r-ink)}
.sb-search::placeholder{color:var(--r-ink-faint)}
.sb-search:focus{outline:none;border-color:var(--r-accent)}
.sb-body{padding:10px;overflow-y:auto;flex:1}
.sb-cat{margin-bottom:10px}
.sb-cat-title{font-size:9px;font-weight:700;color:var(--r-ink3);text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px;display:flex;align-items:center;gap:5px}
.sb-cat-title::after{content:'';flex:1;height:1px;background:var(--r-border)}
.sb-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
.sb-item{display:flex;flex-direction:column;align-items:center;gap:6px;padding:10px 4px;border-radius:12px;border:1px solid var(--r-item-border);background:var(--r-item-bg);color:var(--r-ink);cursor:grab;transition:all .12s;text-align:center}
.sb-item:hover{border-color:var(--r-accent);background:var(--r-accent-tint)}
.sb-item.dragging{opacity:.35}
.sb-item.cable-pick{cursor:pointer}
.sb-item .ico{width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.sb-item .ico svg{width:100%;height:100%}
.sb-item .cm-swatch{display:flex;align-items:center;justify-content:center;height:26px}
.sb-item span{font-size:9.5px;font-weight:600;line-height:1.25}
.sb-item .badge{font-size:7.5px;font-weight:700;color:var(--r-green);background:var(--r-green-bg);border-radius:4px;padding:1px 4px;margin-top:1px}
.canvas-area{flex:1;position:relative;overflow:hidden;background:var(--r-scene-bg);display:flex;flex-direction:column}
.canvas-grid{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.grid-line{stroke:var(--r-grid-line);stroke-opacity:var(--r-grid-op);stroke-width:1.4}
.arrow-head{fill:var(--r-accent)}
.scene{position:relative;overflow:hidden;flex:1}
.zoom-layer{position:absolute;inset:0;transform-origin:center center}
.conn-svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:2;overflow:visible}
/* No base `stroke` here on purpose — same lesson as the stroke-dasharray fix earlier:
   a CSS declaration on `.link` always wins over the per-connection `stroke` attribute
   the JS sets (presentation attributes are the lowest-priority thing in the cascade),
   so a fixed color here would silently force EVERY cable to look the same regardless of
   its own assigned color, no matter what color JS actually set on each individual path.
   Only the port-name pills (which have no competing CSS fill rule) were ever showing each
   cable's real distinct color; the lines themselves have likely always rendered as one
   uniform accent color. `:hover` below is a deliberate exception — it's a real selector
   with real specificity, so intentionally overriding to one highlight color on hover
   still works exactly as before. */
.conn-svg .link{fill:none;stroke-width:3;stroke-linejoin:round;pointer-events:stroke;cursor:pointer;transition:stroke .2s}
.conn-svg .link:hover{stroke:var(--r-accent-deep)}
.conn-svg .link-bg{fill:none;stroke:var(--r-accent);stroke-width:8;stroke-linejoin:round;stroke-linecap:square;opacity:.06}
.conn-svg .link-chevron{pointer-events:none}
/* No fixed stroke/fill here either, for the same reason as .link above — these are now
   colored per-connection from JS (matching the cable, so the line visibly continues right
   into the device instead of every cable's endpoint dot looking like the same generic
   accent-colored ring regardless of which cable it belongs to). */
.conn-svg .port-outer{fill:var(--r-surface);stroke-width:2}
.conn-svg .port-inner{}

/* ═══ Automation Script canvas icons + run links (Sep 2026, redesigned same day after
   user feedback on the first pass's flat dashed-box icon and plain straight line) ═══
   The icon now reuses the device's own floating-object markup (cube-shadow/cube-float/
   glyph-badge/iso-tag) so it reads as a real object standing on the canvas, the same way
   a device does — .script-glyph/.script-tag only add a dashed accent ring/border on top
   of that shared look, so it's still obviously a SCRIPT, not a device, at a glance. Run
   links are now a gentle curve with a marching-dash flow and an arrowhead at the device
   end (drawn in renderScriptLinks()), not a static straight dashed segment. Both stay
   entirely separate from .conn-svg/.iso-node's own styling/state classes — a run link is
   a logical pointer, not a cable, and a script isn't a device. */
.script-link-svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:2;overflow:visible}
.script-link-svg .script-link{fill:none;stroke:var(--r-accent);stroke-width:2.25;stroke-linecap:round;stroke-dasharray:2 7;pointer-events:stroke;cursor:pointer;opacity:.85;transition:opacity .15s,stroke .15s;animation:scriptLinkFlow 1s linear infinite}
.script-link-svg .script-link:hover{opacity:1;stroke:var(--r-accent-deep)}
.script-link-svg .script-link-ghost{stroke-dasharray:5 5;opacity:.55;pointer-events:none;animation:none}
@keyframes scriptLinkFlow{to{stroke-dashoffset:-18}}
.script-node{position:absolute;width:0;height:0;z-index:4}
.script-glyph{background:var(--r-surface2);border:2px dashed var(--r-accent);border-radius:12px;box-shadow:0 6px 14px -6px var(--r-shadow-md);font-size:22px}
.script-node:hover .script-glyph{transform:scale(1.06)}
.script-node.dragging .script-glyph{opacity:.85;transform:translateY(-6px) scale(1.06)}
.script-tag{border-style:dashed;cursor:grab}
.script-node.dragging .script-tag{cursor:grabbing}
.script-tag-name{max-width:120px;overflow:hidden;text-overflow:ellipsis;display:inline-block;vertical-align:bottom}
.script-node-del,.script-node-run,.script-node-handle{position:absolute;display:flex;align-items:center;justify-content:center;border-radius:50%;cursor:pointer;z-index:6;box-shadow:0 2px 6px var(--r-shadow-sm);transition:transform .12s}
.script-node-del:hover,.script-node-run:hover,.script-node-handle:hover{transform:scale(1.15)}
.script-node-del{top:-3px;right:-3px;width:16px;height:16px;background:var(--r-red-bg);color:var(--r-red);font-size:10px;line-height:1}
.script-node-run{top:-3px;left:-3px;width:18px;height:18px;background:var(--r-accent);color:#fff;font-size:9px}
.script-node-handle{top:14px;left:50%;transform:translateX(-50%);width:18px;height:18px;background:var(--r-surface2);border:1.5px solid var(--r-accent);font-size:9px;cursor:crosshair}
.script-node-handle:hover{transform:translateX(-50%) scale(1.15)}

/* My VMs canvas icons (Sep 2026) -- reuses the device's own floating-object markup
   (cube-shadow/cube-float/glyph-badge/iso-tag), same technique as the script-node icons
   above, but with a solid blue ring (not dashed) and blue accent throughout so it reads
   as "a real machine", distinct from scripts' dashed amber ring for a logical pointer. */
.vm-node{position:absolute;width:0;height:0;z-index:4}
.vm-glyph{background:var(--r-surface2);border:2px solid var(--r-blue);border-radius:12px;box-shadow:0 6px 14px -6px var(--r-shadow-md);font-size:22px;transition:transform .15s}
.vm-node:hover .vm-glyph{transform:scale(1.06)}
.vm-node.dragging .vm-glyph{opacity:.85;transform:translateY(-6px) scale(1.06)}
.vm-tag{border-color:var(--r-blue);display:flex;align-items:center;gap:5px}
.vm-status-dot{width:7px;height:7px;border-radius:50%;background:var(--r-ink-faint);flex-shrink:0;transition:background .15s}
.vm-status-dot.on{background:var(--r-green)}
.vm-node-del,.vm-node-power,.vm-node-handle{position:absolute;display:flex;align-items:center;justify-content:center;border-radius:50%;cursor:pointer;z-index:6;box-shadow:0 2px 6px var(--r-shadow-sm);transition:transform .12s}
.vm-node-del:hover,.vm-node-power:hover,.vm-node-handle:hover{transform:scale(1.15)}
.vm-node-del{top:-3px;right:-3px;width:16px;height:16px;background:var(--r-red-bg);color:var(--r-red);font-size:10px;line-height:1}
.vm-node-power{top:-3px;left:-3px;width:18px;height:18px;background:var(--r-blue);color:#fff;font-size:9px}
.vm-node-handle{top:14px;left:50%;transform:translateX(-50%);width:18px;height:18px;background:var(--r-surface2);border:1.5px solid var(--r-blue);font-size:9px;cursor:crosshair}
.vm-node-handle:hover{transform:translateX(-50%) scale(1.15)}

.vm-link-svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:2;overflow:visible}
.vm-link-svg .vm-link{fill:none;stroke:var(--r-blue);stroke-width:2.5;pointer-events:stroke;cursor:pointer;opacity:.85;transition:opacity .15s,stroke-width .15s}
.vm-link-svg .vm-link:hover{opacity:1;stroke-width:3.5}
.vm-link-svg .vm-link-ghost{stroke-dasharray:5 5;opacity:.55;pointer-events:none}
/* Virtual Bridge lines (Sep 2026) -- teal to match the bridge device icon's own color
   (device-catalog.js's virtual-bridge entry, color:#14b8a6), distinct from the blue
   pairwise VM-to-VM lines above so a hub-and-spoke bridge group reads differently at a
   glance from a direct link. */
.vm-link-svg .vm-link-bridge{stroke:#14b8a6}

/* ═══ Security Tools canvas icons (Sep 2026) ═══ Same floating-object technique again
   (cube-shadow/cube-float/glyph-badge/iso-tag), red accent throughout so it reads as
   distinct from scripts (dashed amber) and VMs (solid blue) at a glance -- these are the
   "offense/analysis" objects on the canvas. Dashed ring like scripts (a logical "targets
   this" pointer, not a real cable), but red instead of amber. */
.sectool-node{position:absolute;width:0;height:0;z-index:4}
.sectool-glyph{background:var(--r-surface2);border:2px dashed var(--r-red);border-radius:12px;box-shadow:0 6px 14px -6px var(--r-shadow-md);font-size:22px;transition:transform .15s}
.sectool-node:hover .sectool-glyph{transform:scale(1.06)}
.sectool-node.dragging .sectool-glyph{opacity:.85;transform:translateY(-6px) scale(1.06)}
.sectool-tag{border-color:var(--r-red);border-style:dashed;cursor:grab}
.sectool-node.dragging .sectool-tag{cursor:grabbing}
.sectool-tag-name{max-width:140px;overflow:hidden;text-overflow:ellipsis;display:inline-block;vertical-align:bottom}
.sectool-node-del,.sectool-node-run,.sectool-node-handle{position:absolute;display:flex;align-items:center;justify-content:center;border-radius:50%;cursor:pointer;z-index:6;box-shadow:0 2px 6px var(--r-shadow-sm);transition:transform .12s}
.sectool-node-del:hover,.sectool-node-run:hover,.sectool-node-handle:hover{transform:scale(1.15)}
.sectool-node-del{top:-3px;right:-3px;width:16px;height:16px;background:var(--r-red-bg);color:var(--r-red);font-size:10px;line-height:1}
.sectool-node-run{top:-3px;left:-3px;width:18px;height:18px;background:var(--r-red);color:#fff;font-size:9px}
.sectool-node-handle{top:14px;left:50%;transform:translateX(-50%);width:18px;height:18px;background:var(--r-surface2);border:1.5px solid var(--r-red);font-size:9px;cursor:crosshair}
.sectool-node-handle:hover{transform:translateX(-50%) scale(1.15)}

.sectool-link-svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:2;overflow:visible}
.sectool-link-svg .sectool-link{fill:none;stroke:var(--r-red);stroke-width:2.25;stroke-linecap:round;stroke-dasharray:2 7;pointer-events:stroke;cursor:pointer;opacity:.85;transition:opacity .15s}
.sectool-link-svg .sectool-link:hover{opacity:1}
.sectool-link-svg .sectool-link-ghost{stroke-dasharray:5 5;opacity:.55;pointer-events:none}

.vm-badge-on{background:var(--r-green-bg);color:var(--r-green)}
.vm-badge-off{background:var(--r-surface3);color:var(--r-ink3)}
/* Gear that opens the VMware-style Machine Settings dialog, put on each sidebar My VMs
   item (rendered by renderVmGrid in shared/app.js). Positioned absolute so it doesn't break
   the sb-item column layout; hidden until hover to keep dragging clean. */
.vm-item{position:relative}
.vm-item-gear{position:absolute;top:5px;right:6px;font-size:11px;opacity:0;cursor:pointer;z-index:5;transition:opacity .12s;filter:drop-shadow(0 1px 1px rgba(0,0,0,.4))}
.vm-item:hover .vm-item-gear{opacity:.85}
.vm-item-gear:hover{opacity:1}
.vm-slider-row input[type=range]:disabled{opacity:.4;cursor:not-allowed}
.vm-slider-row{margin:14px 0}
.vm-slider-row label{display:flex;align-items:center;gap:8px;font-size:11.5px;font-weight:700;color:var(--r-ink2);margin-bottom:6px}
.vm-slider-row input[type=range]{width:100%;accent-color:var(--r-accent)}
.vm-slider-badge{background:var(--r-accent-faint);color:var(--r-accent);font-weight:800;font-size:10.5px;padding:2px 8px;border-radius:6px;font-variant-numeric:tabular-nums}

/* ═══ New VM wizard (Sep 2026 redesign) ═══ Replaces the old single-scroll cramped modal
   with a VMware-Workstation-style multi-step flow: a step rail up top, one page of fields
   visible at a time, Back/Next/Create in the footer. Every underlying field keeps its old
   id (see range.html's own comment on the modal) -- this file only styles the new
   chrome/pickers around them, built entirely on the existing --r-* tokens (no new palette),
   per the rest of Radbit's design language. */
.vm-wizard-box{width:640px;max-width:94vw;max-height:86vh}
.vm-wizard-steps{display:flex;align-items:center;padding:14px 20px;border-bottom:1px solid var(--r-border);background:var(--r-surface2);flex-shrink:0;overflow-x:auto}
.vm-wizard-step{display:flex;flex-direction:column;align-items:center;gap:5px;cursor:pointer;flex-shrink:0;opacity:.5;transition:opacity .15s}
.vm-wizard-step.done,.vm-wizard-step.active{opacity:1}
.vm-wizard-step-num{width:26px;height:26px;border-radius:50%;background:var(--r-surface3);color:var(--r-ink3);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;transition:background .2s,color .2s}
.vm-wizard-step.active .vm-wizard-step-num{background:var(--r-accent);color:var(--r-accent-ink)}
.vm-wizard-step.done .vm-wizard-step-num{background:var(--r-green);color:#fff}
.vm-wizard-step-label{font-size:9.5px;font-weight:700;color:var(--r-ink3);white-space:nowrap}
.vm-wizard-step.active .vm-wizard-step-label{color:var(--r-ink)}
.vm-wizard-step-line{flex:1;height:2px;background:var(--r-border);margin:0 6px 16px;min-width:16px}
.vm-wizard-body{min-height:280px}
.vm-wizard-page{animation:secFadeIn .15s ease-out}
.vm-wizard-lbl{display:block;font-size:11.5px;font-weight:700;color:var(--r-ink2);margin:0 0 8px}
.vm-wizard-note{font-size:10.5px;color:var(--r-ink3);line-height:1.6;margin-top:10px}
.vm-hidden-select{display:none}
/* Card pickers replace the old plain <select>s for role/network-type/boot-source -- each
   card just sets its paired hidden <select>'s value on click (pickVmRole() etc., shared/
   app.js), so the rest of the app (submitVmCreate()'s payload build) never needs to know a
   <select> isn't what's actually visible anymore. */
.vm-pick-row{display:flex;gap:10px;margin-bottom:6px}
.vm-pick-card{flex:1;background:var(--r-surface2);border:1.5px solid var(--r-border);border-radius:12px;padding:14px 10px;text-align:center;cursor:pointer;transition:border-color .15s,transform .12s,background .15s}
.vm-pick-card:hover{border-color:var(--r-accent);transform:translateY(-2px)}
.vm-pick-card.selected{border-color:var(--r-accent);background:var(--r-accent-faint)}
.vm-pick-ico{font-size:26px;margin-bottom:6px}
.vm-pick-name{font-size:11.5px;font-weight:800;color:var(--r-ink)}
.vm-pick-row-wide{flex-direction:column}
.vm-pick-card-wide{display:flex;align-items:center;gap:12px;text-align:right;padding:14px}
.vm-pick-card-wide .vm-pick-ico{margin-bottom:0;font-size:22px}
.vm-pick-card-wide .vm-pick-name{margin-bottom:2px}
.vm-pick-desc{font-size:10.5px;color:var(--r-ink3);line-height:1.4}
/* Upload zone (Sep 2026) -- a real drop-zone look (dashed border, big icon, click-to-
   browse) instead of a bare <input type=file>, whose native "Choose File / No file chosen"
   presentation was the least VMware-like thing on the whole page. */
.vm-upload-zone{margin-top:12px;border:2px dashed var(--r-border2);border-radius:12px;padding:22px 14px;text-align:center;cursor:pointer;transition:border-color .15s,background .15s}
.vm-upload-zone:hover{border-color:var(--r-accent);background:var(--r-accent-faint)}
.vm-upload-zone.has-file{border-style:solid;border-color:var(--r-green)}
.vm-upload-ico{font-size:28px;margin-bottom:8px}
.vm-upload-text{font-size:11px;color:var(--r-ink3);font-weight:600}
.vm-upload-zone.has-file .vm-upload-text{color:var(--r-green);font-weight:700}
.vm-wizard-summary{background:var(--r-surface2);border:1px solid var(--r-border);border-radius:12px;padding:14px}
.vm-wizard-summary-row{display:flex;justify-content:space-between;padding:7px 0;font-size:11.5px;border-bottom:1px solid var(--r-border)}
.vm-wizard-summary-row:last-child{border-bottom:none}
.vm-wizard-summary-row span:first-child{color:var(--r-ink3);font-weight:600}
.vm-wizard-summary-row span:last-child{color:var(--r-ink);font-weight:800}
.vm-wizard-footer{display:flex;align-items:center;gap:10px;padding:12px 14px;border-top:1px solid var(--r-border);flex-shrink:0}
.vm-upload-progress{display:flex;align-items:center;gap:10px;padding:10px 14px;border-top:1px solid var(--r-border);background:var(--r-surface2)}
.vm-upload-progress-track{flex:1;height:8px;border-radius:6px;background:var(--r-border);overflow:hidden}
.vm-upload-progress-fill{height:100%;width:0;background:linear-gradient(90deg,var(--r-accent),var(--r-green));border-radius:6px;transition:width .15s}
.vm-upload-progress-pct{font-size:11px;font-weight:800;color:var(--r-ink2);min-width:40px;text-align:right}
/* Media library (Sep 2026) -- the persistent per-tenant ISO/VM-image store shown by the
   create wizard's Boot → Media library card. Header row + a picked-file <select> built
   from GET /api/tenant-vms/media, plus the same dashed drop-zone used by the one-shot
   Upload ISO/Ready image cards so adding to the library reuses the familiar look. */
.vm-lib-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:12px 0 4px}
.vm-lib-title{font-size:12px;font-weight:800;color:var(--r-ink2)}
.vm-lib-empty{margin:8px 0;padding:10px;text-align:center;font-size:11px;color:var(--r-ink3);border:1px dashed var(--r-border2);border-radius:10px}
.vm-media-select{width:100%;margin-top:8px;padding:9px 10px;border:1.5px solid var(--r-border2);border-radius:10px;background:var(--r-surface2);color:var(--r-ink);font-size:12.5px;font-weight:600;cursor:pointer;transition:border-color .15s}
.vm-media-select:hover,.vm-media-select.has-choice{border-color:var(--r-accent)}
.vm-lib-divider{height:1px;background:var(--r-border);margin:12px 0 2px}

/* z-index:4 — must paint above .conn-svg's z-index:2. Without an explicit z-index here this
   position:absolute node sits in the implicit "auto" stacking layer, which CSS always paints
   BELOW any sibling with a positive z-index regardless of DOM order — so cable lines and the
   port-name label pills (both live inside .conn-svg) would draw on top of and visually hide
   the device cards wherever a route or label happened to cross over one. */
.iso-node{position:absolute;width:0;height:0;z-index:4}
.iso-tag{position:absolute;bottom:78px;left:50%;transform:translateX(-50%);display:inline-flex;align-items:center;gap:5px;background:var(--r-surface2);border:1px solid var(--r-border2);border-radius:9px;padding:5px 11px;font-size:11.5px;font-weight:700;color:var(--r-ink);box-shadow:0 4px 12px -4px var(--r-shadow-sm);white-space:nowrap;z-index:3;cursor:pointer;transition:background .2s,border-color .2s}
.iso-tag::after{content:'';position:absolute;left:50%;bottom:-18px;width:0;height:14px;border-left:1.5px dotted var(--r-border2);transform:translateX(-50%)}
.cube-shadow{position:absolute;left:-30px;top:-11px;width:60px;height:28px;transform:scaleY(.45);border-radius:50%;background:radial-gradient(ellipse at center,var(--r-node-shadow),transparent 72%);z-index:1;pointer-events:none}
.cube-float{position:absolute;left:-26px;top:-46px;width:52px;animation:float 3.4s ease-in-out infinite alternate;z-index:2}
@keyframes float{from{transform:translateY(0)}to{transform:translateY(-10px)}}
@keyframes connectPulse{0%,100%{box-shadow:0 0 0 3px rgba(139,92,246,.25)}50%{box-shadow:0 0 0 6px rgba(139,92,246,.08)}}
/* The device now floats free with no card/frame of its own — just the isometric glyph
   itself, sitting on the .cube-shadow ellipse beneath it (a real 3D-object silhouette,
   the way the reference diagram renders devices, not an "app icon" tile). No ambient
   box-shadow here any more; that used to draw a soft rounded-square halo behind every
   icon regardless of the glyph's own shape, which is exactly what read as "still a
   card". The interactive states below (hover/selected/dragging/connecting) still give
   real feedback, just as a ring or lift instead of a shadow that traces a fixed square. */
.glyph-badge{width:52px;height:52px;color:var(--r-ink);display:flex;align-items:center;justify-content:center;transition:transform .18s,filter .18s;cursor:pointer;position:relative}
.glyph-badge svg{width:100%;height:100%;overflow:visible}
.iso-node:hover .glyph-badge{transform:scale(1.06)}
.iso-node.selected .glyph-badge{filter:drop-shadow(0 0 4px var(--r-accent-soft)) drop-shadow(0 0 4px var(--r-accent-soft))}
/* Multi-select (Sep 2026, "ضيف ليا ان انا ممكن احدد اجهزه و اقدر اسحبهم") -- a distinct
   green ring (vs. the accent-color glow above for the single "active" selection that owns
   the props panel) so a Shift-click/marquee group reads clearly as "these move together"
   without being confused with the one node whose properties are currently showing. */
.iso-node.multi-selected .glyph-badge{filter:drop-shadow(0 0 4px var(--r-green,#22c55e)) drop-shadow(0 0 4px var(--r-green,#22c55e))}
#marqueeSelectBox{position:absolute;border:1.5px dashed var(--r-accent);background:var(--r-accent-faint);pointer-events:none;z-index:50;display:none}
/* Sep 2026, real cross-project links ("ربط مشروعين") -- a mirrored device from ANOTHER
   project: visually distinct (dimmed + purple dashed outline) and read-only (no drag/
   click-to-select/context-menu listeners are even attached to it, see _buildShadowNode()
   in network-lab.js) so it never looks or behaves like it's actually editable here. */
.shadow-project-node{opacity:.55;filter:grayscale(.35);cursor:pointer}
.shadow-project-node .cube-float,.shadow-project-node .phy-box{outline:2px dashed #a855f7;outline-offset:4px;border-radius:8px}
.iso-node.dragging .glyph-badge{opacity:.85;transform:translateY(-6px) scale(1.06)}
.iso-node.connect-src .glyph-badge{animation:connectPulse 1.2s ease-in-out infinite}
.node-status{position:absolute;top:-3px;right:-3px;display:flex;gap:2px;z-index:5}
.st-dot{width:7px;height:7px;border-radius:50%;border:1.5px solid var(--r-surface2);transition:background .2s}
.st-dot.on{background:var(--r-green)}
.st-dot.off{background:var(--r-ink-faint)}
.phy-node{position:absolute;z-index:4;cursor:move}
/* Faceplate look — metallic gradient + corner screws instead of a flat card, closer to an
   actual rack-mount unit front panel. */
.phy-box{background:linear-gradient(180deg,var(--r-surface3),var(--r-surface2) 45%,var(--r-surface3));border:2px solid var(--r-border2);border-radius:6px;min-width:140px;position:relative;box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 2px 8px rgba(0,0,0,.18);transition:border-color .15s,box-shadow .15s}
.phy-box::before,.phy-box::after{content:'';position:absolute;top:6px;width:4px;height:4px;border-radius:50%;background:var(--r-border2);box-shadow:inset 0 1px 1px rgba(0,0,0,.5)}
.phy-box::before{left:6px}
.phy-box::after{right:6px}
.rack-mounted .phy-box::before,.rack-mounted .phy-box::after{top:50%;margin-top:-2px}
.phy-node:hover .phy-box{border-color:var(--r-accent);box-shadow:0 4px 16px var(--r-shadow-md)}
.phy-node.selected .phy-box{border-color:var(--r-accent);box-shadow:0 0 0 3px var(--r-accent-soft)}
.phy-node.connect-src .phy-box{border-color:var(--r-purple);box-shadow:0 0 0 3px rgba(139,92,246,.25);animation:connectPulse 1.2s ease-in-out infinite}
.phy-header{display:flex;align-items:center;gap:8px;padding:8px 10px;border-bottom:1px solid var(--r-border);background:var(--r-item-bg);border-radius:6px 6px 0 0}
.phy-header .phy-icon{width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.phy-header .phy-icon svg{width:100%;height:100%}
.phy-header .phy-name{font-size:11px;font-weight:700;color:var(--r-ink);flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.phy-header .phy-status{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.phy-header .phy-status.on{background:var(--r-green)}
.phy-header .phy-status.off{background:var(--r-ink-faint)}
.phy-ports{padding:6px 8px;display:flex;flex-wrap:wrap;gap:4px}
.phy-port{display:flex;align-items:center;gap:4px;padding:3px 6px;border-radius:4px;border:1px solid var(--r-item-border);background:var(--r-item-bg);font-size:9px;font-weight:600;color:var(--r-ink3);cursor:crosshair;transition:all .12s;white-space:nowrap}
.phy-port:hover{border-color:var(--r-accent);background:var(--r-accent-faint);color:var(--r-accent)}
.phy-port.used{border-color:var(--r-red);background:var(--r-red-bg);color:var(--r-red)}
/* Distinct connector shape by port type — a small at-a-glance realism cue: console ports
   are visually "different" (rollover DB9-style), serial ports get squared corners. */
.phy-port[data-ptype="console"]{border-color:var(--r-purple);color:var(--r-purple);border-radius:3px 3px 8px 8px}
.phy-port[data-ptype="serial"]{border-radius:2px}
.phy-port[data-ptype="management"]{border-style:dashed}
.phy-port .pdot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
.phy-port .pdot.free{background:var(--r-green)}
.phy-port .pdot.used{background:var(--r-red)}
.phy-ports-compact{display:none}
.phy-portcount{font-size:9px;color:var(--r-ink3);flex-shrink:0}

/* Rack-mounted device: collapses into a single 1U horizontal bar (header + compact port
   dots side by side) instead of the free-floating stacked card. */
.rack-mounted{z-index:3}
.rack-mounted .phy-box{display:flex;align-items:center;height:100%;border-radius:4px;min-width:0}
.rack-mounted .phy-header{border-bottom:none;background:transparent;border-radius:0;padding:0 6px;flex-shrink:0}
.rack-mounted .phy-ports-full{display:none}
.rack-mounted .phy-ports-compact{display:flex;gap:3px;padding:0 8px;flex-wrap:nowrap;overflow-x:auto;flex:1}
.phy-port-dot{display:flex;align-items:center;justify-content:center;cursor:crosshair;flex-shrink:0}
.phy-port-dot .pdot{width:7px;height:7px;border-radius:50%;border:1px solid var(--r-surface2)}
.phy-port-dot .pdot.free{background:var(--r-green)}
.phy-port-dot .pdot.used{background:var(--r-red)}

/* Rack cabinet frame */
.rack-frame{position:absolute;z-index:1;background:linear-gradient(180deg,#1c1c20,#101012);border:2px solid var(--r-border2);border-radius:10px;box-shadow:0 10px 30px -10px var(--r-shadow-lg);overflow:hidden}
.rack-head{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--r-item-bg);border-bottom:2px solid var(--r-border2);cursor:move;font-size:10.5px;font-weight:700;color:var(--r-ink)}
.rack-head .rack-units{color:var(--r-ink3);font-weight:600;font-size:9.5px}
.rack-head .rack-absorb{margin-inline-start:auto;width:20px;height:20px;border:none;background:transparent;color:var(--r-ink3);cursor:pointer;font-size:11px;border-radius:4px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.rack-head .rack-absorb:hover{background:var(--r-accent-faint);color:var(--r-accent)}
.rack-head .rack-del{width:18px;height:18px;border:none;background:transparent;color:var(--r-ink3);cursor:pointer;font-size:13px;border-radius:4px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.rack-head .rack-del:hover{background:var(--r-red-bg);color:var(--r-red)}
.rack-body{position:relative}
.rack-slot{border-bottom:1px dashed rgba(255,255,255,.06);position:relative;display:flex;align-items:center}
.rack-slot:last-child{border-bottom:none}
.rack-slot-num{position:absolute;left:4px;font-size:7px;color:var(--r-ink-faint);font-weight:700}
.props-panel{position:absolute;top:0;bottom:0;right:0;width:var(--r-props-w);background:var(--r-surface2);border-left:1px solid var(--r-border);box-shadow:-8px 0 24px -8px var(--r-shadow-lg);display:flex;flex-direction:column;overflow:hidden;z-index:20;transition:transform .2s;transform:translateX(0)}
.props-panel.collapsed{transform:translateX(100%)}
.props-head{padding:10px 12px;border-bottom:1px solid var(--r-border);display:flex;align-items:center;gap:8px}
.props-head .ico{width:34px;height:34px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.props-head .ico svg{width:100%;height:100%}
.props-head .info{flex:1;min-width:0}
.props-head .info .name{font-size:12px;font-weight:700;color:var(--r-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.props-head .info .type{font-size:9.5px;color:var(--r-ink3)}
.props-body{padding:10px 12px;overflow-y:auto;flex:1}
.prop-sec{margin-bottom:12px}
.prop-sec-title{font-size:9px;font-weight:700;color:var(--r-ink3);text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}
.prop-row{display:flex;align-items:center;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--r-item-border)}
.prop-row .label{font-size:10px;color:var(--r-ink3)}
.prop-row .value{font-size:10px;font-weight:600;color:var(--r-ink)}
.port-list{list-style:none}
.port-item{display:flex;align-items:center;gap:6px;padding:5px 7px;border-radius:6px;margin-bottom:2px;background:var(--r-item-bg);border:1px solid var(--r-item-border)}
.port-item .dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
.port-item .dot.free{background:var(--r-green)}
.port-item .dot.used{background:var(--r-red)}
.port-item .pname{font-size:9.5px;color:var(--r-ink);flex:1}
.port-item .ptype{font-size:8.5px;color:var(--r-ink3)}
.mod-remove-btn{width:18px;height:18px;border:none;background:transparent;color:var(--r-ink3);cursor:pointer;border-radius:4px;font-size:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.mod-remove-btn:hover{background:var(--r-red-bg);color:var(--r-red)}
/* Console as a centered page (Sep 2026) -- see the range.html comment above #consolePanel.
   The panel itself is now just the dimmed full-screen backdrop; .console-window is the
   actual page, sized generously (not edge-to-edge -- "في منتصف الصفحه") since it now has
   to comfortably fit a router's CLI just as well as a PC/Server's icon desktop. */
.console-panel{position:fixed;inset:0;background:rgba(10,14,24,.55);backdrop-filter:blur(2px);z-index:300;display:none;align-items:center;justify-content:center}
.console-panel.open{display:flex}
.console-window{width:min(1100px,94vw);height:min(720px,90vh);background:var(--r-surface);border:1px solid var(--r-border);border-radius:var(--r-radius);box-shadow:0 24px 64px -12px var(--r-shadow-lg);overflow:hidden;display:flex;flex-direction:column}
.console-head{display:flex;align-items:center;gap:8px;padding:6px 14px;border-bottom:1px solid var(--r-border)}
.console-title{font-size:11px;font-weight:700;color:var(--r-accent)}
.console-body{flex:1;padding:8px 14px;font-family:'Cascadia Code','Fira Code',monospace;font-size:11.5px;color:var(--r-ink2);overflow-y:auto;line-height:1.6}
.console-body .prompt{color:var(--r-green)}
.console-body .output{color:var(--r-ink3)}
.console-body .success{color:var(--r-green)}
.console-body .error{color:var(--r-red)}
.console-body .cmd{color:var(--r-ink)}
.console-input-row{display:flex;align-items:center;gap:6px;padding:6px 14px;border-top:1px solid var(--r-border);flex-shrink:0}
.console-prompt{font-family:'Cascadia Code','Fira Code',monospace;font-size:11.5px;font-weight:700;color:var(--r-green);white-space:nowrap}
.console-input{flex:1;border:none;background:transparent;outline:none;font-family:'Cascadia Code','Fira Code',monospace;font-size:11.5px;color:var(--r-ink);direction:ltr;text-align:left}

/* ═══ PC / endpoint device workspace — Packet-Tracer-style tool switcher ═══ */
.pcw-overlay{position:fixed;inset:0;background:rgba(10,14,24,.55);backdrop-filter:blur(2px);display:none;align-items:center;justify-content:center;z-index:500}
.pcw-overlay.open{display:flex}
.pcw-window{width:680px;max-width:92vw;height:540px;max-height:88vh;background:var(--r-surface);border:1px solid var(--r-border);border-radius:var(--r-radius);box-shadow:0 24px 64px -12px var(--r-shadow-lg);display:flex;flex-direction:column;overflow:hidden}
.pcw-head{display:flex;align-items:center;gap:9px;padding:10px 12px;border-bottom:1px solid var(--r-border);flex-shrink:0}
.pcw-ico{width:30px;height:30px;flex-shrink:0}
.pcw-ico svg{width:100%;height:100%}
.pcw-titles{min-width:0}
.pcw-title{font-size:12.5px;font-weight:700;color:var(--r-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pcw-sub{font-size:9.5px;color:var(--r-ink3)}
.pcw-body{flex:1;display:flex;overflow:hidden}
.pcw-rail{width:78px;flex-shrink:0;background:var(--r-surface2);border-right:1px solid var(--r-border);padding:8px 6px;display:flex;flex-direction:column;gap:4px;overflow-y:auto}
.pcw-rail-btn{display:flex;flex-direction:column;align-items:center;gap:3px;padding:8px 4px;border-radius:8px;border:1px solid transparent;background:transparent;color:var(--r-ink3);cursor:pointer;font-size:9px;font-weight:600;font-family:inherit;text-align:center;line-height:1.2}
.pcw-rail-btn .em{font-size:16px}
.pcw-rail-btn:hover{background:var(--r-item-bg)}
.pcw-rail-btn.active{background:var(--r-accent-tint);border-color:var(--r-accent-soft);color:var(--r-accent)}
.pcw-content{flex:1;overflow-y:auto;display:flex;flex-direction:column;min-width:0}
/* PC/Server GUI desktop (Sep 2026, "عاوز اشوف كل حاجه علي انها ايقونه لخدمه او اداه او
   برنامج معين" -- replaces the old tab-per-capability layout for these two device types
   with a real desktop icon grid: Terminal/IP Config/Install OS (or Database/DHCP for a
   Server), plus one icon per security tool actually attached to this device. Double-click
   an icon to open it, same interaction the product vision described for tools; a small
   "🏠 Desktop" bar at the top of whatever program is open leads back here. */
.pcw-desktop{flex:1;padding:18px;display:flex;flex-wrap:wrap;align-content:flex-start;gap:6px;overflow-y:auto}
.pcw-desktop-icon{width:84px;display:flex;flex-direction:column;align-items:center;gap:6px;padding:10px 4px;border-radius:10px;border:1px solid transparent;cursor:pointer;user-select:none;text-align:center}
.pcw-desktop-icon:hover{background:var(--r-item-bg)}
.pcw-desktop-icon.disabled{opacity:.45;cursor:not-allowed}
.pcw-desktop-ico{width:44px;height:44px;border-radius:11px;background:var(--r-surface2);border:1px solid var(--r-border2);display:flex;align-items:center;justify-content:center;font-size:22px}
.pcw-desktop-icon span{font-size:10px;font-weight:600;color:var(--r-ink2);line-height:1.25;word-break:break-word}
.pcw-back-bar{display:flex;align-items:center;gap:6px;padding:7px 12px;border-bottom:1px solid var(--r-border);flex-shrink:0}
.pcw-back-bar button{display:flex;align-items:center;gap:5px;padding:4px 10px;border-radius:7px;border:1px solid var(--r-item-border);background:var(--r-item-bg);color:var(--r-ink3);font-size:10px;font-weight:700;cursor:pointer;font-family:inherit}
.pcw-back-bar button:hover{color:var(--r-ink);border-color:var(--r-accent-soft)}
/* Convert-to-VM OS picker (Sep 2026) -- see #convertVmModalOverlay in range.html */
.convert-vm-list{max-height:260px;overflow-y:auto;display:flex;flex-direction:column;gap:6px}
.convert-vm-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1.5px solid var(--r-border2);border-radius:9px;cursor:pointer;transition:border-color .12s,background .12s}
.convert-vm-item:hover{background:var(--r-item-bg)}
.convert-vm-item.selected{border-color:var(--r-accent);background:var(--r-accent-tint)}
.convert-vm-ico{width:30px;height:30px;flex-shrink:0;border-radius:8px;background:var(--r-surface2);display:flex;align-items:center;justify-content:center;font-size:15px}
.convert-vm-name{font-size:11.5px;font-weight:700;color:var(--r-ink)}
.convert-vm-sub{font-size:9.5px;color:var(--r-ink3);word-break:break-all}
/* Image/video preview viewer (Sep 2026) -- see fmMediaOpenViewer() in file-manager.js */
.fm-viewer-overlay{position:fixed;inset:0;background:rgba(0,0,0,.82);z-index:9999;display:none;align-items:center;justify-content:center}
.fm-viewer-overlay.open{display:flex}
.fm-viewer-box{position:relative;max-width:92vw;max-height:92vh}
.fm-viewer-close{position:absolute;top:-38px;right:0;width:30px;height:30px;border-radius:8px;border:1px solid rgba(255,255,255,.25);background:rgba(255,255,255,.1);color:#fff;font-size:15px;cursor:pointer}
.fm-viewer-close:hover{background:rgba(255,255,255,.2)}
.fm-viewer-body{display:flex;align-items:center;justify-content:center}
/* VM Settings "Ports" section (Sep 2026) -- see ensureVmSettingsModal() in
   simulators/security-tools.js */
.vm-ports-list{display:flex;flex-direction:column;gap:6px;margin-top:6px}
.vm-port-row{display:flex;align-items:center;gap:8px;padding:7px 10px;border:1px solid var(--r-border2);border-radius:8px;background:var(--r-surface2)}
.vm-port-ico{font-size:14px;flex-shrink:0}
.vm-port-label{flex:1;font-size:11px;font-weight:700;color:var(--r-ink2)}
/* Custom device catalog (Sep 2026) -- see openCustomDeviceModal()/buildDeviceGrid() in
   shared/app.js */
.sb-item{position:relative}
.cd-del{position:absolute;top:4px;right:4px;width:16px;height:16px;border-radius:5px;background:var(--r-red-bg);color:var(--r-red);font-size:10px;line-height:16px;text-align:center;cursor:pointer;opacity:0;transition:opacity .12s}
.sb-item:hover .cd-del{opacity:1}
.cd-icon-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:6px;margin-top:4px}
.cd-icon-opt{width:100%;aspect-ratio:1;border-radius:9px;border:1.5px solid var(--r-border2);background:var(--r-surface2);cursor:pointer;display:flex;align-items:center;justify-content:center;padding:4px;box-sizing:border-box}
.cd-icon-opt:hover{border-color:var(--r-accent-soft)}
.cd-icon-opt.selected{border-color:var(--r-accent);background:var(--r-accent-tint)}
.cd-icon-opt svg{width:100%;height:100%}
.pcw-empty{margin:auto;text-align:center;color:var(--r-ink3);font-size:11px;padding:20px;max-width:260px}
.pcw-empty .em{font-size:26px;display:block;margin-bottom:8px}
.pcw-term-body{flex:1;padding:10px 14px;font-family:'Cascadia Code','Fira Code',monospace;font-size:11.5px;color:var(--r-ink2);overflow-y:auto;line-height:1.6;direction:ltr;text-align:left}
.pcw-term-body .prompt{color:var(--r-green)}
.pcw-term-body .output{color:var(--r-ink3)}
.pcw-term-body .success{color:var(--r-green)}
.pcw-term-body .error{color:var(--r-red)}
.pcw-term-body .cmd{color:var(--r-ink)}
.pcw-term-input-row{display:flex;align-items:center;gap:6px;padding:7px 14px;border-top:1px solid var(--r-border);flex-shrink:0;direction:ltr}
.pcw-term-prompt{font-family:'Cascadia Code','Fira Code',monospace;font-size:11.5px;font-weight:700;color:var(--r-green);white-space:nowrap}
.pcw-term-input{flex:1;border:none;background:transparent;outline:none;font-family:'Cascadia Code','Fira Code',monospace;font-size:11.5px;color:var(--r-ink);direction:ltr;text-align:left}
.pcw-pane{padding:14px 16px;overflow-y:auto}
.pcw-pane-title{font-size:9px;font-weight:700;color:var(--r-ink3);text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px}
.pcw-field{margin-bottom:10px}
.pcw-field label{display:block;font-size:9.5px;color:var(--r-ink3);margin-bottom:4px;font-weight:600}
.pcw-field input,.pcw-field select{width:100%;background:var(--r-surface2);border:1.5px solid var(--r-border2);border-radius:8px;padding:7px 9px;color:var(--r-ink);font-size:11px;font-family:'Cascadia Code','Fira Code',monospace;direction:ltr;text-align:left}
.pcw-row2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.pcw-upload-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:9px;border:1.5px dashed var(--r-border2);border-radius:9px;background:var(--r-item-bg);color:var(--r-ink2);font-size:10.5px;font-weight:600;cursor:pointer;transition:border-color .15s,color .15s;box-sizing:border-box}
.pcw-upload-btn:hover{border-color:var(--r-accent);color:var(--r-accent)}
.pcw-file-note{margin-top:8px;padding:8px 10px;background:var(--r-accent-tint);border:1px solid var(--r-accent-soft);border-radius:8px;font-size:10px;color:var(--r-ink2);word-break:break-word}
.pcw-os-current{display:flex;align-items:center;gap:10px;padding:10px;background:var(--r-item-bg);border:1px solid var(--r-item-border);border-radius:10px;margin-bottom:14px}
.pcw-os-current .badge{width:34px;height:34px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:800;color:#fff;flex-shrink:0}
.pcw-os-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.pcw-os-card{border:1px solid var(--r-item-border);background:var(--r-item-bg);border-radius:10px;padding:10px;cursor:pointer;transition:all .12s;display:flex;flex-direction:column;gap:6px}
.pcw-os-card:hover{border-color:var(--r-accent-soft);transform:translateY(-1px)}
.pcw-os-card.selected{border-color:var(--r-accent);background:var(--r-accent-tint)}
.pcw-os-card .badge{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:7.5px;font-weight:800;color:#fff}
.pcw-os-card .nm{font-size:10.5px;font-weight:700;color:var(--r-ink)}
.pcw-os-card .vr{font-size:8.5px;color:var(--r-ink3)}
.pcw-install-progress{margin-top:12px;background:var(--r-item-bg);border:1px solid var(--r-item-border);border-radius:8px;height:8px;overflow:hidden}
.pcw-install-progress .bar{height:100%;background:var(--r-accent);width:0%;transition:width .12s linear}
.pcw-install-msg{margin-top:6px;font-size:10px;color:var(--r-ink3);font-family:'Cascadia Code','Fira Code',monospace}
.statusbar{display:flex;align-items:center;gap:10px;padding:3px 14px;background:var(--r-surface);border-top:1px solid var(--r-border);font-size:9.5px;color:var(--r-ink-faint);flex-shrink:0;height:var(--r-status-h)}
.st-item{display:flex;align-items:center;gap:4px}
.st-sdot{width:5px;height:5px;border-radius:50%}
.st-sdot.green{background:var(--r-green)}
.st-sdot.yellow{background:var(--r-yellow)}
.statusbar .spacer{flex:1}
.statusbar a{color:var(--r-ink3);cursor:pointer}
.statusbar a:hover{color:var(--r-accent)}
.cable-type-bar{display:flex;align-items:center;gap:6px;background:var(--r-item-bg);border:1px solid var(--r-accent);border-radius:7px;padding:3px 10px}
.ctl-label{font-size:10px;font-weight:700;color:var(--r-accent);white-space:nowrap}
.cable-select{background:var(--r-surface2);border:1px solid var(--r-border2);border-radius:5px;padding:3px 8px;font-size:10px;font-weight:600;color:var(--r-ink);font-family:inherit;cursor:pointer;max-width:160px}
.cable-select:focus{outline:none;border-color:var(--r-accent)}
.cable-hint{font-size:9px;color:var(--r-accent);font-weight:700;white-space:nowrap;animation:pulse 1.5s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
.cable-picker{position:relative}
.cable-trigger{display:flex;align-items:center;gap:7px;background:var(--r-surface2);border:1px solid var(--r-border2);border-radius:5px;padding:3px 9px;font-size:10px;font-weight:600;color:var(--r-ink);font-family:inherit;cursor:pointer;max-width:190px}
.cable-trigger:hover{border-color:var(--r-accent)}
.cable-trigger .cable-caret{font-size:8px;color:var(--r-ink3);margin-inline-start:auto}
.cable-trigger #cableTriggerLabel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cable-menu{position:absolute;top:calc(100% + 6px);right:0;z-index:2000;background:var(--r-surface2);border:1px solid var(--r-border2);border-radius:10px;box-shadow:0 12px 40px var(--r-shadow-lg);min-width:270px;max-width:320px;display:none;padding:6px;max-height:340px;overflow-y:auto}
.cable-menu.show{display:block}
.cable-menu-item{display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:7px;border:1px solid transparent;cursor:pointer;transition:all .12s}
.cable-menu-item:hover{background:var(--r-accent-faint);border-color:var(--r-accent)}
.cable-menu-item.active{background:var(--r-accent-faint);border-color:var(--r-accent)}
.cable-menu-item .cm-swatch{flex-shrink:0;display:flex;align-items:center}
.cable-menu-item .cm-info{flex:1;min-width:0}
.cable-menu-item .cm-name{font-size:10.5px;font-weight:700;color:var(--r-ink)}
.cable-menu-item .cm-name .en{font-weight:500;color:var(--r-ink3);font-size:9px}
.cable-menu-item .cm-desc{font-size:9px;color:var(--r-ink3);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Shape picker (Sep 2026) -- same dropdown-off-a-toolbar-button visual language as
   .cable-picker/.cable-menu above (its own separate ruleset since that one may be
   dead/unused code from an earlier design, not something this should depend on), just
   with single-line icon+label rows instead of the cable menu's two-line swatch cards.
   position:fixed, not absolute: .toolbar has overflow-y:hidden (needed so a very long
   button row scrolls horizontally without ever growing taller), which silently clipped
   an absolutely-positioned dropdown hanging below it -- the menu toggled open but never
   became visible. toggleShapeMenu() in shared/app.js computes left/top from the trigger
   button's own getBoundingClientRect() before showing it, same fix as the Code Editor's
   topbar dropdowns earlier this session. */
.shape-picker{position:relative}
.shape-menu{position:fixed;z-index:2000;background:var(--r-surface2);border:1px solid var(--r-border2);border-radius:10px;box-shadow:0 12px 40px var(--r-shadow-lg);min-width:140px;display:none;padding:5px}
.shape-menu.show{display:block}
.shape-menu-item{display:flex;align-items:center;gap:8px;padding:6px 9px;border-radius:7px;border:1px solid transparent;cursor:pointer;font-size:10.5px;font-weight:600;color:var(--r-ink);transition:all .12s;white-space:nowrap}
.shape-menu-item:hover{background:var(--r-accent-faint);border-color:var(--r-accent)}
.shape-menu-item.active{background:var(--r-accent-faint);border-color:var(--r-accent)}
.shape-menu-item .sm-ico{font-size:13px;flex-shrink:0;width:16px;text-align:center}
/* Comment notes (Sep 2026) -- freestanding draggable text boxes, distinct from the
   existing per-DEVICE note popup (.node-note-btn/.note-popup below, tied to one specific
   device, one line of text in a modal). These live directly on the canvas like
   script-node icons (a plain HTML div in #zoomLayer, not SVG), are always editable (no
   separate view/edit mode to toggle), and use the same _annotColor swatch picker shapes
   already use for their accent. */
.text-note{position:absolute;width:180px;background:var(--r-surface2);border:1px solid var(--r-border2);border-left:3px solid var(--r-accent);border-radius:8px;box-shadow:0 8px 24px -6px var(--r-shadow-md);z-index:4;transition:width .15s,height .15s,border-radius .15s}
.text-note.dragging{opacity:.85}
.text-note-head{display:flex;align-items:center;justify-content:space-between;gap:6px;min-height:26px;padding:4px 5px;cursor:grab;border-bottom:1px solid var(--r-border)}
.text-note.dragging .text-note-head{cursor:grabbing}
/* Both controls sized as real ~20px touch/click targets, not just their glyph's own tiny
   rendered box (reported as "nothing happens when I click them" -- the icons before this
   were bare inline text with no padding/hit area of their own to speak of). */
.text-note-label{font-size:13px;cursor:pointer;border-radius:50%;width:20px;height:20px;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:background .12s,transform .12s}
.text-note-label:hover{background:var(--r-accent-faint)}
.text-note-del{width:20px;height:20px;flex-shrink:0;border-radius:50%;background:var(--r-red-bg);color:var(--r-red);display:flex;align-items:center;justify-content:center;font-size:12px;line-height:1;cursor:pointer;transition:transform .12s,background .12s}
.text-note-del:hover{transform:scale(1.15);background:var(--r-red);color:#fff}
.text-note-body{display:block;width:100%;min-height:44px;box-sizing:border-box;border:none;background:transparent;resize:none;font-size:12px;line-height:1.4;color:var(--r-ink);padding:7px 8px;font-family:inherit;outline:none;overflow:hidden}
.text-note-body::placeholder{color:var(--r-ink3)}
/* Collapsed (Sep 2026, user request: shrink a written note down to a small icon, reopen
   it the same way later) -- the 💬 label fills the entire 34x34 hit area in this state
   (still comfortably clickable/draggable), but the card's own background/border/shadow
   chrome is stripped so the emoji itself is the whole visible icon, not a glyph sitting
   inside a colored badge (a second user request right after collapse first shipped: "the
   shape inside the icon should just BE the icon"). A drop-shadow directly on the emoji
   glyph stands in for the old box-shadow, so it doesn't disappear into a busy canvas. */
.text-note.collapsed{width:34px;height:34px;background:transparent;border:none;box-shadow:none;filter:drop-shadow(0 2px 5px var(--r-shadow-sm))}
.text-note.collapsed:hover{filter:drop-shadow(0 3px 8px var(--r-shadow-md))}
.text-note.collapsed .text-note-head{height:100%;min-height:0;padding:0;border-bottom:none;justify-content:center}
.text-note.collapsed .text-note-del,.text-note.collapsed .text-note-body{display:none}
.text-note.collapsed .text-note-label{width:100%;height:100%;font-size:22px;background:transparent}
.text-note.collapsed .text-note-label:hover{background:transparent;transform:scale(1.12)}
.port-popup{position:fixed;z-index:2000;background:var(--r-surface2);border:1px solid var(--r-border2);border-radius:10px;box-shadow:0 12px 40px var(--r-shadow-lg);min-width:220px;max-width:300px;display:none;padding:0;overflow:hidden}
.port-popup.show{display:block}
.port-popup-head{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--r-border);background:var(--r-item-bg)}
.port-popup-title{font-size:11px;font-weight:700;color:var(--r-ink)}
.port-popup-close{width:20px;height:20px;border-radius:5px;border:none;background:transparent;color:var(--r-ink3);font-size:12px;cursor:pointer;display:flex;align-items:center;justify-content:center}
.port-popup-close:hover{background:var(--r-red-bg);color:var(--r-red)}
.port-popup-list{padding:6px;max-height:300px;overflow-y:auto}
.port-popup-item{display:flex;align-items:center;gap:8px;padding:7px 10px;border-radius:7px;border:1px solid transparent;cursor:pointer;transition:all .12s}
.port-popup-item:hover{background:var(--r-accent-faint);border-color:var(--r-accent)}
.port-popup-item.used{opacity:.4;cursor:not-allowed}
.port-popup-item.used:hover{background:transparent;border-color:transparent}
.port-popup-item .pp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.port-popup-item .pp-dot.free{background:var(--r-green)}
.port-popup-item .pp-dot.used{background:var(--r-red)}
.port-popup-item .pp-name{font-size:10.5px;font-weight:700;color:var(--r-ink);flex:1}
.port-popup-item .pp-type{font-size:9px;color:var(--r-ink3);white-space:nowrap}
.port-popup-item .pp-compat{font-size:8px;color:var(--r-green);font-weight:700;white-space:nowrap}

/* ═══ Node right-click context menu (Sep 2026, "duplicate a device") ═══ */
.node-ctx-menu{position:fixed;z-index:2100;background:var(--r-surface2);border:1px solid var(--r-border2);border-radius:10px;box-shadow:0 12px 40px var(--r-shadow-lg);min-width:150px;display:none;padding:5px;overflow:hidden}
.node-ctx-menu.show{display:block}
.node-ctx-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:7px;cursor:pointer;font-size:11.5px;font-weight:600;color:var(--r-ink);transition:background .12s}
.node-ctx-item:hover{background:var(--r-accent-faint)}
.node-ctx-item.node-ctx-danger{color:var(--r-red)}
.node-ctx-item.node-ctx-danger:hover{background:var(--r-red-bg)}
.node-ctx-sep{height:1px;background:var(--r-border);margin:4px 6px}
.node-ctx-head{font-size:9.5px;font-weight:800;letter-spacing:.4px;color:var(--r-ink3);padding:6px 10px 3px;text-transform:uppercase}
.node-ctx-item.node-ctx-disabled{opacity:.4;pointer-events:none}

/* ═══ Files Transfers panel (Sep 2026, "عاوز اتابع فين وصلت") ═══ Reuses .node-ctx-menu's
   own floating-panel base (position/border/shadow) via the shared class on the panel's own
   element; these rules only add what's specific to a list of progress rows. */
.fm-transfers-panel{min-width:320px;max-width:380px;max-height:380px;overflow-y:auto;padding:8px}
.fm-transfers-head{font-weight:800;font-size:11px;padding:2px 4px 8px;border-bottom:1px solid var(--r-border);margin-bottom:6px}
.fm-transfer-row{display:flex;align-items:flex-start;gap:8px;padding:7px 4px;border-bottom:1px solid var(--r-border)}
.fm-transfer-row:last-child{border-bottom:none}
.fm-transfer-ico{font-size:14px;flex-shrink:0;margin-top:1px}
.fm-transfer-info{flex:1;min-width:0}
.fm-transfer-name{font-size:11px;font-weight:700;color:var(--r-ink);word-break:break-all;line-height:1.3}
.fm-transfer-bar{height:5px;border-radius:3px;background:var(--r-surface2);margin:5px 0;overflow:hidden}
.fm-transfer-bar-fill{height:100%;border-radius:3px;transition:width .3s}
.fm-transfer-status{font-size:9.5px;color:var(--r-ink3);font-weight:600}
/* Sep 2026 ("عاوز اضايف حذف و ايقاف مؤقت") -- Pause/Resume/Cancel/Dismiss buttons on each
   Transfers panel row. */
.fm-transfer-actions{display:flex;flex-direction:column;gap:3px;flex-shrink:0}
.fm-transfer-act{width:20px;height:20px;border-radius:5px;border:1px solid var(--r-border2);background:var(--r-surface2);color:var(--r-ink3);font-size:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .12s}
.fm-transfer-act:hover{background:var(--r-accent-faint);border-color:var(--r-accent);color:var(--r-accent)}
/* A small pulsing dot on the toolbar button while at least one transfer is running, so
   there's a reason to open the panel even when it's closed. */
#fmTransfersBtn{position:relative}
#fmTransfersBtn.fm-transfers-active::after{content:'';position:absolute;top:3px;right:3px;width:7px;height:7px;border-radius:50%;background:var(--r-accent);animation:fmTransferPulse 1.4s ease-in-out infinite}
@keyframes fmTransferPulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(1.3)}}

/* ═══ Device notes — small icon on the node, floating popup to write/read it ═══ */
.node-note-btn{position:absolute;width:18px;height:18px;border-radius:50%;background:var(--r-surface2);border:1px solid var(--r-border2);color:var(--r-ink3);font-size:9.5px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:6;box-shadow:0 2px 6px var(--r-shadow-sm);transition:transform .12s,background .12s,border-color .12s}
.node-note-btn:hover{transform:scale(1.12)}
.node-note-btn.has-note{background:var(--r-accent);border-color:var(--r-accent);color:var(--r-accent-ink)}
.iso-node .node-note-btn{top:-3px;left:-3px}
.phy-node .node-note-btn{top:-7px;left:-7px}
/* Persistent read-only preview of the note, always visible above the device (not just on
   hover/click) — purely decorative and absolutely positioned, so it never affects node.x/y,
   port anchor math, or cable exit-angle calculations; it can only ever overlap ANOTHER
   device's card if two nodes are placed unusually close together, same as the existing name
   tag already can. */
.node-note-bubble{position:absolute;width:max-content;max-width:150px;background:var(--r-surface);border:1px solid var(--r-border2);border-radius:9px;padding:5px 9px;font-size:9.5px;line-height:1.4;color:var(--r-ink2);box-shadow:0 8px 20px -8px var(--r-shadow-lg);cursor:pointer;z-index:4;display:none;text-align:left;direction:ltr}
.node-note-bubble.show{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}
.node-note-bubble:hover{border-color:var(--r-accent);color:var(--r-ink)}
.iso-node .node-note-bubble{bottom:114px;left:50%;transform:translateX(-50%)}
.phy-node .node-note-bubble{top:-46px;left:0}
.note-popup{position:fixed;z-index:2000;background:var(--r-surface2);border:1px solid var(--r-border2);border-radius:10px;box-shadow:0 12px 40px var(--r-shadow-lg);width:240px;display:none;padding:0;overflow:hidden}
.note-popup.show{display:block}
.note-textarea{display:block;width:100%;box-sizing:border-box;border:none;background:transparent;outline:none;resize:vertical;min-height:90px;padding:10px 12px;font-family:inherit;font-size:11px;color:var(--r-ink);line-height:1.5}
.note-popup-actions{display:flex;gap:5px;padding:0 8px 8px}

/* ═══ Automation Scripts editor (Sep 2026) — reuses .modal-backdrop/.modal-box below;
   .open here is this session's own toggle (nothing already wired one up for that shared
   pair despite the CSS existing) rather than a display:none/flex snap, so it can animate. ═══ */
.modal-backdrop.open{display:flex;animation:scriptFadeIn .18s ease}
.modal-backdrop .modal-box{animation:scriptScaleIn .2s cubic-bezier(.22,.8,.36,1)}
@keyframes scriptFadeIn{from{opacity:0}to{opacity:1}}
@keyframes scriptScaleIn{from{opacity:0;transform:scale(.95) translateY(6px)}to{opacity:1;transform:scale(1) translateY(0)}}
.script-name-input{display:block;width:100%;box-sizing:border-box;padding:9px 12px;border-radius:8px;border:1px solid var(--r-item-border);background:var(--r-item-bg);color:var(--r-ink);font-family:inherit;font-size:12.5px;outline:none;margin-bottom:8px;transition:border-color .15s}
.script-name-input:focus{border-color:var(--r-accent)}
.script-body-textarea{display:block;width:100%;box-sizing:border-box;padding:10px 12px;border-radius:8px;border:1px solid var(--r-item-border);background:var(--r-item-bg);color:var(--r-ink);font-family:'JetBrains Mono','Fira Code',Consolas,monospace;font-size:12px;line-height:1.6;outline:none;resize:vertical;min-height:150px;transition:border-color .15s}
.script-body-textarea:focus{border-color:var(--r-accent)}
.script-modal-hint{font-size:10.5px;color:var(--r-ink3);line-height:1.5;margin-top:8px}
.script-item .ico{font-size:20px}
.script-item.dragging{opacity:.4}

/* ═══ Canvas annotations — freehand shapes/pen marks, sit above cables, below device cards ═══ */
.annot-svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:3;overflow:visible}
.annot-shape{pointer-events:all;cursor:pointer}
.annot-shape.selected{stroke-dasharray:5 3!important}
.annot-color-bar{display:flex;align-items:center;gap:8px}
.annot-swatches{display:flex;gap:5px}
.annot-swatch{width:18px;height:18px;border-radius:50%;border:2px solid transparent;cursor:pointer;padding:0;transition:transform .12s,border-color .12s}
.annot-swatch:hover{transform:scale(1.15)}
.annot-swatch.active{border-color:var(--r-ink)}

/* ═══ Labs / Projects — the multi-project picker modal and its toolbar entry point ═══ */
.proj-label{font-size:10.5px;font-weight:600;color:var(--r-ink3);white-space:nowrap;padding:0 2px}
.export-wrap{position:relative}
.export-menu{position:absolute;top:calc(100% + 4px);left:0;background:var(--r-surface2);border:1px solid var(--r-border2);border-radius:9px;box-shadow:0 12px 32px -8px var(--r-shadow-lg);padding:5px;z-index:200;min-width:210px}
.export-menu button{display:block;width:100%;text-align:left;padding:8px 10px;border-radius:6px;border:none;background:transparent;color:var(--r-ink);font-size:11px;font-weight:600;font-family:inherit;cursor:pointer;white-space:nowrap}
.export-menu button:hover{background:var(--r-item-bg)}
.modal-backdrop{position:fixed;inset:0;background:rgba(10,14,24,.55);backdrop-filter:blur(2px);display:none;align-items:center;justify-content:center;z-index:500}
.modal-box{width:520px;max-width:92vw;max-height:80vh;background:var(--r-surface);border:1px solid var(--r-border);border-radius:var(--r-radius);box-shadow:0 24px 64px -12px var(--r-shadow-lg);display:flex;flex-direction:column;overflow:hidden}
.modal-head{display:flex;align-items:center;gap:9px;padding:12px 14px;border-bottom:1px solid var(--r-border);flex-shrink:0}
.modal-head h3{margin:0;flex:1;font-size:13px;font-weight:700;color:var(--r-ink)}
.modal-body{padding:14px;overflow-y:auto}
.projects-list{display:flex;flex-direction:column;gap:8px}
.proj-empty{font-size:11px;color:var(--r-ink3);text-align:center;padding:24px 12px;line-height:1.6}
.proj-row{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:10px;border:1px solid var(--r-item-border);background:var(--r-item-bg)}
.proj-row.active{border-color:var(--r-accent)}
.proj-info{flex:1;min-width:0}
.proj-name{font-size:11.5px;font-weight:700;color:var(--r-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.proj-cur{font-size:9px;font-weight:700;color:var(--r-accent)}
.proj-meta{font-size:9.5px;color:var(--r-ink3);margin-top:2px}
.proj-actions{display:flex;gap:5px;flex-shrink:0}
.proj-btn{padding:5px 9px;border-radius:6px;border:1px solid var(--r-item-border);background:transparent;color:var(--r-ink3);font-size:10px;font-weight:600;font-family:inherit;cursor:pointer;white-space:nowrap}
.proj-btn:hover{border-color:var(--r-accent);color:var(--r-accent)}
.proj-btn.danger:hover{border-color:var(--r-red);color:var(--r-red)}

/* ═══ labs.html — the standalone project-picker home page (EVE-NG-style "Labs" screen) ═══ */
.labs-page{min-height:calc(100vh - var(--r-header-h));display:flex;flex-direction:column;background:var(--r-scene-bg)}
.labs-hero{padding:44px 24px 22px;text-align:center}
.labs-hero h1{margin:0 0 6px;font-size:22px;font-weight:800;color:var(--r-ink)}
.labs-hero p{margin:0;font-size:12px;color:var(--r-ink3)}
.labs-body{flex:1;max-width:960px;margin:0 auto;width:100%;padding:0 24px 48px;box-sizing:border-box}
.labs-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px}
.labs-new-card{border:1.5px dashed var(--r-item-border);background:transparent;display:flex;align-items:center;justify-content:center;min-height:150px;cursor:pointer;border-radius:14px;color:var(--r-ink3);font-size:12px;font-weight:700;transition:border-color .15s,color .15s}
.labs-new-card:hover{border-color:var(--r-accent);color:var(--r-accent)}
.labs-card{background:var(--r-item-bg);border:1px solid var(--r-item-border);border-radius:14px;padding:16px;display:flex;flex-direction:column;gap:10px;transition:border-color .15s}
.labs-card:hover{border-color:var(--r-border2)}
.labs-card-name{font-size:13px;font-weight:700;color:var(--r-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.labs-card-meta{font-size:10px;color:var(--r-ink3);margin-top:-6px}
.labs-export-row{display:flex;gap:5px}
.labs-card-actions{display:flex;gap:6px;margin-top:auto;padding-top:2px}
.labs-card-open{flex:1;justify-content:center}
.labs-empty{grid-column:1/-1;text-align:center;padding:50px 20px;color:var(--r-ink3);font-size:11.5px;line-height:1.8}

/* ═══ Site Plan (Phase 1) — building → floors → rooms → racks, a separate surface from
   the device canvas (#canvasArea), shown/hidden as a pair by showSitePlan()/
   hideSitePlan() in shared/app.js. Deliberately flat/2D for now (no isometric grid, no
   3D) per the explicit request — the "mini 3D" treatment is Phase 3, layered on top of
   this once the data/navigation is solid, not baked into the layout from the start. ═══ */
/* Aug 28 rewrite: fullscreen first-person Site Plan -- replaces the old tree +
   detail-panel layout (.site-shell/.site-tree-panel/.site-3d-panel/.site-rack-*/
   etc. below are now unused, left in place rather than deleted -- same "leave dead
   code rather than rip it out in the same change" pattern used throughout this
   feature). Padding/gap dropped to 0 here (was 20px 24px / 14px) so the 3D view
   fills the surface edge-to-edge -- an immersive first-person view with visible
   margins around it wouldn't read as one; the empty-state prompt (.site-empty,
   the only other thing this area ever shows) has its own internal padding and
   doesn't need the parent's. */
/* Aug 28 SECOND pivot: racks-only, fixed 2D (was fullscreen first-person 3D --
   the user cancelled that entirely: "forget this whole idea; just make the same thing
   but with racks, like a packet tracer"). Back to a normal scrollable padded panel --
   there's no canvas to fill edge-to-edge anymore, just plain HTML. All the old
   .site-fp-*/.site-hud-*/.site-picker-* rules below are still here: .site-hud-btn/
   .site-select/.site-picker-* are still used by the new rack view's header and its
   device-add modal; the rest (.site-fp-shell/.site-fp-stage/.site-fp-crosshair,
   the room-era classes further down) are now dead, left in place rather than torn
   out mid-pivot. */
.site-plan-area{flex:1;display:none;flex-direction:column;overflow-y:auto;padding:24px;background:var(--r-scene-bg);box-sizing:border-box}
.site-racks-page{max-width:1100px;margin:0 auto;width:100%}
.site-racks-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px}
.site-racks-head h2{font-size:18px;font-weight:800;color:var(--r-ink);margin:0}
.site-rack-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:14px}
.site-rack-card{position:relative;background:var(--r-surface);border:1px solid var(--r-border2);border-radius:12px;padding:20px 14px;cursor:pointer;transition:.12s;text-align:center}
.site-rack-card:hover{border-color:var(--r-accent);box-shadow:0 8px 20px -8px var(--r-accent-soft)}
.site-rack-card-ico{font-size:28px;margin-bottom:8px}
.site-rack-card-name{font-size:13px;font-weight:700;color:var(--r-ink)}
.site-rack-card-meta{font-size:11px;color:var(--r-ink3);margin-top:4px}
.site-rack-card-del{position:absolute;top:6px;right:6px;width:22px;height:22px;border-radius:6px;border:none;background:transparent;color:var(--r-ink-faint);cursor:pointer;font-size:12px;display:flex;align-items:center;justify-content:center}
.site-rack-card-del:hover{background:rgba(239,68,68,.15);color:#ef4444}
.site-rack-page{max-width:900px;margin:0 auto;width:100%}
.site-rack-view-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px}
.site-rack-view-title{font-size:14px;font-weight:800;color:var(--r-ink);flex:1}
.site-rack-view-head .site-hud-btn{color:var(--r-ink3);background:var(--r-item-bg);border-color:var(--r-item-border)}
.site-rack-view-head .site-hud-btn:hover{background:var(--r-accent-faint);color:var(--r-accent)}
.site-rack-view-head .site-select{padding:6px 9px;font-size:11px;font-weight:600;border-radius:7px;font-family:inherit;color:var(--r-ink);background:var(--r-item-bg);border:1px solid var(--r-item-border)}
.site-rack-hint{font-size:11px;color:var(--r-ink3);margin-bottom:14px}
.site-rack-frame{position:relative;background:var(--r-surface);border:1px solid var(--r-border2);border-radius:10px;overflow:hidden}
.site-cable-overlay{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:2}
.site-rack-row{position:relative;z-index:1;display:flex;align-items:center;gap:10px;padding:6px 12px;border-bottom:1px solid var(--r-border2);min-height:36px}
.site-rack-row:last-child{border-bottom:none}
.site-rack-row.empty{color:var(--r-ink-faint);font-size:11px}
.site-rack-row.powered-off{opacity:.55}
.site-rack-row-num{width:22px;flex-shrink:0;font-size:10px;font-weight:700;color:var(--r-ink-faint);text-align:center;font-variant-numeric:tabular-nums}
.site-rack-row-empty{font-style:italic}
.site-device-ico{flex-shrink:0}
.site-device-name{flex:0 0 auto;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;color:var(--r-ink)}
.site-device-ports{flex:1;display:flex;flex-wrap:wrap;gap:4px;align-items:center}
.site-port{width:12px;height:12px;border-radius:3px;background:#22c55e;cursor:pointer;flex-shrink:0;box-shadow:0 0 0 1px rgba(0,0,0,.25) inset;transition:transform .1s}
.site-port:hover{transform:scale(1.3)}
.site-port.used{background:#ef4444}
.site-port.armed{background:#fbbf24;box-shadow:0 0 0 2px #fbbf24}
.site-device-actions{flex-shrink:0;display:flex;gap:2px}
.site-icon-btn{width:22px;height:22px;border-radius:5px;border:1px solid var(--r-item-border);background:var(--r-item-bg);color:var(--r-ink3);font-size:10px;cursor:pointer;display:flex;align-items:center;justify-content:center}
.site-icon-btn:hover{background:var(--r-accent-faint);color:var(--r-accent)}
.site-icon-btn:disabled{opacity:.3;cursor:default}
.site-fp-shell{position:absolute;top:0;right:0;bottom:0;left:0;background:#000}
.site-fp-stage{position:absolute;top:0;right:0;bottom:0;left:0}
.site-fp-stage canvas{display:block;width:100%;height:100%}
.site-fp-crosshair{position:absolute;top:50%;left:50%;width:6px;height:6px;margin:-3px 0 0 -3px;border-radius:50%;background:rgba(255,255,255,.85);box-shadow:0 0 0 1.5px rgba(0,0,0,.5);pointer-events:none}
.site-fp-hud{position:absolute;top:14px;left:14px;width:400px;max-width:calc(100% - 28px);display:flex;flex-direction:column;gap:8px;padding:12px 14px;border-radius:12px;background:rgba(15,17,23,.72);backdrop-filter:blur(6px);color:#fff;pointer-events:none;box-sizing:border-box}
.site-fp-hud>*{pointer-events:auto}
.site-hud-top{display:flex;align-items:center;gap:10px}
.site-hud-label{font-size:13px;font-weight:800;color:#fff}
.site-hud-btn{background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.25);color:#fff;border-radius:8px;padding:6px 11px;font-size:11px;font-weight:700;cursor:pointer;font-family:inherit}
.site-hud-btn:hover{background:rgba(255,255,255,.24)}
.site-hud-actions{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.site-hud-actions .site-select{padding:5px 8px;font-size:10.5px;border-radius:7px;border:1px solid rgba(255,255,255,.25);background:rgba(255,255,255,.1);color:#fff}
.site-hud-devpanel{padding-top:8px;margin-top:2px;border-top:1px solid rgba(255,255,255,.18)}
.site-hud-devpanel .site-hud-label{flex:1 1 auto}
.site-hud-hint{font-size:10px;color:rgba(255,255,255,.75);line-height:1.5}
/* Device-add picker -- a centered modal overlay (not part of the persistent HUD),
   opened by the room-level HUD's "💻 Add Device" button. Reuses the real Logical
   sidebar's own .sb-cat/.sb-grid/.sb-item classes so it's visually the same device
   library, just click-to-add-and-mount instead of drag-to-canvas. */
.site-picker-overlay{position:absolute;inset:0;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;z-index:5}
.site-picker-panel{width:min(560px,92%);max-height:80%;overflow-y:auto;background:var(--r-surface);border:1px solid var(--r-border2);border-radius:14px;padding:16px;box-shadow:0 30px 80px -20px rgba(0,0,0,.5)}
.site-picker-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px;font-size:12.5px;font-weight:800;color:var(--r-ink)}
/* Unified single-screen layout (tree + persistent 3D/detail panel) -- replaces the
   old Building/Floor/Room page-drill-down. See the big comment above
   renderSitePlan() in modules/site-plan.js for the full rationale. .site-shell is a
   plain two-column flex row filling .site-plan-area; the tree column has a fixed
   width and scrolls independently of the 3D panel so a tall hierarchy never pushes
   the 3D view off-screen. */
/* The connect-a-cable bar (_renderSiteConnectBar() in modules/site-plan.js) --
   pinned above .site-shell, visible regardless of what's selected in the tree,
   since a connection can span two different rooms/floors. Same accent treatment as
   the "Drawing…" state of the Draw Room button, so it reads as "a mode is active"
   at a glance. */
.site-connect-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 14px;border-radius:12px;border:1.5px solid var(--r-accent);background:var(--r-accent-tint,rgba(99,102,241,.12))}
.site-connect-label{font-size:11.5px;color:var(--r-ink);white-space:nowrap}
.site-connect-arrow{font-size:12px;color:var(--r-ink3);flex-shrink:0}
.site-shell{display:flex;gap:16px;flex:1;min-height:0;align-items:flex-start}
.site-tree-panel{width:300px;flex-shrink:0;max-height:calc(100vh - 180px);overflow-y:auto;padding:12px;border-radius:12px;border:1px solid var(--r-item-border);background:var(--r-item-bg);display:flex;flex-direction:column;gap:2px}
.site-3d-panel{flex:1;min-width:0;display:flex;flex-direction:column;gap:14px}
.site-panel-detail{display:flex;flex-direction:column;gap:14px}
.site-tree-row{display:flex;align-items:center;gap:6px;padding:6px 6px;border-radius:8px;cursor:default}
.site-tree-row.active{background:var(--r-accent-tint,rgba(99,102,241,.14))}
.site-tree-toggle{flex-shrink:0;width:14px;font-size:9px;color:var(--r-ink3);cursor:pointer;text-align:center;user-select:none}
.site-tree-label{flex:1;min-width:0;font-size:11.5px;font-weight:700;color:var(--r-ink);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.site-tree-label:hover{color:var(--r-accent)}
.site-tree-count{flex-shrink:0;font-size:9px;color:var(--r-ink3);white-space:nowrap}
.site-tree-row-actions{display:flex;gap:4px;flex-shrink:0}
.site-tree-row-actions .proj-btn{padding:2px 6px;font-size:9px}
.site-tree-children{margin-inline-start:20px;padding-inline-start:8px;border-inline-start:1.5px dashed var(--r-border2);display:flex;flex-direction:column;gap:2px;margin-top:2px;margin-bottom:4px}
.site-tree-building{margin-bottom:2px}
.site-tree-building>.site-tree-row{padding:8px 6px}
.site-tree-building .site-tree-label{font-size:13px}
.site-tree-rack .site-tree-label{font-weight:500;color:var(--r-ink2,var(--r-ink))}
.site-crumb{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:700;color:var(--r-ink3)}
.site-crumb-link{cursor:pointer;color:var(--r-ink3);transition:color .12s}
.site-crumb-link:hover{color:var(--r-accent)}
.site-crumb-sep{color:var(--r-ink-faint)}
.site-crumb-cur{color:var(--r-ink)}
.site-toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.site-hint{font-size:11px;color:var(--r-ink3)}
.site-select{padding:7px 10px;border-radius:8px;border:1.5px solid var(--r-input-border);background:var(--r-input-bg);color:var(--r-ink);font-size:11px;font-family:inherit}
.site-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px}
.site-card{display:flex;align-items:center;gap:10px;padding:12px;border-radius:12px;border:1px solid var(--r-item-border);background:var(--r-item-bg);cursor:pointer;transition:border-color .15s}
.site-card:hover{border-color:var(--r-accent)}
.site-card-ico{font-size:22px;flex-shrink:0}
.site-card-info{flex:1;min-width:0}
.site-card-name{font-size:12px;font-weight:700;color:var(--r-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.site-card-meta{font-size:9.5px;color:var(--r-ink3);margin-top:2px}
.site-card-actions{display:flex;gap:5px;flex-shrink:0}
.site-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:60px 20px;text-align:center}
.site-empty-ico{font-size:40px}
.site-empty-title{font-size:14px;font-weight:700;color:var(--r-ink)}
.site-empty-sub{font-size:11.5px;color:var(--r-ink3);max-width:420px;line-height:1.7}
.site-floor-plan-wrap{border:1px solid var(--r-border2);border-radius:12px;overflow:hidden;background:var(--r-surface)}
/* overflow:visible defensively -- <svg> clips to its own box by default (same known
   issue already found and fixed once this session for the main device canvas), and
   promptAddAdjacentRoom() clamps new rooms to non-negative coordinates for exactly
   this reason, but a room manually dragged near the very edge could still end up
   partly outside the 0..w/0..h viewBox otherwise. */
.site-floor-plan{display:block;width:100%;height:auto;max-height:52vh;overflow:visible}
.site-floor-outline{fill:none;stroke:var(--r-border2);stroke-width:2}
.site-room-rect{fill:var(--r-accent-tint,var(--r-item-bg));stroke:var(--r-accent);stroke-width:1.5;cursor:pointer;transition:fill .12s}
.site-room-rect:hover{fill:var(--r-item-bg)}
.site-room-drawing{fill:var(--r-item-bg);stroke-dasharray:5 3;pointer-events:none}
.site-room-label{font-size:12px;font-weight:700;fill:var(--r-ink);pointer-events:none;user-select:none}

/* Room-view rack visualization -- same dark-header/numbered-unit-slot visual language
   as the live .rack-frame/.rack-slot on the Physical device canvas (see the comment
   above _renderRackVisual() in modules/site-plan.js for why these are a separate set
   of classes rather than reusing those elements directly), but position:relative in
   normal flex flow here instead of position:absolute inside the pannable/zoomable
   #zoomLayer, and with NO isometric grid or 3D device icons anywhere in this view. */
.site-rack-row{display:flex;flex-wrap:wrap;gap:16px;align-items:flex-start}
/* Room furniture (desk/laptop/chair/cabinet) -- a plain row list, not a card grid
   like racks, since there's no internal detail to expand into. */
.site-furniture{display:flex;flex-direction:column;gap:8px}
.site-furn-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 12px;border-radius:10px;border:1px solid var(--r-item-border);background:var(--r-item-bg);font-size:11.5px;color:var(--r-ink)}
.site-rack{width:220px;background:linear-gradient(180deg,#1c1c20,#101012);border:2px solid var(--r-border2);border-radius:10px;box-shadow:0 10px 30px -10px var(--r-shadow-lg);overflow:hidden;flex-shrink:0}
.site-rack-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 10px;background:var(--r-item-bg);border-bottom:2px solid var(--r-border2);font-size:10.5px;font-weight:700;color:var(--r-ink)}
.site-rack-units{color:var(--r-ink3);font-weight:600;font-size:9.5px}
.site-rack-body{position:relative}
.site-rack-slot{height:20px;border-bottom:1px dashed rgba(255,255,255,.06);display:flex;align-items:center;gap:5px;padding:0 6px 0 22px;position:relative}
.site-rack-slot-num{position:absolute;left:4px;font-size:7px;color:var(--r-ink-faint);font-weight:700}
.site-rack-slot-icon{width:16px;height:16px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
.site-rack-slot-icon svg{width:100%;height:100%}
.site-rack-slot-move{flex-shrink:0;width:12px;height:14px;line-height:14px;padding:0;border:none;background:transparent;color:var(--r-ink-faint);cursor:pointer;font-size:8px;border-radius:3px}
.site-rack-slot-move:hover{color:var(--r-accent);background:var(--r-accent-tint,rgba(99,102,241,.14))}
.site-rack-slot-move:disabled{opacity:.25;cursor:default}
.site-rack-slot-move:disabled:hover{color:var(--r-ink-faint);background:transparent}
.site-rack-slot-dev{font-size:9px;color:var(--r-ink2,#c7ccd6);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}
.site-rack-slot-connect{flex-shrink:0;height:14px;line-height:12px;padding:0 3px;border:none;background:transparent;color:var(--r-ink-faint);cursor:pointer;font-size:9px;border-radius:7px;white-space:nowrap}
.site-rack-slot-connect:hover{color:var(--r-green,#22c55e);background:rgba(34,197,94,.14)}
.site-rack-slot-edit{flex-shrink:0;width:14px;height:14px;line-height:12px;padding:0;border:none;background:transparent;color:var(--r-ink-faint);cursor:pointer;font-size:9px;border-radius:50%}
.site-rack-slot-edit:hover{color:var(--r-accent);background:var(--r-accent-tint,rgba(99,102,241,.14))}
.site-rack-slot-unmount{flex-shrink:0;width:14px;height:14px;line-height:12px;padding:0;border:none;background:transparent;color:var(--r-ink-faint);cursor:pointer;font-size:11px;border-radius:50%}
.site-rack-slot-unmount:hover{color:var(--r-red,#ef4444);background:rgba(239,68,68,.12)}
.site-rack-specs{display:flex;justify-content:space-between;gap:4px;padding:8px 10px;font-size:9px;color:var(--r-ink3);border-top:1px solid var(--r-border2)}
/* Mounting a device into this rack, straight from Site Plan (Aug 27, Physical was
   the only place this used to happen) -- .site-rack-mount is the "pick an existing
   Logical device" row; .site-rack-library is the expandable device-catalog picker
   ("➕ Add New Device"), reusing the real Logical sidebar's own .sb-cat/.sb-grid/
   .sb-item classes (shared/app.css) so it visually IS the same library, just
   click-to-mount instead of drag-to-canvas. */
.site-rack-mount{display:flex;align-items:center;gap:6px;padding:8px 10px;flex-wrap:wrap;border-bottom:1px solid var(--r-border2)}
.site-rack-mount .site-select{flex:1;min-width:0}
.site-rack-mount .site-hint{flex:1;min-width:0}
.site-rack-library{padding:10px;border-bottom:1px solid var(--r-border2);max-height:260px;overflow-y:auto}
.site-rack-actions{display:flex;gap:5px;padding:0 10px 10px}
.site-rack-actions .proj-btn{flex:1;justify-content:center;text-align:center}

/* Room list cards -- a dedicated vertical layout (unlike the generic horizontal
   .site-card every other card in this feature uses), see the comment above this
   HTML in renderFloorView() (modules/site-plan.js) for why: a room card needs a
   third row (the "add adjacent room" buttons) that floor/rack cards don't, and that
   row wrapping inside the same one-line flex row those use is exactly what produced
   the messy stacked-button layout the user pointed out. */
.site-room-card{display:flex;flex-direction:column;gap:8px;padding:12px;border-radius:12px;border:1px solid var(--r-item-border);background:var(--r-item-bg)}
.site-room-card-head{display:flex;align-items:center;gap:10px}
.site-adj-row{display:flex;align-items:center;flex-wrap:wrap;gap:4px}
.site-adj-label{font-size:9.5px;color:var(--r-ink3);margin-inline-end:2px}
.site-adj-row .proj-btn{padding:3px 8px;font-size:12px;line-height:1.4;min-width:26px;text-align:center}
.site-room-card-actions{display:flex;gap:5px}
.site-room-card-actions .proj-btn{flex:1;justify-content:center;text-align:center}

/* Building-level 3D view -- real CSS 3D (perspective/rotateX/rotateZ), see the long
   comment above _render3dBuilding() in modules/site-plan.js for the transform-order
   reasoning. .site-3d-building itself is intentionally 0×0 (position:relative, no
   size) -- every slab is centered around that single origin point via negative
   margins and only separated vertically by translateY, so left/top don't need to be
   50% of anything meaningful. */
.site-3d-stage{height:380px;display:flex;align-items:center;justify-content:center;perspective:1400px;cursor:grab;user-select:none;background:radial-gradient(ellipse at center,var(--r-surface2),var(--r-scene-bg));border-radius:14px;border:1px solid var(--r-border2)}
.site-3d-stage:active{cursor:grabbing}
.site-3d-building{position:relative;width:0;height:0;transform-style:preserve-3d;transition:transform .05s linear}
/* backface-visibility deliberately NOT hidden -- see modules/site-plan.js's comment
   on _SITE3D_DEFAULT_ROT for the actual bug this was masking (a wrong rotation sign
   was showing each slab's backface at a steep angle, rendering as a thin sliver);
   left visible now as a second, independent safety net in case any future rotation
   value still ends up facing the "wrong" way -- a visible backface just looks like
   the same solid gradient plate either way, where a hidden one would vanish. */
.site-3d-floor-slab{position:absolute;left:0;top:0;width:220px;height:150px;margin:-75px 0 0 -110px;background:linear-gradient(135deg,var(--r-accent),var(--r-accent-deep,#1e40af));border:2px solid rgba(255,255,255,.18);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.4);cursor:pointer;transform-style:preserve-3d;transition:filter .12s}
.site-3d-floor-slab:hover{filter:brightness(1.15)}
.site-3d-floor-face{position:absolute;inset:0;transform:rotateX(-90deg) translateZ(1px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;pointer-events:none}
.site-3d-floor-name{font-size:12px;font-weight:800;color:#fff;text-shadow:0 1px 4px rgba(0,0,0,.5)}
.site-3d-floor-meta{font-size:9px;color:rgba(255,255,255,.85);text-shadow:0 1px 4px rgba(0,0,0,.5)}

/* Floor-level 3D scene -- rooms as flat slabs spread out in X/Z (not stacked in Y,
   since every room on one floor sits at the same height), so "room A behind/beside
   room B" on the flat 2D plan reads as real spatial depth once tilted. A different
   accent color (green, not the building view's blue) purely so it's visually obvious
   which level you're looking at when navigating between the two. */
/* backface-visibility not hidden here either -- same reasoning as .site-3d-floor-slab
   above. */
.site-3d-room-slab{position:absolute;left:0;top:0;background:linear-gradient(135deg,var(--r-green,#22c55e),#15803d);border:2px solid rgba(255,255,255,.18);border-radius:6px;box-shadow:0 8px 20px rgba(0,0,0,.35);cursor:pointer;transform-style:preserve-3d;transition:filter .12s;margin:0}
.site-3d-room-slab:hover{filter:brightness(1.15)}
.site-3d-room-face{position:absolute;inset:0;transform:rotateX(-90deg) translateZ(1px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;pointer-events:none}
.site-3d-room-name{font-size:11px;font-weight:800;color:#fff;text-shadow:0 1px 4px rgba(0,0,0,.5);text-align:center;padding:0 4px}
.site-3d-room-meta{font-size:8.5px;color:rgba(255,255,255,.85);text-shadow:0 1px 4px rgba(0,0,0,.5)}

/* Room-level 3D BOX (not a flat slab) -- an open-top dollhouse-style cutaway: floor +
   two walls only (no ceiling/back/left walls), so racks placed inside stay visible
   from the viewing angle. See the long comment above _render3dRoomBox() in
   modules/site-plan.js for the face-positioning math. Deliberately NOT setting
   backface-visibility:hidden here (now consistent with the floor/room slabs, which
   also had it removed after a rotation-sign bug there hid the correct face) --
   if the untested rotateY(90deg) side-wall math above has its rotation direction
   backwards, the wall would render its CSS "back" face instead of "front", which
   for a plain gradient-filled rectangle looks identical either way; hiding
   backfaces would instead make a wrongly-oriented wall vanish completely, a much
   worse failure mode with no way to visually catch it this session. */
.site-3d-box-floor{position:absolute;left:0;top:0;background:linear-gradient(180deg,var(--r-surface3,#2a2a30),var(--r-surface2,#1c1c20));border:1px solid var(--r-border2)}
.site-3d-box-wall{position:absolute;left:0;top:0;background:linear-gradient(180deg,rgba(139,92,246,.35),rgba(139,92,246,.12));border:1px solid rgba(139,92,246,.4)}
.site-3d-rack-box{position:absolute;left:0;top:0;background:linear-gradient(135deg,#374151,#111827);border:1px solid rgba(255,255,255,.25);border-radius:3px;box-shadow:0 4px 10px rgba(0,0,0,.4);cursor:pointer;transform-style:preserve-3d;transition:filter .12s}
.site-3d-rack-box:hover{filter:brightness(1.3)}
.site-3d-rack-face{position:absolute;inset:0;transform:rotateX(-90deg) translateZ(1px);display:flex;align-items:center;justify-content:center;font-size:14px;pointer-events:none}
@keyframes siteRackFlash{0%,100%{box-shadow:0 0 0 0 transparent}25%,75%{box-shadow:0 0 0 3px var(--r-accent)}}
.site-rack-flash{animation:siteRackFlash 1.2s ease-in-out}

/* Real WebGL (Three.js) building view -- a plain container the <canvas> from
   modules/site-plan-3d.js gets appended into. No transform/perspective CSS needed
   here at all (unlike every .site-3d-* class above) -- the 3D is entirely inside the
   canvas's own WebGL context now, this div just sizes and frames it. Not forcing the
   canvas's own width/height via CSS -- Three.js's renderer.setSize() already sets
   matching pixel dimensions as both the canvas's rendering resolution AND its CSS
   style size, and overriding just the CSS side here risks stretching the image to a
   different aspect ratio than it was actually rendered at. */
.site-3d-webgl-stage{height:380px;border-radius:14px;overflow:hidden;border:1px solid var(--r-border2)}
/* toggleSite3dFullscreen() (modules/site-plan-3d.js) calls the real browser
   Fullscreen API on this container -- these rules size it to fill the screen once
   the browser actually grants that, and _wireFullscreenResize() in the same file
   keeps the WebGL renderer/camera aspect ratio matched to whatever size results,
   both entering and leaving. -webkit- prefix included for Safari. */
.site-3d-webgl-stage:fullscreen,.site-3d-webgl-stage:-webkit-full-screen{height:100vh;width:100vw;border-radius:0;background:var(--r-scene-bg)}

/* Range view switch: Network <-> Code Editor <-> Security Tools
   min-width:0 (Sep 2026 fix): .range-view is a flex item of .main{display:flex;
   overflow:hidden} -- without it, a flex item defaults to min-width:auto, so its own
   content (e.g. Security Tools' .sec-grid) dictated the container's width instead of
   shrinking to fit the viewport, and .main's overflow:hidden clipped whatever didn't fit
   instead of letting the grid wrap rows. Confirmed live: this alone was the entire cause
   of the Security Tools cards looking cut off/overlapping, not a .sec-grid issue at all. */
.range-view{flex:1;display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}
.code-frame{flex:1;width:100%;border:none;min-height:0;background:var(--r-surface)}

.sec-frame{flex:1;min-height:0;overflow:auto;background:var(--r-surface)}
.sec-loading{padding:40px;text-align:center;color:var(--r-ink3);font-size:13px;font-weight:600}
.sec-shell{display:flex;height:100%;min-height:0}
.sec-side{width:210px;flex-shrink:0;background:var(--r-surface2);border-inline-end:1px solid var(--r-border);padding:10px;display:flex;flex-direction:column;gap:3px;overflow-y:auto}
.sec-side-title{font-size:11px;font-weight:800;color:var(--r-ink);padding:8px 8px 12px;letter-spacing:.3px}
/* Sidebar redesign (Sep 2026) -- a colored leading-edge bar for the active item (a
   stronger, more legible "you are here" than a text-color change alone), transparent by
   default so the whole row doesn't shift width when it activates. */
.sec-item{padding:9px 10px;border-radius:8px;font-size:12px;font-weight:700;color:var(--r-ink2);cursor:pointer;transition:background .15s,border-color .15s,transform .1s;border-inline-start:3px solid transparent}
.sec-item:hover{background:var(--r-accent-faint);transform:translateX(-2px)}
.sec-item.active{background:var(--r-accent-faint);border-inline-start-color:var(--r-accent);color:var(--r-accent)}
.sec-spacer{flex:1}
.sec-foot{font-size:9px;color:var(--r-ink3);padding:8px;border-top:1px solid var(--r-border);margin-top:8px}
.sec-main{flex:1;min-width:0;overflow:auto;display:flex;flex-direction:column}
.sec-content{flex:1;min-height:0;padding:16px;overflow:auto;animation:secFadeIn .18s ease-out}
@keyframes secFadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
.sec-hd{margin-bottom:14px}
.sec-hd-title{font-size:17px;font-weight:800;color:var(--r-ink)}
.sec-hd-sub{font-size:11px;color:var(--r-ink3);margin-top:3px;max-width:720px;line-height:1.5}
.sec-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;margin-bottom:16px}
.sec-card{background:var(--r-surface2);border:1px solid var(--r-border);border-radius:14px;padding:16px;display:flex;gap:12px;transition:border-color .15s,transform .15s,box-shadow .15s}
.sec-card:hover{border-color:var(--r-accent);transform:translateY(-3px);box-shadow:0 14px 32px -14px var(--r-shadow-md)}
.sec-lib-bar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:14px}
.sec-lib-bar .sec-input{flex:1;min-width:180px;margin-top:0}
.sec-lib-filters{display:flex;gap:6px}
.sec-lib-chip{padding:6px 12px;border-radius:999px;border:1px solid var(--r-border);background:var(--r-surface2);color:var(--r-ink3);font-size:11px;font-weight:700;cursor:pointer;font-family:inherit;transition:all .12s}
.sec-lib-chip.active{background:var(--r-accent);border-color:var(--r-accent);color:var(--r-accent-ink)}
.sec-lib-add{margin-top:0;white-space:nowrap}
.sec-lib-card{flex-direction:column}
.sec-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:2px;flex-shrink:0}
.sec-card-badges{display:flex;gap:4px;align-items:center}
.sec-lib-badge{font-size:9px;font-weight:800;letter-spacing:.3px;padding:2px 8px;border-radius:999px;text-transform:uppercase;white-space:nowrap}
.sec-lib-badge.builtin{background:rgba(250,204,21,.12);color:#facc15;border:1px solid rgba(250,204,21,.35)}
.sec-lib-badge.custom{background:rgba(56,189,248,.12);color:#38bdf8;border:1px solid rgba(56,189,248,.35)}
.sec-ico{width:22px;height:22px;display:inline-flex;flex-shrink:0}
.sec-ico svg{width:100%;height:100%;display:block}
.sec-ico.emoji{font-size:20px;align-items:center;justify-content:center}
.sec-lib-card .sec-card-body{width:100%;flex:none;display:flex;flex-direction:column;gap:6px}
.sec-lib-meta{font-size:10px;font-weight:700;color:var(--r-ink3)}
.sec-open-btn{margin-top:2px;width:100%;justify-content:center;text-align:center}
.sec-lib-empty{grid-column:1/-1;margin:6px 0 0;color:var(--r-ink3);font-size:12px;line-height:1.5}
/* Card icon -- professional squared SVG badge (library redesign). Kept as ONE rule so the
   built-in cards, custom-tool cards, VM cards and the builder's saved-tools list share the
   same look; emojis render centered inside the same badge via .sec-ico.emoji. */
.sec-card-ico{width:42px;height:42px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:19px;border-radius:12px;background:linear-gradient(135deg,var(--r-surface2),var(--r-item-bg));border:1px solid var(--r-border);color:var(--r-accent);transition:transform .15s}
.sec-card:hover .sec-card-ico{transform:scale(1.06) rotate(-3deg)}
.sec-card-body{flex:1;min-width:0}
.sec-vm-role{font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--r-accent);border:1px solid var(--r-border2);border-radius:999px;padding:1px 7px;vertical-align:2px;margin-inline-start:4px}
.sec-vm-specs{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 0}
.sec-vm-chip{font-size:10.5px;font-weight:700;color:var(--r-ink2);background:var(--r-surface);border:1px solid var(--r-border2);border-radius:999px;padding:2px 9px;white-space:nowrap}
.sec-vm-actions{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px;align-items:center}
.sec-vm-actions .sec-btn{margin-top:0}
.sec-vm-actions .sec-btn:last-child{margin-top:0}
/* .sec-card-name = the tool's actual name (Ping, Traceroute...), .sec-card-title = the
   one-line description underneath it -- split into two levels (Sep 2026 redesign) since
   they used to be crammed into a single line with no hierarchy between "what this is
   called" and "what it does". */
.sec-card-name{font-size:13.5px;font-weight:800;color:var(--r-ink);letter-spacing:.1px}
.sec-card-title{font-size:11px;font-weight:600;color:var(--r-ink3);margin:3px 0 10px;line-height:1.4}
.sec-lbl{display:block;font-size:10px;font-weight:700;color:var(--r-ink3);margin:7px 0 3px}
.sec-input{width:100%;box-sizing:border-box;background:var(--r-surface);border:1px solid var(--r-border2);border-radius:7px;padding:7px 10px;font-size:11.5px;color:var(--r-ink);font-family:'Cascadia Code','Fira Code',monospace;direction:ltr;text-align:left;outline:none;transition:border-color .12s,box-shadow .12s}
.sec-input:focus{border-color:var(--r-accent);box-shadow:0 0 0 3px var(--r-accent-faint)}
.sec-btn{margin-top:10px;background:var(--r-accent);color:var(--r-accent-ink);border:none;border-radius:8px;padding:8px 14px;font-size:11.5px;font-weight:800;cursor:pointer;font-family:inherit;transition:filter .12s,transform .1s}
.sec-btn:disabled{opacity:.5;cursor:not-allowed}
.sec-btn:hover:not(:disabled){filter:brightness(1.08);transform:translateY(-1px)}
.sec-btn:active:not(:disabled){transform:translateY(0)}
.sec-note{font-size:10.5px;color:var(--r-ink3);line-height:1.6;background:var(--r-surface);border:1px dashed var(--r-border2);border-radius:8px;padding:9px 11px;margin-bottom:10px}
.sec-out{margin-top:6px;background:var(--r-surface2);border:1px solid var(--r-border);border-radius:12px;padding:14px;animation:secFadeIn .15s ease-out}
.sec-out-title{font-size:11px;font-weight:800;color:var(--r-ink2);margin-bottom:9px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.sec-out-empty{font-size:11.5px;color:var(--r-ink3);padding:8px 2px}
.sec-out-loading{font-size:11.5px;color:var(--r-accent);padding:8px 2px}
/* Sep 2026 (user request, "عدل الحجم"): was pre-wrap+break-all, which mangled anything
   columnar (a packet capture's No./Time/Source/... table) by breaking mid-word wherever a
   line ran long. Real terminal-style output (like a capture) needs its columns to stay
   aligned -- switched to pre + horizontal scroll instead of wrapping, same as any real
   monospace console. */
.sec-pre{background:var(--r-surface);border:1px solid var(--r-border);border-radius:9px;padding:12px;font-family:'Cascadia Code','Fira Code',monospace;font-size:11px;color:var(--r-ink2);white-space:pre;max-height:420px;overflow:auto;direction:ltr;text-align:left;line-height:1.55}
.sec-badge{display:inline-block;font-size:9px;font-weight:800;border-radius:6px;padding:2px 8px;vertical-align:middle}
.sec-badge.ok{background:rgba(34,197,94,.16);color:#22c55e}
.sec-badge.err{background:rgba(239,68,68,.16);color:#ef4444}
.sec-link{background:none;border:none;cursor:pointer;font-size:13px}
.sec-tbl{width:100%;border-collapse:collapse;font-size:11px;margin:6px 0 10px}
.sec-tbl th,.sec-tbl td{border:1px solid var(--r-border);padding:7px 9px;text-align:start}
.sec-tbl th{background:var(--r-surface2);color:var(--r-ink);font-weight:800;font-size:10px}
.sec-tbl td{color:var(--r-ink2)}
.sec-tbl tr{transition:background .1s}
.sec-tbl tbody tr:hover{background:var(--r-accent-faint)}
.sec-tbl .mono{font-family:'Cascadia Code','Fira Code',monospace;direction:ltr;text-align:left;white-space:nowrap}
.sec-audit-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:11px;margin-bottom:14px}
.sec-audit-card{background:var(--r-surface2);border:1px solid var(--r-border);border-radius:11px;padding:14px;text-align:start;font-family:inherit;font-size:12px;font-weight:800;color:var(--r-ink);cursor:pointer;transition:border-color .15s,transform .12s,box-shadow .15s;display:flex;flex-direction:column;gap:5px}
.sec-audit-card:hover{border-color:var(--r-accent);transform:translateY(-2px);box-shadow:0 10px 24px -12px var(--r-shadow-md)}
.sec-audit-ico{font-size:21px}
.sec-audit-t{font-size:12px;font-weight:800}
.sec-audit-sub{font-size:9.5px;color:var(--r-ink3);font-weight:600;font-family:'Cascadia Code','Fira Code',monospace;direction:ltr;text-align:left}
.sec-sim-tabs{display:flex;gap:8px;border-bottom:1px solid var(--r-border);margin-bottom:14px;padding-bottom:10px;flex-wrap:wrap}
.sec-sim-tab{background:var(--r-surface2);border:1px solid var(--r-border);border-radius:8px;padding:7px 14px;font-size:11.5px;font-weight:800;color:var(--r-ink2);cursor:pointer;font-family:inherit;transition:background .12s,color .12s,border-color .12s}
.sec-sim-tab:hover{border-color:var(--r-accent)}
.sec-sim-tab.active{background:var(--r-accent);color:var(--r-accent-ink);border-color:var(--r-accent)}
.sec-sim-body{flex:1;overflow:auto}
.sec-fw-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px}
.sec-fw-row .sec-input{width:auto;flex:1;min-width:120px}
/* ── Sep 2026 redesign additions: elapsed timer, copy button, run history, JSON tokens ── */
.sec-spinner{display:inline-block;width:11px;height:11px;border:2px solid var(--r-accent-faint);border-top-color:var(--r-accent);border-radius:50%;animation:secSpin .6s linear infinite;flex-shrink:0}
@keyframes secSpin{to{transform:rotate(360deg)}}
.sec-elapsed{font-family:'Cascadia Code','Fira Code',monospace;font-size:10px;color:var(--r-ink3);font-weight:700;direction:ltr}
.sec-copy-btn{margin-inline-start:auto;background:var(--r-surface);border:1px solid var(--r-border2);border-radius:6px;padding:3px 9px;font-size:10px;font-weight:700;color:var(--r-ink2);cursor:pointer;font-family:inherit;transition:border-color .12s,color .12s}
.sec-copy-btn:hover{border-color:var(--r-accent);color:var(--r-accent)}
.sec-history{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:10px;padding-top:10px;border-top:1px dashed var(--r-border2)}
.sec-history-lbl{font-size:9.5px;color:var(--r-ink3);font-weight:700}
.sec-history-chip{background:var(--r-surface);border:1px solid var(--r-border2);border-radius:20px;padding:3px 10px;font-size:9.5px;font-weight:700;color:var(--r-ink3);cursor:pointer;font-family:inherit;direction:ltr;transition:border-color .12s,color .12s}
.sec-history-chip:hover{border-color:var(--r-accent)}
.sec-history-chip.active{border-color:var(--r-accent);color:var(--r-accent);background:var(--r-accent-faint)}
.sec-json-key{color:var(--r-accent);font-weight:700}
.sec-json-str{color:#22c55e}
.sec-json-num{color:#3b82f6}
.sec-json-bool{color:#a855f7;font-weight:700}
.sec-json-null{color:var(--r-ink3);font-style:italic}
/* ─── Files view (Explorer-style file manager) ─── */
.fm-tree-i{padding:5px 12px;cursor:pointer;font-size:11px;color:var(--r-ink2);border-radius:4px;margin:1px 4px;display:flex;align-items:center;gap:4px}
.fm-tree-i:hover{background:var(--r-item-bg)}
.fm-tree-i.on{background:var(--r-accent-faint);color:var(--r-accent);font-weight:700}
.fm-tree-tw{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;flex-shrink:0;font-size:9px;color:var(--r-ink3);border-radius:4px;user-select:none}
.fm-tree-tw:hover{background:var(--r-accent-soft,var(--r-surface2));color:var(--r-accent)}
.fm-tree-tw.leaf{pointer-events:none}
.fm-item{text-align:center;padding:12px 6px;border-radius:8px;cursor:pointer;border:2px solid transparent;transition:.1s;user-select:none}
.fm-item:hover{background:var(--r-item-bg)}
.fm-item.sel{background:var(--r-accent-faint);border-color:var(--r-accent)}
/* A grid item backed by a real ISO/VM-image file on the hypervisor (not a simulated
   project file) -- small corner badge + accent-colored size label distinguish it. */
.fm-item.fm-item-media{position:relative;border-color:var(--r-border2)}
.fm-item-badge{position:absolute;top:4px;right:4px;font-size:8px;font-weight:800;padding:1px 5px;border-radius:5px;background:var(--r-accent);color:var(--r-accent-ink);letter-spacing:.3px}
/* Media library card on the Files tab (Sep 2026) -- upload ISO/VM images and see what is
   already stored; the same GET /api/tenant-vms/media listing the Create-VM wizard's
   "Boot → Media library" picker reads. Reuses the wizard's dashed upload-zone look via
   label.vm-upload-zone (a label is inline by default, so it needs an explicit block). */
label.vm-upload-zone{display:block}
.fm-media{display:flex;flex-direction:column;gap:8px;padding:10px 12px;border:1px solid var(--r-border);border-radius:var(--r-radius);background:var(--r-surface2);flex-shrink:0}
.fm-media-head{display:flex;align-items:center;gap:8px}
.fm-media-head b{font-size:12px;color:var(--r-ink2)}
.fm-media-list{display:flex;flex-wrap:wrap;gap:6px;max-height:92px;overflow-y:auto}
.fm-media-chip{display:flex;flex-direction:column;gap:2px;padding:6px 10px;border:1px solid var(--r-border);border-radius:8px;background:var(--r-surface);max-width:240px}
.fm-media-chip-name{font-size:11px;font-weight:700;color:var(--r-ink);word-break:break-all;line-height:1.3}
.fm-media-chip-meta{font-size:9px;color:var(--r-ink3);font-weight:600}
.fm-media-empty{font-size:11px;color:var(--r-ink3);padding:4px 2px;line-height:1.5}
.fm-media-progress{display:flex;align-items:center;gap:10px}
.fm-media-progress-track{flex:1;height:8px;border-radius:6px;background:var(--r-border);overflow:hidden}
.fm-media-progress-fill{height:100%;width:0;background:linear-gradient(90deg,var(--r-accent),var(--r-green));border-radius:6px;transition:width .15s}
/* Files page redesign (Sep 2026) -- the Files tab is now a full-screen two-panel
   workspace: hero strip on top, Media Library card on the left, project Explorer on the
   right. Everything is fluid (fills the browser viewport; no fixed page widths). */
#viewFiles{height:100%;display:flex;flex-direction:column;min-height:0}
#fmShell{flex:1;min-height:0;display:flex}
.fm-page{flex:1;min-height:0;width:100%;display:flex;flex-direction:column;gap:12px;padding:14px;box-sizing:border-box}
.fm-hero{display:flex;align-items:center;gap:14px;padding:16px 18px;border:1px solid var(--r-border);border-radius:var(--r-radius);background:linear-gradient(135deg,var(--r-surface2),var(--r-accent-faint));flex-shrink:0}
.fm-hero-ico{width:46px;height:46px;border-radius:12px;background:var(--r-accent);color:var(--r-accent-ink);display:flex;align-items:center;justify-content:center;font-size:22px;box-shadow:0 10px 24px -10px var(--r-accent-soft);flex-shrink:0}
.fm-hero-txt{display:flex;flex-direction:column;gap:2px}
.fm-hero-title{font-size:15px;font-weight:800;color:var(--r-ink)}
.fm-hero-sub{font-size:11px;color:var(--r-ink3)}
.fm-hero-spacer{flex:1}
/* Storage plan usage bar (Sep 2026) -- hero-strip display of GET /api/cloud/storage,
   same used/quota + warn/danger threshold convention as cloud-app.html's own bar. */
.fm-storage{display:flex;flex-direction:column;gap:4px;min-width:170px;padding:0 6px;flex-shrink:0}
.fm-storage-label{font-size:9.5px;font-weight:800;color:var(--r-ink3);text-transform:uppercase;letter-spacing:.4px}
.fm-storage-track{height:6px;border-radius:4px;background:var(--r-border);overflow:hidden}
.fm-storage-fill{height:100%;width:0;background:var(--r-accent);border-radius:4px;transition:width .3s}
.fm-storage-fill.warn{background:#ca8a04}
.fm-storage-fill.danger{background:var(--r-red)}
.fm-storage-text{font-size:9.5px;font-weight:700;color:var(--r-ink3)}
.fm-body{display:grid;grid-template-columns:minmax(300px,370px) 1fr;gap:12px;flex:1;min-height:0}
/* Unified Media Library + Project browser (Sep 2026) -- one explorer card fills the row
   instead of the old Media Library card + Project card side by side. */
.fm-body.fm-body-single{grid-template-columns:1fr}
.fm-card{display:flex;flex-direction:column;min-height:0;overflow:hidden;border:1px solid var(--r-border);border-radius:var(--r-radius);background:var(--r-surface2)}
.fm-card-head{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--r-border);flex-shrink:0}
.fm-card-head b{font-size:12.5px;color:var(--r-ink)}
.fm-card-sub{font-size:10px;color:var(--r-ink3);font-weight:700;margin-left:auto}
.fm-folder-row{display:flex;align-items:center;gap:8px;padding:8px 12px 0;flex-shrink:0}
.fm-folder-lbl{font-size:10.5px;font-weight:700;color:var(--r-ink3);white-space:nowrap}
.fm-folder-select{flex:1;min-width:0;padding:7px 9px;border:1.5px solid var(--r-border2);border-radius:8px;background:var(--r-surface);color:var(--r-ink);font-size:11.5px;font-weight:600;font-family:inherit;cursor:pointer}
.fm-folder-select:hover{border-color:var(--r-accent)}
.fm-folder-input{flex:1;min-width:0;padding:7px 9px;border:1.5px solid var(--r-border2);border-radius:8px;background:var(--r-surface);color:var(--r-ink);font-size:11px;font-weight:600;font-family:inherit;outline:none}
.fm-folder-input:focus{border-color:var(--r-accent);box-shadow:0 0 0 3px var(--r-accent-faint)}
.fm-folder-input::placeholder{color:var(--r-ink-faint);font-weight:500}
.fm-folder-pick{position:relative;z-index:30;margin:6px 12px 0;background:var(--r-surface2);border:1px solid var(--r-border);border-radius:10px;box-shadow:var(--r-shadow-md);padding:6px;max-height:220px;overflow-y:auto;flex-shrink:0}
.fm-media-dest{display:flex;align-items:center;gap:8px;margin:6px 12px 0;padding:6px 10px;border:1px dashed var(--r-border2);border-radius:8px;background:var(--r-surface);flex-shrink:0;min-width:0}
.fm-media-dest-lbl{font-size:9.5px;font-weight:800;color:var(--r-ink3);text-transform:uppercase;letter-spacing:.4px;white-space:nowrap}
.fm-media-dest-path{font-family:'Cascadia Code','Fira Code',ui-monospace,monospace;font-size:10.5px;font-weight:700;color:var(--r-accent);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:ltr;text-align:left;min-width:0}
.fm-media-tnode{display:flex;align-items:center;gap:4px;padding-top:2px;padding-bottom:2px;padding-right:6px;border-radius:7px;cursor:pointer;min-width:0}
.fm-media-tnode:hover{background:var(--r-surface-hover, var(--r-surface2))}
.fm-media-tnode.active{background:var(--r-accent-soft, rgba(var(--r-accent),.10));box-shadow:inset 2px 0 0 var(--r-accent)}
.fm-media-tn-tw{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;flex-shrink:0;font-size:10px;color:var(--r-ink3);border-radius:4px;user-select:none}
.fm-media-tn-tw:hover{background:var(--r-accent-soft, var(--r-surface2));color:var(--r-accent)}
.fm-media-tn-tw.leaf{pointer-events:none}
.fm-media-tn-tw i{display:block;width:4px;height:4px;border-radius:50%;background:var(--r-border2)}
.fm-media-tn-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:11.5px;font-weight:600;color:var(--r-ink2);min-width:0}
.fm-media-tn-label.active{color:var(--r-accent);font-weight:800}
.fm-media-tn-count{flex-shrink:0;font-size:9px;font-weight:700;color:var(--r-ink3);background:var(--r-surface2);border:1px solid var(--r-border);border-radius:8px;padding:0 5px}
.fm-media-tn-check{flex-shrink:0;font-size:10px;color:var(--r-accent);font-weight:800}
.fm-pick-title{font-size:9.5px;font-weight:800;color:var(--r-ink3);padding:2px 8px 6px;letter-spacing:.4px;text-transform:uppercase}
.fm-pick-item{padding:7px 10px;border-radius:8px;font-size:11px;font-weight:700;color:var(--r-ink2);cursor:pointer}
.fm-pick-item:hover{background:var(--r-accent-faint);color:var(--r-accent)}
.fm-inline-row{display:flex;align-items:center;gap:6px;padding:8px 12px 0;flex-shrink:0}
.fm-inline-inp{flex:1;min-width:0;padding:7px 9px;border:1.5px solid var(--r-border2);border-radius:8px;background:var(--r-surface);color:var(--r-ink);font-size:11px;font-weight:600;font-family:inherit;outline:none}
.fm-inline-inp:focus{border-color:var(--r-accent);box-shadow:0 0 0 3px var(--r-accent-faint)}
.fm-inline-bar{display:flex;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--r-border);background:var(--r-accent-tint);flex-shrink:0}
.fm-inline-lbl{font-size:11px;font-weight:800;color:var(--r-ink)}
.fm-upload-do{display:flex;align-items:center;gap:8px;padding:8px 12px 0;flex-shrink:0}
.fm-upload-pending{font-size:10px;font-weight:800;color:var(--r-accent);background:var(--r-accent-faint);border:1px solid var(--r-accent-soft);border-radius:999px;padding:3px 10px;white-space:nowrap}
/* min-width:0 (Sep 2026, bug fix "فين شريط التقدم"): a flex item's default min-width is
   auto, which means it never actually shrinks below its own text's full length no matter
   what overflow/text-overflow say -- so a long filename+path here was pushing the sibling
   progress bar (also flex, no fixed width of its own) down to zero visible width instead
   of ever truncating. This is what actually makes the ellipsis (already declared below)
   take effect. */
.fm-upload-loc{font-size:10.5px;font-weight:700;color:var(--r-ink2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.fm-upload-btn{background:var(--r-accent);color:var(--r-accent-ink);font-weight:800}
.fm-upload-btn:hover{filter:brightness(1.08)}
.fm-media-folder-row{display:flex;align-items:center;gap:8px;margin-top:6px;padding:7px 10px;border:1px solid var(--r-border);border-radius:9px;background:var(--r-surface);cursor:pointer;transition:border-color .12s,transform .1s}
.fm-media-folder-row:hover{border-color:var(--r-accent);transform:translateX(2px)}
.fm-media-folder-row .fm-media-folder{margin:0;flex:1}
.fm-media-folder-ic{font-size:12px;flex-shrink:0}
.fm-media-folder-name{font-size:11.5px;font-weight:800;color:var(--r-ink);flex:1;word-break:break-all;line-height:1.3}
.fm-media-folder-count{font-size:9.5px;font-weight:700;color:var(--r-ink3);background:var(--r-surface2);border:1px solid var(--r-border);border-radius:999px;padding:1px 8px;white-space:nowrap}
.fm-media-folder-arrow{font-size:11px;color:var(--r-ink3);flex-shrink:0}
.fm-media-list{flex:1;min-height:0;overflow-y:auto;padding:10px 12px;display:flex;flex-direction:column;gap:6px;background:var(--r-scene-bg)}
.fm-media-folder{font-size:10px;font-weight:800;color:var(--r-accent);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 2px}
.fm-media-chip{display:flex;flex-direction:column;gap:2px;padding:8px 10px;border:1px solid var(--r-border);border-radius:8px;background:var(--r-surface);cursor:pointer;transition:border-color .12s,transform .1s}
.fm-media-chip:hover{border-color:var(--r-accent);transform:translateY(-1px)}
.fm-media-chip-name{font-size:11px;font-weight:700;color:var(--r-ink);word-break:break-all;line-height:1.3}
.fm-media-chip-meta{font-size:9.5px;color:var(--r-ink3);font-weight:600}
.fm-media-empty{font-size:11px;color:var(--r-ink3);padding:6px 2px;line-height:1.6}
.fm-files-card{position:relative}
.fm-files-inner{display:flex;flex:1;min-height:0}
.fm-tree-c{width:200px;flex-shrink:0;overflow-y:auto;padding:8px 0;border-left:1px solid var(--r-border);background:var(--r-surface2)}
.fm-ex-main{flex:1;min-width:0;display:flex;flex-direction:column;background:var(--r-surface)}
.fm-ex-toolbar{display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--r-border);flex-shrink:0;flex-wrap:wrap;background:var(--r-surface2)}
.fm-ex-spacer{flex:1}
.fm-bc{display:flex;align-items:center;gap:4px;padding:6px 12px;border-bottom:1px solid var(--r-border);font-size:11px;color:var(--r-ink3);flex-shrink:0;overflow-x:auto;white-space:nowrap}
.fm-grid{flex:1;overflow-y:auto;padding:14px;display:grid;grid-template-columns:repeat(auto-fill,minmax(108px,1fr));gap:10px;align-content:start;background:var(--r-scene-bg)}
.fm-status{display:flex;justify-content:space-between;padding:6px 12px;border-top:1px solid var(--r-border);font-size:10.5px;color:var(--r-ink3);flex-shrink:0;background:var(--r-surface2)}
/* flex:0 0 150px (Sep 2026, same bug as .fm-upload-loc's min-width:0 above): this had no
   width/flex-basis of its own inside the upload bar's flex row, so its .fm-media-progress-
   track (flex:1) had nothing real to fill -- it just collapsed to whatever sliver of space
   fm-upload-loc's untruncated text left behind, usually nothing visible at all. */
.fm-media-progress{display:flex;align-items:center;gap:10px;padding:8px 12px 0;flex:0 0 150px}
.fm-media-progress-pct{font-size:11px;font-weight:800;color:var(--r-ink2);min-width:40px;text-align:right}

/* ═══ Notebook — A4 ruled pages, a surface separate from the device board ═══ */
.notebook-shell{position:fixed;inset:0;z-index:800;background:var(--r-surface);display:flex;flex-direction:column}
.nb-topbar{flex-shrink:0;display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--r-surface2);border-bottom:1px solid var(--r-border);flex-wrap:wrap}
.nb-title{font-size:13px;font-weight:800;color:var(--r-ink);white-space:nowrap}
.nb-tools{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.nb-tool-btn{padding:5px 9px;font-size:11px;font-weight:700;border:1px solid var(--r-border2);border-radius:7px;background:var(--r-surface3);color:var(--r-ink2);cursor:pointer;transition:border-color .12s,background .12s;font-family:inherit}
.nb-tool-btn:hover{border-color:var(--r-accent)}
.nb-tool-btn.active{background:var(--r-accent);color:var(--r-accent-ink);border-color:var(--r-accent)}
.nb-sw{width:16px;height:16px;border-radius:50%;border:2px solid var(--r-border2);cursor:pointer;display:inline-block;flex-shrink:0;box-shadow:0 1px 3px var(--r-shadow-md)}
.nb-sw.active{border-color:var(--r-accent);box-shadow:0 0 0 2px var(--r-accent)}
.nb-size{width:92px}
.nb-nav{display:flex;align-items:center;gap:6px;margin-left:auto}
.nb-nav button,.nb-close{font-size:12px;font-weight:800;border:1px solid var(--r-border2);border-radius:7px;background:var(--r-surface3);color:var(--r-ink2);cursor:pointer;padding:5px 10px}
.nb-nav button:hover{border-color:var(--r-accent)}
.nb-nav button:disabled{opacity:.45;cursor:default}
.nb-close{background:var(--r-red);color:#fff;border-color:var(--r-red)}
.nb-pagecount{font-size:11.5px;font-weight:800;color:var(--r-ink2);white-space:nowrap}
.nb-scroll{flex:1;overflow:auto;padding:28px 20px 60px;display:flex;justify-content:center;align-items:flex-start}
.nb-page{position:relative;width:800px;height:1131px;flex-shrink:0;background:
  linear-gradient(to right,transparent calc(24% - 1px),rgba(214,69,69,.62) 24%,transparent calc(24% + 1px)),
  repeating-linear-gradient(to bottom,#fdfdfd 0 36px,#d9e2f6 36px 38px),
  #fff;
  box-shadow:0 14px 40px -10px var(--r-shadow-lg);border-radius:4px;touch-action:none;cursor:crosshair}
.nb-canvas{position:absolute;inset:0;width:800px;height:1131px;pointer-events:none}
.nb-items{position:absolute;inset:0;overflow:hidden;pointer-events:none}
.nb-item{position:absolute;pointer-events:all;cursor:move;user-select:none;-webkit-user-select:none;touch-action:none}
.nb-item img{width:100%;height:100%;object-fit:contain;display:block;pointer-events:none;user-select:none;-webkit-user-select:none}
.nb-item.selected{outline:2px solid var(--r-accent)}
.nb-item .nb-del{position:absolute;top:-9px;right:-9px;width:19px;height:19px;border-radius:50%;background:var(--r-red);color:#fff;font-size:11px;line-height:19px;text-align:center;cursor:pointer;display:none;z-index:3;border:none}
.nb-item.selected .nb-del{display:block}
.nb-item .nb-handle{position:absolute;right:-6px;bottom:-6px;width:14px;height:14px;background:var(--r-accent);border-radius:3px;cursor:nwse-resize;display:none;z-index:3}
.nb-item.selected .nb-handle{display:block}
.nb-text{position:absolute;transform:translate(-50%,-50%);white-space:pre-wrap;pointer-events:all;cursor:move;font-family:inherit}
.nb-text .nb-del{top:-8px;right:-8px}
.nb-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#b3bdd6;font-size:13px;font-weight:700;pointer-events:none;letter-spacing:.5px}
/* Notebook PDF page picker */
.nb-pdf-picker{position:fixed;inset:0;background:rgba(8,12,20,.6);z-index:850;display:flex;align-items:center;justify-content:center}
.nb-pdf-box{width:520px;max-width:92vw;max-height:76vh;background:var(--r-surface);border:1px solid var(--r-border);border-radius:12px;box-shadow:0 24px 64px -12px var(--r-shadow-lg);display:flex;flex-direction:column;overflow:hidden}
.nb-pdf-head{display:flex;align-items:center;gap:9px;padding:11px 13px;border-bottom:1px solid var(--r-border)}
.nb-pdf-body{flex:1;overflow-y:auto;padding:12px;display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px;align-content:start}
@media print{
  @page{size:A4;margin:0.6cm}
  body>*:not(.notebook-shell){display:none!important}
  .notebook-shell{position:static!important;background:#fff;display:block!important;overflow:visible}
  .nb-scroll{padding:0;overflow:visible;display:block}
  .nb-page{box-shadow:none;page-break-after:always}
  .nb-topbar,.nb-nav,.nb-close,.nb-tools,.nb-pdf-picker{display:none!important}
}

/* ─── MOBILE (≤640px, 2026-10-06): the floating device library must fit the canvas next to the 58px rail ─── */
@media(max-width:640px){
  .sidebar{width:calc(100% - 28px)}
  .site-tree-panel{width:100%;max-height:none}
}

/* touch screens: the Network Lab canvas is driven by shared/touch-bridge.js, so stop the browser scrolling/zooming/selecting under the finger */
@media(pointer:coarse){
  #canvasArea{touch-action:none;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
}
