/* ---------------------------------------------------------------
   Theme tokens. Every color in the UI resolves to one of these, so a
   theme change happens here rather than across the whole file.
   The 3D scene's colors mirror these in theme.js — Three.js needs
   numeric hex, so the two can't literally share a declaration.
   --------------------------------------------------------------- */
:root {
  --bg-deep: #0d0f14;      /* page + input backgrounds */
  --bg-panel: #11141b;     /* panels, buttons, toolbars */
  --bg-hover: #1e293b;     /* hovered controls */
  --border: #334155;       /* control + panel borders */
  --border-subtle: #1e293b;/* quieter panel dividers */
  --text: #e2e8f0;         /* primary text */
  --text-muted: #94a3b8;   /* labels, secondary text */
  --accent: #ffb347;       /* selection highlight */
  --accent-blue: #5a8dee;  /* drop targets, default node color */
  --danger: #f87171;       /* error text */

  --panel-width: 280px;
}

/* Reset the default page margin so our 3D canvas can fill the
   entire browser window, edge to edge. */
html, body {
  margin: 0;
  padding: 0;
  overflow: hidden; /* the 3D scene handles its own "scrolling" (orbiting) */
  height: 100%;
  background: var(--bg-deep);
}

#scene-container {
  position: relative; /* anchors the CSS2D label layer to this element */
  z-index: 0; /* explicit and low, so panels with a higher z-index reliably paint over node labels */
  width: 100vw;
  height: 100vh;
}

/* Three.js inserts a <canvas> element into #scene-container at runtime;
   this makes sure it behaves as a block-level element with no gaps. */
#scene-container canvas {
  display: block;
}

/* Node actions, now docked to the bottom-center of the screen. */
/* Shown only while free-flight mode is active (see freeFlight.js). */
#flight-mode-indicator {
  position: fixed;
  bottom: 64px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  padding: 8px 16px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text-muted);
  font: 13px system-ui, sans-serif;
  white-space: nowrap;
}

#toolbar {
  position: fixed;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  gap: 8px;
}

#toolbar button {
  padding: 8px 14px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font: 13px system-ui, sans-serif;
  cursor: pointer;
}

#toolbar button:hover:not(:disabled) {
  background: var(--bg-hover);
}

/* While Pull/Push Children is actually held down — pointer or
   keyboard — rather than merely hoverable. Same accent used for the
   free-flight status line's active state, so "something is actively
   engaged" reads consistently across the UI. */
#toolbar button.active {
  color: var(--accent);
  border-color: var(--accent);
}

#toolbar button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Shared look for every full-screen modal gate — the sign-in/sign-up
   overlay and the account-management overlay both use this pair of
   classes (.modal-overlay for the full-screen backdrop, .modal-panel
   for the centered box itself, .modal-panel label/input for its
   fields) rather than each carrying its own near-identical copy. Only
   what's genuinely specific to one or the other (the auth form's
   larger h1, the account panel's multiple sections) stays on an id. */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100; /* above every panel, toolbar and the canvas itself */
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-deep);
}

/* Required, not redundant: the `display: flex` above is an author rule
   on a class, and any author `display` beats the browser's own
   `[hidden] { display: none }` — so without this, setting .hidden on
   an element with this class would do nothing and the gate would
   never come down. #flight-mode-indicator, the other element toggled
   this way, sets no display of its own and so needs no equivalent. */
.modal-overlay[hidden] {
  display: none;
}

.modal-panel {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: min(340px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
  overflow-y: auto;
  padding: 28px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
}

#auth-form h1 {
  margin: 0;
  color: var(--text);
  font: 600 22px system-ui, sans-serif;
}

#auth-intro {
  margin: -6px 0 2px;
  color: var(--text-muted);
  font: 13px system-ui, sans-serif;
}

.modal-panel label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  color: var(--text-muted);
  font: 13px system-ui, sans-serif;
}

/* Required, not redundant: the `display: flex` above is an author rule
   on a selector that also matches #auth-invite-code-row, and any
   author `display` beats the browser's own `[hidden] { display: none }`
   — the same trap .modal-overlay's own [hidden] rule exists to avoid.
   Without this, toggling [hidden] on the invite-code row in auth.js
   would do nothing and it would stay visible in sign-in mode too. */
.modal-panel label[hidden] {
  display: none;
}

.modal-panel input {
  padding: 9px 10px;
  background: var(--bg-deep);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font: 14px system-ui, sans-serif;
}

