/* ============================================================================
   /tools/sorting/ — the visualiser's own styling.

   Loaded after /tools/kit.css, which supplies the tokens, wordmark, masthead,
   .card, .copy, footer and #toast. Everything here belongs to the sorting tool
   specifically and is shared by every algorithm page under /tools/sorting/.

   Not fingerprinted, so pages link it with a manual cache-buster:
   /tools/sorting/engine.css?v=6 — bump it on every algorithm page when this
   file changes.
   ========================================================================= */

/* One colour per role, used by both the bars and the narration, so the
   sentence under the chart and the thing it describes always agree.

   The three accent roles borrow kit tokens and therefore follow the theme for
   free. The four below cannot: a resting bar, a sorted run, a scratch bar and
   the span band are all large filled areas, and simply lightening them for
   dark mode would make the chart glare. They get their own dark values. */
:root{
  --bar:#8FA8D8;
  --bar-zone:#BEDDD4;
  --aux-bar:#C7D3EA;
  --span-fill:rgba(47,95,224,.07);
  --span-edge:rgba(47,95,224,.35);
  --bar-cmp:var(--amber);
  --bar-swap:var(--red);
  --bar-done:var(--teal);
}
.dark{
  --bar:#47597F;
  --bar-zone:#2D5F55;
  --aux-bar:#36445F;
  --span-fill:rgba(123,162,247,.10);
  --span-edge:rgba(123,162,247,.42);
}

/* ---------- breadcrumb ----------
   The algorithm pages sit one level below /tools/sorting/, so the eyebrow
   carries the way back up rather than adding a second wordmark. */
.eyebrow a.up{color:var(--blue);text-decoration:none;border-bottom:1px solid transparent}
.eyebrow a.up:hover{border-bottom-color:var(--blue)}

/* ---------- setup ---------- */
.setup{margin-top:22px}
.setup .body{display:grid;gap:14px}
.row{display:flex;flex-wrap:wrap;gap:10px;align-items:end}
.fld{display:grid;gap:4px;min-width:0}
.fld label{font-family:var(--cond);text-transform:uppercase;letter-spacing:.1em;font-size:11px;color:var(--ink-soft)}
.fld input[type=text]{
  font-family:var(--mono);font-size:13px;padding:8px 10px;width:100%;
  border:1px solid var(--rule);border-radius:4px;background:var(--field);color:var(--ink)
}
.fld input[type=text]:focus{outline:2px solid var(--blue);outline-offset:-1px;background:var(--surface)}
.fld.grow{flex:1 1 320px}
.sliders{display:flex;flex-wrap:wrap;gap:18px}
.sl{display:grid;gap:4px;min-width:190px}
.sl .top{display:flex;justify-content:space-between;gap:12px;align-items:baseline}
.sl .top b{font-family:var(--mono);font-size:12.5px;font-weight:500;color:var(--ink)}
.sl input[type=range]{width:100%;accent-color:var(--blue)}
.presets{display:flex;flex-wrap:wrap;gap:6px}
.pz{
  font-family:var(--cond);text-transform:uppercase;letter-spacing:.09em;font-size:11.5px;
  padding:5px 11px;border:1px solid var(--rule);border-radius:100px;background:var(--surface);
  color:var(--ink-soft);cursor:pointer
}
.pz:hover{border-color:var(--blue);color:var(--blue)}
.pz[aria-pressed="true"]{background:var(--ink);border-color:var(--ink);color:var(--paper)}
.err{margin:0;font-size:12.5px;color:var(--red)}
.err[hidden]{display:none}

/* ---------- chart ---------- */
.stage{margin-top:18px}
.bars{
  position:relative;
  display:flex;align-items:flex-end;gap:2px;
  height:320px;padding:22px 2px 0;background:var(--field);
  border:1px solid var(--rule);border-radius:4px
}
/* The slice a divide-and-conquer sort is currently working on. Drawn as a band
   behind the bars rather than a fill, so it composes with every bar state
   instead of competing with them. */
