/* ============================================================================
   Link to NetEra light prototype: components.css
   Every reusable pattern, in page order. Values come from design-tokens.css.

   CONTENTS
   01. Buttons & links
   02. Header / navigation / mobile drawer
   03. Breadcrumbs & page hero (subpages)
   04. Homepage hero (schematic grid + topology illustration)
   05. Stats strip (animated counters)
   06. Cards: topic / editorial
   07. Bento grid (tools)
   08. Terminal window (dark artifact block)
   09. Tags, chips, badges
   10. Quiz sample & leaderboard
   11. Editorial band (packet journey)
   12. News grid + filter bar
   13. Newsletter / community CTA
   14. Footer
   15. Learn: sidebar layout, lesson body, try-it, FAQ, pager
   16. Forms & inline tool UI
   ============================================================================ */

/* ---- 01. Buttons ----------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--fs-base);
  line-height: 1;
  padding: 14px var(--sp-3);
  border-radius: 10px;
  border: 1px solid transparent;
  transition: background-color var(--t-fast), border-color var(--t-fast),
              color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
  white-space: nowrap;
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn .icon--sm { transition: transform var(--t-fast); }
.btn:hover .icon--sm { transform: translateX(2px); }

/* Primary CTA stays teal: the site keeps ONE interactive accent. */
.btn--primary { background: var(--accent); color: var(--on-accent); }
.btn--primary:hover { background: var(--accent-hover); color: var(--on-accent); box-shadow: var(--shadow-sm); }

.btn--ghost { background: var(--surface); color: var(--text); border-color: var(--line-strong); }
.btn--ghost:hover { color: var(--text); border-color: var(--ink-900); background: var(--paper-soft); }

.btn--sm { padding: 10px var(--sp-2); font-size: var(--fs-sm); }

.link-more {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-weight: 500;
  color: var(--accent);
}
.link-more .icon--sm { transition: transform var(--t-fast); }
.link-more:hover .icon--sm { transform: translateX(3px); }

/* ---- 02. Header / navigation ------------------------------------------------ */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--header-bg);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t), box-shadow var(--t);
}
.site-header.is-scrolled {
  border-bottom-color: var(--border);
  box-shadow: var(--shadow-header);
}
.site-header__inner {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 68px;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--fs-lg);
  letter-spacing: var(--track-tight);
  color: var(--text);
  margin-right: auto;
}
.brand:hover { text-decoration: none; color: var(--text); }
.brand__mark { width: 30px; height: 30px; flex: none; }
.brand__tld { color: var(--accent); }
/* Link to NetEra lockup (2026-10-23): one ink per theme; the boot script always sets data-theme. */
/* Two classes: the header sits inside an Elementor widget, and Elementor's .elementor img { height: auto } outranks one class. */
.brand .brand__logo { display: block; height: 36px; width: auto; }
:root[data-theme="dark"] .brand__logo--on-light,
:root:not([data-theme="dark"]) .brand__logo--on-dark { display: none; }

.site-nav { display: none; }
.site-nav .nav-cursor { display: none; }
@media (min-width: 900px) { .site-nav .nav-cursor { display: block; } }
.header-cta { display: none; }
.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--r-sm);
  color: var(--text);
  border: 1px solid var(--border);
  background: var(--surface);
}
.nav-toggle .icon-close, .nav-toggle[aria-expanded="true"] .icon-menu { display: none; }
.nav-toggle[aria-expanded="true"] .icon-close { display: block; }

/* ---- Mobile drawer ---------------------------------------------------------
   Rebuilt 2026-08-31 to the W3Schools mobile menu the owner referenced: a
   full-height sheet of edge-to-edge rows, each separated by a single hairline
   that runs the whole width, with the label left and its chevron hard right.

   It used to be a short dropdown of rounded, gapped, 12px-padded pills floating
   under the header. That reads as a menu card; the reference reads as a page
   you are on, which is why every row is full-bleed and nothing is rounded.

   The panel is FIXED rather than absolute, so the list does not scroll away
   with the page behind it, and it stops at the bottom of the screen and scrolls
   internally instead of running off the end.

   The controls the reference puts inside the sheet: theme toggle and close: 
   are already in the header on this site, and stay there: the hamburger becomes
   the X, and the theme toggle sits beside it. Duplicating either one inside the
   drawer would mean two toggles in the DOM fighting over the same state. */
.site-nav--open {
  display: block;
  position: fixed;
  /* 68px is the header height the lesson rail is also pinned to; keep the two
     in step if the header ever changes. */
  top: 100%;
  left: 0;
  right: 0;
  /* NOT bottom: 0. .site-header carries backdrop-filter: blur(12px), and a
     filter or backdrop-filter makes an element the containing block for its
     position:fixed descendants, so `bottom: 0` resolved against the 69px-tall
     header instead of the viewport and gave this panel a height of 1px. The
     rows still laid out at the right size inside it, which is what made it look
     like a rendering glitch rather than a geometry bug. Viewport units are
     immune: dvh also tracks the mobile URL bar sliding in and out. */
  height: calc(100vh - 68px);
  height: calc(100dvh - 68px);
  background: var(--page);
  border-top: 1px solid var(--border);
  box-shadow: none;
  padding: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  z-index: var(--z-header);
}
.site-nav--open .site-nav__list {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.site-nav--open .site-nav__list > li { border-bottom: 1px solid var(--border); }
.site-nav--open .site-nav__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  min-height: 68px;
  padding: 0 var(--gutter);
  border-radius: 0;
  font-family: var(--font-head);
  font-size: var(--fs-lg);
  font-weight: 500;
  color: var(--text);
  background: var(--surface);
}
.site-nav--open .site-nav__link:hover,
.site-nav--open .site-nav__link:active { background: var(--paper-soft); text-decoration: none; }

/* The CTA is the one row that should still look like a button, so it keeps its
   own treatment and sits below the list with room around it. */
.site-nav--open .site-nav__cta {
  border-bottom: 0;
  padding: var(--sp-3) var(--gutter) var(--sp-6);
  background: var(--page);
}
.site-nav--open .site-nav__cta .btn {
  width: 100%;
  min-height: 52px;
  font-size: var(--fs-base);
}

@media (min-width: 900px) {
  .nav-toggle { display: none; }
  /* nav absolutely centered in the header (brand stays left, CTA right) */
  .site-nav {
    display: block;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }
  /* Slide-tabs pill: a dark cursor slides under the hovered tab and snaps
     back to the current page's tab on mouse leave (SlideTabs pattern). */
  .site-nav__list {
    position: relative;
    display: flex;
    align-items: center;
    gap: 2px;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    border-radius: var(--r-pill);
    padding: 4px;
  }
  /* class+element selector so `.site-nav__list > li` below can't out-rank it
     (the cursor is itself an <li> injected by js/main.js) */
  .site-nav__list > li.nav-cursor {
    position: absolute;
    top: 4px;
    bottom: 4px;
    left: 0;
    width: 0;
    border-radius: var(--r-pill);
    background: var(--ink-900);
    opacity: 0;
    z-index: 0;
    transition: left 0.28s var(--ease), width 0.28s var(--ease), opacity 0.2s var(--ease);
    pointer-events: none;
  }
  .site-nav__list > li { position: relative; z-index: 1; }
  .site-nav__list > li.nav-drop { position: relative; }
  .site-nav__link {
    position: relative;
    display: inline-flex;
    align-items: center;
    padding: 9px 16px;
    border-radius: var(--r-pill);
    font-size: 0.9375rem;
    font-weight: 500;
    color: var(--text-muted);
    transition: color 0.18s var(--ease);
    white-space: nowrap;
  }
  .site-nav__link:hover { text-decoration: none; }
  .site-nav__link[aria-current="page"], .site-nav__link.is-active { color: var(--accent); }
  /* tab the cursor currently sits under: flip to inverse text */
  li.is-on > .site-nav__link,
  li.is-on > .site-nav__link[aria-current="page"],
  li.is-on > .nav-drop__trigger.is-active { color: var(--paper); }
  .site-nav__cta { display: none; }
  .header-cta { display: inline-flex; }
  /* dropdown panels open below the pill */
  .nav-drop__panel { top: calc(100% + 12px); }
}

/* ---- 02b. Nav dropdowns (Learn / Tools) ------------------------------------- */
.nav-drop { position: relative; }
.nav-drop__trigger { gap: 6px; }
.nav-drop__trigger .icon--xs { transition: transform var(--t-fast); }
.nav-drop.is-open > .nav-drop__trigger .icon--xs { transform: rotate(180deg); }

.nav-drop__panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 460px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lift);
  padding: var(--sp-2);
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity 0.18s var(--ease), transform 0.18s var(--ease), visibility 0s linear 0.18s;
  z-index: calc(var(--z-header) + 1);
}
/* hover bridge so the pointer can cross the gap */
.nav-drop__panel::before { content: ""; position: absolute; top: -10px; left: 0; right: 0; height: 10px; }
.nav-drop.is-open .nav-drop__panel {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s, 0s, 0s;
}
.nav-drop__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; }
/* A tall menu (Games) must not run off the bottom of a short screen: the list
   scrolls inside the panel and the footer link ("All games") stays in view.
   210px is the header, the panel's padding and that footer; the admin bar adds
   32px for signed-in editors. Desktop only. (2026-10-23) */
@media (min-width: 900px) {
  .nav-drop__grid { max-height: calc(100vh - 210px); overflow-y: auto; overscroll-behavior: contain; }
  .admin-bar .nav-drop__grid { max-height: calc(100vh - 242px); }
}
/* A dropdown holding a single entry still gets the 460px two-column panel,
   which leaves half of it empty and wraps the one title mid-phrase. Narrow it
   rather than padding the menu out with filler links. */
.nav-drop__panel--single { min-width: 312px; }
.nav-drop__panel--single .nav-drop__grid { grid-template-columns: 1fr; }
.nav-drop__panel--single .nav-drop__foot { flex-wrap: wrap; gap: 4px var(--sp-2); }
/* The rightmost dropdown opens leftward, from its trigger's right edge. The bar
   is centred, so its right-hand triggers move whenever any label changes: when
   "Tech News" became "Blog" (2026-10-06) the bar narrowed, every trigger shifted
   right, and the 460px Arcade panel ran 14px off a 900px screen. Below 900px
   panels are static accordions, where left/right do nothing. */
.nav-drop__panel--end { left: auto; right: 0; }
.nav-drop__item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  color: var(--text);
}
.nav-drop__item:hover { background: var(--paper-soft); text-decoration: none; color: var(--text); }
.nav-drop__item .drop-ic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  flex: none;
  border-radius: 9px;
  background: var(--accent-soft);
  color: var(--accent);
}
.nav-drop__item:hover .drop-ic { background: var(--teal-100); }
.nav-drop__item strong {
  display: block;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--fs-sm);
  line-height: 1.3;
}
.nav-drop__item small {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-top: 1px;
}
.nav-drop__foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--sp-1) var(--sp-2);
  border-top: 1px solid var(--border);
  margin-top: var(--sp-1);
  padding: var(--sp-1) 12px 2px;
  font-size: var(--fs-sm);
}
.nav-drop__foot .link-more { font-size: var(--fs-sm); }
.nav-drop__hint { color: var(--text-muted); }
.nav-drop__hint:hover { color: var(--accent); }

/* ---- Tools mega-menu ------------------------------------------------------
   Learn, Labs and Arcade all use the shared 460px two-column panel above.
   Tools is the exception: fifteen entries in the five groups tools/index.njk
   already uses, three columns balanced 6 / 5 / 4 rows so the panel never needs
   to scroll. Width is desktop-only; inside the mobile drawer it is a
   full-width accordion and the media query below resets it. */
@media (min-width: 900px) {
  /* The panel is anchored left:0 on its own <li>, so its right edge is
     (trigger position + width) and the trigger moves right as the centred nav
     shrinks. Measured 2026-10-07, after "Tech News" became the shorter "Blog":
     the Tools trigger sits 269px in at 900px wide and 331px in at 1024px,
     leaving 631px and 693px of room (it was 245px / 643px before, which is
     why 632px used to fit). 616px keeps 15px spare at 900px. 720px fits from
     ~1100px up and overflows below it: what tests/nav.spec.js guards. */
  .nav-drop__panel--tools { min-width: 616px; max-width: calc(100vw - 24px); }
}
/* Room for the third column's longest names ("CLI Config Generator", "HTTP
   Server Test") on one line each. Below this the panel keeps 616px and those
   two wrap, which is the graceful version of not fitting. At 1100px the
   trigger sits 369px in, so 720px leaves 11px spare. */
@media (min-width: 1100px) {
  .nav-drop__panel--tools { min-width: 720px; }
}

/* Quiet uppercase group label. Doubles as the one-line lead at the top of the
   Learn and Labs panels, where it states the scope instead of naming a group. */
.nav-drop__group {
  margin: 0;
  padding: 6px 12px 4px;
  font-family: var(--font-head);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.nav-drop__col > .nav-drop__group + .nav-drop__group { margin-top: 0; }
.nav-drop__col > .nav-drop__list + .nav-drop__group { margin-top: var(--sp-2); }

.nav-drop__cols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 var(--sp-1); }
.nav-drop__col { min-width: 0; }
.nav-drop__list { list-style: none; margin: 0; padding: 0; }

/* Compact row for the Tools menu: the group heading above it carries the
   meaning the per-item description used to, so fifteen tools fit in the panel
   instead of overflowing it into a scroller. */
.nav-drop__link {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 38px;
  padding: 7px 10px;
  border-radius: var(--r-sm);
  color: var(--text);
  font-size: var(--fs-sm);
}
.nav-drop__link:hover { background: var(--paper-soft); text-decoration: none; color: var(--text); }
.nav-drop__link .icon { flex: none; color: var(--accent); }
.nav-drop__link-name { min-width: 0; }
.nav-drop__note { margin: 6px 10px 0; font-size: var(--fs-xs); color: var(--text-muted); line-height: 1.4; }

/* Marks the four tools that reach the real internet through Globalping. The
   hub says "Nothing here is simulated" about exactly these four; the menu is
   allowed to say it too, and about nothing else. */
.drop-live {
  flex: none;
  margin-left: auto;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--font-head);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
}


/* Mobile: dropdowns collapse into accordions inside the existing drawer */
@media (max-width: 899px) {
  .nav-drop__trigger {
    width: 100%;
    justify-content: space-between;
    align-items: center;
    display: flex;
    min-height: 68px;
    padding: 0 var(--gutter);
    border-radius: 0;
    font-family: var(--font-head);
    font-size: var(--fs-lg);
    font-weight: 500;
    color: var(--text);
    background: var(--surface);
  }
  .nav-drop__trigger:hover, .nav-drop__trigger:active { background: var(--paper-soft); }
  /* The chevron is the reference's affordance for "this opens": bigger than the
     desktop one and pinned to the right edge of the row. */
  .nav-drop__trigger .icon--xs { width: 20px; height: 20px; flex: none; }
  .nav-drop__panel {
    position: static;
    min-width: 0;
    border: 0;
    border-top: 1px solid var(--border);
    box-shadow: none;
    padding: var(--sp-1) 0 var(--sp-2);
    background: var(--page);
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: none;
    display: none;
  }
  .nav-drop__panel .nav-drop__item,
  .nav-drop__panel .nav-drop__link { padding-left: var(--gutter); padding-right: var(--gutter); }
  .nav-drop__panel .nav-drop__group,
  .nav-drop__panel .nav-drop__note,
  .nav-drop__panel .nav-drop__foot { padding-left: var(--gutter); padding-right: var(--gutter); }
  .nav-drop.is-open .nav-drop__panel { display: block; }
  .nav-drop__panel::before { display: none; }
  .nav-drop__grid { grid-template-columns: 1fr; }
  .nav-drop__foot { justify-content: flex-start; }
  .nav-drop__cols { grid-template-columns: 1fr; }
  .nav-drop__link { min-height: 44px; }
  .nav-drop__col > .nav-drop__list + .nav-drop__group { margin-top: var(--sp-1); }
}

/* ---- 03. Breadcrumbs & subpage hero ---------------------------------------- */
.breadcrumbs { font-size: var(--fs-sm); color: var(--text-muted); }
/* the breadcrumb <ol> has no class, so the global ol[class] reset misses it: 
   reset list markers/padding here or it renders "1. Home 2. / …" */
.breadcrumbs ol { list-style: none; padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1); }
.breadcrumbs a { color: var(--text-muted); }
.breadcrumbs a:hover { color: var(--accent); }
.breadcrumbs li:not(:first-child)::before {
  content: "/";
  margin-right: var(--sp-1);
  color: var(--slate-400);
}
.breadcrumbs [aria-current="page"] { color: var(--text); font-weight: 500; }

.page-hero {
  padding-block: var(--sp-6) var(--sp-8);
  /* Design B's hero ground (2026-10-04): a soft acid glow top-right and a
     faint dot grid over --hero-bg, fading out towards the copy. */
  background:
    radial-gradient(720px 300px at 85% -30%, var(--hero-glow), transparent 70%),
    radial-gradient(circle at 1px 1px, var(--hero-dot) 1px, transparent 1.6px) 0 0 / 22px 22px,
    var(--hero-bg);
  border-bottom: 1px solid var(--border);
}
.page-hero .breadcrumbs { margin-bottom: var(--sp-3); }
.page-hero h1 { max-width: 16em; }
.page-hero .lead { margin-top: var(--sp-2); max-width: 56ch; }
.page-hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  margin-top: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.page-hero__meta .meta-item { display: inline-flex; align-items: center; gap: 6px; }
.page-hero__meta .icon--xs { color: var(--accent-decor); }

/* ---- 04. Homepage hero -------------------------------------------------------- */
.hero {
  position: relative;
  overflow: hidden;
  padding-block: var(--sp-8) var(--sp-10);
  /* An explicit colour, not the page's: the hero carries only a grid IMAGE, so
     once --page stopped being white it would have inherited the tint and the
     page would have opened on the same flat colour it scrolls through.
     --surface keeps it a bright slab, the same way .page-hero reads on the
     interior pages. */
  background-color: var(--surface);
  background-image: var(--grid-paper);
  background-size: var(--grid-size);
}
/* fade the schematic grid out toward the bottom */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(255,255,255,0) 40%, var(--surface) 96%);
  pointer-events: none;
}
.hero__inner {
  position: relative;
  z-index: 1;
  display: grid;
  gap: var(--sp-4);
  align-items: center;
}
/* Grid items default to min-width:auto, so they refuse to shrink below their
   content's min-content width. One stubborn child: the terminal's title bar,
   measured at 355px min-content: pinned the whole column at 357px at EVERY
   viewport, and .hero{overflow:hidden} then clipped the headline off-screen
   below ~375px. min-width:0 lets the column track the viewport again. */
.hero__inner > * { min-width: 0; }

/* Phone order: headline → live terminal → pitch. Source order stays
   intro → pitch → art (content before decoration); only the visual order
   moves, so the reading order for a screen reader is unaffected by the
   terminal being lifted. Gap is --sp-4 rather than --sp-6 because there are
   now two gaps instead of one, and every pixel above the fold is spent on
   getting the terminal onto the first screen. */
.hero__intro { order: 1; }
.hero__art   { order: 2; }
.hero__pitch { order: 3; }
.hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: var(--r-pill);
  padding: 6px 14px;
  margin-bottom: var(--sp-3);
}
.hero__eyebrow::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: var(--r-pill);
  background: var(--accent-decor);
  flex: none;
}
@media (max-width: 479px) {
  .hero__eyebrow { font-size: 0.6875rem; letter-spacing: 0.08em; }
}
.hero h1 {
  font-size: var(--fs-display);
  font-weight: 600;
  max-width: 13em;
}
.hero h1 .accent-word {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: var(--accent-decor);
  text-decoration-thickness: 3px;
  text-underline-offset: 7px;
}
.hero .lead { margin-top: var(--sp-3); max-width: 52ch; }
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
}
.hero__hint {
  margin-top: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.hero__hint .icon--xs { color: var(--accent); }

.hero__art { position: relative; min-width: 0; }

/* The bar is a space-between flex row of two nowrap labels; together they set a
   355px floor. Let the title ellipsis instead of forcing the terminal wider
   than the phone. */
.term__bar { gap: var(--sp-2); }
.term__title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.term__tag { flex: none; }
/* On a phone the tag repeats what the input's own placeholder already says
   ("nslookup cloudflare.com") and costs enough width to ellipsis the title.
   Drop the duplicate rather than truncate the name of the thing. */
@media (max-width: 480px) {
  .hero-term .term__tag { display: none; }
}
.hero__art svg { width: 100%; height: auto; }

@media (min-width: 1024px) {
  /* Desktop rebuilds the original two columns: the split into three blocks is
     a mobile ordering device, not a new desktop layout. Explicit placement
     puts intro and pitch back in one left column with the terminal spanning
     both rows on the right, and row-gap 0 keeps their original spacing, which
     came from the h1's own margin rather than a grid gap. */
  .hero__inner { grid-template-columns: 7fr 5fr; gap: 0 var(--sp-8); }
  .hero__intro { grid-column: 1; grid-row: 1; }
  .hero__pitch { grid-column: 1; grid-row: 2; }
  .hero__art   { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
}

/* ---- 05. Stats strip ---------------------------------------------------------- */
.stats { border-block: 1px solid var(--border); background: var(--surface); }
.stats__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-4) var(--sp-3);
  padding-block: var(--sp-5);
}
.stat { position: relative; }
.stat__value {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--fs-2xl);
  letter-spacing: var(--track-tight);
  color: var(--text);
  font-variant-numeric: tabular-nums;
  display: flex;
  align-items: baseline;
  gap: 2px;
}
.stat__value .stat__plus { color: var(--accent); font-size: 0.7em; }
.stat__label {
  margin-top: 4px;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.stat__live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--sp-1);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--accent);
}
.stat__live::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: var(--r-pill);
  background: var(--accent-decor);
}
@media (min-width: 900px) {
  .stats__grid { grid-template-columns: repeat(4, 1fr); }
  .stat + .stat { padding-left: var(--sp-4); border-left: 1px dashed var(--line-strong); }
}