.modal-panel input:focus-visible {
  outline: 2px solid var(--accent-blue);
  outline-offset: 1px;
}

.modal-panel .modal-submit {
  padding: 10px 12px;
  background: var(--accent-blue);
  border: 1px solid var(--accent-blue);
  border-radius: 4px;
  color: var(--bg-deep);
  font: 600 14px system-ui, sans-serif;
  cursor: pointer;
}

.modal-panel .modal-submit:hover:not(:disabled) {
  filter: brightness(1.1);
}

.modal-panel .modal-submit:disabled {
  opacity: 0.6;
  cursor: progress; /* submitting: hashing a password takes a moment on a Pi */
}

/* Delete Account's own button — same shape as .modal-submit, but red,
   since it's the one destructive action anywhere in this modal. */
.modal-submit--danger {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--bg-deep);
}

/* Account management panel: three stacked forms (username, password,
   delete) inside the shared .modal-panel. Each form lays itself out
   the same way .modal-panel itself does its own direct children
   (label, button, status line) — forms aren't flex by default, so
   that has to be repeated here rather than inherited. */
#account-panel h1 {
  margin: 0;
  color: var(--text);
  font: 600 20px system-ui, sans-serif;
}

#account-close-btn {
  align-self: flex-end;
  margin-bottom: -6px; /* pulls the form content back up to where it'd sit without this row at all */
}

#account-panel form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Only between sections, not above the first one — the heading
   already separates that one from the close button above it. */
#account-panel form + form {
  padding-top: 14px;
  border-top: 1px solid var(--border-subtle);
}

#account-delete-intro {
  margin: 0;
  color: var(--text-muted);
  font: 13px system-ui, sans-serif;
}

/* A button that reads as a link — it switches the form between signing
   in and signing up rather than submitting anything, so it shouldn't
   carry the same visual weight as the action it sits under. */
.linklike {
  align-self: center;
  padding: 2px;
  background: none;
  border: none;
  color: var(--text-muted);
  font: 13px system-ui, sans-serif;
  text-decoration: underline;
  cursor: pointer;
}

.linklike:hover {
  color: var(--text);
}

/* Who's signed in, at the bottom of the save/load menu. */
#account-row {
  display: flex;
  flex-basis: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-top: 10px;
  border-top: 1px solid var(--border-subtle);
}

#account-name {
  color: var(--text-muted);
  font: 12px system-ui, sans-serif;
}

/* Shared look for every fixed "show/hide a panel" icon button — the
   hamburger menu button and the hierarchy panel's toggle both use
   this, so the two stay visually and behaviorally identical (full
   button hit area, same hover highlight) rather than each carrying
   its own hand-tuned copy of the same rules. Add to this class, not
   to a specific button's id, if a third one of these shows up later. */
.icon-toggle-btn {
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}

.icon-toggle-btn:hover {
  background: var(--bg-hover);
}

/* Hamburger button that reveals the save/load menu below it. Only
   positioning lives here — appearance comes from .icon-toggle-btn. */
#menu-toggle-btn {
  position: fixed;
  top: 16px;
  left: 16px;
  z-index: 11; /* above #mindspace-bar, so it stays clickable to close the menu too */
}

/* Current mindspace name, shown next to the hamburger button — kept in
   sync with the name field on an interval rather than wired to every
   place that field can change (typing, load, rename, import); see the
   comment in app.js for why. Its sibling span mirrors the save/load
   status line that otherwise only lives inside the collapsed menu (see
   #mindspace-status) — that menu is closed most of the time, so a
   message like "Couldn't save: ..." landing only there would go
   unseen; mirroring it here keeps it visible regardless. */
#current-filename {
  position: fixed;
  top: 16px;
  left: 60px;
  z-index: 10;
  height: 36px;
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: calc(100vw - 76px);
  color: var(--text-muted);
  font: 13px system-ui, sans-serif;
  pointer-events: none;
}

#current-filename-name {
  flex-shrink: 0;
}

#current-filename-status {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#current-filename-status:empty {
  display: none; /* no stray divider/gap when there's nothing to show */
}

#current-filename-status.status--error {
  color: var(--danger);
}

/* Free-flight mode status/controls hint, bottom-left. Content is set
   by app.js from FreeFlight's own control bindings (see freeFlight.js's
   getControlsHint), so a rebinding there is reflected here too. */
#flight-status {
  position: fixed;
  bottom: 16px;
  left: 16px;
  z-index: 10;
  padding: 8px 12px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text-muted);
  font: 12px system-ui, sans-serif;
}

