/* Guides and the architecture page.
 *
 * 24 pages, 23 of them structurally identical, all converted by script. Their markup was
 * kept as-is and this maps the class names they already use onto the shared tokens. The
 * guides are the site's SEO surface and carry per-page structured data, internal links and
 * dozens of screenshots each, so rewriting their markup for looks was never worth the
 * risk. .prose in ar-site.css handles the running text; everything below is the furniture
 * the guides built for themselves.
 */

/* --- Breadcrumbs the guides already had ---------------------------------- */
.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--ar-s-2);
  margin-bottom: var(--ar-s-5);
  font-family: var(--ar-font-mono); font-size: 10.5px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase; color: var(--ar-text-3);
}
.crumbs a { color: var(--ar-text-2); }
.crumbs a:hover { color: var(--ar-text); text-decoration: none; }
.crumbs .sep { color: var(--ar-line-2); }

.guide-meta, .meta {
  font-family: var(--ar-font-mono); font-size: 10.5px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase; color: var(--ar-text-3);
  margin-bottom: var(--ar-s-5);
}
/* Every guide's meta line is a run of bare <span>s. Unstyled they ran together into one
   uppercase sentence - "UPDATED JUNE 2026 MACOS 14.2+ OR WINDOWS 10+ ABLETON LIVE 11+" -
   which reads as a typo rather than as five facts. */
.guide-meta, .meta { display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px var(--ar-s-3); }
/* ::after on the preceding item, not ::before on the next: as a ::before the separator can
   be pushed onto the next line by a wrap and then reads as a stray bullet at the start of
   the line, which is how it first rendered. */
.guide-meta > span:not(:last-child)::after,
.meta > span:not(:last-child)::after {
  content: "\00a0\00b7"; color: var(--ar-line-2);
}

/* --- Table of contents --------------------------------------------------- */
.toc {
  border: var(--ar-border); background: var(--ar-ink);
  padding: var(--ar-s-4) var(--ar-s-5); margin: var(--ar-s-5) 0 var(--ar-s-6);
  max-width: 74ch;
}
.toc-label {
  display: block; margin-bottom: var(--ar-s-3);
  font-family: var(--ar-font-mono); font-size: 10px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase; color: var(--ar-text-2);
}
.toc ol, .toc ul { margin: 0; padding-left: var(--ar-s-5); }
.toc li { margin-bottom: 6px; font-size: 14.5px; }

/* --- Step labels and numeric cells -------------------------------------- */
/* I styled both of these as a 24px circular badge on the assumption they held a bare
   digit. Neither does.
   .step-num contains the words "Step 1" and is followed by <br> in 76 of 80 uses: it is an
   eyebrow ABOVE the heading, not a badge beside it, and forcing six characters into a 24px
   box wrapped it onto two lines inside the circle.
   .num is worse - it is a TABLE CELL class carrying measurement values like
   "0.5000 (-6.0 dBFS)". As a 24px badge it rendered 34x24 with the value clipped, which
   broke the results tables in the technical guides. */
.step-num {
  display: inline-block; margin-bottom: 6px;
  font-family: var(--ar-font-mono); font-size: 10px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
  color: var(--ar-record); line-height: 1.2;
}
/* Measurement values: monospaced and tabular so columns of figures line up, and right
   aligned because that is how numbers are read against each other. */
td.num, .num {
  font-family: var(--ar-font-mono); font-size: 13px;
  font-variant-numeric: tabular-nums; color: var(--ar-text);
  white-space: nowrap;
}
td.num { text-align: right; }
.guide-section { margin-top: var(--ar-s-7); }

/* --- Callouts ------------------------------------------------------------ */
.callout {
  border: var(--ar-border); border-left: 2px solid var(--ar-line-2);
  background: var(--ar-ink); padding: var(--ar-s-4);
  margin: var(--ar-s-5) 0; max-width: 74ch;
  font-size: 15px; color: var(--ar-text-2);
}
.callout.warn, .callout.warning { border-left-color: var(--ar-amber); }
.callout.tip, .callout.ok { border-left-color: var(--ar-green); }
.callout p:last-child { margin-bottom: 0; }

/* --- Figures ------------------------------------------------------------- */
/* The guides size screenshots by intent rather than by pixels. Each caps its width and
   keeps the image inside a hairline so a light screenshot does not bleed into the page. */
figure { margin: var(--ar-s-5) 0; }
figure img { display: block; width: 100%; height: auto; border: var(--ar-border); }
figure figcaption {
  margin-top: var(--ar-s-2);
  font-family: var(--ar-font-mono); font-size: 10.5px; letter-spacing: .06em;
  color: var(--ar-text-3); line-height: 1.6;
}
.figure-native  { max-width: none; }
.figure-medium  { max-width: 720px; }
.figure-narrow  { max-width: 520px; }
.figure-tiny    { max-width: 320px; }
.figure-overlay { max-width: 620px; }

/* --- Plain YouTube embed ------------------------------------------------- */
/* Two guides embed the video directly rather than behind the click-to-load facade. Both
   carried an inline `position:relative; padding-bottom:56.25%; height:0; margin:1.5em auto`
   wrapper, and that broke the moment the article column became a grid: auto inline margins
   make a grid item shrink-to-fit, and an item whose only child is absolutely positioned
   measures ZERO wide. The video collapsed to an empty gap on both pages, at 1180px and up
   only - narrow windows kept plain block flow, which is why it looked fine on phones and
   why the width sweep passed.
   aspect-ratio replaces the padding hack, and the explicit width means the auto margins
   have no free space to absorb, so it cannot collapse again. */