/* ---- 06. Cards: topic & editorial ------------------------------------------- */
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  transition: transform var(--t), box-shadow var(--t), border-color var(--t);
}
.card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lift);
  border-color: var(--accent-decor);
}
.card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  border-radius: 12px;
  background: var(--accent-soft);
  color: var(--accent);
  margin-bottom: var(--sp-2);
}
.card h3, .card h4 { font-size: var(--fs-lg); }
.card__desc { margin-top: var(--sp-1); font-size: var(--fs-sm); color: var(--text-muted); flex-grow: 1; }
.card__meta {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  margin-top: var(--sp-2);
}
/* stretch primary link over the card */
.card__link::after { content: ""; position: absolute; inset: 0; border-radius: var(--r-md); }
.card__link:hover { text-decoration: none; }
.card .card__link { color: var(--text); }
.card__go {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  color: var(--slate-400);
  transition: color var(--t-fast), transform var(--t-fast);
}
.card:hover .card__go { color: var(--accent); transform: translate(2px, -2px); }

/* Editorial grid: one featured card + supporting cards */
.editorial-grid { display: grid; gap: var(--sp-3); }
.card--feature { padding: var(--sp-4); justify-content: flex-end; min-height: 320px; overflow: hidden; }
.card--feature h3 { font-size: var(--fs-xl); }
.card--feature .card__art {
  position: absolute;
  inset: 0 0 auto 0;
  height: 55%;
  pointer-events: none;
}
.card--feature .card__art svg { width: 100%; height: 100%; }
.card--feature .card__body { position: relative; margin-top: auto; }
@media (min-width: 720px) {
  .editorial-grid { grid-template-columns: repeat(2, 1fr); }
  .card--feature { grid-column: span 2; }
}
@media (min-width: 1024px) {
  .editorial-grid { grid-template-columns: repeat(4, 1fr); }
  .card--feature { grid-column: span 2; grid-row: span 2; }
}

/* ---- 07. Bento grid ------------------------------------------------------------ */
.bento { display: grid; gap: var(--sp-3); }
.bento__cell {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  transition: transform var(--t), box-shadow var(--t), border-color var(--t);
  overflow: hidden;
}
/* No vertical lift here (2026-07-19): once these cells hold a real input +
   button, jumping the whole card on hover fights with a visitor mid-type or
   aiming a click. A border/shadow glow gives the same "this responds to you"
   cue without the motion. :focus-within so tabbing into the input gets the
   same highlight a mouse hover would. */
.bento__cell:hover, .bento__cell:focus-within {
  transform: none;
  box-shadow: var(--shadow-md);
  border-color: var(--accent-decor);
}
.bento__head { display: flex; align-items: flex-start; gap: var(--sp-2); }
.bento__head .card__icon { margin-bottom: 0; }
.bento__title-wrap { flex: 1; }
.bento__kicker {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}
.bento__cell h3 { font-size: var(--fs-lg); margin-top: 2px; }
.bento__desc { margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--text-muted); }
.bento__artifact { margin-top: var(--sp-3); }

/* progressive disclosure: extra detail expands on hover / toggle */
.bento__more {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--t-slow);
}
.bento__more > div { overflow: hidden; }
.bento__more-inner {
  padding-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.bento__cell.is-open .bento__more { grid-template-rows: 1fr; }
@media (hover: hover) {
  .bento__cell:hover .bento__more { grid-template-rows: 1fr; }
}
.bento__toggle {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 36px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  color: var(--text-muted);
  background: var(--surface);
  transition: color var(--t-fast), border-color var(--t-fast), transform var(--t);
}
.bento__toggle:hover { color: var(--accent); border-color: var(--accent-decor); }
.bento__cell.is-open .bento__toggle { transform: rotate(180deg); color: var(--accent); }

/* teaser cells (tools hub): whole cell is the link, cues respond to cell hover */
.bento__cell .card__go { z-index: 1; }
.bento__cell:hover .card__go { color: var(--accent); transform: translate(2px, -2px); }
.bento__cell .link-more { align-self: flex-start; }
.bento__cell:hover .link-more .icon--sm { transform: translateX(3px); }
.bento__cell:hover .kv .hl { color: var(--teal-600); }

@media (min-width: 720px) {
  .bento { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  .bento { grid-template-columns: repeat(12, 1fr); }
  .bento__cell--7 { grid-column: span 7; }
  .bento__cell--5 { grid-column: span 5; }
  .bento__cell--6 { grid-column: span 6; }
  .bento__cell--4 { grid-column: span 4; }
  .bento__cell--8 { grid-column: span 8; }
  .bento__cell--3 { grid-column: span 3; }   /* four across, live network tests */
}

/* ---- 08. Terminal window (dark artifact) -------------------------------------- */
.term {
  background: var(--code-bg);
  border: 1px solid var(--code-line);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: var(--shadow-md);
}
.term__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: 10px var(--sp-2);
  border-bottom: 1px solid var(--code-line);
  background: var(--code-surface);
}
.term__title {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--code-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.term__tag {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--code-teal);
  border: 1px solid var(--code-line);
  border-radius: var(--r-pill);
  padding: 2px 10px;
  white-space: nowrap;
}
.term__body {
  margin: 0;
  padding: var(--sp-2) var(--sp-2) var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  line-height: 1.7;
  color: var(--code-text);
  overflow-x: auto;
  white-space: pre;
}
.term__body .c-cmd { color: var(--code-teal); }
.term__body .c-dim { color: var(--code-muted); }
.term__body .c-key { color: var(--code-violet); }
.term__body .c-ok { color: var(--code-teal); }

/* code block inside lesson body reuses .term; lighter variant for configs */
.codeblock { margin-block: var(--sp-3); }

/* ---- 09. Tags, chips, badges ---------------------------------------------------- */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  font-weight: 500;
  border-radius: var(--r-pill);
  padding: 4px 12px;
  border: 1px solid transparent;
  white-space: nowrap;
}
.tag--difficulty { background: var(--paper-mist); color: var(--ink-800); border-color: var(--border); }
.tag--teal { background: var(--teal-100); color: var(--teal-700); }
.tag--violet { background: var(--violet-100); color: var(--violet-600); }
.tag--outline { background: var(--surface); color: var(--text-muted); border-color: var(--line-strong); }

.badge-dot {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.badge-dot::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: var(--r-pill);
  background: var(--accent-decor);
}

/* ---- 10. Quiz sample & leaderboard --------------------------------------------- */
.quiz-sample {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
}
.quiz-sample__q {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--fs-md);
  letter-spacing: var(--track-tight);
}
.quiz-sample__meta {
  display: flex;
  gap: var(--sp-1);
  margin-bottom: var(--sp-2);
  flex-wrap: wrap;
}
.quiz-sample__opts { display: grid; gap: var(--sp-1); margin-top: var(--sp-2); }
.quiz-opt {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  text-align: left;
  padding: 12px var(--sp-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text);
  transition: border-color var(--t-fast), background-color var(--t-fast);
}
.quiz-opt__key {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: 6px;
  width: 24px; height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.quiz-opt:hover { border-color: var(--accent); background: var(--teal-050); }
.quiz-opt.is-correct { border-color: var(--success-600); background: var(--success-100); }
.quiz-opt.is-correct .quiz-opt__key { border-color: var(--success-600); color: var(--success-600); }
.quiz-opt.is-wrong { border-color: var(--danger-600); background: var(--danger-100); }
.quiz-opt.is-wrong .quiz-opt__key { border-color: var(--danger-600); color: var(--danger-600); }
.quiz-opt[disabled] { cursor: default; }
.quiz-sample__expl {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  border-left: 3px solid var(--accent-decor);
  padding-left: var(--sp-2);
}
.quiz-sample__expl strong { color: var(--success-600); }
.quiz-sample__expl[hidden] { display: none; }

/* Playable quiz app (Exercises) */
.quiz-app {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
}
.quiz-app__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.quiz-app__bar b { color: var(--text); }
.quiz-app__progress {
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--paper-mist);
  overflow: hidden;
  margin-top: var(--sp-1);
}
.quiz-app__progress span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  border-radius: var(--r-pill);
  transition: width var(--t);
}
.quiz-app__result { text-align: center; padding: var(--sp-4) var(--sp-2); }
.quiz-app__result .card__icon { margin: 0 auto var(--sp-2); width: 48px; height: 48px; }
.quiz-app__result h3 { font-size: var(--fs-xl); }
.quiz-app__result p { margin: var(--sp-2) auto 0; color: var(--text-muted); font-size: var(--fs-sm); max-width: 42ch; }
.quiz-app__foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-1);
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
}
.quiz-app__foot [data-q-restart] { margin-right: auto; }

/* Difficulty filter pills */
/* Count beside a category name. Quiet, tabular, and never invented. It is
   BANK.filter(topic).length, counted at render time. */
.diff-pill__n {
  margin-left: 6px;
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  opacity: .6;
}
.diff-pill[aria-pressed="true"] .diff-pill__n { opacity: .85; }
/* Two bars stack; the second should not look like a continuation of the first. */
.diff-pills + .diff-pills { margin-top: var(--sp-1); }
.diff-pills { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-bottom: var(--sp-3); }
.diff-pills__label { font-size: var(--fs-sm); color: var(--text-muted); align-self: center; margin-right: var(--sp-1); }
.diff-pill {
  font-size: var(--fs-sm); font-weight: 500; color: var(--text-muted);
  border: 1px solid var(--line-strong); border-radius: var(--r-pill);
  padding: 7px var(--sp-2); background: var(--surface);
  transition: color var(--t-fast), border-color var(--t-fast), background-color var(--t-fast);
}
.diff-pill:hover { color: var(--accent); border-color: var(--accent-decor); }
.diff-pill[aria-pressed="true"] { background: var(--ink-900); border-color: var(--ink-900); color: var(--paper); }

/* Terminal exercise input row */
.term-ex__prompt { font-family: var(--font-mono); color: var(--accent); align-self: center; font-weight: 500; }
.term-ex__input {
  flex: 1 1 200px; font-family: var(--font-mono); font-size: var(--fs-sm);
  padding: 12px var(--sp-2); border: 1px solid var(--line-strong); border-radius: var(--r-sm); background: var(--surface);
}
.term-ex__input:focus-visible { box-shadow: var(--focus-ring); border-color: var(--accent); }

/* Fix-the-config clickable lines */
.fixcfg__lines { display: flex; flex-direction: column; padding: var(--sp-1); }
.fixcfg__line {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%; text-align: left;
  font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--code-text);
  padding: 8px var(--sp-2); border-radius: var(--r-sm); border: 1px solid transparent;
  transition: background-color var(--t-fast);
}
.fixcfg__line:hover { background: rgba(255,255,255,0.05); }
.fixcfg__no { color: var(--code-muted); width: 1.4em; flex: none; }
.fixcfg__line.is-correct { background: rgba(21,127,70,0.22); border-color: var(--success-600); }
.fixcfg__line.is-wrong { background: rgba(194,52,52,0.22); border-color: var(--danger-600); }
.fixcfg__line[disabled] { cursor: default; }
.fixcfg__fix { border-left-color: var(--accent-decor); }

/* Subnet slider exercise */
.subnet-ex__row { display: flex; align-items: center; gap: var(--sp-2); }
.subnet-ex__row label { font-size: var(--fs-sm); color: var(--text-muted); }
.subnet-ex input[type="range"] { flex: 1; accent-color: var(--accent); }
.subnet-ex__val { font-family: var(--font-mono); font-weight: 600; color: var(--accent); min-width: 3.5em; text-align: right; }

/* Scenario branching */
.scenario__text { font-family: var(--font-head); font-weight: 600; font-size: var(--fs-md); margin-bottom: var(--sp-2); }

/* Challenge timer bar + clock */
.challenge-timer span { background: var(--warning-600); }
.challenge-clock { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--text-muted); margin-right: auto; }

/* Progress dashboard: streak + review + mastery */
.progress-band { display: grid; gap: var(--sp-3); }
@media (min-width: 720px) { .progress-band { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .progress-band { grid-template-columns: 1fr 1fr 1.4fr; } }
.stat-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: var(--sp-3); display: flex; flex-direction: column; gap: 4px;
}
.stat-card__value { font-family: var(--font-head); font-weight: 600; font-size: var(--fs-2xl); display: flex; align-items: center; gap: var(--sp-1); }
.stat-card__value .icon { color: var(--warning-600); }
.stat-card__label { font-size: var(--fs-sm); color: var(--text-muted); }
.stat-card--review .stat-card__value { color: var(--accent); }
.stat-card__cta { margin-top: var(--sp-1); font-size: var(--fs-sm); font-weight: 500; }

.mastery-grid { display: grid; gap: var(--sp-2); }
@media (min-width: 560px) { .mastery-grid { grid-template-columns: 1fr 1fr; } }
.mastery-item { display: flex; flex-direction: column; gap: 6px; }
.mastery-item__head { display: flex; justify-content: space-between; font-size: var(--fs-sm); }
.mastery-item__head .num { color: var(--text-muted); }
.mastery-bar { height: 7px; border-radius: var(--r-pill); background: var(--paper-mist); overflow: hidden; }
.mastery-bar span { display: block; height: 100%; background: var(--accent); border-radius: var(--r-pill); transition: width var(--t); }

/* per-topic mastery bar on Learn pages */
.learn-mastery {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-4); display: flex; flex-direction: column; gap: 8px;
}

/* Weekly challenge log */
.challenge-log { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
.challenge-log__total {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: var(--sp-2) var(--sp-3); background: var(--paper-soft); border-bottom: 1px solid var(--border);
}
.challenge-log__total span { font-size: var(--fs-sm); color: var(--text-muted); }
.challenge-log__total strong { font-family: var(--font-head); font-size: var(--fs-lg); }
.challenge-log ul { display: flex; flex-direction: column; }
.challenge-log li {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  padding: 10px var(--sp-3); border-bottom: 1px solid var(--border); font-size: var(--fs-sm);
}
.challenge-log li:last-child { border-bottom: 0; }

/* nav "due for review" badge */
.nav-review-badge {
  display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px;
  padding: 0 5px; margin-left: 5px; border-radius: var(--r-pill);
  background: var(--danger-600); color: #fff; font-family: var(--font-mono);
  font-size: 0.6875rem; font-weight: 500; line-height: 1; vertical-align: middle;
}

.leaderboard {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
}
.leaderboard li.is-me { background: var(--teal-050); }
.leaderboard li.is-me .learner { color: var(--accent); font-weight: 600; }
.leaderboard__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
  background: var(--paper-soft);
}
.leaderboard__title {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--fs-base);
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.leaderboard__title .icon--sm { color: var(--accent); }
.leaderboard ol { display: flex; flex-direction: column; }
.leaderboard li {
  display: grid;
  grid-template-columns: 32px 1fr auto auto;
  align-items: center;
  gap: var(--sp-2);
  padding: 10px var(--sp-3);
  font-size: var(--fs-sm);
  border-bottom: 1px solid var(--border);
}
.leaderboard li:last-child { border-bottom: 0; }
/* Shown until the first run is finished: the grid columns the ranked rows use
   would leave this stranded in the rank column. */
.leaderboard li.leaderboard__empty {
  display: block;
  color: var(--text-muted);
  font-size: var(--fs-sm);
}
.leaderboard .rank {
  font-family: var(--font-mono);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.leaderboard li:first-child .rank { color: var(--accent); font-weight: 600; }
.leaderboard .learner { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.leaderboard .streak {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--violet-600);
}
.leaderboard .streak .icon--xs { color: var(--violet-500); }
.leaderboard .points {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  font-weight: 500;
}
/* Honest empty state: shown until real challenge scores exist */
.leaderboard__empty {
  padding: var(--sp-4) var(--sp-3);
  text-align: center;
}
.leaderboard__empty-title {
  font-family: var(--font-head);
  font-weight: 600;
  color: var(--text);
}
.leaderboard__empty-sub {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  max-width: 34ch;
  margin: 6px auto var(--sp-3);
}
.leaderboard__empty .btn { margin-inline: auto; }

/* ---- 11. Editorial band ---------------------------------------------------------- */
.editorial { overflow: hidden; }
.editorial__inner { display: grid; gap: var(--sp-6); align-items: center; }
.editorial__art {
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-md);
}
.editorial__art svg { width: 100%; height: auto; display: block; }
/* small diagrams (exercises timeline) keep the light schematic panel look */
.editorial__art--panel {
  background: var(--surface);
  background-image: var(--grid-paper);
  background-size: var(--grid-size);
  padding: var(--sp-3);
  box-shadow: none;
}
.editorial__copy .lead { margin-top: var(--sp-2); }
.editorial__copy p + p { margin-top: var(--sp-2); }
.editorial__points { margin-top: var(--sp-3); }
@media (min-width: 1024px) {
  .editorial__inner { grid-template-columns: 7fr 5fr; }
  .editorial__inner--flip .editorial__art { order: -1; }
}

/* ---- 12. News grid + filter bar --------------------------------------------------- */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin-bottom: var(--sp-4);
}
.filter-btn {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text-muted);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  padding: 8px var(--sp-2);
  background: var(--surface);
  transition: color var(--t-fast), border-color var(--t-fast), background-color var(--t-fast);
}
.filter-btn:hover { color: var(--accent); border-color: var(--accent-decor); }
.filter-btn[aria-pressed="true"] {
  background: var(--ink-900);
  border-color: var(--ink-900);
  color: var(--paper);
}

.news-grid { display: grid; gap: var(--sp-3); }
.news-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  transition: transform var(--t), box-shadow var(--t), border-color var(--t);
}
.news-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lift);
  border-color: var(--accent-decor);
}
.news-card__cover { aspect-ratio: 16 / 9; overflow: hidden; border-bottom: 1px solid var(--border); }
.news-card__cover svg { width: 100%; height: 100%; }
/* interaction depth: cover art drifts on hover, a read cue fades in */
@media (prefers-reduced-motion: no-preference) {
  .news-card__cover svg { transition: transform 0.5s var(--ease); transform-origin: 50% 60%; }
  .news-card:hover .news-card__cover svg { transform: scale(1.05); }
  .news-card__body::after { transition: opacity 0.25s var(--ease), transform 0.25s var(--ease); }
}
.news-card__body::after {
  content: "Read the story →";
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--accent);
  opacity: 0;
  transform: translateY(4px);
}
.news-card:hover .news-card__body::after, .news-card:focus-within .news-card__body::after { opacity: 1; transform: none; }
.news-card__body { display: flex; flex-direction: column; flex: 1; padding: var(--sp-3); }
.news-card__meta {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  margin-bottom: var(--sp-1);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.news-card h3 { font-size: var(--fs-lg); }
.news-card__excerpt { margin-top: var(--sp-1); font-size: var(--fs-sm); color: var(--text-muted); flex: 1; }
.news-card time { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--slate-400); }
.news-card .card__link { color: var(--text); }
.news-card[hidden] { display: none; }

@media (min-width: 720px) { .news-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) {
  .news-grid { grid-template-columns: repeat(3, 1fr); }
  .news-card--feature { grid-column: span 2; grid-row: span 2; }
  .news-card--feature h3 { font-size: var(--fs-xl); }
  .news-card--feature .news-card__cover { aspect-ratio: auto; flex: 1; min-height: 260px; }
}

/* Related reading (lessons + articles): reuses .news-card wholesale */
.related-reading { margin-top: var(--sp-6); }
.related-reading .news-grid { grid-template-columns: 1fr; margin-top: var(--sp-3); }
@media (min-width: 720px) { .related-reading .news-grid { grid-template-columns: repeat(2, 1fr); } }
.related-reading .news-card__body::after { content: "Open →"; }

/* Keep reading: under every blog article (2026-10-18). A "Related" list of rows
   (date, headline, two-line summary, cover thumbnail on the right) and a "More
   from the blog" grid of compact tiles. Each row is one big link: the headline's
   ::after stretches over the whole row. */
.keep-reading { margin-top: var(--sp-8); padding-top: var(--sp-5); border-top: 1px solid var(--line); }
.kr-group + .kr-group { margin-top: var(--sp-6); }
.kr-head {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 var(--sp-1);
  font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 500;
  letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--text-muted);
}
.kr-head::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }
.kr-list, .kr-grid { list-style: none; margin: 0; padding: 0; }
.kr-item, .kr-tile { position: relative; border-top: 1px solid var(--line); }
.kr-item { display: grid; grid-template-columns: minmax(0, 1fr) 200px; gap: var(--sp-3); align-items: center; padding: var(--sp-3) 0; }
.kr-list .kr-item:first-child { border-top: 0; }
.kr-meta { margin: 0 0 6px; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-muted); }
.kr-meta span::before { content: " \00B7  "; }
.kr-title { margin: 0; font-family: var(--font-head); font-size: var(--fs-lg); font-weight: 600; line-height: 1.25; }
.kr-title--sm { font-size: var(--fs-md); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.kr-title a { color: var(--text); text-decoration: none; transition: color var(--t-fast); }
.kr-title a::after { content: ""; position: absolute; inset: 0; }
.kr-item:hover .kr-title a, .kr-tile:hover .kr-title a { color: var(--accent); }
.kr-item:focus-within, .kr-tile:focus-within { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--r-sm); }
.kr-title a:focus-visible { outline: none; }
.kr-item__lead {
  margin: 6px 0 0; font-size: var(--fs-sm); line-height: 1.5; color: var(--text-muted);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.kr-thumb { aspect-ratio: 16 / 10; border-radius: var(--r-md); overflow: hidden; border: 1px solid var(--line); background: var(--surface); }
.kr-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform var(--t-slow); }
.kr-item:hover .kr-thumb img, .kr-tile:hover .kr-thumb img { transform: scale(1.05); }
.kr-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--sp-4); }
.kr-tile { display: grid; grid-template-columns: minmax(0, 1fr) 112px; gap: var(--sp-2); align-items: start; padding: var(--sp-2) 0; }
.kr-tile .kr-meta { margin: 6px 0 0; }
.kr-thumb--sm { aspect-ratio: 1 / 1; border-radius: var(--r-sm); }
/* A WordPress post with no cover art and no featured image gets no thumbnail
   column rather than an empty box. */