#flight-status.active {
  color: var(--accent);
  border-color: var(--accent);
}

/* Save/load/rename/delete controls for whole mindspaces — hidden until
   the hamburger button is clicked. */
#mindspace-bar {
  display: none;
  position: fixed;
  top: 60px;
  left: 16px;
  z-index: 10;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  max-width: 380px;
  padding: 12px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 6px;
}

#mindspace-bar.open {
  display: flex;
}

#mindspace-bar select,
#mindspace-bar input {
  padding: 7px 10px;
  background: var(--bg-deep);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font: 13px system-ui, sans-serif;
}

#mindspace-bar button {
  padding: 7px 12px;
  background: var(--bg-deep);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font: 13px system-ui, sans-serif;
  cursor: pointer;
}

#mindspace-bar button:hover {
  background: var(--bg-hover);
}

#mindspace-status {
  flex-basis: 100%;
  font-size: 12px;
  color: var(--text-muted);
  min-height: 1.2em;
}

.autosave-control {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-muted);
}

.autosave-control select {
  padding: 5px 8px;
  background: var(--bg-deep);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font: 13px system-ui, sans-serif;
}

.autosave-control select:disabled {
  opacity: 0.5;
}

#mindspace-status.status--error {
  color: var(--danger);
}

/* Node name search, docked to the top-center. */
#search-bar {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  width: var(--panel-width);
}

#search-bar input {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 12px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font: 13px system-ui, sans-serif;
}

/* Floating name label above each node, rendered by CSS2DRenderer.
   `.node-label` is the outer element CSS2DRenderer positions directly
   every frame — left bare here so nothing we do ever conflicts with
   that. All visual styling, plus the distance-based scale applied by
   distanceFade.js, lives on `.node-label-text` instead, which
   CSS2DRenderer never touches.
   Text color is set per-node in JS (node.js's setBaseColor), matching
   the node's own hierarchy color — this stylesheet only handles the
   parts that don't vary per node. */
.node-label {
  pointer-events: auto; /* opts back into clicks (see interactions.js) — the container around it stays pass-through */
  cursor: pointer;
}

.node-label-text {
  display: inline-block;
  /* CSS2DRenderer already centers `.node-label` itself on the node's
     anchor point (its default `center` is (0.5, 0.5), i.e. it applies
     its own `translate(-50%, -50%)` — see three.js's CSS2DRenderer.js).
     This element sits flush inside that already-centered box, so it
     only needs the other half of a vertical shift to go from
     "vertically centered on the anchor" to "bottom edge at the
     anchor" — no horizontal shift at all, since the outer element
     already centered that axis. Re-applying a full -50% horizontally
     (as an earlier version of this rule did) double-counts the outer
     element's own centering and drags the label left by roughly half
     its own width — a fixed, scale-independent error that distanceFade.js's
     later per-node label width (via distance-based text scale) simply
     made obvious. */
  transform: translateY(-50%);
  transform-origin: center bottom;
  padding: 2px 8px;
  font: 13px system-ui, sans-serif;
  background: rgba(13, 15, 20, 0.65);
  border-radius: 4px;
  white-space: nowrap;
  /* Scale is only recomputed every couple of frames (see
     distanceFade.js), not continuously — this transition smooths that
     into a continuous-looking change instead of visible small steps. */
  transition: transform 0.15s ease-out;
}

/* Selection is shown via the label's own font weight, not a color
   override — the label's color is reserved for showing which branch
   the node belongs to, at all times, selected or not. The class lives
   on the outer element (see node.js's setSelected), so this reaches
   the actual text via a descendant selector. */
.node-label--selected .node-label-text {
  font-weight: 700;
}

/* Toggle button for the hierarchy panel below — same corner the
   panel itself docks to, same .icon-toggle-btn look and hover
   behavior as #menu-toggle-btn, only positioning differs. Sized up
   from the small collapse-arrow glyph this replaced, since it's now
   the button's whole (and only) content rather than one detail in a
   header row. */
#hierarchy-toggle-btn {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 11; /* above #hierarchy-panel, so it stays clickable to close it too */
  font-size: 22px;
}

/* Persistent tree view of all nodes, docked to the upper-right —
   hidden until #hierarchy-toggle-btn opens it, same pattern as
   #mindspace-bar/#menu-toggle-btn (see app.js: both use
   makeToggleable). Starts open by default (see the startToggled
   option passed there), unlike the save/load menu. */
