/* ═══════════════════════════════════════════════════════════════════════════
   site-system.css — the layout system. Loaded on every page, after
   site-chrome.css and before the page's own inline <style>.

   WHY THIS FILE EXISTS. site-chrome.css lifted the rules that were duplicated
   across the pages; it is a de-duplication layer, not a design. The design
   lived in each page's inline <style> and had drifted into one shape repeated
   eight times a page and sixty times a site:

     eyebrow (numbered)  ->  64-96px condensed all-caps slab whose second half
     turns orange  ->  one paragraph  ->  three bordered cards with a centred
     icon  ->  the next section, at the same width, with the same padding.

   Nothing there is wrong on its own. Repeated without variance it is the
   signature of a generated page, and that is what a visitor reads first.

   WHAT THIS FILE IS. The temperament is an instrument, not a brochure: the
   page should look like the thing the product produces. Rules instead of
   card borders, data instead of decoration, a type scale with a real top and
   a real bottom, and section shapes that differ from each other on purpose.

   THE FOUR RATIONS. Each of these was unlimited before, which is why the page
   read as uniform. They are budgets now, and the budget is the design:

     1. ORANGE. Revised 9 Sep 2026, after the first pass cut it too far. The
        original rule -- one editorial use per page -- produced a page that
        was technically correct and visually grey: the brand's only colour
        appeared once, on a figure, and the rest read as a wireframe of the
        site rather than the site.

        The rule now names WHAT orange marks, rather than how often:

          - anything you can act on: primary CTAs, links, hovered controls
          - the labels that structure the page: eyebrows, meta, status
          - the figures the page is arguing with: prices, measurements, stats
          - the leaders and ticks in a list of what you get

        What it still never marks is a HEADING. "Half the sentence turns
        orange" was the strongest generated-page tell on this site and it
        fired on every section of every page; that is the one thing from the
        old design that does not come back. A heading may contain one marked
        word (`.ms-mark`) where that word is genuinely the point.
     2. DISPLAY TYPE. `.ms-display` appears once per page, in the hero.
        Section heads are `.ms-h2` and are a third of its size. If everything
        is 80px then nothing is.
     3. THE DRAFTING MOTIF. Dot grid, corner ticks and crosshairs are a
        signal, and a signal used everywhere is wallpaper. Grid in the hero;
        ticks on the one framed product surface; nothing else.
     4. MOTION. One 220ms settle when a band first enters. No per-child
        stagger, no word-by-word reveal, no parallax. An instrument does not
        animate itself in.

   NAMING. Everything here is prefixed `ms-` and is new, so nothing in this
   file can collide with a page's inline <style> or lose a specificity tie to
   it. Converting a page means writing `ms-` markup AND deleting the inline
   rules it replaces -- leaving the old rules behind is what would produce a
   half-converted page that looks worse than either design.

   COLOUR. Every value below resolves to a token already in
   tailwind.config.js. Do not introduce a surface here: the ladder is derived
   (a constant +4.39 L* per rung) and the text contrasts are measured against
   it, `ash` on `steel` at 4.60:1 being the one with no headroom left.
   ═════════════════════════════════════════════════════════════════════════ */

:root {
  /* Surfaces — the tailwind ladder, restated so this file is readable on its
     own and so a band can be re-toned in one place. */
  --ms-ink: #181818;
  --ms-ground: #1c1c1c;
  --ms-raised: #232323;
  --ms-hover: #2c2c2c;
  --ms-rule: #3c3c3c;
  --ms-rule-soft: #2e2e2e;

  /* TEXT TONES ARE NOT FREE TO INVENT. tailwind.config.js measured every one
     of these against the surface it actually sits on, and `graphite` (#696969)
     is a DISABLED-GLYPH tone: roughly 3.2:1 on the page ground, under the 4.5
     floor and under even the 3.0 large-text floor on `steel`. The first draft
     of this file used it for eyebrows, meta and every `.ms-note`, which put a
     good part of the page's small print below AA on a phone in daylight —
     which is exactly where superintendents read this site.

     So there are two muted tones here and no third: `fog` for copy, `ash` for
     labels and meta. Anything quieter than ash is a rule, not text. */
  --ms-text: #f1f1f1;      /* chalk */
  --ms-muted: #acacac;     /* fog  — body copy */
  --ms-dim: #949494;       /* ash  — labels, meta, notes, eyebrows */
  --ms-accent: #f97316;      /* safety orange — the brand's only colour */
  --ms-hover-accent: #fb923c;

  /* Rhythm. Bands vary by ROLE, not by whim: a dense band of records is
     tighter than a band that exists to give the eye somewhere to rest. */
  --ms-band-y: 5.5rem;
  --ms-band-y-tight: 3.25rem;
  --ms-band-y-loose: 8rem;
  --ms-gutter: 1.5rem;
  --ms-measure: 34rem;      /* body copy never runs wider than this */
}

/* The phone gets its own rhythm, and it is tighter than a scaled-down desktop
   one. A band's padding is read against the viewport it sits in: 3.5rem above
   and below a section is a comfortable beat on a 900px-tall desktop window and
   a third of a phone screen, so the same value that paces the page on a laptop
   turns it into a corridor on a phone. Fourteen bands at the old values spent
   1,472px — 1.7 phone screens — on vertical air alone.

   Only the tokens move; no band, gap or type scale is special-cased, so the
   proportions the system was designed with survive and there is nothing extra
   to keep in sync. */
@media (max-width: 767px) {
  :root {
    --ms-band-y: 2.5rem;
    --ms-band-y-tight: 1.5rem;
    --ms-band-y-loose: 3rem;
    --ms-gutter: 1.25rem;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   1. BANDS — the horizontal slices of a page.

   The old page had exactly one band shape. These are four, and a converted
   page is expected to alternate them. Two `ms-band` in a row is allowed; four
   is the metronome coming back.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-band {
  position: relative;
  padding-top: var(--ms-band-y);
  padding-bottom: var(--ms-band-y);
}

/* The hairline that separates bands. It is a top border, never a bottom one,
   so a band can be removed without leaving a doubled rule behind it. */
.ms-band--ruled { border-top: 1px solid var(--ms-rule-soft); }

.ms-band--tight { padding-top: var(--ms-band-y-tight); padding-bottom: var(--ms-band-y-tight); }
.ms-band--loose { padding-top: var(--ms-band-y-loose); padding-bottom: var(--ms-band-y-loose); }

.ms-band--raised { background: var(--ms-raised); }
.ms-band--sunk   { background: var(--ms-ink); }

/* Full-bleed: the band's own content runs to the viewport edge. Used for a
   product surface that should read as the instrument itself rather than as a
   picture of one inside a card. */
.ms-band--bleed { padding-left: 0; padding-right: 0; }

.ms-wrap {
  width: 100%;
  max-width: 78rem;
  margin: 0 auto;
  padding-left: var(--ms-gutter);
  padding-right: var(--ms-gutter);
}

/* A deliberately narrow column. The "quiet" band — one idea, lots of air,
   nothing to compare. A page with no quiet band reads relentless.
   
   The BOX stays the same width as every other band and only the CONTENT is
   capped, so the narrow band's left edge lands on the same vertical as the
   wide ones. Centring it instead (max-width on the wrapper plus auto margins)
   put one section's text on a different axis from the rest and made the page
   look like two designs — the exact thing this is here to stop. */
.ms-wrap--narrow > * { max-width: 48rem; }
.ms-wrap--wide   { max-width: 90rem; }

/* ── The asymmetric split ─────────────────────────────────────────────────
   Left column states the section and stays put; right column carries the
   evidence and scrolls past it. This is the shape that most breaks the
   metronome, because the eye stops re-finding the heading in the same place. */
.ms-split {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
  gap: 3.5rem;
  align-items: start;
}

.ms-split__aside { position: sticky; top: 6.5rem; }

@media (max-width: 1023px) {
  .ms-split { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
  .ms-split__aside { position: static; }
}

/* ══════════════════════════════════════════════════════════════════════════
   2. TYPE — a scale with a real top and a real bottom.

   The site set every section head in the condensed display face at 64-96px
   uppercase. That is one size, used for the page's single most important
   sentence and for the seventh sub-point alike, so it carried no hierarchy at
   all. Here the display face appears once; section heads drop to the body
   face at roughly a third of the size, which is what makes the hero read as
   the hero.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-display {
  font-family: 'IBM Plex Sans Condensed', 'IBM Plex Sans', 'Arial Narrow', sans-serif;
  font-weight: 700;
  font-size: clamp(2.4rem, 4.4vw, 3.6rem);
  line-height: 0.98;
  letter-spacing: -0.022em;
  color: var(--ms-text);
  text-wrap: balance;
  margin: 0;
}

/* Sentence case, not caps. An all-caps slab is legible at a glance and
   illegible as a sentence, which is why every one of them had to be two short
   lines — and why they all sounded the same. */
.ms-h2 {
  font-family: 'IBM Plex Sans', -apple-system, 'Segoe UI', sans-serif;
  font-weight: 600;
  font-size: clamp(1.4rem, 2.1vw, 1.9rem);
  line-height: 1.18;
  letter-spacing: -0.014em;
  color: var(--ms-text);
  text-wrap: balance;
  margin: 0;
}

.ms-h3 {
  font-family: 'IBM Plex Sans', -apple-system, 'Segoe UI', sans-serif;
  font-weight: 600;
  font-size: 1.0625rem;
  line-height: 1.35;
  letter-spacing: -0.006em;
  color: var(--ms-text);
  margin: 0;
}

/* The eyebrow. No number: numbering your own sections is an outline showing
   through the page. No orange either — it is a label, not a thing to click. */
.ms-eyebrow {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--ms-accent);
  margin: 0 0 1.15rem;
  display: block;
}

.ms-lede {
  font-size: 1.0625rem;
  line-height: 1.66;
  color: var(--ms-muted);
  max-width: var(--ms-measure);
  margin: 1.1rem 0 0;
}

.ms-body {
  font-size: 0.9375rem;
  line-height: 1.68;
  color: var(--ms-muted);
  max-width: var(--ms-measure);
  margin: 0;
}

.ms-body + .ms-body { margin-top: 1rem; }

.ms-note {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.75rem;
  line-height: 1.6;
  color: var(--ms-dim);
  margin: 0;
}

/* The one editorial accent per page. Named so its uses are greppable and the
   ration is enforceable rather than aspirational. */
.ms-mark { color: var(--ms-accent); }

.ms-strong { color: var(--ms-text); font-weight: 500; }

/* ══════════════════════════════════════════════════════════════════════════
   3. RECORDS — the shape that replaces the three-up card grid.

   A card is a box drawn around content to make it look like something. A
   record is a row in a table the product could have printed. The site sells
   software that turns drawings into rows; the page should be able to show a
   row without wrapping it in a rounded rectangle first.

   Every `.ms-rec` carries a top hairline and nothing else — no side borders,
   no bottom border, no radius, no shadow. The list reads as one object.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-recs { border-top: 1px solid var(--ms-rule); }

.ms-rec {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr) auto;
  gap: 1.5rem 2.5rem;
  align-items: baseline;
  padding: 1.4rem 0.5rem 1.4rem 0;
  border-bottom: 1px solid var(--ms-rule-soft);
  transition: background-color .16s ease;
}

/* The hover fill bleeds past the text so the row reads as a target, without
   the row acquiring an outline it does not have at rest. */
.ms-rec:hover { background: rgba(255, 255, 255, 0.022); }

.ms-rec__key {
  font-family: 'IBM Plex Sans', -apple-system, sans-serif;
  font-weight: 600;
  font-size: 1rem;
  color: var(--ms-text);
  letter-spacing: -0.008em;
}

.ms-rec__val {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--ms-muted);
}

.ms-rec__meta {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.75rem;
  color: var(--ms-dim);
  white-space: nowrap;
  letter-spacing: 0.02em;
}

@media (max-width: 767px) {
  .ms-rec {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.4rem;
    padding: 1.1rem 0;
  }
  .ms-rec__meta { white-space: normal; }
}

/* ── Figures set as data, not as a marketing stat row ────────────────────── */
.ms-figs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  border-top: 1px solid var(--ms-rule);
}