.span-band{
  position:absolute;top:0;bottom:0;pointer-events:none;
  background:var(--span-fill);border-left:1px solid var(--span-edge);
  border-right:1px solid var(--span-edge);border-radius:2px;
  transition:left .1s linear,width .1s linear
}
.span-band[hidden]{display:none}
/* The frontier a partition maintains: everything left of this line is already
   known to be smaller than the pivot. */
.wall-line{
  position:absolute;top:14px;bottom:0;width:2px;pointer-events:none;
  background:var(--blue-deep);border-radius:1px;
  transition:left .1s linear
}
.wall-line::before{
  content:"";position:absolute;top:-6px;left:-3px;
  border:4px solid transparent;border-top-color:var(--blue-deep)
}
.wall-line[hidden]{display:none}
@media(max-width:700px){.bars{height:220px}}
.bar{
  flex:1 1 0;min-width:0;position:relative;
  background:var(--bar);border-radius:2px 2px 0 0;
  transition:height .1s linear,background-color .1s linear
}
/* Bar states, weakest first — paint() applies them in this order, so the last
   one that matches wins:
     zone  sorted among themselves, but a later value may still land between
     done  final: nothing will move this again
     mark  the running "best so far" a scan is holding on to
     cmp   the pair being compared right now
     swp   the pair being exchanged, or the value being shifted
     hole  the slot a value was lifted out of — drawn as an outline, at the
           height of the value being carried, because that value is in a
           variable and not in the array at all */
.bar.zone{background:var(--bar-zone)}
.bar.done{background:var(--bar-done)}
.bar.mark{background:var(--blue-deep)}
.bar.cmp{background:var(--bar-cmp)}
.bar.swp{background:var(--bar-swap)}
.bar.hole,.bar.stale{
  background:repeating-linear-gradient(45deg,var(--field),var(--field) 4px,var(--rule-soft) 4px,var(--rule-soft) 8px);
  border:1px dashed var(--ink-soft);border-bottom:0
}
.bar.hole b,.bar.stale b{color:var(--ink-soft);font-style:italic}

/* ---------- auxiliary buffers ----------
   Merge sort's O(n) extra memory, shown rather than described: the two runs
   being merged live here, outside the array, while the array is overwritten. */
.aux{
  margin:10px 0 0;padding:11px 12px;
  background:var(--sunken);border:1px solid var(--rule);border-radius:4px
}
.aux[hidden]{display:none}
.aux-label{
  font-family:var(--cond);text-transform:uppercase;letter-spacing:.1em;
  font-size:11px;color:var(--ink-soft);margin-bottom:9px
}
.aux-label b{font-family:var(--mono);letter-spacing:0;text-transform:none;color:var(--ink)}
.aux-runs{display:flex;align-items:flex-end;gap:14px}
.aux-run{display:flex;align-items:flex-end;gap:2px;flex:0 1 auto;min-width:0;height:64px}
.aux-run.empty{display:none}
.ab{
  position:relative;width:20px;min-width:6px;flex:0 1 20px;
  background:var(--aux-bar);border-radius:2px 2px 0 0
}
.ab.used{background:var(--rule-soft)}
.ab.next{background:var(--bar-cmp)}
.ab b{
  position:absolute;bottom:100%;left:50%;transform:translateX(-50%);
  font-family:var(--mono);font-size:10px;color:var(--ink-soft);padding-bottom:2px;line-height:1
}
.ab.used b{color:var(--rule-strong)}
.ab.next b{color:var(--ink);font-weight:600}
.aux-vs{
  flex:0 0 auto;align-self:center;font-family:var(--cond);text-transform:uppercase;
  letter-spacing:.1em;font-size:10.5px;color:var(--ink-soft)
}
@media(max-width:620px){.ab{width:12px;flex-basis:12px}.aux-run{height:48px}.ab b{display:none}}
/* The value sits above its bar; dropped once the bars are too thin to carry a
   two-digit number without overlapping. */
.bar b{
  position:absolute;bottom:100%;left:50%;transform:translateX(-50%);
  font-family:var(--mono);font-size:10.5px;font-weight:500;color:var(--ink-soft);
  padding-bottom:3px;line-height:1
}
.bar.cmp b,.bar.swp b{color:var(--ink);font-weight:600}
.bars.dense .bar b{display:none}

