/* ============================================================================
   guide.css - page layer for the Rust Optimization Guide.

   Loads AFTER ook.css (tokens, reset, nav, footer, buttons, .card, .tile,
   .shead, .q, .more, badges, chips, focus, preference blocks) and after
   optimization/style.css (the tool-page vocabulary). Everything here is
   specific to this one page: the two-pane shell, the filter rail, the ruled
   entry list, the disclosure motion, and the catalog's callouts.

   The page is built around one rule: the collapsed row is the surface, and
   everything a reader needs only once (how to read the guide, why a tweak
   works, what not to fall for, how to take a restore point) lives behind a
   native disclosure - never stacked in front of the catalog.

   No component in this file duplicates one the design system already names.
   ========================================================================== */


/* ============================================================================
   1. PAGE FRAME
   The sticky rail on narrow viewports needs a measured offset so a deep link
   lands under the chrome instead of behind it. --stick-h is written by app.js.
   ========================================================================== */

html { scroll-padding-top: calc(var(--nav-h) + var(--stick-h, 0px) + var(--s4)); }

/* The second skip link steps aside so both stay legible when tabbed through. */
.skip-2:focus { left: calc(var(--s3) + 10.5rem); }


/* ============================================================================
   1b. HERO
   The band, its light and its measure all come from ook.css. The hero is ONE
   column here: the right-hand cell used to hold a "Start here" card that
   repeated both buttons beside it and sent the reader off the page before a
   single tweak was on screen. What replaces it is the catalog, ~700px further
   up the page.
   ========================================================================== */

/* Hero rhythm - the SAME two lines on all six sub-site surface pages (guide,
   config, audit, commands, binds, changes): a tighter band than the shared one,
   because every hero here is now a heading, one line and one action, and the
   tool or list under it is the point. Set page-locally rather than in
   ook.css because the gallery pages consume the shared band too. */
.hero { padding-block: clamp(32px, 3.5vw, 48px) var(--s5); }
main > .hero + section { margin-top: var(--s6); }

/* The one size decision left to the page: the headline is capped so it sets on
   two balanced lines inside the shared copy column instead of three. */
.hero h1 {
  font-size: clamp(2.25rem, 4.6vw, 3.5rem);
  text-wrap: balance;
  margin-bottom: var(--s4);
}

.hero-actions { margin-top: var(--s5); }

/* One short action, so it stays a pill instead of becoming a full-width bar.
   The shared stacking rule exists for a row of buttons of unequal width. */
@media (max-width: 39.99em) {
  .hero-actions { display: flex; flex-wrap: wrap; }
  .hero-actions .btn { width: auto; }
}

/* On a phone the band is spending its height on air: one column, no aside, and
   a catalog that has to start on the first screen. */
@media (max-width: 47.99em) {
  .hero h1 { margin-bottom: var(--s3); }
  .hero-actions { margin-top: var(--s4); }
}


/* ============================================================================
   2. HOW TO READ THIS GUIDE
   Everything a reader needs ONCE - the three rules, the evidence glossary, the
   restore-point procedure - inside one closed disclosure sitting directly above
   the catalog. It used to be a full band between the hero and the first tweak:
   three cards, a four-column legend, a 47-word footnote and a card of Windows
   instructions, ~700px of instructions-about-instructions that every visitor
   scrolled past and nobody needed twice.
   ========================================================================== */

.howto {
  margin-bottom: var(--s5);
  border-radius: var(--r-md);
}

@media (max-width: 47.99em) { .howto { margin-bottom: var(--s4); } }

.howto > summary {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: 44px;
  padding: var(--s2) var(--s4);
  border-radius: var(--r-md);
  color: var(--fg-2);
  font-size: var(--fs-sm);
  font-weight: 500;
}

.howto > summary:focus-visible { outline-offset: -3px; }

.howto-body { padding: 0 var(--s4) var(--s5); }

.howto-rules { display: grid; gap: var(--s3); max-width: var(--measure); }

.howto-rules p {
  margin: 0;
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

.howto-rules b { color: var(--fg-0); }

/* -- The evidence legend --------------------------------------------------
   A key has to look like the thing it keys, so these are the CATALOG's labels:
   the same dot-and-word every entry row carries, at the same four hues. */
.howto-body > .glabel { display: block; margin: var(--s5) 0 var(--s3); }

.ev-list {
  margin: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s3) var(--s5);
}