.video-frame {
  position: relative;
  width: 100%; max-width: 720px;
  margin: var(--ar-s-5) auto 0;
  aspect-ratio: 16 / 9;
  background: #000; border: var(--ar-border);
}
.video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* --- YouTube facade ------------------------------------------------------ */
/* Two guides embed a walkthrough behind a click-to-load facade (it saves ~600 KB of
   third-party JS and defers YouTube's cookies until the visitor actually asks for the
   video). All of its styling lived in the inline <style> block the conversion removed, so
   it had been rendering as a bare thumbnail with a default empty <button> below it - the
   small grey dash above the meta line. The activation script was never touched, so the
   video still played; it just had nothing to click.
   Restyled to the site's language rather than restored verbatim: hairline instead of a
   10px radius and a 36px drop shadow, and the disc uses the brand's record red. */
.video-embed {
  position: relative; display: block; margin: var(--ar-s-5) 0 var(--ar-s-6);
  aspect-ratio: 16 / 9; overflow: hidden; cursor: pointer;
  background: #000; border: var(--ar-border);
}
.video-embed img {
  display: block; width: 100%; height: 100%; object-fit: cover; border: 0;
  transition: transform .4s ease, opacity .3s ease;
}
.video-embed:hover img, .video-embed:focus-visible img { transform: scale(1.015); opacity: .92; }
.video-embed:focus-visible { outline: var(--ar-focus); outline-offset: var(--ar-focus-offset); }
.video-embed .play-btn {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 64px; height: 64px; border-radius: 50%; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--ar-record); border: 0; cursor: pointer;
  transition: transform .2s ease, background .2s ease;
}
.video-embed:hover .play-btn { transform: translate(-50%, -50%) scale(1.06); }
/* The play triangle. The button is deliberately empty in the markup (it carries only an
   aria-label), so the glyph is drawn here. */
.video-embed .play-btn::after {
  content: ""; display: block; width: 0; height: 0; margin-left: 4px;
  border-style: solid; border-width: 10px 0 10px 17px;
  border-color: transparent transparent transparent #fff;
}
.video-embed .video-label {
  position: absolute; top: 10px; left: 10px; z-index: 1;
  padding: 5px 9px; background: rgba(0,0,0,.72); color: #fff;
  font-family: var(--ar-font-mono); font-size: 10px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
}
/* Once activated the script replaces the contents with the real iframe. */
.video-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
@media (max-width: 620px) {
  .video-embed .play-btn { width: 52px; height: 52px; }
  .video-embed .play-btn::after { border-width: 8px 0 8px 14px; }
}

/* --- Comparison and results tables --------------------------------------- */
/* .compare is TWO different things in this codebase, which is why three comparison guides
   were rendering with no table styling whatsoever - no rules, no header treatment, browser
   default padding:
     <div class="compare">    - a horizontal scroll container (on-demand-mode, voicemeeter,
                                sound-siphon)
     <table class="compare">  - the table ITSELF (audioroute-vs-blackhole, loopback-
                                alternative, audio-hijack-alternative)
   The old rule only styled `.compare` as a scroll container, so on the three pages where it
   names a table it did nothing at all. Element-qualified selectors separate them. */
table.compare, .compare-table, .results-table {
  width: 100%; border-collapse: collapse; font-size: 14px; margin: var(--ar-s-5) 0;
}
/* 10px rather than the 16px token either side. A seven-column comparison needed 640px of
   min-content in a 570px measure and so carried a horizontal scrollbar at every desktop
   width - and most of the excess was cell padding, which is a cheaper thing to spend than
   the reading width or the type size. */
table.compare th, table.compare td,
.compare-table th, .compare-table td,
.results-table th, .results-table td {
  padding: 9px 10px; text-align: left;
  /* .055 alpha was invisible against the panel: rows ran together into a block of text,
     which is what made these tables read as messy rather than as a table. */
  border-bottom: 1px solid rgba(255,255,255,.09);
  vertical-align: top;
}
table.compare thead th, .compare-table thead th, .results-table thead th {
  font-family: var(--ar-font-mono); font-size: 10.5px; letter-spacing: var(--ar-tracking-label);
  text-transform: uppercase; color: var(--ar-text-2); font-weight: 400;
  background: var(--ar-ink); border-bottom: var(--ar-border);
  vertical-align: bottom;
}
table.compare td, .compare-table td, .results-table td { color: var(--ar-text-2); }
/* The left-hand column of a side-by-side names the row, so it is a subject, not a value. */
table.compare tbody th, .compare-table tbody th, .results-table tbody th {
  color: var(--ar-text); font-weight: 500;
}
table.compare td:first-child, .compare-table td:first-child, .results-table td:first-child {
  color: var(--ar-text);
}
table.compare tbody tr:last-child th, table.compare tbody tr:last-child td,
.compare-table tbody tr:last-child td, .results-table tbody tr:last-child td { border-bottom: 0; }

/* The scroll-container sense of .compare. */
div.compare, .table-scroll { overflow-x: auto; margin: var(--ar-s-5) 0; }

.yes, .verdict-ok { color: var(--ar-green); }
.no  { color: var(--ar-text-2); }
.warn, .partial { color: var(--ar-amber); }
.paid { color: var(--ar-text-2); }

.pill {
  display: inline-block; padding: 2px 8px;
  border: var(--ar-border); background: var(--ar-ink);
  font-family: var(--ar-font-mono); font-size: 10px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase; color: var(--ar-text-2);
}

/* --- Guide index cards --------------------------------------------------- */
.guide-card {
  display: block; border: var(--ar-border); background: var(--ar-panel-2);
  padding: var(--ar-s-5); margin-bottom: var(--ar-s-3);
}
.guide-card:hover { border-color: var(--ar-line-2); text-decoration: none; }
.guide-card h3, .guide-card h2 { margin: 0 0 var(--ar-s-2); font-size: 17px; color: var(--ar-text); }
.guide-card p { margin: 0; color: var(--ar-text-2); font-size: 14.5px; }