/* The sentence explaining the frame. Fixed minimum height so the layout does
   not jump as the text changes length during playback. */
.note{
  display:flex;align-items:center;gap:10px;min-height:44px;
  margin:12px 0 0;padding:9px 12px;font-size:13.5px;
  background:var(--sunken-2);border:1px solid var(--rule);border-radius:4px;color:var(--ink)
}
.note .pin{flex:0 0 auto;width:9px;height:9px;border-radius:50%;background:var(--bar)}
.note.cmp .pin{background:var(--bar-cmp)}
.note.swp .pin{background:var(--bar-swap)}
.note.done .pin{background:var(--bar-done)}
.note.mark .pin{background:var(--blue-deep)}
.note code{font-family:var(--mono);font-size:.93em}
/* The value currently held outside the array, shown beside the narration so
   there is somewhere to look for it while its slot stands empty. */
.note .held{
  margin-left:auto;flex:0 0 auto;font-family:var(--mono);font-size:12px;
  background:var(--surface);border:1px solid var(--rule);border-radius:3px;
  padding:2px 8px;color:var(--ink-soft);white-space:nowrap
}
.note .held b{color:var(--ink);font-weight:600}
.note .held[hidden]{display:none}

/* ---------- legend ---------- */
.legend{display:flex;flex-wrap:wrap;gap:12px 16px;margin:11px 0 0}
.legend span{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--ink-soft)}
.legend i{width:11px;height:11px;border-radius:2px;flex:0 0 auto}
.legend i.k-zone{background:var(--bar-zone)}
.legend i.k-done{background:var(--bar-done)}
.legend i.k-mark{background:var(--blue-deep)}
.legend i.k-cmp{background:var(--bar-cmp)}
.legend i.k-swp{background:var(--bar-swap)}
.legend i.k-hole,.legend i.k-stale{background:var(--rule-soft);border:1px dashed var(--ink-soft)}
.legend i.k-span{background:var(--span-fill);border:1px solid var(--span-edge)}
.legend i.k-wall{background:transparent;border-left:2px solid var(--blue-deep);border-radius:0;width:6px}

/* ---------- transport ---------- */
.transport{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:12px}
.tb{
  font-family:var(--sans);font-size:13.5px;font-weight:500;
  background:var(--surface);border:1px solid var(--rule);color:var(--ink);
  padding:8px 14px;border-radius:5px;cursor:pointer;display:inline-flex;align-items:center;gap:7px
}
.tb:hover:not(:disabled){border-color:var(--blue);color:var(--blue)}
.tb:disabled{color:var(--dim);cursor:not-allowed}
.tb.primary{background:var(--blue-deep);border-color:var(--blue-deep);color:var(--on-accent);min-width:104px;justify-content:center}
.tb.primary:hover{background:var(--blue-press);border-color:var(--blue-press);color:var(--on-accent)}
.scrub{flex:1 1 220px;display:flex;align-items:center;gap:10px;min-width:0}
.scrub input{flex:1 1 auto;min-width:0;accent-color:var(--blue)}
.scrub .pos{font-family:var(--mono);font-size:12px;color:var(--ink-soft);white-space:nowrap}
.phase{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:none;color:var(--on-bar)}

/* ---------- counters ---------- */
.counts{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--rule);margin-top:1px}
@media(max-width:640px){.counts{grid-template-columns:repeat(2,1fr)}}
.ct{background:var(--surface);padding:12px 16px 14px;display:grid;gap:4px;min-width:0}
.ct .cl{font-family:var(--cond);text-transform:uppercase;letter-spacing:.1em;font-size:11px;color:var(--ink-soft)}
.ct .cv{font-family:var(--mono);font-size:17px;color:var(--ink)}