@media (min-width: 34em) { .ev-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.ev-row {
  display: grid;
  gap: var(--s1);
  align-content: start;
}

.ev-row dd {
  margin: 0;
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

/* The restore-point procedure keeps ONE home on the page. app.js clones this
   body into the entries that ask for a restore point. */
.restore-how { margin-top: var(--s5); }
.restore-how-body ol { margin: 0 0 var(--s3); padding-left: var(--s5); }
.restore-how-body li { margin-bottom: var(--s2); }
.restore-how-body li b { color: var(--fg-1); }
.restore-how-body p { margin: 0; }


/* ============================================================================
   2b. SYMPTOM ROUTER
   The one surface on this page organised by what is WRONG rather than by what
   to change. It is a router, not a second navigation system: a label, six
   chips in the reader's own words, and - once one is picked - a bounded strip
   saying what the catalog is now showing, what the symptom usually means, what
   the route cannot fix, and the way back.

   No new component: the picks are the design system's .chip (the same object
   the filter rail uses, so a pressed one reads as a filter, which is exactly
   what it is), the evidence dot is .ev-label, and the way back is the rail's
   own .rail-btn.
   ========================================================================== */

/* Every pixel this section spends is a pixel of catalog pushed below the fold,
   and the guide's fold budget is written down (section 1b): the tier head and
   the top of the first row have to survive. So the router is three parts and no
   more - a heading, one line, and the chips - and from tablet width up the
   heading and the line share a row, so the whole router costs two. */
.triage {
  display: grid;
  gap: var(--s2);
  margin-bottom: var(--s4);
}

@media (min-width: 48em) {
  .triage {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: baseline;
    column-gap: var(--s3);
  }

  .triage-picks { grid-column: 1 / -1; }
}

/* Below a tier head, above body copy: this is a utility heading, and a second
   h2 at full section size directly above "How to read this guide" would read
   as the page's real title. */
.triage-title {
  margin: 0;
  font-size: var(--fs-h3);
  letter-spacing: var(--tk-h3);
}

.triage-lede {
  margin: 0;
  max-width: var(--measure);
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

.triage-picks { display: flex; flex-wrap: wrap; gap: 6px; }

/* The reader's words are longer than a filter value, so the chip sets at the
   rail's size rather than the body size. Measured honestly: at 390px most of
   these DO run the width of the column and the block costs ~330px at rest.
   That is the price of six plain-language lines and it is paid once, at the
   top; the routed state no longer pays it, because the answer strip renders
   down beside the rows instead of up here. */
.triage-picks .chip {
  min-height: 30px;
  padding-inline: 12px;
  font-size: var(--fs-xs);
  text-align: left;
}

@media (pointer: coarse) { .triage-picks .chip { min-height: 38px; } }

/* -- What the route is doing, once one is picked --------------------------
   This block is NOT inside .triage. It renders directly above the catalog,
   because between the chips and the rows sit the how-to-read card and the
   badge key: keeping the answer up with the chips put the first routed row
   below the fold of a 390x844 screen, so the reader clicked a symptom and got
   an explanation with no list under it. */
.triage-state {
  margin: 0 0 var(--s4);
  padding: var(--s4);
  border: 1px solid var(--line-2);
  border-left: 3px solid var(--accent-500);
  border-radius: var(--r-md);
  background: var(--bg-2);
  box-shadow: var(--hairline);
  animation: guide-fade-in var(--dur-base) var(--ease-out) both;
}

@media (prefers-reduced-motion: reduce) { .triage-state { animation: none; } }

.triage-state > p { margin: 0 0 var(--s2); max-width: var(--measure); }

/* The provenance and the way back on one row: this strip sits directly above
   the rows it describes, so every line it spends is a line of catalog pushed
   down. It wraps to two rows on a phone rather than squeezing either half. */
.triage-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2) var(--s4);
  margin-bottom: var(--s2);
}

.triage-top .triage-trust { margin: 0; }
.triage-top .rail-btn { flex: none; }

/* The same dot a catalog row carries, over a DIFFERENT object: there it grades
   an entry's recommendation, here it grades the routing. The referent is
   printed, not implied - this is the one place on the site where the four
   words mean something other than what the glossary below defines. */
.triage-trust {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 var(--s2);
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

.triage-trust .glabel { margin: 0; }

.triage-means,
.triage-honest,
.triage-vars,
.triage-rules {
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

.triage-means { color: var(--fg-1); }

/* The honest half is not a footnote, so it is not styled like one: it gets the
   same weight as the mechanism, with a rule marking it as the other side. */
.triage-honest {
  padding-left: var(--s3);
  border-left: 1px solid var(--line-1);
}

.triage-honest b { color: var(--fg-1); }

.triage-vars { margin-bottom: 0; }
.triage-vars .glabel { margin-right: var(--s1); }
.triage-vars code { font-size: .95em; }
.triage-vars a { text-decoration: none; }
.triage-vars a:hover code { color: var(--fg-0); }

/* The audit review's checks for this symptom. Renders only when data/rules.js
   is loaded on this page AND the id resolves, so the ids may land before the
   link does. Same shape as the settings line: a label, then routes out. */
.triage-rules { margin-top: var(--s2); max-width: var(--measure); }
.triage-rules .glabel { display: block; margin-bottom: var(--s1); }
.triage-rules ul { margin: 0; padding-left: var(--s5); }
.triage-rules li { margin-bottom: 2px; }

@media (max-width: 47.99em) {
  .triage { margin-bottom: var(--s4); }
  /* Tighter against the head it introduces: on a phone the strip, the head and
     the rows are competing for one 844px screen, and the strip is the half the
     reader has already read by the time the rows matter. */
  .triage-state { padding: var(--s3); margin-bottom: var(--s2); }
}

@media (forced-colors: active) {
  .triage-state { border: 1px solid CanvasText; }
}

@media print { .triage { display: none !important; } }


/* ============================================================================
   3. THE TWO-PANE SHELL
   >=75em: a sticky filter rail beside the catalog. Below that the same markup
   collapses into a compact sticky bar with the filters behind a disclosure.
   ========================================================================== */

/* Closer to the hero than a full section rhythm: with the intro band gone, the
   shell IS the page, and empty ground between a two-line deck and the first
   tier reads as a missing section rather than as breathing room. */
.guide-shell { margin-top: var(--s6); }
.catalog-col { min-width: 0; }

@media (max-width: 47.99em) { .guide-shell { margin-top: var(--s5); } }

/* Only the CATALOG is two-pane. The refusals and the closing band are chapters
   of the page: they sit on the page's own left gutter like the hero and the
   footer, at full container width, not indented behind a rail that has ended.
   (main > section + section carries the rhythm from .nevers onward; the shell
   is a div, so its own successor needs the rule spelled out.) */
.guide-shell + .nevers { margin-top: var(--section-y); }

@media (min-width: 75em) {
  .guide-shell {
    display: grid;
    /* 16.5rem, not 15: at 15 the filter groups broke into orphan lines - one
       chip alone under three - and a rail of ragged debris is worse than a
       slightly wider one. See the two-column group rule in section 5. */
    grid-template-columns: 16.5rem minmax(0, 1fr);
    gap: var(--s7);
    align-items: start;
  }
}


/* ============================================================================
   4. THE RAIL
   ========================================================================== */

.rail {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}

/* -- Narrow: a compact bar that stays with the reader --------------------- */
@media (max-width: 74.99em) {
  .rail {
    position: sticky;
    top: var(--nav-h);
    z-index: 20;
    gap: var(--s2);
    padding-block: var(--s3);
  }

  /* Full-bleed background without vw maths: below 75em the wrap is always
     padding-limited, so reaching one gutter each way reaches the edge. */
  .rail::before {
    content: "";
    position: absolute;
    inset: 0 calc(var(--gutter) * -1);
    z-index: -1;
    background: color-mix(in srgb, var(--bg-0) 86%, transparent);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    border-bottom: 1px solid transparent;
    transition: border-color var(--dur-base) var(--ease-std),
                box-shadow var(--dur-base) var(--ease-std);
  }

  .rail.is-stuck::before {
    border-bottom-color: var(--line-1);
    box-shadow: 0 6px 20px -14px rgba(0, 0, 0, .9);
  }

  /* Reading progress, driven by the scroll timeline: no listener, no JS. In an
     engine without animation-timeline the animation never runs and the rule
     below leaves the rail at scaleX(0), i.e. invisible. */
  .rail::after {
    content: "";
    position: absolute;
    left: calc(var(--gutter) * -1);
    right: calc(var(--gutter) * -1);
    bottom: -1px;
    height: 2px;
    background: var(--accent-500);
    transform: scaleX(0);
    transform-origin: 0 50%;
  }

  @media (prefers-reduced-motion: no-preference) {
    .rail::after {
      animation: guide-prog linear;
      animation-timeline: scroll(root block);
    }
  }

  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .rail::before { background: var(--bg-0); }
  }

  .rail-bar {
    display: flex;
    align-items: center;
    gap: var(--s3);
  }

  .rail-search { flex: 1 1 auto; min-width: 0; }

  .search-kbd { display: none; }
  .rail .count { flex: none; }
}

@media (min-width: 75em) {
  /* The rail is its own scroller, and with the filter sheet open its content is
     ~80px taller than a 900px viewport allows - which is how the Evidence group,
     the sort toggle and Reset ended up unreachable on the WIDEST screens while
     the identical sheet fits whole on a phone. Two changes fix that: the rail
     claims the 12px of slack it was leaving under itself, and it declares a
     scrollbar COLOUR, which is what makes the thin track persistent instead of
     an overlay that fades out - the reader can see the region scrolls. The
     track only exists when the content actually overflows. */
  .rail {
    position: sticky;
    top: calc(var(--nav-h) + var(--s5));
    max-height: calc(100dvh - var(--nav-h) - var(--s5) - var(--s3));
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--line-2) transparent;
    padding-right: var(--s2);
  }

  .rail-bar { display: flex; flex-direction: column; gap: var(--s2); }

  /* Orientation before filtering: the jump list sits above the filter groups,
     so it is on screen without scrolling the rail. */
  .rail-bar { order: 0; }
  .rail-jump { order: 1; }
  .filter-sheet { order: 2; }
}

/* -- Search field --------------------------------------------------------- */
.rail-search { position: relative; }

/* The label stays in the accessibility tree and nowhere else, at EVERY width: a
   search box whose placeholder already reads "refresh rate, affinity" does not
   need the word printed over it as well. */
.rail-search .glabel {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.search-field { position: relative; display: flex; align-items: center; }

.search-field .search-ico {
  position: absolute;
  left: var(--s3);
  color: var(--fg-3);
  pointer-events: none;
  transition: color var(--dur-base) var(--ease-std);
}

.search-field:focus-within .search-ico { color: var(--accent-400); }

/* Safari draws its own search-field furniture and ignores the box model
   otherwise; reset the appearance and every decoration it adds. */
#search {
  -webkit-appearance: none;
  appearance: none;
  min-height: 40px;
  padding-left: calc(var(--s5) + var(--s3));
  padding-right: var(--s6);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
}

#search::-webkit-search-decoration,
#search::-webkit-search-cancel-button,
#search::-webkit-search-results-button,
#search::-webkit-search-results-decoration { -webkit-appearance: none; }

#search:focus-visible { border-color: var(--accent-500); }

.search-kbd {
  position: absolute;
  right: var(--s2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding-inline: 5px;
  border: 1px solid var(--line-2);
  border-radius: 4px;
  background: var(--bg-2);
  color: var(--fg-3);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  line-height: 1;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-std);
}

.search-field:focus-within .search-kbd { opacity: 0; }

.rail .count { color: var(--fg-2); font-size: var(--fs-xs); }

/* Declared last so the compact-bar overrides win over the block above them. */
@media (pointer: coarse) { .search-kbd { display: none; } }

@media (max-width: 74.99em) {
  .search-kbd { display: none; }
  .rail-search { max-width: 26rem; }
  .rail .count { margin-left: auto; }
}


/* ============================================================================
   5. FILTER SHEET
   ========================================================================== */

.filter-sheet { border-top: 1px solid var(--line-1); }

.filter-sheet > summary {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: 40px;
  color: var(--fg-2);
  font-size: var(--fs-sm);
  font-weight: 500;
}

.filter-sheet > summary .sheet-ico { color: var(--fg-3); }

.fcount {
  display: none;
  min-width: 18px;
  height: 18px;
  padding-inline: 5px;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-full);
  background: var(--accent-500);
  color: #1b1410;
  font-size: var(--fs-2xs);
  font-weight: 600;
  line-height: 1;
}

