/* Nodal app shell — global chrome for the hosted SPA. */
html, body { margin: 0; height: 100%; }
#root { height: 100vh; }
/* Dynamic viewport height: on phones the browser's address bar shrinks and
   grows, and 100vh ignores that, leaving the bottom of the app unreachable. */
@supports (height: 100dvh) { #root { height: 100dvh; } }
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { overscroll-behavior-y: none; }

/* ── App frame ─────────────────────────────────────────────────────────
   Desktop: a fixed sidebar beside the content. Below 900px the sidebar
   leaves the flow and becomes an off-canvas drawer — at 248px wide it was
   swallowing most of a phone screen and squeezing the content into a
   column too narrow to read. */
.nodal-shell {
  display: flex;
  height: 100%;
  min-height: 560px;
  background: var(--bg);
  font-family: var(--font-sans);
  color: var(--text);
}
.nodal-sidebar {
  width: var(--sidebar-w);
  flex: 0 0 auto;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: 16px 12px;
  overflow-y: auto;
}
.nodal-main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
.nodal-topbar {
  height: var(--topbar-h);
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 24px;
  padding-right: max(24px, env(safe-area-inset-right));
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.nodal-content {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 28px 32px;
  padding-bottom: max(28px, env(safe-area-inset-bottom));
}
.nodal-scrim { display: none; }
.nodal-hamburger { display: none; }

@media (max-width: 900px) {
  .nodal-shell { min-height: 0; }
  .nodal-sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 60;
    width: min(84vw, 300px);
    padding-top: max(16px, env(safe-area-inset-top));
    padding-bottom: max(16px, env(safe-area-inset-bottom));
    transform: translateX(-100%);
    transition: transform 220ms cubic-bezier(0.2, 0, 0, 1);
    box-shadow: none;
    will-change: transform;
  }
  .nodal-sidebar.is-open { transform: translateX(0); box-shadow: 0 0 40px rgba(0, 0, 0, 0.22); }
  .nodal-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 50;
    background: rgba(12, 16, 24, 0.44);
    opacity: 0;
    pointer-events: none;
    transition: opacity 220ms ease;
  }
  .nodal-scrim.is-open { opacity: 1; pointer-events: auto; }
  .nodal-hamburger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex: 0 0 auto;
    margin-left: -8px;
    padding: 0;
    background: none;
    border: none;
    border-radius: var(--radius-md);
    color: var(--text);
    cursor: pointer;
  }
  .nodal-hamburger:active { background: var(--surface-sunken); }
  .nodal-topbar { padding: 0 14px; gap: 8px; padding-top: env(safe-area-inset-top); height: calc(var(--topbar-h) + env(safe-area-inset-top)); }
  .nodal-content { padding: 18px 14px; padding-bottom: max(24px, env(safe-area-inset-bottom)); }
  .nodal-hide-mobile { display: none !important; }
  /* Icon-only actions in a cramped bar; the icon still carries the meaning. */
  .nodal-btn-label { display: none; }
}

/* ── Responsive layout utilities ───────────────────────────────────────
   The screens are written with inline styles, so shared grid shapes live
   here as classes; that is the only way a media query can reach them. */