.kr-item--nothumb, .kr-tile--nothumb { grid-template-columns: minmax(0, 1fr) !important; }
@media (max-width: 639px) {
  .kr-item { grid-template-columns: minmax(0, 1fr) 104px; align-items: start; gap: var(--sp-2); }
  .kr-thumb { aspect-ratio: 1 / 1; border-radius: var(--r-sm); }
  .kr-title { font-size: var(--fs-md); }
  .kr-grid { grid-template-columns: 1fr; }
  .kr-tile { grid-template-columns: minmax(0, 1fr) 88px; }
}
@media (prefers-reduced-motion: reduce) {
  .kr-thumb img { transition: none; }
  .kr-item:hover .kr-thumb img, .kr-tile:hover .kr-thumb img { transform: none; }
}

/* Photography placeholder slots: real photos drop in later without layout
   changes; the neutral fill only shows if the slot is empty. */
.photo-slot { background: var(--paper-mist); }

/* Community CTA reveal note */
.card__note {
  position: relative;
  z-index: 1;
  margin-top: var(--sp-1);
  font-size: var(--fs-sm);
  color: var(--accent);
  border-left: 3px solid var(--accent-decor);
  padding-left: var(--sp-2);
}
.card__note[hidden] { display: none; }

/* Article pages (tech news) */
.article-cover {
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
}
.article-cover svg { width: 100%; height: auto; display: block; }

/* ---- 13. Newsletter / community CTA ----------------------------------------------- */
.community {
  background: var(--code-bg);
  color: var(--code-text);
  border-radius: var(--r-lg);
  padding: var(--sp-6) var(--sp-4);
  position: relative;
  overflow: hidden;
}
.community::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(480px 200px at 12% 0%, rgba(0, 224, 198, 0.14), transparent 70%),
    radial-gradient(420px 200px at 88% 100%, rgba(107, 91, 255, 0.16), transparent 70%);
  pointer-events: none;
}
.community__inner { position: relative; max-width: 560px; }
.community h2 { color: var(--code-text); font-size: var(--fs-2xl); }
.community p { color: var(--code-muted); margin-top: var(--sp-2); }
.community .eyebrow { color: var(--code-teal); }
.community .eyebrow::before { background: var(--code-teal); }
.newsletter-form { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-3); }
.newsletter-form input[type="email"] {
  flex: 1 1 240px;
  padding: 13px var(--sp-2);
  border-radius: 10px;
  border: 1px solid var(--code-line);
  background: var(--code-surface);
  color: var(--code-text);
}
.newsletter-form input[type="email"]::placeholder { color: var(--code-muted); }
.newsletter-form input[type="email"]:focus-visible { box-shadow: 0 0 0 2px var(--code-bg), 0 0 0 4px var(--code-teal); }
.community .btn--primary { background: var(--teal-500); color: var(--ink-900); font-weight: 600; }
.community .btn--primary:hover { background: var(--code-teal); }
.newsletter-form__note { width: 100%; font-size: var(--fs-xs); color: var(--code-muted); margin-top: var(--sp-1); }
.newsletter-form__ok {
  width: 100%;
  margin-top: var(--sp-1);
  font-size: var(--fs-sm);
  color: var(--code-teal);
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

/* ---- 14. Footer --------------------------------------------------------------------- */
.site-footer { border-top: 1px solid var(--border); background: var(--surface); }
.site-footer__grid {
  display: grid;
  gap: var(--sp-4);
  padding-block: var(--sp-8) var(--sp-4);
}
/* base.css resets `ul[class], ol[class]` only, and these footer lists carry no
   class, so every footer column had been rendering bullet discs. Nothing else
   on the site shows them; this was an oversight, not a choice. */
.site-footer nav ul { list-style: none; padding: 0; }
.site-footer__brand p {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  max-width: 30ch;
}
/* Community has no real Discord/GitHub/YouTube destinations yet (2026-07-16 audit): 
   de-listed from primary nav and footer "Site" links, and footer social icons hidden,
   until real links exist. Remove these two rules once destinations are live. */
/* Two URL shapes, because the same stylesheet now serves the static build and
   the WordPress one: the static site links to community/index.html, WordPress
   to a pretty permalink ending /community/. Matching only the first is why
   Community reappeared in the WordPress nav. */
.site-header .site-nav__list > li:has(> a[href$="community/index.html"]),
.site-header .site-nav__list > li:has(> a[href$="/community/"]),
.site-footer nav[aria-label="Site"] li:has(> a[href$="community/index.html"]),
.site-footer nav[aria-label="Site"] li:has(> a[href$="/community/"]) {
  display: none;
}
.site-footer__social { display: none; }
.site-footer__social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px; height: 38px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  transition: color var(--t-fast), border-color var(--t-fast);
}
.site-footer__social a:hover { color: var(--accent); border-color: var(--accent-decor); }
.site-footer h4 {
  font-size: var(--fs-sm);
  font-family: var(--font-mono);
  font-weight: 500;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--sp-2);
}
.site-footer ul { display: flex; flex-direction: column; gap: 10px; }
.site-footer ul a { color: var(--text); font-size: var(--fs-sm); }
.site-footer ul a:hover { color: var(--accent); }
.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding-block: var(--sp-3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.site-footer__proto {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--slate-400);
}
@media (min-width: 720px) {
  .site-footer__grid { grid-template-columns: 1.4fr 1fr 1fr 1fr; }
}
/* Five children as of 2026-08-31: brand plus Learn, Tools, Site and About.
   The four-column rule above left the new column wrapped onto a second row
   with three empty cells beside it. Below this width they wrap deliberately:
   four nav columns plus the brand in under 1000px gives ~180px each, which is
   too narrow for "Terms and Conditions" to sit on one line. */
@media (min-width: 1000px) {
  .site-footer__grid { grid-template-columns: 1.3fr repeat(4, 1fr); }
}

/* ---- 15. Learn: sidebar layout, lesson body, FAQ, pager ----------------------------- */
.learn-layout {
  display: grid;
  gap: var(--sp-4);
  padding-block: var(--sp-4) var(--sp-10);
  align-items: start;
}
@media (min-width: 1024px) {
  .learn-layout { grid-template-columns: 264px minmax(0, 1fr); gap: var(--sp-8); }
}

.topics-nav { min-width: 0; }
/* ---- Learn rail, phone layout ---------------------------------------------
   Modelled directly on the W3Schools mobile tutorial rail: a dark full-bleed
   bar under the site header holding a menu button and the tracks as scrollable
   tabs, then a panel listing the selected track's lessons beneath a quiet
   uppercase title. The earlier version put the tabs inside a bordered card
   below the filter box, which read as a form, not a navigation bar. */
.topics-bar {
  display: flex;
  align-items: stretch;
  gap: 0;
  background: var(--code-bg); /* dark in both themes; --ink-900 is the text colour */
  /* Full-bleed comes from left:0/right:0 in the fixed rule below. A negative
     margin-inline used to do it, but combined with those offsets the box
     overshot the viewport on both sides and pushed the last track out of
     reach. */
}

.topics-bar__menu {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  min-height: 48px;
  background: rgba(255, 255, 255, .08);
  border: 0;
  color: #FFFFFF;
  cursor: pointer;
}
.topics-bar__menu:hover { background: rgba(255, 255, 255, .16); }

.topics-tabs {
  display: flex;
  gap: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  min-width: 0;
  flex: 1 1 auto;
}
.topics-tabs::-webkit-scrollbar { display: none; }

.topics-tab {
  flex: 0 0 auto;
  min-height: 48px;
  padding: 0 var(--sp-2);
  background: transparent;
  border: 0;
  border-radius: 0;
  font: inherit;
  font-family: var(--font-head);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: rgba(255, 255, 255, .78);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--t-fast), color var(--t-fast);
}
.topics-tab:hover { background: rgba(255, 255, 255, .1); color: #FFFFFF; }
/* Selected track lifts out of the dark bar onto the panel below it, which is
   what marks the bar and the panel as one object rather than two. */
.topics-tab[aria-selected="true"] {
  background: var(--surface);
  color: var(--accent);
}

/* Fixed, not sticky. Sticky could never work here: the panel beside it is
   position:fixed and therefore out of flow, which collapsed .topics-nav to the
   bar's own 48px: a sticky box can only travel inside its parent, so the bar
   scrolled away the moment the page moved. .topics-nav keeps a 48px min-height
   below so the article still starts under the bar rather than beneath it. */
.topics-bar {
  position: fixed;
  top: 68px;
  left: 0;
  right: 0;
  z-index: 40;
}
.topics-nav { min-height: 48px; }

/* The filter is desktop-only now. On a phone the track bar already puts every
   lesson two taps away, and a search field was the first thing in the panel: 
   occupying the spot the reference gives to the track title. */
.topics-search { display: none; }

/* Panel is a fixed overlay, not a block in the flow. In the flow it scrolled
   away with the article as soon as a track had few enough lessons to fit on
   one screen; fixed, it holds its place until dismissed no matter how long the
   list grows. ~72% of the width, matching the reference, leaving a strip of the
   page visible: tapping that strip closes it. */
.topics-panel {
  display: none;
  position: fixed;
  top: calc(68px + 48px);
  left: 0;
  width: 72%;
  max-width: 340px;
  z-index: 39;
  /* Full height under the bar. A content-height panel was tried and left a
     floating bottom edge mid-screen; running to the bottom reads as a real
     panel instead, and dismissal handles the "it is in the way" problem
     rather than shrinking it. */
  bottom: 0;
  overflow: hidden;          /* the inner list does the scrolling */
  box-shadow: var(--shadow-lift);
}
.topics-panel.is-open { display: block; }

.topics-backdrop {
  position: fixed;
  inset: 0;
  z-index: 38;
  background: rgba(14, 22, 38, .32);
}
.topics-backdrop[hidden] { display: none; }

.topics-nav__inner {
  padding-top: 0;
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  border-right: 1px solid var(--border);
}

/* Only the selected track's lessons are listed on a phone. Set in the template
   for the no-JS case, maintained by main.js after that. */
.topics-group[data-tab-hidden] { display: none; }

@media (min-width: 1024px) {
  .topics-nav { position: sticky; top: calc(68px + var(--sp-3)); max-height: calc(100vh - 120px); overflow-y: auto; }
  /* The bar is the phone control surface; desktop lists every track instead. */
  .topics-bar { display: none; }
  .topics-backdrop { display: none; }
  /* Back into the flow beside the article: the overlay geometry is phone-only. */
  .topics-panel {
    display: block;
    position: static;
    width: auto;
    max-width: none;
    overflow: visible;
    box-shadow: none;
  }
  /* Desktop scrolls the rail itself, so the inner list must not add a second
     scrollbar inside the first, and it drops the phone's panel chrome. */
  .topics-nav { min-height: 0; }
  .topics-search { display: block; }
  .topics-nav__inner {
    height: auto;
    max-height: none;
    overflow: visible;
    background: transparent;
    border: 0;
    border-radius: 0;
  }
  .topics-group[data-tab-hidden] { display: block; }
}

.topics-group { border-bottom: 1px solid var(--border); padding-block: var(--sp-1); }

/* Phone: the track name reads as the panel's quiet title: the "CSS TUTORIAL"
   line in the reference, not as a control, because the tab bar above already
   switches tracks. The chevron goes with it. Desktop restores both. */
@media (max-width: 1023.98px) {
  .topics-group { border-bottom: 0; padding-block: 0; }
  .topics-group__toggle {
    pointer-events: none;
    padding: var(--sp-2) var(--sp-2) var(--sp-1);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: 500;
    letter-spacing: var(--track-wide);
    text-transform: uppercase;
    color: var(--text-muted);
  }
  .topics-group__toggle .icon--xs { display: none; }
  .topics-group__list { padding-bottom: var(--sp-2); }
  .topics-group__list a { padding-block: 11px; }
}
.topics-group__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: var(--sp-1);
  padding: 10px 6px;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--fs-sm);
  color: var(--text);
  border-radius: var(--r-sm);
}
.topics-group__toggle:hover { color: var(--accent); }
.topics-group__toggle .icon--xs { color: var(--slate-400); transition: transform var(--t-fast); }
.topics-group__toggle[aria-expanded="true"] .icon--xs { transform: rotate(180deg); }
.topics-group__list { display: none; padding-bottom: var(--sp-1); }
.topics-group__toggle[aria-expanded="true"] + .topics-group__list { display: block; }
.topics-group__list a {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 7px 6px 7px var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  border-left: 2px solid var(--border);
  margin-left: 6px;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.topics-group__list a:hover { color: var(--accent); border-left-color: var(--accent-decor); text-decoration: none; }
.topics-group__list a[aria-current="page"] {
  color: var(--accent);
  border-left-color: var(--accent);
  font-weight: 500;
  background: linear-gradient(to right, var(--teal-050), transparent);
}
.topics-group__list .soon {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--slate-400);
  margin-left: auto;
  letter-spacing: var(--track-wide);
}

/* Lesson article */
.lesson { min-width: 0; max-width: 760px; }
.lesson::after { content: ""; display: block; clear: both; } /* contain floated figures */
.lesson__updated {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  background: var(--paper-soft);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 5px 14px;
  margin-bottom: var(--sp-3);
}
.lesson__updated .icon--xs { color: var(--accent); }
.lesson h1 { margin-bottom: var(--sp-2); }
.lesson > .lead { margin-bottom: var(--sp-4); }
.lesson h2 {
  font-size: var(--fs-xl);
  margin-top: var(--sp-6);
  margin-bottom: var(--sp-2);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
}
/* A card inside page content keeps its own heading: no section divider and
   no section gap above it (the Contact card showed both, 2026-10-23). */
.lesson .card .card__title { margin-top: 0; padding-top: 0; border-top: 0; }
/* On a phone a long button label (an email address) wraps inside the button
   instead of pushing it past the card's edge. */
@media (max-width: 479px) {
  .card .btn { max-width: 100%; white-space: normal; text-align: center; line-height: 1.3; }
}
.lesson h3 { font-size: var(--fs-lg); margin-top: var(--sp-4); margin-bottom: var(--sp-1); }
.lesson p { margin-bottom: var(--sp-2); }
.lesson ul:not([class]), .lesson ol:not([class]) { margin: 0 0 var(--sp-2) 0; padding-left: var(--sp-3); }
.lesson li { margin-bottom: 6px; }
.lesson .table-wrap { margin-block: var(--sp-3); }

/* Direct answer block (AEO): self-contained answer right under a question H2 */
.answer-box {
  background: var(--teal-050);
  border: 1px solid var(--teal-100);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-3);
}
.answer-box p { margin: 0; font-size: var(--fs-base); color: var(--ink-800); }

/* Callout: related tool / cross-links */
.callout {
  display: flex;
  gap: var(--sp-2);
  align-items: flex-start;
  background: var(--violet-050);
  border: 1px solid var(--violet-100);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  margin-block: var(--sp-4);
}
.callout__icon {
  color: var(--violet-600);
  background: var(--violet-100);
  border-radius: 10px;
  width: 40px; height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.callout h3 { margin: 0 0 4px; font-size: var(--fs-base); }
.callout p { margin: 0; font-size: var(--fs-sm); color: var(--text-muted); }
.callout a { font-weight: 500; }

/* Try-it-yourself interactive block */
.tryit {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  margin-block: var(--sp-4);
  box-shadow: var(--shadow-sm);
}
/* Live tool embeds inside the homepage Tools bento (2026-07-19): the bento
   cell's own artifact spacing already handles the gap, so the widget itself
   shouldn't add its article-body margin on top of it. */
.bento__artifact .tryit { margin-block: 0; }
.tryit__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  background: var(--paper-soft);
  border-bottom: 1px solid var(--border);
}
.tryit__label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 500;
}
.tryit__body { padding: var(--sp-3); }
.tryit__row { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.tryit__row input[type="text"] {
  flex: 1 1 220px;
  min-width: 0; /* an input's intrinsic width (~20 chars) would otherwise overflow narrow rows */
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  padding: 12px var(--sp-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
}
.tryit__row input[type="text"]:focus-visible { box-shadow: var(--focus-ring); border-color: var(--accent); }
.tryit__error { margin-top: var(--sp-1); font-size: var(--fs-sm); color: var(--danger-600); }
.tryit__error[hidden] { display: none; }
.tryit__out { margin-top: var(--sp-3); }
.tryit__out[hidden] { display: none; }
.tryit__hint { margin-top: var(--sp-2); font-size: var(--fs-xs); color: var(--text-muted); }

/* Key-value result table used by tools */
.kv {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}
.kv th {
  text-align: left;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  padding: 7px var(--sp-2) 7px 0;
  border-bottom: 1px dashed var(--border);
  white-space: nowrap;
  width: 42%;
}
.kv td {
  font-family: var(--font-mono);
  color: var(--text);
  padding: 7px 0;
  border-bottom: 1px dashed var(--border);
}
.kv tr:last-child th, .kv tr:last-child td { border-bottom: 0; }
.kv .hl { color: var(--accent); font-weight: 500; }

/* WHOIS/RDAP result cards (DNS Lookup tool) */
.whois-results { display: grid; gap: var(--sp-2); margin-top: var(--sp-3); }
.whois-loading {
  padding: var(--sp-3);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  color: var(--text-muted);
  font-size: var(--fs-sm);
}
.whois-card {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  overflow: hidden;
}
.whois-card > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--paper-soft);
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--fs-base);
  border-bottom: 1px solid transparent;
}
.whois-card > summary::-webkit-details-marker { display: none; }
.whois-card > summary .icon--sm { color: var(--text-muted); flex: none; transition: transform var(--t-fast); }
.whois-card[open] > summary { border-bottom-color: var(--border); }
.whois-card[open] > summary .icon--sm { transform: rotate(180deg); }
.whois-card__body { padding: 2px var(--sp-3) var(--sp-1); }
.whois-row {
  display: grid;
  grid-template-columns: minmax(120px, 200px) 1fr;
  gap: 2px var(--sp-3);
  padding: 11px 0;
  border-bottom: 1px dashed var(--border);
  font-size: var(--fs-sm);
}
.whois-row:last-child { border-bottom: 0; }
.whois-row .k { color: var(--text-muted); }
.whois-row .v { font-family: var(--font-mono); color: var(--text); word-break: break-word; }
.whois-row .v--plain { font-family: var(--font-body); }
.whois-ns { display: flex; flex-direction: column; gap: 3px; }
.whois-ttl { color: var(--text-muted); font-size: var(--fs-xs); }
/* dig-style raw answer section (DNS Lookup, 2026-10-04) */
.dig-answer {
  margin: 0; padding: var(--sp-2) var(--sp-3);
  background: var(--code-bg); color: var(--code-text);
  border-radius: var(--r-sm); overflow-x: auto;
  font-family: var(--font-mono); font-size: var(--fs-sm); line-height: 1.7;
  tab-size: 2; -moz-tab-size: 2; white-space: pre;
}
.dig-answer__note { margin: var(--sp-2) 0 0; font-size: var(--fs-sm); color: var(--text-muted); }