/* ---------- code ---------- */
.cols{display:grid;grid-template-columns:1.15fr 1fr;gap:18px;align-items:start;margin-top:18px}
@media(max-width:960px){.cols{grid-template-columns:1fr}}
.code{
  margin:0;font-family:var(--mono);font-size:13px;line-height:1.7;
  background:var(--sunken);border:1px solid var(--rule);border-radius:4px;
  padding:10px 0;overflow-x:auto
}
.code .ln{display:block;padding:0 12px 0 0;white-space:pre;position:relative}
.code .ln .no{display:inline-block;width:34px;padding-right:12px;text-align:right;color:var(--dim);user-select:none}
.code .ln.on{background:var(--amber-bg);box-shadow:inset 3px 0 0 var(--amber)}
.code .ln.on .no{color:var(--amber);font-weight:600}
.code .kw{color:var(--blue-deep);font-weight:600}
.code .fn{color:var(--teal)}
.code .nm{color:var(--amber)}
.code .cm{color:var(--dim)}

/* ---------- facts ---------- */
.facts{display:grid;gap:0}
.ft{display:grid;grid-template-columns:132px minmax(0,1fr);gap:12px;padding:8px 0;border-bottom:1px solid var(--rule-soft)}
.ft:last-child{border-bottom:0}
.ft dt{font-family:var(--cond);text-transform:uppercase;letter-spacing:.1em;font-size:11px;color:var(--ink-soft);margin:0}
.ft dd{margin:0;font-size:13.5px;color:var(--ink)}
.ft dd b{font-family:var(--mono);font-weight:600}
.ft dd .why{display:block;font-size:12.5px;color:var(--ink-soft);margin-top:2px}
.ft dd code{font-family:var(--mono);font-size:.92em}
@media(max-width:520px){.ft{grid-template-columns:1fr;gap:2px}}

.tally{
  margin:14px 0 0;padding:10px 12px;font-size:13px;color:var(--ink-soft);
  background:var(--sunken-2);border:1px solid var(--rule);border-left:3px solid var(--blue);border-radius:4px
}
.tally b{font-family:var(--mono);color:var(--ink);font-weight:600}

.keys{margin:10px 0 0;font-size:12.5px;color:var(--ink-soft)}
.keys kbd{
  font-family:var(--mono);font-size:11.5px;background:var(--surface);
  border:1px solid var(--rule);border-bottom-width:2px;border-radius:3px;padding:1px 5px;color:var(--ink)
}

/* ============================================================================
   ONE-SCREEN LAYOUT

   Opt-in: a page wraps its content in <div class="wrap wide"><div class="screen">
   and gives <main> the class `fit`. Three columns then share the viewport —
   the array and chart on the left, the code in the middle, the costs on the
   right — so the highlighted line and the bars it moved are always in view
   together. Nothing about the markup the engine renders changes; this only
   re-lays it out.

   Code listings too long for a short screen scroll inside their own column,
   (the tally under them stays put), and engine.js keeps the highlighted line
   in view. Narrow or short windows
   fall back to the normal scrolling page at the end of this block.
   ========================================================================= */
.wrap.wide{max-width:none;padding:0 20px}
.screen{height:100vh;height:100dvh;display:flex;flex-direction:column;padding-bottom:14px}
.screen .home{margin-top:12px;font-size:23px}
/* The lede takes all the width the headline leaves, so it sets in two lines
   rather than three. */
.screen header.mast{padding:6px 0 10px;grid-template-columns:auto minmax(0,1fr);gap:6px 48px}
.screen header.mast .lede{max-width:none}
.screen header.mast h1{font-size:clamp(21px,2.3vw,27px)}
.screen header.mast .lede{font-size:13.5px;padding-top:8px}
.screen footer{flex:0 0 auto;margin-top:8px;padding-top:6px;font-size:11.5px;line-height:1.4}

.fit{
  flex:1 1 auto;min-height:0;margin-top:10px;
  display:grid;grid-template-columns:minmax(0,1.4fr) minmax(340px,.95fr) minmax(300px,.92fr);gap:12px
}
.fit > *{min-height:0}
.fit .left{display:flex;flex-direction:column;gap:10px;min-height:0}
.fit .card h2{padding:8px 14px}