.nodal-cols-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
.nodal-cols-main { display: grid; grid-template-columns: 1.5fr 1fr; gap: 24px; align-items: start; }
.nodal-cols-side { display: grid; grid-template-columns: minmax(240px, 300px) minmax(0, 1fr); gap: 24px; align-items: start; }
.nodal-cols-form { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.nodal-datarow { display: grid; grid-template-columns: 1.3fr 1fr 84px 34px; gap: 8px; align-items: center; }
.nodal-datarow-3 { display: grid; grid-template-columns: 1.3fr 1fr 34px; gap: 8px; align-items: center; }

/* Notes is a writing surface, so it gets the room the other screens cap away
   — the editor was scrolling its own text while the page sat in a 1080px
   column with the rest of the window empty beside it. The editor takes two
   thirds of the split; the cap only stops lines growing unreadably long on a
   very wide monitor. */
.nodal-notes-page { max-width: 1600px; margin: 0 auto; }
.nodal-cols-notes { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 24px; align-items: start; }
/* Key data pairs sit two-up with a rule between them on desktop. */
.nodal-keydata { display: grid; grid-template-columns: 1fr 1fr; gap: 0; }
.nodal-detail-head { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 6px; }

@media (max-width: 820px) {
  .nodal-cols-2, .nodal-cols-main, .nodal-cols-side, .nodal-cols-form, .nodal-cols-notes { grid-template-columns: 1fr; }
  /* One value per row: the vertical rule and the paired padding only make
     sense while there are two columns to separate. */
  .nodal-keydata { grid-template-columns: 1fr; }
  .nodal-keydata > div { border-right: none !important; padding-left: 0 !important; padding-right: 0 !important; }
  /* Title and actions stack rather than fighting over one line. */
  .nodal-detail-head { flex-wrap: wrap; }
  .nodal-detail-head > div:last-child { width: 100%; }
  /* Two per row keeps field and value paired; the unit and the remove
     button fall to the line below rather than being crushed. */
  .nodal-datarow, .nodal-datarow-3 { grid-template-columns: 1fr 1fr; row-gap: 6px; }
  .nodal-datarow-head { display: none !important; }
}

/* Dashboard hero: icon, copy and the call to action share a row on desktop.
   Below 700px the copy would be squeezed to one word per line, so it stacks
   and the button goes full width. */
.nodal-hero { display: flex; align-items: center; gap: 20px; }
@media (max-width: 700px) {
  .nodal-hero { flex-direction: column; align-items: flex-start; gap: 14px; }
  /* !important because the design-system Button sets width inline
     (width: fullWidth ? '100%' : 'auto'), which a stylesheet cannot outrank. */
  .nodal-hero > button { width: 100% !important; justify-content: center; }
}

/* Study rows: one line on desktop. On a phone the title had to share the
   line with the status pill and the date, truncating it to a letter or two,
   so the pill and date drop to a second line and the title gets the width. */
@media (max-width: 620px) {
  .nodal-study-row { flex-wrap: wrap; row-gap: 9px; }
  .nodal-study-main { flex: 1 1 calc(100% - 52px) !important; }
}

/* Touch targets: a 26px hit area is fine with a mouse and a miss with a
   thumb. Only widen where it does not disturb the desktop layout. */
@media (hover: none) and (pointer: coarse) {
  .nodal-ref-folder-del { opacity: 1; width: 34px; height: 34px; }
  .nodal-note-toolbar button { min-width: 36px; height: 36px; }
}
a { color: var(--primary); }
a:hover { color: var(--accent-hover); }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: 99px;
  border: 3px solid var(--surface);
}

/* ── Notes rich editor ─────────────────────────────────────────────── */
.nodal-note-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
  padding: 6px;
  background: var(--surface-sunken);
  border: 1px solid var(--field-border);
  border-bottom: none;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}
.nodal-note-toolbar button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 30px;
  padding: 0 8px;
  gap: 6px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background var(--dur), color var(--dur);
}
.nodal-note-toolbar button:hover { background: var(--surface); color: var(--text); }
.nodal-note-toolbar .nodal-note-sep {
  width: 1px;
  align-self: stretch;
  background: var(--border);
  margin: 4px 5px;
}

.nodal-note-editor {
  max-height: 640px;
  overflow-y: auto;
  /* Prose must wrap, never scroll sideways. Without this a single long token
     — a URL, a part number, a pasted path — widens the content and the whole
     editor scrolls, clipping every line mid-word at both edges. */
  overflow-x: hidden;
  overflow-wrap: anywhere;
  word-break: break-word;
  padding: 14px 16px;
  background: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: 1.7;
  color: var(--text);
  outline: none;
  resize: vertical;
  transition: border-color var(--dur), box-shadow var(--dur);
}
.nodal-note-editor:focus {
  border-color: var(--border-focus);
  box-shadow: var(--shadow-focus);
}
.nodal-note-editor.is-dragover {
  border-color: var(--accent);
  box-shadow: var(--shadow-focus);
}
.nodal-note-editor[data-empty="true"]::before {
  content: attr(data-placeholder);
  color: var(--text-subtle);
  pointer-events: none;
}
.nodal-note-editor img {
  max-width: 100%;
  height: auto;
  border-radius: 8px;
  margin: 8px 0;
  display: block;
}
.nodal-note-editor ul,
.nodal-note-editor ol { padding-left: 24px; margin: 8px 0; }
/* Multilevel numbering, the way a word processor does it: each level down
   changes the marker on its own, so Tab inside a list produces 1. → a. → i.
   and • → ○ → ▪ without anything being chosen. Picking a style from the
   toolbar writes list-style-type inline, which outranks all of this. */
.nodal-note-editor ol { list-style-type: decimal; }
.nodal-note-editor ol ol { list-style-type: lower-alpha; }
.nodal-note-editor ol ol ol { list-style-type: lower-roman; }
.nodal-note-editor ol ol ol ol { list-style-type: upper-alpha; }
.nodal-note-editor ol ol ol ol ol { list-style-type: upper-roman; }
.nodal-note-editor ul { list-style-type: disc; }
.nodal-note-editor ul ul { list-style-type: circle; }
.nodal-note-editor ul ul ul { list-style-type: square; }
/* A nested list is a continuation of its parent item, not a new paragraph. */
.nodal-note-editor li > ul,
.nodal-note-editor li > ol { margin: 2px 0; }

/* A real separator, in place of a hand-typed row of dashes: full width, and
   it stays put when the text around it is edited. */
.nodal-note-editor hr {
  border: 0;
  border-top: 1px solid var(--border-strong);
  height: 0;
  margin: 18px 0;
}