/* pcap reader (2026-10-04) */
.pcap-drop { display: grid; place-items: center; gap: var(--sp-1); padding: var(--sp-6) var(--sp-3); text-align: center; border: 2px dashed var(--line-strong); border-radius: var(--r-md); background: var(--surface-alt); cursor: pointer; transition: border-color var(--t-fast), background-color var(--t-fast); }
.pcap-drop:hover, .pcap-drop.is-over { border-color: var(--accent); background: var(--accent-soft); }
.pcap-drop:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.pcap-drop .icon { width: 34px; height: 34px; color: var(--accent); }
.pcap-drop p { margin: 0; }
.pcap-drop__sub { font-size: var(--fs-sm); color: var(--text-muted); }
.pcap-sample { font: inherit; font-size: var(--fs-sm); color: var(--accent); text-decoration: underline; cursor: pointer; }
.pcap-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px var(--sp-3); margin-bottom: var(--sp-2); font-size: var(--fs-sm); color: var(--text-muted); }
.pcap-meta .btn { margin-left: auto; }
.pcap-scroll { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--r-sm); }
.pcap-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); white-space: nowrap; }
.pcap-table th { position: sticky; top: 0; background: var(--surface-alt); text-align: left; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: var(--track-wide); color: var(--text-muted); padding: 8px 10px; }
.pcap-table td { padding: 6px 10px; border-top: 1px solid var(--border); font-family: var(--font-mono); }
.pcap-table tbody tr { cursor: pointer; }
.pcap-table tbody tr:hover { background: var(--paper-mist); }
.pcap-table tbody tr.is-sel { background: var(--accent-soft); }
.pcap-table tbody tr:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pcap-info { white-space: nowrap; max-width: 36ch; overflow: hidden; text-overflow: ellipsis; }
.pcap-proto { display: inline-block; padding: 1px 7px; border-radius: var(--r-pill); font-size: var(--fs-xs); font-weight: 600; background: var(--paper-mist); color: var(--text); }
.pcap-proto--tcp, .pcap-proto--https, .pcap-proto--http, .pcap-proto--ssh { background: var(--teal-100); color: var(--teal-700); }
.pcap-proto--udp, .pcap-proto--dns, .pcap-proto--dhcp, .pcap-proto--ntp { background: var(--violet-100); color: var(--violet-600); }
.pcap-proto--icmp, .pcap-proto--icmpv6, .pcap-proto--arp { background: var(--warning-100); color: var(--warning-600); }
.pcap-detail { margin-top: var(--sp-3); padding: var(--sp-3); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-alt); }
.pcap-detail[hidden] { display: none; }
.pcap-detail h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-md); }
.pcap-layers { list-style: none; margin: 0 0 var(--sp-2); padding: 0; display: grid; gap: 4px; }
.pcap-layers li { display: grid; grid-template-columns: 110px minmax(0, 1fr); gap: 10px; padding: 6px 8px; border-radius: var(--r-sm); background: var(--surface); font-size: var(--fs-sm); }
.pcap-layers__k { font-weight: 600; color: var(--accent); }
.pcap-layers li > span:last-child { font-family: var(--font-mono); overflow-wrap: anywhere; }
.pcap-hex { margin: 0; padding: var(--sp-2); border-radius: var(--r-sm); background: var(--code-bg); color: var(--code-text); font-family: var(--font-mono); font-size: 0.78rem; line-height: 1.5; white-space: pre; overflow-x: auto; }
@media (max-width: 480px) { .pcap-layers li { grid-template-columns: 1fr; gap: 2px; } }
.whois-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.whois-chip {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  background: var(--paper-mist);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 2px 10px;
}
.whois-chip--ok { color: var(--success-600); background: var(--success-100); border-color: transparent; }
@media (max-width: 520px) {
  .whois-row { grid-template-columns: 1fr; gap: 2px; }
  .whois-row .k { font-size: var(--fs-xs); }
}

/* FAQ accordion */
.faq { margin-top: var(--sp-4); }
.faq details {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  margin-bottom: var(--sp-1);
  transition: border-color var(--t-fast);
}
.faq details[open] { border-color: var(--accent-decor); }
.faq summary {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--fs-base);
  cursor: pointer;
  border-radius: var(--r-md);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary .icon--sm { color: var(--text-muted); flex: none; transition: transform var(--t-fast); }
.faq details[open] summary .icon--sm { transform: rotate(180deg); color: var(--accent); }
.faq details > div { padding: 0 var(--sp-3) var(--sp-3); }
.faq details p { margin: 0; font-size: var(--fs-sm); color: var(--text-muted); }

/* Prev / next pager: two W3Schools-style buttons, left + right */
.pager {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-6);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
.pager a {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 12px 28px;
  border-radius: var(--r-md);
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--fs-base);
  border: 1.5px solid var(--ink-900, #0E1626);
  color: var(--text);
  background: transparent;
  transition: background var(--t-fast), color var(--t-fast), transform var(--t-fast);
  /* Tech-news pagers carry full headlines; without this the buttons cannot
     shrink past their longest word and push the page wider than the viewport. */
  min-width: 0;
}
.pager a > * { min-width: 0; }
/* --surface-2 was never defined, so hover fell back to near-white #F5F7FB under
   light text: the button went blank on hover. --surface-alt is the theme's own. */
.pager a:hover { text-decoration: none; background: var(--surface-alt); transform: translateY(-2px); }
/* Tool pages carry three "Another tool" links instead of the usual two, which
   cannot share one row on a phone. Auto-fit columns keep them on one row on
   desktop and stack them full-width once the viewport drops below ~2 columns. */
.pager--tools {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.pager a.pager__next {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);   /* white on light, near-black on the dark theme */
  margin-left: auto;
}
.pager a.pager__next:hover { background: var(--accent-strong, #055c54); border-color: var(--accent-strong, #055c54); }
@media (prefers-reduced-motion: reduce) { .pager a:hover { transform: none; } }
/* Titled pagers (articles; lessons on WordPress) carry a small label and the
   neighbour's title. Without these rules the two ran together inline
   ("PreviousThe CLI toolkit") and the buttons were squeezed side by side. */
.pager a > span:has(> small) { display: flex; flex-direction: column; gap: 2px; }
.pager a small {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: var(--track-wide, 0.08em);
  text-transform: uppercase;
  opacity: 0.75;
}
.pager a strong { font-weight: 600; line-height: 1.3; }
.pager:has(small) a { max-width: 48%; padding: 14px 20px; }
.pager:has(small) a.pager__next { text-align: right; }
@media (max-width: 639px) {
  .pager:has(small) { flex-direction: column; align-items: stretch; gap: var(--sp-1); }
  .pager:has(small) > span:empty { display: none; }
  .pager:has(small) a { max-width: none; justify-content: space-between; padding: 12px 16px; }
  .pager:has(small) a.pager__prev { justify-content: flex-start; }
  .pager:has(small) a.pager__next { margin-left: 0; text-align: left; }
}

/* Topic reader (learn hub): sidebar topics anchor to sections with an
   explanatory paragraph and a diagram floated inside the text. */
.topics-read { min-width: 0; }
.topics-read > .track-head { margin-top: var(--sp-8); }
.topics-read > .track-head:first-child { margin-top: 0; }
.topic-read { padding-block: var(--sp-4); border-bottom: 1px solid var(--border); }
.topic-read:last-of-type { border-bottom: 0; }
.topic-read::after { content: ""; display: block; clear: both; }
.topic-read h3 {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-lg);
  margin-bottom: var(--sp-1);
}
/* Lesson figure: a large, centered illustration in the content flow
   (GeeksforGeeks-style), not a small floated thumbnail. */
.topic-read__fig {
  width: min(640px, 100%);
  margin: var(--sp-4) auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  padding: var(--sp-3);
  box-shadow: var(--shadow-xs);
}
.topic-read__fig svg { width: 100%; height: auto; display: block; }
.topic-read__fig figcaption {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-muted);
  text-align: center;
  padding: var(--sp-1) 6px 2px;
}
.topic-read p { margin-bottom: var(--sp-2); }
.topic-read .link-more { font-size: var(--fs-sm); }

/* Track headers (learn hub): replaces the numbered-eyebrow pattern */
.track-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-1) var(--sp-2);
  border-bottom: 1px solid var(--border);
  padding-bottom: var(--sp-1);
}
.track-head h2 { font-size: var(--fs-xl); }

/* Learning path strip (learn hub) */
.path-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  background: var(--paper-soft);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-4);
}
.path-strip .step { display: inline-flex; align-items: center; gap: 6px; font-weight: 500; color: var(--text); }
.path-strip .step::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: var(--r-pill);
  background: var(--paper);
  border: 2px solid var(--accent-decor);
}
.path-strip .sep { color: var(--slate-400); font-family: var(--font-mono); }

/* Practice options (exercises): four centered ways-in cards */
.practice-grid { display: grid; gap: var(--sp-3); }
@media (min-width: 600px) { .practice-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .practice-grid { grid-template-columns: repeat(4, 1fr); } }
.practice-card {
  align-items: center;
  text-align: center;
  padding: var(--sp-4) var(--sp-3);
}
.practice-card .card__icon {
  width: 52px;
  height: 52px;
  border-radius: 14px;
  margin-inline: auto;
}
.practice-card .card__icon--violet { background: var(--violet-100); color: var(--violet-600); }
.practice-card .card__desc { flex-grow: 1; }
.practice-card .btn {
  margin-top: var(--sp-3);
  width: 100%;
  justify-content: center;
}

/* Mode cards (exercises) */
.mode-card { padding: var(--sp-4); }
.mode-card__head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.mode-card__head .card__icon { margin-bottom: 0; }
.mode-card--challenge .card__icon { background: var(--violet-100); color: var(--violet-600); }
.mode-card h3 { font-size: var(--fs-xl); }
.mode-card .node-list { margin-block: var(--sp-2); font-size: var(--fs-sm); color: var(--text-muted); }
.mode-card .btn { align-self: flex-start; margin-top: auto; }

/* ---- 16. Forms misc ------------------------------------------------------------------ */
input::placeholder { color: var(--slate-400); }

/* utility */
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-6 { margin-top: var(--sp-6); }

/* "Complete the command" exercise (W3Schools-style): dark panel of monospace
   checkbox options + Show Answer / Submit Answer actions. */
.cmd-ex__panel {
  background: var(--code-bg);
  border: 1px solid var(--code-line);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.cmd-ex__opt {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 10px 12px;
  border-radius: var(--r-sm, 6px);
  border: 1px solid transparent;
  cursor: pointer;
}
.cmd-ex__opt:hover { border-color: var(--code-line); }
.cmd-ex__opt input {
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
  flex-shrink: 0;
}
.cmd-ex__opt code {
  background: none;
  border: 0;
  padding: 0;
  color: #E8ECF3;
  font-size: var(--fs-sm);
}
.cmd-ex__opt.is-correct { border-color: var(--ok, #2E7D32); background: rgba(46, 204, 113, 0.12); }
.cmd-ex__opt.is-correct code { color: #7BE3A8; }
.cmd-ex__opt.is-wrong { border-color: var(--danger, #C62828); background: rgba(225, 75, 75, 0.12); }
.cmd-ex__opt.is-wrong code { color: #F1A9A9; }
.cmd-ex__opt:has(input:disabled) { cursor: default; }
.cmd-ex__actions {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
  /* .btn is white-space: nowrap, so at 320px "Show Answer" (126px) + gap +
     "Submit Answer" (148px) could not fit the 238px row and the second button
     ran 11px off the side of the phone. Nothing scrolled, because .cmd-ex
     clipped it, which is why a scrollWidth check never caught this and only
     the clipping test did. It surfaced at random because the lesson quiz deals
     a "command" question only some of the time. */
  flex-wrap: wrap;
}

/* Hero interactive terminal: reuses the .term / .c-* system; adds a fixed
   scrollable body, an input row and a blinking caret. Dark card on the light
   hero (same convention as the exercises terminal). */
.hero-term { width: 100%; }
.hero-term__body {
  height: 300px;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-word;
  /* The bar is hidden, not the scrolling. A real terminal does not show a
     browser scrollbar down its side, and this pane auto-scrolls to the newest
     line anyway: the bar was only ever visual noise over the output. Wheel,
     touch and keyboard scrolling all still work. */
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;       /* old Edge */
}
.hero-term__body::-webkit-scrollbar { width: 0; height: 0; }
.term__body .c-err { color: #F98A8A; }
.term__body .c-hi { color: var(--code-text); }
.hero-term__inputrow {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 10px var(--sp-2);
  border-top: 1px solid var(--code-line);
}
.hero-term__input {
  flex: 1; min-width: 0;
  background: none; border: 0; outline: none; box-shadow: none;
  font-family: var(--font-mono); font-size: var(--fs-sm);
  color: var(--code-text); caret-color: var(--code-teal);
}
.hero-term__input::placeholder { color: var(--code-muted); }
.hero-term__caret {
  display: inline-block; width: 8px; height: 1em; vertical-align: -2px;
  background: var(--code-teal); animation: heroCaret 1.05s steps(1) infinite;
}
@keyframes heroCaret { 50% { opacity: 0; } }
@media (max-width: 640px) { .hero-term__body { height: 230px; } }
@media (prefers-reduced-motion: reduce) { .hero-term__caret { animation: none; } }

/* ============================================================================
   Tech News: editorial layout (Memence-inspired): feature story, two-column
   feed + sticky sidebar, "more stories" row, dark keep-learning band.
   ============================================================================ */
.news-feature {
  display: grid;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  margin-top: var(--sp-4);
}
@media (min-width: 900px) {
  .news-feature { grid-template-columns: 7fr 5fr; }
  .news-feature__cover { height: 100%; }
  .news-feature__cover svg { height: 100%; }
}
.news-feature__cover { line-height: 0; }
.news-feature__cover svg { width: 100%; display: block; }
.news-feature__body { padding: clamp(var(--sp-3), 3vw, var(--sp-5)); align-self: center; }
.news-feature__title { font-size: var(--fs-2xl); line-height: 1.15; margin: var(--sp-1) 0 var(--sp-2); }
.news-feature__excerpt { color: var(--text-muted); margin-bottom: var(--sp-2); }

.news-layout { display: grid; gap: var(--sp-6); margin-top: var(--sp-6); align-items: start; }
@media (min-width: 1024px) {
  .news-layout { grid-template-columns: minmax(0, 1fr) 320px; }
  .news-side { position: sticky; top: 96px; }
}
.news-feed { display: flex; flex-direction: column; min-width: 0; }
.news-row { display: grid; gap: var(--sp-2); padding-block: var(--sp-4); border-top: 1px solid var(--border); }
.news-row:first-child { border-top: 0; padding-top: 0; }
@media (min-width: 640px) {
  .news-row { grid-template-columns: 220px minmax(0, 1fr); gap: var(--sp-3); align-items: start; }
}
.news-row__cover { display: block; border-radius: var(--r-md); overflow: hidden; border: 1px solid var(--border); line-height: 0; }
.news-row__cover svg { width: 100%; height: auto; display: block; }
.news-row__body h3 { font-size: var(--fs-lg); margin: 4px 0 var(--sp-1); }

.news-side__title { font-size: var(--fs-lg); margin-bottom: var(--sp-2); }
.picks { list-style: none; margin: 0 0 var(--sp-5); padding: 0; }
.pick { display: block; padding: var(--sp-2) 0; }
.picks li + li .pick { border-top: 1px solid var(--border); }
.pick__date {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-bottom: 4px;
}
.pick__t { font-weight: 500; color: var(--text); }
.pick:hover { text-decoration: none; }
.pick:hover .pick__t, .pick:focus-visible .pick__t { color: var(--accent); }
.news-side__note {
  background: var(--paper-soft);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
}
.news-side__note p { font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: var(--sp-2); }

.news-more-head { margin: var(--sp-8) 0 var(--sp-4); }
.news-more-head h2 { font-size: var(--fs-xl); }

.news-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  background: var(--code-bg);
  border: 1px solid var(--code-line);
  border-radius: var(--r-lg);
  padding: clamp(var(--sp-3), 4vw, var(--sp-6));
  margin-top: var(--sp-8);
}
.news-cta h2 { color: var(--code-text); font-size: var(--fs-xl); max-width: 26ch; }
.news-cta p { color: var(--code-muted); margin-top: 6px; }

/* ============================================================================
   Homepage wayfinding rail (audit 3i): fixed dot nav on long pages, desktop
   only. Dots mark each section; the label appears on hover/focus; the active
   section's dot is filled teal (synced by main.js).
   ============================================================================ */
.page-rail { position: fixed; right: 22px; top: 50%; transform: translateY(-50%); z-index: 40; display: none; }
@media (min-width: 1200px) { .page-rail { display: block; } }
.page-rail ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.page-rail a { position: relative; display: flex; align-items: center; justify-content: flex-end; padding: 2px; }
.page-rail a::after {
  content: "";
  width: 9px; height: 9px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--text-muted);
  background: transparent;
  transition: background var(--t-fast, 0.15s), border-color var(--t-fast, 0.15s), transform var(--t-fast, 0.15s);
}
.page-rail a:hover::after, .page-rail a:focus-visible::after { border-color: var(--accent); }
.page-rail a.is-on::after { background: var(--accent); border-color: var(--accent); transform: scale(1.2); }
.page-rail .lbl {
  position: absolute;
  right: 22px;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 3px 10px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-fast, 0.15s);
}
.page-rail a:hover .lbl, .page-rail a:focus-visible .lbl { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .page-rail a::after, .page-rail .lbl { transition: none; }
}

/* ============================================================================
   Lesson search (topics sidebar): injected by main.js on every lesson page.
   ============================================================================ */
.topics-search {
  position: relative;
  margin-bottom: var(--sp-2);
}
.topics-search .icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  pointer-events: none;
}
.topics-search input {
  width: 100%;
  min-height: 44px;
  padding: 10px 12px 10px 40px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-sm);
  font-family: var(--font-body, inherit);
}
.topics-search input::placeholder { color: var(--text-muted); }
.topics-search input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--focus-ring, 0 0 0 3px rgba(7, 112, 102, 0.25));
}
.topics-search__empty {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  padding: var(--sp-1) 2px var(--sp-2);
}
.topics-search__empty b { color: var(--text); word-break: break-word; }

/* ============================================================================
   Career ladder (career/index.html): vertical role progression.
   ============================================================================ */
.ladder { list-style: none; margin: 0; padding: 0; max-width: 720px; }
.ladder-step {
  position: relative;
  padding: 0 0 var(--sp-5) 56px;
}
.ladder-step::before {                 /* connecting rail */
  content: "";
  position: absolute;
  left: 19px;
  top: 40px;
  bottom: 4px;
  width: 2px;
  background: linear-gradient(180deg, var(--accent-decor, #08B3A0), var(--border));
}
.ladder-step:last-child { padding-bottom: 0; }
.ladder-step:last-child::before { display: none; }
.ladder-step__num {
  position: absolute;
  left: 0;
  top: 0;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  border: 2px solid var(--accent-decor, #08B3A0);
  background: var(--surface);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: 500;
}
.ladder-step h2 { font-size: var(--fs-xl); margin-bottom: 4px; }
.ladder-step__role {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--sp-1);
}
.ladder-step p { color: var(--text-muted); margin-bottom: var(--sp-1); }
.ladder-step .link-more { font-size: var(--fs-sm); }

/* ============================================================================
   Build log (roadmap/index.html): dated entries, newest first.
   ============================================================================ */
.log { list-style: none; margin: 0; padding: 0; max-width: 720px; }
.log-entry {
  display: grid;
  grid-template-columns: 118px minmax(0, 1fr);
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-top: 1px solid var(--border);
}
.log-entry:first-child { border-top: 0; padding-top: 0; }
.log-entry time {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  padding-top: 3px;
  white-space: nowrap;
}
.log-entry h2 { font-size: var(--fs-base); font-weight: 600; margin-bottom: 4px; }
.log-entry p { font-size: var(--fs-sm); color: var(--text-muted); }
@media (max-width: 560px) {
  .log-entry { grid-template-columns: 1fr; gap: 4px; }
}

/* ============================================================================
   Career ladder: layout/interactivity pass (added v21)
   Per-step icon (.card__icon) in a header row, a "day in the life" node-list,
   a chip row of tools/skills (.tag), a stage-fit mini-quiz (.career-quiz reuses
   .quiz-sample), and a per-stage mastery readout for returning visitors
   (reuses .mastery-item, filled by main.js from ltn_mastery_summary).
   The numbered pill + .ladder-step::before already form the connecting rail.
   ============================================================================ */
.ladder-step__head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.ladder-step__head .card__icon { margin-bottom: 0; flex: none; width: 40px; height: 40px; }
.ladder-step__head > div { min-width: 0; }
.ladder-step__head h2 { margin-bottom: 0; }
.ladder-step__head .ladder-step__role { margin-bottom: 2px; }
.ladder-step__label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  margin: var(--sp-2) 0 6px;
}
.ladder-step__day { margin: 0; }
.ladder-step__day li { color: var(--text-muted); font-size: var(--fs-sm); }
.ladder-step__tags {
  list-style: none;
  padding: 0;
  margin: var(--sp-2) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.ladder-step__foot { margin-top: var(--sp-2); }
.ladder-step__foot .link-more { font-size: var(--fs-sm); }
.ladder-step__progress { margin-top: var(--sp-2); max-width: 340px; }
.ladder-step__progress .mastery-item {
  background: var(--paper-soft);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 10px var(--sp-2);
}
.ladder-step__badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: var(--r-pill);
  padding: 3px 10px;
  white-space: nowrap;
}
.ladder-step.is-recommended .ladder-step__num {
  background: var(--accent);
  color: var(--paper);
  border-color: var(--accent);
}
.ladder-step.is-recommended {
  border-radius: var(--r-md);
  outline: 2px solid var(--accent-decor, #08B3A0);
  outline-offset: 8px;
}

/* Stage-fit mini-quiz */
.career-quiz { max-width: 720px; margin-bottom: var(--sp-2); }
.career-quiz__progress {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-bottom: var(--sp-1);
}
.career-quiz__result h3 { font-size: var(--fs-md); color: var(--text); margin-bottom: 4px; }
.career-quiz__result p { margin-bottom: 0; }
.career-quiz__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-2);
  margin-top: var(--sp-2);
}
.career-quiz__reset {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.career-quiz__reset:hover { color: var(--accent); }

/* ============================================================================
   Roadmap changelog: timeline + Shipped / Planned columns (added v21)
   Timeline mirrors the proven .ladder-step rail (absolute node-dot + ::before
   line + left-padded body); the date sits on the rail beside each dot. Type
   chips reuse .tag--teal / --violet / --outline and carry data-type hooks for
   a future filter bar once the log is long enough to need one.
   ============================================================================ */
.changelog {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--sp-6);
  align-items: start;
}
.changelog__title { font-size: var(--fs-lg); margin-bottom: var(--sp-3); }
.changelog__shipped { min-width: 0; }
.changelog__planned { position: sticky; top: 92px; }
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline__item { position: relative; padding: 0 0 var(--sp-4) var(--sp-5); }
.timeline__item::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 20px;
  bottom: -4px;
  width: 2px;
  background: linear-gradient(180deg, var(--accent-decor, #08B3A0), var(--border));
}
.timeline__item:last-child { padding-bottom: 0; }
.timeline__item:last-child::before { display: none; }
.timeline__dot {
  position: absolute;
  left: 0;
  top: 4px;
  width: 14px;
  height: 14px;
  border-radius: var(--r-pill);
  background: var(--paper);
  border: 2px solid var(--accent-decor, #08B3A0);
}
.timeline__date {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text-muted);
  margin-bottom: 6px;
}
.timeline__tags {
  list-style: none;
  padding: 0;
  margin: 0 0 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.timeline__item h3 { font-size: var(--fs-base); font-weight: 600; margin-bottom: 4px; }
.timeline__item p { font-size: var(--fs-sm); color: var(--text-muted); }
.planned-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
}
.planned-card__note { font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: var(--sp-2); }
.planned-card .node-list { margin: 0; }
.planned-card .node-list li { font-size: var(--fs-sm); color: var(--text); }
@media (max-width: 860px) {
  .changelog { grid-template-columns: 1fr; gap: var(--sp-5); }
  .changelog__planned { position: static; }
}

/* ============================================================================
   Ported tools (tools-extra.js): class strip, base converter, CLI generator,
   packet hex view, hop visualizer. All token-driven; no new palette values
   beyond the field hues in the hex view (chosen for 3:1+ on --code-bg).
   ============================================================================ */
.tool-select {
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-sm);
}
.tool-select:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring, 0 0 0 3px rgba(7,112,102,0.25)); }
.tool-select--grow { flex: 1; }