#hierarchy-panel {
  display: none;
  position: fixed;
  top: 0;
  right: 0;
  width: var(--panel-width);
  /* Capped so a long node list scrolls internally rather than
     growing down into #info-panel. */
  max-height: 45vh;
  box-sizing: border-box;
  padding: 20px;
  background: var(--bg-panel);
  border-left: 1px solid var(--bg-hover);
  border-bottom: 1px solid var(--bg-hover);
  color: var(--text);
  font-family: system-ui, sans-serif;
  overflow-y: auto;
  z-index: 5;
}

#hierarchy-panel.open {
  display: block;
}

#hierarchy-panel-header {
  margin-bottom: 12px;
}

#hierarchy-panel h2 {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}

.hierarchy-item {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px 0;
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
}

.hierarchy-item:hover {
  color: var(--accent);
}

.hierarchy-item-label {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The Blender-style disclosure twisty. Always present and reserving
   its width even on a leaf node with nothing to toggle (see
   hierarchyPanel.js) — only .hierarchy-toggle--expandable ones get a
   glyph, a hover color, and a pointer cursor, so a leaf row's label
   still lines up with its siblings' without looking clickable itself. */
.hierarchy-toggle {
  flex-shrink: 0;
  width: 16px;
  text-align: center;
  font-size: 15px;
  line-height: 1;
  color: var(--text-muted);
  transition: transform 0.15s ease-out;
}

.hierarchy-toggle--expandable {
  cursor: pointer;
}

.hierarchy-toggle--expandable:hover {
  color: var(--text);
}

/* Points sideways when the branch is collapsed — same rotate-a-glyph
   technique as #hierarchy-toggle-btn above, for the same "this twisty
   currently points at hidden content" reading. */
.hierarchy-toggle--collapsed {
  transform: rotate(-90deg);
}

.hierarchy-item--selected {
  color: var(--accent);
  background: rgba(255, 179, 71, 0.1);
  border-radius: 3px;
}

.hierarchy-item {
  cursor: grab;
}

.hierarchy-item--dragging {
  opacity: 0.4;
}

.hierarchy-item--drop-target {
  background: rgba(90, 141, 238, 0.25);
  outline: 1px dashed var(--accent-blue);
  border-radius: 3px;
}

/* Type-to-search dropdown used for choosing a node's parent, and
   (same class, see index.html) for adding a linked node. */
.parent-search {
  position: relative;
}

/* Current links, as removable chips — see linkedNodesPanel.js. Same
   "tag list" shape as file-attachment-list's tiles, just pill-shaped
   text instead of square thumbnails, since a link is identified by
   the other node's name, not a picture. */
.linked-node-chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0 18px; /* the 18px matches #info-panel label's own margin-bottom, for even spacing before Notes */
}

.linked-node-chip {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px 4px 4px 10px;
  background: var(--bg-deep);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 12px;
}

.linked-node-chip-name {
  color: var(--text);
  cursor: pointer;
}

.linked-node-chip-name:hover {
  color: var(--accent);
}

.linked-node-chip-remove {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--text-muted);
  font-size: 13px;
  cursor: pointer;
}

.linked-node-chip-remove:hover {
  background: var(--danger);
  color: var(--bg-panel);
}

.suggestions {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 4px;
  max-height: 160px;
  overflow-y: auto;
  background: var(--bg-deep);
  border: 1px solid var(--border);
  border-radius: 4px;
  z-index: 20;
}

.suggestions.visible {
  display: block;
}

.suggestion-item {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 6px 8px;
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
  overflow: hidden; /* so a long combined label+hint truncates as a unit rather than overflowing the dropdown */
}

.suggestion-item:hover {
  background: var(--bg-hover);
}

.suggestion-item-label {
  flex-shrink: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* A small "Parent ← Grandparent" breadcrumb (see searchDropdown.js) —
   smaller and muted so it reads as secondary context for the label
   right before it, not a second result of its own. Shrinks/truncates
   before the label does, so it's the first thing to give way if the
   two together don't fit. */
.suggestion-item-hint {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--text-muted);
  font-size: 11px;
}

/* Wraps the file input, hint text, and the attachment list itself —
   drag-and-drop and paste-while-hovering (see fileAttachments.js) both
   target this element specifically, so it's sized and bordered like an
   actual drop target rather than a bare label+input pair. */
#node-file-dropzone {
  margin-top: 6px;
  padding: 12px;
  border: 2px dashed var(--border);
  border-radius: 6px;
  transition: border-color 0.15s ease-out, background-color 0.15s ease-out;
}