.fcount.on { display: inline-flex; }

.sheet-body { padding-top: var(--s3); }

/* Native disclosure motion; browsers without ::details-content simply snap. */
.filter-sheet::details-content,
.entry::details-content,
.howto::details-content {
  block-size: 0;
  opacity: 0;
  overflow: clip;
  transition: block-size var(--dur-slow) var(--ease-out),
              opacity var(--dur-base) var(--ease-std),
              content-visibility var(--dur-slow) allow-discrete;
}

.filter-sheet[open]::details-content,
.entry[open]::details-content,
.howto[open]::details-content {
  block-size: auto;
  opacity: 1;
}

@media (max-width: 74.99em) {
  .sheet-body {
    max-height: min(62vh, 30rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-bottom: var(--s3);
    scrollbar-width: thin;
  }
}

/* -- Bounded filter groups ------------------------------------------------ */
.filter-groups {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  margin-top: 0;
}

/* No box per group: a rail of four filled rectangles next to a column of cards
   is one rectangle too many. The uppercase label plus a rule is enough. */
.filter-group {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  padding: var(--s3) 0 0;
  background: none;
  border: 0;
  border-top: 1px solid var(--line-1);
  border-radius: 0;
}

.filter-groups > .filter-group:first-child { border-top: 0; padding-top: 0; }

/* The label and its "?" travel as one line above the chips. The mark is a
   sibling of the label, never a child: inside it, its own text would join the
   group's accessible name. */
.glabel-line {
  flex: 0 0 100%;
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s1);
}

.filter-group .glabel {
  font-weight: 600;
  color: var(--fg-3);
}

/* The rail's note stays FLOATING at every width. The design system drops the
   popover into flow below 640px so it cannot clip at a screen edge, but here it
   would stretch the one-line label row it lives on; anchored to the mark's left
   edge it has the whole catalog column to open into instead. */