/* IP class strip */
.classbar { display: flex; gap: 6px; margin: var(--sp-2) 0; }
.classbar__seg {
  flex: 1;
  text-align: center;
  padding: 8px 4px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--paper-soft);
  transition: border-color 0.15s, background 0.15s;
}
.classbar__seg b { display: block; font-family: var(--font-head); font-size: var(--fs-base); }
.classbar__seg i { font-style: normal; font-family: var(--font-mono); font-size: 0.6875rem; color: var(--text-muted); }
.classbar__seg.is-match { border-color: var(--accent); background: var(--teal-050, #E6F7F4); }
.classbar__seg.is-match b { color: var(--accent); }

/* base converter rows */
.conv-row { display: grid; grid-template-columns: 120px minmax(0, 1fr); gap: var(--sp-2); align-items: center; margin-bottom: var(--sp-1); }
.conv-row span { font-size: var(--fs-sm); font-weight: 500; }
.conv-row input {
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
}
.conv-row input:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring, 0 0 0 3px rgba(7,112,102,0.25)); }
.conv-row input.is-invalid { border-color: var(--danger, #C62828); }
@media (max-width: 480px) { .conv-row { grid-template-columns: 1fr; gap: 4px; } }

/* Hex/Dec/Bin: bit-width control and the padded view it drives (2026-10-04). */
.conv-width { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: var(--sp-3); }
.conv-width__lbl { font-size: var(--fs-sm); font-weight: 500; margin-right: 4px; }
.conv-width__btn {
  min-height: 36px; padding: 6px 12px;
  border: 1px solid var(--border); border-radius: var(--r-pill);
  background: var(--surface); color: var(--text-muted);
  font-family: var(--font-mono); font-size: var(--fs-xs); cursor: pointer;
  transition: border-color var(--t-fast), color var(--t-fast), background-color var(--t-fast);
}
.conv-width__btn:hover { border-color: var(--accent); color: var(--text); }
.conv-width__btn.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.conv-width__btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.conv-view { margin-top: var(--sp-3); padding: var(--sp-3); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-alt); }
.conv-view__grid { display: grid; gap: var(--sp-2); grid-template-columns: 1fr; }
@media (min-width: 560px) { .conv-view__grid { grid-template-columns: 1fr auto; align-items: baseline; } .conv-view__grid > div:last-child { grid-column: 1 / -1; } }
.conv-view__k { display: block; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: var(--track-wide); color: var(--text-muted); margin-bottom: 2px; }
.conv-view__v { font-family: var(--font-mono); font-size: var(--fs-md); color: var(--text); overflow-wrap: anywhere; }
.conv-view__note { margin: var(--sp-2) 0 0; font-size: var(--fs-sm); color: var(--warning-600); }
.conv-bytes { width: 100%; margin-top: var(--sp-3); border-collapse: collapse; table-layout: fixed; }
.conv-bytes th { font-size: var(--fs-xs); font-weight: 500; text-transform: uppercase; letter-spacing: var(--track-wide); color: var(--text-muted); padding: 0 6px 4px; text-align: center; }
.conv-bytes td { border: 1px solid var(--border); border-radius: var(--r-sm); padding: 8px 4px; text-align: center; }
.conv-bytes code { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--text); white-space: nowrap; }
.conv-bytes small { display: block; margin-top: 3px; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent); }
@media (max-width: 559px) { .conv-bytes, .conv-bytes thead, .conv-bytes tbody, .conv-bytes tr { display: block; } .conv-bytes th { display: none; } .conv-bytes td { margin-bottom: 4px; } }

/* CLI generator params */
.gen-params { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--sp-2); margin-top: var(--sp-2); }
.gen-field span { display: block; font-size: var(--fs-xs); font-weight: 500; color: var(--text-muted); margin-bottom: 4px; }
.gen-field input, .gen-field select {
  width: 100%;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
}
.gen-field input:focus-visible, .gen-field select:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring, 0 0 0 3px rgba(7,112,102,0.25)); }

/* packet hex view: dark panel like .term */
.hexgrid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 4px;
  padding: var(--sp-2);
  margin-top: var(--sp-2);
  background: var(--code-bg);
  border: 1px solid var(--code-line);
  border-radius: var(--r-md);
}
.hexbyte {
  min-height: 34px;
  border: 1px solid transparent;
  border-radius: 6px;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: #93A0B4;
  background: rgba(255, 255, 255, 0.04);
  cursor: pointer;
}
.hexbyte.f-ver, .hexbyte.f-len { color: #E8ECF3; }
.hexbyte.f-ttl { background: rgba(224, 146, 10, 0.22); color: #FFC978; }
.hexbyte.f-proto { background: rgba(0, 224, 198, 0.18); color: #7ADCCE; }
.hexbyte.f-src { background: rgba(0, 224, 198, 0.26); color: #00E0C6; }
.hexbyte.f-dst { background: rgba(107, 91, 255, 0.3); color: #B7ADFF; }
.hexbyte.f-sport { background: rgba(122, 220, 206, 0.16); color: #7ADCCE; }
.hexbyte.f-dport { background: rgba(156, 143, 255, 0.2); color: #9C8FFF; }
.hexbyte.f-seq { background: rgba(232, 236, 243, 0.12); color: #E8ECF3; }
.hexbyte.is-hot { border-color: #00E0C6; }
.hexbyte:focus-visible { outline: 2px solid #00E0C6; outline-offset: 1px; }
.hexlegend { display: flex; flex-wrap: wrap; gap: 8px; margin-top: var(--sp-2); }
.hexleg {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  font-size: var(--fs-xs);
  font-family: var(--font-mono);
  color: var(--text);
  cursor: pointer;
}
.hexleg:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring, 0 0 0 3px rgba(7,112,102,0.25)); }
.hexleg i { width: 10px; height: 10px; border-radius: 3px; }
.hexleg.f-ttl i { background: #E0920A; } .hexleg.f-proto i { background: #0AA88F; }
.hexleg.f-src i { background: #077066; } .hexleg.f-dst i { background: #5646E0; }
.hexleg.f-sport i { background: #7ADCCE; } .hexleg.f-dport i { background: #9C8FFF; }
.hexleg.f-seq i { background: #525E70; }
@media (max-width: 480px) { .hexgrid { grid-template-columns: repeat(4, 1fr); } }

/* hop visualizer map */
.hopmap { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: var(--sp-3); }
.hopstep {
  min-width: 96px;
  text-align: center;
  padding: 10px 8px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--paper-soft);
  transition: border-color 0.2s, background 0.2s;
}
.hopstep__n {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--border);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-bottom: 4px;
}
.hopstep__ms { display: block; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text); }
.hopstep__ip { display: block; font-family: var(--font-mono); font-size: 0.6875rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; max-width: 120px; margin-inline: auto; }
.hopstep.is-live { border-color: var(--accent); background: var(--teal-050, #E6F7F4); }
.hopstep.is-live .hopstep__n { border-color: var(--accent); color: var(--accent); }
.hopstep.is-timeout { border-color: var(--danger, #C62828); background: #FDF2F2; }
.hopstep.is-timeout .hopstep__n { border-color: var(--danger, #C62828); color: var(--danger, #C62828); }
.hopmap__link { flex: 1 0 12px; height: 2px; min-width: 12px; background: var(--border); border-radius: 2px; }
.hopmap__link.is-live { background: var(--accent-decor, #08B3A0); }
@media (prefers-reduced-motion: reduce) {
  .classbar__seg, .hopstep { transition: none; }
}

/* ============================================================================
   Tools mega-menu: landscape layout
   Superseded 2026-08-31. The old rule selected the Tools panel with
   `:has(.nav-drop__item[href$="whats-my-ip.html"])`: "the panel that happens
   to contain What's My IP", because an earlier attempt keyed off the footer
   link and silently failed on all 16 tools/ pages, where that href is a bare
   "index.html". A tool rename would have unstyled the entire menu with no
   error. The panel now carries .nav-drop__panel--tools and is styled with the
   other mega-menu rules further up this file; nothing needs to guess.
   ============================================================================ */

/* ============================================================================
   Probe tools (ping / traceroute / MTR / HTTP test): added v22
   Browsers have no raw sockets and cannot send ICMP, so these run on the
   Globalping probe network and render the REAL output returned. Layout reuses
   .tryit / .term / .kv; only the few probe-specific bits live here.
   ============================================================================ */
.probe-row { align-items: center; }
.probe-row input[type="text"] { flex: 1 1 200px; min-width: 0; }
.probe-row .tool-select { flex: 0 1 auto; }
.probe-wait {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.probe-wait::before {
  content: "";
  width: 12px; height: 12px;
  flex: none;
  border-radius: var(--r-pill);
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: probe-spin 0.7s linear infinite;
}
@keyframes probe-spin { to { transform: rotate(360deg); } }
.probe-from { font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: var(--sp-2); }
.probe-from strong { color: var(--text); font-weight: 600; }
.probe-quota { margin-top: var(--sp-2); font-size: var(--fs-xs); color: var(--text-muted); }
.tryit__out .kv { margin-bottom: var(--sp-3); }
/* raw probe output keeps terminal formatting but must not force page scroll */
.term__body pre { margin: 0; font: inherit; color: inherit; white-space: pre; }
@media (prefers-reduced-motion: reduce) {
  .probe-wait::before { animation: none; }
}

/* ============================================================================
   Shared lookup error card + button cooldown: added v25
   One error pattern for every API-dependent tool (DNS, ASN, MAC, probes), so
   a failure looks the same everywhere. Uses existing tokens only; no new
   colours or fonts. Replaces the old "sample data" fallbacks, which invented
   plausible-looking results when a lookup failed.
   ============================================================================ */
.lookup-error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-left: 3px solid var(--danger-600, #C62828);
  border-radius: var(--r-sm);
  /* surface-alt, not paper-soft: on the dark theme paper-soft IS the card
     surface, so the card would blend into whatever it sits inside. */
  background: var(--surface-alt);
}
.lookup-error + .lookup-error,
.whois-card + .lookup-error { margin-top: var(--sp-2); }
.lookup-error__icon { flex: none; color: var(--danger-600, #C62828); line-height: 0; padding-top: 2px; }
.lookup-error strong {
  display: block;
  font-family: var(--font-head);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
}
.lookup-error p { margin: 2px 0 0; font-size: var(--fs-sm); color: var(--text-muted); }

/* brief post-request cooldown on submit buttons */
.btn.is-cooling,
button.is-cooling { opacity: 0.6; cursor: default; }

/* ============================================================================
   Theme toggle: added v28
   Sits in the header between the CTA and the mobile menu button. Injected by
   main.js, so it exists on every page without touching 68 duplicated headers.
   ============================================================================ */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;              /* full 44px touch target */
  flex: none;
  margin-left: var(--sp-1);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--t-fast), border-color var(--t-fast), background-color var(--t-fast);
}
.theme-toggle:hover {
  color: var(--accent);
  border-color: var(--accent-decor);
  background: var(--accent-soft);
}
.theme-toggle:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.theme-toggle .icon { transition: transform var(--t); }
.theme-toggle:hover .icon { transform: rotate(12deg); }

/* The whole page cross-fades rather than snapping between themes. Scoped to
   colour properties only so layout and transforms are untouched. */
:root.theme-animating,
:root.theme-animating body,
:root.theme-animating .site-header,
:root.theme-animating .card,
:root.theme-animating .bento__cell {
  transition: background-color 0.25s var(--ease), border-color 0.25s var(--ease), color 0.25s var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .theme-toggle .icon { transition: none; }
  .theme-toggle:hover .icon { transform: none; }
  :root.theme-animating,
  :root.theme-animating body,
  :root.theme-animating .site-header,
  :root.theme-animating .card,
  :root.theme-animating .bento__cell { transition: none; }
}

/* Mobile: keep the toggle beside the hamburger, never inside the drawer. */
@media (max-width: 899px) {
  .theme-toggle { margin-left: auto; margin-right: 4px; }
}

/* ============================================================================
   WHOIS result rows in a NARROW container (homepage embed): fix v31
   Two bugs, both only visible where the results sit in a narrow column:
   1. `.whois-row .v` sets word-break:break-word, which cascaded into the
      status chips and split words mid-letter ("prohibit / ed"), turning each
      pill into a tall oval. Chips now never break internally; the flex row
      wraps them instead.
   2. The stacking rule was a VIEWPORT media query (max-width:520px), so the
      homepage embed (narrow at full desktop width) never got it. A
      container query fixes it based on the actual column width.
   ============================================================================ */
.whois-chip { white-space: nowrap; }
.whois-results { container-type: inline-size; }
@container (max-width: 430px) {
  .whois-row { grid-template-columns: 1fr; gap: 2px; }
  .whois-row .k { font-size: var(--fs-xs); }
}
/* last resort: an unbreakable value can still scroll inside its own cell
   rather than pushing the panel wider than the page */
.whois-row .v { min-width: 0; overflow-wrap: anywhere; }
.whois-chips { max-width: 100%; }

/* Tool search boxes: if a hint ever outgrows a narrow input, fade it out
   rather than hard-cutting mid-character (v32). */
.tryit__row input[type="text"] { text-overflow: ellipsis; }

/* What's My IP: live readings bar (v32).
   The tool used to render once at mount with no control at all, so it read as
   a dead panel. It now has a refresh action and a "live" dot. */
.wmi-bar { align-items: center; gap: var(--sp-1); flex-wrap: wrap; margin-bottom: var(--sp-2); }
.wmi-note { flex: 1 1 160px; min-width: 0; font-size: var(--fs-xs); color: var(--text-muted); margin: 0; }
.wmi-live {
  width: 8px; height: 8px; flex: none;
  border-radius: var(--r-pill);
  background: var(--accent-decor);
}
.wmi-live.is-pulse { animation: wmi-pulse 0.6s var(--ease); }
@keyframes wmi-pulse {
  0% { transform: scale(1); opacity: 1; }
  50% { transform: scale(2.1); opacity: 0.45; }
  100% { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .wmi-live.is-pulse { animation: none; }
}
/* keep the refresh control on the 44px touch-target floor */
.wmi-bar .btn { min-height: 44px; }

/* ============================================================================
   Labs: RJ-45 wiring game (added v35)
   Reuses .tryit / .btn / .tool-select / .tag / .lookup-error. Only the jack,
   strands and swatches are new. Wire colours come from inline --wire custom
   properties set by the JS data table: they are CONTENT colours (real cable
   colours), never theme tokens.
   ============================================================================ */
.rj-controls { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.rj-ctl { display: flex; flex-direction: column; gap: 4px; }
.rj-ctl__label { font-size: var(--fs-xs); color: var(--text-muted); }
.rj-ctl__note { font-size: var(--fs-xs); color: var(--text-muted); margin: 0 0 6px; flex: 1 1 160px; }
.rj-controls .btn { margin-left: auto; min-height: 44px; }
.rj-howto { font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: var(--sp-3); }

.rj-end { margin-bottom: var(--sp-4); }
.rj-end__head { display: flex; align-items: center; gap: var(--sp-1); margin-bottom: var(--sp-1); }
.rj-end__head h3 { font-size: var(--fs-base); margin: 0; }

/* the jack: eight stacked pins, so long strand names stay readable at 375px */
.rj-jack { list-style: none; margin: 0 0 var(--sp-2); padding: 0; display: grid; gap: 4px; }
.rj-pin { display: grid; grid-template-columns: 28px minmax(0, 1fr); align-items: center; gap: var(--sp-1); }
.rj-pin__n { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-muted); text-align: right; }
.rj-slot {
  display: flex; align-items: center; gap: var(--sp-1);
  width: 100%; min-height: 44px;
  padding: 4px 10px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface-alt);
  text-align: left;
  transition: border-color var(--t-fast), background-color var(--t-fast);
}
.rj-slot:hover, .rj-slot.is-target { border-color: var(--accent-decor); }
.rj-slot.is-over { border-style: solid; border-color: var(--accent); background: var(--accent-soft); }
.rj-slot:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.rj-slot__empty { font-size: var(--fs-xs); color: var(--slate-400); }
.rj-pin.is-right .rj-slot { border-style: solid; border-color: var(--success-600); background: var(--success-100); }
.rj-pin.is-wrong .rj-slot { border-style: solid; border-color: var(--danger-600); background: var(--danger-100); }
/* shape, not just colour, so the verdict survives colour-blindness */
.rj-pin.is-right .rj-slot::after,
.rj-pin.is-wrong .rj-slot::after {
  margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: 500;
}
.rj-pin.is-right .rj-slot::after { content: "OK"; color: var(--success-600); }
.rj-pin.is-wrong .rj-slot::after { content: "X"; color: var(--danger-600); }

.rj-strand { display: inline-flex; align-items: center; gap: var(--sp-1); min-width: 0; }
.rj-strand__name { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text); white-space: nowrap; }
.rj-strand--draggable {
  min-height: 44px; width: 100%;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  cursor: grab;
  touch-action: none;          /* let the pointer handler own the gesture */
  transition: border-color var(--t-fast), transform var(--t-fast);
}
.rj-strand--draggable:hover { border-color: var(--accent-decor); }
.rj-strand--draggable:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.rj-strand--draggable.is-picked { border-color: var(--accent); background: var(--accent-soft); }
.rj-slot.is-picked { border-style: solid; border-color: var(--accent); }

/* colour swatches: solid, or white with a stripe of the pair colour */
.rj-sw {
  width: 26px; height: 14px; flex: none;
  border-radius: 3px;
  border: 1px solid rgba(14, 22, 38, 0.25);
  background: var(--wire);
}
.rj-sw--striped {
  background: repeating-linear-gradient(115deg, #FFFFFF 0 5px, var(--wire) 5px 9px);
}

.rj-tray__label { font-size: var(--fs-xs); color: var(--text-muted); margin: 0 0 6px; }
.rj-tray { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 6px; }
.rj-tray__done { font-size: var(--fs-sm); color: var(--text-muted); }

.rj-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-top: var(--sp-2); }
.rj-attempts { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-muted); margin-left: auto; }

.rj-answer {
  border: 1px solid var(--border); border-left: 3px solid var(--accent-decor);
  border-radius: var(--r-sm); background: var(--surface-alt);
  padding: var(--sp-2); margin-top: var(--sp-2);
}
.rj-answer__t { font-weight: 600; font-size: var(--fs-sm); margin: 0 0 6px; }
.rj-answer__row { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-muted); margin: 0 0 4px; word-break: break-word; }

.rj-win {
  margin-top: var(--sp-2); padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--success-600); border-radius: var(--r-sm);
  background: var(--success-100); font-size: var(--fs-sm); color: var(--text);
}
.rj-win strong { color: var(--success-600); }

/* the strand that follows the pointer during a drag */
.rj-ghost {
  position: fixed; top: 0; left: 0; z-index: 999;
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 6px 10px;
  border: 1px solid var(--accent); border-radius: var(--r-sm);
  background: var(--surface); box-shadow: var(--shadow-lift);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .rj-slot, .rj-strand--draggable { transition: none; }
}
@media (max-width: 420px) {
  .rj-controls .btn { margin-left: 0; width: 100%; }
  .rj-attempts { margin-left: 0; width: 100%; }
  .rj-tray { grid-template-columns: 1fr 1fr; }
  .rj-strand__name { font-size: 11px; }
}

/* ============================================================================
   Labs: Subnet Designer + Firewall Simulator (added v36)
   Reuses .data tables, .tool-select, .btn, .mastery-bar, .lookup-error and the
   .rj-win success card from the wiring game. Only the lab-specific chrome and
   the firewall trace states are new.
   ============================================================================ */
.lab-brief { margin-bottom: var(--sp-3); }
.lab-brief__t { font-family: var(--font-head); font-weight: 600; font-size: var(--fs-md); margin: 0 0 4px; }
.lab-brief__d { font-size: var(--fs-sm); color: var(--text-muted); margin: 0; }

.lab-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-2); margin-top: var(--sp-3); }
.lab-actions .btn { min-height: 44px; }
/* There is no global disabled style for .btn, and these are the first labs to
   disable one. A primary button at full contrast that does nothing when
   clicked is worse than no button. */
.lab-actions .btn[disabled] { opacity: 0.45; cursor: default; box-shadow: none; }
.lab-actions .btn[disabled]:hover { background: var(--accent); }
.lab-actions .btn--ghost[disabled]:hover { background: var(--surface); border-color: var(--line-strong); color: var(--text); }

.tool-select--sm { min-height: 40px; padding: 6px 8px; font-size: var(--fs-xs); }

/* --- Subnet Designer --- */
.lab-table td.mono, .lab-table .mono { font-family: var(--font-mono); font-size: var(--fs-xs); }
.lab-table tr.is-ok th[scope="row"] { color: var(--success-600); }
.lab-table tr.is-bad th[scope="row"] { color: var(--danger-600); }
.lab-table tr.is-bad td { background: var(--danger-100); }
.lab-meter { margin-top: var(--sp-3); display: flex; flex-direction: column; gap: 6px; }
.lab-meter__head { display: flex; justify-content: space-between; font-size: var(--fs-sm); }
.lab-meter__head .num { color: var(--text-muted); font-family: var(--font-mono); font-size: var(--fs-xs); }

/* --- Firewall Simulator --- */
.fw-table th, .fw-table td { vertical-align: middle; }
.fw-in {
  width: 100%; min-width: 0; min-height: 40px;
  padding: 6px 8px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-xs);
}
.fw-in:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
.fw-in--port { max-width: 92px; }
.fw-order { display: flex; gap: 2px; }
.fw-move {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; flex: none;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text-muted);
}
.fw-move:hover:not([disabled]) { color: var(--accent); border-color: var(--accent-decor); }
.fw-move[disabled] { opacity: 0.35; cursor: default; }
.fw-move:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.fw-move--del:hover { color: var(--danger-600); border-color: var(--danger-600); }

/* evaluation trace: scanned, passed over, and the rule that won */
.fw-table tr.fw-scan td, .fw-table tr.fw-scan th { background: var(--accent-soft); }
.fw-table tr.fw-miss td, .fw-table tr.fw-miss th { opacity: 0.55; }
.fw-table tr.fw-hit td, .fw-table tr.fw-hit th {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}
.fw-implicit td { font-size: var(--fs-xs); color: var(--text-muted); font-style: italic; }

.fw-packet { margin-top: var(--sp-3); padding: var(--sp-3); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-alt); }
.fw-packet__t { font-family: var(--font-head); font-weight: 600; font-size: var(--fs-sm); margin: 0 0 var(--sp-2); }
.fw-packet__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-2); }
.fw-f { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.fw-f label { font-size: var(--fs-xs); color: var(--text-muted); }

.fw-verdict {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-2);
  margin-top: var(--sp-3); padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r-sm);
}
.fw-verdict p { margin: 0; font-size: var(--fs-sm); color: var(--text-muted); flex: 1 1 220px; }
.fw-verdict__tag {
  font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 500;
  padding: 4px 12px; border-radius: var(--r-pill); white-space: nowrap;
}
.fw-verdict.is-allow { border-color: var(--success-600); background: var(--success-100); }
.fw-verdict.is-allow .fw-verdict__tag { background: var(--success-600); color: var(--paper); }
.fw-verdict.is-deny { border-color: var(--danger-600); background: var(--danger-100); }
.fw-verdict.is-deny .fw-verdict__tag { background: var(--danger-600); color: var(--paper); }

@media (prefers-reduced-motion: reduce) {
  .fw-table tr { transition: none; }
}
@media (max-width: 560px) {
  .fw-in--port { max-width: none; }
  .lab-actions .btn { width: 100%; }
}
/* over-capacity design: the bar alone would read as a full, healthy 100% */
.lab-meter.is-over .lab-meter__head .num { color: var(--danger-600); font-weight: 500; }
.lab-meter.is-over .mastery-bar span { background: var(--danger-600); }
/* Reorder/delete controls: 32px is fine for a mouse but under the 44px touch
   floor, so grow them on coarse pointers rather than widening the table for
   everyone. */
@media (pointer: coarse) {
  .fw-move { width: 44px; height: 44px; }
}

/* ============================================================================
   Labs: Topology Builder + VLAN Playground (added v39)
   Topology nodes are real <button>s positioned over an SVG cable layer, so
   focus rings, keyboard access and 44px targets come from the button element
   rather than being rebuilt for SVG.
   ============================================================================ */
.topo-palette { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-bottom: var(--sp-2); }
.topo-palette .btn { min-height: 44px; }
.topo-add .icon { color: var(--accent); }

.topo-canvas {
  position: relative;
  height: 340px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background-color: var(--surface-alt);
  background-image: var(--grid-paper);
  background-size: var(--grid-size);
  overflow: hidden;
  touch-action: none;              /* the drag handler owns the gesture */
}
.topo-empty {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-size: var(--fs-sm); color: var(--text-muted); margin: 0;
}
.topo-wires { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.topo-wire { stroke: var(--accent-decor); stroke-width: 2; }
.topo-wire.is-bad { stroke: var(--danger-600); stroke-dasharray: 5 4; }

.topo-node {
  position: absolute;
  transform: translate(-50%, -50%);
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px; padding: 6px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-xs); font-weight: 500;
  white-space: nowrap;
  cursor: grab;
  box-shadow: var(--shadow-xs);
}
.topo-node .icon { color: var(--accent); flex: none; }
.topo-node:hover { border-color: var(--accent-decor); }
.topo-node:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.topo-node.is-selected { border-color: var(--accent); background: var(--accent-soft); }

.topo-findings { list-style: none; margin: var(--sp-2) 0 0; padding: 0; display: grid; gap: 6px; }
.topo-finding {
  display: flex; align-items: flex-start; gap: var(--sp-1);
  padding: 10px var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface-alt);
  font-size: var(--fs-sm); color: var(--text-muted);
}
.topo-finding__tag {
  flex: none; font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 500;
  padding: 2px 8px; border-radius: var(--r-pill);
}
.topo-finding.is-bad { border-left: 3px solid var(--danger-600); }
.topo-finding.is-bad .topo-finding__tag { background: var(--danger-100); color: var(--danger-600); }
.topo-finding.is-warn { border-left: 3px solid var(--warning-600); }
.topo-finding.is-warn .topo-finding__tag { background: var(--warning-100); color: var(--warning-600); }
.topo-note { margin-top: var(--sp-1); font-size: var(--fs-xs); color: var(--text-muted); }

/* --- VLAN Playground --- */
.vlan-toggles { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); margin-bottom: var(--sp-3); }
.vlan-toggle { display: inline-flex; align-items: center; gap: 8px; font-size: var(--fs-sm); min-height: 44px; }
.vlan-toggle input { width: 18px; height: 18px; accent-color: var(--accent); }

.vlan-grid { display: grid; gap: var(--sp-2); grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.vlan-col {
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-alt); padding: var(--sp-2);
}
.vlan-col__head { font-size: var(--fs-sm); margin: 0 0 2px; }
.vlan-col__head span { color: var(--text-muted); font-weight: 400; }
.vlan-col__meta { font-size: var(--fs-xs); color: var(--text-muted); margin: 0 0 var(--sp-2); }
.vlan-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.vlan-list li { display: flex; align-items: center; gap: 6px; }
.vlan-host {
  flex: 1 1 auto; min-width: 0; min-height: 44px;
  padding: 6px 10px; text-align: left;
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text); font-size: var(--fs-xs);
}
.vlan-host:hover { border-color: var(--accent-decor); }
.vlan-host:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.vlan-host.is-picked { border-color: var(--accent); background: var(--accent-soft); font-weight: 500; }
.vlan-host__n { overflow: hidden; text-overflow: ellipsis; display: block; }
.vlan-move { flex: none; width: 104px; }
.vlan-empty { font-size: var(--fs-xs); color: var(--slate-400); }
.vlan-hint { font-size: var(--fs-sm); color: var(--text-muted); }

@media (prefers-reduced-motion: reduce) {
  .topo-node, .vlan-host { transition: none; }
}
@media (max-width: 520px) {
  .topo-canvas { height: 300px; }
  .topo-node { font-size: 11px; padding: 6px 10px; }
  .vlan-move { width: 92px; }
}

/* ============================================================================
   Stretched-link containment fix (v44)
   .card__link::after is `position:absolute; inset:0`, so it needs a positioned
   ancestor to stop at. .card, .news-card and .bento__cell all set
   position:relative: .news-feature and .news-row never did, so their stretched
   links escaped to the initial containing block and covered the whole page,
   swallowing clicks on anything above them (the tech-news category filter bar
   was unreachable because of this).
   Caught by the Playwright suite: a real click reports the interception, while
   JS element.click() bypasses pointer events entirely and always "passed".
   ============================================================================ */
.news-feature { position: relative; }
.news-row { position: relative; }

/* ============================================================================
   Article action rail + share sheet (v45)
   Two placements for one component. Narrow viewports get a horizontal bar in
   normal flow: no fixed positioning, so it cannot cover content or the footer.
   From 1120px up it becomes a vertical rail parked in the left gutter.

   Why 1120px: .container--narrow is 760px of content plus 2 x --gutter (40px
   at this width) = 840px, leaving (1120 - 840) / 2 = 140px of free space each
   side. The rail needs 56px + 24px of gap = 80px, so it clears the viewport
   edge by 60px and never triggers a horizontal scrollbar.

   Lesson pages deliberately do NOT get the floating placement: .learn-layout
   already spends its left gutter on the 264px topics nav.
   ============================================================================ */
/* Containing block for the floating rail at >=1120px. */
.article-shell { position: relative; }

.share-rail {
  display: flex;
  gap: var(--sp-1);
  margin-bottom: var(--sp-3);
  flex-wrap: wrap;
}

.share-rail__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  min-height: 44px;
  padding: 0 var(--sp-2);
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 500;
  cursor: pointer;
  transition: border-color .15s ease, color .15s ease, background-color .15s ease;
}
.share-rail__btn:hover { border-color: var(--accent); color: var(--accent); }
.share-rail__btn .icon { flex: 0 0 auto; }

/* Saved state. The accent is the only interactive colour on the site, so the
   "on" state is a fill of it rather than a second hue. */
.share-rail__btn.is-on {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}
.share-rail__btn.is-on .icon { fill: currentColor; }

@media (min-width: 1120px) {
  /* The slot spans the article's full height so the rail inside it has
     something to be sticky within. pointer-events is off on the slot and back
     on for the rail itself: a full-height absolute box next to the content is
     exactly the shape that swallowed clicks in the v44 stretched-link bug. */
  .share-rail-slot {
    position: absolute;
    top: 0;
    bottom: 0;
    left: -80px;
    width: 56px;
    pointer-events: none;
  }
  .share-rail {
    position: sticky;
    top: 96px;
    flex-direction: column;
    gap: var(--sp-1);
    margin-bottom: 0;
    pointer-events: auto;
  }
  .share-rail__btn {
    width: 56px;
    height: 56px;
    padding: 0;
    flex-direction: column;
    gap: 2px;
    border-radius: var(--r-md);
    box-shadow: var(--shadow-xs);
  }
  .share-rail__label { font-size: 10px; letter-spacing: .01em; }
}

/* ---- inline rail (listing pages) ------------------------------------------
   The floating rail above needs a tall .share-rail-slot to be sticky inside,
   which only an article has. On a hub the rail sits in the flow instead, so
   these rules undo the >=1120px vertical treatment. They come after that media
   query on purpose: same specificity on .share-rail, later wins. */
.share-rail--inline {
  position: static;
  flex-direction: row;
  align-items: center;
  margin: var(--sp-3) 0 0;
  pointer-events: auto;
}
.share-rail--inline .share-rail__btn {
  width: auto;
  height: auto;
  min-height: 44px;
  padding: 0 var(--sp-2);
  flex-direction: row;
  gap: var(--sp-1);
  box-shadow: none;
}
.share-rail--inline .share-rail__label { font-size: var(--fs-sm); letter-spacing: 0; }

/* How many articles are waiting in the saved list. This is the visitor's own
   count of their own bookmarks, not a public total, and js/share.js keeps it
   hidden at zero. */
.share-rail__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--paper);
  font-family: var(--font-head);
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.share-rail__count[hidden] { display: none; }

/* ---- saved articles page --------------------------------------------------
   Everything here is painted by js/share.js from localStorage, so the styles
   have to cover an empty page as gracefully as a full one: the empty state is
   the state most visitors will meet first. */
.saved-list__count {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin: 0 0 var(--sp-2);
}
.saved-list { list-style: none; margin: 0; padding: 0; }
.saved-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.saved-item + .saved-item { margin-top: var(--sp-1); }
.saved-item__body { min-width: 0; flex: 1 1 auto; }
.saved-item__title {
  font-family: var(--font-head);
  font-weight: 600;
  color: var(--text);
}
.saved-item__title:hover { color: var(--accent); }
.saved-item__meta {
  margin: 2px 0 0;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.saved-item__remove {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}
.saved-item__remove:hover { border-color: var(--border); color: var(--text); background: var(--paper-soft); }

.saved-empty {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-4);
  max-width: 56ch;
}
.saved-empty__lead {
  font-family: var(--font-head);
  font-size: var(--fs-lg);
  font-weight: 600;
  margin: 0 0 var(--sp-1);
}
.saved-empty p { color: var(--text-muted); margin: 0 0 var(--sp-2); }
.saved-empty__note { font-size: var(--fs-sm); }

/* ---- share sheet ---------------------------------------------------------- */
.share-sheet {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.share-sheet[hidden] { display: none; }

.share-sheet__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(14, 22, 38, .55);
  opacity: 0;
  transition: opacity .18s ease;
}
.share-sheet.is-open .share-sheet__backdrop { opacity: 1; }

.share-sheet__panel {
  position: relative;
  width: 100%;
  max-width: 460px;
  max-height: 88vh;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: var(--shadow-lift);
  padding: var(--sp-3);
  transform: translateY(16px);
  opacity: 0;
  transition: transform .18s ease, opacity .18s ease;
}
.share-sheet.is-open .share-sheet__panel { transform: none; opacity: 1; }

@media (min-width: 640px) {
  .share-sheet { align-items: center; }
  .share-sheet__panel { border-radius: var(--r-lg); }
}

.share-sheet__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.share-sheet__head h2 { margin: 0; font-size: var(--fs-lg); }

.share-sheet__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.share-sheet__close:hover { color: var(--text); border-color: var(--accent); }

.share-sheet__preview {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 96px;
  gap: var(--sp-2);
  align-items: center;
  padding: var(--sp-2);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  margin-bottom: var(--sp-3);
}
.share-sheet__source {
  margin: 0 0 4px;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: .06em;
}
.share-sheet__title {
  margin: 0;
  font-family: var(--font-head);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
}
.share-sheet__thumb {
  overflow: hidden;
  border-radius: var(--r-sm);
  aspect-ratio: 16 / 10;
  background: var(--surface);
}
.share-sheet__thumb svg { width: 100%; height: 100%; display: block; }

.share-sheet__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
  gap: var(--sp-1);
}

