:root{
  --bg:#F1F3F1;
  --surface:#FFFFFF;
  --border:#D9E0DB;
  --text:#182420;
  --text-muted:#57655D;
  --line:#B7C2BC;
  --line-active:#0E7C74;
  --danger:#B3261E;
  --warn:#9A6A12;
  --good:#2E7D32;
  --stage-1:#0B4E49; --stage-2:#116B63; --stage-3:#0E7C74; --stage-4:#29948A; --stage-5:#4CB3A6;
  --shadow: 0 1px 2px rgba(15,30,25,0.06), 0 6px 16px -10px rgba(15,30,25,0.16);
  --shadow-active: 0 2px 4px rgba(15,30,25,0.08), 0 18px 40px -16px rgba(15,30,25,0.28);
  --serif: "Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
  --sans: "Segoe UI",-apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
  --mono: "SFMono-Regular",Consolas,"Cascadia Mono",Menlo,monospace;
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --bg:#0F1512; --surface:#151F1B; --border:#2A3A34; --line:#3C4C45; --line-active:#3FD6C4;
    --text:#E6EEE9; --text-muted:#9CAFA5;
    --danger:#FF8A75;
    --warn:#E3B156;
    --good:#6FCF7A;
    --stage-1:#12564F; --stage-2:#187A70; --stage-3:#1E9186; --stage-4:#29A99C; --stage-5:#5CC7B8;
    --shadow: 0 1px 2px rgba(0,0,0,0.35), 0 6px 16px -10px rgba(0,0,0,0.5);
    --shadow-active: 0 2px 4px rgba(0,0,0,0.4), 0 20px 44px -16px rgba(0,0,0,0.7);
  }
}
:root[data-theme="dark"]{
  --bg:#0F1512; --surface:#151F1B; --border:#2A3A34; --line:#3C4C45; --line-active:#3FD6C4;
  --text:#E6EEE9; --text-muted:#9CAFA5;
  --danger:#FF8A75;
  --warn:#E3B156;
  --good:#6FCF7A;
  --stage-1:#12564F; --stage-2:#187A70; --stage-3:#1E9186; --stage-4:#29A99C; --stage-5:#5CC7B8;
  --shadow: 0 1px 2px rgba(0,0,0,0.35), 0 6px 16px -10px rgba(0,0,0,0.5);
  --shadow-active: 0 2px 4px rgba(0,0,0,0.4), 0 20px 44px -16px rgba(0,0,0,0.7);
}
:root[data-theme="light"]{
  --bg:#F1F3F1; --surface:#FFFFFF; --border:#D9E0DB; --line:#B7C2BC; --line-active:#0E7C74;
  --text:#182420; --text-muted:#57655D;
  --danger:#B3261E;
  --warn:#9A6A12;
  --good:#2E7D32;
  --stage-1:#0B4E49; --stage-2:#116B63; --stage-3:#0E7C74; --stage-4:#29948A; --stage-5:#4CB3A6;
  --shadow: 0 1px 2px rgba(15,30,25,0.06), 0 6px 16px -10px rgba(15,30,25,0.16);
  --shadow-active: 0 2px 4px rgba(15,30,25,0.08), 0 18px 40px -16px rgba(15,30,25,0.28);
}

*{box-sizing:border-box;}
/* The `hidden` attribute is only display:none in the UA sheet, so ANY
   author display rule silently beats it -- which is how the Approve/Revert
   pair stayed on screen with nothing pending. One guard here, rather than
   a per-class `.thing[hidden]` each new element has to remember. */
[hidden]{display:none !important;}
html,body{height:100%;margin:0;overflow:hidden;background:var(--bg);}
.app{height:100%;display:flex;flex-direction:column;font-family:var(--sans);}

.topbar{
  flex:0 0 auto;
  display:flex;align-items:center;justify-content:space-between;
  flex-wrap:wrap;
  padding:10px 22px;
  background:var(--surface);
  border-bottom:1px solid var(--border);
  z-index:3;
  gap:10px 16px;
}
.topbar h1{font-family:var(--serif);font-size:1rem;font-weight:600;margin:0;color:var(--text);flex:0 0 auto;}
.brand{display:flex;flex-direction:row;align-items:center;flex-wrap:wrap;gap:16px;flex:0 0 auto;}
.brand-logo{flex:0 0 auto;margin-right:-9px;}
.metrics-stack{display:flex;flex-direction:column;gap:2px;}
.compact-toggle{
  font-family:var(--mono);
  font-size:0.68rem;
  padding:3px 9px;
  border-radius:999px;
  border:1px solid var(--border);
  background:var(--surface);
  color:var(--text-muted);
  cursor:pointer;
  transition:background 200ms ease, color 200ms ease, border-color 200ms ease;
}
.compact-toggle:hover{border-color:var(--line-active);color:var(--line-active);}
.compact-toggle.is-on{background:var(--line-active);color:var(--surface);border-color:var(--line-active);}