.ms-fig {
  padding: 1.5rem 1.5rem 1.5rem 0;
  border-bottom: 1px solid var(--ms-rule-soft);
}

.ms-fig__n {
  font-family: 'IBM Plex Sans Condensed', 'IBM Plex Sans', sans-serif;
  font-weight: 700;
  font-size: 2.1rem;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ms-text);
  display: block;
}

.ms-fig__l {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.7rem;
  line-height: 1.5;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ms-accent);
  margin-top: 0.55rem;
  display: block;
}

/* ══════════════════════════════════════════════════════════════════════════
   4. PANELS — used sparingly, where a thing genuinely is a separate object.

   The old `.tool-card` gave every panel a 1px box, a 4px radius and two
   corner registration ticks. A panel here is a flat raised surface with a
   hairline and no radius; the ticks are a separate opt-in, for the one
   surface per page that is meant to read as a drawing sheet.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-panel {
  background: var(--ms-raised);
  border: 1px solid var(--ms-rule-soft);
  border-radius: 0;
  padding: 1.6rem;
}

.ms-panel--flush { padding: 0; overflow: hidden; }

/* Registration ticks. One surface per page. */
.ms-ticked { position: relative; }
.ms-ticked::before,
.ms-ticked::after {
  content: "";
  position: absolute;
  width: 10px;
  height: 10px;
  border-color: var(--ms-rule);
  border-style: solid;
  pointer-events: none;
}
.ms-ticked::before { top: -1px; left: -1px; border-width: 1px 0 0 1px; }
.ms-ticked::after  { bottom: -1px; right: -1px; border-width: 0 1px 1px 0; }

/* A product screenshot, framed as an instrument readout rather than a photo:
   recessed ground, hairline, no radius, no drop shadow. */
.ms-screen {
  background: var(--ms-ink);
  border: 1px solid var(--ms-rule-soft);
  overflow: hidden;
  line-height: 0;
}
.ms-screen img,
.ms-screen video { display: block; width: 100%; height: auto; }

.ms-screen__cap {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--ms-dim);
  padding: 0.7rem 0 0;
  line-height: 1.5;
}

/* ══════════════════════════════════════════════════════════════════════════
   5. CONTROLS
   ═════════════════════════════════════════════════════════════════════════ */

.ms-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-family: 'IBM Plex Sans', -apple-system, sans-serif;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.004em;
  padding: 0.72rem 1.35rem;
  border: 1px solid transparent;
  border-radius: 2px;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}

/* Flat orange. The old CTA was a 135deg gradient with a sweeping shine on
   hover; a gradient is what a button looks like when it is trying to look
   like a button. */
.ms-btn--primary { background: var(--ms-accent); color: #131313; }
.ms-btn--primary:hover { background: #fb923c; }

.ms-btn--ghost {
  background: transparent;
  color: var(--ms-text);
  border-color: var(--ms-rule);
}
.ms-btn--ghost:hover { border-color: var(--ms-dim); background: rgba(255, 255, 255, 0.03); }

/* The quietest call to action there is: a text link with a rule under it that
   only completes on hover. */
.ms-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ms-accent);
  text-decoration: none;
  border-bottom: 1px solid rgba(249, 115, 22, .35);
  padding-bottom: 2px;
  transition: color .15s ease, border-color .15s ease;
}
.ms-link:hover { color: var(--ms-hover-accent); border-color: var(--ms-accent); }

/* A button that also carries Tailwind's `hidden` must stay hidden. This file
   loads after Tailwind, so `.ms-btn { display:inline-flex }` would otherwise
   out-rank `hidden` and show a desktop-only button on a phone. The same
   trap as the pricing toggle (§30); this closes it for every button. */
.ms-btn.hidden { display: none; }
@media (min-width: 1024px) {
  .ms-btn.hidden.lg\:inline-flex { display: inline-flex; }
}

.ms-links {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem 2.25rem;
}
/* One line that scrolls sideways instead of wrapping into a tall block. */
.ms-links--scroll {
  flex-wrap: nowrap;
  overflow-x: auto;
  white-space: nowrap;
  scrollbar-width: none;
  gap: 1.5rem;
}
.ms-links--scroll::-webkit-scrollbar { display: none; }
/* Links styled as notes (`.ms-link.ms-note`) picked up `* + .ms-note`'s 1.35rem
   top margin, which is for prose — in a flex row it dropped every link after
   the first half a line below it (the FAQ's category links). */