.share-target {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-height: 76px;
  padding: var(--sp-1) 4px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-xs);
  text-decoration: none;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease;
}
.share-target:hover { background: var(--surface-alt); border-color: var(--border); }

.share-target__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--r-pill);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  color: var(--text);
}
.share-target:hover .share-target__icon { border-color: var(--accent); color: var(--accent); }
.share-target__label { text-align: center; line-height: 1.2; }
.share-target.is-on .share-target__icon { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }

.share-sheet__native {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  width: 100%;
  min-height: 44px;
  margin-top: var(--sp-3);
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font: inherit;
  font-size: var(--fs-sm);
  cursor: pointer;
}
.share-sheet__native:hover { color: var(--accent); border-color: var(--accent); }

body.has-share-open { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .share-sheet__backdrop,
  .share-sheet__panel { transition: none; }
  .share-sheet__panel { transform: none; }
}

/* ===========================================================================
   Subnetting Speed Test (js/speedtest.js): step 2: the drill itself.
   Built on the existing quiz shell (.quiz-app, .quiz-app__bar,
   .quiz-app__progress, .challenge-timer/.challenge-clock, .diff-pill) so the
   drill inherits the exercise look rather than forking a second one.
   The polished result card and the certificate arrive in steps 3 and 4.
   =========================================================================== */

/* ---- setup ---- */
.st-setup {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
}
.st-field + .st-field { margin-top: var(--sp-3); }
.st-field__label {
  display: block;
  font-family: var(--font-head);
  font-weight: 600;
  margin-bottom: var(--sp-1);
}
.st-field .diff-pills { margin-bottom: 0; }
.st-field__hint {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin-top: var(--sp-1);
}
.st-start { margin-top: var(--sp-3); width: 100%; }
@media (min-width: 560px) { .st-start { width: auto; } }

/* ---- the run ---- */
.st-q { margin-top: var(--sp-3); }
.st-prompt {
  font-family: var(--font-head);
  font-size: var(--fs-lg);
  font-weight: 600;
  line-height: 1.35;
}
/* b in the prompt marks the thing being asked for: weight alone is not
   enough when the whole prompt is already semibold. */
.st-prompt b { color: var(--accent); font-weight: 700; }

.st-answer {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin-top: var(--sp-2);
}
.st-input {
  flex: 1 1 200px;
  min-width: 0;                       /* or a long placeholder widens the row past 375px */
  font-family: var(--font-mono);
  font-size: var(--fs-base);          /* 16px: anything smaller makes iOS zoom on focus */
  padding: 12px var(--sp-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
}
.st-input:focus-visible { box-shadow: var(--focus-ring); border-color: var(--accent); outline: none; }
.st-answer .btn { flex: 0 0 auto; }

/* .challenge-clock carries margin-right:auto for its use in .quiz-app__foot.
   Inside the bar (which is already space-between) that pulls the clock left
   until it collides with the counter ("correct 060s"). */
.st-run .challenge-clock { margin-right: 0; }

/* Feedback for the question BEFORE this one. It sits above the prompt and is
   labelled, because an unlabelled verdict directly above a fresh question
   reads as a verdict on that question. */
.st-last {
  min-height: 1.5em;                  /* reserve the row so the form never jumps */
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.st-last__tag {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 1px 8px;
  margin-right: 4px;
  white-space: nowrap;
}
.st-last.is-correct { color: var(--success-600); }
.st-last.is-wrong { color: var(--text-muted); }
.st-last.is-wrong b { color: var(--danger-600); }

/* Reduced motion: the bar still shrinks, it just does not animate between
   ticks. .quiz-app__progress span carries transition: width var(--t). */
.challenge-timer.is-static span { transition: none; }

/* ---------------------------------------------------------------------------
   Result card (js/drill-kit.js). Shared by every timed drill: nothing here is
   subnetting-specific. Step 4 adds the certificate and the print stylesheet.
   --------------------------------------------------------------------------- */
.rcard {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  text-align: center;
}
.rcard__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.rcard__hero { display: flex; justify-content: center; margin-top: var(--sp-2); }

/* ---- circular badges ---- */
.rbadge { position: relative; display: inline-flex; flex-direction: column; align-items: center; }
.rbadge__ring {
  width: var(--rb, 92px);
  height: var(--rb, 92px);
  display: block;
  transform: rotate(-90deg);          /* arcs start at 12 o'clock */
}
.rbadge__track { fill: none; stroke: var(--paper-mist); stroke-width: 7; }
.rbadge__arc {
  fill: none;
  stroke: var(--accent);
  stroke-width: 7;
  stroke-linecap: round;
  transition: stroke-dashoffset var(--t);
}
.rbadge--muted .rbadge__arc { stroke: var(--line-strong); }
.rbadge--goal .rbadge__arc { stroke: var(--success-600); }
.rbadge__val {
  position: absolute;
  top: 0; left: 0;
  width: var(--rb, 92px);
  height: var(--rb, 92px);
  /* flex, not grid: grid makes the unit suffix its own row and stacks "%"
     underneath the number. */
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--fs-xl);
  color: var(--text);
}
.rbadge__val i { font-style: normal; font-size: 0.6em; margin-left: 1px; }
.rbadge__label {
  margin-top: 6px;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.rcard__hero .rbadge { --rb: 156px; }
.rcard__hero .rbadge__val { font-size: clamp(2.5rem, 11vw, 3.25rem); color: var(--accent); }
.rcard__hero .rbadge--goal .rbadge__val { color: var(--success-600); }

.rcard__goalstate { margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--text-muted); }
.rcard__goalstate.is-met { color: var(--success-600); font-weight: 600; }

/* ---- Speed x Accuracy = Net ---- */
.rcard__eq {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: var(--sp-1) var(--sp-2);
  margin-top: var(--sp-4);
}
.rcard__eq { --rb: 84px; }
.rcard__eq .rbadge__val { font-size: var(--fs-lg); }
.rcard__op {
  font-family: var(--font-head);
  font-size: var(--fs-xl);
  color: var(--text-muted);
  line-height: var(--rb);             /* centre on the badge, not the label */
}
/* Three badges plus two operators do not fit a 375px column at 84px, and the
   row wraps with the "=" orphaned on the first line. Shrink the whole row. */
@media (max-width: 460px) {
  .rcard__eq { --rb: 66px; gap: var(--sp-1); }
  .rcard__eq .rbadge__val { font-size: var(--fs-base); }
  .rcard__op { font-size: var(--fs-lg); }
}
.rcard__counts { margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--text-muted); }