/* Project controls, sharing the compact toggle's pill shape so the whole
   right-hand end of the topbar reads as one row of controls. */
.topbar-actions{display:flex;align-items:center;flex-wrap:wrap;gap:6px;}
.bar-btn{
  font-family:var(--mono);
  font-size:0.68rem;
  padding:3px 9px;
  border-radius:999px;
  border:1px solid var(--border);
  background:var(--surface);
  color:var(--text-muted);
  cursor:pointer;
  transition:background 200ms ease, color 200ms ease, border-color 200ms ease;
}
.bar-btn:hover{border-color:var(--line-active);color:var(--line-active);}
.bar-btn:disabled{opacity:0.45;cursor:default;}
.bar-btn:disabled:hover{border-color:var(--border);color:var(--text-muted);}
a.bar-btn{text-decoration:none;display:inline-block;}

/* Approve/Revert -- shown only while this session has an uncommitted
   diff (see updateImpactActionsVisibility in app.js). */
.impact-actions{display:inline-flex;gap:6px;}
.bar-btn-approve{border-color:var(--good);color:var(--good);position:relative;}
.bar-btn-approve:hover{background:color-mix(in srgb, var(--good) 12%, transparent);}
.bar-btn-revert{border-color:var(--warn);color:var(--warn);}
.bar-btn-revert:hover{background:color-mix(in srgb, var(--warn) 12%, transparent);}

/* How many cards this session has touched (added, edited, or deleted)
   and hasn't approved yet -- see updateImpactActionsVisibility in app.js.
   Parked off the button's own top-right corner, same idea as the
   remove-card-btn's corner placement on a card. */
.impact-count{
  position:absolute;
  top:-7px; right:-7px;
  min-width:15px; height:15px;
  padding:0 3px;
  border-radius:999px;
  background:var(--good);
  color:var(--surface);
  font-family:var(--mono);
  font-size:0.62rem;
  font-weight:700;
  line-height:15px;
  text-align:center;
}
.impact-count:empty{display:none;}
/* The dot marks changes that exist only in this browser -- autosaved, so
   safe across a refresh, but not yet in any file the user holds. */
.project-name{
  font-family:var(--mono);
  font-size:0.68rem;
  color:var(--text);
  max-width:20ch;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  margin-right:2px;
}
.project-name.is-unsaved::after{
  content:"\2022";
  color:var(--warn);
  margin-left:5px;
  font-size:0.9rem;
  line-height:0;
  vertical-align:middle;
}

/* Click the project name to rename it or switch to a different one --
   see FEATURES.md #14 (multiple named projects, replacing the
   single shared one). */
.project-switcher{position:relative;}
.project-name-btn{
  display:flex;
  align-items:center;
  gap:2px;
  background:none;
  border:none;
  padding:2px 4px;
  border-radius:6px;
  cursor:pointer;
}
.project-name-btn:hover{background:var(--bg);}
.project-name-btn .project-name{margin-right:0;}
.project-caret{font-size:0.6rem;color:var(--text-muted);}
.project-dropdown{
  position:absolute;
  top:calc(100% + 6px);
  left:0;
  z-index:40;
  width:240px;
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:10px;
  box-shadow:var(--shadow);
  padding:8px;
  display:flex;
  flex-direction:column;
  gap:6px;
}
.project-dropdown-head{padding-bottom:6px;border-bottom:1px solid var(--border);}
.rename-input{
  font-family:var(--mono);
  font-size:0.74rem;
  padding:6px 8px;
  border:1px solid var(--border);
  border-radius:8px;
  background:var(--bg);
  color:var(--text);
  width:100%;
  box-sizing:border-box;
}
.rename-input:focus{
  outline:2px solid var(--line-active);
  outline-offset:-1px;
  border-color:var(--line-active);
}
.project-list{display:flex;flex-direction:column;gap:2px;max-height:40vh;overflow-y:auto;}
.project-row{
  display:flex;
  align-items:center;
  gap:6px;
  border-radius:6px;
  padding:5px 6px;
}
.project-row:hover{background:var(--bg);}
.project-row-name{
  flex:1 1 auto;
  min-width:0;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  background:none;
  border:none;
  text-align:left;
  font-family:var(--mono);
  font-size:0.72rem;
  color:var(--text);
  cursor:pointer;
  padding:0;
}
.project-row.is-current .project-row-name{color:var(--line-active);font-weight:700;cursor:default;}
.project-row-del{
  flex:0 0 auto;
  background:none;
  border:none;
  color:var(--text-muted);
  font-size:0.85rem;
  line-height:1;
  cursor:pointer;
  padding:2px 4px;
  border-radius:4px;
}
.project-row-del:hover{color:var(--danger);}