.ms-links > .ms-note { margin-top: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   6. MOTION — one gesture, and it cannot hide anything.

   panel-polish.js used to add a reveal to every top-level <section> on every
   page and cascade each grid child at 55ms intervals — the same staggered
   fade eight times a page, sitewide. That is the motion vocabulary of a
   template, and it is switched off for converted pages (see `ms-scope` in
   panel-polish.js). This is what replaces it.

   NOTHING HERE ANIMATES OPACITY, AND THAT IS THE WHOLE DESIGN.

   The first version faded from `opacity: 0`, with the resting state gated on
   an `html.ms-motion` class that the script only set once it had an observer,
   plus a timer that revealed everything if the visitor had not scrolled. It
   was tested by scrolling a real browser, and the test showed the timer
   winning every time: a reader who looks at the hero for three seconds — or
   any crawler — got the whole page revealed with no animation at all. So the
   effect was mostly dead code, and the part that was not dead was the part
   that could hide sixty bands behind a script.

   Animating only `transform` removes the failure mode instead of guarding it.
   If the observer never fires, if the script is blocked, if the page is
   prerendered or read by a crawler, every band is fully painted and merely
   sits eight pixels low — which nobody can see and nobody can be harmed by.
   That is the lesson of the count-up scanner this codebase already removed
   once, applied before it costs anything.

   Do not add an opacity to these rules.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-enter {
  transform: translateY(8px);
  transition: transform .32s cubic-bezier(.22, .61, .36, 1);
  will-change: transform;
}

.ms-enter.ms-in { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .ms-enter { transform: none; transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   7. RESTRAINT — turning the old polish kit down.

   LOAD ORDER, stated correctly because this repo documents it religiously and
   an earlier draft of this note had it backwards. This file loads at the
   site-chrome.css slot: after src/tailwind.css, BEFORE the page's own inline
   <style>, and therefore before panel-polish.css, which is linked after the
   inline block and is the last stylesheet on the page.

   So these rules do NOT win on source order. They win on specificity —
   `.ms-scope .pnl-lift:hover` is (0,3,0) against panel-polish's (0,2,0) — and
   they are a belt to the braces of panel-polish.js, which already returns
   early on a page whose <body> carries `ms-scope`. Both exist because the two
   fire at different times: the script decides what gets tagged at DOMContent-
   Loaded, and these catch anything tagged before the class was read.

   A page that has not been converted carries no `ms-scope`, so nothing below
   can reach it.
   ═════════════════════════════════════════════════════════════════════════ */

/* A converted band never lifts, glows, or shines, whatever the runtime tagged
   it with. The instrument does not move when you look at it. */
.ms-scope .pnl-lift:hover,
.ms-scope .pnl-mag:hover { transform: none; box-shadow: none; }

.ms-scope .pnl-cta::after { display: none; }

/* The runtime cascade sets an inline transition-delay on grid children. Inside
   converted markup there is no cascade to delay. */
.ms-scope .pnl-cascade { transition-delay: 0s !important; }

/* ══════════════════════════════════════════════════════════════════════════
   8. THE DRAFTING GRID — REMOVED 9 Sep 2026.
   ═════════════════════════════════════════════════════════════════════════ */

/* The 72px box grid that sat behind every hero is gone, by decision, sitewide.
   It was one of the system's four rations and the hero's only decorative
   motif; the heroes now carry their weight with type and real drawings
   instead.

   THE CLASS IS DEPRECATED BUT NOT INERT. `.ms-grid-field` is still on 61
   pages and still establishes the positioning context those heroes' absolutely
   positioned children are laid out against — dropping `position: relative`
   here would throw every one of them to the page corner, which is the exact
   failure `adopt-system.mjs` already shipped once. So the paint goes and the
   containing block stays. Strip the class from the markup only after checking
   that band for absolutely positioned descendants.

   `isolation: isolate` went with the ::before — it existed to keep the grid's
   negative z-index inside the band, and there is no longer a negative
   z-index to contain.

   The old ::before carried the fade mask, so the mask left with it. The
   comment that used to sit here recorded why the mask could never move onto
   the <section> itself: a mask applies to an element's whole rendered
   content, so it faded the COPY along with the grid and the bottom third of
   every hero rendered progressively transparent. Keep that in mind for §3's
   full-bleed drawing — it is the same trap, and the drawing needs its own
   masked layer. */
.ms-grid-field {
  position: relative;
}

/* ══════════════════════════════════════════════════════════════════════════
   9. THE PROGRAM LADDER — a record per product.

   Six products used to be six `.tool-card`s: a bordered box each, with a
   stage chip, a name in 28px caps, a price, a hook, a screenshot, a bullet
   list and two buttons, repeated to a grid. Read down the page they were
   indistinguishable, which is the opposite of what a product list is for.

   As records they line up on the same three columns, so the eye compares
   price against price and scope against scope instead of re-reading six
   copies of the same card.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-prog {
  display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr) minmax(0, 11rem);
  gap: 1.25rem 2.5rem;
  padding: 1.75rem 0;
  border-bottom: 1px solid var(--ms-rule-soft);
  align-items: start;
}

.ms-prog__stage {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.65rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ms-accent);
  display: block;
  margin-bottom: 0.5rem;
}

.ms-prog__name {
  font-family: 'IBM Plex Sans', -apple-system, sans-serif;
  font-weight: 600;
  font-size: 1.125rem;
  letter-spacing: -0.012em;
  color: var(--ms-text);
  display: block;
}

.ms-prog__price {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.8125rem;
  color: var(--ms-dim);
  display: block;
  margin-top: 0.4rem;
}
.ms-prog__price b { color: var(--ms-accent); font-weight: 600; }

.ms-prog__hook {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--ms-text);
  margin: 0 0 0.85rem;
}

/* Capabilities as a hanging list with a rule for a bullet — the dimension
   leader off a drawing, not a disc. */
.ms-prog__caps { list-style: none; margin: 0; padding: 0; }
.ms-prog__caps li {
  position: relative;
  padding-left: 1.15rem;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--ms-muted);
  margin-bottom: 0.4rem;
}
.ms-prog__caps li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 0.6rem;
  height: 1px;
  background: var(--ms-dim);
}

.ms-prog__replaces {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.7rem;
  line-height: 1.55;
  color: var(--ms-dim);
  margin: 0.9rem 0 0;
  padding-top: 0.7rem;
  border-top: 1px dotted var(--ms-rule);
}

.ms-prog__act { display: flex; flex-direction: column; gap: 0.6rem; align-items: flex-start; }

@media (max-width: 1023px) {
  .ms-prog { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
  .ms-prog__act { flex-direction: row; flex-wrap: wrap; gap: 1.25rem; }
}

/* ── The programs list on a phone ─────────────────────────────────────────
   Six programs, each with a hook, five capability lines and a "Replaces"
   note, is the densest thing on the homepage: 3,570px, a fifth of the whole
   page, and thirty bullets in a row before the reader reaches anything else.

   The capability lines are the part that compresses without losing the
   argument. The first two carry the specific, checkable claims a skim needs
   ("catches a sleeve that never made it into the structural slab"); the rest
   are corroboration, and corroboration is what a disclosure is for. The
   collapse is applied by site-system.js rather than by CSS, because CSS can
   open a <details> but cannot close one — which means the no-JS path leaves
   every line visible. Failure adds content here; it never subtracts it. */
@media (max-width: 767px) {
  .ms-prog { padding-top: 1.25rem; padding-bottom: 1.25rem; }
  .ms-prog__hook { margin-bottom: 0.7rem; }

  .ms-prog__more { margin-top: 0.35rem; }
  .ms-prog__more > summary {
    display: inline-flex; align-items: center; gap: 0.35rem;
    font-family: var(--ms-mono, ui-monospace, monospace);
    font-size: 0.72rem; letter-spacing: 0.04em; text-transform: uppercase;
    color: var(--ms-accent); cursor: pointer; list-style: none;
    padding: 0.45rem 0;            /* a 44px-ish tap target, not a 12px word */
  }
  .ms-prog__more > summary::-webkit-details-marker { display: none; }
  .ms-prog__more > summary::after { content: "+"; font-size: 0.9rem; line-height: 1; }
  .ms-prog__more[open] > summary::after { content: "\2212"; }
  .ms-prog__more[open] > summary { margin-bottom: 0.2rem; }
  /* The disclosed lines are the same list, so they inherit .ms-prog__caps
     styling and the dash markers line up with the two above them. */
  .ms-prog__more .ms-prog__caps { margin: 0; }

  /* Prose leading, read against a phone column rather than a desktop one.
     1.68 is generous at a 640px measure and cavernous at 350px, where a line
     holds eight words instead of sixteen — the same ratio produces twice the
     gaps for the same paragraph. 1.5 keeps the paragraph readable and gives
     back roughly a line in every six.

     Nothing is hidden by this and no size changes; only the space between
     lines, which is the one part of the type scale that is measured against
     line length rather than against the other sizes. */
  .ms-body { line-height: 1.5; }
  .ms-lede { line-height: 1.4; }
  .ms-note { line-height: 1.45; }
  .ms-body + .ms-body { margin-top: 0.75rem; }
  * + .ms-note { margin-top: 1rem; }
}

/* ══════════════════════════════════════════════════════════════════════════
   10. THE FILMSTRIP — every product surface in one full-bleed row.

   The five screenshots used to sit one per card, each cropped into its own
   box. Side by side at the page's full width they do the job the cards were
   pretending to: they show that these are eight real programs, and they let
   the surfaces be compared.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-strip {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(19rem, 1fr);
  gap: 1px;
  background: var(--ms-rule-soft);
  border-top: 1px solid var(--ms-rule-soft);
  border-bottom: 1px solid var(--ms-rule-soft);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}

.ms-strip__cell {
  background: var(--ms-ink);
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
}

.ms-strip__cell > img,
.ms-strip__cell > video,
.ms-strip__cell > a > img,
.ms-strip__cell > a > video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: top left;
}

.ms-strip__cap {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  color: var(--ms-dim);
  padding: 0.7rem 0.9rem 0.85rem;
  border-top: 1px solid var(--ms-rule-soft);
  background: var(--ms-ground);
}

/* ══════════════════════════════════════════════════════════════════════════
   11. DATA TABLE — the comparison, set as a table rather than as a graphic.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }

.ms-table th {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ms-dim);
  text-align: left;
  padding: 0 1rem 0.7rem 0;
  border-bottom: 1px solid var(--ms-rule);
}

.ms-table td {
  padding: 0.85rem 1rem 0.85rem 0;
  border-bottom: 1px solid var(--ms-rule-soft);
  color: var(--ms-muted);
  vertical-align: top;
}

.ms-table td:first-child { color: var(--ms-dim); }
.ms-table .ms-td-ours { color: var(--ms-accent); font-weight: 500; }
.ms-table tr:last-child td { border-bottom: none; }

/* The one row that is the argument. A single left rule, not a filled band. */
.ms-table tr.ms-tr-total td {
  border-top: 1px solid var(--ms-rule);
  padding-top: 1.05rem;
  color: var(--ms-text);
  font-weight: 600;
}

/* ══════════════════════════════════════════════════════════════════════════
   12. THE PLAN LADDER
   ═════════════════════════════════════════════════════════════════════════ */

.ms-plan {
  display: grid;
  grid-template-columns: minmax(0, 9rem) minmax(0, 7rem) minmax(0, 1fr) auto;
  gap: 1rem 2rem;
  align-items: baseline;
  padding: 1.2rem 0.75rem 1.2rem 0;
  border-bottom: 1px solid var(--ms-rule-soft);
  text-decoration: none;
  transition: background-color .16s ease;
}
.ms-plan:hover { background: rgba(255, 255, 255, 0.022); }

.ms-plan__n {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ms-accent);
}

.ms-plan__p {
  font-family: 'IBM Plex Sans Condensed', 'IBM Plex Sans', sans-serif;
  font-weight: 700;
  font-size: 1.6rem;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ms-text);
}
.ms-plan__p span {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--ms-dim);
  letter-spacing: 0;
}

.ms-plan__d { font-size: 0.8125rem; line-height: 1.55; color: var(--ms-muted); }

.ms-plan__go {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.72rem;
  color: var(--ms-accent);
  white-space: nowrap;
  transition: color .15s ease;
}
.ms-plan:hover .ms-plan__go { color: var(--ms-hover-accent); }

@media (max-width: 767px) {
  .ms-plan { grid-template-columns: minmax(0, 1fr) auto; gap: 0.35rem 1rem; }
  .ms-plan__d { grid-column: 1 / -1; }
  .ms-plan__go { grid-column: 1 / -1; }
}

/* ══════════════════════════════════════════════════════════════════════════
   13. QUOTES — attribution first, no card.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-quotes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 0;
  border-top: 1px solid var(--ms-rule);
}

.ms-quote {
  padding: 1.6rem 1.75rem 1.6rem 0;
  border-bottom: 1px solid var(--ms-rule-soft);
}

.ms-quote blockquote {
  font-size: 0.9375rem;
  line-height: 1.66;
  color: var(--ms-text);
  margin: 0 0 1.1rem;
}

.ms-quote__who {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.7rem;
  line-height: 1.7;
  color: var(--ms-dim);
  letter-spacing: 0.02em;
}
.ms-quote__who b { color: var(--ms-accent); font-weight: 500; display: block; }

/* ══════════════════════════════════════════════════════════════════════════
   14. STEPS — numbered where numbering is the content, not the outline.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-steps { list-style: none; margin: 0; padding: 0; counter-reset: ms-step; }

.ms-steps li {
  position: relative;
  padding: 1.05rem 0 1.05rem 3rem;
  border-bottom: 1px solid var(--ms-rule-soft);
  font-size: 0.875rem;
  line-height: 1.62;
  color: var(--ms-muted);
}
.ms-steps li:first-child { border-top: 1px solid var(--ms-rule-soft); }

.ms-steps li::before {
  counter-increment: ms-step;
  content: counter(ms-step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 1.15rem;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.7rem;
  color: var(--ms-accent);
  letter-spacing: 0.08em;
}

.ms-steps b { color: var(--ms-text); font-weight: 600; }

/* ══════════════════════════════════════════════════════════════════════════
   15. TICKS — the plan-inclusion list.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-ticks { list-style: none; margin: 0; padding: 0; }
.ms-ticks li {
  position: relative;
  padding: 0.42rem 0 0.42rem 1.35rem;
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--ms-muted);
}
/* Orange, after all. The first pass greyed these on the argument that a
   bullet is not an action — true, but it left a plan's inclusion list, the
   one place a buyer reads to see what they get, with no colour in it. A tick
   beside "Blueprint Auditor" marks something you receive, which is close
   enough to something you can act on. */