/* ---- setup: presets on one row; sliders and the custom box on the next ---- */
.fit .setup{margin-top:0;flex:0 0 auto}
.fit .setup .body{display:flex;flex-wrap:wrap;gap:10px 18px;align-items:center;padding:10px 14px 12px}
.fit .setup .sliders{gap:14px;flex:0 0 auto;flex-wrap:nowrap}
.fit .setup .sl{min-width:150px;flex:0 1 170px;gap:2px}
.fit .setup .sl .top{white-space:nowrap}
.fit .setup .sl label{font-family:var(--cond);text-transform:uppercase;letter-spacing:.1em;font-size:11px;color:var(--ink-soft)}
.fit .setup .sl input{margin:0}
/* The box's label stays for screen readers; its placeholder carries the hint. */
.fit .setup .row{flex:1 1 240px;gap:8px;align-items:center}
.fit .setup .fld.grow{flex:1 1 140px}
.fit .setup .fld label{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.fit .setup .fld input[type=text]{padding:6px 9px}
.fit .setup .tb{padding:6px 12px;font-size:12.5px;flex:0 0 auto;white-space:nowrap}
.fit .setup .err{flex:1 1 100%}

/* ---- stage: the chart takes whatever height is left ---- */
.fit #sort-stage{flex:1 1 auto;min-height:0;display:flex}
.fit .stage{margin-top:0;flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
.fit .stage .body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding:10px 14px 10px}
.fit .stage .bars{flex:1 1 auto;height:auto;min-height:100px}
.fit .stage .aux{margin-top:8px;padding:7px 10px}
.fit .stage .aux-label{margin-bottom:14px}
.fit .stage .aux-run{height:40px}
.fit .stage .note{min-height:38px;margin-top:8px;padding:6px 11px;font-size:13px}
.fit .stage .legend{margin-top:7px}
.fit .stage .transport{margin-top:8px}
.fit .stage .transport .tb{padding:6px 12px;font-size:12.5px}
.fit .stage .keys{margin-top:6px;font-size:11.5px}
.fit .counts{grid-template-columns:repeat(4,1fr)}
.fit .counts .ct{padding:7px 14px 8px;gap:1px}
.fit .counts .cv{font-size:15px}

/* ---- code and costs: full-height columns ---- */
.fit #sort-code, .fit .costs{display:flex;flex-direction:column;min-height:0}
.fit #sort-code .card, .fit .costs{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
.fit #sort-code .body, .fit .costs .body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:10px 12px 12px}
/* The listing scrolls inside its own box when it is too long for the screen;
   the tally under it stays put. */
.fit #sort-code .body{display:flex;flex-direction:column;overflow:hidden}
.fit .code{flex:0 1 auto;min-height:0;overflow:auto;font-size:12px;line-height:1.45;padding:8px 0}
.fit .tally{flex:0 0 auto;margin-top:10px;font-size:12.5px;padding:8px 11px}
/* Long lines wrap rather than hide behind a sideways scroll, with the
   continuation hanging past the line numbers. */
.fit .code .ln{white-space:pre-wrap;padding-left:46px;text-indent:-46px}
.fit .code .ln .no{text-indent:0}
/* Heading and verdict on one line, the explanation under them. */
.fit .facts{margin:0}
.fit .ft{display:block;padding:5px 0}
.fit .ft:first-child{padding-top:0}
.fit .ft dt, .fit .ft dd{display:inline}
.fit .ft dt{margin-right:8px}
.fit .ft dd{font-size:13px}
.fit .ft dd .why{font-size:11.5px;line-height:1.42;margin-top:1px}
.fit .costs .tally, .fit .costs .tree-note{margin-top:8px !important;font-size:12px;padding:8px 11px}

@media (max-width:1200px), (max-height:640px){
  .screen{height:auto}
  .fit{display:block}
  .fit .left{display:block}
  .fit .stage{margin-top:14px}
  .fit .stage .bars{height:280px;flex:none}
  .fit .stage .aux-run{height:64px}
  .fit #sort-code, .fit .costs{margin-top:14px}
  .fit #sort-code .body, .fit .costs .body{overflow:visible}
}