.filter-group .q > .q-pop {
  position: absolute;
  top: calc(100% + var(--s2));
  left: -4px;
  right: auto;
  transform: none;
  width: max-content;
  max-width: min(34ch, calc(100vw - 4rem));
  margin-top: 0;
}

/* In the desktop rail the note has one boundary it cannot cross: the sticky
   column scrolls, so anything wider than the track left of it is clipped. */
@media (min-width: 75em) {
  .filter-group .q > .q-pop { max-width: 11.5rem; }
}

.filter-group .chip {
  min-height: 28px;
  padding-inline: 10px;
  font-size: var(--fs-xs);
  background: var(--bg-2);
  border-color: var(--line-2);
}

.filter-group .chip:hover { background: var(--bg-3); border-color: var(--stroke); }

/* The mark is 18px so it sits inside its label's line; the design system already
   grows the TARGET to 26px with a ::after overlay. The filter sheet is the
   primary filter UI on a phone, where 26px is still under a finger, so on a
   coarse pointer this one instance opens the overlay to 44px. */
@media (pointer: coarse) {
  .filter-group .q > summary::after { inset: -13px; }
}

/* In the rail the groups are blocks, not wrapped rows: two equal columns, so
   every group reads as a rectangle whatever its value count. Wrapping by
   content width left "low" alone on one line and "vendor inference" on
   another - four groups, four different silhouettes. */
@media (min-width: 75em) {
  .filter-group {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s2);
  }

  .glabel-line { grid-column: 1 / -1; margin-bottom: 0; }
  .filter-group .chip { justify-content: center; padding-inline: var(--s2); }
}

.filter-group .chip[aria-pressed="true"] {
  background: var(--bg-3);
  border-color: var(--accent-500);
  color: var(--fg-0);
}

@media (pointer: coarse) { .filter-group .chip { min-height: 36px; } }

/* -- Actions: sort and reset, on one control height ----------------------- */
.rail-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin-top: var(--s4);
}

.sort-toggle {
  display: inline-flex;
  border: 1px solid var(--stroke);
  border-radius: var(--r-sm);
  overflow: hidden;
}

.sort-toggle label {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding-inline: var(--s3);
  background: var(--bg-1);
  color: var(--fg-2);
  font-size: var(--fs-xs);
  font-weight: 500;
  cursor: pointer;
  transition: background-color var(--dur-base) var(--ease-std),
              color var(--dur-base) var(--ease-std);
}

/* No negative margins: the divider is a real border on the second label, so
   fractional device pixel ratios cannot round it into a seam. */
.sort-toggle input ~ input + label { border-left: 1px solid var(--line-2); }

.sort-toggle label:hover { background: var(--bg-2); color: var(--fg-0); }

.sort-toggle input:checked + label {
  background: var(--bg-3);
  color: var(--fg-0);
  box-shadow: inset 0 -2px 0 var(--accent-500);
}

.sort-toggle input:focus-visible + label {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

.rail-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  padding-inline: var(--s3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--bg-1);
  color: var(--fg-2);
  font-size: var(--fs-xs);
  font-weight: 500;
  transition: background-color var(--dur-base) var(--ease-std),
              border-color var(--dur-base) var(--ease-std),
              color var(--dur-base) var(--ease-std),
              transform var(--dur-fast) var(--ease-std);
}

.rail-btn:hover { background: var(--bg-3); border-color: var(--stroke); color: var(--fg-0); }
.rail-btn:active { transform: translateY(1px); }

/* In the desktop rail the sheet is taller than the scroller that holds it, so
   its two ANSWERS - how to order the catalog, and how to undo the filtering -
   are pinned to the rail's bottom edge while the four groups scroll past them.
   A filter panel whose Reset can scroll out of reach is not reachable. */
@media (min-width: 75em) {
  .rail-actions {
    position: sticky;
    bottom: 0;
    z-index: 1;
    margin-top: var(--s3);
    padding-block: var(--s3);
    border-top: 1px solid var(--line-1);
    background: var(--bg-0);
  }
}

@media (pointer: coarse) {
  .rail-btn, .sort-toggle label { min-height: 40px; }
}


/* ============================================================================
   6. RAIL JUMP LIST - desktop only.
   Below 75em the rail is a one-line bar and this list has nowhere to live, so
   the refusals keep their entry point in the HERO instead - the don'ts button
   stands at every width, and under 75em it is the only route to #never. (An
   earlier build assumed a narrow intro band that no longer exists, and the
   refusals lost their only entry point under 75em.)
   ========================================================================== */

.rail-jump { display: none; }

@media (min-width: 75em) {
  .rail-jump {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-bottom: var(--s4);
    border-bottom: 1px solid var(--line-1);
  }

  .rail-jump a {
    display: flex;
    align-items: center;
    gap: var(--s2);
    min-height: 32px;
    padding-inline: var(--s2);
    border-radius: var(--r-sm);
    color: var(--fg-2);
    font-size: var(--fs-xs);
    font-weight: 500;
    text-decoration: none;
    transition: background-color var(--dur-base) var(--ease-std),
                color var(--dur-base) var(--ease-std);
  }

  .rail-jump a { align-items: flex-start; padding-block: 7px; line-height: var(--lh-snug); }
  .rail-jump a:hover { background: var(--bg-2); color: var(--fg-0); }

  .rail-jump .jk {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border: 1px solid var(--line-2);
    border-radius: var(--r-sm);
    color: var(--fg-3);
    font-size: var(--fs-2xs);
    font-variant-numeric: tabular-nums;
  }

  .rail-jump .jl { min-width: 0; }
  .rail-jump .jn { margin-left: auto; color: var(--fg-3); font-variant-numeric: tabular-nums; }
  .rail-jump a.is-empty { opacity: .4; pointer-events: none; }

  /* Scroll position as an information channel: the tier you are reading. */
  .rail-jump a.is-current { background: var(--bg-2); color: var(--fg-0); }
  .rail-jump a.is-current .jk { border-color: var(--accent-500); color: var(--accent-300); }
  .rail-jump a.jump-never { margin-top: var(--s2); color: var(--imp-crit-fg); }
  .rail-jump a.jump-never .jk { border-color: var(--imp-crit-line); color: var(--imp-crit-fg); }
  .rail-jump a.jump-never:hover { background: var(--imp-crit-bg); }
  .rail-jump .jump-head { margin-bottom: var(--s1); }
}