.ms-ticks li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.95em;
  width: 0.7rem;
  height: 1px;
  background: var(--ms-accent);
}

/* ══════════════════════════════════════════════════════════════════════════
   16. PAGE PARTS — the compositions the homepage is built from.

   These are layouts, not components: each one exists because a particular
   band needed a shape no other band on the page uses. That is the point. A
   page where every band shares one layout is the page this replaced.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── Hero ─────────────────────────────────────────────────────────────────
   Stacked, not two-up. The first draft put the headline in one column and the
   product in the other, which rendered the screenshot at about 480px — a
   thumbnail of an interface, legible as "some software exists" and nothing
   more. A page whose argument is "look at what it reads off your drawings"
   cannot afford an unreadable screenshot, so the sentence takes a measured
   column and the instrument takes the full width underneath it. */
.ms-hero__lead { max-width: 44rem; }

.ms-hero__cta { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-top: 2rem; }
.ms-hero__note { margin-top: 1rem; }

/* The four facts, as a definition list across the full band. It was a mono
   paragraph with two orange words in it; a contractor checking whether we
   publish an accuracy figure should find it laid out, not buried in a
   sentence. */
.ms-hero__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 1.5rem 2.5rem;
  margin: 3rem 0 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--ms-rule-soft);
}
.ms-hero__facts dt {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.65rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ms-dim);
  margin-bottom: 0.45rem;
}
.ms-hero__facts dd {
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--ms-muted);
  margin: 0;
}
.ms-hero__facts dd a {
  color: var(--ms-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(249, 115, 22, .35);
}
.ms-hero__facts dd a:hover { color: var(--ms-hover-accent); }

.ms-hero__shot { margin: 3rem 0 0; }

.ms-hero__prov {
  margin: 2.5rem 0 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--ms-rule-soft);
  font-size: 0.875rem;
  line-height: 1.65;
  color: var(--ms-muted);
  max-width: 52rem;
}
.ms-hero__prov a {
  color: var(--ms-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(249, 115, 22, .35);
}
.ms-hero__prov a:hover { color: var(--ms-hover-accent); }

@media (max-width: 767px) {
  .ms-hero__facts { margin-top: 2rem; }
  .ms-hero__shot { margin-top: 2rem; }
}

/* ── Proof strip ─────────────────────────────────────────────────────────── */
.ms-proof {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 4fr);
  gap: 3rem;
  align-items: center;
}

.ms-proof__q { margin: 0; }
.ms-proof__q p {
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--ms-text);
  margin: 0 0 0.9rem;
}
.ms-proof__q footer {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.72rem;
  line-height: 1.6;
  color: var(--ms-dim);
}

.ms-proof__figs { border-top: none; }
.ms-proof__figs .ms-fig { border-bottom: none; border-top: 1px solid var(--ms-rule); padding-top: 1.1rem; }
.ms-proof__figs .ms-fig dd { margin: 0; }

.ms-proof__terms { margin-top: 2.25rem; padding-top: 1.25rem; border-top: 1px solid var(--ms-rule-soft); }

@media (max-width: 1023px) {
  .ms-proof { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
}

/* ── Section head: title left, one aside right ───────────────────────────── */
.ms-sechead {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem 2.5rem;
}

/* ── The fleet band ──────────────────────────────────────────────────────── */
.ms-fleet {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 20rem);
  gap: 3.5rem;
  align-items: start;
}

.ms-fleet__buy {
  border: 1px solid var(--ms-rule);
  padding: 1.75rem;
  background: var(--ms-ground);
  position: sticky;
  top: 6.5rem;
}

.ms-fleet__price {
  font-family: 'IBM Plex Sans Condensed', 'IBM Plex Sans', sans-serif;
  font-weight: 700;
  font-size: 3rem;
  line-height: 1;
  letter-spacing: -0.025em;
  color: var(--ms-text);
  margin: 0 0 0.6rem;
}
.ms-fleet__price span {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.85rem;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ms-dim);
}

@media (max-width: 1023px) {
  .ms-fleet { grid-template-columns: minmax(0, 1fr); gap: 2.5rem; }
  .ms-fleet__buy { position: static; }
}

/* ── The isolation diagram ───────────────────────────────────────────────── */
.ms-boundary {
  max-width: 26rem;
  padding: 1.5rem;
  border: 1px solid var(--ms-rule-soft);
  background: var(--ms-ground);
}
.ms-boundary svg { width: 100%; height: auto; display: block; }

/* ── Callout: a raised aside inside a band, one per band at most ─────────── */
.ms-callout {
  margin-top: 2.5rem;
  padding: 1.75rem;
  border: 1px solid var(--ms-rule-soft);
  background: var(--ms-raised);
}

/* A record whose key needs a label above it. */
.ms-rec__sub {
  display: block;
  order: -1;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ms-accent);
  margin-bottom: 0.4rem;
}
.ms-rec__key { display: flex; flex-direction: column; }

/* An inline link inside quiet mono copy. Underlined rather than coloured, so
   the accent stays rationed and the link is still obviously a link. */