#node-file-hint {
  margin: 0 0 4px;
  font-size: 11px;
  color: var(--text-muted);
}

/* While a drag is over the dropzone (not while just hovering with an
   empty cursor — that's paste's own concern, tracked separately in JS
   and not given its own visual state since there's nothing to preview
   before pasting the way a drag payload can be seen coming). */
.file-dropzone--active {
  border-color: var(--accent-blue);
  background: rgba(90, 141, 238, 0.08);
}

.file-attachment-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
  /* Enlarges the actual drop target even before anything's attached
     yet — an empty list would otherwise collapse to zero height. */
  min-height: 88px;
}

.file-attachment {
  position: relative;
  width: 64px;
  height: 64px;
}

.file-attachment-thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--border);
  cursor: pointer; /* opens the larger preview — see imagePreview.js */
}

/* Marks whichever image is currently this node's icon (see
   node.js's setIcon, set from the preview overlay) — same accent used
   for "currently selected/active" everywhere else in the app. */
.file-attachment-thumb--icon {
  border: 2px solid var(--accent);
}

/* A non-image attachment: the same footprint as a thumbnail, but a
   generic file icon plus its name instead of a picture, and it's a
   real link (clicking it downloads the file — see fileAttachments.js)
   rather than a click handler on a plain tile. */
.file-attachment-doc {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 100%;
  height: 100%;
  padding: 4px;
  box-sizing: border-box;
  background: var(--bg-deep);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text-muted);
  text-decoration: none;
}

.file-attachment-doc:hover {
  color: var(--text);
  border-color: var(--text-muted);
}

.file-attachment-doc .btn-icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
}

.file-attachment-name {
  width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-align: center;
  font-size: 10px;
}

.file-attachment-remove {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 18px;
  height: 18px;
  line-height: 16px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-panel);
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
}

.file-attachment-remove:hover {
  background: var(--danger);
  color: var(--bg-panel);
}

/* Larger image preview (see imagePreview.js). Deliberately its own
   overlay rather than .modal-overlay: that one is a flat, fully opaque
   background (right for a sign-in/account gate, which really does
   need to block everything behind it) — this is a translucent,
   blurred scrim instead, so the 3D scene stays faintly visible around
   the image's edges rather than disappearing behind flat grey. Same
   [hidden]-override need as .modal-overlay, for the same reason: an
   author `display` on this id would otherwise beat the browser's own
   `[hidden] { display: none }`. */
#image-preview-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(13, 15, 20, 0.82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

#image-preview-overlay[hidden] {
  display: none;
}

/* Shrink-wraps to the image's own rendered box (line-height: 0 removes
   the small baseline gap a plain inline-block <img> otherwise leaves
   below itself) — this is what lets the crop box below be positioned
   in the same coordinate space as the actual visible image, in CSS
   pixels, rather than the much larger flex-centered overlay. Clips its
   own box-shadow-based dimming mask (see .image-preview-crop-box) to
   the image's own rounded corners instead of bleeding past them. */
#image-preview-stage {
  position: relative;
  display: inline-block;
  line-height: 0;
  border-radius: 4px;
  overflow: hidden;
}

#image-preview-img {
  display: block;
  max-width: 90vw;
  max-height: 90vh;
  /* Lifts the image visually off the blurred scene behind it — the
     same reasoning Discord's own viewer shadow serves. */
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
}

/* The crop selection — see imagePreview.js for the drag/resize logic
   that sets its left/top/width/height. The oversized spread box-shadow
   is a standard trick for dimming everything OUTSIDE an absolutely
   positioned box without a second overlay element: a shadow far larger
   than the viewport, in a color rather than blurred, just paints a
   solid tint everywhere beyond the box's own edge. */
#image-preview-crop-box {
  position: absolute;
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
  cursor: move;
  touch-action: none; /* this box handles its own drag; don't let the browser also try to scroll/zoom */
}

#image-preview-crop-handle {
  position: absolute;
  right: -7px;
  bottom: -7px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg-deep);
  cursor: nwse-resize;
  touch-action: none;
}

/* Set-icon/crop/close buttons — same .node-action-btn look used
   throughout the info panel (see index.html), grouped in one fixed
   corner rather than each positioning itself independently. */
#image-preview-actions {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 1; /* above the image, which centers itself in the same overlay */
  display: flex;
  gap: 8px;
}

#image-preview-status {
  position: fixed;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
}

