/* Graph tab: a compact node library beside a lane-bound connection canvas. */

/* Both columns share a useful baseline height and grow with their content.
   The page remains the vertical scroller. */
.gr-shell {
  --gr-panel-min-height: 820px;
  display: grid;
  grid-template-columns: minmax(180px, 14%) minmax(0, 1fr);
  gap: var(--panel-gap);
  min-height: var(--gr-panel-min-height);
  margin-bottom: -40px;
}

.gr-shell.is-busy { cursor: wait; }
.gr-shell.is-busy button, .gr-shell.is-busy input, .gr-shell.is-busy select { pointer-events: none; }

/* Node library */
.gr-drawer {
  position: relative;
  z-index: 5;
  min-width: 0;
  min-height: var(--gr-panel-min-height);
  display: flex;
  flex-direction: column;
  padding: 14px;
  border: 1px solid var(--line);
  background: var(--paper-raised);
  box-shadow: 0 9px 28px rgba(34, 31, 25, .045);
}

.gr-drawer-head { min-height: 34px; display: flex; align-items: center; margin-bottom: 10px; }
.gr-panel-title { flex: none; }

/* The active underline follows the label; tabs span the drawer without
   horizontal button padding. */
.gr-drawer-tabs { display: flex; justify-content: space-between; }
.gr-drawer-tabs button {
  position: relative;
  min-width: 0;
  padding: 9px 10px 10px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: var(--text-label);
  text-align: left;
}
.gr-drawer-tabs button::after { content: ""; position: absolute; right: 0; bottom: 0; left: 0; height: 2px; background: var(--blue); transform: scaleX(0); transition: transform .12s; }
.gr-drawer-tabs button:hover, .gr-drawer-tabs button.is-active { color: var(--ink); }
.gr-drawer-tabs button.is-active::after { transform: scaleX(1); }
.gr-search { display: block; margin: 12px 0 9px; }
.gr-search input { width: 100%; }

/* Rows use the shared .tree-row-line component (core/base.css) — see
   /style#folder-trees. .gr-source-row/.gr-source-tree carry only what's
   specific to being a drag source: the grab cursor, the dragging fade, the
   placed-in-graph tint and status badge. */