/* ---- blocks ---- */
.rcard__block {
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  text-align: left;
}
.rcard__block h3 { font-size: var(--fs-lg); }
.rcard__blockhead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-1);
}
.rcard__fine { margin-top: var(--sp-1); font-size: var(--fs-xs); color: var(--text-muted); }
.rcard__note {
  margin-top: var(--sp-2);
  padding: 10px var(--sp-2);
  border-radius: var(--r-sm);
  background: var(--warning-100);
  color: var(--text);
  font-size: var(--fs-sm);
}

/* ---- history chart ----
   Labels are absolutely positioned off the column's --h, so the plot's content
   box is exactly the bar area and the goal line lands on scale. */
.rchart { margin-top: var(--sp-3); }
.rchart__plot {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 6px;
  height: 140px;
  margin: 26px 0 22px;                /* room for the labels outside the box */
}
.rchart__plot.has-goal::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  bottom: var(--goal);
  border-top: 1px dashed var(--line-strong);
}
.rchart__col {
  position: relative;
  flex: 1;
  height: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
/* Dark mode does not redefine --teal-500, so --accent-decor and --accent both
   resolve to #08B3A0 there and a colour swap marks nothing. Opacity separates
   the best bar in both themes; the "best" label carries the meaning for anyone
   who cannot see the difference at all. */
.rchart__bar {
  width: 100%;
  max-width: 34px;
  height: var(--h);
  min-height: 2px;
  border-radius: 3px 3px 0 0;
  background: var(--accent);
  opacity: 0.45;
}
.rchart__bar.is-best { opacity: 1; }
.rchart__val {
  position: absolute;
  left: 0; right: 0;
  bottom: calc(var(--h) + 5px);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.rchart__best {
  position: absolute;
  left: 0; right: 0;
  top: calc(100% + 5px);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
}
.rchart__empty { margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--text-muted); }

@media (prefers-reduced-motion: no-preference) {
  .rchart:not(.is-static) .rchart__bar {
    animation: rchart-grow var(--t-slow) var(--ease) both;
  }
}
@keyframes rchart-grow { from { height: 0; } to { height: var(--h); } }

/* ---- goal form ---- */
.rcard__goalform { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-2); }
.rcard__goalform .st-input { flex: 1 1 160px; }

/* ---- next tests ---- */
.rcard__grid {
  display: grid;
  gap: var(--sp-1);
  grid-template-columns: repeat(2, 1fr);
  margin-top: var(--sp-2);
}
@media (min-width: 620px) { .rcard__grid { grid-template-columns: repeat(4, 1fr); } }
.rcard__tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 64px;
  justify-content: center;
  padding: 10px var(--sp-2);
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  transition: border-color var(--t-fast), color var(--t-fast);
}
.rcard__tile:hover { border-color: var(--accent); color: var(--accent); }
.rcard__tile:focus-visible { box-shadow: var(--focus-ring); outline: none; }
.rcard__tile strong { font-family: var(--font-head); font-size: var(--fs-base); }
.rcard__tile small { font-size: var(--fs-xs); color: var(--text-muted); }

/* ---- actions ---- */
.rcard__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-1);
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
}

/* ---------------------------------------------------------------------------
   Practice certificate (js/drill-kit.js, section 4).
   On screen it follows the site's theme like everything else. On paper it is
   forced light: see the print block at the end, which is the first and only
   @media print rule on this site.
   --------------------------------------------------------------------------- */
.ltn-certwrap { margin-top: var(--sp-3); text-align: left; }
.ltn-certwrap__form label {
  display: block;
  font-family: var(--font-head);
  font-weight: 600;
  margin-bottom: var(--sp-1);
}
.ltn-certwrap__row { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.ltn-certwrap__row .st-input { flex: 1 1 220px; }

.ltn-cert {
  margin-top: var(--sp-3);
  padding: var(--sp-4) var(--sp-3);
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  /* The inner rule is what makes it read as a certificate rather than another
     card: one hairline inset from the border, no seal, no flourish. */
  box-shadow: inset 0 0 0 1px var(--border);
  outline: 1px solid var(--border);
  outline-offset: -10px;
}
.ltn-cert__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-1);
  margin-bottom: var(--sp-4);
}
.ltn-cert__brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-head);
  font-weight: 700;
}
.ltn-cert__mark { width: 26px; height: 26px; flex: none; }
.ltn-cert__kind {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
  border: 1px solid var(--accent-decor);
  border-radius: var(--r-pill);
  padding: 3px 10px;
}
.ltn-cert__name {
  font-family: var(--font-head);
  font-size: clamp(1.75rem, 6vw, 2.5rem);
  font-weight: 700;
  line-height: 1.15;
  word-break: break-word;
}
.ltn-cert__name i { font-style: normal; color: var(--text-muted); opacity: 0.55; }
.ltn-cert__what { margin-top: var(--sp-1); font-size: var(--fs-md); }
.ltn-cert__meta {
  margin-top: var(--sp-1);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.ltn-cert__eq {
  --rb: 78px;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: var(--sp-1) var(--sp-2);
  margin: var(--sp-4) 0;
}
.ltn-cert__eq .rbadge__val { font-size: var(--fs-base); }
@media (max-width: 460px) { .ltn-cert__eq { --rb: 62px; } }
.ltn-cert__context {
  margin: 0 auto;
  max-width: 52ch;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.ltn-cert__disclaimer {
  margin: var(--sp-3) auto 0;
  max-width: 52ch;
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* ===========================================================================
   PRINT. Dark on screen, light on paper: a dark background wastes ink and
   prints badly, so every colour below is stated explicitly rather than left to
   the theme tokens: the page may well be in dark mode when the user prints.

   Only the certificate reaches the paper. js/drill-kit.js marks the ancestor
   chain from the certificate up to <body> with .ltn-print-keep, so this rule
   hides everything that is neither on that chain nor inside the certificate, 
   without this stylesheet needing to know anything about the page around it.
   =========================================================================== */
@media print {
  @page { size: A4 portrait; margin: 14mm; }

  html, body {
    background: #FFFFFF !important;
    color: #14181F !important;
    height: auto !important;
    margin: 0 !important;
    padding: 0 !important;
  }

  /* Scoped to pages that actually have a certificate. Unscoped, this hid every
     element on every page at print time, which meant printing the cheat sheets
     produced a blank sheet of paper. The certificate is rendered by JS, so
     :has() is exactly the right test: the rule applies when the thing it exists
     for is on the page. */
  body:has(.ltn-cert) *:not(.ltn-print-keep):not(.ltn-cert):not(.ltn-cert *) { display: none !important; }

  .ltn-print-keep {
    display: block !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    outline: 0 !important;
    background: none !important;
    box-shadow: none !important;
    max-width: none !important;
    width: auto !important;
  }

  .ltn-cert {
    /* Keep the ring arcs and the brand mark: browsers drop backgrounds and
       some colours on print unless told otherwise. */
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
    break-inside: avoid;
    page-break-inside: avoid;
    margin: 0 !important;
    padding: 16mm 12mm !important;
    background: #FFFFFF !important;
    color: #14181F !important;
    border: 1.5pt solid #14181F !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    outline: 0.75pt solid #8A97A8 !important;
    outline-offset: -6mm;
  }
  .ltn-cert__brand { color: #14181F !important; }
  .ltn-cert__kind {
    color: #0A7F73 !important;
    border-color: #0A7F73 !important;
  }
  .ltn-cert__name { color: #14181F !important; font-size: 30pt !important; }
  .ltn-cert__what { color: #14181F !important; font-size: 13pt !important; }
  .ltn-cert__meta { color: #3F4A5A !important; font-size: 10pt !important; }
  .ltn-cert__context { color: #3F4A5A !important; font-size: 9.5pt !important; }
  .ltn-cert__disclaimer {
    color: #3F4A5A !important;
    font-size: 8.5pt !important;
    border-top-color: #C6CEDA !important;
  }

  /* The badges are SVG strokes; they inherit nothing useful on paper. */
  .ltn-cert__eq { --rb: 84px; margin: 12mm 0 !important; }
  .ltn-cert .rbadge__track { stroke: #DDE4EE !important; }
  .ltn-cert .rbadge__arc { stroke: #0A7F73 !important; }
  .ltn-cert .rbadge--muted .rbadge__arc { stroke: #8A97A8 !important; }
  .ltn-cert .rbadge__val { color: #14181F !important; font-size: 15pt !important; }
  .ltn-cert .rbadge__label { color: #3F4A5A !important; font-size: 8.5pt !important; }
  .ltn-cert .rcard__op { color: #3F4A5A !important; font-size: 16pt !important; }
}

/* ---------------------------------------------------------------------------
   Shared-edge grid panel (2026-08-28).
   Cells share one hairline instead of floating apart with gaps: the 1px grid
   gap exposes the panel's own background, so every internal rule is exactly one
   border wide and the block reads as a single schematic rather than a scatter
   of cards. Small radius on purpose: a 22px radius reads soft/consumer, a 6px
   radius reads technical, which is what this site is.
   --------------------------------------------------------------------------- */
.grid-panel {
  display: grid;
  gap: 1px;                          /* the gap IS the hairline */
  background: var(--border);         /* and this is what shows through it */
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}
.grid-panel__cell {
  position: relative;
  min-width: 0;                      /* same grid-item shrink rule as the hero */
  display: flex;
  flex-direction: column;
  background: var(--surface);
  padding: var(--sp-3);
  transition: background-color var(--t-fast);
}
/* Hover wash rather than lift: these cells sit flush against each other, so a
   transform would break the shared edges apart. */
.grid-panel__cell--link:hover { background: var(--paper-soft); }
.grid-panel__cell--link:focus-within { background: var(--paper-soft); }

/* The feature + three-links layout: one tall cell beside a stack of rows. */
@media (min-width: 900px) {
  .grid-panel--feature { grid-template-columns: 1.35fr 1fr; }
  .grid-panel--feature > .grid-panel__cell--feature { grid-row: span 3; }
}

.grid-panel__kicker {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}
.grid-panel__title {
  font-family: var(--font-head);
  font-size: var(--fs-lg);
  font-weight: 600;
  margin-top: 2px;
}
.grid-panel__desc { margin-top: var(--sp-1); font-size: var(--fs-sm); color: var(--text-muted); }
.grid-panel__cell--link .grid-panel__title { font-size: var(--fs-base); }

/* The corner affordance, matching .card__go but sized for a flush cell. */
.grid-panel__go {
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
  color: var(--text-muted);
  transition: color var(--t-fast), transform var(--t-fast);
}
.grid-panel__cell--link:hover .grid-panel__go { color: var(--accent); transform: translate(2px, -2px); }
@media (prefers-reduced-motion: reduce) {
  .grid-panel__cell--link:hover .grid-panel__go { transform: none; }
}

/* Live preview well inside a feature cell. */
.grid-panel__live { margin-top: var(--sp-3); }
.grid-panel__live .tryit { margin: 0; }

/* Section label as a bordered chip. */
.eyebrow--chip {
  display: inline-block;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 3px 10px;
}

/* ---------------------------------------------------------------------------
   Start-here routing + the honesty block (2026-08-30).
   --------------------------------------------------------------------------- */
@media (min-width: 900px) {
  .grid-panel--thirds { grid-template-columns: repeat(3, 1fr); }
}
/* The recommended path is first among equals, not a different species: an
   accent edge and a coloured kicker, no size change, so the other two still
   read as real options rather than consolation prizes. */
.grid-panel__cell--lead { box-shadow: inset 3px 0 0 var(--accent); }
.grid-panel__cell--lead .grid-panel__kicker { color: var(--accent); }
@media (min-width: 900px) {
  .grid-panel__cell--lead { box-shadow: inset 0 3px 0 var(--accent); }
}

.honesty-list {
  list-style: none;
  margin-top: var(--sp-3);
  display: grid;
  gap: var(--sp-2);
}
.honesty-list li {
  position: relative;
  padding-left: var(--sp-3);
  font-size: var(--fs-sm);
}
.honesty-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}
.honesty-list b { font-weight: 600; }
.honesty-note {
  margin-top: var(--sp-3);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  opacity: 0.85;
}

/* ---------------------------------------------------------------------------
   Track scope index (2026-08-30). Four designers asked for this independently:
   eight names and eight counts, so a visitor can answer "do you cover my gap"
   without opening the nav. Four across on desktop, two at 360px, never one,
   because a single column turns a two-second scan into a scroll.
   --------------------------------------------------------------------------- */
.grid-panel--tracks { grid-template-columns: repeat(2, 1fr); }
@media (min-width: 720px)  { .grid-panel--tracks { grid-template-columns: repeat(4, 1fr); } }

.track-cell { gap: 2px; padding: var(--sp-2); }
.track-cell__name {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--fs-base);
  line-height: 1.25;
}
.track-cell__count {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* ---------------------------------------------------------------------------
   Story byline: avatar, name, date (2026-08-30).
   Added to both builds so the news cards carry an author, which the prototype
   did not have. Sized so the avatar sits on the same baseline as the two lines
   of text beside it rather than floating above them.
   --------------------------------------------------------------------------- */
.news-byline {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: var(--sp-2);
}
.news-byline__avatar {
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: 50%;
  background: var(--paper-mist);
}
.news-byline__text {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}
.news-byline__text strong {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
}
.news-byline__text time {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* ============================================================================
   Labs: purpose modes, and the two labs added with them (v74, 2026-09-06)

   The hub groups labs by what you do in them (Observe / Build / Troubleshoot)
   rather than by topic. Everything below is layout and state; all colour comes
   from the existing tokens, so both themes and the one-teal-accent rule hold
   without a second definition.
   ============================================================================ */

/* ---- Hub: mode sections ---------------------------------------------------- */
.lab-mode + .lab-mode { margin-top: var(--sp-6); }

.lab-mode__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border);
}
.lab-mode__ic {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--accent);
}
.lab-mode__title-wrap { flex: 1 1 auto; min-width: 0; }
.lab-mode__name { font-size: var(--fs-xl); margin: 0; }
.lab-mode__verb {
  margin: 2px 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}
/* The count is the honest one: labs only, never the tools grouped beside them. */
.lab-mode__count {
  flex: none;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  white-space: nowrap;
}
.lab-mode__desc {
  margin: var(--sp-2) 0 var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  max-width: 68ch;
}

.lab-card__ic {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  color: var(--accent);
}
/* Marks a card that is NOT a lab, so an Observe group holding two tools can
   never read as four labs. Deliberately quiet. It is a fact, not a badge. */
.lab-kind {
  flex: none;
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 2px 10px;
  white-space: nowrap;
  /* .card__go is absolutely positioned at top/right var(--sp-3) and the cell
     pads var(--sp-4), so the head's right edge sits 16px INSIDE the arrow.
     Reserve the difference plus a gap, or the pill and the arrow overlap. */
  margin-right: var(--sp-3);
}

@media (max-width: 520px) {
  .lab-mode__head { flex-wrap: wrap; }
  .lab-mode__count { width: 100%; padding-left: 60px; }
}

/* ============================================================================
   Observe: TCP three-way handshake
   A message ladder in HTML rather than SVG: arrow bodies are CSS borders, so
   they stay crisp at any width and the labels stay real text (an SVG stretched
   with preserveAspectRatio="none" distorts its own type).
   ============================================================================ */
.hs-peers {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.hs-peer {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-alt);
  padding: var(--sp-2);
  min-width: 0;
}
.hs-peer__name {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 6px;
  font-size: var(--fs-sm); font-weight: 600;
}
.hs-peer__name .icon { color: var(--accent); flex: none; }
.hs-peer__state {
  display: inline-block;
  font-family: var(--font-mono); font-size: var(--fs-xs);
  padding: 3px 10px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--text-muted);
  transition: background-color var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.hs-peer__state.is-open {
  border-color: var(--success-600);
  background: var(--success-100);
  color: var(--success-600);
  font-weight: 500;
}
.hs-peer__seq { margin: 6px 0 0; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-muted); }

.hs-msgs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.hs-msg { display: flex; flex-direction: column; gap: 6px; }
.hs-msg__seg {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text);
  background: var(--accent-soft);
  border: 1px solid var(--accent-decor);
  border-radius: var(--r-pill);
  padding: 4px 12px;
  align-self: flex-start;
}
.hs-msg--rtl .hs-msg__seg { align-self: flex-end; }

/* arrow body + CSS arrowhead, direction set by the modifier */
.hs-arrow { position: relative; height: 2px; background: var(--accent); border-radius: 2px; }
.hs-arrow::after {
  content: ''; position: absolute; top: -5px;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
}
.hs-msg--ltr .hs-arrow::after { right: -1px; border-left: 9px solid var(--accent); }
.hs-msg--rtl .hs-arrow::after { left: -1px; border-right: 9px solid var(--accent); }

.hs-msg__why { margin: 0; font-size: var(--fs-sm); color: var(--text-muted); }

/* Entrance: the arrow draws from the sender's side. Purely decorative, so it
   is switched off wholesale under reduced motion below. */
.hs-msg.is-new .hs-arrow { animation: hs-draw var(--t-slow) both; }
.hs-msg--ltr.is-new .hs-arrow { transform-origin: left center; }
.hs-msg--rtl.is-new .hs-arrow { transform-origin: right center; }
@keyframes hs-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.hs-note {
  margin-top: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-sm);
  background: var(--surface-alt);
}
.hs-note__t { margin: 0 0 4px; font-family: var(--font-head); font-weight: 600; font-size: var(--fs-sm); }
.hs-note p { margin: 0; font-size: var(--fs-sm); color: var(--text-muted); }
.hs-progress { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-muted); }

@media (prefers-reduced-motion: reduce) {
  .hs-msg.is-new .hs-arrow { animation: none; }
  .hs-peer__state { transition: none; }
}

/* ============================================================================
   Troubleshoot: Host A can't reach Host B
   The console is the site's existing dark terminal motif; only the scrollback
   and the input row are new.
   ============================================================================ */
.ts-ticket {
  border: 1px solid var(--border);
  border-left: 3px solid var(--warning-600);
  border-radius: var(--r-sm);
  background: var(--surface-alt);
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-3);
}
.ts-ticket__head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px var(--sp-2);
  margin-bottom: 4px;
}
.ts-ticket__id {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  color: var(--warning-600); background: var(--warning-100);
  border-radius: var(--r-pill); padding: 2px 10px;
}
.ts-ticket__t { margin: 0; font-family: var(--font-head); font-weight: 600; font-size: var(--fs-sm); }
.ts-ticket p { margin: 0; font-size: var(--fs-sm); color: var(--text-muted); }

/* device picker: tabs, but they switch a console rather than a panel */
.ts-devices { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-bottom: var(--sp-2); }
.ts-device {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 8px var(--sp-2);
  border: 1px solid var(--line-strong); border-radius: var(--r-pill);
  background: var(--surface); color: var(--text-muted);
  font-size: var(--fs-sm); font-weight: 500;
  transition: color var(--t-fast), border-color var(--t-fast), background-color var(--t-fast);
}
.ts-device .icon { flex: none; }
.ts-device:hover { color: var(--accent); border-color: var(--accent-decor); }
.ts-device:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ts-device[aria-pressed="true"] {
  background: var(--ink-900); border-color: var(--ink-900); color: var(--paper);
}

.ts-console { margin-bottom: var(--sp-3); }
.ts-console__body {
  margin: 0;
  padding: var(--sp-2);
  max-height: 320px;
  overflow-y: auto;
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  line-height: 1.7;
  color: var(--code-text);
  white-space: pre;
}
.ts-console__body .c-cmd { color: var(--code-teal); }
.ts-console__body .c-dim { color: var(--code-muted); }
.ts-console__body .c-key { color: var(--code-violet); }
.ts-console__body .c-err { color: #F98A8A; }

.ts-form {
  display: flex; flex-wrap: wrap; gap: var(--sp-1);
  padding: var(--sp-2);
  border-top: 1px solid var(--code-line);
  background: var(--code-surface);
}
.ts-form__prompt {
  font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: 500;
  color: var(--code-teal); align-self: center; flex: none;
}
.ts-form__in {
  flex: 1 1 200px; min-width: 0; min-height: 44px;
  padding: 10px var(--sp-2);
  font-family: var(--font-mono); font-size: var(--fs-sm);
  border: 1px solid var(--code-line); border-radius: var(--r-sm);
  background: var(--code-bg); color: var(--code-text);
}
.ts-form__in::placeholder { color: var(--code-muted); }
.ts-form__in:focus-visible { outline: none; border-color: var(--code-teal); box-shadow: var(--focus-ring); }
.ts-form .btn { min-height: 44px; flex: none; }

.ts-diag { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-alt); padding: var(--sp-3); }
.ts-diag__t { margin: 0 0 4px; font-family: var(--font-head); font-weight: 600; font-size: var(--fs-md); }
.ts-diag__d { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); color: var(--text-muted); }
.ts-choices { display: grid; gap: var(--sp-1); margin: 0 0 var(--sp-3); }
.ts-choice {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  width: 100%; text-align: left;
  min-height: 44px; padding: 12px var(--sp-2);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text);
  font-size: var(--fs-sm);
  transition: border-color var(--t-fast), background-color var(--t-fast);
}
.ts-choice:hover:not([disabled]) { border-color: var(--accent-decor); }
.ts-choice:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ts-choice[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); }
.ts-choice[disabled] { cursor: default; }
.ts-choice[disabled]:not(.is-right):not(.is-wrong) { opacity: 0.55; }
.ts-choice__mark {
  flex: none; width: 18px; height: 18px; margin-top: 1px;
  border: 1px solid var(--line-strong); border-radius: 50%;
}
.ts-choice[aria-pressed="true"] .ts-choice__mark { border-color: var(--accent); box-shadow: inset 0 0 0 4px var(--accent); }
.ts-choice.is-right { border-color: var(--success-600); background: var(--success-100); }
.ts-choice.is-wrong { border-color: var(--danger-600); background: var(--danger-100); }

.ts-verdict {
  margin-top: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r-sm);
}
.ts-verdict__tag {
  display: inline-block;
  font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 500;
  padding: 4px 12px; border-radius: var(--r-pill);
  margin-bottom: 8px;
}
.ts-verdict p { margin: 0 0 8px; font-size: var(--fs-sm); color: var(--text-muted); }
.ts-verdict p:last-child { margin-bottom: 0; }
.ts-verdict.is-right { border-color: var(--success-600); background: var(--success-100); }
.ts-verdict.is-right .ts-verdict__tag { background: var(--success-600); color: var(--paper); }
.ts-verdict.is-wrong { border-color: var(--danger-600); background: var(--danger-100); }
.ts-verdict.is-wrong .ts-verdict__tag { background: var(--danger-600); color: var(--paper); }
.ts-verdict code {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 4px; padding: 1px 5px;
  /* base.css sets `p code { white-space: nowrap }` sitewide; these strings are
     whole sentences of console output, so they have to be allowed to wrap or
     they push the page sideways on a phone. */
  white-space: normal;
}

.ts-hint { margin-top: var(--sp-2); font-size: var(--fs-xs); color: var(--text-muted); }

@media (prefers-reduced-motion: reduce) {
  .ts-device, .ts-choice { transition: none; }
}
@media (max-width: 560px) {
  .hs-peers { grid-template-columns: minmax(0, 1fr); }
  .ts-form__prompt { width: 100%; }
  .ts-form .btn { width: 100%; }
  .ts-console__body { max-height: 260px; }
}

/* Drill prompts can carry a full IPv6 address, which is a single unbreakable
   word 458px wide at a 320px viewport. <wbr> in the markup gives preferred
   break points between groups; this is the safety net for anything else long
   that reaches these two elements. Added with the Subnet Trainer (v75). */
.st-prompt, .st-last { overflow-wrap: anywhere; }

/* ============================================================================
   Interview Prep (v76): reuses .faq for the reveal, so only the parts that do
   not exist elsewhere are defined here.
   ============================================================================ */
.interview-role[hidden] { display: none !important; }
.interview-role .faq { margin-top: var(--sp-3); }

/* The line that makes this page worth more than a generic question list, so it
   is given its own weight rather than reading as another paragraph. */
.interview-listen {
  margin-top: var(--sp-2) !important;
  padding: 10px var(--sp-2);
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: var(--fs-sm);
  color: var(--ink-800) !important;
}
.interview-listen strong { color: var(--text); }

.interview-links { margin-top: var(--sp-2) !important; font-size: var(--fs-xs) !important; }

.interview-mark {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1);
  margin-top: var(--sp-3);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border);
}
.interview-mark[hidden] { display: none; }
.interview-mark__label { font-size: var(--fs-xs); color: var(--text-muted); margin-right: 4px; }
/* 44px is the touch floor everywhere else on the site; these are no exception
   just because they sit inside a disclosure. */