/* --- Classes the conversion left with no rule at all --------------------- */
/* Found by diffing every class used across the 26 pages against the three sheets they
   load, after the YouTube facade turned up completely unstyled. Two of these mattered
   structurally rather than cosmetically: .table-scroll is the container that keeps a
   nine-column comparison table from widening the page, and .opt-grid is a two-column
   layout that had collapsed to a stack of unbounded blocks. */

.tprice { font-size: 13px; color: var(--ar-text-3); }

/* Side-by-side option blocks. .col already carries min-width:0 and collapses at 820px. */
.opt-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ar-s-5); margin: var(--ar-s-5) 0;
}
.opt-grid h4 { margin: 0 0 var(--ar-s-2); font-size: 15.5px; }
.opt-meta {
  margin: 0 0 var(--ar-s-3);
  font-family: var(--ar-font-mono); font-size: 11px; color: var(--ar-text-3);
}

/* Two more summary blocks, in the same role as .quick-answer on the other guides: the one
   paragraph a reader who reads nothing else should get. They were rendering as plain body
   copy with a bold line above them. */
.tldr {
  border: var(--ar-border); border-left: 2px solid var(--ar-record);
  background: var(--ar-ink);
  padding: var(--ar-s-5); margin: var(--ar-s-5) 0 var(--ar-s-6);
}
.tldr p:last-child { margin-bottom: 0; }
.tldr-label, .summary-label {
  margin-bottom: var(--ar-s-3);
  font-family: var(--ar-font-mono); font-size: 10px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
  color: var(--ar-record);
}

/* Pill variants. */
.pill.free { color: var(--ar-green); border-color: rgba(76,175,80,.3); }
.pill.alt  { color: var(--ar-amber); border-color: rgba(232,163,61,.3); }
/* .callout already handles .warn and .tip; .danger is the third state it was missing. */
.callout.danger { border-left-color: var(--ar-record); }

.figure-wide { max-width: none; }
.lead { font-size: 17.5px; color: var(--ar-text-2); }
.back {
  display: inline-block; margin-bottom: var(--ar-s-4);
  font-family: var(--ar-font-mono); font-size: 10.5px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
  color: var(--ar-text-2); text-decoration: none;
}
.back:hover { color: var(--ar-text); text-decoration: none; }

/* --- Closing CTA the guides end on -------------------------------------- */
.cta-box {
  border: var(--ar-border); background: var(--ar-ink);
  padding: var(--ar-s-6); margin: var(--ar-s-7) 0 0; max-width: 74ch;
}
.cta-box h2, .cta-box h3 { margin-top: 0; }
.btn-secondary {
  background: transparent; border-color: var(--ar-line);
  color: var(--ar-text-2); font-weight: 400;
}
.btn-secondary:hover { color: var(--ar-text); border-color: var(--ar-line-2); background: transparent; }

/* Two-column blocks inside guides collapse rather than squeeze. */
.col { min-width: 0; }
@media (max-width: 820px) { .col { grid-column: 1 / -1; } }

/* Phones: comparison and results tables stop being tables ---------------- */
/* Same treatment as the landing page's comparison. A multi-column table cannot fit 344px,
   and the alternative - scrolling it sideways - is the cropped-content-behind-a-scrollbar
   that reads as a broken page. Each row becomes a card, with every value labelled by its
   column name from the data-col attribute a script copied out of the thead. */
@media (max-width: 620px) {
  .compare-table, .results-table { display: block; }
  .compare-table thead, .results-table thead { display: none; }
  .compare-table tbody, .compare-table tr, .compare-table th, .compare-table td,
  .results-table tbody, .results-table tr, .results-table th, .results-table td { display: block; }
  .compare-table tbody tr, .results-table tbody tr {
    border: var(--ar-border); margin-bottom: var(--ar-s-3); padding: var(--ar-s-2) 0;
  }
  .compare-table tbody th, .results-table tbody th {
    border-bottom: 1px solid rgba(255,255,255,.055);
    color: var(--ar-text); padding: 8px var(--ar-s-4);
  }
  .compare-table tbody td, .results-table tbody td {
    display: grid; grid-template-columns: 40% 1fr; gap: var(--ar-s-3);
    align-items: baseline; border-bottom: 0; padding: 5px var(--ar-s-4); font-size: 13px;
  }
  .compare-table tbody td::before, .results-table tbody td::before {
    content: attr(data-col);
    font-family: var(--ar-font-mono); font-size: 9.5px; letter-spacing: .08em;
    text-transform: uppercase; color: var(--ar-line-2);
  }
  /* a cell with no column name (a spanning or spacer cell) should not reserve the label gutter */
  .compare-table tbody td:not([data-col])::before,
  .results-table tbody td:not([data-col])::before { content: ""; }
}

/* ============================================================================
   Guides index — a directory, not an article
   ============================================================================
   This page is one of the most visited on the site and it was the weakest. What was
   wrong, in order of how much it hurt:

     1. Every card was underlined. .guide-card is a block-level <a>, so the shell's
        running-text underline rule struck through the title, all three lines of the
        description and the meta row. The page read as a wall of links.
     2. It sat inside .prose, so a 22-card directory was squeezed into a 68ch reading
        measure with two thirds of the rack empty beside it.
     3. .guide-jump - the category nav the old page had, still present in the markup -
        had no CSS at all, so the site's navigation rendered as four stray links.
     4. The cards had no hover state and no affordance: nothing said "clickable".
     5. Section headings looked like body text rather than the site's .sec-h idiom.
   ============================================================================ */

/* Bigger than the shell's document h1. This is a landing page for 22 guides and the
   site's second most visited surface, so the title carries the weight of one. */