/* ============================================================================
   7. CATALOG - tier heads
   ========================================================================== */

/* -- The badge column, named once -----------------------------------------
   One line above the catalog, drawn with the row's own two components: the
   dot-and-word for evidence, the stroked pill for impact. A key that is set in
   plain text teaches nothing; a key that is the object does. It is deliberately
   a single quiet line - the taxonomy itself is settled and is not restated -
   and it is NOT measure-capped, because a key that wraps stops being one. */
.badge-legend {
  margin: 0 0 var(--s5);
  color: var(--fg-3);
  font-size: var(--fs-xs);
  line-height: 1.75;
}

/* No horizontal margin on the two swatches: the word space either side already
   separates them, and 3px more put a visible gap in front of the comma. */
.badge-legend .ev-label,
.badge-legend .badge { vertical-align: 1px; }

@media (max-width: 47.99em) { .badge-legend { margin-bottom: var(--s3); } }

#catalog { min-height: 80vh; }
#catalog.rendered { min-height: 0; }

/* The heading itself is the site's one section header (.shead); the tier adds
   only its kicker, its rule and its rhythm. */
.tier-head {
  margin: var(--s8) 0 var(--s5);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--line-1);
}

.tier-head.is-first { margin-top: 0; }
.tier-head .eyebrow { margin-bottom: var(--s1); }
.tier-head > .shead { margin-bottom: 0; }
.tier-head .tier-blurb { font-size: var(--fs-sm); line-height: var(--lh-snug); }

/* The count is a fact about the heading, so it sits on the heading's baseline
   directly after it - not as a pill floating at the far right of the rule. */
.tier-count,
.head-count {
  white-space: nowrap;
  color: var(--fg-3);
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}


/* ============================================================================
   8. ENTRIES - one ruled list per tier, not a stack of capsules
   The frame, the fill and the radius belong to the LIST; a row owns only the
   hairline above it. That is what turns twelve entries into something you can
   run your eye down instead of twelve separate objects of unequal height.
   ========================================================================== */

.tier-list {
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: var(--bg-2);
  background-image: var(--card-sheen);
  box-shadow: var(--sh-card), var(--hairline);
  overflow: clip;
}

/* An empty list is not a frame around nothing: sorting by impact empties every
   tier list, and sorting by tier empties the all-entries list. */
.tier-list:empty { display: none; }

.entry {
  border: 0;
  border-radius: 0;
  margin: 0;
  background-color: transparent;
  background-image: none;
  box-shadow: none;
  transition: background-color var(--dur-base) var(--ease-std),
              box-shadow var(--dur-base) var(--ease-std),
              opacity var(--dur-base) var(--ease-std),
              transform var(--dur-base) var(--ease-std);
}

.entry + .entry { border-top: 1px solid var(--line-1); }

/* Whichever row is first VISIBLE loses the separator, so a filtered list never
   opens on a doubled hairline under its own top edge. app.js sets the class. */
.entry.is-lead { border-top: 0; }

/* Two rows: the title and its two badges, then the scan line under the title.
   The marker column spans both, so the body below keeps aligning under the
   title whatever the row's height. */
.entry > summary {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr) max-content;
  grid-template-areas:
    "mark title badges"
    "mark sum   sum";
  align-items: center;
  gap: var(--s1) var(--s3);
  padding: var(--s3) var(--s4);
  min-height: 56px;
  border-radius: 0;
  transition: background-color var(--dur-base) var(--ease-std),
              box-shadow var(--dur-base) var(--ease-std);
}

.entry > summary > .marker { grid-area: mark; align-self: center; }
.entry .etitle { grid-area: title; }
.entry .entry-badges { grid-area: badges; }
.entry .esum { grid-area: sum; }

/* One clause of what the entry actually does, so 27 rows can be ruled in or out
   without opening one. Quieter than the title and never longer than a line at
   desktop; it is a scan line, not a deck. */
.entry .esum {
  margin: 0;
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  text-wrap: pretty;
}

.entry .esum code { font-size: .875em; background: var(--bg-1); color: var(--accent-300); }

/* A hardware gate has to be legible BEFORE the row is opened - it is the thing
   that stops the wrong reader applying the wrong tweak. Same tone as the gate
   callout inside the body, at tag weight. */
.gate-tag {
  display: inline-block;
  margin-right: var(--s2);
  padding: 1px 7px;
  border: 1px solid var(--imp-med-line);
  border-radius: var(--r-full);
  background: var(--imp-med-bg);
  color: var(--imp-med-fg);
  font-size: var(--fs-2xs);
  letter-spacing: .02em;
  white-space: nowrap;
  vertical-align: 1px;
}

/* The one risk grade that must be legible BEFORE the row is opened: an entry
   that wants a System Restore point taken first. Same tag metrics as the gate,
   in the warning tone, so a safety flag never hides behind a disclosure. */
.risk-tag {
  display: inline-block;
  margin-right: var(--s2);
  padding: 1px 7px;
  border: 1px solid var(--warn-line);
  border-radius: var(--r-full);
  background: var(--warn-bg);
  color: var(--warn-fg);
  font-size: var(--fs-2xs);
  letter-spacing: .02em;
  white-space: nowrap;
  vertical-align: 1px;
}

/* The whole interaction model of this page is "click a row", so a row has to
   answer the pointer - but only with warmth. The lit left edge belongs to the
   OPEN state alone; when hover borrowed it too, a hovered closed row was
   indistinguishable from an open one's header. The plain fill is declared
   first for engines without color-mix. */
.entry > summary:hover {
  background: var(--bg-3);
  background: color-mix(in srgb, var(--bg-2) 92%, var(--accent-500) 8%);
}

/* An open entry is a panel, not a row: it sits on a darker ground with a lit
   edge - the page's one accent bar - so 400px of body detaches from the 56px
   rows around it. */
.entry[open] {
  background-color: var(--bg-1);
  box-shadow: inset 2px 0 0 var(--accent-500);
}

.entry[open] > summary { border-bottom: 1px solid var(--line-1); }

.entry > summary::marker { content: ""; }
.entry > summary::-webkit-details-marker { display: none; }