/* Transient messages: autosave trouble, an unreadable file, LLM errors.
   Fixed to the viewport corner so they never disturb the graph layout. */
.toasts{
  position:fixed;
  right:14px;
  bottom:14px;
  z-index:50;
  display:flex;
  flex-direction:column;
  align-items:flex-end;
  gap:8px;
  pointer-events:none;
}
.toast{
  max-width:min(38ch, calc(100vw - 40px));
  padding:8px 12px;
  border-radius:10px;
  border:1px solid var(--border);
  background:var(--surface);
  color:var(--text);
  box-shadow:var(--shadow);
  font-size:0.78rem;
  line-height:1.35;
  opacity:1;
  transform:translateY(0);
  transition:opacity 250ms ease, transform 250ms ease;
}
.toast.is-error{border-color:var(--danger);}
.toast.is-leaving{opacity:0;transform:translateY(6px);}
@media (prefers-reduced-motion: reduce){
  .toast{transition:none;}
}
.metrics-row{display:flex;flex-wrap:wrap;row-gap:2px;gap:10px;font-family:var(--mono);font-size:0.66rem;color:var(--text-muted);}
.metrics-row span{white-space:nowrap;}
.metrics-row b{font-weight:700;}
.metrics-row .metrics-label{color:var(--text);font-weight:700;}
.metrics-row .metrics-avg b{color:var(--text);}
/* Coverage (REG/URS/FRS/SRS) and quality (URS/FRS/SRS/TC) are offset by
   one level from each other -- this half-item nudge on the second row is
   what makes that read as a staggered pair instead of two lists that just
   don't line up. ~25px is half an item's natural width (~41px) plus half
   the row's own 10px gap. */
.metrics-row-shifted{margin-left:25px;}

.viewport{flex:1 1 auto;position:relative;overflow:hidden;background:var(--bg);}
.world{position:absolute;top:0;left:0;transform-origin:0 0;}
.edges{position:absolute;top:0;left:0;overflow:visible;pointer-events:none;}
.compact-headers{position:absolute;top:0;left:0;pointer-events:none;}
/* Drag feedback for the compact-view reorder/re-chapter gesture (see
   reorderState). The indicator marks where the dragged row would land;
   shown/hidden and repositioned entirely via inline styles in JS. */
.card.is-dragging, .compact-header.is-dragging{ opacity:0.35; }
.compact-drop-indicator{
  position:absolute;
  height:3px;
  margin-top:-1.5px;
  background:var(--line-active);
  border-radius:2px;
  pointer-events:none;
  display:none;
  z-index:5;
}

/* One row per chapter, only in compact view — see renderCompactHeaders().
   Depth is expressed by insetting the row's own left edge and shrinking
   its width (set inline per row, not padding — padding just left a
   hollow strip of empty space on the left instead of actually shrinking
   the block) plus a shrinking font-size/weight, both computed in JS so
   any depth up to 10 degrades sensibly without needing 10 CSS classes.
   Chapters are editable/draggable/deletable like requirements — just
   never linkable (no handles, never appear in EDGES) — hence the number
   + title + edit/add/delete buttons packed into one row here. */