.guide-index h1 { font-size: clamp(36px, 5vw, 56px); margin-bottom: var(--ar-s-4); }
.guide-index > .lede { max-width: 62ch; margin-bottom: var(--ar-s-6); }
/* Taking this page out of .prose also took the underline off its inline links. Colour
   alone is not a reliable signal, so running-text links get it back - scoped so the card
   anchors and the nav pills stay clean. */
.guide-index p a:not(.guide-card):not(.btn) {
  text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px;
}

/* --- Category navigation ------------------------------------------------- */
/* The whole point of an index is getting somewhere. This is the first thing under the
   lede and it stays legible as a row of targets rather than a run of prose links. */
.guide-jump {
  display: flex; flex-wrap: wrap; gap: var(--ar-s-3);
  border-top: var(--ar-border);
  padding: var(--ar-s-5) 0 0; margin-bottom: var(--ar-s-8);
}
.guide-jump a {
  display: inline-flex; align-items: center; min-height: 34px; padding: 0 var(--ar-s-4);
  border: 1px solid var(--ar-line-2); background: var(--ar-ink);
  color: var(--ar-text); text-decoration: none;
  font-family: var(--ar-font-mono); font-size: 11px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
  transition: color 140ms ease-out, border-color 140ms ease-out, background 140ms ease-out;
}
.guide-jump a:hover {
  color: var(--ar-text); border-color: var(--ar-line-2); background: var(--ar-panel);
  text-decoration: none;
}

/* --- Section headings --------------------------------------------------- */
/* Matches the .sec-h idiom used everywhere else: a hairline, then the heading. */
h2.guide-section {
  font-size: clamp(20px, 2.2vw, 26px);
  border-top: var(--ar-border); padding-top: var(--ar-s-5);
  margin: var(--ar-s-8) 0 var(--ar-s-5);
}
/* The category nav already separates the header from the first section, so the first
   heading drops its own rule instead of drawing a third line in the same space. */
h2.guide-section:first-of-type { margin-top: 0; border-top: 0; padding-top: 0; }

/* --- The grid ----------------------------------------------------------- */
/* Three across in the 1240 rack, two on a laptop, one on a phone. 22 guides in a single
   column is a scroll; in a grid it is a menu you can scan. */
.guide-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--ar-s-4); margin-bottom: var(--ar-s-6);
}