.status-text {
  font-size: 12px;
  color: var(--text-muted);
  min-height: 1.2em;
  margin-top: 6px;
}

.status-text.status--error {
  color: var(--danger);
}

/* Selected-node detail panel — occupies the lower portion of the same
   right-hand column, below the hierarchy tree above it. */
/* Selected-node detail panel — anchored to the bottom-right,
   decoupled from the hierarchy panel above it. Since the hierarchy
   panel sizes itself to its content (see max-height above), the two
   would otherwise fight over vertical space; anchoring each from the
   opposite edge of the screen avoids that.
   Sized to its own content (max-height + overflow) rather than a
   fixed height — a fixed height here used to leave a block of empty
   space below whatever the panel actually had in it (most visibly
   when a node has no image attachments), instead of the panel simply
   ending where its content does. */
#info-panel {
  position: fixed;
  bottom: 0;
  right: 0;
  width: var(--panel-width);
  max-height: 70vh;
  overflow-y: auto; /* long notes / many attachments scroll instead of overflowing */
  box-sizing: border-box;
  padding: 24px 20px;
  background: var(--bg-panel);
  border-left: 1px solid var(--bg-hover);
  border-top: 1px solid var(--bg-hover);
  color: var(--text);
  font-family: system-ui, sans-serif;
  transform: translateX(100%);
  transition: transform 0.15s ease-out;
  z-index: 5;
}

#info-panel.visible {
  transform: translateX(0);
}

#info-panel h2 {
  margin: 0 0 16px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}

/* Shared look for every icon-only button in the info panel — Focus,
   Pull/Push Children, and Delete all use this one class rather than
   each having its own near-identical hover/active/disabled rules, so
   a future action button added here (or restyled) stays consistent
   for free. Sizing (flex vs. fixed-width) is layered on top per
   button group below, since that's the one thing that legitimately
   differs between "three buttons sharing a row" and "one on its own". */
.node-action-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  background: var(--bg-deep);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  cursor: pointer;
}

/* The `display: flex` above is an author rule, and any author
   `display` beats the browser's own `[hidden] { display: none }` —
   the same trap documented at every other toggled panel in this file.
   Needed here because the image preview swaps its action buttons via
   [hidden] (crop apply/cancel vs. set-icon/close — see
   imagePreview.js) rather than each managing its own visibility rule. */
.node-action-btn[hidden] {
  display: none;
}

.node-action-btn:hover:not(:disabled) {
  background: var(--bg-hover);
}

/* While Pull/Push Children is actually held down — pointer or
   keyboard — rather than merely hoverable. Same accent used
   elsewhere for "something is actively engaged". */
.node-action-btn.active {
  color: var(--accent);
  border-color: var(--accent);
}

.node-action-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Delete is destructive, unlike its siblings — a distinct hover color
   is the one deliberate visual difference from the shared style above. */
.node-action-btn--danger:hover:not(:disabled) {
  color: var(--danger);
  border-color: var(--danger);
}

/* Every icon in an action button is the same inline Lucide-style SVG
   shape (see index.html) — sized here once so every icon lines up at
   the same size regardless of how many path/circle elements it has. */
.btn-icon {
  width: 16px;
  height: 16px;
}

/* Focus/Pull/Push Children — moved here from the bottom toolbar since
   they all act on whatever node this panel is currently showing, and
   the panel is only ever visible when a node is selected anyway. */
#node-info-actions {
  display: flex;
  gap: 6px;
  margin-bottom: 20px;
}

/* flex: 1 1 0 — three equal-width buttons sharing one row, always,
   rather than the row wrapping one onto a line of its own the moment
   it doesn't quite fit. min-width: 0 is what lets a flex item actually
   shrink below its content's natural width. */
#node-info-actions .node-action-btn {
  flex: 1 1 0;
  min-width: 0;
}

/* Delete Node — moved here from the bottom toolbar too, but set apart
   in its own row at the very bottom of the panel (see index.html)
   rather than grouped with the actions above, and right-aligned since
   it's the one destructive action here. */
#node-info-footer {
  display: flex;
  justify-content: flex-end;
  margin-top: 8px;
}

#info-panel label {
  display: block;
  margin-bottom: 18px;
  font-size: 13px;
  color: var(--text-muted);
}

#info-panel input,
#info-panel textarea {
  display: block;
  width: 100%;
  margin-top: 6px;
  box-sizing: border-box;
  padding: 8px;
  background: var(--bg-deep);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font: inherit;
  resize: vertical;
}
