.sortable-list-parent {
  position: relative;
  --lift-z: 1;
  --sortable-item-inset: .5rem;
  --sortable-item-gap: .5rem;
  --item-border-width: 3px;
  --lift-border-color: var(--warn);
  /* grow to the longest single line of text inside it */
  width: max-content;
  max-width: 100%;
}

.sortable-list-parent,
.sortable-list,
.sortable-list-clone {
/* Align the parent's border box with the list's content box, to translate
viewport-relative values into containing block offsets with: 
const cloneTop = sourceRect.top - listParentRect.top;
*/
  margin: 0;
  border: 0;
  padding: 0;
}

.sortable-list {
  position: relative;
  /* "Scroll anchoring" — the browser may scroll the window after layout change to keep
   certain elements in view. The insertBefore on drop triggers layout change, causing the
   browser to scroll the window down, so the just-dropped item is hidden behind the sticky
   nav. Problem verified in Chromium: 149.0.7827.54 — this fixes it.

   overflow-anchor: none tells the browser to not scroll — leave it where it was dropped.
  */
  overflow-anchor: none;
}

.sortable-list,
.sortable-list-clone {
  list-style: decimal inside;
  width: 100%;
}

.sortable-list-parent > ol > li {
  padding: .5rem .75rem;
  border-radius: .25rem;
  font-size: var(--text-sm);
  margin-inline: var(--sortable-item-inset);
}

.sortable-list > li {
  position: relative;
  background: var(--panel-bg-sunken);
  border-left: var(--item-border-width) solid var(--border);
  cursor: grab;
  /* Prevents native scrolling/zooming for stylus and hybrid
     inputs where touchstart is bypassed */
  touch-action: none;
  transition: border-color var(--dur-normal);
}

.sortable-list > li + li {
  margin-block-start: var(--sortable-item-gap);
}

/* The sortable list's item-entrance animation. The module owns it but applies it
   nowhere here -- consumers do (diagnose.css gates it on #chains-wrap.entering). It
   reads as unused in this file by design; don't move or delete it. */
@keyframes list-enter {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
}

.sortable-list > li.active-drag-source {
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
  opacity: .3;
  cursor: grabbing;
  /* Opacity transition is scoped to this class: lifting dims slowly, but
     removing it on drop snaps back to full opacity at once, so the dropped
     item is solid the instant the cool-down animation starts. */
  transition: border-color var(--dur-normal), opacity var(--dur-slow);
}

.sortable-list:has(> li.active-drag-source) > li:is(.correct, .incorrect) {
  border-left-color: var(--border);
  background: var(--panel-bg-sunken);
}

.sortable-list-clone {
  position: absolute;
  z-index: calc(var(--lift-z) + 1);
  /* The clone is never an interaction target.
   But, its WAAPI animation outlives list-drag-active during the settle glide, 
   so drag-wide pointer-events rule does not cover its whole lifetime. */
  pointer-events: none;
  transform: translateY(var(--drag-offset, 0px));
}

.sortable-list-clone > li {
  background: var(--accent-bg);
  border-left: var(--item-border-width) solid var(--lift-border-color);
  cursor: grabbing;
  box-shadow: var(--box-shadow-lg);
}

/* The drop bar: one per list on .sortable-list-parent (a sibling of the clone),
   built with the form and reused. --dropbar-position slides it to the gap before the drop
   target (a transform -- composites, no layout). Hidden by default; --dropbar-opacity reveals it. */
.sortable-list-dropbar {
  position: absolute;
  z-index: var(--lift-z);
  --dropbar-thickness: .35rem;
  --dropbar-offset: calc(var(--dropbar-thickness) * -1);
  top: var(--dropbar-offset);
  left: 0;
  right: var(--sortable-item-inset);
  height: var(--dropbar-thickness);
  background: var(--accent);
  border-radius: 9999px;
  pointer-events: none;
  opacity: var(--dropbar-opacity, 0);
  transform: translateY(var(--dropbar-position, 0px));
  transition: opacity var(--dur-fast) ease-out;   /* fades on every show/hide */
}

.sortable-list-dropbar.dropbar-gliding {
  transition: opacity var(--dur-fast) ease-out, transform var(--dur-fast) ease-out;
}

/* On drop the LI briefly wears the dragged clone's lifted look, then cools to
   its resting item. The keyframe declares that lifted start, so it needs no
   painted-first arming. */
@keyframes item-drop-cool {
  from {
    background: var(--accent-bg);
    border-left-color: var(--lift-border-color);
    box-shadow: var(--box-shadow-lg);
  }
}

.sortable-list > li.dropped {
  /* Stay above the displaced sibling that FLIPs past during the settle, so the
  dropped item cools on top instead of being covered. Clears with the class. */
  z-index: var(--lift-z);
  animation: item-drop-cool var(--dur-normal) ease-out;
}

html.list-drag-active {
  &, & * {
    cursor: grabbing;
    user-select: none;
  }

  /* Pointer events fall through to <html> — excluded, so it stays hittable
     and the document-bound drag listeners keep receiving events. Drop targets
     resolve by coordinate math over the frozen baseline; hit-testing
     (e.target / elementFromPoint) returns <html> while this class is on. */
  & * { pointer-events: none }
}

/* hover rule specificity (0,3,2) outranks .correct / .incorrect's (0,2,1)
  so hovering a graded item works, and, with the base li border-color transition */
html:not(.list-drag-active) .sortable-list > li:hover {
  border-color: var(--accent);
}

.sortable-list > li.correct {
  border-left-color: var(--correct-border);
  background: var(--correct-bg);
}

.sortable-list > li.incorrect {
  border-left-color: var(--error-border);
  background: var(--error-bg);
}

@keyframes all-correct-pulse {
  30% {
    transform: scale(1.02);
    box-shadow: 0 0 .75rem var(--correct-border), inset 0 0 .5rem var(--correct-border);
    border-color: transparent;
  }
  100% {
    transform: scale(1);
    box-shadow: none;
  }
}

.sortable-list.all-correct > li {
  animation: all-correct-pulse var(--dur-normal) ease-out;
  animation-delay: calc(var(--i) * var(--dur-fast) / 2);
}

.sortable-list[aria-disabled="true"] > li {
  cursor: default;
}