/* Colour and highlight pickers: a swatch grid hanging off its toolbar button. */
.nodal-swatch-wrap { position: relative; display: inline-flex; }
.nodal-swatches {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 40;
  display: grid;
  grid-template-columns: repeat(4, 30px);
  gap: 5px;
  padding: 7px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg, 0 10px 30px rgba(0, 0, 0, 0.14));
}
.nodal-swatch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: transform var(--dur), border-color var(--dur);
}
.nodal-swatch:hover { transform: scale(1.08); border-color: var(--border-strong); }
.nodal-swatch.is-none { background: var(--surface-sunken); color: var(--text-muted); }

/* Note catalogue: tiles that reflow to the width available, so a long list
   stays two-dimensional instead of becoming an endless column. */
.nodal-catalogue {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 12px;
  align-items: stretch;
}
.nodal-cat-act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background var(--dur), color var(--dur);
}
.nodal-cat-act:hover { background: var(--surface-sunken); color: var(--text); }

/* Marker-style pickers sit in the toolbar as compact selects. */
.nodal-list-style {
  height: 30px;
  max-width: 116px;
  padding: 0 4px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  cursor: pointer;
}
.nodal-list-style:hover { background: var(--surface); color: var(--text); }
.nodal-list-style:focus-visible { border-color: var(--border-focus); outline: none; }
/* The caret is inside a list of this kind, so the picker is showing a real
   setting rather than a placeholder — say so, the way an active B or I does. */
.nodal-list-style.is-set {
  background: var(--primary-soft);
  color: var(--primary-soft-text);
  border-color: var(--border-strong);
  font-weight: var(--fw-semibold);
}
/* Genuinely wide content — a pasted table, a code block — scrolls inside its
   own box rather than dragging the whole editor sideways with it.
   The prose break rules above must NOT reach inside: applied to a table they
   let the browser shrink every column to one character rather than overflow,
   which shatters the table instead of scrolling it. */
.nodal-note-editor table {
  display: block;
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
}
.nodal-note-editor table,
.nodal-note-editor pre { overflow-wrap: normal; word-break: normal; }
.nodal-note-editor pre { max-width: 100%; overflow-x: auto; white-space: pre; }
.nodal-note-editor iframe,
.nodal-note-editor video { max-width: 100%; }
.nodal-note-editor p { margin: 0 0 8px; }
.nodal-note-editor:first-child { margin-top: 0; }

/* ── Notes: embedded HTML views ─────────────────────────────────────── */
.nodal-embed {
  margin: 12px 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  overflow: hidden;
  user-select: none;
}
.nodal-embed-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 6px 5px 10px;
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
}
.nodal-embed-chip {
  flex: 0 0 auto;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  background: var(--primary-soft);
  color: var(--primary-soft-text);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
}
.nodal-embed-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-muted);
}
.nodal-embed-bar button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: 14px;
  cursor: pointer;
  transition: background var(--dur), color var(--dur);
}
.nodal-embed-bar button:hover { background: var(--surface); color: var(--text); }
.nodal-embed-bar button.is-on {
  background: var(--primary-soft);
  color: var(--primary-soft-text);
  border-color: var(--border-strong);
}
/* Resizable box: the view fills it, so dragging the bottom edge sets the
   height of any page that cannot report its own. */
.nodal-embed-frame {
  position: relative;
  background: #fff;
  overflow: hidden;
  resize: vertical;
  min-height: 120px;
}
.nodal-embed-frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  background: #fff;
}
/* Locked by default: clicks select the block instead of reaching the view.
   The bar's cursor button lifts the shield when you want to interact. The
   bottom strip stays clear so the resize grip is always grabbable. */
.nodal-embed-frame::after {
  content: "";
  position: absolute;
  inset: 0 0 14px 0;
}
.nodal-embed[data-interactive="1"] .nodal-embed-frame::after { display: none; }

/* ── Reference library: folder rows ─────────────────────────────────── */
.nodal-ref-folder {
  display: flex;
  align-items: center;
  gap: 2px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  transition: background var(--dur), border-color var(--dur);
}
.nodal-ref-folder:hover { background: var(--surface-sunken); }
.nodal-ref-folder.is-active {
  background: var(--primary-soft);
  border-color: var(--border-strong);
}
.nodal-ref-folder-open {
  display: flex;
  align-items: center;
  gap: 9px;
  flex: 1;
  min-width: 0;
  padding: 8px 6px 8px 10px;
  background: none;
  border: none;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
}
.nodal-ref-folder-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nodal-ref-folder.is-active .nodal-ref-folder-name { font-weight: 600; }
.nodal-ref-folder-count {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.nodal-ref-folder-del {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin-right: 5px;
  flex: 0 0 auto;
  padding: 0;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text-subtle);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur), color var(--dur);
}
.nodal-ref-folder:hover .nodal-ref-folder-del,
.nodal-ref-folder-del:focus-visible { opacity: 1; }
.nodal-ref-folder-del:hover { color: var(--danger-text); }