.gr-source-list { padding-top: 4px; }
.gr-source-list > .tree-row-line, .gr-source-list > .gr-source-tree { margin-top: 4px; }
.gr-source-row { cursor: grab; }
.gr-source-row:active { cursor: grabbing; }
.gr-source-row[draggable="false"] { cursor: default; }
.gr-source-row.is-dragging { opacity: .3; }
.gr-source-row.is-placed .gr-source-copy strong { color: var(--blue); }
.gr-source-copy { min-width: 0; display: grid; gap: 3px; }
.gr-source-copy strong { overflow: hidden; font: 400 var(--text-body)/1.3 var(--sans); text-overflow: ellipsis; white-space: nowrap; }
.gr-source-copy small { overflow: hidden; color: var(--muted); font-size: var(--text-meta); text-overflow: ellipsis; white-space: nowrap; }
.gr-source-status { flex: none; color: var(--blue); font-size: var(--text-meta); font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
.gr-source-status.is-icon { display: grid; place-items: center; }
.gr-source-status.is-icon svg { width: 11px; height: 11px; stroke-width: 2.2; }
.gr-source-tree > summary { display: block; list-style: none; cursor: pointer; }
.gr-source-tree > summary::-webkit-details-marker { display: none; }
.gr-source-tree[open] > summary .tree-row-toggle { transform: rotate(90deg); }
.gr-source-tree[data-empty] > summary .tree-row-toggle { visibility: hidden; }
.gr-source-children .tree-row-line { margin-top: 4px; }
.gr-drawer-empty { margin: 0; padding: 22px 4px; color: var(--muted); font-size: 10px; text-align: center; }

/* Graph canvas */
.gr-graph-head { min-height: 34px; display: flex; align-items: center; gap: 12px; margin-bottom: 18px; pointer-events: auto; }
.gr-readonly { color: var(--muted); font-size: 9px; letter-spacing: .1em; text-transform: uppercase; }

.gr-new-lane { min-height: 30px; padding: 1px 10px 0; border: 1px solid var(--line); border-radius: 2px; background: var(--paper-raised); color: var(--muted); cursor: pointer; font-size: 11px; }
.gr-new-lane:hover { border-color: var(--ink); color: var(--ink); }
.gr-connect-prompt { min-width: 0; margin-left: auto; color: var(--muted); font-size: 10px; }
.gr-connect-prompt.is-active { color: var(--blue); }

/* The wrap holds the two edge-fade overlays (below), pinned to the visible
   left/right edges regardless of how far .gr-stage itself has scrolled. */
.gr-stage-wrap { position: relative; min-width: 0; min-height: var(--gr-panel-min-height); }
/* overscroll-behavior isn't set here (was: contain) — with the page itself
   now the vertical scroller (.gr-stage no longer scrolls vertically), a
   mouse wheel over the lanes needs its scroll to chain through to the
   page; contain was built for when this element owned that scroll too,
   and stopped it dead the moment .gr-stage had no scroll room of its own
   to consume it into — which, for the y axis, is now always. */
.gr-stage { min-width: 0; min-height: var(--gr-panel-min-height); height: 100%; overflow-x: auto; overflow-y: visible; }
/* Vertical scroll is gone (the stage grows to fit every lane's full
   height, see .gr-canvas/.gr-lanes below) — horizontal stays, there can
   still be more lanes than fit the width, hence the fade cues. */
.gr-stage-edge { position: absolute; top: 0; bottom: 0; z-index: 6; width: 36px; pointer-events: none; opacity: 0; transition: opacity .15s; }
.gr-stage-edge-left { left: 0; background: linear-gradient(to right, var(--paper), transparent); }
.gr-stage-edge-right { right: 0; background: linear-gradient(to left, var(--paper), transparent); }
.gr-stage-wrap.can-scroll-left .gr-stage-edge-left,
.gr-stage-wrap.can-scroll-right .gr-stage-edge-right { opacity: 1; }
/* Fixed to the viewport and positioned by JS (see updateStageEdgeArrows),
   not centered on the lanes column's own (possibly much taller than the
   screen) full height — so it tracks the page's scroll position and stays
   in view rather than sitting wherever that column's vertical middle
   happens to fall, on-screen or not. */
.gr-stage-edge-arrow { position: fixed; z-index: 6; display: grid; place-items: center; width: 32px; height: 32px; border: 1px solid var(--line); border-radius: 50%; background: var(--paper-raised); color: var(--muted); box-shadow: 0 4px 13px rgba(34, 31, 25, .1); opacity: 0; pointer-events: none; transition: opacity .15s; }
.gr-stage-edge-left .gr-stage-edge-arrow { transform: rotate(180deg); }
.gr-stage-wrap.can-scroll-left.is-in-view .gr-stage-edge-left .gr-stage-edge-arrow,
.gr-stage-wrap.can-scroll-right.is-in-view .gr-stage-edge-right .gr-stage-edge-arrow { opacity: 1; }
.gr-canvas {
  position: relative;
  width: max-content;
  min-width: 100%;
  min-height: 100%;
  padding: 28px;
  border: 1px solid var(--line);
  background-image:
    linear-gradient(to right, color-mix(in srgb, var(--line) 32%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in srgb, var(--line) 32%, transparent) 1px, transparent 1px);
  background-size: 24px 24px;
}
/* align-items: start (not stretch) — each lane's background is its own
   content's height, not every lane matched to the tallest one in the row.
   The rest of the page growing to fit content (rather than capping and
   scrolling internally) is what keeps a tall lane's own background from
   being cut short now, not row-stretching every lane to match it. */
.gr-lanes { position: relative; width: max-content; min-width: 100%; display: grid; grid-auto-flow: column; grid-auto-columns: 286px; align-items: start; gap: 76px; pointer-events: none; }
.gr-lane { display: flex; flex-direction: column; border: 1px solid var(--line); background: color-mix(in srgb, var(--paper-raised) 87%, transparent); box-shadow: 0 9px 28px rgba(34, 31, 25, .035); pointer-events: auto; transition: border-color .12s, background .12s, box-shadow .12s; }
.gr-lane.is-over { border-color: var(--blue); background: color-mix(in srgb, var(--blue) 6%, var(--paper-raised)); box-shadow: 0 0 0 3px rgba(24, 79, 209, .13); }
.gr-lane > header { min-height: 68px; display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 13px 14px 12px; border-bottom: 1px solid var(--line); }
.gr-lane > header h2 { max-width: 220px; margin: 0; overflow-wrap: anywhere; font: 400 20px/1.08 var(--serif); }
.gr-lane-title { max-width: 220px; display: block; margin: 0; padding: 0; border: 0; background: transparent; color: var(--ink); cursor: text; overflow-wrap: anywhere; font: 400 20px/1.08 var(--serif); text-align: left; }
.gr-lane-title:hover { color: var(--blue); }
.gr-lane-title-input { width: 220px; margin: 0; padding: 2px 0; border: 0; border-bottom: 1px solid var(--blue); outline: 0; background: transparent; color: var(--ink); font: 400 20px/1.08 var(--serif); }
.gr-lane-meta { display: flex; align-items: center; gap: 5px; }
.gr-lane > header b { color: var(--muted); font-size: 9px; font-weight: 400; font-variant-numeric: tabular-nums; }
.gr-lane-body { flex: 1; display: grid; align-content: start; gap: 13px; padding: 13px; }
.gr-lane-body > p { margin: 78px 0 0; color: var(--muted-line); font-size: 9px; text-align: center; }
.is-dragging-node .gr-lane:not(.is-over) { border-style: dashed; }
.is-dragging-lane .gr-lane:not(.is-over) { border-style: dashed; }
.gr-lane > header .gr-lane-grip { flex: none; margin: 0; color: var(--muted-line); font-size: 13px; font-weight: 400; letter-spacing: 0; text-transform: none; line-height: 1; cursor: grab; }
.gr-lane > header .gr-lane-grip:hover { color: var(--muted); }
.gr-lane > header .gr-lane-grip:active { cursor: grabbing; }
.gr-lane.is-dragging { opacity: .35; }

.gr-first-lane { width: max-content; max-width: calc(100vw - 320px); align-self: start; margin: 48px 0 0 18px; padding: 10px 12px; border: 1px solid var(--line); background: var(--paper-raised); }
.gr-first-lane p { margin: 0; color: var(--muted); font-size: 10px; }

/* Lane nodes and scoped collection anchors */
.gr-node { --gr-node-color: var(--blue); position: relative; z-index: 3; min-width: 0; padding: 13px 15px 12px; border: 1px solid #cec8bd; border-radius: 2px; background: var(--paper-raised); cursor: grab; box-shadow: 0 4px 13px rgba(34, 31, 25, .055); transition: border-color .12s, opacity .12s, transform .12s; }
.gr-node-asset { --gr-node-color: #c86548; }
.gr-node-reference { --gr-node-color: #74558f; padding: 9px 12px; }
.gr-node:hover { border-color: var(--gr-node-color); transform: translateY(-1px); }
.gr-node:active { cursor: grabbing; }
.gr-node.is-dragging { opacity: .3; }
/* No eyebrow label anymore, so nothing reserves a row above the title —
   the remove button floats in the corner and the title wraps around it,
   rather than the title sitting below an emptied-out header row. */
.gr-node-remove { float: right; margin: 0 0 4px 8px; }
.gr-node h3 { margin: 0; overflow-wrap: anywhere; font: 400 17px/1.18 var(--serif); }
.gr-node > p { margin: 4px 0 0; overflow: hidden; color: var(--muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
/* Reference card: who wrote it reads first, the title follows small and
   italic like a citation — and the whole card runs shorter than a
   collection/asset/experiment card. */
.gr-node > .gr-node-authors { margin: 0; overflow-wrap: anywhere; white-space: normal; color: var(--ink); font: 400 12px/1.3 var(--serif); }
.gr-node > .gr-node-ref-title { margin: 3px 0 0; overflow-wrap: anywhere; white-space: normal; color: var(--muted); font: italic 400 10px/1.35 var(--serif); }
.gr-node-reference .gr-port { top: 40px; }
.gr-node-links { display: block; clear: both; margin-top: 9px; color: var(--muted); font-size: 8px; font-variant-numeric: tabular-nums; }

.gr-scopes { clear: both; margin: 11px -4px 0; border-top: 1px solid var(--line); }
.gr-scopes > summary { padding: 7px 4px 5px; color: var(--muted); cursor: pointer; font-size: 8px; list-style: none; }
.gr-scopes > summary::-webkit-details-marker { display: none; }
.gr-scopes > summary::before { content: "›"; display: inline-block; width: 10px; transition: transform .12s; }
.gr-scopes[open] > summary::before { transform: rotate(90deg); }
.gr-scope-list { display: grid; gap: 3px; padding: 3px 0 0; }
.gr-scope { position: relative; display: grid; grid-template-columns: 14px minmax(0, 1fr) auto; align-items: center; gap: 6px; min-height: 27px; padding: 4px 7px; border: 1px solid color-mix(in srgb, var(--line) 78%, transparent); background: var(--paper); }
.gr-scope-2 { margin-left: 14px; background: color-mix(in srgb, var(--paper) 55%, transparent); }
.gr-scope-mark { color: var(--blue); font: 400 9px/1 var(--serif); }
/* A bucket with batches: its own <details>, collapsible independent of the
   outer gr-scopes list and of every other bucket. */
summary.gr-scope { cursor: pointer; list-style: none; }
.gr-scope-toggle { display: grid; place-items: center; transition: transform .12s; }
.gr-scope-bucket[open] > summary .gr-scope-toggle { transform: rotate(90deg); }
.gr-scope-batches { display: grid; gap: 3px; margin-top: 3px; }
.gr-scope-name { min-width: 0; overflow: hidden; font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
.gr-scope-kind { color: var(--muted); font-size: 7px; text-transform: uppercase; }

.gr-port { position: absolute; z-index: 5; top: 48px; width: 15px; height: 15px; padding: 0; border: 2px solid var(--paper-raised); border-radius: 50%; background: var(--gr-node-color, var(--blue)); box-shadow: 0 0 0 1px var(--gr-node-color, var(--blue)); cursor: crosshair; touch-action: none; transition: transform .12s, box-shadow .12s; }
.gr-port:hover:not(:disabled), .gr-port.is-active, .gr-port.is-snap { transform: scale(1.28); box-shadow: 0 0 0 4px color-mix(in srgb, var(--gr-node-color, var(--blue)) 22%, transparent); }
.gr-port:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
.gr-port:disabled { cursor: default; opacity: .35; }
.gr-port-input { left: -9px; }
.gr-port-output { right: -9px; }
.gr-port.is-compact { top: 50%; width: 11px; height: 11px; border-width: 2px; transform: translateY(-50%); }
.gr-port.is-compact:hover { transform: translateY(-50%) scale(1.25); }
.gr-scope .gr-port-input { left: -7px; }
.gr-scope .gr-port-output { right: -7px; }
.gr-shell.is-connecting .gr-port-input:not(:disabled) { animation: gr-port-pulse .72s ease-in-out infinite alternate; }

/* Authored links sit above node cards, so a line into an input port isn't
   hidden under the node it terminates at; ports (z-index 5) stay clickable
   above that. */
.gr-edges { position: absolute; z-index: 4; top: 0; left: 0; overflow: visible; pointer-events: none; }
.gr-edges marker path { fill: var(--blue); }
.gr-edge-halo { fill: none; stroke: var(--paper-raised); stroke-width: 7; stroke-linecap: round; stroke-linejoin: round; opacity: .92; }
.gr-edge-line { fill: none; stroke: var(--blue); stroke-width: 2; stroke-linecap: round; opacity: .78; }
/* Hand-rotated per edge — see arrowAngleDeg in index.js for why this isn't
   a marker-end — so it needs its own fill rule alongside the line's stroke. */
.gr-edge-arrow { fill: var(--blue); }
.gr-edge-reference .gr-edge-line { stroke: #74558f; }
.gr-edge-reference .gr-edge-arrow { fill: #74558f; }
.gr-edge-evidence .gr-edge-line { stroke: #2f7458; stroke-dasharray: 5 4; }
.gr-edge-evidence .gr-edge-arrow { fill: #2f7458; }
.gr-edge-conclusion .gr-edge-line { stroke: #b07a21; }
.gr-edge-conclusion .gr-edge-arrow { fill: #b07a21; }
.gr-edge-produces .gr-edge-line { stroke: #c86548; }
.gr-edge-produces .gr-edge-arrow { fill: #c86548; }
.gr-edge-hit { fill: none; stroke: transparent; stroke-width: 16; pointer-events: stroke; cursor: pointer; }
/* Shown when several edges collapse onto one route (a scoped endpoint hidden behind a closed bucket/batch list). */
.gr-edge-count { fill: var(--muted); font: 700 9px/1 var(--sans); text-anchor: middle; paint-order: stroke; stroke: var(--paper); stroke-width: 3px; }
.gr-edge:hover .gr-edge-line { stroke-width: 3; opacity: 1; }
/* Hovering (or a click-pinned) node/scope dims every other edge. The halo
   is a separate layer, not nested in .gr-edge (see drawEdges' comment on
   why), so it needs its own rule — otherwise a dimmed edge's pale,
   still-full-opacity halo is *all* that's left visible, which reads as
   "more white" rather than "more transparent". Both go low enough to
   read as gone, not just faded. */
.gr-edges.is-focusing .gr-edge:not(.is-focus-match) { opacity: .06; }
.gr-edges.is-focusing .gr-edge-halo:not(.is-focus-match) { opacity: .06; }
.gr-edges-focus { z-index: 4; }
.gr-edges-focus .gr-edge-halo { stroke-width: 9; opacity: 1; }
.gr-edges-focus .gr-edge-line { stroke-width: 3; opacity: 1; filter: drop-shadow(0 1px 1px rgba(34, 31, 25, .2)); }
.gr-edges-focus .gr-edge-arrow { filter: drop-shadow(0 1px 1px rgba(34, 31, 25, .2)); }
.gr-link-live { fill: none; stroke: var(--blue); stroke-width: 2.5; stroke-dasharray: 6 4; pointer-events: none; }

/* Connection editor */
.gr-edge-dialog { width: min(620px, calc(100% - 28px)); }
.gr-edge-dialog [hidden] { display: none; }
.gr-edge-form { padding: 30px 32px 32px; }
.gr-edge-form .dialog-head { margin-bottom: 18px; }
.gr-edge-form .dialog-head h2 { font-size: 34px; }
.gr-edge-form .eyebrow { margin-bottom: 7px; }
.gr-edge-route { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 10px; margin-bottom: 24px; padding: 13px; border-block: 1px solid var(--line); color: var(--muted); font: 400 13px/1.3 var(--serif); }
.gr-edge-route span { min-width: 0; overflow-wrap: anywhere; }
.gr-edge-route span:last-child { text-align: right; }
.gr-edge-route b { color: var(--blue); font-weight: 400; }
.gr-dialog-status { min-height: 16px; margin: 10px 0 0; color: var(--red); font-size: 10px; }

/* Graph action confirmations stay separate from the connection editor. */
.gr-action-dialog { width: min(var(--dialog-sm), calc(100% - 28px)); }
.gr-action-dialog::backdrop { background: rgba(23, 23, 19, .46); }
.gr-action-form { position: relative; display: grid; gap: 22px; padding: 28px; }
.gr-action-close { position: absolute; top: 12px; right: 12px; }
.gr-action-form header { padding-right: 28px; }
.gr-action-form h2 { margin: 0; font: 400 29px/1.05 var(--serif); letter-spacing: -.015em; }
.gr-action-form header p { margin: 9px 0 0; color: var(--muted); font-size: 12px; line-height: 1.5; }
.gr-action-buttons { display: flex; justify-content: flex-end; gap: 9px; }
.gr-action-dialog .gr-action-confirm { border-color: var(--red); background: var(--red); }
.gr-action-dialog .gr-action-confirm:hover { border-color: #982f25; background: #982f25; }

@keyframes gr-port-pulse { to { box-shadow: 0 0 0 5px rgba(24, 79, 209, .18); } }

/* Sideways on a phone the viewport is ~350px tall, so the canvas takes the
   whole screen rather than the ~160px left under the masthead and tabs. The
   organization tab does the same with its plot. */
@media (max-height: 520px) and (orientation: landscape) {
  .gr-shell {
    --gr-panel-min-height: 0;
    position: fixed;
    inset: 0;
    z-index: 100;
    height: 100dvh;
    min-height: 0;
    margin: 0;
    padding: 10px;
    gap: 10px;
    background: var(--paper);
  }
  /* .gr-shell is fixed/fullscreen here, so unlike the default (page-scroll)
     layout there's no outer scroll to fall back on — restore the
     cap-and-scroll the base rules used to provide everywhere. */
  /* contain, unlike the base rule: .gr-stage really does own the vertical
     scroll here, and this keeps a bounce at its edge from also rubber-
     banding the page underneath. */
  .gr-stage { overflow-y: auto; overscroll-behavior: contain; }
}

@media (max-width: 700px) {
  /* A phone reads the graph, it does not build it. The node library is the
     tool for adding things, so it goes, and with it every control that writes:
     new lanes, the remove buttons, the connection ports and the prompt that
     drives them. What is left is the canvas, which pans and scrolls by touch
     on its own. */
  .gr-shell {
    --gr-panel-min-height: 0;
    height: calc(100dvh - 172px);
    min-height: 420px;
    grid-template-columns: minmax(0, 1fr);
    margin-bottom: 0;
  }
  .gr-drawer,
  .gr-new-lane,
  .gr-lane-remove,
  .gr-node-remove,
  .gr-connect-prompt { display: none; }
  /* The ports stay in the layout: drawEdges measures their rects to place
     every line, and display:none makes those rects zero, which collapses the
     whole edge layer. They are made inert and unobtrusive instead — still the
     anchor points, no longer handles. */
  .gr-port {
    width: 9px;
    height: 9px;
    top: 42px;
    border-width: 1px;
    cursor: default;
    pointer-events: none;
  }
  /* Re-centred on the lane edge for the smaller dot, so edges still meet the
     node rather than starting a few pixels outside it. */
  .gr-port-input { left: -5px; }
  .gr-port-output { right: -5px; }
  /* Renaming a lane is a write too; the title stays but stops being a button. */
  .gr-lane-title { cursor: default; pointer-events: none; }

  /* .gr-shell is capped to the viewport here too (below) — same reasoning
     as the landscape block above: restore cap-and-scroll for this row. */
  .gr-stage-wrap { height: 100%; }
  .gr-graph-head { min-height: 0; margin-bottom: 8px; }
  .gr-stage { min-height: 0; overflow-y: auto; overscroll-behavior: contain; }

  /* Narrower lanes and a shorter gap, so more than one is on screen at a time
     without the edges between them crossing awkwardly. */
  .gr-lanes { grid-auto-columns: 224px; gap: 44px; }
  .gr-canvas { padding: 16px; }
  .gr-node h3 { font-size: 15px; }
  .gr-lane > header { min-height: 54px; padding: 10px 11px 9px; }
  .gr-lane > header h2, .gr-lane-title { max-width: 150px; font-size: 17px; }
  .gr-lane-body { gap: 10px; padding: 10px; }
  .gr-first-lane { max-width: calc(100vw - 60px); margin-left: 8px; }
  .gr-edge-form { padding: 25px 18px; }
}