.entry .etitle {
  font-size: var(--fs-body);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--fg-0);
  line-height: var(--lh-snug);
  margin: 0;
  text-wrap: pretty;
  min-width: 0;
}

.entry .etitle code {
  font-size: .875em;
  background: var(--bg-1);
  color: var(--accent-300);
}

/* Two fixed meta columns, evidence then impact, so every row's impact pill
   starts and ends on the same x - that alignment is what makes 27 rows
   scannable. Difficulty and risk are deliberately NOT here: four tags took more
   width than the titles they described, and those two axes matter once you are
   already reading the entry, where the open body states them in full. */
.entry-badges {
  display: grid;
  grid-template-columns: 5.75rem 5.25rem;
  align-items: center;
  gap: var(--s2);
}

.entry-badges .badge {
  width: 100%;
  justify-content: center;
  padding: 2px 7px;
  font-size: 11px;
  letter-spacing: .04em;
}

/* Evidence is NOT a pill here or anywhere: it is ook.css's .ev-label, a dot
   plus a word, the same object the Config Tuner labels its rows with. Two
   taxonomies drawn as the same stroked pill on all 27 rows had left the row's
   ranking signal with exactly the weight of its provenance signal; the stroked
   pill is now impact's alone. Nothing about the component is restated here -
   this page only says where it sits. */
.entry-badges .ev-label { min-width: 0; }

/* Four criticals stack into a column in Tier 3, so the loudest element on the
   page is also its most repeated one. Warmed off the cool red and onto the
   accent's own family, and thinned to a wash: it still reads as stop, without
   painting a red bar down the tier. Flat fallbacks first, for engines with no
   color-mix(). */
.entry-badges .badge.imp-critical {
  background: #452019;
  background: color-mix(in srgb, #b8503c 20%, transparent);
  border-color: #6b3226;
  border-color: color-mix(in srgb, #b8503c 45%, transparent);
  color: #f0a68f;
}

/* The evidence label is a REQUIRED surface signal - it is what makes a claim
   auditable - so it survives the phone. It used to be display:none below 48em,
   which hid it on the majority viewport. The badges drop to their own line
   under the scan line instead, where both fit at full size. */
@media (max-width: 48em) {
  .entry > summary {
    grid-template-columns: 16px minmax(0, 1fr);
    grid-template-areas:
      "mark title"
      "mark sum"
      "mark badges";
    align-items: start;
    gap: var(--s1) var(--s2);
    padding: var(--s3);
    min-height: 52px;
  }

  .entry > summary > .marker { align-self: start; margin-top: 3px; }

  .entry-badges {
    grid-template-columns: max-content max-content;
    justify-content: start;
    gap: var(--s3);
    margin-top: var(--s1);
  }

  .entry-badges .badge { width: auto; }

  /* The body keeps aligning under the title: marker column plus the gap. */
  .entry-body { padding: var(--s3) var(--s3) var(--s5) calc(var(--s3) + 16px + var(--s2)); }
}

/* -- Disclosure marker ----------------------------------------------------
   27 rows of quiet text need the affordance to be findable: the caret rests at
   just over half strength and comes to full accent under the pointer. Scoped to
   the ROW's own summary, so the reveals nested in an open body keep the design
   system's own marker weight. */
.entry > summary > .marker { color: var(--fg-3); opacity: .55; }

.entry > summary:hover > .marker,
.entry > summary:focus-visible > .marker,
.entry[open] > summary > .marker { opacity: 1; color: var(--accent-400); }

/* -- Body ----------------------------------------------------------------- */
.entry-body {
  padding: var(--s4) var(--s4) var(--s5) calc(var(--s4) + 16px + var(--s3));
}

.entry-body > * { max-width: var(--measure); }
.entry-body .entry-meta,
.entry-body .entry-foot { max-width: none; }

/* The second pass, one collapsed row each: why it works, what not to fall for,
   and (where the risk asks for one) how to take a restore point. They sit BELOW
   the steps because a reader who opened an entry came for the instructions -
   every body used to open with 30-70 words of rationale ahead of step 1. */
.entry-body > .more { margin-top: var(--s4); }
.entry-body > .more + .more { margin-top: var(--s2); }
.entry-body .restore-inline { margin-top: var(--s3); }

/* Revealed material is marked as revealed: the same 2px rule the undo line
   carries, so a nested procedure cannot be misread as a continuation of the
   steps it sits under. */
.entry-body .more-body {
  margin-top: var(--s2);
  padding-left: var(--s3);
  border-left: 2px solid var(--line-1);
}

.entry-body .more-body > ul,
.entry-body .more-body > ol { margin: 0; padding-left: var(--s5); }
.entry-body .more-body p { margin: 0; }

.entry-body ol,
.entry-body ul { padding-left: var(--s5); }
.entry-body li { margin-bottom: var(--s2); line-height: var(--lh-snug); }

h4.slabel {
  font-size: var(--fs-2xs);
  line-height: var(--lh-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tk-label);
  color: var(--fg-3);
  margin: var(--s5) 0 var(--s2);
}

/* -- The inert chip -------------------------------------------------------
   The rail toggles difficulty and risk with .chip; the open entry and the
   "how to read this guide" reveal STATE the same values. One vocabulary, one
   component: this
   is that component with its interactivity removed, at the rail's own metrics,
   so a value and its filter are recognisably the same object. */
.chip.is-static {
  min-height: 28px;
  padding-inline: 10px;
  background: var(--bg-2);
  border-color: var(--line-2);
  color: var(--fg-1);
  font-size: var(--fs-xs);
  cursor: default;
}

.chip.is-static:hover {
  background: var(--bg-2);
  border-color: var(--line-2);
  color: var(--fg-1);
}

.chip.is-static:active { transform: none; }

/* Impact and evidence are on the summary row directly above, so the open body
   states only the two quiet axes - as a micro-label over its chip, not a fourth
   badge vocabulary restating what the reader just clicked. */
.entry-meta {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  grid-template-rows: auto auto;
  justify-content: start;
  align-items: center;
  column-gap: var(--s6);
  row-gap: var(--s1);
  margin: 0 0 var(--s5);
}

/* The label is the site's micro-label component (.glabel), not a private one. */
.entry-meta .glabel { color: var(--fg-3); }

.entry-meta dd {
  margin: 0;
  color: var(--fg-1);
  font-size: var(--fs-xs);
  line-height: var(--lh-label);
}

.reversible {
  margin-top: var(--s3);
  padding-left: var(--s3);
  border-left: 2px solid var(--line-2);
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

/* -- The settings an entry uses ---------------------------------------------
   Rendered from the shared dataset (data/cvars.js), never from this entry's
   prose, so a default printed here cannot drift from the reference. Each row is
   name, shipped default, one line of what it does, and a route to the full
   record; the name is the site's copy chip, because the next thing a reader
   does with it is paste it into the F1 console. */
.setlist {
  display: grid;
  gap: var(--s3);
  margin: var(--s2) 0 0;
  padding: 0;
  list-style: none;
}

.setlist li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--s1) var(--s3);
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--line-1);
}