.compact-header{
  position:absolute;
  box-sizing:border-box;
  display:flex;
  align-items:center;
  gap:6px;
  padding:0 4px 0 10px;
  color:var(--text-muted);
  font-family:var(--sans);
  cursor:grab;
  border-bottom:1px solid var(--border);
  /* auto, not none: a compact-view drag needs elementFromPoint() to be
     able to land on a header (see the reorder/re-chapter drag below). */
  pointer-events:auto;
}
.compact-header.is-dragging{ cursor:grabbing; }
.ch-number{
  font-family:var(--mono);
  color:var(--text-muted);
  flex:0 0 auto;
}
.ch-title{
  flex:1 1 auto;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
.ch-title[contenteditable="true"]{
  cursor:text;
  outline:1px dashed var(--line-active);
  outline-offset:2px;
  border-radius:3px;
  color:var(--text);
}
.ch-btn{
  flex:0 0 auto;
  width:16px;height:16px;
  border-radius:50%;
  border:1px solid var(--border);
  background:var(--surface);
  color:var(--text-muted);
  font-size:0.6rem;line-height:1;
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;padding:0;
  transition:border-color 200ms ease, color 200ms ease, opacity 200ms ease;
}
.ch-btn:hover{border-color:var(--line-active);color:var(--line-active);}
.ch-del:hover{border-color:var(--danger);color:var(--danger);}
.ch-btn:disabled{opacity:0.35;cursor:default;}
.ch-btn:disabled:hover{border-color:var(--border);color:var(--text-muted);}

.card{
  position:absolute;
  width:360px;
  background:var(--surface);
  border:2px solid transparent;
  border-radius:14px;
  padding:15px 18px;
  box-shadow:var(--shadow);
  cursor:pointer;
  /* visible, not hidden: .remove-card-btn deliberately hangs outside this
     box (see its own rule below), and title/id already clip their own
     single-line overflow -- nothing here needs the parent to clip too. */
  overflow:visible;
  transition:border-color 420ms ease, opacity 200ms ease, height 250ms ease;
  touch-action:none;
}
.card.is-collapsed hr,
.card.is-collapsed .card-text{ display:none; }

/* Compact view: one card = one line — link quality, id, title, coverage,
   in that order left to right (card-quality/card-coverage are normally
   absolutely positioned in the corners; here they rejoin the flex row
   via `order`). */
.card.is-line{
  display:flex;
  align-items:center;
  gap:10px;
  padding:0 16px;
}
.card.is-line .card-quality{ order:0; position:static; flex:0 0 auto; }
.card.is-line .card-id{ order:1; margin-bottom:0; flex:0 0 auto; padding-left:0; }
.card.is-line .card-title{
  order:2;
  flex:1 1 auto;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
  font-size:0.82rem;
}
.card.is-line .card-coverage{ order:3; position:static; flex:0 0 auto; }
.card.is-line .edit-btn,
.card.is-line .confirm-edit-btn,
.card.is-line .revert-edit-btn,
.card.is-line .remove-card-btn,
.card.is-line .revert-item-btn{ display:none !important; }
.card:focus-visible{border-color:var(--card-color);}
/* Ctrl/Cmd+click adds a card to the multi-select (extraSelected) alongside
   the single .is-active card -- styled identically, since both are just
   "selected" from the filter/highlight's point of view. */
.card.is-active, .card.is-multi-selected{
  border-color:var(--card-color);
  cursor:default;
  z-index:2;
}
.card-id{font-family:var(--mono);font-weight:700;font-size:0.72rem;color:var(--card-color);margin-bottom:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.card-title{font-family:var(--sans);font-weight:700;font-size:0.98rem;color:var(--text);line-height:1.28;transition:color 300ms ease;}
.card hr{border:none;border-top:1px solid var(--border);margin:10px 0;}
.card-text{font-size:0.82rem;line-height:1.5;color:var(--text);transition:color 300ms ease;}
/* Selecting a card dims the title/body text of every card outside its
   ancestor/descendant chain (see collectActiveChain) — same chain the
   edge highlight already uses, so text and links dim/light up together
   instead of two separate "what's related" signals. */
.card.is-unrelated .card-title,
.card.is-unrelated .card-text{ color:color-mix(in srgb, var(--text-muted) 55%, var(--bg) 45%); }
.card-text[contenteditable="true"],
.card-title[contenteditable="true"]{
  cursor:text;
  outline:1px dashed var(--line-active);
  outline-offset:4px;
  border-radius:4px;
}

/* Grab strips at the card's left/right edges — dragging a link must
   start from one of these, not from anywhere on the card. Highlighted
   only on hover so they don't clutter the default look. */
.card-handle{
  position:absolute;
  top:0; bottom:0;
  width:18px;
  cursor:grab;
  background:transparent;
  transition:background 150ms ease;
}
.card-handle.left{ left:0; border-radius:12px 0 0 12px; }
.card-handle.right{ right:0; border-radius:0 12px 12px 0; }
.card-handle:hover{ background:color-mix(in srgb, var(--line-active) 16%, transparent); }

.add-btn{
  position:absolute;
  width:26px;height:26px;
  transform:translate(-50%,-50%);
  border-radius:50%;
  border:1px solid var(--border);
  background:var(--surface);
  color:var(--text-muted);
  font-size:1rem;font-weight:600;line-height:1;
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;padding:0;
  box-shadow:var(--shadow);
  z-index:3;
  transition:border-color 200ms ease, color 200ms ease, opacity 200ms ease;
}
.add-btn:hover{ border-color:var(--line-active); color:var(--line-active); }
.add-btn:focus-visible{ outline:2px solid var(--line-active); outline-offset:2px; }

/* Hanging outside the card's own top edge, same treatment as
   remove-card-btn/revert-item-btn just to their left (right:50px clears
   revert-item-btn's right:19px slot) -- moved here 2026-08-26 from
   top:10px, INSIDE the card, where it used to sit directly beside the
   coverage badge. That badge can now be much wider than a bare score
   (see appendBadgeDelta in app.js), and its right:10px anchor means it
   only grows further left as it widens -- so a fixed inside slot for
   this row was always going to collide with a wide enough badge. top:-18
   (not remove-card-btn's -13) leaves a couple of pixels clear under the
   badge's own top:9 -- -13 alone still clipped a corner of a maximally
   wide badge by ~2px. Outside the card entirely, there is no badge
   content to collide with regardless of width. edit-btn and
   confirm-edit-btn/revert-edit-btn never show at the same time (is-active
   vs. is-editing), so it's safe for edit-btn to reuse confirm-edit-btn's
   slot too -- see its own rule below. */
.edit-btn,
.confirm-edit-btn,
.revert-edit-btn{
  display:none;
  position:absolute;
  top:-18px;right:50px;
  width:24px;height:24px;
  border-radius:50%;
  border:1px solid var(--border);
  background:var(--surface);
  color:var(--text-muted);
  font-size:0.8rem;line-height:1;
  align-items:center;justify-content:center;
  cursor:pointer;padding:0;
  box-shadow:var(--shadow);
  transition:border-color 420ms ease, color 420ms ease;
}
.edit-btn:hover{border-color:var(--line-active);color:var(--line-active);}
.edit-btn:focus-visible, .confirm-edit-btn:focus-visible, .revert-edit-btn:focus-visible{
  outline:2px solid var(--line-active);outline-offset:2px;
}
.card.is-active .edit-btn{display:flex;}
.card.is-editing .edit-btn{display:none;}

/* Discard (amber) sits where the edit button was, right next to the
   badge; save (green) is the next slot further left -- reads left-to-
   right as "save, discard, [coverage badge]". */
.revert-edit-btn{ color:var(--warn); }
.revert-edit-btn:hover{border-color:var(--warn);background:color-mix(in srgb, var(--warn) 12%, transparent);}
.card.is-editing .revert-edit-btn{display:flex;}

.confirm-edit-btn{ right:84px; color:var(--good); }
.confirm-edit-btn:hover{border-color:var(--good);background:color-mix(in srgb, var(--good) 12%, transparent);}
.card.is-editing .confirm-edit-btn{display:flex;}

/* Deliberately outside the card's own box, hanging off its top-right
   corner -- .card is overflow:visible precisely so this can sit somewhere
   a click meant for the card, a badge, or the edit button can never
   accidentally land. Shown only while editing, same as the save/discard
   pair, so it's never a stray control sitting there the rest of the time. */
.remove-card-btn{
  display:none;
  position:absolute;
  top:-18px;right:-13px;
  z-index:5;
  width:24px;height:24px;
  border-radius:50%;
  border:1px solid var(--border);
  background:var(--surface);
  color:var(--danger);
  font-size:0.9rem;line-height:1;
  align-items:center;justify-content:center;
  cursor:pointer;padding:0;
  box-shadow:var(--shadow);
  transition:border-color 200ms ease, background 200ms ease;
}
.remove-card-btn:hover{border-color:var(--danger);background:color-mix(in srgb, var(--danger) 12%, transparent);}
.remove-card-btn:focus-visible{outline:2px solid var(--danger);outline-offset:2px;}
.card.is-editing .remove-card-btn{display:flex;}

/* Per-item revert (undo this card's own diff back to the last checkpoint —
   see revertCardToControlPoint in app.js), parked just to the left of the
   delete button since both act on "this card" rather than the whole
   session. Only shown while editing AND this card actually has a diff of
   its own (has-diff, toggled in renderDiffMarker) -- nothing to revert on
   an unmodified card. */
.revert-item-btn{
  display:none;
  position:absolute;
  top:-18px; right:19px;
  z-index:5;
  width:22px;height:22px;
  border-radius:50%;
  border:1px solid var(--border);
  background:var(--surface);
  color:var(--warn);
  font-size:0.85rem;line-height:1;
  align-items:center;justify-content:center;
  cursor:pointer;padding:0;
  box-shadow:var(--shadow);
  transition:border-color 200ms ease, background 200ms ease;
}
.revert-item-btn:hover{border-color:var(--warn);background:color-mix(in srgb, var(--warn) 12%, transparent);}
.revert-item-btn:focus-visible{outline:2px solid var(--warn);outline-offset:2px;}
.card.is-editing.has-diff .revert-item-btn{display:flex;}

/* Impact lock zones (see the Impact/checkpoint section of app.js) --
   touched cards plus their direct linked neighbors, until Approve or
   Revert. No separate dot/badge: the card itself changes colour instead --
   green border/tint for THIS session's own zone (still fully editable,
   just reserved), grey/dimmed + desaturated for another session's (not
   editable; a native title tooltip on hover names whose lock it is -- see
   applyLockVisuals in app.js). A genuinely abandoned lock is recovered
   from the change-log page (main.py/history.html's force-revert), not by
   clicking anything on the card. */
.card.is-locked-mine{
  border-color:var(--good);
  background:color-mix(in srgb, var(--good) 6%, var(--surface));
}
.card.is-locked-other{
  opacity:0.55;
  filter:grayscale(0.7);
  border-color:var(--text-muted);
  cursor:default;
}
.card.is-locked-other .edit-btn{display:none;}
.card.is-locked-other .remove-card-btn{display:none;}

/* Backward quality -- how well this requirement is justified by
   everything linked above it (llmAnalyzeBackward). Only shown for
   col > 0 -- nothing precedes the leftmost column, so there's nothing to
   score here for it. The id shifts right via .has-quality to leave room.
   Clickable: this badge IS the trigger to (re)score it, there is no
   separate button -- styled like one (background/border/pill) so that
   reads at a glance, unlike the plain-text link-quality readout on a
   link, which has no action behind it. */
.card-quality, .card-coverage{
  position:absolute;
  top:9px;
  font-family:var(--mono);
  font-size:0.66rem;
  font-weight:700;
  letter-spacing:0.01em;
  padding:2px 7px;
  border-radius:999px;
  background:var(--surface);
  border:1px solid var(--border);
  box-shadow:var(--shadow);
  cursor:pointer;
  transition:border-color 200ms ease;
}
.card-quality:hover, .card-coverage:hover{border-color:var(--line-active);}
.card-quality{ left:16px; }
/* Wide enough for the longest realistic badge -- "100% -17%" plus its
   border/padding -- now that a badge can carry a delta alongside its
   score (see appendBadgeDelta in app.js). The old 54px only fit a bare
   score and let the id text run underneath a badge showing a delta. */
.card.has-quality .card-id{ padding-left:82px; }
.card.has-coverage .card-id{ padding-right:82px; }
.card.is-line.has-quality .card-id,
.card.is-line.has-coverage .card-id{ padding-left:0; padding-right:0; }

/* Mid-request: the rotation-arrow glyph actually spins, rather than
   sitting there as a static, ambiguous "is this working?" character --
   but only the glyph (.badge-spin-icon, an inner span -- see
   renderBusyBadge in app.js), not the badge itself. Animating the badge
   would spin its whole pill shape, border and position included, around
   its own center, which reads as the badge tumbling rather than a plain
   "working" spinner. */
.card-quality.is-busy, .card-coverage.is-busy{
  display:inline-block;
  cursor:default;
}
.badge-spin-icon{
  display:inline-block;
  margin-left:4px;
  animation:badge-spin 900ms linear infinite;
}
@keyframes badge-spin{to{transform:rotate(360deg);}}

/* The very corner -- edit/confirm/revert/unlock all sit one slot to its
   left (see their own rule) so they never overlap it. The rightmost level
   (TC) has no further level to be covered by, so it gets none. */
.card-coverage{ right:10px; }

/* non-scaling-stroke keeps this width difference visible in screen
   pixels regardless of the world's current zoom — without it, compact
   view's smaller-than-1 zoom (fitting all 5 columns at once) shrinks
   both the active and inactive stroke widths toward each other until
   the highlight is barely perceptible. */
.edge-path{fill:none;stroke:var(--line);stroke-width:1.5;opacity:0.3;vector-effect:non-scaling-stroke;transition:stroke 420ms ease, stroke-width 420ms ease, opacity 200ms ease;}
/* is-active-edge = touches the active card directly (also the only ones
   that get a delete cross, see .edge-del below). is-chain-edge = the
   wider cascade — every ancestor and every descendant of the active
   card, any number of columns away — styled identically so the whole
   chain reads as one highlighted path, not just its first link. */
.edge-path.is-chain-edge,
.edge-path.is-active-edge{stroke:var(--line-active);stroke-width:2.6;opacity:0.95;}
.edge-path.edge-temp{stroke-dasharray:5 4;stroke:var(--line-active);opacity:0.9;transition:none;}
/* A proposed link (from Find links) is drawn dashed and in the warning
   colour so it reads as "not decided yet" without having to click it --
   it stays this way, and stays on screen regardless of what's selected
   (see applyEdgeVisibility), until confirmed or deleted. */
.edge-path.is-proposed{stroke:var(--warn);stroke-dasharray:7 5;opacity:0.9;}
.edge-dot{opacity:0.45;transition:fill 420ms ease, opacity 200ms ease;}
.edge-dot.is-chain-edge,
.edge-dot.is-active-edge{opacity:1;}
.edge-dot.is-proposed{fill:var(--warn);opacity:1;}

.edge-del{
  position:absolute;
  width:18px;height:18px;
  transform:translate(-50%,-50%);
  border-radius:50%;
  border:1px solid var(--border);
  background:var(--surface);
  color:var(--text-muted);
  font-size:0.72rem;line-height:1;
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;padding:0;
  box-shadow:var(--shadow);
  transition:border-color 420ms ease, color 420ms ease, opacity 200ms ease;
}
.edge-del:hover{border-color:var(--danger);color:var(--danger);}
.edge-del:focus-visible{outline:2px solid var(--line-active);outline-offset:2px;}

/* Temporary: only links between two currently-visible cards are shown;
   links to a hidden card fade out instead of vanishing (same speed as
   the cards themselves). Links touching the active card are additionally
   highlighted green via .is-active-edge above, and only THOSE get a
   delete cross — a link shown only because it connects two relatives
   (not the active card) can't be deleted from here. A proposed link is
   the exception: it's always shown and always deletable/confirmable
   (see applyEdgeVisibility / .is-proposed), since it's waiting on a
   decision regardless of what's currently focused. Deleting (including
   rejecting a proposal, which uses this same × button) now happens
   immediately — no pending/red step — with a single level of undo via
   Ctrl+Z instead. */
.app.hide-inactive-links .edge-path:not(.is-shown-edge):not(.edge-temp),
.app.hide-inactive-links .edge-dot:not(.is-shown-edge){
  opacity:0;
  pointer-events:none;
}
.app.hide-inactive-links .edge-del:not(.is-active-edge):not(.is-proposed){
  opacity:0;
  pointer-events:none;
}

@media (prefers-reduced-motion: reduce){
  .card, .edge-path, .edge-dot, .edge-del{ transition:none; }
}

/* ------------------------------------------------------------------
   Settings panel: model and the two editable prompts.
   ------------------------------------------------------------------ */
.bar-icon{font-size:0.85rem;line-height:1;padding:3px 8px;}
.settings-overlay{
  position:fixed;
  inset:0;
  z-index:60;
  display:flex;
  align-items:flex-start;
  justify-content:center;
  padding:5vh 16px;
  background:color-mix(in srgb, var(--text) 28%, transparent);
  overflow-y:auto;
}
.settings-panel{
  width:min(660px, 100%);
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:14px;
  box-shadow:var(--shadow);
  display:flex;
  flex-direction:column;
  max-height:90vh;
}
.settings-head{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  padding:14px 18px;
  border-bottom:1px solid var(--border);
}
.settings-head h2{margin:0;font-size:0.95rem;letter-spacing:0.01em;}
.settings-body{padding:16px 18px 20px;overflow-y:auto;display:flex;flex-direction:column;gap:18px;}
.prompts{display:flex;flex-direction:column;gap:18px;}
.field{display:flex;flex-direction:column;gap:6px;}
.field-head{display:flex;align-items:center;justify-content:space-between;gap:10px;}
.field-label{font-size:0.78rem;font-weight:700;color:var(--text);}
.field-hint{font-size:0.7rem;line-height:1.4;color:var(--text-muted);}
.field input, .field textarea{
  font-family:var(--mono);
  font-size:0.74rem;
  line-height:1.5;
  padding:8px 10px;
  border:1px solid var(--border);
  border-radius:8px;
  background:var(--bg);
  color:var(--text);
  width:100%;
  box-sizing:border-box;
}
.field textarea{resize:vertical;min-height:90px;}
.field input:focus, .field textarea:focus{
  outline:2px solid var(--line-active);
  outline-offset:-1px;
  border-color:var(--line-active);
}
/* An edited prompt is marked so it's obvious at a glance which of the
   four are no longer the shipped defaults. */
.field.is-modified .field-label::after{
  content:" · edited";
  color:var(--warn);
  font-weight:400;
}
.prompt-reset{font-size:0.64rem;padding:2px 8px;}

/* ------------------------------------------------------------------
   AI scoring: score badges, per-card actions.
   ------------------------------------------------------------------ */

/* The link-quality readout, centred on the link's midpoint like the
   delete cross directly beneath it. Only shown while there's a decision
   pending on this specific link -- a Find-links proposal awaiting
   confirm, or one just drawn by hand -- not for every established link,
   which would just repeat what the two per-card badges already show (see
   applyEdgeVisibility's isQualityShown in app.js). Plain text, not a
   button (no controls of its own, see edgeQuality() in data.js) -- but a
   proposed link's bar also holds the confirm button (see
   .edge-bar.is-proposed below), so it needs a container to read as one
   cluster there. */
.edge-bar{
  position:absolute;
  transform:translate(-50%, -50%);
  display:none;
  align-items:center;
  gap:3px;
  z-index:4;
  opacity:0;
  pointer-events:none;
  transition:opacity 200ms ease;
}
.edge-bar.is-shown-edge{opacity:1;pointer-events:auto;display:flex;}
.edge-bar.is-proposed{
  padding:2px 4px;
  border-radius:999px;
  background:var(--surface);
  border:1px solid var(--warn);
  box-shadow:var(--shadow);
}
.edge-score{
  font-family:var(--mono);
  font-size:0.62rem;
  font-weight:700;
  white-space:nowrap;
  cursor:help;
}
.edge-btn{
  font-size:0.62rem;
  line-height:1;
  width:16px;
  height:16px;
  padding:0;
  border-radius:50%;
  border:1px solid var(--border);
  background:var(--bg);
  color:var(--text-muted);
  cursor:pointer;
}
.edge-accept:hover{border-color:var(--good);color:var(--good);}
/* "+3%"/"-3%" after a badge's own number (see appendBadgeDelta in app.js)
   -- how much this score moved at its last recompute. Replaced the old
   strikethrough-for-stale-scores treatment 2026-08-26: a coloured delta is
   useful information, a struck-through number was just a warning with
   nothing underneath it. */
.badge-delta{
  margin-left:2px;
  font-weight:700;
  vertical-align:super;
  font-size:0.75em;
}
.badge-delta.is-up{color:var(--good);}
.badge-delta.is-down{color:var(--danger);}
.badge-delta.is-flat{color:var(--text-muted);}

/* Find links: back inside the active card's own bottom row (see the
   ACTION_BAR_H reservation in relayout) -- left half for an upstream
   candidate, right half for a downstream one. Each anchors from its own
   given (left) coordinate: .side-left hugs that point on its left edge,
   .side-right on its right edge, so the two never collide regardless of
   label length. Shown on every card in normal view (left only if it has
   an upstream level, right only if it has a downstream one -- see
   updateFindLinksButtons); the warning colour just marks "this proposes
   a link", not a judgement about the card. */
.find-links-btn{
  position:absolute;
  display:none;
  transform:translateY(-50%);
  align-items:center;
  justify-content:center;
  font-family:var(--mono);
  font-size:0.64rem;
  padding:3px 10px;
  border-radius:999px;
  border:1px solid var(--warn);
  background:var(--surface);
  color:var(--warn);
  box-shadow:var(--shadow);
  cursor:pointer;
  white-space:nowrap;
  z-index:3;
  transition:background 200ms ease, color 200ms ease;
}
.find-links-btn.side-right{ transform:translate(-100%,-50%); }
.find-links-btn:hover{background:var(--warn);color:var(--surface);}
.find-links-btn:focus-visible{outline:2px solid var(--line-active);outline-offset:2px;}

.metrics-scope{color:var(--text-muted);font-weight:400;}