.diff-pill--sm { min-height: 44px; padding: 8px var(--sp-2); font-size: var(--fs-xs); }

/* A question you have marked keeps a quiet edge, so scanning the page shows
   where you stand without opening anything. */
details[data-mark-state="ok"] { border-left: 3px solid var(--success-600); }
details[data-mark-state="work"] { border-left: 3px solid var(--warning-600); }

@media (max-width: 520px) {
  .interview-mark__label { width: 100%; margin-bottom: 4px; }
  .diff-pill--sm { flex: 1 1 auto; }
}

/* ============================================================================
   Cheat Sheets (v77): reference, printed. Tables reuse table.data and
   .table-wrap from base.css; only the sheet frame and the print behaviour are
   new here.
   ============================================================================ */
.cheat-sheet { margin-top: var(--sp-6); }
.cheat-sheet__head {
  display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-2); margin-bottom: var(--sp-3);
  padding-bottom: var(--sp-2); border-bottom: 2px solid var(--ink-900);
}
.cheat-sheet__head h2 { font-size: var(--fs-xl); margin: 0; }
.cheat-sheet__blurb { margin: 4px 0 0; font-size: var(--fs-sm); color: var(--text-muted); max-width: 60ch; }
.cheat-print { flex: none; min-height: 44px; }
.cheat-print[hidden] { display: none; }

.cheat-group {
  font-size: var(--fs-sm); font-family: var(--font-mono);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--accent); margin: var(--sp-3) 0 var(--sp-1);
}
.cheat-table th[scope="row"] { font-family: var(--font-mono); font-size: var(--fs-xs); white-space: nowrap; }
.cheat-table--cmd th[scope="row"] { width: 42%; }
.cheat-table--cmd code { background: none; border: 0; padding: 0; }

.cheat-notes { margin: var(--sp-2) 0 0; padding-left: var(--sp-3); display: grid; gap: 6px; }
.cheat-notes li { font-size: var(--fs-sm); color: var(--text-muted); }
.cheat-links { margin-top: var(--sp-2); font-size: var(--fs-xs); }

@media (max-width: 560px) {
  .cheat-print { width: 100%; justify-content: center; }
  .cheat-table--cmd th[scope="row"] { width: auto; }
}

@media print {
  /* On paper: the sheet, and nothing else. The site chrome, the explanatory
     callout and the print buttons are all screen furniture. */
  body:has(.cheat-sheets) .site-header,
  body:has(.cheat-sheets) .site-footer,
  body:has(.cheat-sheets) .page-hero,
  body:has(.cheat-sheets) .callout,
  body:has(.cheat-sheets) .cheat-print,
  body:has(.cheat-sheets) .cheat-links { display: none !important; }

  /* "Print this sheet" prints one. Without JS the browser prints them all,
     which is a worse outcome rather than a broken one. */
  body.ltn-printing .cheat-sheet:not(.is-printing) { display: none !important; }

  .cheat-sheet { margin: 0; page-break-after: always; break-after: page; }
  .cheat-sheet:last-of-type { page-break-after: auto; break-after: auto; }
  .table-wrap { overflow: visible !important; border: 0 !important; }
  .cheat-table { font-size: 9pt; width: 100%; }
  .cheat-table thead { display: table-header-group; }   /* repeat the header on every page */
  .cheat-table tr { page-break-inside: avoid; break-inside: avoid; }
  .cheat-notes li { font-size: 8pt; }
}

/* ============================================================================
   Roadmap questionnaire (v78). One question per screen, large answers, a
   progress bar, Continue disabled until you choose. Answers are real radio
   inputs, so arrow keys, Enter and screen readers work without extra code;
   the card styling reacts to them through :has(), which this sheet already
   relies on elsewhere.
   ============================================================================ */
.rm { margin-bottom: var(--sp-6); }
.rm[hidden] { display: none; }

.rm-card, .rm-result {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); padding: var(--sp-4);
  box-shadow: var(--shadow-md);
}
.rm-result:focus, .rm-q:focus { outline: none; }

.rm-top { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4); min-height: 44px; }
.rm-back {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  min-height: 44px; padding: 0 var(--sp-1); margin-left: calc(var(--sp-1) * -1);
  border: 0; background: none; color: var(--text-muted); font-size: var(--fs-sm); border-radius: var(--r-sm);
}
.rm-back:hover { color: var(--accent); }
.rm-back:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.rm-progress { flex: 1; height: 10px; border-radius: var(--r-pill); background: var(--paper-mist); overflow: hidden; }
.rm-progress span { display: block; height: 100%; background: var(--accent); border-radius: inherit; transition: width var(--t); }

.rm-kicker { margin: 0 0 6px; font-family: var(--font-mono); font-size: var(--fs-xs); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--accent); }
.rm-q { font-size: var(--fs-2xl); line-height: 1.2; margin: 0; text-wrap: balance; }
.rm-sub { margin: var(--sp-1) 0 0; color: var(--text-muted); font-size: var(--fs-base); }

.rm-options { border: 0; padding: 0; margin: var(--sp-4) 0 0; display: grid; gap: var(--sp-2); min-width: 0; }
.rm-option {
  position: relative; display: flex; align-items: center; gap: var(--sp-2);
  min-height: 64px; padding: var(--sp-2) var(--sp-3);
  border: 2px solid var(--line-strong); border-bottom-width: 4px; border-radius: var(--r-md);
  background: var(--surface); cursor: pointer;
  transition: border-color var(--t-fast), background-color var(--t-fast), transform var(--t-fast);
}
.rm-option input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.rm-option:hover { border-color: var(--accent-decor); background: var(--surface-alt); }
.rm-option:active { transform: translateY(2px); }
.rm-option:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.rm-option:has(input:focus-visible) { box-shadow: var(--focus-ring); }
.rm-option:has(input:disabled) { cursor: default; transform: none; }
.rm-option__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rm-option__text strong { font-size: var(--fs-md); font-weight: 600; color: var(--text); }
.rm-option__text small { font-size: var(--fs-sm); color: var(--text-muted); }
.rm-option.is-right { border-color: var(--success-600); background: var(--success-100); }
.rm-option.is-wrong { border-color: var(--danger-600); background: var(--danger-100); }

/* signal-strength bars as the level indicator */
.rm-bars { width: 26px; height: 22px; flex: none; }
.rm-bars rect { fill: var(--line-strong); }
.rm-bars rect.is-on { fill: var(--accent); }

.rm-feedback { margin-top: var(--sp-3); padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md); font-size: var(--fs-sm); color: var(--text); }
.rm-feedback[hidden] { display: none; }
.rm-feedback.is-right { background: var(--success-100); border-left: 4px solid var(--success-600); }
.rm-feedback.is-wrong { background: var(--danger-100); border-left: 4px solid var(--danger-600); }

.rm-foot { display: flex; justify-content: flex-end; margin-top: var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--border); }
.rm-next { min-height: 48px; min-width: 160px; }
.rm-next[disabled] { opacity: .45; cursor: default; box-shadow: none; }
.rm-next[disabled]:hover { background: var(--accent); }

/* ---- result ---- */
.rm-result__title { font-size: var(--fs-2xl); margin: 0; text-wrap: balance; }
.rm-result__summary { margin: var(--sp-2) 0 0; color: var(--text-muted); max-width: 65ch; }

.rm-start {
  position: relative; display: grid; gap: 4px;
  margin-top: var(--sp-3); padding: var(--sp-3) calc(var(--sp-6) + var(--sp-2)) var(--sp-3) var(--sp-3);
  border-radius: var(--r-md); background: var(--accent); color: var(--on-accent);
  text-decoration: none; transition: background-color var(--t-fast);
}
.rm-start:hover { background: var(--accent-hover); color: var(--on-accent); }
.rm-start:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.rm-start__label { font-family: var(--font-mono); font-size: var(--fs-xs); letter-spacing: var(--track-wide); text-transform: uppercase; opacity: .85; }
.rm-start__title { font-family: var(--font-head); font-size: var(--fs-xl); font-weight: 600; line-height: 1.2; }
.rm-start__meta { font-size: var(--fs-sm); opacity: .85; }
.rm-start__go { position: absolute; right: var(--sp-3); top: 50%; transform: translateY(-50%); display: inline-flex; }
.rm-start__go .icon { width: 28px; height: 28px; }

.rm-moved { margin: var(--sp-2) 0 0; padding: var(--sp-2) var(--sp-3); border-left: 3px solid var(--warning-600); background: var(--warning-100); border-radius: 0 var(--r-sm) var(--r-sm) 0; font-size: var(--fs-sm); }

.rm-h3 { margin: var(--sp-5) 0 var(--sp-2); font-size: var(--fs-lg); }
.rm-tracks { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); }
.rm-track { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); }
.rm-track.is-start { border: 2px solid var(--accent); }
.rm-track.is-skim { background: var(--surface-alt); }
.rm-track__head, .rm-track summary {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px var(--sp-1);
  padding: 12px var(--sp-2); min-height: 44px;
}
.rm-track summary { cursor: pointer; list-style: none; }
.rm-track summary::-webkit-details-marker { display: none; }
.rm-track summary:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--r-md); }
.rm-track__name { font-family: var(--font-head); font-weight: 600; margin-right: auto; }
.rm-track.is-skim .rm-track__name { color: var(--text-muted); }
.rm-track__n { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-muted); }
.rm-chip { font-family: var(--font-mono); font-size: var(--fs-xs); padding: 2px 10px; border-radius: var(--r-pill); white-space: nowrap; border: 1px solid transparent; }
.rm-chip.is-start { background: var(--accent); color: var(--on-accent); }
.rm-chip.is-skim { border-color: var(--line-strong); color: var(--text-muted); }
.rm-chip.is-path { background: var(--accent-soft); color: var(--accent); }
.rm-chip.is-review { background: var(--warning-100); color: var(--warning-600); }
.rm-lessons { list-style: none; margin: 0; padding: 0 var(--sp-2) var(--sp-2); display: flex; flex-wrap: wrap; gap: 6px; }
.rm-lessons a {
  display: inline-flex; align-items: center; min-height: 44px; padding: 4px 14px;
  border: 1px solid var(--border); border-radius: var(--r-pill);
  font-size: var(--fs-sm); text-decoration: none; background: var(--surface);
}
.rm-lessons a:hover { border-color: var(--accent-decor); }

.rm-extras { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.rm-extra { display: flex; flex-direction: column; gap: 4px; height: 100%; padding: var(--sp-2); border: 1px solid var(--border); border-radius: var(--r-md); text-decoration: none; background: var(--surface-alt); }
.rm-extra:hover { border-color: var(--accent-decor); }
.rm-extra strong { color: var(--text); font-family: var(--font-head); }
.rm-extra span { color: var(--text-muted); font-size: var(--fs-sm); }

.rm-actions { margin-top: var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--border); }
.rm-actions .btn { min-height: 44px; }
.rm-fine { margin: var(--sp-2) 0 0; font-size: var(--fs-xs); color: var(--text-muted); }

.rm-guide h2 { font-size: var(--fs-xl); }
.rm-guide > p { margin: var(--sp-1) 0 var(--sp-3); color: var(--text-muted); }

@media (prefers-reduced-motion: reduce) {
  .rm-option, .rm-progress span, .rm-start { transition: none; }
  .rm-option:active { transform: none; }
}
@media (max-width: 560px) {
  .rm-card, .rm-result { padding: var(--sp-3) var(--sp-2); }
  .rm-option { padding: var(--sp-2); }
  .rm-next { width: 100%; }
  .rm-start { padding-right: var(--sp-6); }
}

/* ============================================================================
   News slideshow + paginated hub (v80, 2026-09-20)

   The hub used to print every story on one page, which grew without limit as
   the archive did. It now shows a slideshow of the newest stories and a fixed
   number of rows beneath, and anything past that moves to page 2, so the page
   has a ceiling no matter how much is published.

   The track is a scroll-snap strip: it works with no JavaScript, swipes
   natively on a phone, and js/news-slider.js only adds arrows, dots and
   auto-advance on top.
   ========================================================================= */
.news-slider { position: relative; margin-top: var(--sp-4); }

.news-slider__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
  gap: var(--sp-3);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* No scroll-behavior here on purpose: js/news-slider.js passes `behavior` to
     scrollTo() itself: smooth normally, instant when the reader prefers
     reduced motion, so setting it here as well would be a second opinion on
     the same question, and the reduced-motion case would need undoing again. */
  /* The scrollbar is noise here: the dots and arrows say where you are. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.news-slider__track::-webkit-scrollbar { display: none; }

.news-slide {
  scroll-snap-align: start;
  position: relative;
  display: grid;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  min-width: 0;
}
@media (min-width: 900px) {
  .news-slide { grid-template-columns: 1.15fr 1fr; align-items: stretch; }
}
.news-slide__cover { background: var(--paper-soft); display: grid; place-items: center; min-height: 190px; }
.news-slide__cover svg { width: 100%; height: 100%; display: block; }
.news-slide__body { padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); justify-content: center; }
.news-slide__body h2 { font-size: var(--fs-xl); }
@media (min-width: 900px) {
  .news-slide__body { padding: var(--sp-5); }
  .news-slide__body h2 { font-size: var(--fs-2xl); }
}

/* Controls sit under the track on a phone, where an arrow overlapping the
   headline would cover the thing it is pointing at. */
.news-slider__controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}
.news-slider__arrows { display: flex; gap: var(--sp-1); }
.news-slider__btn {
  display: inline-grid;
  place-items: center;
  width: 44px;                     /* the site's touch floor */
  height: 44px;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--text);
  transition: background-color var(--t-fast), border-color var(--t-fast), opacity var(--t-fast);
}
.news-slider__btn:hover:not(:disabled) { background: var(--paper-soft); border-color: var(--ink-900); }
.news-slider__btn:disabled { opacity: .35; cursor: default; }

.news-slider__dots { display: flex; gap: 6px; align-items: center; }
/* The BUTTON is the 44px touch target; the 4px bar is drawn inside it. Done
   the other way round (a 4px button with a padded ::after) the visual works
   but every tap-target audit, and this site's own 44px floor, sees 4px. */
.news-slider__dot {
  display: grid;
  place-items: center;
  width: 28px;
  height: 44px;
  border: 0;
  padding: 0;
  background: none;
}
.news-slider__dot::before {
  content: "";
  display: block;
  width: 28px;
  height: 4px;
  border-radius: 999px;
  background: var(--line-strong);
  transition: background-color var(--t-fast);
}
.news-slider__dot[aria-current="true"]::before { background: var(--accent); }

/* ---- pager ---------------------------------------------------------------- */
.news-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-top: var(--sp-6);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
.news-pager__pages { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; }
.news-pager__page {
  min-width: 44px;
  height: 44px;
  display: inline-grid;
  place-items: center;
  padding-inline: var(--sp-2);
  border-radius: 10px;
  border: 1px solid var(--border);
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.news-pager__page:hover { text-decoration: none; border-color: var(--line-strong); background: var(--paper-soft); }
.news-pager__page[aria-current="page"] { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.news-pager__side { display: flex; gap: var(--sp-1); }
.news-pager__side .btn--sm { white-space: nowrap; }

@media (max-width: 560px) {
  .news-pager { flex-direction: column; align-items: stretch; gap: var(--sp-3); }
  .news-pager__side { justify-content: space-between; }
}

/* The hub's cards clamp their summary to three lines. Left unclamped, one long
   lead sets the height of its whole row and the page grows by a couple of
   hundred pixels for prose nobody reads on an index: the full lead is one
   click away on the story itself. Scoped to the hub so the home page, which
   shows three hand-picked stories, still prints its summaries in full. */
.news-grid--hub .news-card__excerpt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

/* TCP lab, 2026-10-22: the player builds each segment (js/labs-observe.js). */
.tcp-levels { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-1) var(--sp-2); margin-bottom: 6px; }
.tcp-levels__btns { display: flex; flex-wrap: wrap; gap: 6px; }
.tcp-level {
  border: 1px solid var(--line-strong); background: transparent; color: var(--text);
  border-radius: var(--r-pill); padding: 7px 14px; min-height: 40px;
  font-family: var(--font-body); font-weight: 600; font-size: var(--fs-sm); cursor: pointer;
}
.tcp-level b { font-family: var(--font-mono); color: var(--accent); margin-right: 4px; }
.tcp-level:hover { border-color: var(--accent); }
.tcp-level:focus-visible, .tcp-flag:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.tcp-level[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.tcp-level[aria-pressed="true"] b { color: inherit; }
.tcp-line { margin: 0 0 var(--sp-3); font-size: var(--fs-sm); color: var(--text-muted); }
.hs-msg.is-lost .hs-arrow { background: var(--danger-600); opacity: 0.55; background-image: none; }
.hs-msg.is-lost .hs-msg__seg { border-color: var(--danger-600); color: var(--danger-600); text-decoration: line-through; }
.tcp-build {
  margin-top: var(--sp-3); padding: var(--sp-3);
  border: 1px solid var(--accent); border-radius: var(--r-md); background: var(--surface-alt);
  display: grid; gap: var(--sp-2);
}
.tcp-build__head { max-width: none; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6px 12px; margin: 0; font-family: var(--font-mono); font-size: var(--fs-xs); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); }
.tcp-dir { color: var(--accent); }
.tcp-build__prompt { margin: 0; font-weight: 500; }
.tcp-flags { display: flex; flex-wrap: wrap; gap: 6px; }
.tcp-flag {
  min-width: 64px; min-height: 40px; padding: 6px 12px; cursor: pointer;
  border: 1px solid var(--line-strong); border-radius: var(--r-sm); background: var(--code-bg); color: var(--text-muted);
  font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: 500;
}
.tcp-flag[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.tcp-nums { display: flex; flex-wrap: wrap; align-items: end; gap: var(--sp-2); }
.tcp-nums label { display: grid; gap: 4px; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-muted); }
.tcp-nums input {
  width: 11rem; max-width: 100%; min-height: 40px; padding: 6px 10px;
  border: 1px solid var(--line-strong); border-radius: var(--r-sm); background: var(--code-bg); color: var(--code-text);
  font-family: var(--font-mono); font-size: var(--fs-sm);
}
.tcp-nums input:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
.tcp-nums input.is-bad { border-color: var(--danger-600); }
.tcp-len { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--text-muted); padding-bottom: 10px; }
.tcp-build .lab-actions { margin-top: 0; }
.tcp-feedback:empty { display: none; }
.tcp-feedback ul { margin: 0; padding: 10px 14px 10px 28px; border-radius: var(--r-sm); background: var(--danger-100); color: var(--text); font-size: var(--fs-sm); display: grid; gap: 6px; }