/* --- The card ----------------------------------------------------------- */
.guide-index .guide-card {
  position: relative; display: flex; flex-direction: column; gap: var(--ar-s-3);
  border: var(--ar-border); background: var(--ar-panel-2);
  padding: var(--ar-s-5) var(--ar-s-5) var(--ar-s-6);
  margin: 0; text-decoration: none;
  transition: border-color 160ms ease-out, background 160ms ease-out, transform 160ms ease-out;
}
.guide-index .guide-card:hover {
  border-color: var(--ar-line-2); background: var(--ar-panel);
  transform: translateY(-1px); text-decoration: none;
}
.guide-index .guide-card h3 {
  margin: 0; font-size: 16.5px; line-height: 1.3; color: var(--ar-text);
  text-decoration: none;
}
.guide-index .guide-card:hover h3 { color: #FFFFFF; }
.guide-index .guide-card p {
  margin: 0; font-size: 14px; line-height: 1.6; color: var(--ar-text-2);
  text-decoration: none;
}
/* The meta row is the scannable part - platform and reading time - so it sits at the
   bottom of every card on a common baseline regardless of description length. */
.guide-index .guide-card .guide-meta {
  margin: auto 0 0; padding-top: var(--ar-s-2);
  font-family: var(--ar-font-mono); font-size: 9.5px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
  color: var(--ar-text-3); text-decoration: none;
}
/* An arrow that only appears on hover: the affordance without the clutter. */
.guide-index .guide-card::after {
  content: "\2192"; position: absolute; right: var(--ar-s-5); bottom: var(--ar-s-4);
  color: var(--ar-record); font-size: 14px; opacity: 0;
  transform: translateX(-4px);
  transition: opacity 160ms ease-out, transform 160ms ease-out;
}
.guide-index .guide-card:hover::after { opacity: 1; transform: translateX(0); }
.guide-index .guide-card:focus-visible { outline: var(--ar-focus); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .guide-index .guide-card, .guide-index .guide-card::after { transition: none; }
  .guide-index .guide-card:hover { transform: none; }
}

/* --- Atmosphere on the index header ------------------------------------- */
/* A darkened plate behind the page title, using the same image as the landing hero rather
   than generating anything new. Same discipline as the hero, for the same reason: screen
   blending only lifts highlights, a horizontal scrim keeps the copy column flat, and the
   plate is masked out in px from the top so it can never reach the lede or the category
   nav. Measured after: title zone lifted, copy zones untouched. */
.guide-index { position: relative; overflow: hidden; }
.guide-index > * { position: relative; z-index: 1; }
.guide-index::before, .guide-index::after { content: ""; position: absolute; inset: 0; z-index: 0; }
.guide-index::before {
  /* Light, not a photograph. The hero plate cropped into this band read as a grey diagonal
     streak - structure with no context, which looks like a smudge on the display rather
     than a lit room. Two soft radials have no structure to misread: one warm source high
     on the right, one cool fill behind the title. */
  background-image:
    radial-gradient(120% 160% at 80% -22%, rgba(255,178,108,.30), rgba(255,178,108,.09) 36%, transparent 68%),
    radial-gradient(95% 130% at 10% -32%, rgba(190,215,255,.10), transparent 58%);
  background-repeat: no-repeat;
  mix-blend-mode: screen; opacity: 1;
  -webkit-mask-image: linear-gradient(180deg, #000 74px, rgba(0,0,0,0) 132px);
          mask-image: linear-gradient(180deg, #000 74px, rgba(0,0,0,0) 132px);
}
.guide-index::after {
  /* Opaque only to 8%, not 34%. At 34% the scrim covered the title column exactly, so the
     plate lit nothing but empty space to its right - measured +0.0 lift on the h1. The
     title is 42px --ar-text against a 3:1 floor, so it has the headroom to carry light;
     what protects the lede and the category nav below it is the MASK, which ends the plate
     at 132px, not this scrim. */
  background: linear-gradient(90deg, var(--ar-bg) 8%, rgba(30,30,30,.42) 46%, rgba(30,30,30,.22) 100%);
}
/* Single column: the copy spans the full width, so a scrim protecting the left column
   protects nothing. Same call as the landing hero. */
@media (max-width: 1000px) {
  .guide-index::before, .guide-index::after { display: none; }
}

/* --- Balanced rows ------------------------------------------------------- */
/* Two sections hold exactly four cards, which in a three-across grid leaves one card
   alone on a second row beside two empty cells - the same orphan that read as a mistake
   in the footer and the press strip. :has() lets a four-card section lay out 2x2 without
   per-section markup. Six-card sections already fill 3+3; the eight-card section ends on a
   row of two, which reads as a pair rather than a leftover. */
.guide-grid:has(> .guide-card:nth-child(4):last-child) {
  grid-template-columns: repeat(2, minmax(300px, 1fr));
}
@media (max-width: 700px) {
  .guide-grid, .guide-grid:has(> .guide-card:nth-child(4):last-child) {
    grid-template-columns: 1fr;
  }
}

/* --- Header presence ---------------------------------------------------- */
/* The kicker carries the page's scale, so it gets room, and the plate above is pulled
   deeper so the title sits IN the light rather than beside it. */
/* The kicker sits where the light is strongest, and at 10px grey it measured 4.49:1 -
   one hundredth under the floor. Brightening it is the better fix than dimming the light:
   "22 guides" is the page's most persuasive line and it should be legible. */
.guide-index .hero-kicker { margin-bottom: var(--ar-s-4); }
.guide-index .hero-kicker .lab { color: var(--ar-text); }
.guide-index::before {
  /* Measured from the live DOM: kicker 48-73, h1 89-132, lede 148-203. The plate is solid
     across the kicker and title and fully gone by 142px, six pixels before the lede starts,
     so the 18px grey lede never sits on lifted ground. */
  -webkit-mask-image: linear-gradient(180deg, #000 0 126px, rgba(0,0,0,0) 142px);
          mask-image: linear-gradient(180deg, #000 0 126px, rgba(0,0,0,0) 142px);
  opacity: .62;
}

/* ============================================================================
   Guide layout — the measure belongs to the column, not the page
   ============================================================================
   .prose caps its own max-width at 68ch, which capped the whole SECTION. On a 1240px rack
   that left a 640px ribbon of text with 600px of nothing beside it: correct line length,
   wrong page. A long-form guide should keep the measure and use the remaining width for
   something worth having, which is the contents list - so it stops being an inline block
   the reader scrolls past and becomes a rail that tracks where they are.
   ============================================================================ */

/* The guides wrap all their content in a single .wrap div, so that is the grid container. */
.band.prose:has(> .wrap) { max-width: none; }
/* px, not ch: ch resolves against the element's own font, and on this wrapper that gave
   685px - 84 characters per line, well past comfortable. Measured at 8.15px per character,
   so 570px is about 70. */
/* margin-inline is not optional. Nothing in the shell gives .wrap auto margins, so between
   the 570px cap and the 1180px grid the column pinned itself to the left edge of the rack
   and left ~500px of empty panel to the right of every paragraph. On screen that reads as a
   frame drawn in the wrong place rather than as a measure: the text should sit between the
   two rules, not against one of them. */
.band.prose > .wrap { max-width: 570px; margin-inline: auto; }

/* :has(> .toc) - the rail layout is only correct for a page that HAS a contents list.
   /architecture had none, so it got the two-column grid with an empty 300px column, which
   pushed its article column off-centre for the same reason as above. */
@media (min-width: 1180px) {
  .band.prose > .wrap:has(> .toc) {
    max-width: none;
    display: grid;
    /* Cap the rail. As minmax(230px, 1fr) it absorbed every spare pixel of the rack and
       gave a seven-item contents list 535px, which is what made the page read as a narrow
       strip of text beside a sparse box. 300px is enough for the longest entry at 13.5px.
       The pair is then centred in the rack so the leftover width sits either side of a
       composed block instead of being dumped in one column. */
    grid-template-columns: minmax(0, 570px) minmax(220px, 300px);
    justify-content: center;
    column-gap: var(--ar-s-8);
    align-items: start;
  }
  /* Everything is article column by default; min-width:0 so a wide figure or table shrinks
     to the column instead of forcing the grid wider. */
  .band.prose > .wrap:has(> .toc) > * { grid-column: 1; min-width: 0; }

  /* The contents list moves into the rail and follows the reader. 68px clears the 52px
     sticky chassis with room to breathe. */
  .band.prose > .wrap > .toc {
    grid-column: 2;
    grid-row: 2 / span 500;
    position: sticky; top: 68px;
    margin: 0; max-width: none;
    align-self: start;
    max-height: calc(100vh - 96px); overflow-y: auto;
  }
  .band.prose > .wrap > .toc ol,
  .band.prose > .wrap > .toc ul { padding-left: var(--ar-s-4); }
  .band.prose > .wrap > .toc li { font-size: 13.5px; line-height: 1.5; }
  /* The shell sets columns:2 for a contents list sitting in the body flow. In a 300px
     rail that wraps four-word entries against each other. */
  .band.prose > .wrap > .toc ul { columns: 1; }
}

/* A guide's own figures may use the full article column, but no more. */
.band.prose > .wrap > figure { max-width: 100%; }

/* ---------------------------------------------------------------------------
   Wide comparisons
   ---------------------------------------------------------------------------
   A seven-column table in a 570px column wraps every cell onto three or four lines: it
   technically fits and is still unreadable. So on the pages built around a comparison, the
   table gets more width than the measure.

   The first attempt spanned it across both grid columns and made the contents list
   non-sticky so a tracking rail could not slide under a full-width table. That was worse:
   `grid-row: auto` places the list at its SOURCE position - after the lede and the quick
   answer - so the rail appeared halfway down the page, and because it is taller than the
   paragraph it shared a row with, it tore a 300px hole in the article column.

   These pages therefore have no rail at all. One centred column, contents inline at the top
   exactly as they render on a phone, and the table pulled wider than the column by half its
   excess on each side so it stays centred on the text above it. Nothing overlaps, nothing is
   off-centre, and there is no empty 300px track.
   --------------------------------------------------------------------------- */
@media (min-width: 1180px) {
  .band.prose > .wrap:has(.table-scroll),
  .band.prose > .wrap:has(> table.compare),
  .band.prose > .wrap:has(> div.compare) {
    display: block;
    max-width: 570px;
    margin-inline: auto;
  }
  /* The contents list drops back to being a block in the column. */
  .band.prose > .wrap:has(.table-scroll) > .toc,
  .band.prose > .wrap:has(> table.compare) > .toc,
  .band.prose > .wrap:has(> div.compare) > .toc {
    position: static; max-width: none; max-height: none; overflow: visible;
    margin: var(--ar-s-5) 0 var(--ar-s-6);
  }
  /* 934px matches the composed width the rail pages use, so the two layouts agree about how
     wide "wide" is. The negative margins are half the excess, which centres the table on the
     570px measure rather than letting it hang off to one side. */
  .band.prose > .wrap > .table-scroll,
  .band.prose > .wrap > div.compare,
  .band.prose > .wrap > table.compare {
    --ar-wide: 934px;
    width: var(--ar-wide);
    max-width: none;
    margin-inline: calc((570px - var(--ar-wide)) / 2);
  }
}

/* ============================================================================
   /architecture
   ============================================================================
   Four faults, all of them layout rather than copy:

   1. "The short version" was two paragraphs under an h2, indistinguishable from the six
      sections of detail below it. It is an abstract, so it now looks like one.
   2. The signal-flow diagram was box-drawing characters in a bare <div> with no CSS at
      all, so it reflowed as ordinary prose - 17 lines of pipes and corners shuffled into
      a paragraph. It is now built out of elements.
   3. <span class="pill">1</span>Daemon had no space between the number and the title,
      because .pill is a standalone tag elsewhere and carries no trailing margin.
   4. The article column sat against the left rule of the rack (fixed above).
   ============================================================================ */

/* --- The abstract -------------------------------------------------------- */
/* Same red left rule as .quick-answer in the guides: on this site that mark means "read
   this bit if you read nothing else". The heading drops to a mono eyebrow so the block
   reads as a preface to the article rather than as its first section. */
.summary {
  border: var(--ar-border); border-left: 2px solid var(--ar-record);
  background: var(--ar-ink);
  padding: var(--ar-s-5) var(--ar-s-5) var(--ar-s-2);
  margin: var(--ar-s-6) 0;
}
.prose .summary h2, .summary h2 {
  margin: 0 0 var(--ar-s-3);
  font-family: var(--ar-font-mono); font-size: 10.5px; font-weight: 400;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
  color: var(--ar-record);
}
.summary p:last-child { margin-bottom: var(--ar-s-4); }

/* --- Section separation -------------------------------------------------- */
/* Direct children only, so the abstract's nested h2 keeps its eyebrow treatment. */
.arch > .wrap > h2 {
  border-top: var(--ar-border);
  padding-top: var(--ar-s-5);
  margin-top: var(--ar-s-6);
}

/* --- Numbered component blocks ------------------------------------------ */
/* A hairline rail per component: enough to separate four blocks that are all prose,
   without four boxes fighting the diagram above them for attention. */
.arch .card {
  border-left: 1px solid var(--ar-line-2);
  padding-left: var(--ar-s-5);
  margin: var(--ar-s-5) 0;
}
.arch .card h3 {
  display: flex; align-items: baseline; gap: 10px;
  margin-top: 0;
}
.card-num {
  flex: none;
  font-family: var(--ar-font-mono); font-size: 11px; font-weight: 400;
  letter-spacing: var(--ar-tracking-label);
  color: var(--ar-record);
}
.card-num::after { content: "."; }

/* --- Signal-flow diagram ------------------------------------------------- */
/* Deliberately not an image: it has to stay legible at 344px, survive a text-size bump,
   and be readable by a screen reader, and the labels are the whole content. Nodes are
   panels, connectors are hairlines with CSS arrowheads. */
.flow {
  margin: var(--ar-s-6) 0;
  padding: var(--ar-s-6) var(--ar-s-5);
  border: var(--ar-border); background: var(--ar-ink);
  display: flex; flex-direction: column; align-items: center; gap: var(--ar-s-3);
}
.flow-node {
  display: flex; flex-direction: column; gap: 2px;
  min-width: 0; padding: 10px var(--ar-s-4);
  border: var(--ar-border); background: var(--ar-panel-2);
  text-align: center;
}
/* Colour carries the page's argument rather than decorating it, and it reuses the grammar
   the homepage diagram already established: blue is audio being captured (.cord-live.in),
   red is audio arriving at its destination (.cord-live.out), and grey is a line with no
   audio on it at all. That last one is the point of the whole section - the tray app talks
   to the daemon over a socket and never touches the signal - so leaving the UDS wire grey
   says it faster than the paragraph underneath does. */
/* A blue fill turned the daemon into what looked like a selected button - the brightest
   object in the frame, and reading as a UI state rather than as a role. The marks that
   carry the meaning are the top edge, the sublabel and the line leaving it, so those are
   blue and the panel stays the same as its neighbour. inset rather than border-top: a 2px
   border would make this node 1px taller than the tray app beside it. */
.flow-node.is-key {
  border-color: var(--ar-blue-24);
  box-shadow: inset 0 2px 0 var(--ar-blue);
}
.flow-node.is-key .flow-s { color: var(--ar-blue); }
/* The output pair keeps neutral borders. Red on both boxes AND their dividers AND their
   labels made them look like a pair of error states; the accent belongs on the one line
   that is actually the "audio arrives here" moment. */
.flow-n { font-size: 14px; color: var(--ar-text); line-height: 1.3; }
.flow-s {
  font-family: var(--ar-font-mono); font-size: 9.5px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
  color: var(--ar-text-3);
}
.flow-lbl {
  font-family: var(--ar-font-mono); font-size: 9.5px; line-height: 1.45;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
  color: var(--ar-text-3); text-align: center;
}

/* Tray app <-> daemon.
   Five columns with matching gutters either side of the daemon, so the DAEMON sits on the
   figure's centre line and the tray app hangs off it to the left. Centring the pair instead
   put the ring-buffer line below the gap between the two boxes, as if the audio came from
   nowhere - it comes out of the daemon. Off-axis is also the right place for the tray app:
   it is a branch, not a stage in the path. */
.flow-pair {
  display: grid; width: 100%;
  grid-template-columns: minmax(0, 1fr) 60px auto 60px minmax(0, 1fr);
  align-items: center;
}
.flow-pair > .flow-node:first-child { justify-self: end; }
.flow-wire {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
}
/* The label rides above the line instead of sharing a flex column with it: as a column the
   pair centred as a unit, which left the line sitting below the middle of the two nodes. */
.flow-pair .flow-wire { position: relative; display: block; padding: 0 var(--ar-s-2); }
.flow-pair .flow-lbl { position: absolute; left: 0; right: 0; bottom: calc(50% + 7px); }
.flow-wire .wire { position: relative; display: block; width: 100%; height: 7px; }
.flow-wire .wire::before {
  content: ""; position: absolute; top: 3px; left: 5px; right: 5px;
  height: 1px; background: var(--ar-line-2);
}
.flow-wire .wire::after, .flow-wire .wire i {
  content: ""; position: absolute; top: 0; display: block;
  width: 5px; height: 7px; background: var(--ar-line-2);
}
.flow-wire .wire::after { right: 0; clip-path: polygon(0 0, 100% 50%, 0 100%); }
.flow-wire .wire i     { left: 0;  clip-path: polygon(100% 0, 0 50%, 100% 100%); }

/* daemon -> ring buffer */
/* The label is taken out of flow so the line itself lands on the diagram's centre axis,
   directly under the daemon-to-plugin path, rather than the line-plus-label pair being
   centred and the line ending up left of everything it connects. */
.flow-drop { position: relative; padding: 2px 0; }
.flow-drop .flow-lbl {
  position: absolute; left: calc(50% + 12px); top: 50%; transform: translateY(-50%);
  /* The containing block is the 7px-wide connector, so without this the two label lines
     wrap one word per line down the side of the arrow. */
  white-space: nowrap; text-align: left;
}
.flow-drop .wire-v { position: relative; display: block; width: 7px; height: 38px; }
/* Dashes travelling down the line, same idea as the animated cords on the homepage, at a
   speed that reads as flow rather than as a loading bar. */
.flow-drop .wire-v::before {
  content: ""; position: absolute; left: 3px; top: 0; bottom: 5px; width: 1px;
  /* Two layers: a continuous line that is always there, and one bright segment travelling
     down it. The line never looks broken, and the movement reads as signal. */
  background-color: var(--ar-blue-24);
  background-image: linear-gradient(to bottom, transparent 0 20%, var(--ar-blue) 35% 65%, transparent 80% 100%);
  background-size: 1px 160%; background-repeat: no-repeat;
  animation: ar-drop-flow 2.2s linear infinite;
}
@keyframes ar-drop-flow {
  from { background-position: 0 -160%; }
  to   { background-position: 0 160%; }
}
@media (prefers-reduced-motion: reduce) {
  .flow-drop .wire-v::before {
    animation: none; background-image: none; background-color: var(--ar-blue); opacity: .7;
  }
}
.flow-drop .wire-v::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 7px; height: 5px; background: var(--ar-blue);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.flow-drop .flow-lbl { color: var(--ar-blue); opacity: .8; }
.flow-drop .flow-lbl { text-align: left; }

/* the two output paths */
.flow-outs {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: stretch; gap: var(--ar-s-4);
  width: 100%; max-width: 470px;
}
.flow-outs .flow-node { justify-content: flex-start; }
.flow-or {
  align-self: center;
  font-family: var(--ar-font-mono); font-size: 9.5px;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
  color: var(--ar-text-3);
}
/* The destination is part of the node rather than a fifth connector: two more arrows in a
   diagram this small was noise, and "-> DAW track" is the node's whole purpose. */
.flow-dest {
  margin-top: var(--ar-s-3); padding-top: var(--ar-s-2);
  border-top: 1px solid var(--ar-record-24);
  font-family: var(--ar-font-mono); font-size: 10px;
  letter-spacing: .06em; color: var(--ar-record);
}
.flow-dest::before { content: "\2192\00a0"; color: var(--ar-record); }

.flow figcaption {
  margin-top: var(--ar-s-3); max-width: 52ch;
  font-family: var(--ar-font-sans); font-size: 13px; letter-spacing: 0; line-height: 1.6; color: var(--ar-text-3); text-align: center;
}

/* Phones: the pair stacks and its connector turns through 90 degrees, the two output
   paths go one above the other. No sideways scroll, no shrinking type. */
@media (max-width: 620px) {
  .flow { padding: var(--ar-s-5) var(--ar-s-4); }
  /* Stacked: one column, the three children fall into three rows in source order. */
  .flow-pair { grid-template-columns: 1fr; align-items: stretch; }
  .flow-pair > .flow-node:first-child { justify-self: stretch; }
  /* Same specificity as the absolute-label rule above, or the label stays pinned. */
  .flow-pair .flow-wire {
    position: static; display: flex; flex: none; max-width: none;
    flex-direction: row; align-items: center; justify-content: center;
    gap: var(--ar-s-3); padding: 2px 0;
  }
  .flow-pair .flow-lbl { position: static; }
  .flow-wire .wire { width: 7px; height: 30px; }
  .flow-wire .wire::before { top: 5px; bottom: 5px; left: 3px; right: auto; width: 1px; height: auto; }
  .flow-wire .wire::after {
    top: auto; bottom: 0; right: auto; left: 0;
    width: 7px; height: 5px; clip-path: polygon(0 0, 100% 0, 50% 100%);
  }
  .flow-wire .wire i {
    left: 0; top: 0; width: 7px; height: 5px; clip-path: polygon(50% 0, 100% 100%, 0 100%);
  }
  .flow-outs { grid-template-columns: 1fr; max-width: none; }
  .flow-or { justify-self: center; }
}

/* --- Contact block the page closes on ----------------------------------- */
/* .contact-box is styled in page-content.css, which /architecture does not load, so this
   block was rendering as bare prose with no frame at all. */
.contact-box {
  border: var(--ar-border); background: var(--ar-ink);
  padding: var(--ar-s-5); margin: var(--ar-s-6) 0 0;
}
.contact-box h2, .contact-box h3 { margin-top: 0; font-size: 17.5px; }
.contact-box p:last-child { margin-bottom: 0; }
.contact-email { font-family: var(--ar-font-mono); font-size: 14px; }

/* --- Quick answer ------------------------------------------------------- */
/* Every guide opens with one of these: the answer in three lines for the reader who does
   not want the walkthrough. They arrived carrying the OLD design inline - #2F80ED blue,
   14px radii, gradient fills - which inline styles made untouchable by any stylesheet, so
   they were the last thing on the site still rendering in the previous language.
   Square, hairline, record-red label: the same vocabulary as everything else. The left
   rule in record-red marks it as the page's one claim rather than a note. */
.quick-answer {
  border: var(--ar-border); border-left: 2px solid var(--ar-record);
  background: var(--ar-ink);
  padding: var(--ar-s-5); margin: 0 0 var(--ar-s-6);
}
.qa-label {
  font-family: var(--ar-font-mono); font-size: 10px; font-weight: 400;
  letter-spacing: var(--ar-tracking-label); text-transform: uppercase;
  color: var(--ar-record); margin-bottom: var(--ar-s-3);
}
.qa-lead { margin: 0; font-size: 15.5px; line-height: 1.65; color: var(--ar-text); }
.qa-steps { margin: var(--ar-s-4) 0 0; padding-left: var(--ar-s-5); }
.qa-steps li { font-size: 14.5px; line-height: 1.6; color: var(--ar-text-2); margin-bottom: 6px; }
.qa-steps li::marker { color: var(--ar-line-2); }
.quick-answer a { color: var(--ar-blue); }

/* --- Contents rail, and links that are not shouting --------------------- */
/* Seven bright blue underlined lines in a box read as a link dump, and they spend the
   site's one action colour on navigation the reader has not asked for yet. Blue is reserved
   for things you act on. The contents list is orientation: muted until hovered. */
/* .prose prefix so this reads as (0,2,1) and wins against the shell's (0,1,2) running-text
   rule on class count, without needing !important or a longer selector. */
.prose .toc a, .toc a {
  color: var(--ar-text-2); text-decoration: none;
  border-bottom: 1px solid transparent;
}
.prose .toc a:hover, .toc a:hover {
  color: var(--ar-text); text-decoration: none;
  border-bottom-color: var(--ar-line-2);
}
.toc li::marker { color: var(--ar-line-2); }
.toc ol { counter-reset: none; }

/* Breadcrumbs are orientation too, and the running-text underline was striking through
   every one of them. */
.prose .crumbs a, .crumbs a { text-decoration: none; border-bottom: 1px solid transparent; }
.prose .crumbs a:hover, .crumbs a:hover { border-bottom-color: var(--ar-line-2); text-decoration: none; }

/* A guide title needs air before its opening paragraph; the shell's h1 margin was tuned
   for a page header followed by a lede with its own spacing. */
.band.prose > .wrap > h1 { margin-bottom: var(--ar-s-5); }
.band.prose > .wrap > h1 + p { margin-top: 0; }

/* Two contents entries carry <strong>: the guide author marked the two main paths through
   the article, which is real information worth keeping. But .prose strong lifts them to
   full white, so five grey lines and two white ones read as a rendering fault rather than
   as hierarchy. The emphasis moves to the marker instead - record-red, the colour this site
   uses for "this is the thing" - and the text stays on one value. */
.prose .toc strong, .toc strong { color: inherit; font-weight: 500; }
.toc li:has(> a > strong) { color: var(--ar-text); }
.toc li:has(> a > strong)::marker { color: var(--ar-record); }
.prose .toc li:has(> a > strong) a { color: var(--ar-text); }