.setlist li:last-child { padding-bottom: 0; border-bottom: 0; }

.setlist .sn { justify-self: start; }

.setlist .sd {
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
  color: var(--fg-2);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

.setlist .sd .glabel { margin-right: var(--s1); }

.setlist .sw {
  grid-column: 1 / -1;
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

.setlist .sref {
  grid-column: 1 / -1;
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  font-size: var(--fs-xs);
}

/* The bridge to the guided route, stated where the manual work is. */
.set-tuner {
  margin: 0 0 var(--s3);
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

@media (max-width: 40em) {
  .setlist li { grid-template-columns: minmax(0, 1fr); }
  .setlist .sd { grid-column: 1; grid-row: auto; justify-self: start; }
}

.entry-foot {
  margin-top: var(--s5);
  gap: var(--s3) var(--s4);
}

/* The entry foot carries actions only. The trace id that used to sit here
   ("RULE C-7") was an identifier from an internal ruleset with no public
   referent, explained by a hover title that a touch reader never sees. */

/* A related entry is a link INTO this catalog, so the group says so: a label
   in front, an arrow inside the chip. Unlabelled, it read as a second button
   sitting beside "Copy link". */
.related {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
}

.related-label { color: var(--fg-3); }

.related ul {
  list-style: none;
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin: 0;
  padding: 0;
}

.related a {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  min-height: 24px;
  padding: 0 var(--s2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  color: var(--fg-2);
  font-size: var(--fs-xs);
  text-decoration: none;
  transition: border-color var(--dur-base) var(--ease-std),
              color var(--dur-base) var(--ease-std),
              background-color var(--dur-base) var(--ease-std);
}

.related a .icon {
  flex: none;
  color: var(--fg-3);
  transition: color var(--dur-base) var(--ease-std),
              transform var(--dur-base) var(--ease-out);
}

.related a:hover { border-color: var(--stroke); color: var(--fg-0); background: var(--bg-3); }
.related a:hover .icon { color: var(--accent-400); transform: translateX(2px); }

.xref { color: var(--accent-400); }

/* -- Copy affordances ----------------------------------------------------- */
.copylink {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-width: 7.5rem;
}

.copylink .ci { position: relative; flex: none; width: 14px; height: 14px; }
.copylink .ci svg { position: absolute; inset: 0; transition: opacity var(--dur-base) var(--ease-std); }
.copylink .ci-check { opacity: 0; color: var(--ok); }
.copylink[data-copied="true"] { border-color: var(--ok); color: var(--ok); }
.copylink[data-copied="true"] .ci-copy { opacity: 0; }
.copylink[data-copied="true"] .ci-check { opacity: 1; }
.copylink[data-copied="fail"] { border-color: var(--warn-line); color: var(--warn-fg); }

.copy-code[data-copied="fail"] { border-color: var(--warn-line); color: var(--warn-fg); }
.copy-code[data-copied="fail"] .ci-copy { opacity: 1; color: var(--warn-fg); }


/* ============================================================================
   9. CALLOUTS - one primitive for gates, warnings and refusals
   ========================================================================== */

.callout {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  margin: var(--s3) 0;
  border-radius: var(--r-md);
  border: 1px solid var(--tone-line, var(--line-2));
  background: var(--tone-bg, var(--bg-2));
  box-shadow: var(--hairline);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

.callout > .callout-ico { color: var(--tone-fg, var(--fg-3)); margin-top: 2px; }
.callout > div > b:first-child { color: var(--tone-fg, var(--fg-1)); }
.callout p { margin: 0; }
.callout p + p { margin-top: var(--s2); }

.callout-warn { --tone-bg: var(--warn-bg); --tone-line: var(--warn-line); --tone-fg: var(--warn-fg); color: var(--warn-fg); }
.callout-gate { --tone-bg: var(--imp-med-bg); --tone-line: var(--imp-med-line); --tone-fg: var(--imp-med-fg); color: var(--fg-1); }
.callout-info { --tone-bg: var(--bg-2); --tone-line: var(--line-2); --tone-fg: var(--teal); color: var(--fg-2); }

.noscript-note { grid-template-columns: 1fr; max-width: var(--measure); }

/* style.css gives .gate a bare colour; the callout primitive owns it now. */
.entry-body .gate { margin: var(--s3) 0; color: var(--fg-1); font-size: var(--fs-sm); }
.entry-body .warning { margin: var(--s3) 0; }


/* ============================================================================
   10. WHAT WE REFUSE
   ========================================================================== */

/* The refusals are the catalog's last chapter, and they are laid out as one:
   a page-width section on the page's own left edge, so its heading starts on
   the same x as "How to read this guide" and "Keep going" instead of sitting
   indented behind a rail that belongs to the catalog. */
.head-count { margin-left: var(--s2); }

/* Thirteen refusals use the catalog's own list and its own row: one line each,
   the reason visible collapsed, the argument inside. A wall of thirteen prose
   cards was 40% of the page's height and nobody read the tenth. */
.nevers-list .entry { margin-bottom: 0; }

/* The edge carries the weight of the reason, so the column has rhythm instead
   of thirteen identical red hairlines. */
.never-entry > summary { border-left: 3px solid var(--line-2); }
.never-entry.nk-harm > summary { border-left-color: var(--imp-crit-line); }
.never-entry.nk-risk > summary { border-left-color: var(--imp-high-line); }
.never-entry[open] > summary { border-left-color: var(--accent-500); }
/* One track, not max-content: thirteen tags of different lengths hung off the
   right edge put their dividers at thirteen different x. A fixed column gives
   the section a single rule running down it. */
.never-entry .entry-badges { grid-template-columns: max-content; }

@media (min-width: 48em) {
  .never-entry .entry-badges { grid-template-columns: 7rem; }
}

/* A refusal has no scan line and one short tag, so its row stays a single line
   on the phone rather than dropping the tag under the title the way a catalog
   row's two badges do. */
@media (max-width: 48em) {
  .never-entry > summary {
    grid-template-columns: 16px minmax(0, 1fr) max-content;
    grid-template-areas: "mark title badges";
    align-items: center;
  }

  .never-entry > summary > .marker { align-self: center; margin-top: 0; }
  .never-entry .entry-badges { margin-top: 0; }
}
.never-entry .entry-body { padding-bottom: var(--s4); }
.never-entry .entry-foot { margin-top: var(--s4); }

/* The seventh pill vocabulary on this page was the refusal reason, in the same
   stroked form as impact. It is a tag now: a rule, then the word. The severity
   it carries is already drawn - it is the colour of the row's left edge. */
.refuse-tag {
  display: inline-flex;
  align-items: center;
  padding-left: var(--s3);
  border-left: 1px solid var(--line-1);
  color: var(--fg-3);
  font-size: var(--fs-xs);
  line-height: var(--lh-label);
  text-transform: uppercase;
  letter-spacing: .08em;
  white-space: nowrap;
}


/* ============================================================================
   11. EMPTY STATE
   ========================================================================== */

.empty {
  display: grid;
  justify-items: center;
  gap: var(--s4);
  color: var(--fg-2);
  border: 1px dashed var(--line-2);
  border-radius: var(--r-lg);
  padding: var(--s8) var(--s5);
  text-align: center;
  animation: guide-fade-in var(--dur-slow) var(--ease-out) both;
}

.empty p { margin: 0; max-width: 46ch; }
.empty .empty-why { color: var(--fg-0); font-size: var(--fs-body-lg); line-height: var(--lh-snug); }

@keyframes guide-fade-in { from { opacity: 0; transform: translateY(6px); } }


/* ============================================================================
   12. STATE AND MOTION
   ========================================================================== */

.is-hidden { display: none !important; }

@media (prefers-reduced-motion: no-preference) {
  .entry {
    transition: border-color var(--dur-base) var(--ease-std),
                box-shadow var(--dur-base) var(--ease-std),
                opacity var(--dur-base) var(--ease-std),
                transform var(--dur-base) var(--ease-std),
                display var(--dur-base) allow-discrete;
  }

  .entry.is-hidden {
    display: none !important;
    opacity: 0;
    transform: translateY(-4px) scale(.995);
  }

  .entry[open] .entry-body { animation: guide-rise var(--dur-slow) var(--ease-out); }

  #catalog.first-paint .entry {
    animation: guide-rise var(--dur-slow) var(--ease-out) both;
    animation-delay: calc(var(--i, 0) * 18ms);
  }
}

@keyframes guide-rise { from { opacity: 0; transform: translateY(6px); } }
@keyframes guide-prog { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* Arrival cue: a ring that decays. It is drawn INSIDE the row, because the row
   now lives in a clipped list and an outer halo would be cut off by it. */
.entry.land,
.tier-head.land { animation: guide-land 1200ms var(--ease-out) 1; }

@keyframes guide-land {
  0%   { box-shadow: inset 0 0 0 2px var(--accent-500); background-color: rgba(216, 112, 60, .10); }
  22%  { box-shadow: inset 0 0 0 2px rgba(216, 112, 60, .55); background-color: rgba(216, 112, 60, .06); }
  100% { box-shadow: inset 0 0 0 2px rgba(216, 112, 60, 0); background-color: transparent; }
}


/* ============================================================================
   13. KEEP GOING - the shared tile row
   Three destinations since the Command Reference joined, so the band rides the
   shared .tile-grid tracks unmodified: one column, still one between the
   breakpoints (a three-up row has no honest two-column form), three across at
   62em. The tiles, their chrome and their hover are the site's .tile component.
   ========================================================================== */

/* The kicker rows lost their text - they ranked the destinations instead of
   naming them - so the chevron keeps the corner it has always been read in. */
.keep-going .tile-kicker { justify-content: flex-end; }


/* ============================================================================
   14. PREFERENCE AND OUTPUT BLOCKS
   ========================================================================== */

@media (forced-colors: active) {
  .entry-badges .badge,
  .gate-tag,
  .risk-tag,
  .howto,
  .tier-list,
  .callout,
  .rail-btn,
  .sort-toggle { border: 1px solid CanvasText; }

  .entry + .entry { border-top: 1px solid CanvasText; }

  .sort-toggle input:checked + label { outline: 2px solid Highlight; outline-offset: -2px; }
}

@media print {
  .rail,
  .rail-jump,
  .copylink,
  .keep-going,
  .search-kbd,
  .copy-code .ci,
  .skip { display: none !important; }

  .guide-shell { display: block; }

  /* On paper there is no hover and no scroll, so the list frame stops earning
     its keep and each entry becomes its own block again. */
  .tier-list {
    border: 0;
    background: none;
    box-shadow: none;
    overflow: visible;
  }

  .entry {
    break-inside: avoid;
    border: 1px solid #bbb;
    margin-bottom: 10pt;
    box-shadow: none;
  }

  .entry + .entry { border-top: 1px solid #bbb; }

  .entry::details-content,
  .entry[open]::details-content {
    block-size: auto;
    opacity: 1;
    content-visibility: visible;
    overflow: visible;
  }

  .entry-body > * { max-width: none; }
  .tier-head { break-after: avoid; }
  .copy-code { border: 0; background: transparent; padding: 0; color: inherit; }

  /* The status ramps are tuned for a dark ground; on paper they become ink. */
  .badge,
  .gate-tag,
  .risk-tag {
    background: transparent !important;
    color: #1a1a1a !important;
    border-color: #b9b3a9 !important;
  }

  .badge .dot,
  .ev-label > .dot { background: #5a5a5a !important; }
  .callout { background: transparent !important; border-color: #b9b3a9 !important; color: #1a1a1a !important; }
  .callout > .callout-ico { color: #5a5a5a !important; }
}