.ms-note-link {
  color: var(--ms-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(249, 115, 22, .35);
  transition: color .15s ease, text-decoration-color .15s ease;
}
.ms-note-link:hover { color: var(--ms-hover-accent); text-decoration-color: var(--ms-accent); }

.ms-body a:not([class]),
.ms-lede a:not([class]),
.ms-rec__val a:not([class]),
.ms-prog__caps a:not([class]) {
  color: var(--ms-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(249, 115, 22, .35);
  transition: color .15s ease, text-decoration-color .15s ease;
}
.ms-body a:not([class]):hover,
.ms-lede a:not([class]):hover,
.ms-rec__val a:not([class]):hover,
.ms-prog__caps a:not([class]):hover { color: var(--ms-hover-accent); text-decoration-color: var(--ms-accent); }

/* The figure that IS the argument in a table — emphasis without spending the
   page's one editorial orange on it. */
.ms-mark-quiet { color: var(--ms-text); font-weight: 700; }

/* A record used as a link keeps the row shape and loses the anchor default. */
a.ms-rec, a.ms-plan { text-decoration: none; color: inherit; }
a.ms-rec .ms-rec__meta { color: var(--ms-accent); }
a.ms-rec:hover .ms-rec__key { color: var(--ms-accent); }
a.ms-rec:hover .ms-rec__meta { color: var(--ms-hover-accent); }

/* ── Pager ──────────────────────────────────────────────────────────────── */
.ms-pager {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  color: var(--ms-dim);
  background: none;
  border: none;
  border-top: 1px solid var(--ms-rule);
  padding: 0.5rem 0.9rem 0 0;
  margin-right: 0.5rem;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.ms-pager:hover { color: var(--ms-dim); }
.ms-pager.is-on { color: var(--ms-text); border-color: var(--ms-accent); }

/* ══════════════════════════════════════════════════════════════════════════
   17. A PRODUCT SCREENSHOT ON A PHONE

   A 1920px-wide audit screen scaled into a 350px column is not a screenshot,
   it is a texture: the OBC article numbers, the FAIL/WARN chips and the rule
   text — the entire reason the image is on the page — are all under a pixel
   high. Cropping to a region would hide the rest, so the frame becomes a
   scroll container instead and the image keeps a legible intrinsic width.
   The visitor swipes across the readout, which is what they would do with a
   drawing anyway.

   `.ms-hero__shot` was the first user; `.ms-shot` is the same treatment for
   any figure on any page, because the supporting pages now carry cropped
   readouts with exactly the same problem. Measured on a real 390px viewport,
   not a --window-size guess: headless Chrome clamps that flag to a 500px
   viewport, so a phone check written that way silently runs at a width no
   phone has. Use CDP_W with test/ui-harness/cdp.mjs.

   --ms-shot-w IS THE POINT OF THE OPT-IN. 780px suits a full app window. A
   crop is already zoomed in, so it wants its own number: set it to the crop's
   natural width and the image is pixel-exact and sharp, and the reader swipes
   a shorter distance. Set it larger than natural and it upsamples, which is
   worse than shrinking.
   ═════════════════════════════════════════════════════════════════════════ */
@media (max-width: 767px) {
  .ms-hero__shot .ms-screen,
  .ms-shot .ms-screen {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    /* Momentum scrolling inside a band that also scrolls vertically: without
       this the browser may claim the gesture for the page. */
    overscroll-behavior-x: contain;
  }
  .ms-hero__shot .ms-screen img,
  .ms-shot .ms-screen img {
    width: var(--ms-shot-w, 780px);
    max-width: none;
  }
  /* The ticks are drawn on the frame, so they must not scroll away with the
     content inside it. */
  .ms-hero__shot .ms-ticked::before,
  .ms-hero__shot .ms-ticked::after,
  .ms-shot .ms-ticked::before,
  .ms-shot .ms-ticked::after { display: none; }
  .ms-hero__shot .ms-screen__cap::after,
  .ms-shot .ms-screen__cap::after {
    content: " · swipe to read across";
    color: var(--ms-dim);
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   18. RHYTHM — the system owns the gaps between things.

   Converted pages lose their utility margins (`mb-8`, `mt-5`, `mb-3`) along
   with the rest of the old class attribute, and that is deliberate: those
   values were chosen one element at a time across sixty pages, so no two
   sections agreed on the distance between a heading and the thing it heads.
   Setting the gaps here means a page cannot invent its own vertical rhythm by
   accident, and a page that genuinely needs a different one says so with an
   explicit style that still wins.

   These are adjacent-sibling rules, so they only fire where two typed
   elements actually sit next to each other. Nothing here can reach markup the
   system does not own.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-eyebrow + .ms-display,
.ms-eyebrow + .ms-h2,
.ms-eyebrow + .ms-h3 { margin-top: 0; }

.ms-display + * { margin-top: 1.75rem; }
.ms-h2 + *      { margin-top: 1.15rem; }
.ms-h3 + *      { margin-top: 0.75rem; }

.ms-lede + *,
.ms-body + *    { margin-top: 1.35rem; }

.ms-note + *    { margin-top: 1rem; }

/* …and the same gap from the other side, for copy that follows a block the
   system typed (a table, a record list, a figure row). Adjacent margins
   collapse, so this never doubles up with the rules above. */
* + .ms-lede,
* + .ms-body,
* + .ms-note    { margin-top: 1.35rem; }

/* A figure is not typed by any rule above, and preflight zeroes a <figure>'s
   margin, so a screenshot sat flush on whatever preceded it — the hero CTA
   buttons on a dozen pages, an article byline, the homepage stat row — and its
   caption sat flush on whatever followed. Declared before the heading rules so
   a heading after a figure still gets its own, larger air. `.ms-hero__shot`
   is deliberately absent: it has its own tuned margin (§ hero, 3rem / 2rem on
   a tablet) that a (0,1,0) rule here would silently overwrite by source order. */
* + .ms-shot { margin-top: 2.25rem; }
.ms-shot + * { margin-top: 2rem; }

/* A heading that follows content needs air ABOVE it, or the page reads as one
   undifferentiated column. */
* + .ms-eyebrow { margin-top: 3rem; }
* + .ms-h2      { margin-top: 2.75rem; }
* + .ms-h3      { margin-top: 2rem; }

/* …except as the first thing in a band or a column, where the band's own
   padding is already the air. */
.ms-band > .ms-wrap > :first-child,
.ms-wrap > :first-child,
.ms-split__aside > :first-child,
.ms-split__main > :first-child,
.ms-callout > :first-child,
.ms-fleet__buy > :first-child { margin-top: 0; }

/* Blocks that carry their own top rule bring their own separation. */
.ms-recs, .ms-figs, .ms-table, .ms-quotes, .ms-steps, .ms-strip { margin-top: 2.25rem; }
.ms-links { margin-top: 1.6rem; }
.ms-btn + .ms-btn { margin-top: 0; }

/* A table inside a scroller: the scroller is the sibling, so it carries the
   gap instead. */
.ms-h2 + [style*="overflow"], .ms-h2 + .ms-tablewrap { margin-top: 2.25rem; }

/* ══════════════════════════════════════════════════════════════════════════
   19. SCROLL-EXPAND — the product surface grows to full bleed as you scroll.

   Modelled on reactbits' ScrollExpandMedia. Three things were changed on the
   way in, because the original is a React component built for a different
   kind of page:

   1. NO SCROLL CAPTURE. The reference implementation holds the wheel until
      the expansion finishes. On a site where the visitor is a contractor
      checking a price between meetings, taking their scroll away is a defect
      dressed as an effect — and it breaks keyboard paging, screen-reader
      navigation and every trackpad that reports momentum. Here the progress
      is read FROM the natural scroll position and never writes to it.

   2. NOTHING IS HIDDEN. The inset only applies once the script has marked the
      element live (`[data-ms-expand].is-live`). With no JS, a blocked script
      or a crawler, the media renders at its final full-bleed size — which is
      the state the page is arguing for anyway.

   3. IT ANIMATES WIDTH, NOT SCALE. `transform: scale()` on a 1920px
      screenshot resamples it, and the whole point of this surface is that the
      OBC article numbers and the FAIL/WARN chips are legible. Insetting the
      margins keeps every pixel at its native ratio.

   This is the one place on the site where motion does real work: the argument
   the hero makes is "look at what it reads off your drawings", and the media
   growing from a framed panel into the full width of the screen is that
   sentence, performed once, at the top of one page.
   ═════════════════════════════════════════════════════════════════════════ */

.ms-expand { --ms-p: 1; }

/* The resting inset at progress 0 — how much narrower than the viewport the
   frame starts. Only ever applied once JS is driving it. */
.ms-expand.is-live {
  --ms-expand-inset: 20vw;
}

.ms-expand__frame {
  margin-inline: calc(var(--ms-expand-inset, 0px) * (1 - var(--ms-p)));
  transition: margin-inline .12s linear;
  will-change: margin-inline;
}

/* The registration ticks belong to a framed panel. Once the surface is
   full-bleed there is no frame for them to register, so they fade with the
   inset rather than sitting orphaned in the viewport corners. */
.ms-expand .ms-ticked::before,
.ms-expand .ms-ticked::after { opacity: calc(1 - var(--ms-p)); }

/* At full bleed the side hairlines are on the viewport edge, where they read
   as a rendering artefact rather than as a frame. */
.ms-expand.is-live .ms-screen {
  border-left-width: calc(1px * (1 - var(--ms-p)));
  border-right-width: calc(1px * (1 - var(--ms-p)));
}

.ms-expand__cap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 1.25rem;
  margin-inline: calc(var(--ms-expand-inset, 0px) * (1 - var(--ms-p)));
  padding-top: 0.7rem;
  transition: margin-inline .12s linear;
}
/* On a phone the surface is edge to edge, so the caption under it started at
   x=0 against the glass — the one line of text on the page with no gutter. */
@media (max-width: 767px) {
  .ms-expand__cap { padding-inline: var(--ms-gutter); }
}

/* The hint retires once the surface is open — it is an instruction, and an
   instruction that stays after it has been followed is noise. */
.ms-expand__hint {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ms-dim);
  opacity: calc(1 - var(--ms-p));
  transition: opacity .18s linear;
  white-space: nowrap;
}

@media (max-width: 1023px) {
  .ms-expand.is-live { --ms-expand-inset: 8vw; }
}

/* A phone is already close to full bleed and the surface is already scrolling
   sideways to stay legible; growing it by another 8% buys nothing and fights
   that gesture. */
@media (max-width: 767px) {
  .ms-expand.is-live { --ms-expand-inset: 0px; }
  .ms-expand__hint { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ms-expand.is-live { --ms-expand-inset: 0px; }
  .ms-expand__frame,
  .ms-expand__cap { transition: none; }
  .ms-expand__hint { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   20. PLAN CARDS — one button each, and one footer line across the row.

   The five cards bottom-align on their own (equal-height grid), but Solo and
   Field carried TWO full-weight buttons where the other three carried one, so
   their footers were ~160px taller and their primary CTA sat 164px higher up
   the card. Read across, the row of buttons stepped.

   `.lvl-alt` is the fix at the source: the trial is the action, and
   "subscribe now" is the quieter alternative underneath it rather than a
   second button competing with it. `.lvl-foot` then pins what is left to a
   common track so the primary CTAs land on one line.
   ═════════════════════════════════════════════════════════════════════════ */

.lvl-alt {
  color: var(--ms-dim);
  text-decoration: none;
  letter-spacing: 0.02em;
  transition: color .15s ease;
}
.lvl-alt:hover { color: var(--ms-accent); }

/* The footer of every plan card sits on the same track. `min-height` is the
   tallest footer in the row (Firm: a seat stepper, a button and a note), so
   every card's primary button starts at the same y whatever sits under it. */
.lvl-card .lvl-foot {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  /* The tallest real footer in the row is Firm: a button, a seat stepper and
     a yearly note, measured at 198px. Anything lower and the taller cards
     push their own button up out of line, which is the whole defect. */
  min-height: 12.5rem;
}

/* The primary CTA goes first in every footer, whatever the markup order.
   Pro and Firm put their "extra seats" stepper above the button, which left
   their buttons 95px below Solo's and the row still stepping. The stepper is
   an optional adjustment, not the action, so it reads better underneath —
   and `order` does it without reshuffling five different footers by hand. */
.lvl-card .lvl-foot > a:not(.lvl-alt) { order: -1; }

/* Whatever ends up under the button — fine print, a seat stepper, the quiet
   alternative link — gets the same gap from it. */
.lvl-card .lvl-foot > a:not(.lvl-alt) + * { margin-top: 0.85rem; }

@media (max-width: 1023px) {
  /* Stacked on a phone there is no row to align to, and a 152px floor just
     adds dead space to the shortest card. */
  .lvl-card .lvl-foot { min-height: 0; }
}

/* ══════════════════════════════════════════════════════════════════════════
   20. THE LOGO STRIP — "Works with", on the integrations page.

   The motion is panel-polish's .pnl-marquee: transform only, never opacity,
   paused on hover, stopped outright under prefers-reduced-motion. Two things
   are added here.

   A PAUSE THAT NEEDS NO SCRIPT. Anything that moves for longer than five
   seconds has to be stoppable (WCAG 2.2.2), and hover is not a control a
   keyboard or a thumb can reach. A real checkbox is the control and CSS reads
   its state, so it works with scripts blocked and is focusable by default.

   ONE OPTICAL HEIGHT FOR TWO KINDS OF MARK. Icons and wordmarks do not share
   a box, so they get different heights, and icons carry their product name
   beside them: a SharePoint glyph alone is not self-evident to everyone
   reading an integrations page.

   The marks are the owners' own files. The three whose wordmarks are black —
   Procore, AutoCAD, QuickBooks — are the reversed treatment: only their
   unfilled, default-black paths went white, and every brand colour is exactly
   as issued.
   ═════════════════════════════════════════════════════════════════════════ */
.ms-logos { margin-top: 2rem; }
.ms-logos__set {
  display: flex;
  align-items: center;
  gap: 3.25rem;
  /* equal to the gap, so the join between the two copies is seamless */
  padding: 0 3.25rem 0 0;
  margin: 0;
  list-style: none;
}
.ms-logos__item {
  display: flex;
  align-items: center;
  gap: .7rem;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: .72rem;
  letter-spacing: .08em;
  color: var(--ms-muted);
  white-space: nowrap;
}
.ms-logos__item img { display: block; height: 30px; width: auto; }
.ms-logos__item--word img { height: 24px; }
.ms-logos__pause {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
.ms-logos__pause:checked ~ .pnl-marquee .pnl-marquee-track { animation-play-state: paused; }
.ms-logos__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: flex-end;   /* the Pause control, on its own at the right */
  gap: .75rem 1.5rem;
  margin-top: 1.1rem;
}
.ms-logos__toggle {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ms-dim);
  border-bottom: 1px solid var(--ms-rule);
  cursor: pointer;
}
.ms-logos__toggle:hover { color: var(--ms-text); }
.ms-logos__pause:focus-visible ~ .ms-logos__foot .ms-logos__toggle {
  outline: 2px solid var(--ms-accent);
  outline-offset: 3px;
}
.ms-logos__play { display: none; }
.ms-logos__pause:checked ~ .ms-logos__foot .ms-logos__play { display: inline; }
.ms-logos__pause:checked ~ .ms-logos__foot .ms-logos__stop { display: none; }
@media (prefers-reduced-motion: reduce) {
  /* Nothing moves, so there is nothing to pause. */
  .ms-logos__toggle { display: none; }
}
@media (max-width: 767px) {
  .ms-logos__set { gap: 2.25rem; padding-right: 2.25rem; }
  .ms-logos__item img { height: 24px; }
  .ms-logos__item--word img { height: 19px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   21. THE HERO DRAWING — a plan bled off the right edge of a program hero.

   The move borrowed from panelai.app's product pages: the artifact itself,
   full-bleed and fading into the ground, instead of a framed screenshot
   beside the headline. The drawing is tools/make-hero-drawing.py, drawn for
   the purpose, because every real set in reach belongs to another firm.

   THE MASK IS ON THIS LAYER, NEVER ON THE BAND. §8 records what happens the
   other way round: a mask on the <section> faded the copy along with the art.
   The layer also clips its own image, so a 62rem drawing can never widen the
   page, and it only exists at lg and up — beside a headline there is room for
   it; above a phone's single column it would sit behind the text.

   Static opacity only. Nothing here is animated, which is what keeps it clear
   of the rule that motion never animates opacity.
   ═════════════════════════════════════════════════════════════════════════ */
.ms-hero-drawing {
  display: none;
  position: absolute;
  top: 0;
  right: 0;
  width: 55%;
  height: min(100%, 46rem);
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
  -webkit-mask-image: radial-gradient(ellipse 78% 72% at 80% 48%, #000 22%, transparent 70%);
          mask-image: radial-gradient(ellipse 78% 72% at 80% 48%, #000 22%, transparent 70%);
}
.ms-hero-drawing img {
  position: absolute;
  top: 50%;
  left: 6%;
  width: 62rem;
  max-width: none;
  height: auto;
  transform: translateY(-50%);
  opacity: .36;
}
/* Whatever follows the layer in the hero paints above it. Positioned content
   (Tailwind's .absolute decorations) keeps its own positioning. */
.ms-hero-drawing ~ :not(.absolute) { position: relative; z-index: 1; }
@media (min-width: 1024px) {
  .ms-hero-drawing { display: block; }
}

/* The post-award heroes put the headline straight in .ms-wrap, so it runs the
   full 78rem and would sit on top of the drawing. Held to the same measure as
   the pre-award heroes' copy column, which stops short of the layer. */
@media (min-width: 1024px) {
  .ms-hero-drawing ~ .ms-wrap .ms-display { max-width: 44rem; }
}

/* §17, continued: a diagram is an inline <svg>, not an <img>, and gets the
   same frame and the same phone treatment — on a phone it scrolls across at a
   legible width instead of shrinking its labels to nothing. */
.ms-screen > svg { display: block; width: 100%; height: auto; }
@media (max-width: 767px) {
  .ms-shot .ms-screen > svg { width: var(--ms-shot-w, 780px); max-width: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   22. THE PAGE-WIDE GUIDE LINES — the fifth drafting motif, off.

   §8 records four drafting motifs removed on 10 Sep 2026. This is a fifth
   that survived: panel-polish.css paints `.pnl-on body::before`, a fixed
   112px column of hairlines behind the WHOLE page, and the head script sets
   `pnl-on` on 95 pages — including every converted one. It read as a faint
   grid behind the program list on the homepage.

   Off on converted pages only; `ms-scope` is what marks one. (0,2,1) beats
   panel-polish's (0,1,1) whatever the load order.
   ═════════════════════════════════════════════════════════════════════════ */
.pnl-on body.ms-scope::before { content: none; }

/* The newsletter form. widgets.js injects its own rules, which centre the form
   under copy the system sets flush left — two axes in one band — and set the
   hint in #696969, the disabled-glyph grey that fails AA on this ground.
   Scoped to converted pages, so (0,2,0)+ beats the injected (0,1,0)/(0,2,0)
   whatever order they land in. */
.ms-scope .mp-nl-wrap { align-items: flex-start; margin: 0; }
.ms-scope .mp-nl-wrap .mp-nl-form { justify-content: flex-start; }
.ms-scope .mp-nl-wrap .mp-nl-msg { text-align: left; color: var(--ms-dim); }

/* ══════════════════════════════════════════════════════════════════════════
   23. TABS — several products in the space of one, without hiding any.

   The homepage listed the programs twice: thirty capability lines with no
   pictures, then a strip of five unlabelled clips further down. A tab per
   program puts each clip beside the sentence it proves.

   THE MARKUP IS THE NO-JS PAGE. Panels are written out one after another,
   each with its own name, so a blocked script, a crawler or find-in-page gets
   every program stacked — the old ladder, near enough. site-system.js builds
   the tab row and marks inactive panels `hidden`; nothing here hides a panel
   that the script did not hide.

   `[hidden]` needs restating: Tailwind's preflight sets it at (0,1,0), and
   `.ms-tabs__panel { display: grid }` below would out-source-order it.
   ═════════════════════════════════════════════════════════════════════════ */
.ms-tabs { margin-top: 2.25rem; }

.ms-tabs__list {
  display: flex;
  border-bottom: 1px solid var(--ms-rule);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.ms-tabs__list::-webkit-scrollbar { display: none; }

.ms-tabs__tab {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 0.9rem 1.5rem 0.85rem 0;
  font-family: 'IBM Plex Sans', -apple-system, sans-serif;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.006em;
  color: var(--ms-dim);
  white-space: nowrap;
  text-align: left;
  cursor: pointer;
  scroll-snap-align: start;
  transition: color .15s ease, border-color .15s ease;
}
.ms-tabs__tab + .ms-tabs__tab { padding-left: 1.5rem; }

/* The stage label above the name: "01 · Drawings in". It is what orders the
   row — the tabs follow the job, not the alphabet. */
.ms-tabs__tab small {
  display: block;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ms-dim);
  margin-bottom: 0.3rem;
}
.ms-tabs__tab:hover { color: var(--ms-text); }
.ms-tabs__tab[aria-selected="true"] { color: var(--ms-text); border-bottom-color: var(--ms-accent); }
.ms-tabs__tab[aria-selected="true"] small { color: var(--ms-accent); }
.ms-tabs__tab:focus-visible { outline: 2px solid var(--ms-accent); outline-offset: -2px; }

.ms-tabs__panel {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: 2.5rem;
  align-items: start;
  padding: 2rem 0;
  border-bottom: 1px solid var(--ms-rule-soft);
}
.ms-tabs__panel[hidden] { display: none; }
/* Once there is a tab row, the panel is one object under it, not a list row. */
.ms-tabs.is-live .ms-tabs__panel { border-bottom: 0; padding-bottom: 0; }

.ms-tabs__copy .ms-prog__name { font-size: 1.25rem; margin: 0; }
/* The tab already names the stage; inside a live panel the same label twice
   is noise. With no script there is no tab row, and it shows. */
.ms-tabs.is-live .ms-tabs__copy .ms-prog__stage { display: none; }
.ms-tabs__copy .ms-prog__hook { margin-top: 1rem; }
.ms-tabs__copy .ms-prog__act { flex-direction: row; flex-wrap: wrap; gap: 1.25rem; margin-top: 1.4rem; }

/* On a desktop the whole row fits, so it should read as one ruled strip
   rather than a list running off the right edge: tighter padding, and each
   tab grows from its own width to share what is left. Measured at 1440 with
   eight programs, the old 3rem between labels pushed the eighth off screen.

   The row KEEPS its sideways scroll here. Eight labels need ~1,060px and the
   78rem column gives 1,200 from a 1,248px window up — so between 1024 and
   1248 they do not fit, and with `overflow-x: visible` the last tab spilled
   84px past the window at 1024. A row that scrolls is the phone behaviour
   already; a tab outside the viewport is a page that scrolls sideways. */
@media (min-width: 1024px) {
  .ms-tabs__tab { flex: 1 1 auto; padding-right: 1rem; }
  .ms-tabs__tab + .ms-tabs__tab { padding-left: 1rem; }
}

/* The two actions under a program are one pair: the note-link picked up the
   body size while its neighbour sat at 0.875rem, so they read as unrelated. */
.ms-prog__act .ms-note-link { font-size: 0.875rem; }

@media (max-width: 1023px) {
  .ms-tabs__panel { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
}
@media (max-width: 767px) {
  .ms-tabs__tab { font-size: 0.875rem; padding-right: 1.1rem; }
  .ms-tabs__tab + .ms-tabs__tab { padding-left: 1.1rem; }
  .ms-tabs__panel { padding-top: 1.25rem; }
}

/* ══════════════════════════════════════════════════════════════════════════
   24. FAQ — native <details>, one open at a time where the browser can.

   `name` on each <details> makes the group exclusive with no script (Chrome
   120, Safari 17.2, Firefox 130). Older browsers let several stay open, which
   is a fine way for an FAQ to fail. The answer is never hidden by this file:
   a closed <details> is the browser's own state, so the question list is
   there for every reader and every crawler.

   The marker rotates — transform only, like every motion in this system.
   ═════════════════════════════════════════════════════════════════════════ */
.ms-faq { border-top: 1px solid var(--ms-rule); margin-top: 2.25rem; }

.ms-faq__item { border-bottom: 1px solid var(--ms-rule-soft); }

.ms-faq__q {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.15rem 0;
  font-family: 'IBM Plex Sans', -apple-system, sans-serif;
  font-size: 0.9875rem;
  font-weight: 600;
  line-height: 1.45;
  color: var(--ms-text);
  cursor: pointer;
  list-style: none;
}
.ms-faq__q::-webkit-details-marker { display: none; }
.ms-faq__q::after {
  content: "+";
  flex-shrink: 0;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 1.1rem;
  font-weight: 400;
  line-height: 1;
  color: var(--ms-accent);
  transition: transform .2s ease;
}
.ms-faq__item[open] > .ms-faq__q::after { transform: rotate(45deg); }
.ms-faq__q:hover { color: var(--ms-hover-accent); }
.ms-faq__q:focus-visible { outline: 2px solid var(--ms-accent); outline-offset: 2px; }
/* A long list (the FAQ page's 26) reads as a list, not a stack of cards. */
.ms-faq--dense .ms-faq__q { padding: 0.8rem 0; }

.ms-faq__a { padding: 0 0 1.35rem; max-width: 44rem; }
.ms-faq__a p {
  font-size: 0.9375rem;
  line-height: 1.68;
  color: var(--ms-muted);
  margin: 0;
}
.ms-faq__a p + p { margin-top: 0.8rem; }
.ms-faq__a a { color: var(--ms-accent); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgba(249, 115, 22, .35); }
.ms-faq__a a:hover { color: var(--ms-hover-accent); }

@media (prefers-reduced-motion: reduce) {
  .ms-faq__q::after { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   25. SUB-NAV — a map of a long page, pinned under the site nav.

   The product pages run twelve bands and sixteen desktop screens. A row of
   jump links under the nav tells the visitor where pricing and the FAQ are
   before they scroll for them. It is a <div role="navigation">, not a <nav>:
   the sync scripts and several tests find the site nav as the first <nav> on
   the page, and a second one is not worth the risk to them.

   The current section is marked by site-system.js with a class — a colour
   change, not a motion.
   ═════════════════════════════════════════════════════════════════════════ */
:root { --ms-nav-h: 69px; }
@media (max-width: 767px) { :root { --ms-nav-h: 65px; } }

.ms-subnav {
  position: sticky;
  top: var(--ms-nav-h);
  z-index: 40;
  background: rgba(28, 28, 28, 0.96);
  border-bottom: 1px solid var(--ms-rule-soft);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.ms-subnav__in {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  min-height: 3.25rem;
}
.ms-subnav__title {
  font-family: 'IBM Plex Sans', -apple-system, sans-serif;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ms-text);
  white-space: nowrap;
}
.ms-subnav__links {
  display: flex;
  gap: 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
  flex: 1 1 auto;
  min-width: 0;
}
.ms-subnav__links::-webkit-scrollbar { display: none; }
.ms-subnav__links a {
  display: block;
  padding: 1rem 0 0.9rem;
  border-bottom: 2px solid transparent;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ms-dim);
  text-decoration: none;
  white-space: nowrap;
  transition: color .15s ease, border-color .15s ease;
}
.ms-subnav__links a:hover { color: var(--ms-text); }
.ms-subnav__links a.is-on { color: var(--ms-accent); border-bottom-color: var(--ms-accent); }
.ms-subnav__cta { flex-shrink: 0; padding: 0.5rem 1rem; font-size: 0.8125rem; }

/* A jump lands below both bars, not under them. */
html:has(.ms-subnav) [id] { scroll-margin-top: calc(var(--ms-nav-h) + 4.25rem); }

@media (max-width: 767px) {
  .ms-subnav__title, .ms-subnav__cta { display: none; }
  .ms-subnav__in { min-height: 2.75rem; }
  .ms-subnav__links a { padding: 0.85rem 0 0.75rem; }
}

/* ══════════════════════════════════════════════════════════════════════════
   26. CELLS — the ruled grid that replaces a pile of rounded cards.

   The records (§3) are rows; some content is genuinely a grid — nine trades,
   six capabilities, four resource types. These are cells in a drawn table:
   one top rule, a hairline under every cell and between the columns, no
   radius and no outer frame. The first column keeps no left padding so its
   text stays on the page's left axis with every other band.

   A cell that is a link is the whole cell, so the target is the size of the
   thing it names.
   ═════════════════════════════════════════════════════════════════════════ */
.ms-cells {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid var(--ms-rule);
  margin-top: 2.25rem;
}
.ms-cells--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ms-cells--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.ms-cell {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.5rem;
  border-bottom: 1px solid var(--ms-rule-soft);
  text-decoration: none;
  color: inherit;
  transition: background-color .16s ease;
}

/* Column dividers and the left axis, by column count. */
.ms-cells > .ms-cell:nth-child(3n+1) { padding-left: 0; }
.ms-cells > .ms-cell:not(:nth-child(3n+1)) { border-left: 1px solid var(--ms-rule-soft); }
.ms-cells--2 > .ms-cell:nth-child(n) { padding-left: 1.5rem; border-left: 1px solid var(--ms-rule-soft); }
.ms-cells--2 > .ms-cell:nth-child(2n+1) { padding-left: 0; border-left: 0; }
.ms-cells--4 > .ms-cell:nth-child(n) { padding-left: 1.5rem; border-left: 1px solid var(--ms-rule-soft); }
.ms-cells--4 > .ms-cell:nth-child(4n+1) { padding-left: 0; border-left: 0; }

a.ms-cell:hover { background: var(--ms-hover); }
a.ms-cell:hover .ms-cell__h { color: var(--ms-accent); }
a.ms-cell:focus-visible { outline: 2px solid var(--ms-accent); outline-offset: -2px; }

.ms-cell__k {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ms-accent);
}
.ms-cell__h {
  font-family: 'IBM Plex Sans', -apple-system, sans-serif;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.008em;
  color: var(--ms-text);
  margin: 0;
  transition: color .15s ease;
}
.ms-cell__p { font-size: 0.875rem; line-height: 1.6; color: var(--ms-muted); margin: 0; }
.ms-cell__m {
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  color: var(--ms-dim);
  margin-top: auto;
  padding-top: 0.5rem;
}
a.ms-cell .ms-cell__m { color: var(--ms-accent); }
/* A status that is not an action ("In development") stays grey even inside a link. */
a.ms-cell .ms-cell__m--dim, .ms-cell__m--dim { color: var(--ms-dim); }

@media (max-width: 1023px) {
  .ms-cells, .ms-cells--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ms-cells > .ms-cell:nth-child(n) { padding-left: 1.5rem; border-left: 1px solid var(--ms-rule-soft); }
  .ms-cells > .ms-cell:nth-child(2n+1) { padding-left: 0; border-left: 0; }
}
@media (max-width: 767px) {
  .ms-cells, .ms-cells--2, .ms-cells--4 { grid-template-columns: minmax(0, 1fr); }
  .ms-cells > .ms-cell:nth-child(n) { padding: 1.1rem 0; border-left: 0; }
}

/* ══════════════════════════════════════════════════════════════════════════
   27. ASSURANCE ROW — what happens if it does not work out.

   Four facts across one rule, set at the point of decision. Every one of
   them must already be true in terms.html; this row states policy, it does
   not make it. The trial takes a card, so "no card" never appears here.
   ═════════════════════════════════════════════════════════════════════════ */
.ms-assure {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 0 2rem;
  margin: 2.25rem 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--ms-rule-soft);
}
.ms-assure li { padding: 1.1rem 0 0; }
.ms-assure b {
  display: block;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ms-accent);
  margin-bottom: 0.35rem;
}
.ms-assure span { font-size: 0.8125rem; line-height: 1.55; color: var(--ms-muted); }
@media (max-width: 767px) {
  .ms-assure { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 1.25rem; }
}

/* ══════════════════════════════════════════════════════════════════════════
   28. FLOW — one real exchange, three screens, left to right.

   Upload, ask, result. Each step is a real screen and one line under it, so
   the AI is shown doing one specific job rather than described as able to.
   ═════════════════════════════════════════════════════════════════════════ */
.ms-flow {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem;
  margin: 2.25rem 0 0;
  padding: 0;
  list-style: none;
}
.ms-flow__n {
  display: block;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ms-accent);
  margin-bottom: 0.75rem;
}
.ms-flow__t { font-size: 0.875rem; line-height: 1.55; color: var(--ms-muted); margin: 0.85rem 0 0; }
@media (max-width: 767px) {
  .ms-flow { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
}

/* ══════════════════════════════════════════════════════════════════════════
   30. PRICING — the plan columns, the billing switch and the plan matrix.

   The pricing page was the last core page on the pre-system vocabulary:
   five rounded cards with a hover lift, a gradient button each, a bordered
   "true on every plan" box and a rounded table. The markup keeps every hook
   the page's scripts use — .bill-month / .bill-year, data-price-cad,
   data-plan-cta, the seat steppers, .lvl-card / .lvl-foot (§20) — and only
   the shape changes: five columns ruled like the records, Pro marked by a
   raised ground and an orange top rule instead of a thicker orange box.

   THE TRAP. The billing toggle hides the other period with Tailwind's
   `hidden` (display:none, (0,1,0)). This file loads after Tailwind, so a
   plain `.lvl-btn { display:flex }` here would OUT-RANK `hidden` and show
   the monthly buttons in yearly mode. Every display rule below that can land
   on a toggled element is written `:not(.hidden)`.
   ═════════════════════════════════════════════════════════════════════════ */

/* The billing switch — a small segmented control, not a filled orange slab. */
.ms-scope .tab-btn {
  background: transparent;
  border: 1px solid var(--ms-rule);
  border-radius: 2px;
  color: var(--ms-dim);
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.5rem 0.9rem;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background-color .15s ease;
}
.ms-scope .tab-btn:hover { color: var(--ms-text); border-color: var(--ms-dim); }
.ms-scope .tab-btn.active { background: var(--ms-raised); color: var(--ms-text); border-color: var(--ms-accent); }

/* Five columns on one top rule. */
.ms-plancols {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  border-top: 1px solid var(--ms-rule);
  align-items: stretch;
}
.ms-plancols > .lvl-card {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 1.9rem 1.25rem 1.6rem;
  border-bottom: 1px solid var(--ms-rule-soft);
}
.ms-plancols > .lvl-card + .lvl-card { border-left: 1px solid var(--ms-rule-soft); }
.ms-plancols > .lvl-card:first-child { padding-left: 0; }
.ms-plancols > .lvl-card--pro { background: var(--ms-raised); box-shadow: inset 0 2px 0 var(--ms-accent); }

.lvl-badge {
  position: absolute;
  top: 0;
  left: 1.25rem;
  transform: translateY(-50%);
  background: var(--ms-accent);
  color: #131313;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.22rem 0.5rem;
  white-space: nowrap;
}

.lvl-ticks li { font-size: 0.8125rem; }
/* What a plan does NOT open: same list, a grey leader and grey text. */
.ms-ticks li.lvl-no { color: var(--ms-dim); }
.ms-ticks li.lvl-no::before { background: var(--ms-rule); }

.lvl-btn:not(.hidden) { display: flex; width: 100%; justify-content: center; }

@media (max-width: 1023px) {
  .ms-plancols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ms-plancols > .lvl-card + .lvl-card { border-left: 0; }
  .ms-plancols > .lvl-card:nth-child(2n) { border-left: 1px solid var(--ms-rule-soft); }
  .ms-plancols > .lvl-card:nth-child(2n+1) { padding-left: 0; }
  .ms-plancols > .lvl-card--pro:nth-child(2n+1) { padding-left: 1.25rem; }
}
@media (max-width: 639px) {
  .ms-plancols { grid-template-columns: minmax(0, 1fr); }
  .ms-plancols > .lvl-card:nth-child(n) { border-left: 0; padding-left: 0; padding-right: 0; }
  .ms-plancols > .lvl-card--pro:nth-child(n) { padding-left: 1rem; padding-right: 1rem; }
  .lvl-badge { left: 1rem; }
}

/* A figure a cell is arguing with — the cost of each option. */
.ms-cell__fig {
  font-family: 'IBM Plex Sans Condensed', 'IBM Plex Sans', sans-serif;
  font-weight: 700;
  font-size: 1.9rem;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ms-accent);
  margin: 0.25rem 0 0.35rem;
}
/* Inside a cell the flex gap is the rhythm. §18's `* + .ms-h3` (2rem) and
   `* + .ms-body` (1.35rem) are for a heading or paragraph that follows a
   block in a band; applied inside a cell they opened a 2rem hole between a
   cell's label ("STAGE 1") and its name. (0,2,0) beats them. */
.ms-cell .ms-h3, .ms-cell .ms-body, .ms-cell .ms-h2 { margin-top: 0; }

/* A text link at the foot of a cell keeps its own width and sits on the
   cell's floor, so a row of cells ends on one line. */
.ms-cell > .ms-link { align-self: flex-start; margin-top: auto; padding-top: 0.25rem; }

/* The one cell of a set the page recommends. */
.ms-cells > .ms-cell--mark { background: var(--ms-raised); box-shadow: inset 0 2px 0 var(--ms-accent); }
.ms-cells > .ms-cell--mark:nth-child(n) { padding-left: 1.5rem; padding-right: 1.5rem; }
.ms-cell .ms-ticks { margin-top: 0.35rem; }

/* The plan matrix: five plans across, programs and terms down. */
.ms-tablewrap--matrix { overflow-x: auto; margin-top: 2.25rem; }
/* Fixed layout: five plan columns of one width, so a long cell ("up to 20 +
   overage") cannot squeeze Solo, Field and Crew into slivers beside it. */
/* 700, not 760: a tablet's wrap is ~718px, and 760 made the matrix scroll a
   40px sliver there — a sideways wobble for two pixels of text. */
.ms-table--matrix { min-width: 700px; table-layout: fixed; }
.ms-table--matrix th:first-child { width: 32%; }
/* Row labels are the names of things, set in the body face. §11's `th` rule
   (mono, uppercase, tracked) is right for column heads and wrong for a list
   of programs — it had "ADVANCED TRADE ENGINES" shouting down the table. */
.ms-table--matrix tbody th {
  font-family: 'IBM Plex Sans', -apple-system, sans-serif;
  font-size: 0.875rem;
  letter-spacing: 0;
  text-transform: none;
}
.ms-table--matrix th, .ms-table--matrix td { text-align: center; }
.ms-table--matrix th:first-child, .ms-table--matrix td:first-child { text-align: left; }
.ms-table--matrix tbody th { font-weight: 400; color: var(--ms-text); padding: 0.8rem 1rem 0.8rem 0; border-bottom: 1px solid var(--ms-rule-soft); }
/* The row label sat middle while its cells sat top (a two-line value put the
   label half a line low), and on the last row the label kept its rule when the
   cells dropped theirs — a 320px stub of a line under "Support". */
.ms-table--matrix tbody th { vertical-align: top; }
.ms-table--matrix tbody tr:last-child th { border-bottom: none; }
.ms-table--matrix tbody th a { color: var(--ms-text); text-decoration: none; }
.ms-table--matrix tbody th a:hover { color: var(--ms-accent); }
.ms-table--matrix thead th:not(:first-child) {
  font-family: 'IBM Plex Sans Condensed', 'IBM Plex Sans', sans-serif;
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--ms-text);
}
.ms-table--matrix thead th.ms-th-ours { color: var(--ms-accent); }
/* A comparison (/vs/): three columns of prose, read left to right. */
.ms-table--vs { min-width: 640px; }
.ms-table--vs th, .ms-table--vs td { text-align: left !important; }
.ms-table--vs td { padding-right: 1.25rem; }
.ms-table--vs tr:last-child th { border-bottom: none; }
/* On a phone the three columns stack: the row, then each side labelled, so
   the other product is never off-screen behind a sideways scroll. */
@media (max-width: 639px) {
  /* !important: a phone rule elsewhere gives every scrolling table a 560px floor. */
  .ms-table--vs, .ms-table--vs tbody { display: block; width: 100%; min-width: 0 !important; table-layout: auto; }
  .ms-table--vs td, .ms-table--vs tbody th { white-space: normal; overflow-wrap: anywhere; }
  .ms-table--vs thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .ms-table--vs tr { display: block; padding: 0.9rem 0; border-bottom: 1px solid var(--ms-rule-soft); }
  .ms-table--vs tbody th, .ms-table--vs td { display: block; border-bottom: 0; padding: 0; }
  .ms-table--vs tbody th { font-weight: 600; margin-bottom: 0.35rem; }
  .ms-table--vs td { padding: 0.15rem 0 0; }
  .ms-table--vs td::before { content: attr(data-col) ": "; color: var(--ms-dim); font-size: 0.75rem; }
}
.ms-td-yes { color: var(--ms-accent) !important; }
.ms-td-no { color: var(--ms-dim) !important; }
.ms-td-txt { font-size: 0.8125rem; }
.ms-table--matrix tr.ms-tr-group th {
  text-align: left;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ms-accent);
  padding: 1.6rem 0 0.6rem;
  border-bottom: 1px solid var(--ms-rule);
}
/* The header row follows the reader down the table on a desktop, pinned
   under the site nav and the sub-nav. Only where the table fits the column:
   a sticky cell inside an overflow scroller sticks to the scroller, not the
   page, so on a phone the table scrolls sideways and the header does not pin. */
@media (min-width: 1024px) {
  .ms-tablewrap--matrix { overflow: visible; }
  .ms-table--matrix thead th {
    position: sticky;
    top: calc(var(--ms-nav-h) + 3.3rem);
    background: var(--ms-ground);
    z-index: 2;
  }
  /* No sub-nav on the page: pin straight under the site nav. */
  html:not(:has(.ms-subnav)) .ms-table--matrix thead th { top: var(--ms-nav-h); }
}

/* The sub-nav's right-hand slot when it holds a control instead of a CTA
   (the currency switch on pricing). Unlike the CTA it stays on a phone. */
.ms-subnav__aside { display: flex; align-items: center; gap: 0.35rem; flex-shrink: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   29. CLOSE — the one way a page ends.

   Every page ended differently: a centred slab here, a bordered box there, a
   button with no sentence above it somewhere else. One band now: the ask on
   the left, the two buttons on the right, the assurance row under both.
   ═════════════════════════════════════════════════════════════════════════ */
.ms-close__in {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 1.5rem 3rem;
  align-items: end;
}
.ms-close__cta { display: flex; flex-wrap: wrap; gap: 0.85rem; }
@media (max-width: 767px) {
  .ms-close__in { grid-template-columns: minmax(0, 1fr); align-items: start; }
}

/* Articles (/construction/resources/*): consecutive paragraphs inside a band
   ran together with no gap — the template set the spacing on the wrapper,
   between bands, and nothing within one. */
.article .ms-band > p + p { margin-top: 1rem; }

/* …and the same template put a full .ms-band (5.5rem above AND below) around
   every section of a narrow reading column that already spaces its sections
   (`space-y-10`), so each H2 sat under ~220px of nothing at 1440 and the
   articles read as unfinished. One band's worth of air between sections is
   the column's own gap plus a little. Within a section, nothing spaced a list,
   a table or a callout from the sentence that introduced it. */
.article .ms-wrap--narrow > .ms-band { padding-top: 0.75rem; padding-bottom: 0.75rem; }
.article .ms-band > * + :not(.ms-shot) { margin-top: 1.1rem; }
.article .ms-band > .overflow-x-auto table { border-collapse: collapse; }
.article .ms-band > .overflow-x-auto th {
  border-bottom: 1px solid var(--ms-rule);
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ms-dim);
}
.article .ms-band > .overflow-x-auto td {
  border-bottom: 1px solid var(--ms-rule-soft);
  vertical-align: top;
}

/* The way back to the index. It used to sit in the nav bar ahead of the logo,
   which pushed the logo ~85px right on these pages alone. */
.ms-crumb {
  display: inline-block;
  margin-bottom: 1.25rem;
  font-family: 'IBM Plex Mono', 'SF Mono', monospace;
  font-size: 0.75rem;
  color: var(--ms-dim);
  transition: color .15s ease;
}
.ms-crumb:hover { color: var(--ms-text); }
.ms-crumb + .ms-eyebrow { margin-top: 0; }

/* A grid inside .ms-wrap (the docs pages: sidebar and content) collapses to one
   auto column on a phone, and a grid item defaults to min-width:auto. A table
   carrying the 560px phone floor from mobile-app.css widened the whole column,
   so every heading and paragraph beside it ran off a 390px screen. With the
   floor on the item removed, the table scrolls inside its own wrapper. */
.ms-wrap.grid > * { min-width: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   27. MOBILE SECTION COLLAPSE — opt in with data-collapse-mobile="Label".

   Only ever clips: the markup stays intact for crawlers and find-in-page.
   Moved here from panel-polish, which only the homepage loads: the product
   page for Blueprint Auditor carried the attribute on a 4,400px section and
   nothing on that page implemented it, so a phone scrolled all of it.
   ═════════════════════════════════════════════════════════════════════════ */
.pnl-collapse-btn {
  display: none;
  width: calc(100% - 2rem);
  margin: -0.5rem 1rem 1.75rem;
  padding: 0.8rem 1rem;
  background: transparent;
  border: 1px solid #444444;
  border-radius: 6px;
  color: var(--ms-accent);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
}
.pnl-collapse-btn .pnl-collapse-ic { font-size: 0.6rem; margin-left: 0.35rem; }

@media (max-width: 767px) {
  .pnl-collapsible { position: relative; }
  .pnl-collapsible > :first-child {
    max-height: 20rem;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(180deg, #000 60%, transparent 100%);
            mask-image: linear-gradient(180deg, #000 60%, transparent 100%);
  }
  .pnl-collapsible.pnl-open > :first-child {
    max-height: none;
    overflow: visible;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .pnl-collapsible > .pnl-collapse-btn { display: block; }
}
