/*
Theme Name: Microspectra Aperture
Theme URI: https://www.microspectra.com/
Author: CRAIC Technologies
Description: Classic theme for microspectra.com. Every page uses one of five templates; no page
  is hand-rolled. Signature elements: the measuring aperture used small, and the octagonal
  optical head used once per page, large.
Version: 0.59.0
  0.36.0 IS ALREADY PUBLISHED, AND IT IS A DIFFERENT FILE. v1.33.0 shipped this stylesheet at
  0.36.0 and editing continued afterwards without a second bump, so the deployed copy is 248,020
  bytes and this one is 250,856 — 2,836 bytes of rules (the comparison table's type scale, the
  deferred-answer styling, and the fix for prose link lists rendering in the browser's default
  blue) that a browser holding `style.css?ver=0.36.0` would never fetch. The version IS the cache
  key: shipping two different files under one number means the people who visited first are the
  ones who keep the broken page. Found by fetching the deployed asset and comparing bytes.
Requires at least: 6.0
Requires PHP: 8.0
License: Proprietary
Text Domain: microspectra-aperture
*/

/* Self-hosted variable fonts. The weight is declared as the font's own axis
   RANGE, not pinned: these files are variable, and a fixed font-weight tells
   the browser the face IS that weight, which disables interpolation. Pinned,
   Lexend 700 rendered at 400 and Manrope rendered at its 200 default. */
@font-face{font-family:'Lexend';src:url('assets/fonts/Lexend-variable.woff2') format('woff2');
  font-weight:100 900;font-style:normal;font-display:swap}
@font-face{font-family:'Manrope';src:url('assets/fonts/Manrope-variable.woff2') format('woff2');
  font-weight:200 800;font-style:normal;font-display:swap}
@font-face{font-family:'Martian Mono';src:url('assets/fonts/MartianMono-variable.woff2') format('woff2');
  font-weight:100 800;font-style:normal;font-display:swap}

/* ----------------------------------------------------------- box sizing
   The theme set box-sizing NOWHERE, so every element was content-box and any
   element combining width:100% with padding or a border overflowed its container
   by exactly that padding plus border. Measured on the RFI form: inputs compute to
   width:640px and render at 669px — a 29px overflow that persisted at EVERY
   viewport, including 640px and wider, because it is not a responsive failure.
   It showed on the form first only because that is where width:100% and padding
   coincide; it was latent on every element that ever gains a border. */
*, *::before, *::after{ box-sizing:border-box; }

:root{
  --red:#EE2E24;        /* the logo red, exact — the only red on the site */
  --stage:#0D0E10;      /* sampled from the Apollo M stage plate */
  --paper:#F4F4F5;      /* the instrument body */
  /* The same two colours as channel triplets, for the one place the design needs them
     with an alpha: the hero scrim, which must be a gradient over the wall rather than a
     flat fill. Stated HERE, beside the hex they come from, and referenced everywhere
     else — eight loose rgba(13,14,16,...) stops scattered through the hero rules would
     be eight copies of --stage that S-TOKENS-03 cannot even see, because it reads
     hex literals and rgba() is not one. */
  --stage-rgb:13,14,16;
  --paper-rgb:244,244,245;
  /* THE SECOND DARK SURFACE. --stage is the page's dark plane; this is a sub-area WITHIN a
     page — a navigation panel opening over it, a drawer, or a band of content that has to
     read as its own area rather than as more of the same black. One value, so that every
     such area on every page is the same area.

     Not a grey picked by eye. Sampling the rendered hero band at 1440 — 939,900 pixels of its
     dark structure, pure black excluded — the 75th percentile is rgb(19,22,26) and the
     commonest quantised tones are #121212, #181818 and #1E2424, with a cool family (#182A42,
     #18243C) from the micrographs. This sits in that family, 1.29:1 off the stage: a plane
     change, not a highlight, and 14.99:1 against white for the pages where it sits between
     two light sections.

     IT CARRIES ITS OWN SECONDARY INK, and that is not optional. --dim measures 4.51:1 here
     against a 4.5:1 floor — one hundredth of a point of margin, on a surface whose whole
     purpose is to hold body copy. --dim-panel measures 7.05:1 on it. */
  --stage-panel:#23272E;
  --dim-panel:#AEB2BA;   /* secondary type on --stage-panel: 7.05:1 */
  --white:#FFFFFF;
  /* SECONDARY TYPE ON THE STAGE, LIFTED FROM THE MOCKUP'S VALUE. The approved design sets
     #8A8D95, which measures 5.82:1 on --stage — over the 4.5:1 AA floor and under the 7:1
     AAA one, and on a page that is dark for most of its height it reads as faint at the 17px
     and 12px this ink is used at. #9CA0A8 measures 7.36:1 and is the same hue and chroma,
     scaled. The three-step hierarchy on the stage is unchanged and now better separated:
     --white 19.31:1, --paper 17.57:1, --dim 7.36:1. */
  --dim:#9CA0A8;        /* secondary type on the stage: 7.36:1 */
  --dim-ink:#585B63;    /* secondary type on the body */

  /* THE HAIRLINE, AND THE FIRMER ONE. --line-ink was READ twelve times in this file and
     declared nowhere; it survived S-TOKENS-03's undeclared-name check only because every one
     of those twelve carried the same fallback, so it painted correctly by accident — the
     thirteenth would not have. The three craic plugins had the same shape under the name
     --rule, with THREE different fallbacks (rgba(0,0,0,.12), rgba(20,22,27,.12) and #dcd7cf):
     three hairline colours, one name, none of them declared, none of them this site's ink.
     Stated once, derived from --stage like every other alpha in this file. */
  --line-ink:rgba(var(--stage-rgb),.14);
  --line-firm:rgba(var(--stage-rgb),.22);

  /* SECONDARY INK, CHOSEN BY THE SURFACE RATHER THAN BY AN ANCESTOR CLASS.
     --dim is legible on the stage and --dim-ink on the body, and until now the choice
     between them was made by the selector `.msa-stage .eyebrow`. That works only for
     components that carry the class. .msa-rail__cta does not: it declares
     `background:var(--stage)` directly, so it is dark by colour and light by selector, and
     its eyebrow rendered #585B63 on #0D0E10 — 2.84:1, against a 4.5:1 requirement, on every
     railed page (263 of them). There were two ways to be dark and only one of them was
     recognised.
     A surface now names its own ink once, and type reads --ink-dim without knowing where it
     is. S-CONTRAST-59 fails any rule that sets a --stage background without setting it. */
  --ink-dim:var(--dim-ink);   /* the default surface is the body */

  --gap-section:4.5rem; /* the ONE break between all major sections */
  /* AND THE ONE BREAK BETWEEN A SECTION HEAD AND THE BLOCK IT INTRODUCES. Measured on the
     home page before this token existed, head-bottom to body-top: 70px under the paired
     figures, 70px under the micrograph grid, 70px under the instruments, 20px under the
     dedicated-systems cards and 64px under the science library. Five relationships of the
     same kind, five different numbers, none of them chosen — 70 and 64 came from two
     different rules in the mockup and 20 was a list reset. One value, smaller than
     --gap-section so a heading reads as attached to its own content rather than floating
     between two blocks. */
  --gap-head:4rem;      /* 64px */
  --measure:68ch;       /* body prose cap, in the body's own character width */
  /* The same distance as a LENGTH, for blocks whose font-size is not the body's.
     68ch at the 1rem body size resolves to 680px; stating it once here is what
     keeps a heading's right edge on the same line as the paragraph below it. */
  --measure-block:42.5rem;
  /* THE MASTHEAD'S HEIGHT, DECLARED WHERE IT IS READ FROM.
     This existed only inside `@media (max-width:759px)`, and two rules outside that query read
     it as `var(--msa-head-h, 70px)` — so at every width above 759 the LITERAL was the operative
     value and nothing kept it in step. Measured: the masthead is 69.9px at 900px and up and
     64px below that, and the token's old breakpoint was 759, so between 760 and 899 the
     fallback said 70 while the header was 64. `html{scroll-padding-block-start}` reads this at
     every width, so an in-page anchor landed 6px under the masthead across that whole band.
     The override below sits at the header's OWN breakpoint, not a nearby one. */
  --msa-head-h:70px;
  /* The page title's size and leading, named here rather than only inside .msa-h1, because
     the rail aligns its photograph to the title's LAST LINE and needs the same numbers. A
     custom property declared on .msa-h1 would not be visible to a sibling. */
  --h1-size:clamp(1.625rem,1.35rem + 1.2vw,2.5rem);
  --h1-leading:1.15;
  --wrap:1360px;
  /* The page's side gutter. This exact clamp was already written inline in two hero
     rules; it is now stated once and referenced everywhere, for the same reason
     --stage-rgb exists — a value repeated in two places is a value that drifts. */
  --gutter:clamp(1.25rem,4vw,3rem);

  --font-display:'Lexend',system-ui,sans-serif;
  --font-body:'Manrope',system-ui,sans-serif;
  --font-mono:'Martian Mono',ui-monospace,monospace;

  /* the regular octagon — the optical head. Used at two sizes only. */
  --oct:polygon(29.29% 0%,70.71% 0%,100% 29.29%,100% 70.71%,
                70.71% 100%,29.29% 100%,0% 70.71%,0% 29.29%);
}

/* White goes on the red, never dark. Compliance is carried by size:
   #FFFFFF on #EE2E24 is 4.157:1, which clears AA at the large-text
   threshold (>=24px regular, >=18.66px bold) and nowhere else. */
.msa-on-red{background:var(--red);color:var(--white);font-size:19px;font-weight:700}

/* THE PAGE DECLARES ITS OWN BACKGROUND. Neither html nor body set background-color, so
   every white surface on this site was the USER AGENT's default canvas, not a decision the
   stylesheet made. Measured: getComputedStyle on both returned rgba(0,0,0,0). It renders
   white in every shipping browser, which is why nobody noticed — but a theme whose dark body
   copy is legible only because of a default it never states is one forced-colors mode or one
   embedding context away from unreadable, and S-CONTRAST-59 cannot measure a canvas that is
   not declared. Stated once, in the token the rest of the palette is stated in. */
body{font-family:var(--font-body);background:var(--white)}
h1,h2,h3,h4,h5,h6{font-family:var(--font-display)}

/* Heading rhythm reset. S-BALANCE-08 caught this for real, on the live Home
   page, not a fixture: an unreset browser-default h1 bottom margin (~0.67em)
   has no padding or border anywhere in header/main/section/footer to stop it,
   so it collapses straight through every one of them and lands as real,
   visible space between the section and the footer — with nothing above the
   heading to match it, since the same collapse escapes upward too. Gap above
   measured 0px, gap below measured 21.44px, on a page with zero custom
   spacing CSS. The same UA-default collapse applies identically to h2-h6 (all
   carry their own default vertical margin), so the reset covers all six, not
   just the one that happened to be on the page under test — the defect
   generalised, not just the instance.

   Scoped to headings only. p, ul, ol, dl, figure and blockquote are
   deliberately left at their UA defaults: .msa-prose — the wrapper every
   template puts real body copy inside — carries no CSS of its own yet, so
   those UA margins are the ONLY rhythm prose content has until the task that
   actually styles .msa-prose sets it on purpose. Zeroing them now would trade
   a measured, real defect for an unmeasured one: paragraphs and list items
   rendering flush against each other with nothing to catch it once Plan B/C
   content lands. */
h1,h2,h3,h4,h5,h6{margin:0}

/* .msa-prose establishes a block formatting context so the trailing element's
   bottom margin cannot escape the wrapper into the section — the same collapse
   that produced a real 0px-above / 21.44px-below asymmetry on the live page.
   The UA margins on p, ul, ol, dl, figure and blockquote are still untouched;
   what this rule's earlier note deferred — the heading rhythm inside prose — is
   now set below, under "prose rhythm", because a page body with sections had
   nothing holding its h2s apart. */
.msa-prose{
  display:flow-root;
  line-height:1.65;
  /* 16px was the UA default, never a decision. On a 680px column that is about 85
     characters a line — well past the 66-75 the measure is set for — and it reads small on
     a desktop page selling laboratory instruments to people who will be on it a while.
     18px above the rail's own breakpoint brings the same column to about 75 characters, so
     the type gets larger and the line gets SHORTER. Below 1100px the column is narrow
     already and 16px is right. */
  font-size:1rem;
  color:var(--stage);
  /* Body copy is client copy and can contain a string longer than a 360px column —
     the same argument style.css already makes for headings ("long-word headings")
     and for .msa-hero__message. It is not hypothetical here: adding the page's side
     gutter below took 20px off the usable width at 360px and S-STRUCT-07 immediately
     failed /contact-us/craic-worldwide-sales/, where the mailto link
     `muhammad_saeed@gulfbioanalytical.com` renders as one unbreakable 318px word.
     It had been clearing a 360px viewport by 2px. break-word is declared on the
     wrapper, and inherits, so it covers every long word in every imported body —
     emails, URLs, part numbers — rather than the one address that happened to be
     measured first. No hyphens:auto: a hyphen inserted into an email address would
     read as part of it. */
  overflow-wrap:break-word;
}

/* ============================================================== the page shell
   Every template emits the same small set of content blocks inside a bare
   <section>/<article>: .msa-h1, .msa-prose, plus <time> (News) and .msa-cards
   (Index). Until now NOT ONE of them carried a width, a gutter or any vertical
   space. Measured on /support/the-science/uv-visible-nir-microscope-design/ at
   1440px: the h1 starts at x=0 and body copy runs the full 1440px of the viewport
   with the text touching both edges of the screen. That is true of all 219
   imported pages, not of one of them.

   So the shell belongs to the TEMPLATES, not to the page being built on top of
   them. A home page given its own margins would be precisely the hand-rolled
   one-off the contract forbids ("individual pages that dont stnadardize the
   look"), and it would leave the same defect on the other 218.

   PADDING, NEVER MARGIN, on these outermost blocks. S-BALANCE-08 rule 2 measures
   a section's rendered gap above against its gap below, walking out to the
   nearest real sibling — the header's bottom edge and the footer's top edge. A
   margin on a section's first or last child COLLAPSES THROUGH the section
   (nothing here carries a border or padding to stop it) and moves the section's
   own rendered edge with it, which is the asymmetry that rule exists to catch
   and which it has already caught once on this theme. Padding sits inside the
   border box, so the section's edges do not move and the rhythm stays symmetric.

   --wrap, not --measure, on the containers: images, tables and the product tab
   strip need the full column. The reading measure is applied below, to the text
   blocks themselves. */
/* .msa-crumbs joins this list rather than restating the shell: it sat at x=0 while the title
   under it sat at x=88, because the gutter belongs to these elements and not to the <article>
   around them. One list, so a fifth element cannot drift from the other four.
   .msa-index__stand joined it for the identical reason and after the identical mistake: the
   deck line under a section title rendered flush to the viewport at x=0 while the title it
   belongs to sat at x=88. A new element beside these four needs to be IN this list, not
   styled beside it. */
.msa-crumbs,
.msa-h1,
.msa-index__stand,
.msa-prose,
.msa-cards,
.msa-news > time{
  max-width:var(--wrap);
  margin-inline:auto;
  padding-inline:var(--gutter);
}
/* The first thing on a page that has no hero. The Feature template opens with the
   hero instead, which brings its own --gap-section padding on --stage. */
.msa-h1{
  padding-block-start:var(--gap-section);
  /* The page title had NO font-size in this file. It was rendering at the UA's `2em`
     — 32px — identically at 360px and at 1440px, so the one heading on the page never
     grew with the viewport and, at 360, "Microspectrophotometer" (22 characters, this
     client's core noun, in headings right across the catalogue) could not fit on a
     line and broke mid-word without a hyphen.

     26px at 360 fits it whole (measured: 304px of a 320px column); 40px at 1440 gives
     the title the weight a page's only h1 should carry. The floor is the number that
     makes the word fit, not a round one. */
  font-size:var(--h1-size);
  line-height:var(--h1-leading);
  letter-spacing:-.01em;
  /* NO max-width here, and that is deliberate after trying one. The page shell centres these
     blocks with `margin-inline:auto`, so capping the h1's width does not shorten its line —
     it INDENTS the title, which measured x=316 against body copy at x=88. The ragged-edge
     finding this was reaching for was about paragraphs and h2-h6 alternating right edges
     inside one column; the h1 is left-aligned in the full column and its text ends where the
     words end, which is not the same defect. */
}
/* EXCEPT WHERE A RAIL IS ABOUT TO SIT UNDER ITS LAST WORD. On a railed page the title runs the
   full shell while the column beneath it is 680px and the rail starts at x=840. Measured on
   /products/508-pv-microscope-microspectrophotometer/ at 1440: the h1's text ended at x=843.4
   against a rail edge at x=840 — "Spectrophotometer" finishing 3.4px INSIDE the enquiry card's
   column, directly above its top-left corner. At 1280 and 1100 the same title clears by 27px
   and 50px, so this is the widest screen's defect and only the widest screen's.

   PADDING, NOT max-width, and the difference is measured. A max-width on this element cannot
   shorten the line: the shell rule above sets `margin-inline:auto`, so a narrower box CENTRES
   — the x=316 indent recorded in the comment above. Adding `margin-inline-start:0` to stop
   that then drags the title off the body's own left edge: measured, the ink moved from x=88 to
   x=48 while every paragraph under it stayed at 88. Reserving the space on the right instead
   leaves the left edge exactly where the shell puts it and only moves the last word.

   The reserved width is the grid's own arithmetic, in the grid's own tokens, so the two cannot
   drift: whatever the shell is, less one gutter, less the reading measure, is what the title
   must not use. At 1440 that is 1360 - 48 - 680 = 632, giving the h1 the same 680px content
   box the prose has and ending its ink at x=768 against a rail at x=840.

   Only where the rail is beside it. Below 1100 the rail stacks underneath and there is nothing
   to collide with, so the title keeps the full width.

   ONE FACT, ASKED ONCE. This was `.msa-h1:has(+ .msa-body--railed)`, on the stated grounds
   that "the templates emit <h1> then <div class='msa-body msa-body--railed'>". Three of the
   four do; template-news.php emits a <time> between them when the page records a publication
   date, and an adjacent-sibling test does not reach across it. So on 15 news pages this rule
   matched nothing and reserved 48px instead of 632px, while the rail's own lift — keyed off a
   different condition — applied regardless, and the enquiry card painted over the title's
   text on 7 of them. The template now answers "is there a rail" once, before the heading, and
   stamps `.msa-h1--railed`; the lift and the reservation cannot disagree because there is no
   longer a second question to disagree with. */
@media (min-width:1100px){
  .msa-h1--railed{
    /* --measure-block, NOT --measure. The measure is 68ch and `ch` resolves against the
       element's OWN font: on a 40px h1 that is about 1,496px, so the subtraction went
       negative and the padding computed to 0 — the rule looked applied and did nothing.
       --measure-block is the same 68ch expressed as a length (42.5rem) for precisely this
       case, and the grid rule below says so in its own comment. */
    padding-inline-end:calc(min(100%, var(--wrap)) - var(--gutter) - var(--measure-block));
  }
}
.msa-prose{padding-block:1.75rem var(--gap-section)}
/* No h1 above it, and the hero's dark block ends hard, so the body starts on the
   full section break rather than the after-a-heading gap. */
.msa-feature .msa-prose{padding-block-start:var(--gap-section)}
/* A page with no body of its own — 12 of them: the section ancestors and the Index
   pages that are only their children — must not reserve space for copy that is not
   there. :empty is exact here: the_content() on an empty page emits
   `<div class="msa-prose"></div>` with no text node at all (verified against the
   served markup). Only the TOP padding goes: the section break below is what still
   separates the page from the footer, and dropping both would leave those twelve
   pages with their heading resting on the footer's top edge. */
.msa-prose:empty{padding-block-start:0}
/* The section's bottom padding is the whole gap, and nothing may add to it. The last block
   in a body carries its own UA margin — a trailing <p> adds 16px, an <ul> 16, a <table> 0 —
   so the visible space between the copy and the footer varied across NINETEEN distinct
   values from 31.6px to 113.5px on 210 of 218 pages, against a uniform 69px above. The
   rhythm the design promises is symmetric; this is what makes the measurement match it. */
.msa-prose > :last-child,
.msa-prose > :last-child > :last-child{margin-block-end:0}
/* `.msa-prose .cols{margin-block:1.75rem}` is the same specificity as the reset above and
   comes later in the file, so it won. Measured: 62 pages ending in a two-column block had a
   100px trailing gap against the design's 72px, which is the exact leak this reset exists to
   stop. Qualified so it outranks the .cols rule rather than depending on source order. */
.msa-prose > .cols:last-child,
.msa-prose > :last-child > .cols:last-child{margin-block-end:0}
.msa-cards{padding-block-end:var(--gap-section)}
.msa-news > time{
  display:block;
  padding-block-start:.75rem;
  font-family:var(--font-mono);
  font-size:.8125rem;
  color:var(--ink-dim);
}

/* ------------------------------------------------------------- prose rhythm
   The heading reset above sets margin:0 on h1-h6 so that a collapsing UA margin
   cannot escape a section (see its own note for the measurement). That was right,
   and it left h2-h6 with NO space at all — invisible while the only heading on any
   page was the template's own single h1, and visible the moment a page body
   carries sections, as the home page does. Restated here as chosen values rather
   than restored UA defaults, and scoped inside .msa-prose, which is a block
   formatting context, so none of it can collapse back out into the section.

   Type scale: the clamp minimums are the UA sizes these headings already render
   at (1.5em / 1.17em), so nothing gets larger at 360px and no new overflow is
   introduced at the width S-STRUCT-07 is strictest about; they only grow with the
   viewport. */
.msa-prose :is(h2,h3,h4,h5,h6){margin-block:2.5rem .5rem;line-height:1.2;font-family:var(--font-display)}
/* A heading's last word does not wrap alone: "On Demand Technical Support – pay only when you
   need / it" (the v1.46.0 visual review). Balanced lines, as the hero and the section heads
   already are; a browser without text-wrap simply wraps as before. */
.msa-prose :is(h2,h3,h4,h5,h6){text-wrap:balance}
/* A superscript or subscript must not open the line it sits on: "508 PV™" and "10²–10⁶"
   raised their whole line by ~4px against the lines around them. */
.msa-prose :is(sup,sub){line-height:0}
/* The scale, re-cut once the h1 had a size of its own. It used to start at the UA's
   own 1.5em/1.17em, which was right while the h1 was also at the UA's 2em — the three
   were 32/24/19 and in proportion. With the h1 now 26px at 360 (the size at which
   "Microspectrophotometer" fits a line), a 24px h2 sits 2px under the page's only
   title, and on a product page whose body opens with a long h2 the SECOND heading
   reads as the loudest thing on the screen. Stepped down so each level is visibly
   one below the last at every width: 26/21/18 at 360, 39/29/21 at 1440. */
.msa-prose h2{font-size:clamp(1.3125rem,1.15rem + .55vw,1.8125rem)}
.msa-prose h3{font-size:clamp(1.125rem,1.05rem + .3vw,1.3125rem)}
/* h4-h6 had no size at all, so they fell to the UA's em-relative defaults: h5 renders at
   0.83em and h6 at 0.67em, both bold — SMALLER than the body copy around them. Imported
   content uses them freely (62 h4s, 46 h5s), and /support/learn/ opens on an <h5> lead
   paragraph that read as small print above the copy it introduces. A heading is never
   smaller than the text it heads. */
/* h4-h6. The rule this file states is "a heading is never smaller than the text it heads",
   and these values broke it: h5 was exactly 16px against 16px body, and h6 was 15px —
   ONE PIXEL SMALLER than the copy beneath it. They were also fixed rem values that never
   scaled, so at 320px the whole scale was 18/17/16/15, four levels inside 3px.

   h4 and h5 are now strictly above the 1rem body and both scale with the viewport. h6
   stays AT 1rem — never below it — and is separated by weight and case instead, which is
   the same device the footer's column headings and the eyebrow use. Six type sizes inside
   a 68ch column is more than a reader can tell apart; at the sixth level, case does the
   work that size cannot. */
.msa-prose h4{font-size:clamp(1.0625rem,1rem + .22vw,1.1875rem);font-weight:600}
.msa-prose h5{font-size:clamp(1.03125rem,1rem + .12vw,1.0625rem);font-weight:600}
.msa-prose h6{font-size:1.0rem;font-weight:700;letter-spacing:.02em;text-transform:uppercase}
.msa-prose > :first-child{margin-block-start:0}

/* The reading measure, applied to the text blocks and not to the container, so a
   table, a figure or a tab panel still gets the full column. Direct children only:
   a descendant selector would also cap paragraphs inside table cells and inside
   the product tab strip, which would squeeze layouts this rule has no business
   touching. Imported content that WordPress's builder wrapped in <div>s is
   therefore untouched — see the report; the typographic pass over imported bodies
   is not this task. */
/* ONE right edge for everything in the reading column, and it is a LENGTH, not a `ch`.
   `ch` is relative to the element's OWN font-size, so 68ch on a 26px h2 resolved to 1224px
   while the 16px paragraph beside it resolved to 680px — measured on the home page, three
   different right edges among the direct children of one .msa-prose: 768, 1312 and 1352, a
   584px spread. --measure stays in ch because that is the correct unit for a line of body
   text; --measure-block is the same distance expressed once, at the body size, so every
   block in the column ends where the body copy ends.

   The `> div:not([class])` and `> div.row` arms exist because 28 pages wrap their prose in a
   bare or Bootstrap-remnant div, which the direct-child selector cannot see: on /products/flex/
   consecutive paragraphs ended at x=768 and x=992 alternately, on all nine flagship instrument
   pages. `.cols` and its `.col` children are excluded — they are a deliberate grid, and the
   rule below already frees them. */
.msa-prose > :is(p,ul,ol,dl,blockquote):not(.msa-cards){max-width:var(--measure)}
.msa-prose > :is(h2,h3,h4,h5,h6){max-width:var(--measure-block)}
/* The wrapper list is measured, not guessed: these are every element class that
   actually wraps prose in this corpus. With only three of them named, 77 pages
   still rendered two to four different right edges. */
.msa-prose :is(div:not([class]), div.row, div.page-content, div.craic-tabs__body, div.ft-text, div.tpzone, div.prodtabs, div.scipg2, td, td.copy) > :is(p,ul,ol,dl,blockquote):not(.craic-tabs__kit):not(.craic-tabs__index ul):not(.craic-papers__list):not(.craic-papers__index ul){
  /* The kit and the index are rows of chips, not prose, and the reading measure is for
     prose. Capped at 748px, four chips of 380-437px each fitted one per row and the row
     became the four stacked lines the grouping existed to remove. */
  max-width:var(--measure);
}
.msa-prose :is(div:not([class]), div.row, div.page-content, div.craic-tabs__body, div.ft-text, div.tpzone, div.prodtabs, div.scipg2, td, td.copy) > :is(h2,h3,h4,h5,h6){
  max-width:var(--measure-block);
}

/* Links. Brand red at body size measures 4.16:1 on white — the same arithmetic
   that governs white ON the red — so it clears AA as large text only and CANNOT
   be the colour of a 16px link. The red goes into the underline, where it is
   decoration and carries no contrast requirement, and the text itself stays
   --stage at ~19:1. Hover thickens the rule rather than recolouring the text,
   for the same reason: a hover state that fails AA is still a state that fails
   AA.

   THE :not() IS LOAD-BEARING, not tidiness. `.msa-prose a` is one class plus one
   type, which outranks the bare `.btn` class: without the exclusion this rule
   repaints every call-to-action button in the imported content --stage ON --red,
   which is near-black on red — the combination S-ONRED-10 exists to fail, because on
   #EE2E24 the readable choice is white. The buttons carry their own colour contract
   (19px/700 white on red) and this rule must not reach them.

   `.msa-card__link` is excluded for the same reason and not a different one: a card is
   a whole block, not a run of words, and it carries its own underline contract — the
   rule appears on hover, under the title only. Without the exclusion this selector wins
   on specificity and underlines the card's title AND its description, on each of the
   home page's five card grids. */
/* THE PAPERS LIBRARY BRINGS ITS OWN. `.craic-papers__list a` and `.craic-papers__index a` are a
   request control and a jump chip, both styled by the plugin that renders them; without the
   exclusion this rule out-specifies both and repaints 164 request links in the prose idiom. */
.msa-prose a:not(.btn):not(.btn--ghost):not(ul[class] a):not(:has(figure)):not(.msa-solve__tool):not(.msa-coverage__field):not(.msa-coverage__cell):not(.craic-tabs__index a):not(.craic-tabs__kit a):not(.craic-papers__list a):not(.craic-papers__index a):not(.tablinks--chips a){
  color:var(--stage);
  text-decoration-line:underline;
  text-decoration-color:var(--red);
  text-decoration-thickness:2px;
  text-underline-offset:.16em;
}
/* A LIST OF LINKS IN PROSE IS STILL PROSE. `:not(ul[class] a)` above exists to keep this rule
   out of the card grids and figure lists, where a link is a component and not a sentence — and
   it does that by excluding EVERY ul that carries a class, which caught two lists that are
   neither. Measured across the Solutions tree: 13 links rendering in the browser's default
   blue, #0000EE, on three pages — the three "learn more" articles at the foot of
   /solutions/microspectrophotometer-options/ (ul.is-linklist, which craic-tabs styles only
   inside a panel, and this page has no panels) and the ten in the link runs that
   import/clean-bodies.py rule 21 built last release (ul.msa-linkrun). Blue underlined links on
   a site whose link colour is ink with a red rule read as an unstyled page.

   Named rather than un-excluded: the two classes are prose lists by definition — every item is
   one link and nothing else — and adding them back by name cannot reach a component. */
.msa-prose :is(ul.is-linklist, ul.msa-linkrun) a{
  /* AND THE 24px HIT BOX, for the same reason these needed a colour: the rule that hands one to
     a standalone link excludes any anchor inside a classed <ul>, and craic-tabs styles
     .is-linklist only INSIDE a panel — so on a page with no panels these got neither. Measured
     by S-CONTROL-68 at 390px: 'CRAIC Apollo™ MicroRaman Spectrometer' 22px tall. A list of
     destinations is a list of controls. */
  display:inline-block;
  padding-block:.25rem;
  min-inline-size:24px;
  color:var(--stage);
  text-decoration-line:underline;
  text-decoration-color:var(--red);
  text-decoration-thickness:2px;
  text-underline-offset:.16em;
}
.msa-prose :is(ul.is-linklist, ul.msa-linkrun) a:hover{text-decoration-thickness:4px}
.msa-prose :is(ul.is-linklist, ul.msa-linkrun) a:focus-visible{
  text-decoration-thickness:4px;
  outline:3px solid var(--stage);
  outline-offset:2px;
}
.msa-prose a:not(.btn):not(.btn--ghost):not(ul[class] a):not(:has(figure)):hover,
.msa-prose a:not(.btn):not(.btn--ghost):not(ul[class] a):not(:has(figure)):focus-visible{text-decoration-thickness:4px}
.msa-prose a:not(.btn):not(.btn--ghost):not(ul[class] a):not(:has(figure)):focus-visible{
  outline:3px solid var(--stage);
  outline-offset:2px;
}

/* The measuring aperture, used small — spec §3.3 names the list bullet as one of
   the two places it is spent. A square, because the aperture is a square; the
   octagon is the optical head and is spent once per page, large, on the hero
   plate.

   ::marker, not a positioned ::before. The first version drew the square as an
   absolutely-positioned pseudo-element at left:-1.1rem, which only lands correctly
   inside a list that has the theme's own start padding. craic-tabs owns lists too:
   `.craic-tabs__body ul.is-linklist` sets `list-style:none; padding:0` at a higher
   specificity than any rule here, so a positioned marker would have been drawn
   1.1rem OUTSIDE those lists, on top of whatever sits to their left, on every
   product page carrying a link list. ::marker cannot do that — it is the list's own
   marker box, so a list that has turned its markers off simply has nothing here to
   colour, and the plugin's layout is left entirely alone. */
.msa-prose ul{list-style-type:square}
/* Except a row of chips, which carries its own mark inside each chip. The aperture marker is
   right for a list of sentences; on the kit and the index it drew a second red square outside
   each row, so every chip had two. */
.msa-prose :is(ul.craic-tabs__kit, .craic-tabs__index ul, ul.craic-papers__list,
               .craic-papers__index ul, ul.msa-apptech__kit){list-style:none}
.msa-prose :is(ul.craic-tabs__kit, .craic-tabs__index ul, ul.craic-papers__list,
               .craic-papers__index ul, ul.msa-apptech__kit) > li::marker{content:""}
.msa-prose li::marker{color:var(--red)}
.msa-prose li{margin-block:.4rem}

/* Card grid — the Index template's ul.msa-cards (template-index.php), one card
   per child page. template-index.php already stamps the real item count onto
   data-count at render time, so the column count can be CHOSEN per count rather
   than fixed to whatever a test fixture happened to have. S-BALANCE-08 forbids
   an orphan row: a column count that does not divide the item count evenly
   ("8 items -> 4x2, never 5+3").

   Mobile-first default is a single column. That is not a placeholder — one
   column divides ANY item count with no remainder, so it is the correct,
   permanently-safe layout, not just the smallest one, and it is also the right
   stacked layout on a narrow viewport regardless of balance.

   At >=768px, up to 4 columns — the rule's own "4x2" example sets that ceiling,
   and wider than 4 short cards gets cramped inside --wrap's 1360px. For each
   count the WIDEST divisor of that count in {2,3,4} is used, so the grid is as
   dense as it can be without ever leaving a short last row. Counts with no
   divisor in {2,3,4} — 1, 5, 7, 11, 13, and anything above 16 not enumerated
   below — fall through to the single-column default. That is not a compromise:
   a prime count of cards has no arrangement into 2-4 columns that avoids an
   orphan row, so one column is the only correct layout for those counts, not a
   fallback for one this rule couldn't solve. Above 16 children on one Index
   page is outside what this site's actual content (nine instruments total, per
   the audit) will ever produce; extending the table costs one line per count if
   that changes. */
.msa-cards{
  display:grid;grid-template-columns:1fr;gap:1rem;list-style:none;
  /* A ROW IS AS TALL AS ITS OWN CARD. The default `stretch` was invisible while every
     card was one line; now that a card opens, its row-mates stretched to match it and
     stood as 200px boxes holding one title and a lot of nothing. */
  align-items:start;
  /* NEITHER SHORTHAND. Both `padding:0` and `margin:0` were written here to clear the
     UA list defaults, and BOTH destroyed a property the page-shell rule above sets on
     this same selector — `padding-inline:var(--gutter)` and `margin-inline:auto`, at
     identical specificity, so the later shorthand simply wins.

     The padding half was found and fixed first: the cards started at x=0 and touched
     the footer on all thirteen Index pages. The margin half was left, and it is worse,
     because it scales with the viewport. Measured on /support/ before this fix:

         1440   h1 ink at x=88    grid at x=0     right gutter 128px
         1920   h1 ink at x=328   grid at x=0     right gutter 608px
         2560   h1 ink at x=648   grid at x=0     right gutter 1248px

     At 1920 the header, the h1 and the footer all align at x=328 while the card grid
     alone starts at x=48 — the page's own title 280px to the right of the content it
     names, on the thirteen pages that carry the site's primary navigation.

     Fixing one instance of a class and leaving the other in the same declaration is
     the exact failure this project has a rule about. Neither reset was needed: the UA
     indent is `padding-inline-start` and the UA margin is block-only, and the shell
     rule's own `padding-inline` and `margin-inline` override both. */
  /* block-START only. `margin-block:0` here would undo the head gap declared above in the
     same rule — the shorthand-eats-the-longhand failure this rule's own comment is about,
     one property along. */
  margin-block-end:0;
  padding-block-start:0;
}
/* One grid at every count, and the last row is FILLED rather than left ragged.

   The table this replaced could only choose a column count that DIVIDED the item
   count, so a count with no divisor in {2,3,4} fell through to a single column:
   /support/ rendered 12 cards four across while /support/learn/ rendered its 11 in
   one column directly beneath it, and /product-service/'s 91 made a column 91 cards
   long. Same grid everywhere is the point of having templates at all.

   The remainder is stamped by template-index.php (CSS cannot compute count % cols).
   A tail of 1 widens the last card to the full row; a tail of 2 widens the
   second-to-last, leaving the pair to fill three tracks. Either way no row is short,
   which is the property S-BALANCE-08 rule 1 exists to hold. That rule USED to test
   `items % cols`, which is only a proxy: it would have called this grid an orphan row
   because 3 does not divide 13, while the rendered grid has no short row at all. It
   now measures the last row's rendered span against the track set, and it was mutation
   tested against this page — with the span removed the last row falls 853px short of a
   1,264px track set and the gate fails. Stricter about the real property, not looser. */
/* minmax(0,1fr), never a bare 1fr. `1fr` is `minmax(auto,1fr)`, so a card whose title is a
   long unbroken product name floors its own track and the columns come out unequal — measured
   on the home page, a four-card grid rendered 458/458/316 and another 526.5/526.5/179. This is
   trap 2 in the project's own list and it had been paid for twice already. */
@media (min-width:700px){
  .msa-cards{grid-template-columns:repeat(2,minmax(0,1fr))}
  .msa-cards[data-tail2="1"] > .msa-card:last-child{grid-column:span 2}
}
@media (min-width:1100px){
  .msa-cards{grid-template-columns:repeat(3,minmax(0,1fr))}
  /* Every :not() here is load-bearing, not tidiness. A span rule that still matches forces an
     IMPLICIT extra track, so a two-column grid silently renders three — which is exactly what
     a four-card grid did after the two-column rule below was added and these were left
     unguarded: the rule applied, and the surviving `span 3` put the third column back. */
  .msa-cards[data-tail2="1"]:not([data-count="1"]):not([data-count="2"]) > .msa-card:last-child{grid-column:auto}
  /* The tail card fills the row so there is no orphan, but it must not READ as a divider:
     measured at 1264 x 60 it was 21:1, holding 180px of text. `justify-self:start` with the
     track width as a cap keeps the row full (the grid cell still spans) while the card itself
     stays the size of its peers. */
  .msa-cards[data-tail3="1"]:not([data-tail2="0"]):not([data-count="1"]):not([data-count="2"]) > .msa-card:last-child{
    grid-column:span 3;
  }
  /* The CELL spans so the row is full and there is no orphan; the CARD stays the size of its
     peers and is centred in that cell. A card widened to the whole track set measured 1264 x
     60 — 21:1, holding 180px of text, reading as a divider rather than as a peer. Left-aligned
     in the spanned cell it reads as the ragged row the span exists to prevent. Centred, a
     final odd card reads as the deliberate end of a list, which is what it is. */
  /* THE SAME GUARD AS THE SPAN ABOVE. These two rules cap the tail card so it stays the size
     of its peers inside a spanned cell — which is only meaningful when the cell IS spanned.
     The span rules carry `:not([data-tail2="0"])` because a count two divides renders 2-up
     with no span at all; these, their other half, did not. Measured on the home page: the
     four-card Techniques and Service grids render 2x2 (correctly, tracks 624px 624px), and
     this rule still capped the last card at calc(33.3333% - 10.6667px) — a 205px card beside
     a 624px one, a hole in the middle of the row, and "microspectroscopy" broken mid-word as
     "microspectroscop / y". A guard added to one half of a pair is the defect the pair exists
     to prevent. */
  /* AND THE DISCLOSURE CARD IS A CARD. These caps named `.msa-card__link` only — the plain
     link card — so on /solutions/, whose seven children render as `.msa-card__open`
     disclosures, the cap matched nothing and the tail card filled its spanned cell: measured
     1264px beside six of 411. Six neat boxes and then one the width of the page is the first
     thing an eye lands on. The note above this pair says a guard added to one half of a pair
     is the defect the pair exists to prevent; the same is true of a card VARIANT. */
  .msa-cards[data-tail3="1"]:not([data-tail2="0"]):not([data-count="1"]):not([data-count="2"]) > .msa-card:last-child > :is(.msa-card__link,.msa-card__open){
    max-width:calc((100% - 2rem) / 3);
    margin-inline:auto;
  }
  .msa-cards[data-tail3="2"]:not([data-tail2="0"]):not([data-count="1"]):not([data-count="2"]) > .msa-card:nth-last-child(2) > :is(.msa-card__link,.msa-card__open){
    max-width:calc((200% - 1rem) / 3);
    margin-inline:auto;
  }
  .msa-cards[data-tail3="2"]:not([data-tail2="0"]):not([data-count="1"]):not([data-count="2"]) > .msa-card:nth-last-child(2){grid-column:span 2}
  /* A grid too small to fill one row gets a row its own size instead of a spanned tail.
     /contact-us/ has two cards, and the tail rule rendered them 837px and 411px — two
     siblings at different widths for no reason a reader can infer. Three columns is the
     shape for a grid with something to arrange; one or two cards have nothing to arrange. */
  .msa-cards[data-count="1"]{grid-template-columns:minmax(0,1fr)}
  .msa-cards[data-count="2"]{grid-template-columns:repeat(2,minmax(0,1fr))}
  .msa-cards[data-count="1"] > .msa-card,
  .msa-cards[data-count="2"] > .msa-card{grid-column:auto}
  /* A count that three does not divide but two does gets TWO columns and no span at all.
     The span is the honest answer for a count no small number divides — 13 products, 11
     explainers, the 91 under /product-service/ — but it was also being spent on 4, 8 and
     14, where it produced a 1264px card holding a 180px label beside three 411px ones: a
     21:1 letterbox that reads as a mistake rather than a decision. Now 4 renders 2x2, 8
     renders 2x4, and only the genuinely indivisible counts widen a card. */
  .msa-cards[data-tail2="0"]:not([data-tail3="0"]):not([data-count="1"]):not([data-count="2"]){
    grid-template-columns:repeat(2,minmax(0,1fr));
  }
  .msa-cards[data-tail2="0"]:not([data-tail3="0"]) > .msa-card{grid-column:auto}
}

/* The card itself. There was no rule for one: `.msa-card` and `.msa-card a` matched
   nothing in this file, and `.msa-prose a` cannot reach them because the grid is a
   sibling of .msa-prose, not inside it. Every card on every Index page therefore
   rendered as a UA-default underlined #0000EE link on white — the one place in the
   build where the browser's stylesheet was still showing through.

   The whole card is the hit area, not the words: a title-only target is a 3mm-tall
   tap target on a phone, and these ARE the site's navigation for the 139 pages the
   menu does not carry. */
.msa-card{display:flex}

/* ONE LEFT EDGE INSIDE A TAB PANEL.
   An uncaptioned figure is centred in the containers the import produced, which is right where
   the figure is the whole of its block. In a tab panel it is not: the copy beside it is capped
   at the reading measure and starts at the panel's padding, so a 576px photograph centred in a
   1198px panel put its middle at x=720 while the paragraph above it ran 121→869. Two centres,
   one panel. The figure takes the copy's left edge instead. */
/* SIX TABS, ONE ROW. The strip needs 1,288px for its six buttons inside 1,262px, so the last
   one — "Applications Papers" — dropped to a second row on its own, left-aligned under the
   first. 26px is the whole difference; trimming each button's side padding by 3px recovers 36
   and the strip holds one line. The type is untouched: the labels are the client's own product
   names and they do not get smaller to fit. */
/* The buttons carry no class of their own — they are bare <button role="tab"> — so a
   `.craic-tabs__btn` selector matched nothing at all. */
.craic-tabs__list [role="tab"]{padding-inline:0.96rem}

/* ------------------------------------------------- comparing three instruments
   The options page exists so a buyer can choose between the 508 PV, the FLEX PRO and the
   2030PV PRO, and it presented them one after another down 3,300px of a page whose right half
   was empty — the two things being compared were never on screen together. Three columns, so
   the comparison is a glance rather than a scroll.

   The full width is the point: this page uses the feature template, which has no rail, and the
   copy was capped at the reading measure while 600px sat unused beside it. Three columns spend
   that width on the comparison instead of leaving it blank.

   Each column carries the instrument's OWN product photograph — the white-ground shot its
   product rail already uses. The originals had a dark frame and a blue gradient label baked
   into the JPEG at 550x1069, so no rule here could have made them agree with the rest of the
   site. */
.msa-compare{
  display:grid;
  grid-template-columns:1fr;
  gap:2.5rem 2rem;
  align-items:start;
  margin-block:var(--gap-head) var(--gap-section);
  max-width:none;
}
@media (min-width:760px){ .msa-compare{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media (min-width:1100px){ .msa-compare{grid-template-columns:repeat(3,minmax(0,1fr))} }
.msa-compare__item{display:grid;gap:.75rem;align-content:start;min-width:0}
/* The photograph sits on the page, not in a box: these are white-ground studio shots and
   multiply drops their background into the page exactly as the product rails do. */
.msa-compare__shot{margin:0}
/* CAPPED BY HEIGHT, NOT WIDTH. These are portrait studio shots — 540x750 and taller — so a
   width cap still left 520px of instrument standing above the first word of the description,
   and three columns of that is a wall of microscopes with the comparison underneath it. The
   height is what needs holding; the width follows. */
.msa-compare__shot img{
  display:block;
  inline-size:auto;
  max-inline-size:100%;
  block-size:auto;
  max-block-size:230px;
  mix-blend-mode:multiply;
}
.msa-compare__name{
  margin:0;
  font-family:var(--font-display);
  font-size:1.375rem;
  font-weight:600;
  line-height:1.2;
  color:var(--stage);
}
.msa-compare__lede{margin:0;font-size:1rem;line-height:1.55;max-width:none}
/* "Key Features:" was an <h6> in capitals with a colon — a heading level used for a label, and
   the last level in the document outline used for the first thing under a name. It is a label
   now, and reads as one. */
.msa-compare__label{
  margin:.35rem 0 0;
  font-family:var(--font-mono);
  font-size:.6875rem;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--ink-dim);
}
.msa-compare__specs{
  margin:0;
  /* ROOM FOR THE MARKER. `padding:0` put the list marker outside the content box, which is
     invisible in the first column of a grid that starts at the page edge — the 508 PV column
     showed no bullets while the two beside it did, on identical markup. The theme's own square
     marker is kept; it only needs somewhere to sit. */
  padding-inline-start:1.1rem;
  display:grid;
  gap:.35rem;
  font-size:.9375rem;
  line-height:1.5;
  max-width:none;
}
/* NO BULLET OF ITS OWN. `.msa-prose` already draws the aperture square on every list item, so
   adding one here put two red squares in front of every feature. The list keeps the site's
   marker; this only sets the rhythm between items. */
.msa-compare__specs li{margin:0}
.msa-compare__more{
  justify-self:start;
  margin-block-start:.35rem;
  font-size:.9375rem;
  font-weight:600;
  color:var(--stage);
  text-decoration:underline;
  text-decoration-color:var(--red);
  text-decoration-thickness:2px;
  text-underline-offset:3px;
}
.msa-compare__more:hover{text-decoration-color:var(--stage)}
.msa-compare__more:focus-visible{outline:3px solid var(--stage);outline-offset:3px}

/* ------------------------------------------------- a card that opens
   /solutions/ listed seven pages as bare titles: a reader had to guess what "CRAIC Solutions"
   or "Raman for semiconductor" contained, or click and find out. Each card now says what its
   page is about and offers the page itself — the client's own opening sentence, stored as the
   page's excerpt so they can rewrite it.

   <details> rather than a button wired to aria-expanded and a hidden region. The browser
   supplies the open state, the keyboard behaviour and the semantics; nothing here needs a
   script, and a reader without one still gets every word. The link lives INSIDE the panel, so
   the click that opens a card and the click that leaves the page are never the same click. */
.msa-card__open{
  inline-size:100%;
  background:var(--white);
  border:1px solid #E1E2E5;
  border-inline-start:3px solid var(--red);
  transition:border-color .12s ease, box-shadow .12s ease;
}
.msa-card__open[open]{border-color:#CFD1D6;box-shadow:0 1px 0 rgba(13,14,16,.06)}
.msa-card__summary{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:1rem;
  padding:1.15rem 1.25rem;
  cursor:pointer;
  list-style:none;                       /* the UA triangle; ours is the sign below */
  /* EVERY CLOSED TILE THE SAME SIZE. Seven cards whose titles run from "CRAIC Solutions" to
     "Microscope Photometer Applications" gave seven different heights, and a grid of mismatched
     boxes reads as broken rather than as a set — the client's word for it was discordance. The
     floor is set from the two-line case, so a one-line title has the same box as a two-line one
     and the row is level whatever the copy does. */
  min-block-size:5.25rem;
  align-items:center;
}
.msa-card__summary::-webkit-details-marker{display:none}
.msa-card__summary:focus-visible{outline:3px solid var(--stage);outline-offset:-3px}
.msa-card__summary:hover .msa-card__title{text-decoration-color:var(--red)}

/* THE SIGN IS THE APERTURE, TURNING. Closed it is the square this site puts wherever it says
   "here is a thing"; open it rotates to a diamond. One mark, two states — no plus, no
   chevron, nothing borrowed from a component library. */
.msa-card__sign{
  inline-size:9px;
  block-size:9px;
  flex:0 0 auto;
  background:var(--red);
  transition:transform .16s ease;
}
.msa-card__open[open] .msa-card__sign{transform:rotate(45deg)}
@media (prefers-reduced-motion:reduce){
  .msa-card__sign{transition:none}
}

.msa-card__panel{
  padding:0 1.25rem 1.15rem;
  display:grid;
  gap:.85rem;
  justify-items:start;
}
.msa-card__panel .msa-card__blurb{margin:0;font-size:.9375rem;line-height:1.55;color:var(--ink-dim)}
/* The way out of the card. Underlined because it is a link and this is body-sized text, in
   the ink rather than the red: the red is the mark, and a row of red links would make seven
   cards shout at once. */
.msa-card__more{
  font-size:.9375rem;
  font-weight:600;
  color:var(--stage);
  text-decoration:underline;
  text-decoration-color:var(--red);
  text-underline-offset:3px;
  text-decoration-thickness:2px;
}
.msa-card__more:hover{text-decoration-color:var(--stage)}
.msa-card__more:focus-visible{outline:3px solid var(--stage);outline-offset:3px}
.msa-card__link{
  display:flex;flex-direction:column;gap:.35rem;
  inline-size:100%;
  padding:1.15rem 1.25rem;
  background:var(--white);
  border:1px solid #E1E2E5;
  /* The aperture again, used small: a red edge on the leading side, the same mark the
     list bullets carry. It thickens on hover rather than the card changing colour —
     brand red is mass for the single action, and thirteen cards are not that. */
  border-inline-start:3px solid var(--red);
  color:var(--stage);
  text-decoration:none;
  transition:border-color .12s ease,box-shadow .12s ease,transform .12s ease;
}
.msa-card__title{
  font-family:var(--font-display);
  font-weight:600;
  font-size:1.0625rem;
  line-height:1.25;
  text-decoration:underline;
  text-decoration-color:transparent;
  text-decoration-thickness:2px;
  text-underline-offset:.16em;
  transition:text-decoration-color .12s ease;
}
.msa-card__blurb{font-size:.9375rem;line-height:1.45;color:var(--ink-dim)}
.msa-card__link:hover{
  border-color:#C9CBD0;
  border-inline-start-color:var(--red);
  box-shadow:0 2px 10px rgba(var(--stage-rgb),.09);
  transform:translateY(-1px);
}
.msa-card__link:hover .msa-card__title{text-decoration-color:var(--red)}
.msa-card__link:focus-visible{
  outline:3px solid var(--stage);
  outline-offset:2px;
}
@media (prefers-reduced-motion:reduce){
  .msa-card__link{transition:none}
  .msa-card__link:hover{transform:none}
}

/* Hero (inc/hero.php) — the h1 is small and the rotating message is large, which is
   the split spec §4.1 asks for: document hierarchy and visual hierarchy are separate
   things, so the crawler's one title need not be the biggest type on the page.

   The message stack is the load-bearing part. Every message sits in the SAME grid
   cell (grid-area:1/1), so the cell is as tall as the tallest message and the box
   reserves that height before a single line of JavaScript runs. The surplus messages
   are visibility:hidden, not [hidden]/display:none: a display:none box contributes no
   height, so the reservation would silently become "as tall as message one" and
   revealing a longer message would push the page down — cumulative layout shift on
   the largest element above the fold, which is precisely what the speed tenet
   forbids. visibility:hidden keeps the box, and takes the text out of the
   accessibility tree, so a screen reader still reads exactly one message.

   Consequence worth stating: the reveal is a visibility swap and nothing else. No
   height, position or content is computed anywhere, in CSS or in script, so the
   layout is identical whichever message is showing and with scripts off entirely. */
.msa-hero{
  position:relative;
  overflow:hidden;          /* also a block formatting context: no hero margin escapes
                               into .msa-feature, which S-BALANCE-08 measures */
  background:var(--stage);
  color:var(--paper);
}

/* ------------------------------------------------------- the wall (spec §4.3, §4.4)
   Different COUNTS per breakpoint, not different sizes. Every frame in the pool is in
   the served HTML; the surplus ones are display:none, and because every frame is also
   loading="lazy", a hidden frame never intersects the viewport and is never fetched.
   That is BROWSER behaviour, not something this stylesheet can promise, which is why
   S-HEROIMG-19 counts real network requests at each width instead of reading these
   rules back — and it is why the hold rule below exists, because these rules alone were
   measured NOT to be enough.

   The four tiers are spec §4.4's own: 6 frames in 3 columns, 12 in 4, 15 in 5, 18 in 6.
   Every one of those pairs divides exactly — 6/3, 12/4, 15/5, 18/6 — so the visible
   grid never carries an orphan row, which is the balance rule this client treats as
   contractual. inc/hero.php stamps the same table onto data-msa-hero-wall so the gate
   reads the contract off the page rather than restating it here a second time.

   nth-child, not nth-of-type or a class: the per-load shuffle reorders the frames in
   the DOM, so "which N are shown" has to follow SOURCE ORDER or the shuffle would
   change nothing.

   That is also the trap. "The shuffle runs before first layout, so the rules below only
   ever see the final order" was the assumption here, and it is FALSE: Chromium performs
   rendering updates DURING parsing, so the first N frames in the SERVED order can be
   laid out — and start fetching — while the parser is still building the rest of the
   wall. Measured at 1440px, 1 load in 8 fetched 23 images instead of 19, every leaked
   frame served inside the tier and shuffled out of it. The hold rule below is what
   actually makes this safe; these tier rules never see the served order at all. */
.msa-hero__wall{
  position:absolute;
  top:0;
  left:0;
  right:0;
  z-index:0;
  display:grid;
  grid-template-columns:repeat(3,1fr);
  grid-auto-rows:minmax(0,1fr);
  /* The aspect ratio is cols/rows, so a cell is SQUARE at every tier — 3x2, 4x3, 5x3,
     6x3. It is not decoration. Stretching the wall to inset:0 instead makes the row
     height whatever the copy left over: measured at 360px that was a 120x508 cell,
     which object-fit:cover fills by scaling a 200px micrograph four times vertically.
     Square cells also put the frames at the size these micrographs were actually
     published at, which is the one thing the v3 mockup asked not to be dishonest about.
     Consequence: the wall no longer reaches the foot of a tall hero, so it is masked
     out over its own bottom half rather than ending on a visible seam — that fade has
     to follow the wall, not the scrim, because where it ends depends on the copy. */
  aspect-ratio:3/2;
  -webkit-mask-image:linear-gradient(180deg,#000 46%,transparent 100%);
  mask-image:linear-gradient(180deg,#000 46%,transparent 100%);
}
.msa-hero__wall img{
  display:none;
  width:100%;
  height:100%;
  object-fit:cover;
  /* Knocked back rather than left at full strength: the wall is the texture the copy
     sits on, and a legible micrograph behind a headline competes with it. */
  filter:grayscale(.16) brightness(.54) contrast(1.04);
}
.msa-hero__wall img:nth-child(-n+6){display:block}
@media (min-width:640px){
  .msa-hero__wall{grid-template-columns:repeat(4,1fr);aspect-ratio:4/3}
  .msa-hero__wall img:nth-child(-n+12){display:block}
}
@media (min-width:900px){
  .msa-hero__wall{grid-template-columns:repeat(5,1fr);aspect-ratio:5/3}
  .msa-hero__wall img:nth-child(-n+15){display:block}
}
@media (min-width:1240px){
  .msa-hero__wall{grid-template-columns:repeat(6,1fr);aspect-ratio:6/3}
  .msa-hero__wall img:nth-child(-n+18){display:block}
}

/* The wall is not rendered until its frames have been shuffled — see the wp_head marker
   in inc/hero.php for the measurement behind this. display:none on the CONTAINER, not
   visibility or opacity on the frames: a hidden box still intersects the viewport and
   still fetches its lazy image, and it is the fetching that has to be prevented. It also
   deliberately does not touch the four tier rules above, so there is nothing here to keep
   in step with them.

   [data-msa-js] is set by an inline script in the head, before the wall is parsed. With
   scripting off it is never set, this rule never matches, and the wall renders straight
   from the served order under the same tier rules — the no-JavaScript hero is a complete
   one, not a blank space. */
[data-msa-js] .msa-hero__wall[data-msa-hero-hold]{display:none}

/* The scrim is a background-IMAGE (a gradient), never a background-colour: S-ONRED-10
   walks up from every text run to the first non-transparent background-color and would
   otherwise stop at a translucent layer and report the run AMBIGUOUS. The copy's first
   real background stays --stage, which is what it visually sits on.

   Solid at the foot, so the wall's bottom edge is never a visible seam — the same job
   the v3 mockup gave a separate .fade element, done in the one gradient. */
.msa-hero__scrim{
  position:absolute;
  inset:0;
  z-index:1;
  background:linear-gradient(180deg,
    rgba(var(--stage-rgb),.88) 0%, rgba(var(--stage-rgb),.90) 34%,
    rgba(var(--stage-rgb),.96) 72%, rgba(var(--stage-rgb),1) 100%);
}
@media (min-width:900px){
  /* Wide enough for a directional scrim: dense on the left where the copy is, opening
     up on the right where the wall shows past the octagon. */
  .msa-hero__scrim{
    background:
      linear-gradient(0deg,rgba(var(--stage-rgb),1) 0%,rgba(var(--stage-rgb),0) 34%),
      linear-gradient(96deg,rgba(var(--stage-rgb),.99) 0%,rgba(var(--stage-rgb),.975) 34%,
                      rgba(var(--stage-rgb),.91) 53%,rgba(var(--stage-rgb),.70) 72%,rgba(var(--stage-rgb),.58) 100%);
  }
}

/* ------------------------------------------------------------------- hero contents */
.msa-hero__inner,
.msa-hero__counters{
  position:relative;        /* above the wall and the scrim without a stacking hack */
  z-index:2;
  max-width:var(--wrap);
  margin-inline:auto;
}
.msa-hero__inner{
  padding:var(--gap-section) var(--gutter) 0;
  display:grid;
  gap:3rem;
}
@media (min-width:900px){
  .msa-hero__inner{grid-template-columns:minmax(0,1fr) 400px;gap:3.5rem;align-items:center}
}
@media (min-width:1240px){
  .msa-hero__inner{grid-template-columns:minmax(0,1fr) 520px;gap:5.5rem}
}
/* min-width:0 — as a grid child the copy column otherwise refuses to shrink below the
   min-content width of its longest word and drags the hero sideways on a handset,
   which S-STRUCT-07 fails the whole page for. */
.msa-hero__copy{min-width:0;display:grid;gap:1.5rem;align-content:start}
.msa-hero__title{
  font-family:var(--font-mono);
  font-size:.875rem;
  font-weight:600;
  letter-spacing:.06em;
  /* THE SAME MARK EVERY OTHER EYEBROW CARRIES. This one was grey text with no aperture square,
     so the most important eyebrow on the site was the only one that did not look like one —
     it read as a stray line of small print above the headline. The square is drawn here rather
     than added to the markup because this element is an <h1>, and its text is the accessible
     name of the page. --red on --stage measures 4.7:1, above the 4.5 floor at this size. */
  display:flex;
  align-items:center;
  gap:11px;
  color:var(--red);         /* 5.8:1 on --stage. --dim-ink is a body-colour token and would
                               measure 3.1:1 here, which is why this was never --dim-ink. */
}

/* THE HERO WITH NO MESSAGE UNDER IT. On the front page the h1 is an eyebrow introducing
   three 52px lines; on an interior Feature page those lines are the site's pitch and no
   longer render (inc/hero.php), so the same 14px --dim treatment left the page's own name
   as the smallest, faintest thing in a 520px hero. Here the h1 takes the job the messages
   had: the message size, the message colour, the message measure. Same element, same one
   h1 per page — only the role changed, so nothing about the document outline moves. */
.msa-hero__title::before{
  content:"";
  inline-size:8px; block-size:8px;
  flex:0 0 8px;
  background:var(--red);
}
/* The lead variant IS the headline, not an eyebrow: no mark, no flex row. */
.msa-hero--lead .msa-hero__title::before{ content:none; }
.msa-hero--lead .msa-hero__title{
  display:block;
  color:var(--on-stage, #fff);
  font-family:inherit;
  font-size:clamp(2rem, 1.15rem + 3.6vw, 3.25rem);
  line-height:1.06;
  letter-spacing:-.02em;
  color:var(--on-stage, #fff);
  max-inline-size:var(--measure-block);
  text-wrap:balance;
}
/* THE SLACK FALLS ABOVE THE HEADLINE, NOT BELOW IT. Ten client statements share one grid cell
   so the row reserves the tallest — which is right, and is why this page measures CLS 0 — but
   six of the ten are two lines in a box sized for three, and with the default `stretch` the
   spare 50px opened between the headline and the lede it introduces. Aligning to the end puts
   that space between the eyebrow and the headline, where a gap reads as breathing room rather
   than as a missing line. */
.msa-hero__messages{display:grid;align-items:end}
.msa-hero__message{
  grid-area:1/1;
  margin:0;
  visibility:hidden;
  font-family:var(--font-display);
  font-size:clamp(1.75rem,1rem + 3.4vw,3.25rem);
  font-weight:600;
  line-height:1.1;
  color:var(--white);
  max-width:22ch;
  /* A message is client copy and can contain anything, including a word longer than
     a 360px column. S-STRUCT-07 fails the whole page on one pixel of horizontal
     overflow, so the break is declared here rather than left to the copy. */
  overflow-wrap:break-word;
}
.msa-hero__message[data-msa-hero-active]{visibility:visible}

/* ------------------------------------------------- the focal octagon (the LCP element)
   The plate is the optical head: a red octagon with the micrograph clipped to the same
   octagon inside it. This is the ONE place --oct is spent at size; the buttons cut
   their own chamfer rather than borrow it, so the mark never competes with itself.

   aspect-ratio:1 is the box reservation. The plate's height is derived from its width
   by the stylesheet, so the box exists before the image has a single byte and cannot
   move when it decodes — balance is a contract term here, and this is the largest
   element above the fold. */
.msa-hero__figure{min-width:0}
@media (min-width:900px){.msa-hero__figure{justify-self:end;width:100%}}
.msa-hero__slides{display:grid}
.msa-hero__slide{
  grid-area:1/1;
  margin:0;
  opacity:0;
  /* visibility is switched with no duration, but DELAYED by the fade on the way out,
     so the outgoing slide stays in the accessibility tree for exactly as long as it
     is still on screen and no longer. Transitioning opacity alone would leave a
     fully transparent slide readable to a screen reader forever. */
  visibility:hidden;
  transition:opacity .6s ease,visibility 0s linear .6s;
}
.msa-hero__slide[data-msa-hero-active]{
  opacity:1;
  visibility:visible;
  transition:opacity .6s ease,visibility 0s linear 0s;
}
@media (prefers-reduced-motion:reduce){
  .msa-hero__slide,.msa-hero__slide[data-msa-hero-active]{transition:none}
}
/* The FIGURE keeps its box on every slide — that is what reserves the height of the
   longest CAPTION, so a slide change cannot move the page. It is the IMG inside the
   inactive slides that is display:none, and that is deliberate: a lazy image with no
   box is never fetched, so slides two and three do not spend the focal budget three
   times over at load. The timer stamps data-msa-hero-ready on the slide it is about to
   show, which un-hides the image and lets it load BEFORE the crossfade starts. */
.msa-hero__slide:not([data-msa-hero-active]):not([data-msa-hero-ready]) img{display:none}
.msa-hero__plate{
  width:100%;
  /* 520 -> 380 -> 400 -> 520 across the four tiers, which is the v3 mockup's own
     sequence: full column on a handset (spec 4.4's "the octagon filling the column"),
     capped at 380 on a tablet where a 520px octagon above a stacked copy column makes
     the hero 1162px tall, then back to the design width once the copy sits beside it.
     The `sizes` attribute in inc/hero.php restates these four numbers deliberately —
     they are the same measurement expressed to the layout engine and to the image
     picker, and there is no way to derive one from the other in CSS. */
  max-width:520px;
  aspect-ratio:1;
  padding:4px;
  background:var(--red);
  -webkit-clip-path:var(--oct);
  clip-path:var(--oct);
}
/* Centred while it is capped and stacked. Between 640 and 899 the plate is held at 380px
   but the `margin-left:auto` that positions it only arrives at 900 — so nothing placed it,
   and the site's signature mark sat hard against the left edge of its own row with the void
   beside it: measured 209px of empty dark at 640, 327 at 768, and 447 at 899, where the
   octagon fills 46% of its row. `margin-inline:auto` centres it for exactly the band in
   which it is narrower than its column and has nothing to sit beside; at 900 the copy
   arrives on its left and it goes back to being right-aligned against it. */
/* The plate reaches its 520px cap at ~568px, not at 640 — so between 568 and 639 it was
   narrower than its column with nothing centring it, and drifted from 4.4px off at 570 to
   68px off at 639. The band the comment describes starts where the CAP starts. */
@media (min-width:568px){.msa-hero__plate{margin-inline:auto}}
@media (min-width:640px){.msa-hero__plate{max-width:380px;margin-inline:auto}}
@media (min-width:900px){.msa-hero__plate{max-width:520px;margin-inline:0 0;margin-left:auto}}
.msa-hero__plate img{
  display:block;
  width:100%;
  height:100%;
  object-fit:cover;
  background:var(--stage);
  -webkit-clip-path:var(--oct);
  clip-path:var(--oct);
}
.msa-hero__caption{
  font-family:var(--font-mono);
  font-size:.75rem;
  line-height:1.45;
  color:var(--dim);
  /* Client copy in a narrow column. Declared on the class, not fixed on the one caption
     that happens to overflow, because the next long word will be in a different one. */
  overflow-wrap:break-word;
  padding-top:.9rem;
  /* Two lines held open. Captions travel with the slide and differ in length, and the
     figures are stacked in one cell, so the cell is as tall as the longest caption —
     but only if every caption has a box, which is why the figure is never
     display:none. This floor keeps a one-line caption from being the reservation. */
  min-height:calc(.9rem + 2 * 1.45 * .75rem);
}
/* CENTRED ON THE IMAGE, not ranged right. The caption box already spans the figure — measured
   on staging, caption 832..1352 against image 836..1348, both centred on 1092 — but the text
   sat at the right edge, so the label read as detached from the thing it names. The client
   reads micrographs for a living; a caption that is not centred under its own figure is the
   first thing that looks wrong. */
@media (min-width:900px){.msa-hero__caption{text-align:center}}

/* ------------------------------------------------------------------- the counters
   Admin fields with static values (spec §4.3). This said "Four items in two columns or
   four — both divide exactly, so the row is never orphaned at any width", and that
   stopped being true the moment one of the four was withdrawn: a counter can be blanked
   from the settings screen, and three items in two columns is the orphan row
   S-BALANCE-08 forbids. So the column count follows the item count, the same way
   .msa-cards does — inc/hero.php stamps data-count, and every count below has a column
   count that divides it. One column is the mobile-first default because it divides ANY
   count, which makes it correct rather than merely small. */
.msa-hero__counters{
  list-style:none;
  margin:3rem auto 0;
  /* NO BOTTOM PADDING. The section that follows carries --gap-section of its own, and both
     were being paid: measured at 1440, the last counter label ended at y=889 and the eyebrow
     below it began at 1033 — a 144px gap, where the same page puts 72px between two stage
     sections and 64px between a heading and its own content. Nobody chose 144; it is two
     paddings meeting. The gap is the next section's to state. */
  padding:2.25rem var(--gutter) 0;
  display:grid;
  grid-template-columns:1fr;
  gap:1.75rem 1.5rem;
  align-items:start;
  position:relative;
}
/* NO RULE ABOVE THE COUNTERS.
   There was one — 1,264px of hairline at 16% white, drawn inside the gutter so it lined up
   with the content rather than the box. Aligning it was the right fix for the wrong element:
   the counters are already separated from the copy above them by 3rem of space and by the
   change from sentences to figures, and a full-width line across a photograph is the one
   decoration on this hero that carries no information. The space does the work. */
@media (min-width:640px){
  .msa-hero__counters[data-count="2"],
  .msa-hero__counters[data-count="4"]{grid-template-columns:repeat(2,1fr)}
}
/* 900px, the width the hero already becomes two columns at: three or four counters
   side by side need the full bar. A 12px mono label in a 175px cell at 640 wraps to
   four lines, so three-up waits for the room rather than taking it early. */
@media (min-width:900px){
  .msa-hero__counters[data-count="3"]{grid-template-columns:repeat(3,1fr)}
  .msa-hero__counters[data-count="4"]{grid-template-columns:repeat(4,1fr)}
}
.msa-hero__counter{min-width:0;display:grid;gap:.55rem;align-content:start}
.msa-hero__count{
  font-family:var(--font-display);
  font-size:clamp(1.75rem,1.2rem + 1.2vw,2.375rem);
  font-weight:500;
  line-height:1;
  letter-spacing:-.03em;
  color:var(--white);
}
.msa-hero__count-label{
  font-family:var(--font-mono);
  font-size:.75rem;
  line-height:1.45;
  color:var(--dim);
  overflow-wrap:break-word;
}

/* ---------------------------------------------------------------- buttons
   Two styles, no more. .btn is the ask — Request information, the one action
   worth the brand red. .btn--ghost is everything supporting it: a datasheet,
   a spec table, a related instrument. A third style is how a design stops
   meaning anything, so there isn't one.

   The chamfer is a 6px 45deg cut on two opposite corners. It reads as machined
   rather than rounded, which is the register the instruments are photographed
   in, and it deliberately does NOT reuse --oct: the octagon is the optical head
   and is spent at two sizes only. A button borrowing it would dilute the mark.

   19px/700 is not a style choice. White on --red measures 4.16:1, which clears
   WCAG AA as LARGE text (3:1) and fails it as normal text (4.5:1). Large text
   means >=18.66px at >=700, so the label has to sit at or above that to be
   legal on the red. Shrinking it is not a visual decision, it is a compliance
   regression — see the contrast note in the design standards. */
.btn,
.btn--ghost{
  display:inline-flex;
  align-items:center;
  gap:.55em;
  padding:14px 28px;            /* the mockup's own metric */
  font-family:var(--font-body); /* Manrope, as the mockup's .btn sets */
  font-size:1.1875rem;          /* 19px — the AA large-text floor on --red */
  font-weight:700;
  letter-spacing:-.012em;
  line-height:1.15;
  white-space:nowrap;
  text-decoration:none;
  cursor:pointer;
  /* SQUARE, and a real border. This carried a 6px chamfer on two corners, which reads as a
     rounded box at button scale and is not what the approved design does: the mockup's .btn is
     `border:1px solid transparent; padding:14px 28px` with no clip-path at all, and its ghost
     variant is a 1px border rather than an inset shadow. The chamfer also forced two
     workarounds that are no longer needed — the ghost's inset shadow (a border inside a
     clip-path gets sliced at the cut) and a hand-drawn focus ring (clip-path crops an outline).
     Both revert to the plain thing now. The octagon stays where it belongs: the optical head. */
  border:1px solid transparent;
  transition:background-color .18s ease,color .18s ease,border-color .18s ease,transform .18s ease;
}

.btn{ background:var(--red); color:var(--white); border-color:var(--red); }
.btn:hover,.btn:focus-visible{ background:var(--stage); color:var(--white);
  border-color:var(--stage); }
/* Red -> near-black on hover, not a darker red. A second red is the defect the live site already
   has (four competing reds) and S-TOKENS-03 rejects it by design: a darkened red was the first attempt
   here and the gate caught it at dE 7.2. Red/white/black is the whole palette; the state change
   uses it rather than inventing a shade. White on --stage measures ~19:1. */
.btn:active{ transform:translateY(1px); }

/* Ghost carries its edge as an inset shadow, not a border: a border inside a
   clip-path gets sliced at the chamfer and shows a hairline gap on the cut. */
.btn--ghost{
  background:transparent;
  color:var(--stage);
  border-color:rgba(var(--stage-rgb),.30);
}
.btn--ghost:hover,.btn--ghost:focus-visible{
  background:var(--stage); color:var(--white); border-color:var(--stage);
}
.btn--ghost:hover,.btn--ghost:focus-visible{ background:var(--stage); color:var(--paper); }

/* On the dark stage the ghost inverts; the red button is already legible there. */
.msa-stage .btn--ghost{ color:var(--paper); border-color:rgba(var(--paper-rgb),.30); }
.msa-stage .btn--ghost:hover,.msa-stage .btn--ghost:focus-visible{
  background:var(--paper); color:var(--stage);
}

/* The ring is offset so it clears the border rather than sitting on it. */
.btn:focus-visible,.btn--ghost:focus-visible{
  outline:3px solid var(--stage);
  outline-offset:3px;
}
.msa-stage .btn:focus-visible,.msa-stage .btn--ghost:focus-visible{ outline-color:var(--paper); }

/* The call-to-action block the import writes in place of the old button GIFs. */
.msa-cta{ margin:2rem 0 0; }

@media (prefers-reduced-motion:reduce){
  .btn,.btn--ghost{ transition:none; }
  .btn:active{ transform:none; }
}

/* ------------------------------------------------------------------ the product videos
   Every instrument page opens on a tab holding its product video. Vimeo's oEmbed returns a
   fixed 426x240 iframe and WordPress scales that to 500x281 — a fixed box in a panel whose
   width is 680px on a desktop and 320px on a handset, so it left 180px of empty panel on one
   and overflowed the other. `add_theme_support('responsive-embeds')` sets the body class but
   only rewrites BLOCK embeds; these are classic [embed] shortcodes, so the ratio is applied
   here.

   16/9 is the ratio Vimeo returns for all seven of these videos (426x240), not a guess. */
.msa-prose iframe[src*="player.vimeo.com"],
.msa-prose iframe[src*="youtube.com/embed"]{
  display:block;
  inline-size:100%;
  block-size:auto;
  aspect-ratio:16 / 9;
  border:0;
  background:var(--stage);
}

/* ------------------------------------------------------- media containment
   The theme carried NO max-width rule for media. Content images were held only by
   craic-tabs.css, and only inside tab panels — so the first image placed anywhere
   else overflowed its column and scrolled the page sideways. Not hypothetical: the
   import localised 187 images, 38 of them wider than 800px and one at 1920px, and
   WordPress writes each image's natural width into the markup.

   height:auto is load-bearing, not tidiness. 95 of the imported <img> tags carry
   only ONE of width/height; constraining the width without releasing the height
   would squash every one of them out of aspect ratio.

   iframe/embed/object are here for the oEmbed video wrappers, which WordPress
   emits at a fixed 500px and which overflow the 360px and 430px viewports. */
.msa-prose :is(img, video, iframe, embed, object){ max-width:100%; }
/* HEIGHT, which max-width alone does not touch. The oEmbed markup carries a hardcoded
   width="500" height="281", so the width shrank with the column and the height did not:
   measured at 360px the product video rendered 254x281 — TALLER THAN IT IS WIDE, 197% of
   its correct height, with red pillarbox bands beside the poster. 13 of 13 embeds on the
   site, i.e. the whole product range.

   aspect-ratio on the embed rather than a padding-box wrapper, because the wrapper would
   have to be added to content this theme does not own. `attr()` is not usable for this in
   any shipping browser, so the ratio is stated: every embed on this site is a 16:9 Vimeo
   player, and a non-16:9 embed added later gets a correct box from height:auto anyway. */
.msa-prose :is(img, video, object){ height:auto; }
.msa-prose :is(iframe, embed){ aspect-ratio:16 / 9; height:auto; }
.msa-prose :is(img, video){ height:auto; }

/* A table cannot be made narrower than its content, so it scrolls inside its own
   box rather than taking the page with it. The wrapper is what scrolls; the page
   body must never scroll horizontally. */
/* Descendant, not child: the imported tables sit inside builder wrappers, so a
   `>` selector matched none of them. Measured: tr at 425px inside a 360px viewport. */
/* max-content, so a table narrower than its column stops pretending to be wider. With a bare
   `display:block` the box filled the container whatever the columns needed: the service
   contracts table's four bundles occupy about 300px inside a 614px box, and the scroll shadow
   above painted its "there is more this way" gradient across the 300px of nothing to the
   right. A cue pointing at empty space is the same defect the gradient colours were fixed for.
   Content wider than the column still hits max-width and scrolls. */
.msa-prose table{ display:block; overflow-x:auto; inline-size:max-content; max-width:100%; }

/* ------------------------------------------------- where you are
   A visitor from a search result lands three levels down knowing neither what section they are
   in nor how to leave it. The trail is the page's own ancestry, so it always agrees with the
   URL above it.

   NOT A ROW OF SLASHES. The default separator for this is "/" or a middle dot, and this site
   already owns a mark for "here is a thing": the aperture square that leads every eyebrow and
   sits on the enquiry card. It does the separating, at 5px, in the brand red, so the one piece
   of colour in the trail is the client's own — and the last crumb, the page you are on, carries
   none, because nothing follows it.

   Set at the eyebrow's size and letter-spacing but NOT in its uppercase: a breadcrumb is read
   as words, and product names like "UVM-1™" and "rIQ™" lose their shape in capitals. */
/* THE TRAIL CLOSES THE CHROME; IT DOES NOT OPEN THE PAGE.
   First attempt gave it the section gap the title used to own, which put 72px of air above a
   13px line and 9px between it and a 39px heading — the trail floated in the middle belonging
   to neither. It answers "where am I", which is the masthead's question, not the headline's.
   So it sits close under the black bar, as the last line of the chrome, and the full section
   gap below it opens the page proper. The space says what belongs to what. */
.msa-crumbs{
  padding-block-start:1.25rem;
  margin-block-end:0;
  font-size:.8125rem;
  color:var(--ink-dim);
}
.msa-crumbs__list{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.1rem .6rem;
  margin:0;
  padding:0;
  list-style:none;
}
.msa-crumbs__item{display:flex;align-items:center;gap:.6rem;margin:0}
.msa-crumbs__item:not(:last-child)::after{
  content:"";
  inline-size:5px;
  block-size:5px;
  background:var(--red);
  flex:0 0 auto;
}
/* THE 24px HIT BOX (WCAG 2.5.8) ON THE TRAIL. Measured on the deployed site, every page and
   every width: "Home" and "Solutions" render 18-19px tall. A breadcrumb entry is a standalone
   navigation control — the rule 1,800 lines down gives every other one on this site a 24px box
   and cannot reach these, because the trail is not inside .msa-prose. 13px type on 1.4 is 18px,
   so 3px each side clears the floor without moving the trail's own rhythm. */
.msa-crumbs a,
.msa-crumbs [aria-current="page"]{ display:inline-block; padding-block:.1875rem; }
.msa-crumbs a{
  color:var(--ink-dim);
  text-decoration:none;
  border-block-end:1px solid transparent;
}
.msa-crumbs a:hover,
.msa-crumbs a:focus-visible{color:var(--stage);border-block-end-color:var(--red)}
.msa-crumbs a:focus-visible{outline:3px solid var(--stage);outline-offset:3px}
/* The page you are on: the only crumb in the body ink, so the trail reads toward it. */
.msa-crumbs [aria-current="page"]{color:var(--stage)}

/* The heading keeps its own section gap: the trail above it belongs to the masthead, so the
   page still opens with the same measure of air it opened with before there was a trail. */
.msa-crumbs + .msa-h1{padding-block-start:var(--gap-section)}

@media (max-width:759px){
  .msa-crumbs{font-size:.75rem}
  /* On a phone the trail can run to three lines. The ancestors are what a reader needs; the
     page they are on is the page they can see. */
  .msa-crumbs__item:last-child{display:none}
  .msa-crumbs__item:nth-last-child(2)::after{display:none}
}

/* ------------------------------------------------- long-word headings
   "Microspectrophotometer" is 22 characters and it is this client's core noun, so
   it appears in product headings across the catalogue. At 360px it does not fit on
   one line at display size and, being a single word, it cannot wrap — measured:
   h1.msa-h1 scrollWidth 404 against clientWidth 360 on the 2030PV PRO page, which
   scrolls the whole document sideways.

   hyphens:auto is the fix rather than break-word alone, because it breaks at
   syllable boundaries and leaves a hyphen: "Microspectro-photometer" reads as the
   word it is. break-word stays as the fallback for a string with no legal break
   point, such as a part number, so the page can never scroll even when hyphenation
   has nothing to work with. WordPress emits lang="en-US" on <html>, which is what
   makes hyphens:auto resolve. */
.msa-h1, .msa-prose :is(h1, h2, h3, h4){
  overflow-wrap:break-word;
}
/* hyphens:auto ONLY where a heading can actually overflow. It was unconditional, to solve a
   360px overflow, and it fired at every width — measured 15 mid-word breaks in headings at
   1440px, most in columns with hundreds of spare pixels: "…ed to anal / yze indivi…" in a
   904px column. "ana-lyze" in a 26px display heading reads as a typo. Below 700px the
   column is narrow enough that "Microspectrophotometer" genuinely cannot fit and a hyphen
   is the right answer; above it, break-word alone is the safety net. */
@media (max-width:699px){
  .msa-h1, .msa-prose :is(h1, h2, h3, h4){hyphens:auto}
}

/* ==================================================================== navigation
   Before this, the site had ZERO menus and 219 pages that linked to each other only
   through whatever the imported bodies happened to say. header.php already called
   wp_nav_menu(['theme_location' => 'primary']) and functions.php already registered
   that location; both rendered nothing, because nothing had ever been assigned to it.
   import/build-menu.py builds the menu from the live site's own navigation — 80 items
   over 80 distinct pages — and this is what draws it.

   WHY THE WHOLE MENU IS IN THE MARKUP AT EVERY WIDTH. All 80 links are served on all
   219 pages, open or shut. That is what makes them crawlable, what makes S-LAUNCH-17's
   link check see them, and what lets the disclosure below be pure CSS: nothing is
   built by script, so nothing is missing when script does not run.

   THE DISCLOSURE IS <details>, AND THE DESKTOP BAR IS THE SAME ELEMENT HELD OPEN.
   One menu, one list, one set of links — not a drawer and a duplicate bar, which is
   how the live site does it (Helix Ultimate renders its megamenu and its off-canvas
   copy twice on every page) and how a site ends up with two navigations that drift
   apart. <summary> is a real button to a screen reader, focusable and operable from
   the keyboard with no tabindex and no key handler, and it carries its own expanded
   state. At >=900px the summary is hidden and the panel is held open by CSS.

   HOLDING IT OPEN NEEDS TWO DECLARATIONS AND A GUARD, and the guard is the point.
   Browsers hide a closed <details>'s contents through a shadow slot, so a rule on the
   light-DOM list is not guaranteed to reach it; ::details-content is the pseudo-element
   that addresses the slot directly, and it is recent. Measured in this harness
   (Chromium 151): CSS.supports('selector(::details-content)') is true, and BOTH the
   ::details-content override and a plain `display` on the list reveal the contents.
   Both are written, so either mechanism is enough. Hiding the summary is then gated on
   @supports selector(::details-content): on a browser too old for it the summary stays
   visible at every width and the menu is a working click-to-open button. The failure
   this guard exists to prevent is the other order — summary hidden, panel still shut,
   and a desktop visitor with no navigation at all.

   THE CONTAINING BLOCK IS .msa-head__inner, and it was chosen by measurement, not by
   assumption: ::details-content could plausibly have carried layout containment, which
   would have re-anchored every absolutely-positioned panel inside it. Probed in this
   harness with an open <details> inside a flex bar — an absolutely-positioned list
   resolved against the positioned ancestor OUTSIDE the details (x=0, width=viewport,
   y=bar bottom) at both 360px and 1440px. So one positioned ancestor serves both the
   mobile drawer and the desktop panels, and the panels line up with --wrap's gutters
   because that element is the wrap.

   This section sits at the END of the stylesheet on purpose. The menu toggle reuses
   .btn--ghost rather than inventing a third button style ("A third style is how a
   design stops meaning anything, so there isn't one"), and several rules below have to
   outrank it. Source order after the buttons block is what makes that hold without a
   specificity arms race. */

/* The skip link. It is the first focusable thing on every page, and on a site whose
   navigation is 80 links it is not decoration: without it a keyboard visitor tabs
   through the entire menu on every page before reaching the content. It sits on
   --stage rather than --red because it is chrome, not a call to action, and because
   white on --stage measures ~19:1 with no size floor to respect. */
.msa-skip{
  position:absolute;
  z-index:100;
  inset-block-start:0;
  inset-inline-start:var(--gutter);
  padding:.7rem 1.25rem;
  background:var(--stage);
  color:var(--paper);
  font-family:var(--font-display);
  font-size:1rem;
  font-weight:600;
  text-decoration:none;
  transform:translateY(-140%);
}
.msa-skip:focus{ transform:translateY(0); }
.msa-skip:focus-visible{ outline:3px solid var(--red); outline-offset:2px; }

/* WordPress's own class for a label that a screen reader must hear and a screen must not
   show. The theme had no rule for it, so every core-generated one — the search results
   pagination's "Posts navigation" heading is the one this build reaches — printed as
   visible body copy. Core emits the class; a theme that does not define it is not
   opting out of the label, it is displaying it. Clip-path AND clip: the deprecated
   `clip` is still what older WebKit honours, and the two together are the shape core's
   own themes ship. `white-space:nowrap` stops a clipped label collapsing to one narrow
   column and stretching the line box it sits in. */
.screen-reader-text{
  position:absolute;
  width:1px;
  height:1px;
  margin:-1px;
  padding:0;
  overflow:hidden;
  clip:rect(0 0 0 0);
  clip-path:inset(50%);
  border:0;
  white-space:nowrap;
}
.screen-reader-text:focus{
  position:static;
  width:auto;
  height:auto;
  margin:0;
  overflow:visible;
  clip:auto;
  clip-path:none;
  white-space:normal;
}

.msa-head{
  position:relative;        /* the panels below are z-indexed over the hero, whose
                               .msa-hero__inner sits at z-index 2 in the root context */
  z-index:20;
  background:var(--stage);
  color:var(--paper);
}
/* PADDING, NEVER MARGIN, on the header — the same rule the page shell above states and
   for the same measured reason: S-BALANCE-08 rule 2 reads a section's gap above from
   this element's rendered bottom edge, and a margin here would move that edge and
   report as asymmetry on all 219 pages at once. */
.msa-head__inner{
  position:relative;        /* the containing block for the drawer AND the panels */
  max-width:var(--wrap);
  margin-inline:auto;
  padding-inline:var(--gutter);
  min-height:4rem;
  display:flex;
  align-items:center;
  /* THE MASTHEAD CLUSTERS LEFT. space-between pushed the navigation to the far right and left
     650px of nothing between the wordmark and the first link — the arrangement every template
     produces. The approved design sets wordmark, links and the one action as a single group
     reading left to right, with the empty space after them rather than inside them. */
  justify-content:flex-start;
  gap:2.5rem;
}

/* The wordmark, and the site's only route home — /  is not in the live navigation's 70
   paths because on the live site the logo is the home link. The name is read from the
   database (get_bloginfo), never written here. */
.msa-brand{
  display:flex;
  align-items:center;
  gap:.6em;
  min-width:0;
  font-family:var(--font-display);
  font-size:clamp(1rem,.92rem + .34vw,1.1875rem);
  font-weight:600;
  letter-spacing:-.01em;
  line-height:1.2;
  color:var(--white);
  text-decoration:none;
  /* The wordmark is the site's only route home and it is a 19px-tall line of type at
     360px — 5px short of WCAG 2.5.8's 24px minimum target. The padding is what makes
     the hit box legal; the type is unchanged. */
  padding-block:.2rem;
}
/* The measuring aperture, used small — spec §3.3's other permitted place for it
   beside the list bullet. A square, because the aperture is a square. Red appears
   three times in this header and nowhere else in it: this mark, the rule under the
   section you are in, and the aperture beside the page you are on. */
.msa-brand__mark{
  flex:none;
  inline-size:.6em;
  block-size:.6em;
  background:var(--red);
}
.msa-brand__name{ overflow-wrap:break-word; }
.msa-brand:hover .msa-brand__name{
  text-decoration:underline;
  text-decoration-color:var(--red);
  text-decoration-thickness:2px;
  text-underline-offset:.2em;
}

/* The toggle is a <summary>, so the default disclosure triangle has to go — in both
   spellings, because Chromium and Firefox draw it with ::marker on a list-item display
   and WebKit draws it with its own pseudo-element. .btn--ghost's display:inline-flex
   already removes the first in practice; list-style:none states it rather than relying
   on that side effect. */
/* THE MENU CONTROL SITS AT THE RIGHT EDGE, at every width where it is the way in. Measured
   before: at 768 it was a 108x52 box at x=198 with 462px of empty header to its right, and at
   390 a 88x52 box at x=169 with the right third of the row empty — a control parked in the
   middle of a row it was the only occupant of. .msa-nav carries the margin rather than the
   <summary>, so the drawer it opens keeps its own full-width containing block. The desktop bar
   resets this where the navigation fills the row itself. */
.msa-nav{ margin-inline-start:auto; }
/* MASTHEAD SCALE, NOT PAGE SCALE. .btn--ghost carries the mockup's own button metric —
   14px/28px padding at 19px/700 — which is right for the pair of controls under the hero
   headline and wrong for a control inside a 64px bar: measured, it drew a 108x52 box with 6px
   of clearance top and bottom, twice the weight of the 117x38 action the same header shows on
   a wide screen. It takes the navigation's own type size here, and a 44px floor because on
   these widths it is a thumb target rather than a pointer target. */
.msa-nav__toggle{
  list-style:none;
  padding:.5rem 1.05rem;
  min-block-size:44px;
  font-family:var(--font-display);
  font-size:.9375rem;
  font-weight:600;
  letter-spacing:-.01em;
}
/* The masthead has one action colour. The wide-screen control fills red on hover; the ghost's
   own dark-surface rule fills it paper-white, which puts the largest, brightest block on the
   page inside the header — the shape this rebuild has already removed from that corner once. */
.msa-stage .msa-nav__toggle:hover,
.msa-stage .msa-nav__toggle:focus-visible{
  background:var(--red);
  border-color:var(--red);
  color:var(--white);
}
.msa-nav__toggle::-webkit-details-marker{ display:none; }
.msa-nav__toggle::marker{ content:""; }

/* ------------------------------------------------------------- the drawer (<900px)
   In its own scroll container with a bounded height: 80 links stacked is taller than
   any handset, and a drawer that runs past the bottom of the screen with no way back
   is not a menu. overflow-y also forces overflow-x to compute to auto, so a long
   product name scrolls inside the drawer rather than taking the document sideways —
   which S-STRUCT-07 fails the whole page for. */
.msa-nav__list{
  position:absolute;
  inset-inline:0;
  top:100%;
  z-index:1;
  margin:0;
  padding:.5rem var(--gutter) 1.5rem;
  list-style:none;
  background:var(--stage-panel);
  --ink-dim:var(--dim-panel);
  /* Three shadows, three jobs: the seam under the masthead, a ring that states where the
     drawer ends on a dark page, and the drop that lifts it off a light one. The ring is an
     OUTER shadow at zero blur and zero spread offset, so it costs no layout — a border here
     would move the drawer 2px and take the seam with it. */
  box-shadow:inset 0 1px 0 0 rgba(var(--paper-rgb),.16),
             0 0 0 1px rgba(var(--paper-rgb),.10),
             0 24px 48px -20px rgba(var(--stage-rgb),.85);
  max-height:min(72vh,34rem);
  overflow-y:auto;
  overscroll-behavior:contain;
}
.msa-nav__list a{
  display:block;
  color:var(--paper);
  text-decoration:none;
  /* A menu label is client copy: "Microspectrophotometer" is 22 characters and this
     client's core noun, and the same argument the headings above make applies here. */
  overflow-wrap:break-word;
  hyphens:auto;
}
.msa-nav__list > li > a{
  padding:.6rem 0 .3rem;
  font-family:var(--font-display);
  font-size:1.0625rem;
  font-weight:600;
  line-height:1.25;
  color:var(--white);
}
.msa-nav__list .sub-menu{
  margin:0 0 .5rem;
  padding:0 0 0 .85rem;
  list-style:none;
  border-inline-start:2px solid rgba(var(--paper-rgb),.18);
}
.msa-nav__list .sub-menu .sub-menu{ margin-block:.15rem .5rem; }
.msa-nav__list .sub-menu a{
  padding:.4rem 0;
  font-family:var(--font-body);
  font-size:.9375rem;
  line-height:1.4;
}
/* The masthead's one action, distinguishable from the five links beside it. A ghost button
   rather than a filled one: the header sits on the stage and a solid red block at 15px would
   outweigh the wordmark next to it. Desktop only — in the drawer every item is already a
   full-width row, and a bordered box inside a list of rows reads as a mistake. */
@media (min-width:900px){
  /* CENTRED, AND THE AUTO MARGIN BELOW IS WHY IT WAS NOT. `margin-block:auto` centres a child
     only inside a flex or grid container; this <a>'s parent is an <li>, which is a block box,
     so the declaration did nothing and the control hung from the top of the row. Measured at
     1440: the four links ran y=32..102 and the action y=32..70 — a 38px box in a 70px row,
     sitting 16px high. The <li> is a flex line now, which is what makes the centring real.

     AND IT SITS AT THE ROW'S RIGHT EDGE. The utility strip directly above spans the full page
     — tagline at the left gutter, telephone at the right — while the masthead's content ended
     at x=760 of 1440 and left 41% of the row empty, so the two rows disagreed about where the
     page ends. This is not the `space-between` that an earlier release removed: that split the
     wordmark from the first link and put 650px inside the group. The wordmark and the four
     links stay together on the left; only the one action moves, to the edge the telephone
     number above it already occupies. */
  .msa-nav{ flex:1 1 auto; margin-inline-start:0; }
  .msa-nav__list > li.msa-nav__cta{
    display:flex;
    align-items:center;
    margin-inline-start:auto;
  }
  .msa-nav__list > li.msa-nav__cta > a{
    /* A TIGHT CONTROL, VERTICALLY CENTRED. The first version put a 1px border on a link that
       is display:block and fills the masthead's full 72px, so it drew a 114x72 rectangle in
       the top-right corner — the brightest, largest shape on a dark page, competing with the
       octagon for the eye. The approved design's is 115x44 and sits inside the row. */
    border:1px solid rgba(244,244,245,.32);
    padding:.55rem 1.1rem;
    margin-block:auto;
    line-height:1.2;
    border-radius:0;
  }
  .msa-nav__list > li.msa-nav__cta > a:hover{
    border-color:var(--red);
    background:var(--red);
    color:var(--white);
    text-decoration:none;
  }
}
.msa-nav__list a:hover{
  color:var(--white);
  text-decoration:underline;
  text-decoration-color:var(--red);
  text-decoration-thickness:2px;
  text-underline-offset:.18em;
}
/* The page you are on, marked with the aperture. WordPress puts aria-current="page" on
   this same anchor, so the mark is a visual echo of something already announced. */
.msa-nav__list .current-menu-item > a{ color:var(--white); }
.msa-nav__list .current-menu-item > a::before{
  content:"";
  display:inline-block;
  inline-size:.5em;
  block-size:.5em;
  margin-inline-end:.5em;
  background:var(--red);
}

/* Every focusable thing in the header takes the same ring, and it is --paper because
   the surface is --stage. That is the rule style.css already states for a control on
   the stage (.msa-stage .btn:focus-visible), applied to the links as well so the
   keyboard path through the header never loses its place. */
.msa-head a:focus-visible{
  outline:3px solid var(--paper);
  outline-offset:2px;
}

/* ------------------------------------------------------------------- the bar (>=900px)
   900px, not a new number: it is the width the hero already switches to two columns at,
   and the bar needs about the same room. Measured at 900px with the real labels: the
   five top-level items occupy 468px of the 828px between the gutters, leaving the
   wordmark its ~190px and about 170px of slack. A longer label wraps the row rather
   than overflowing it — flex-wrap, not overflow, is the failure mode here.

   AND (hover:hover), which is not tidiness — it is what makes 75 of the 80 links
   reachable on a tablet. Everything below opens a panel on :hover or :focus-within and
   nothing else: there is no tap fallback, because there is no script. On a touch device
   at >=900px (an iPad in landscape is 1024) a finger cannot hover, and a tap on a
   top-level item follows its link instead of opening the panel, so every link inside
   every panel was unreachable. Gating the whole bar on a pointer that can hover means a
   coarse-pointer device keeps the drawer at any width — the <summary> is visible, one tap
   opens it, and all 80 links are inside it, already expanded. The media query is the
   fallback; a second mechanism bolted on top of the bar would be the drift. */
@media (min-width:900px) and (hover:hover){
  /* Hold the disclosure open. Two mechanisms; see this section's header note. */
  .msa-nav__disclosure::details-content{ content-visibility:visible; block-size:auto; }

  .msa-nav__list{
    margin-inline-start:-.875rem;   /* see the link's padding-inline below */
    position:static;
    display:flex;
    flex-wrap:wrap;
    /* flex-start, not flex-end. The list used to shrink-wrap its links, so `flex-end` had
       nothing to push against and did nothing. Now that .msa-nav fills the row so the action
       can reach the right edge, `flex-end` WOULD push all five items right and reopen the gap
       between the wordmark and the first link. The links pack against the wordmark; the
       action's own auto margin takes the slack. */
    justify-content:flex-start;
    /* NO GAP BETWEEN THE ITEMS — the spacing lives inside each link instead (below). A flex
       `gap` here put 28px of dead ul between every pair of items: a pointer moving diagonally
       out of a label toward the panel underneath it crossed that strip, hovered nothing, and
       the panel closed. The distance between labels is identical; what changed is that every
       pixel of it now belongs to one item or the next, so a sideways drift lands on a
       neighbour rather than on nothing. */
    gap:0;
    max-height:none;
    overflow:visible;       /* an ancestor with overflow would clip the panels */
    padding:0;
    background:none;
    box-shadow:none;
  }
  .msa-nav__list > li{ position:static; }
  .msa-nav__list > li > a{
    position:relative;
    padding-block:1.6rem;
    /* Half the old gap on each side, so the gap between two LABELS is unchanged at 1.75rem
       and the hit target grows by 28px. The list pulls back by the same amount at its start
       so the wordmark-to-first-label distance does not move either. */
    padding-inline:.875rem;
    font-size:.9375rem;
    font-weight:500;
    color:var(--paper);
  }
  /* The top-level indicator: the brand red, drawn as a rule under the item rather than
     behind its label. Red is not a legible background for 15px text (white on --red is
     4.16:1 and only clears AA at 19px/700), so on this bar it is a mark, never a fill. */
  .msa-nav__list > li > a::after{
    content:"";
    position:absolute;
    /* inset by the link's own padding: the mark measures the LABEL, which is what it names. */
    inset-inline:.875rem;
    inset-block-end:0;
    block-size:3px;
    background:var(--red);
    transform:scaleX(0);
    transform-origin:left center;
    transition:transform .18s ease;
  }
  .msa-nav__list > li:hover > a::after,
  .msa-nav__list > li:focus-within > a::after,
  .msa-nav__list > li.current-menu-item > a::after,
  .msa-nav__list > li.current-menu-ancestor > a::after,
  .msa-nav__list > li.current-menu-parent > a::after{ transform:scaleX(1); }
  /* At this width the top-level item already has the rule; the aperture would say the
     same thing twice and shift the label doing it. */
  .msa-nav__list > li.current-menu-item > a::before{ display:none; }
  .msa-nav__list > li > a:hover{ color:var(--white); text-decoration:none; }

  /* The panel. One per top-level item, spanning the wrap, opened by hover OR by focus
     landing anywhere inside it — :focus-within is what makes the keyboard path work
     without a line of script. It is a descendant of the <li>, so pointing at the panel
     keeps its own item open however far the panel reaches. */
  /* THE PANEL DOES NOT VANISH THE INSTANT THE POINTER SLIPS. It was display:none/display:block,
     which is binary and immediate: a pointer travelling from a 66px label down into the panel
     under it, on any angle that left the label before it crossed the panel's top edge, closed
     the menu and sent the reader back to the start. Reported from real use, and reproducible.

     A grace period, not an animation. The panel is always in the box tree and hidden with
     `visibility`, so the change can be delayed; visibility:hidden also keeps its links out of
     the tab order and out of the accessibility tree, which display:none did. Opening is
     immediate (delay 0); closing waits .32s, long enough to cross a gap and come back and
     short enough that a deliberate exit does not feel stuck.

     The one case that must NOT wait is moving to a neighbouring item, or two panels would sit
     on top of each other. `:has(> li:hover)` is true exactly while the pointer is on some item
     or inside its panel, so a sibling closes at once; when the pointer leaves the bar
     altogether it is false and the grace applies. */
  .msa-nav__list > li > .sub-menu{
    display:block;
    visibility:hidden;
    transition:visibility 0s linear .32s;
    position:absolute;
    inset-inline:0;
    top:100%;
    z-index:1;
    margin:0;
    padding:2rem var(--gutter) 2.25rem;
    border:0;
    background:var(--stage-panel);
    --ink-dim:var(--dim-panel);
    box-shadow:inset 0 1px 0 0 rgba(var(--paper-rgb),.16),
               0 0 0 1px rgba(var(--paper-rgb),.10),
               0 24px 48px -20px rgba(var(--stage-rgb),.85);
    /* MULTI-COLUMN, NOT GRID, and the difference is measured. A grid sizes every row
       to its tallest cell: the Support panel's "What is...?" group is 10 links tall and
       "The Science" is 11, so a 4-column grid laid 30 links out over three rows, two of
       them nearly empty beside a 350px cell, and stood 920px tall to do it. Columns
       flow the same items top-to-bottom and pack the short entries against the tall
       ones instead of beside them. break-inside on the item is what keeps a group and
       its heading together when the flow reaches the bottom of a column. */
    columns:16rem 4;
    column-gap:2.5rem;
    /* And capped, because columns alone are not enough at every width. A hover panel
       that runs off the bottom of the screen cannot be scrolled to: reaching the
       scrollbar takes the pointer off the item holding it open. Measured after the
       switch to columns — the two big panels are 401px and 456px at 1440x900, but both
       reach 576px, this cap, in a 900px-tall viewport, where uncapped they would run
       past the fold. Capped, the pointer stays inside the panel while the wheel moves
       it, so it stays open; overscroll-behavior stops that scroll chaining into the
       page once the panel bottoms out. */
    max-block-size:min(70vh,36rem);
    overflow-y:auto;
    overscroll-behavior:contain;
  }
  /* A STRUCTURED PANEL IS A GRID; A FLAT ONE STAYS MULTICOL. The note above is right that a
     grid was worse for Support and Products — 30 links whose groups run 10 and 11 deep, where
     a grid sizes every row to its tallest cell and stands 920px tall to do it. It is wrong for
     a panel whose second level HAS groups: the Solutions panel carries Applications (with four
     field pages under it) beside three single entries, and multicol packed all three of those
     into column two behind the tall one, leaving columns three and four empty. Measured: four
     second-level items, two columns used, x=88 and x=414 for all four.

     `:has(> li > .sub-menu)` is true only of a panel with a nested group, so the switch reaches
     the structured panel and nothing else. One row, so the objection the note raises — rows
     sized to the tallest cell — cannot arise: there is one row by construction. */
  /* AND ONLY WHEN IT IS SMALL ENOUGH TO BE A ROW. `:has(> li > .sub-menu)` alone also matched
     Support, whose second level carries "What is...?" (10 children) and "The Science" (11):
     grid-auto-flow:column gave it ELEVEN columns of 119px, which is the failure the note above
     describes arriving by a different door. `:not(:has(> li:nth-child(5)))` is the count test
     CSS can express — four second-level items or fewer — so the structured Solutions panel gets
     its row and the two big flat panels keep the multicol that was measured right for them. */
  .msa-nav__list > li > .sub-menu:has(> li > .sub-menu):not(:has(> li:nth-child(5))){
    columns:auto;
    display:grid;
    grid-auto-flow:column;
    grid-auto-columns:minmax(0,1fr);
    column-gap:2.5rem;
    align-items:start;
  }
  /* The second level becomes the column's heading, and its children read beneath it. Without
     this every entry in the panel is the same 15px/600 link and a reader cannot tell which of
     them is the group and which are its members. */
  .msa-nav__list > li > .sub-menu:has(> li > .sub-menu):not(:has(> li:nth-child(5))) > li > a{
    font-size:1rem;
    font-weight:700;
    color:var(--white);
  }
  .msa-nav__list > li > .sub-menu:has(> li > .sub-menu):not(:has(> li:nth-child(5))) > li > .sub-menu{
    margin-block-start:.6rem;
    padding-inline-start:0;
    /* AND THE RULE THE FLAT-PANEL STYLE DRAWS, which this one only half-overrode.
       `.msa-nav__list > li > .sub-menu .sub-menu` gives every nested list a 2px grey rule on its
       leading edge AND .85rem of padding to clear it. This rule out-specifies the padding and
       set it to 0 — but said nothing about the border, so on the Solutions panel alone the
       children sat FLUSH AGAINST the line while Products and Support cleared theirs. That is
       what the client saw: one dropdown touching the rule and the others not.
       The rule goes rather than the padding coming back. In a flat panel it is the only thing
       saying these links belong to the one above them; in this panel the COLUMN says it, and a
       vertical rule down each of three columns is a third line doing a job already done. The
       children now share the leading edge of their own heading, which is what a column is. */
    border-inline-start:0;
    list-style:none;
    display:grid;
    gap:.1rem;
  }
  .msa-nav__list > li > .sub-menu:has(> li > .sub-menu):not(:has(> li:nth-child(5))) > li > .sub-menu > li > a{
    font-size:.9375rem;
    font-weight:500;
  }
  .msa-nav__list > li:hover > .sub-menu,
  .msa-nav__list > li:focus-within > .sub-menu{ visibility:visible; transition-delay:0s; }
  .msa-nav__list:has(> li:hover) > li:not(:hover) > .sub-menu{ transition-delay:0s; }
  .msa-nav__list > li > .sub-menu > li{
    break-inside:avoid;
    margin-block-end:1rem;
  }
  .msa-nav__list > li > .sub-menu > li > a{
    padding:.15rem 0;
    font-family:var(--font-display);
    font-size:.9375rem;
    font-weight:600;
    color:var(--white);
  }
  /* Third level: shown with its parent, never a second hover. A flyout that only
     appears on a sustained hover over a 15px target is the mechanism this menu is
     replacing, and it cannot be reached by keyboard at all without script. */
  .msa-nav__list > li > .sub-menu .sub-menu{
    margin:.4rem 0 0;
    padding:0 0 0 .85rem;
    border-inline-start:2px solid rgba(var(--paper-rgb),.18);
  }
  .msa-nav__list > li > .sub-menu .sub-menu a{
    font-family:var(--font-body);
    font-size:.875rem;
    font-weight:400;
    color:var(--paper);
  }
}

/* The summary only goes away where the panel is provably openable without it — which
   now also means "by a pointer that can hover". Without the hover condition here, a touch
   device at >=900px would lose the toggle as well as the panels, leaving a header with no
   way into the navigation at all. */
@supports selector(::details-content){
  @media (min-width:900px) and (hover:hover){
    .msa-nav .msa-nav__toggle{ display:none; }
  }
}

/* The red rule under a top-level item is the only thing in the header that moves; the
   skip link snaps into place with no transition at all, deliberately. */
@media (prefers-reduced-motion:reduce){
  .msa-nav__list > li > a::after{ transition:none; }
}

/* ==================================================== imported-content layout
   Everything below styles markup the IMPORT produces. It is grouped here, at the
   end, because it is the one part of this stylesheet whose selectors are not the
   theme's own vocabulary — they are the shapes import/transform.py emits and the
   legacy Joomla classes it chose to keep.

   ------------------------------------------------- the editorial column grid
   transform.py reads each imported Bootstrap row's intent and emits
   `.cols cols--aside|--rail|--even` with `.col` children, and its docstring says
   "see style.css 'Editorial column ratios'". THERE WAS NO SUCH SECTION. Measured
   on this harness 2026-08-23: 111 published pages carry a .cols grid, 81 of them
   cols--aside and 9 cols--rail, and not one `.cols` rule existed in this file — so
   on half the site an authored two-column layout rendered as two stacked
   full-width blocks, with a 209px contact thumbnail sitting alone above the copy
   it belongs beside.

   The ratios are the transform's own: aside is the 4|8 and 5|7 rows, rail the 3|9.
   Measured across the 92 grids in the content, the first column averages 537
   characters of text against the second's 1,095 (rail: 173 against 1,498), so
   narrow-first is the real shape and not an assumption.

   minmax(0,…) on BOTH tracks, never a bare fr: a grid track is min-content by
   default, so one long unbreakable string — a part number, one of the mailto
   addresses on the worldwide-sales page — floors the track at its own width and
   pushes the grid wider than the page. That is trap 4 in the project's own list
   and it has already been paid for twice on this build.

   Stacks below 900px. Not a breakpoint chosen to match another: 900px is where
   the narrow track of --wrap's grid stops being wide enough for a figure at its
   published size, and it is where the header's own navigation switches. */
.msa-prose .cols{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:1.5rem 2.5rem;
  align-items:start;
  margin-block:1.75rem;
}
.msa-prose .cols > .col{min-width:0}
/* The reading measure is applied to the prose OUTSIDE a grid; inside one the track
   is already narrower than 68ch, and capping again would strand the text at the
   left edge of its own column. */
.msa-prose .cols > .col > :is(p,ul,ol,dl,blockquote,h2,h3,h4,h5,h6){max-width:none}
@media (min-width:900px){
  /* A `.cols` with no --aside/--rail/--even modifier had no tracks at all and rendered as one
     column — 89 blocks, every one inside a tab panel, where the authored two-column layout
     simply never happened. Equal columns is the neutral answer when transform.py could not
     read an intent, and it is what `cols--even` means. */
  .msa-prose .cols{grid-template-columns:repeat(2,minmax(0,1fr))}
  .msa-prose .cols--aside{grid-template-columns:minmax(0,1fr) minmax(0,2fr)}
  .msa-prose .cols--rail{grid-template-columns:minmax(0,1fr) minmax(0,3fr)}
  .msa-prose .cols--even{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* The 21 blocks whose content is the other way round — the argument in the column the
     import marked as supporting. import/clean-bodies.py measures both columns' text and
     stamps `cols--flip`; only the TRACKS reverse, never the DOM order, because the source
     order is the correct reading order and is what a narrow viewport and a screen reader
     get. Before this, /product-service/75-microspectrometer/ ran 2,334 characters down a
     288px ribbon at ~33 characters a line while the 576px column beside it sat empty for
     1,030px. */
  .msa-prose .cols--aside.cols--flip{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}
  /* AN ASIDE THAT IS NOT AN ASIDE STACKS INSTEAD.
     `cols--aside` gives its narrow track one third of the container. Inside a railed body
     that is 213px — about 25 characters a line — which is fine for the 209-300px thumbnail
     the ratio was designed around and unreadable for running copy. Measured across all 92
     narrow-track grids on the site, four ask that track to carry more than 250 characters,
     and three of those hold no image at all: the UVM-1 page ran 603 characters down a 213px
     ribbon beside a half-empty tab block, and Consumables ran 1,518. They stack, so the copy
     gets the full measure and what was beside it gets the full width.
     The fourth (149-protein-crystal) keeps its grid: its narrow column holds two captioned
     figures, which is what an aside is for.
     Two classes, because `.cols--aside.cols--flip` above is three and would otherwise win
     on specificity — which it did, silently, on the first attempt. */
  .msa-prose .cols.cols--stack{grid-template-columns:minmax(0,1fr)}
}
/* A figure in a column fills the column rather than sitting at its published
   thumbnail size in the middle of it — these are 209-300px 2008 JPEGs and the
   column is 420px, so `width:100%` would UPSCALE them. max-inline-size, so a
   picture never grows past the pixels it actually has. */
/* margin-INLINE only. `margin:0` here removed the figure's vertical rhythm too, and
   measured on /product-service/142-biotechnology/ the Request Information button then
   ended at exactly the y the next figure started at — the two read as overlapping.
   A column's first and last child still sit flush with the column's own edges. */
.msa-prose .cols > .col > figure{margin-inline:0}
.msa-prose .cols > .col > :first-child{margin-block-start:0}
.msa-prose .cols > .col > :last-child{margin-block-end:0}
.msa-prose .cols > .col img{display:block;height:auto}

/* --------------------------------------------------------- figures and images
   `img{max-width:100%}` was the only image rule in the file. A figure had no
   margin discipline and a figcaption was body copy, so a caption was
   indistinguishable from the paragraph under it. */
.msa-prose figure{margin-block:1.75rem;margin-inline:0}
.msa-prose figure img{display:block;height:auto}
.msa-prose figcaption{
  margin-block-start:.6rem;
  font-family:var(--font-mono);
  font-size:.75rem;
  line-height:1.5;
  color:var(--ink-dim);
}
/* width="0" on 50 images across 41 pages (import artefact: the source markup
   carried `width: 209px` in a style attribute and 0 in the attribute). A zero
   width attribute is an aspect-ratio hint of 0/314, and browsers honour it — the
   image reserves no horizontal space and then paints over the reservation. The
   attribute is repaired in the content by import/clean-bodies.py; this rule is the
   belt: an intrinsic size of zero is never what the layout should use. */
.msa-prose img[width="0"]{inline-size:auto}

/* ------------------------------------------------------------------- tables
   `display:block;overflow-x:auto` was the whole table rule — it stopped a wide
   table breaking the page and did nothing else, so the Solutions matrix rendered
   as three unrelated columns of floating text with no cell padding, no rules and
   no header. The scroll behaviour is kept exactly as it was and the table is given
   its structure back. */
/* The scroll AFFORDANCE. `overflow-x:auto` was already here and it is not enough: measured
   on /solutions/ at 320px, scrollWidth 483 against clientWidth 320 with the scrollbar
   consuming zero layout height, so the third column ("The Tool") is entirely off-screen and
   every line in the second is guillotined mid-word — "An imaging pho…", "A self-containe…"
   — with nothing on screen to say there is more. Four pages carry a legacy layout table
   like this and on /solutions/ the hidden column is the page's entire payload.

   A right-edge mask that lifts once the table is scrolled to its end, which is the pattern
   craic-tabs.css already uses for its own strip, plus a scrollbar that is actually visible.
   The mask is applied to a wrapper-free element via a background-attachment trick so no
   markup change is needed: the gradient is painted on the element's own padding box and
   scrolls with the content, revealing that the row continues. */
.msa-prose table{
  border-collapse:collapse;
  font-size:.9375rem;
  line-height:1.5;
  scrollbar-width:thin;
  /* The COVER gradients must be the colour the table actually sits on, which is white —
     they were --paper (#F4F4F5), so at rest they rendered as a permanent 40px pale-grey
     stripe instead of disappearing. Measured: that stripe was five times more prominent
     than the shadow marking the edge with more content, so the strongest cue on the page
     pointed at the EXHAUSTED side. And the shadow itself was 18% over 12px, which reads
     as a hairline; it is 34% over 20px now, on a scroller whose scrollbar occupies zero
     layout height on a phone and is therefore invisible at rest. */
  background:
    linear-gradient(90deg, var(--white) 30%, rgba(255,255,255,0)) left center / 2.5rem 100% no-repeat,
    linear-gradient(90deg, rgba(255,255,255,0), var(--white) 70%) right center / 2.5rem 100% no-repeat,
    radial-gradient(farthest-side at 0 50%, rgba(var(--stage-rgb),.34), rgba(var(--stage-rgb),0)) left center / 1.25rem 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(var(--stage-rgb),.34), rgba(var(--stage-rgb),0)) right center / 1.25rem 100% no-repeat;
  background-attachment:local, local, scroll, scroll;
}
.msa-prose :is(th,td){
  padding:.7rem .9rem;
  border-block-end:1px solid #E1E2E5;
  text-align:start;
  vertical-align:top;
}
.msa-prose th{
  font-family:var(--font-display);
  font-weight:600;
  border-block-end:2px solid var(--stage);
  white-space:nowrap;
}
.msa-prose thead th{background:var(--white)}
.msa-prose tbody tr:last-child :is(th,td){border-block-end:0}

/* AN EMPTY PARAGRAPH NOTHING SERVED. msa_drop_empty_paragraphs() removes these on the way out
   of the_content, and the served bytes of every page carry none — checked with curl against
   the deployed site. They appear anyway, inside .craic-tabs__body and .tpzone, because the
   tabs plugin rebuilds its panels in the BROWSER after every server filter has run. Measured
   on the deployed site at 1440: 18 of the 28 product pages, one to five each, each an empty
   box carrying the body's paragraph margins the moment its panel is opened.
   A runtime element wants a runtime rule. `:empty` matches only a paragraph with nothing in it
   at all — `<p> </p>` has a text node and is left alone, which is why the server-side rule that
   handles whitespace and &nbsp; is still the one doing that work. */
.msa-prose p:empty{display:none}

/* Likewise a column left with nothing in it. Converting the carbon-nanotube page's layout
   table to this grid gave one row an empty left cell — the row whose arrow pointed at a button
   that was never imported — and an empty grid child still takes its track and its gap. */
.msa-prose .cols > .col:empty{display:none}

/* ------------------------------------------------- the specification table
   Measured against the client's own live page before changing anything. Theirs sets the
   instrument name in the brand red at about 145px, the application at normal weight, and the
   description SMALLER AND DIMMER — three levels, deliberately. The rebuild had flattened all
   three to one size and one colour, and squeezed the first column to 105px so that every
   instrument name wrapped onto two lines: "20/30 / Film™", "CRAIC / CoalPro II™". The name is
   the row's subject and the column a buyer scans; it is the one thing that must not wrap.

   THE FIRST COLUMN SHRINKS TO ITS CONTENT. `width:1%` with `nowrap` is the idiom that makes a
   table give a column exactly what its longest cell needs and hand the remainder to the rest.
   The longest name in the catalogue is "CRAIC CoalPro II™"; nothing is hard-coded to it.

   No vertical rules, because the client's table has none and because a spec table with 24
   cells does not need 24 boxes drawn around it. The row hairline is the only structure. */
/* .msa-table--index is stamped by import/stamp-table-shape.py on the tables whose first
   column is an IDENTIFIER rather than a sentence — measured at 17 characters against 48 and
   82 on the other two. Position alone was not enough: the same rule applied to /solutions/,
   whose first column is prose, forced a 690px column and wrapped "GeoImage™" in the one
   beside it. CSS cannot measure what is in a cell, so the measurement is made once, upstream,
   and the table carries the answer. */
.msa-prose table.msa-table--index td:first-child,
.msa-prose table.msa-table--index th:first-child{
  width:1%;
  white-space:nowrap;
  font-family:var(--font-display);
  font-weight:600;
  color:var(--stage);
}
/* The client's third level, restored: the description supports the row, it does not compete
   with the instrument it describes. Index tables only — the last column of /solutions/ is the
   product link and of /support2/service-contracts/ is the price, and neither is support. */
.msa-prose table.msa-table--index td:last-child{
  font-size:.9375rem;
  color:var(--ink-dim);
}
.msa-prose table:has(th) th{
  font-size:.8125rem;
  letter-spacing:.01em;
  color:var(--ink-dim);
  border-block-end:1px solid var(--stage);
}

/* The sentence under a section title. Sized between the h1 and the body so it reads as the
   title's continuation rather than as the first paragraph of the content below it, and capped
   at the block measure so it never runs the full 1360 of the shell. */
/* THE DECK LINE, NOT A CAPTION. On /solutions/ the h1 is the single word "Solutions" and does
   almost no work; this sentence is what actually tells a reader what the page answers. It was
   set at 19px in --ink-dim, which is body-copy weight for the line carrying the page's whole
   framing, and it read as a caption under the title. Set as a deck: larger, in full ink, with
   the section gap under it so the lookup below reads as the answer to it rather than as the
   next paragraph. */
/* THE CONTAINER CARRIES THE GUTTER, THE CHILD CARRIES THE MEASURE — the split this stylesheet
   uses everywhere else, and getting it wrong here cost two attempts. Capping the <p> itself
   put it in the shell list AND gave it a max-inline-size narrower than the wrap, so
   `margin-inline:auto` centred the whole box: the deck line started at x=400 while the title
   it belongs to started at x=88. The paragraph stays wrap-wide and the span inside it holds
   the line length. */
.msa-index__stand > span{
  display:block;
  max-inline-size:34ch;
  text-wrap:balance;
}
@media (min-width:900px){ .msa-index__stand > span{max-inline-size:40ch} }

/* --ink WAS A NAME NOTHING DECLARED. Thirteen rules read it — here, in .msa-index__stand, and
   through both comparison tables — and no stylesheet, no PHP file and no inline style ever set
   it. A var() with no declaration and no fallback makes the WHOLE declaration invalid at
   computed-value time, and what that costs depends on the property it is in:

     color:var(--ink)                       inherits, which on this page is the same near-black
     border-block-end:2px solid var(--ink)  THE BORDER DOES NOT PAINT
     outline:3px solid var(--ink)           THE FOCUS OUTLINE DOES NOT PAINT

   so nine harmless uses hid four that were not: the header underline was missing from both
   tables on /solutions/microspectrophotometer-options/, and the links inside them had no focus
   indicator at all. It is --stage, and it always was: .msa-prose sets `color:var(--stage)` and
   eight other body-ink rules in this file say the same thing the same way. The phantom name is
   gone rather than declared — a second token for a colour that already has one is the defect
   S-TOKENS-03 exists for. That gate now fails any var() naming a property nothing declares. */

.msa-index__stand{
  margin-block:1.25rem 0;
  font-size:clamp(1.375rem,2.1vw,1.625rem);
  line-height:1.35;
  font-weight:500;
  letter-spacing:-.01em;
  color:var(--stage);
}

/* ================================== a tab panel whose copy sits above a lone picture
   MEASURED ON /solutions/applications/, the Introduction panel: two paragraphs 748px wide, then
   a figure 1198px wide holding a 576px image. 622px of the panel is white space beside the
   picture, the panel stands 785px tall to carry 811 characters, and that box is the first thing
   a reader meets on the page. The shape is in 21 panels across 8 pages; ten of them are on that
   one page.

   AND IT IS FIXED HERE, NOT IN THE CONTENT. Pairing them into a `.cols` grid in the stored
   markup was tried and reverted: craic-tabs rebuilds these panels in the BROWSER after every
   server filter, and re-parsing a panel whose body had been re-wrapped cost four image nodes
   out of twenty-two on that page. The picture's position beside the copy is presentation, so it
   belongs in the stylesheet, where the plugin's rebuild never sees it and no client content
   changes at all.

   `:has(> figure:last-child)` selects the shape; `:not(:has(.cols))` leaves alone the 13 panels
   an earlier release already paired in markup, so the two treatments cannot both apply. Below
   900px it does not apply either — one column is the right answer on a handset, and 900 is the
   breakpoint this stylesheet already switches its column grids at. */
@media (min-width:900px){
  /* RESERVED SPACE, because CSS cannot group siblings and the source order is copy-then-picture.
     Three attempts are recorded here so the fourth is not attempted blind:

       * a grid with the figure spanning every row — a spanning item contributes its height to
         the tracks it spans, so a 318px picture beside two 148px paragraphs sized both rows to
         159px and opened a 158px hole between the paragraphs. `grid-auto-rows:min-content` did
         not close it.
       * a float — a float only affects content that comes AFTER it, and the figure is the last
         child, so the picture dropped below the copy and sat right-aligned under it.
       * wrapping the copy in a column in the stored markup — that is the correct structure and
         it cost four image nodes of twenty-two on /solutions/applications/, because craic-tabs
         rebuilds these panels in the BROWSER and re-parsed the re-wrapped body. Reverted.

     So the body reserves a column on its trailing side and the figure is placed in it. Nothing
     depends on source order, no track can stretch, and no client content changes. The picture
     is capped at the body's own height so that an item taken out of flow cannot spill past the
     panel that holds it. */
  /* THE SAME SHAPE ONE LEVEL DOWN. A field subsection is a panel body in miniature — copy then
     one picture — and seven of them on /solutions/applications/ rendered the list above the
     image while the other six sat side by side, because those six were paired in markup and
     these were not. Measured: Materials Science, Pharmaceutical, Art, GeoImage, rIQ for Glass,
     Pharmaceutical & Biology and Art again. `img:last-child` is here too because one of them
     (GeoImage) ends in a bare <img> rather than a <figure>. */
  .craic-tabs__body:has(> :is(figure,img):last-child):not(:has(.cols)),
  .craic-tabs__subsection:has(> :is(figure,img):last-child):not(:has(.cols)){
    position:relative;
    padding-inline-end:calc(36% + 3.25rem);
  }
  .craic-tabs__body:has(> :is(figure,img):last-child):not(:has(.cols)) > :is(figure,img):last-child,
  .craic-tabs__subsection:has(> :is(figure,img):last-child):not(:has(.cols)) > :is(figure,img):last-child{
    position:absolute;
    inset-block-start:0;
    inset-inline-end:0;
    inline-size:36%;
    max-block-size:100%;
    margin:0;
  }
  /* THE SUBSECTION'S COLUMN IS THE PICTURE'S OWN WIDTH, not a percentage of it. 36% is right
     for a panel body, where the reserved column scales with the panel; inside a field
     subsection it made the picture 431px while the same picture in a paired column next to it
     was 384px, which is the width raggedness the plugin's plate rule sets out to end — and no
     amount of sizing in the plugin can reach it, because this rule is what sizes the box the
     picture is placed in. Reserved at the plate's own 24rem so both shapes land on one width.
     The panel-level rule above is left at 36% and is not part of the series. */
  .craic-tabs__subsection:has(> :is(figure,img):last-child):not(:has(.cols)){
    padding-inline-end:calc(24rem + 3.25rem);
  }
  .craic-tabs__subsection:has(> :is(figure,img):last-child):not(:has(.cols)) > :is(figure,img):last-child{
    inline-size:24rem;
  }
  /* `inline-size:100%` here ENLARGED the picture to fill the column it was given, which is
     the one thing the figure rule 200 lines above says never to do — these are 209-410px
     files and six of them have no larger original in the library. The column stays reserved
     at one width and the picture keeps its own pixels inside it, flush to the trailing edge
     the reservation already aligns it to. */
  .craic-tabs__body:has(> :is(figure,img):last-child):not(:has(.cols)) > figure:last-child img,
  .craic-tabs__subsection:has(> :is(figure,img):last-child):not(:has(.cols)) > figure:last-child img{
    inline-size:auto;
    max-inline-size:100%;
    block-size:auto;
    max-block-size:100%;
    margin-inline:auto 0;
    object-fit:contain;
    object-position:top right;
  }
}

/* ============================================================== the figure column
   The aside column on the Solutions field pages is a SERIES: a sample picture, a spectrum, and
   the line that names each. Measured before, on the three pages that share the shape — pictures
   at 212, 267 / 260, 217 / 255, 218px, so six pictures at six widths; the naming line set as
   body copy, indistinguishable from the argument in the column beside it; and the "Request
   information" button sitting BETWEEN the two pictures, breaking the series in half on every
   one of them.

   ONE COLUMN WIDTH AND NO PICTURE ENLARGED, the same rule the applications page's plates
   follow and for the same measured reason: these are 212-267px files with nothing larger in the
   library, so a uniform raster width is a uniform blur. The COLUMN is uniform; each picture
   keeps its own pixels and sits at the column's leading edge, so the series has one left edge
   to read down. */
.msa-prose .cols--aside > .col:has(figure, img){
  gap:var(--gap-head);
}
.msa-prose .cols--aside > .col > :is(figure, img){
  margin-inline:0;
  max-inline-size:100%;
}
.msa-prose .cols--aside > .col > figure > img,
.msa-prose .cols--aside > .col > img{
  inline-size:auto;
  max-inline-size:100%;
  block-size:auto;
  margin-inline:0;
}
/* THE NAMING LINE IS A CAPTION, and now says so in the markup as well — import/clean-bodies.py
   rule 20 turns a picture plus the short line after it into <figure><figcaption>, on the 40 of
   54 such pairs in this corpus that are captions rather than prose. Mono at 13px is the face
   this site already uses for instrument metadata (the hero's figure caption, the spectral
   scale), which is exactly what a caption naming a sample and a technique is. */
.msa-prose figcaption{
  margin-block-start:.7rem;
  font-family:var(--font-mono);
  font-size:.8125rem;
  line-height:1.55;
  color:var(--ink-dim);
  max-inline-size:34ch;
  /* LEFT, with the pictures. The caption inherited `text-align:center` from the imported
     figure and centred itself under a 267px picture inside a 380px column, so the one line
     naming the figure was the only thing in the series not starting at the column's edge. */
  text-align:start;
}
/* A run of links that were each a paragraph of their own, made a list by
   import/clean-bodies.py rule 22. It is a list of destinations, so it gets the site's list
   rhythm and its aperture marker, and none of the paragraph spacing it used to have. */
.msa-prose ul.msa-linkrun{
  margin-block:1.25rem;
  padding-inline-start:1.15rem;
}
.msa-prose ul.msa-linkrun > li{ margin-block:.45rem; }
/* THE BUTTON GOES AFTER THE PICTURES, not between them. `.col` is a column flex container
   (craic-tabs.css sets it, and this file's grid puts the aside column in one), so `order`
   moves it with no change to the stored content and no change to the reading order a screen
   reader or a narrow viewport gets — the same technique, and the same reason, as the product
   shot pinned after the capability grid in craic-tabs.css.

   Scoped to a column that actually holds pictures: in a text-only column there is nothing for
   the button to be wedged between and it should stay where its author put it. */
.msa-prose .cols--aside > .col:has(:is(figure, img)){
  display:flex;
  flex-direction:column;
  align-items:flex-start;
}
.msa-prose .cols--aside > .col:has(:is(figure, img)) > p.msa-cta{
  order:1;
  margin-block-start:var(--gap-head);
}

/* ================================================================ the sub-page masthead
   ONE CONTINUOUS DARK FIELD, which is the whole point. header.php's masthead is --stage; the
   breadcrumb sat on the page ground beneath it; msa_hero_html() was --stage again. Measured on
   every page of the /solutions/ tree: black to y=101, a 39px WHITE band, black again to y=781.
   A white slot cut between two blacks. The trail is inside this band now, so the dark runs from
   the top of the browser to where the content starts and there is no seam left to look at.

   It is also 250-ish px rather than 641. The hero it replaces was the home page's, photograph
   and all, on seven pages whose subjects it did not match. See inc/pagehead.php. */
.msa-pagehead{
  position:relative;
  background:var(--stage);
  color:var(--white);
  /* No block-start padding: header.php's masthead ends flush against this, and any gap here
     would put a hairline of page ground back between the two dark fields. */
  padding-block-end:0;
}
.msa-pagehead__inner{
  max-width:var(--wrap);
  margin-inline:auto;
  padding-inline:var(--gutter);
  padding-block:1.5rem 3.25rem;
}
/* The trail is a child of the band now, and the band already carries the wrap and the gutter.
   `.msa-crumbs` is in the shell selector list at the top of this file, so without this it
   applies BOTH and the trail indents twice — measured 96px in from the h1 beneath it. */
.msa-pagehead .msa-crumbs{
  max-width:none;
  margin-inline:0;
  padding-inline:0;
  padding-block-start:0;
  margin-block-end:2.25rem;
  color:var(--dim);
}
.msa-pagehead .msa-crumbs a{ color:var(--dim); border-block-end-color:transparent; }
.msa-pagehead .msa-crumbs a:hover,
.msa-pagehead .msa-crumbs a:focus-visible{ color:var(--white); border-block-end-color:var(--red); }
.msa-pagehead .msa-crumbs a:focus-visible{ outline:3px solid var(--white); outline-offset:3px; }
.msa-pagehead .msa-crumbs [aria-current="page"]{ color:var(--white); }

.msa-pagehead__title{
  /* One step down from the home page's hero: this is a page title, not the site's one claim. */
  font-size:clamp(2.125rem, 4.4vw, 3.375rem);
  font-weight:600;
  line-height:1.06;
  letter-spacing:-.02em;
  max-width:20ch;
  /* "Microspectrophotometer" IS 22 CHARACTERS and it is this client's core noun — the nav rule
     240 lines up says so and breaks it for the same reason. Measured on the deployed site at
     390px: "Microspectrophotometer Options" overflowed its own 350px box to 405px and grew the
     document to 425px, so /solutions/microspectrophotometer-options/ scrolled sideways by 35px
     on a phone. It was the only page in the tree that did, because it is the only one whose
     title carries the word. */
  overflow-wrap:break-word;
  hyphens:auto;
}
.msa-pagehead__stand{
  margin-block-start:1.25rem;
  max-width:var(--measure);
  font-size:clamp(1.0625rem, 1.35vw, 1.1875rem);
  line-height:1.6;
  color:var(--dim);
}


/* Shown only to someone who can edit the page, by inc/shortcodes.php, when the plugin that
   owns a shortcode is deactivated. It is a warning about the site, not part of the site, so it
   looks like one and never inherits the prose measure. */
.msa-offline{
  margin-block:1rem;
  padding:.75rem 1rem;
  border-inline-start:4px solid var(--red);
  background:rgba(238,46,36,0.08);
  color:var(--stage);
  font-size:.9375rem;
}

/* ---- the scale ------------------------------------------------------------------------
   200 to 2500nm across the full width, drawn to scale, so the visible octave takes the 17.4%
   of it that it actually takes. That proportion is the argument these instruments make, and
   stretching the visible part to look better would be making the opposite one. The numbers
   are the client's own: /about-craic/news/532-… gives 200-400, 400-700 and 700-2500 nm, and
   three product pages give "200 to 2500 nm". */
.msa-pagehead__scale{
  max-width:var(--wrap);
  margin-inline:auto;
  padding-inline:var(--gutter);
  padding-block-end:1.5rem;
}
.msa-pagehead__band{
  block-size:6px;
  /* The fallback is the band's OWN out-of-band colour, the faint white it already uses either
     side of 380-780nm. --spectrum is written into the style attribute by
     msa_pagehead_scale_html(), so the only way it goes missing is a plugin that strips inline
     styles — a real thing on a WordPress install the client adds plugins to. Without a
     fallback the declaration is invalid at computed-value time and the bar paints nothing: a
     6px gap where the spectrum should be. With one it is a plain neutral bar, which is what a
     spectrum you cannot colour honestly looks like. */
  background:var(--spectrum, rgba(255,255,255,0.24));
}
.msa-pagehead__ticks{
  position:relative;
  block-size:1.25rem;
  margin-block-start:.5rem;
  font-family:var(--font-mono);
  /* .75rem, not .6875rem. This file already holds the finding two hundred lines down — "11px is
     under every floor worth holding, and neither needs to be that small" — and raised the two
     other 11px label styles to 12px for it. This one shipped at 11px and is the same kind of
     thing. */
  font-size:.75rem;
  letter-spacing:.06em;
  color:var(--dim);
}
.msa-pagehead__tick{
  position:absolute;
  inset-block-start:0;
  /* No fallback here, deliberately, and it is not the oversight the band's was. A position has
     no sensible default: every candidate — 0, auto, 50% — stacks four labels on one spot, which
     reads as broken rather than as plain. If --at is ever missing the band above has already
     lost its colours, and four piled labels under a blank bar is the loudest possible way to
     say so. */
  inset-inline-start:var(--at);
  white-space:nowrap;
}
/* The first tick would hang off the leading edge and the last off the trailing one. */
.msa-pagehead__tick:first-child{ transform:none; }
.msa-pagehead__tick:not(:first-child):not(:last-child){ transform:translateX(-50%); }
.msa-pagehead__tick:last-child{ inset-inline-start:auto; inset-inline-end:0; }

@media (max-width:759px){
  .msa-pagehead__inner{ padding-block:1.25rem 2.5rem; }
  .msa-pagehead .msa-crumbs{ margin-block-end:1.5rem; }
  .msa-pagehead__title{ max-width:none; }
}

/* ============================================ the challenge / solution / tool lookup
   THE PAGE'S WHOLE VALUE, AND IT WAS SET LIKE A SPREADSHEET. Seven problems a buyer
   recognises, each with the instrument that answers it — and measured on the deployed page at
   1440, the columns came out 555 / 600 / 95px. The TOOL, which is the answer the reader came
   for, had 95px of 1264: seven and a half per cent of the table, so "GeoImage™" wrapped with
   its trademark orphaned on a second line and the link's own underline broken across the
   wrap. The two columns of prose had eighty-eight per cent between them and needed far less.

   So the proportions follow the reading instead: the challenge leads at 34%, the solution
   explains at 42%, and the tool — the thing to click — gets 24% and never wraps. The type
   carries the same order: the challenge is what a reader scans down the left until one of them
   is theirs, so it is the heaviest; the solution is the sentence they read once they stop; the
   tool is a destination, set as one rather than as an underlined fragment of a sentence.

   THE ROW IS THE UNIT. A 1,264px row of three columns is a long way for an eye to track, and
   the answer sits at the far end of it. Hover and keyboard focus light the whole row, so the
   line you are reading stays the line you are reading all the way across. */
.msa-prose table.msa-solve{
  inline-size:100%;
  table-layout:fixed;
  display:table;
  border-collapse:collapse;
  background:none;
  margin-block:var(--gap-head) var(--gap-section);
}
.msa-prose table.msa-solve :is(th,td){
  white-space:normal;
  vertical-align:top;
  text-align:start;
  padding:1.15rem 1.75rem 1.15rem 0;
  border-block-end:1px solid #E1E2E5;
}
.msa-prose table.msa-solve thead th{
  padding-block-end:.9rem;
  border-block-end:2px solid var(--stage);
  font-size:.8125rem;
  font-weight:600;
  letter-spacing:.04em;
  text-transform:uppercase;
  color:var(--ink-dim);
}
.msa-prose table.msa-solve tbody tr:last-child :is(th,td){border-block-end:0}
.msa-prose table.msa-solve :is(th,td):nth-child(1){inline-size:34%}
.msa-prose table.msa-solve :is(th,td):nth-child(2){inline-size:42%}
.msa-prose table.msa-solve :is(th,td):nth-child(3){inline-size:24%;padding-inline-end:0}

.msa-prose table.msa-solve tbody td:nth-child(1){
  font-size:1.125rem;
  font-weight:600;
  line-height:1.4;
  color:var(--stage);
}
.msa-prose table.msa-solve tbody td:nth-child(2),
.msa-prose table.msa-solve tbody td:nth-child(2) p{
  font-size:1rem;
  line-height:1.55;
  color:var(--ink-dim);
  margin:0;
  max-width:none;
}
/* The tool as a destination. An inline underline made the answer look like a footnote to the
   sentence beside it; a bordered chip makes it the thing to press, and the aperture square is
   the mark this site uses wherever it says "this is the subject". */
/* THE CHIP NEEDS ITS OWN CLASS TO WIN. `.msa-prose :is(p,li,td,…) > a:only-child` — the rule
   that gives a link which is its cell's whole content the block-link treatment — carries five
   classes of specificity, and a chip selected by position carries four. Measured: the chip
   kept `text-decoration:underline` in the brand red and `display:inline-block`, so it was a
   bordered box with an underlined word inside it. The anchors are classed in the content
   instead of the rule being out-nested, and the exclusion below says so where that rule is. */
.msa-prose table.msa-solve tbody td .msa-solve__tool,
.msa-prose table.msa-solve tbody td:nth-child(3) a{
  display:inline-flex;
  align-items:center;
  gap:.55rem;
  padding:.5rem .85rem;
  border:1px solid #D6D8DC;
  font-size:1rem;
  font-weight:600;
  color:var(--stage);
  text-decoration:none;
  transition:border-color 140ms ease, background-color 140ms ease;
}
.msa-prose table.msa-solve tbody td:nth-child(3) a::before{
  content:"";
  inline-size:8px; block-size:8px;
  flex:0 0 8px;
  background:var(--red);
}
.msa-prose table.msa-solve tbody td:nth-child(3) a:hover{border-color:var(--stage);background:#fff}
.msa-prose table.msa-solve tbody td:nth-child(3) a:focus-visible{
  outline:3px solid var(--stage); outline-offset:2px;
}
/* Tracking the row across the table. Not a stripe on every other row — that decorates all
   seven; this marks the one being read. */
/* THE HIGHLIGHT GETS ITS PADDING WITHOUT MOVING THE TEXT. The row tint ran flush to the
   text on both sides — measured, the first cell's inline-start padding is 0, because the
   challenge has to start at x=88 on the same grid line as the h1 and the standfirst above it.
   Padding the cell would have pushed the copy off that line to buy the tint some air. A
   box-shadow spreads the same colour 1rem past each end of the row instead, so the highlight
   is inset from the text while the text stays exactly where the grid puts it. */
.msa-prose table.msa-solve tbody tr:hover,
.msa-prose table.msa-solve tbody tr:focus-within{
  background:#EDEEF0;
  box-shadow:-1rem 0 0 #EDEEF0, 1rem 0 0 #EDEEF0;
}

@media (max-width:759px){
  /* Row-major, and the rows ARE the items here, so the site's own stacking is the right one:
     each challenge becomes a block carrying its solution and its tool, labelled from the
     client's own data-th. */
  .msa-prose table.msa-solve,
  .msa-prose table.msa-solve thead,
  .msa-prose table.msa-solve tbody,
  .msa-prose table.msa-solve tr,
  .msa-prose table.msa-solve :is(th,td){display:block}
  .msa-prose table.msa-solve thead{position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%)}
  .msa-prose table.msa-solve :is(th,td):nth-child(1),
  .msa-prose table.msa-solve :is(th,td):nth-child(2),
  .msa-prose table.msa-solve :is(th,td):nth-child(3){inline-size:auto}
  .msa-prose table.msa-solve tbody tr{padding-block:1.35rem;border-block-end:1px solid #E1E2E5}
  .msa-prose table.msa-solve tbody tr:last-child{border-block-end:0}
  .msa-prose table.msa-solve tbody td{padding:0;border-block-end:0}
  .msa-prose table.msa-solve tbody td:nth-child(2){margin-block-start:.35rem}
  .msa-prose table.msa-solve tbody td:nth-child(3){margin-block-start:.9rem}
}

/* ================================================== the instrument comparison matrix
   WHAT IT REPLACES, MEASURED IN CHROMIUM AT 1440 ON THE DEPLOYED SITE. Three
   `.msa-compare__item` cards, 662, 695 and 720px tall — three independent columns of three
   different contents, so nothing lined up. The "Read the …" link, the one action the page
   offers, landed on three different lines; the key-feature lists ran 2, 4 and 4 items, and
   the 508 PV's second item was a SENTENCE where the other two columns had values. A reader
   could not follow a single axis across, which is the only thing a comparison is for.

   SO IT IS A TABLE, because that is what this content has always been: three instruments,
   five axes, one value per cell. The rows align because rows are what a table has. The
   <th scope="col"> and <th scope="row"> give a screen reader the association for free — a
   grid of divs would have to fake it and would fake it worse.

   THE RANGE ROW IS THE ONE SET LARGE. Of the five axes it is the one that decides most
   purchases — 350–1,000 against 240–1,000 against 200–2,500 nm is the whole choice for a
   buyer who knows their sample — and tabular figures put the digits on one optical column so
   the three can be read against each other rather than merely read.

   NO FEATURED COLUMN, no badge, no recommended instrument. Which one is right depends on the
   sample, and the client does not rank them. A pricing-table treatment would answer a
   question this page is not entitled to answer. */
.msa-prose table.msa-matrix{
  inline-size:100%;
  /* FIXED, so the columns are equal and the comparison is a comparison. With auto layout the
     three column heads sized to their own content — the 508 PV's summary sentence is 145
     characters and the FLEX PRO's 131 — so one instrument took the width of the table and the
     other two were pushed past its right edge. Equal columns are also the honest layout: the
     three are being weighed against each other, and a column that is wider because its
     sentence is longer weighs it more. */
  table-layout:fixed;
  /* AND IT MUST STILL BE A TABLE BOX. The site's generic table rule sets `display:block` with
     `overflow-x:auto`, which is right for a wide data table that has to scroll — and it makes
     `table-layout:fixed` inert, because a block is not a table. Measured with it inherited:
     the three columns laid out at 1158, 1137 and 1343px inside a 1264px table, a scrollWidth
     of 3782, and two of the three instruments off the right edge. Fixed layout means this one
     cannot overflow, so it does not need the scroller and can be a table again. */
  display:table;
  border-collapse:collapse;
  margin-block:var(--gap-head) var(--gap-section);
  background:none;
}
.msa-prose table.msa-matrix :is(th,td){
  vertical-align:top;
  text-align:start;
  /* WRAPPING, EXPLICITLY. A generic rule further down sets `white-space:nowrap` on data-table
     cells, which is right for a row of short values and fatal here: a fixed-layout table
     still cannot shrink a cell below the width of an unbreakable line, so three 130-character
     summaries on one line gave the table a scrollWidth of 2,220 inside a 1,264px box and
     pushed 868px of horizontal scroll onto the whole document. */
  white-space:normal;
  padding:1.15rem 1.5rem 1.15rem 0;
  border-block-end:1px solid #E1E2E5;
}
.msa-prose table.msa-matrix tbody tr:last-child :is(th,td){border-block-end:0}
/* The corner is empty by design: it is the cell where the row axis meets the column axis and
   there is nothing true to put in it. It carries no rule, so the header row reads as three
   instruments rather than four columns. */
.msa-prose table.msa-matrix .msa-matrix__corner{border-block-end:0;inline-size:11rem}

.msa-prose table.msa-matrix thead th{
  padding-block-end:1.5rem;
  border-block-end:2px solid var(--stage);
  font-weight:400;
}
/* THE COMPARISON IS THE PAGE'S PAYLOAD AND IT WAS THE SMALLEST TEXT ON IT. Measured at 1440
   before this: the matrix set its cells at 17px, its row labels and column ledes at 15px and
   its footnote at 14px, while the prose UNDER it — the explanation of what the table already
   said — ran at 18px. Seven type sizes on one page, four of them below the body size, with the
   four smallest all inside the thing a buyer came to read. The table now sets at the body size
   and the sizes below it step down from there rather than up to it. */
.msa-matrix__shot{display:block;margin-block-end:1rem}
/* The photograph is the fastest way to tell three instruments apart, and it was rendering at
   166x230 from a 540x750 file — 31% of the size on disk. Given the column, it doubles. */
.msa-matrix__shot img{
  display:block;
  inline-size:100%;
  max-inline-size:15rem;
  block-size:auto;
  mix-blend-mode:multiply;   /* the shots are on white; this seats them on the page's ground */
}
.msa-matrix__name{
  display:block;
  font-size:1.375rem;
  font-weight:700;
  line-height:1.2;
  letter-spacing:-.01em;
  color:var(--stage);
}
.msa-matrix__lede{
  display:block;
  margin-block-start:.5rem;
  font-size:1rem;
  line-height:1.5;
  font-weight:400;
  color:var(--ink-dim);
}
.msa-prose table.msa-matrix tbody th{
  font-size:1rem;
  font-weight:500;
  color:var(--ink-dim);
  padding-inline-end:2rem;
}
.msa-prose table.msa-matrix tbody td{
  font-size:1.125rem;
  line-height:1.45;
  color:var(--stage);
}
.msa-prose table.msa-matrix tr.msa-matrix__row--range td{
  font-size:1.5rem;
  font-weight:600;
  letter-spacing:-.015em;
  font-variant-numeric:tabular-nums lining-nums;
}
.msa-matrix__none{color:var(--ink-dim)}
/* AN ANSWER THAT IS A PROPERTY, NOT A SPECIFICATION. The 508 PV is a modular head, so its
   transmission, reflection and fluorescence ranges are whatever the buyer's own microscope
   does. That IS the answer, and the client's card gives it in one sentence covering all three
   rows — but set in the same ink and weight as "UV-VIS-NIR" in the columns beside it,
   "Determined by your microscope" three times running reads as three blanks where the other
   two instruments have data. Dimmed and set in the label face, it reads as the kind of answer
   it is: this one depends on your kit, which is the whole point of the modular one. */
.msa-prose table.msa-matrix .msa-matrix__depends{
  color:var(--ink-dim);
  font-family:var(--font-mono);
  font-size:.8125em;
  line-height:1.5;
  letter-spacing:.01em;
  display:inline-block;
}
.msa-prose table.msa-matrix tfoot td{
  padding-block:1.5rem;
  border-block-end:0;
}
.msa-matrix__more{
  font-size:1.0625rem;
  font-weight:600;
  color:var(--stage);
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-decoration-color:var(--red);
  text-underline-offset:.2em;
}
.msa-matrix__more:hover{text-decoration-thickness:2px}
.msa-matrix__more:focus-visible{outline:3px solid var(--stage);outline-offset:3px}
/* A FOOTNOTE TO THE TABLE, SET AS ONE. This sentence explains the three cells in the 508 PV
   column that answer "determined by your microscope" — it is the table's own small print. Set
   as an ordinary dim paragraph it read as a stray line stranded between the table above it and
   the argument below, belonging to neither, which is exactly how the client described it.

   It now sits tight under the table, in the same mono the cells it explains are set in, behind
   the aperture square this site uses wherever it says "this is the subject". Then a full
   section gap, so the prose that follows starts as a new thing rather than continuing it. */
.msa-prose .msa-matrix__note{
  display:flex;
  gap:.6rem;
  margin-block:.75rem var(--gap-section);
  font-family:var(--font-mono);
  font-size:.8125rem;
  line-height:1.55;
  letter-spacing:.01em;
  color:var(--ink-dim);
  max-inline-size:var(--measure-block);
}
.msa-prose .msa-matrix__note::before{
  content:"";
  flex:0 0 7px;
  inline-size:7px;
  block-size:7px;
  margin-block-start:.5em;
  background:var(--red);
}

@media (max-width:759px){
  /* BY AXIS, NOT BY INSTRUMENT, and that is the honest reading of this DOM. A table is
     row-major: the source order is the three column heads, then each axis with its three
     values. A phone layout that claimed to give one instrument at a time would have to
     reorder the DOM, and reordering a comparison is how a screen reader and a sighted reader
     end up with different pages.

     So each ROW becomes a block: the axis as its heading, then that axis's three values, each
     carrying the instrument's own name from `data-th` — the same mechanism the site's other
     data tables use. "Spectral range — 508 PV™ 350–1,000 nm, FLEX PRO™ 240–1,000 nm,
     2030PV PRO™ 200–2.500 nm" is a comparison a thumb can read. The three column heads keep
     their photographs and stack above it, so the reader meets the instruments first. */
  .msa-prose table.msa-matrix,
  .msa-prose table.msa-matrix thead,
  .msa-prose table.msa-matrix tbody,
  .msa-prose table.msa-matrix tfoot,
  .msa-prose table.msa-matrix tr,
  .msa-prose table.msa-matrix :is(th,td){display:block}
  .msa-prose table.msa-matrix .msa-matrix__corner{display:none}

  /* THE GENERIC STACKING HIDES A THEAD, AND THIS ONE MUST NOT BE HIDDEN. The site's other
     data tables are row-major — the items are the rows — so below 760px their column
     headings move into each cell through `data-th` and the <thead> is taken out of the flow
     (`position:absolute;width:1px;clip-path:inset(50%)`). This matrix is column-major: its
     thead IS the three instruments, photographs and all. Measured before this override: the
     header cell computed to 24px wide inside a 1px thead, and every instrument photograph
     rendered at zero. */
  .msa-prose table.msa-matrix thead{
    position:static;
    inline-size:auto;
    block-size:auto;
    overflow:visible;
    clip-path:none;
  }
  .msa-prose table.msa-matrix thead th{
    padding-block:0 1.75rem;
    border-block-end:0;
  }
  .msa-prose table.msa-matrix thead tr{
    border-block-end:2px solid var(--stage);
    margin-block-end:.5rem;
  }
  .msa-matrix__shot img{max-inline-size:9rem}

  .msa-prose table.msa-matrix tbody tr{
    padding-block:1.25rem;
    border-block-end:1px solid #E1E2E5;
  }
  .msa-prose table.msa-matrix tbody tr:last-child{border-block-end:0}
  .msa-prose table.msa-matrix tbody th{
    padding:0 0 .55rem;
    border-block-end:0;
    font-size:.875rem;
    letter-spacing:.04em;
    text-transform:uppercase;
  }
  .msa-prose table.msa-matrix tbody td{
    padding:.2rem 0 0;
    border-block-end:0;
    font-size:1.0625rem;
  }
  /* The instrument's own name in front of its value, from the client's markup. */
  .msa-prose table.msa-matrix tbody td::before{
    content:attr(data-th) " ";
    color:var(--ink-dim);
    font-size:.875rem;
  }
  .msa-prose table.msa-matrix tr.msa-matrix__row--range td{font-size:1.25rem}
  .msa-prose table.msa-matrix tr.msa-matrix__row--range td::before{
    font-size:.875rem;font-weight:400;letter-spacing:normal;
  }
  .msa-prose table.msa-matrix tfoot td{padding-block:.6rem 0;border-block-end:0}
  .msa-prose table.msa-matrix tfoot td:empty{display:none}
}

/* ------------------------------------------------- below 760px it stops being a table
   A three-column specification table cannot be read at 350px, and the horizontal scroller
   above — correct for a wide data table — asks a reader on a phone to swipe every row to find
   the column they want. Each row becomes a block instead: the instrument name, then each value
   under its own column heading.

   THE LABELS ARE ALREADY IN THE CONTENT. import/transform.py stamps `data-th` on every cell of
   the three real data tables — 53 attributes, measured — so this reads the client's own column
   headings rather than inventing a mechanism to carry them.

   760, because it is the breakpoint this stylesheet already switches the header and the column
   grids at. One number, not a fourth one. */
@media (max-width:759px){
  .msa-prose table:has(th),
  .msa-prose table:has(th) tbody,
  .msa-prose table:has(th) tr,
  .msa-prose table:has(th) :is(th,td){display:block}
  .msa-prose table:has(th){background:none;overflow-x:visible}
  .msa-prose table:has(th) thead{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
  .msa-prose table:has(th) tbody tr{
    padding-block:1.25rem;
    border-block-end:1px solid #E1E2E5;
  }
  .msa-prose table:has(th) tbody tr:last-child{border-block-end:0}
  .msa-prose table:has(th) td{padding:0;border:0}
  /* The instrument name leads its block, marked with the aperture — the same square the
     eyebrows and the card links carry, so the reader meets one shape wherever this site says
     "this is the subject". */
  .msa-prose table.msa-table--index td:first-child{
    width:auto;
    white-space:normal;
    font-size:1.0625rem;
    margin-block-end:.6rem;
    padding-inline-start:1.05rem;
    position:relative;
  }
  .msa-prose table.msa-table--index td:first-child::before{
    content:"";
    position:absolute;
    inset-inline-start:0;
    inset-block-start:.45em;
    inline-size:8px;
    block-size:8px;
    background:var(--red);
  }
  /* Every other cell carries its column's heading, from the client's own markup. */
  .msa-prose table:has(th) td[data-th]:not(:first-child){
    margin-block-start:.5rem;
    font-size:1rem;
    color:var(--stage);
  }
  .msa-prose table:has(th) td[data-th]:not(:first-child)::before{
    content:attr(data-th);
    display:block;
    font-size:.75rem;
    font-weight:600;
    color:var(--ink-dim);
    margin-block-end:.15rem;
  }
  .msa-prose table.msa-table--index td:last-child{font-size:.9375rem;color:var(--ink-dim)}
}
/* A cell whose content is a bare string sits at the top of its box; one whose content is a
   <p> is pushed down by the paragraph's own margin. Both shapes are in one table on
   /solutions/, so its rows read as if the columns were misaligned. The cell owns the
   padding; the paragraph inside it owns none. */
.msa-prose :is(th,td) > :first-child{margin-block-start:0}
.msa-prose :is(th,td) > :last-child{margin-block-end:0}

/* ------------------------------------------------- legacy classes, kept in use
   These come from the client's own Joomla templates and the import deliberately
   preserved them. Each one was doing nothing until now.

   .redcopy is the one that needs stating: it is red emphasis in the source, and
   red emphasis is what this design cannot reproduce literally — brand red on
   white measures 4.16:1, which clears AA for large text only and can never be the
   colour of a 16px sentence. So the emphasis is carried by weight, and the red
   goes into a rule beneath it, where it is decoration and carries no contrast
   requirement. Same argument, same answer, as the prose link colour above. */
.msa-prose .redcopy{
  font-weight:600;
  color:var(--stage);
  box-shadow:inset 0 -2px 0 var(--red);
}
/* The lead-in line at the top of a tab panel ("Key Features*", "The Power of Raman
   Microspectroscopy"). It is a <p> in the source, so it cannot become a heading
   element here without rewriting content — it is given a heading's typography and
   left as the paragraph it is. */
.msa-prose .tabheadin{
  margin-block:0 .9rem;
  font-family:var(--font-display);
  font-weight:600;
  font-size:1.0625rem;
  line-height:1.3;
  color:var(--stage);
}
/* Fine print: the trademark statement, the specifications disclaimer, the medical-use
   sentence. Quiet, but --dim-ink on --paper is 7.2:1, so quiet is not unreadable. */
.msa-prose :is(.legal-note,.footer_excerpt){
  font-size:.875rem;
  line-height:1.65;
  color:var(--ink-dim);
}
.msa-prose .legal-note{margin-block-start:2.5rem}
/* `max-width:none` released the fine print from the body measure so it fills its column.
   On a railed page that column is 680px and nothing showed. On the index template the prose
   runs the full 1,264px, and the disclaimer ran 126 characters a line — the longest measure
   on the site, on its least readable type. Capped to the same absolute measure the body uses.
   NOT `var(--measure)`: that is 68ch, and ch resolves against the element's OWN font, so at
   14px it would compute to about 476px and set the fine print narrower than the copy above
   it. --measure-block is in rem and stays put. */
.msa-prose .legal-note p{margin-block:.5rem;max-width:var(--measure-block)}
/* How to reach us: one block wherever the client lists contact methods — the Contact page, the
   support pages, and fifteen service tabs (import/content-standards.py builds the tabs' and
   pages' from the client's own labels). It was unstyled markup, so each label rendered as a full
   h3 subsection: three headings of the same size as the copy's own for "Phone or Fax". The label
   is set as a small caption over its lines, the methods side by side where the column allows,
   one rule above the group. The label stays an h3 for the outline; only its voice is quieter. */
.msa-prose .contact-methods{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,14rem),1fr));
  gap:1.25rem 2.5rem;
  max-width:var(--measure-block);
  margin-block:1.75rem 1.5rem;
  padding-block-start:1.25rem;
  border-block-start:1px solid var(--line-ink);
}
.msa-prose .contact-method :is(h2,h3,h4,h5,h6){
  margin:0 0 .4rem;
  font-family:var(--font-body);
  font-size:.875rem;
  font-weight:700;
  line-height:1.35;
  letter-spacing:.01em;
  color:var(--ink-dim);
}
.msa-prose .contact-method p{margin:0;max-width:none}
.msa-prose .contact-method p em{font-size:.875rem;color:var(--ink-dim)}
.msa-prose .contact-methods:last-child{margin-block-end:0}
/* NOT A TAB'S OWN PICTURE. The lone-figure centring rule above reaches any figure inside an
   unclassed <div>, and a page that wraps its tab set in one (/products/2030-microspectrophotometer/)
   handed a floated tab photograph `margin-inline:auto`, which on a float is 0: the words butted
   against the picture there and on 508 PV, GeoImage and the polarizer kits, while 2030XL — no
   wrapper — kept its 2rem gutter. Excluded at the rule, as the comment there already intends. */
/* PICTURES IN A ROW. `p.image-row` is the sister build's mark for two figures meant side by side
   (the Apollo M's closing spectra, the graphene page); unstyled here they stacked at the left
   edge. They sit together, centred, each at its own width up to half the column. */
.msa-prose .image-row{display:flex;flex-wrap:wrap;justify-content:center;align-items:flex-start;gap:1rem 1.5rem}
/* Side by side only where both fit at a readable size: each asks for 16rem and grows to 24rem, so
   in the Apollo page's full column the pair sits together, and in the graphene page's narrow
   column (~430px) they wrap to one per line at full size rather than shrink to ~200px apiece. */
.msa-prose .image-row img{display:block;flex:1 1 16rem;min-width:0;max-width:min(100%,24rem);height:auto;object-fit:contain}
/* A TALL PHOTOGRAPH DOES NOT FILL A PHONE. A lone portrait image at full column width ran ~845px
   tall at 390px (the graphene page's Apollo M), so the first screen held a picture and nothing
   else. On a phone a prose image is held to 60% of the screen's height, its width following. */
@media (max-width:640px){
  .msa-prose :is(figure,p,div) > img:not(.inline-icon){max-height:60vh;width:auto;margin-inline:auto}
}
/* AN ICON IN A SENTENCE stays in it. `.msa-prose :is(img, …)` makes every prose image a block of
   its own — right for a figure, wrong for the 17px PDF mark in "Conditions of Sales [icon].",
   which landed on its own line with the full stop on a third. content-standards.py marks an
   image no larger than 32px, inside a block that also holds words, as `.inline-icon`. */
/* The column selector is there for SPECIFICITY: `.msa-prose .cols > .col img{display:block}`
   (0,3,1) outranked the plain rule inside the support tabs' two-column layout and the icon went
   back to a line of its own. No margin: content-standards.py puts a real space between an icon
   and the words beside it, so every icon sits the same distance from its text. */
.msa-prose img.inline-icon,
.msa-prose .cols > .col img.inline-icon{display:inline-block;vertical-align:-.2em;margin:0}
/* A regional sub-heading on the worldwide-sales page. The source paints it #aaa —
   2.3:1 on this ground, and it was authored for a dark template. */
.msa-prose .bheaderdark2{color:var(--stage)}
/* A citation, on the publications pages. */
.msa-prose .gs_citr{
  margin-block:1rem;
  padding-inline-start:1rem;
  border-inline-start:2px solid #E1E2E5;
  font-size:.9375rem;
  line-height:1.55;
  color:var(--ink-dim);
}
.msa-prose .contentheading{
  font-family:var(--font-display);
  font-weight:600;
  font-size:clamp(1.1875rem,1.05rem + .45vw,1.375rem);
  line-height:1.2;
  margin-block:2.5rem .5rem;
}

/* ------------------------------------------------------------------ the rule
   <hr> appears 12 times in imported bodies and had no rule, so it rendered as the
   UA's inset 3D groove. */
.msa-prose hr{
  border:0;
  border-block-start:1px solid #E1E2E5;
  margin-block:2.5rem;
}

/* ==================================================================== the footer
   footer.php was `<footer class="msa-foot"></footer>` and this file had no rule for
   it — an empty black-less band on all 219 pages. It is the design's third plane:
   the stage the specimens sit on, closing the page the way the header opens it.

   The grid is brand | sections | contact, collapsing to one column below 700px in
   the same order. minmax(0,…) on every track for the reason the editorial grid
   states: an email address is one unbreakable word and it is IN this footer. */
.msa-foot{
  background:var(--stage);
  color:var(--paper);
  /* NO top margin, and this is the theme's oldest rule restated rather than a preference.
     The page shell's note says it in capitals — "PADDING, NEVER MARGIN, on these outermost
     blocks" — because S-BALANCE-08 rule 2 measures a section's rendered gap ABOVE against
     its gap BELOW, walking out to the nearest real sibling: the header's bottom edge and
     this element's top edge. Every template's section sits flush against both, with its
     breathing room as PADDING inside it, so the measurement is 0 above and 0 below and the
     rhythm is symmetric.

     A `margin-block-start:var(--gap-section)` here opened a 72px gap below every section
     while the gap above stayed 0, on all 218 pages. Measured on four page types before it
     was removed: msa-feature, msa-news, msa-article and msa-index all reported above=0,
     below=72. The gate caught it, which is what it is for.

     Nothing is lost visually: the section already ends with --gap-section of its own bottom
     padding (.msa-prose, or .msa-cards on an Index page), and .msa-foot__inner adds 3.5rem
     of its own above the first column. */
}
.msa-foot__inner{
  max-width:var(--wrap);
  margin-inline:auto;
  padding:3.5rem var(--gutter);
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:2.5rem;
}
@media (min-width:700px){
  .msa-foot__inner{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) minmax(0,1.2fr);gap:2rem 3rem}
}
.msa-foot .msa-brand{display:inline-flex;align-items:center;gap:.6rem;text-decoration:none;color:inherit;padding-block:.2rem}
.msa-foot__blurb{
  margin-block:1rem 0;
  max-width:34ch;
  font-size:.9375rem;
  line-height:1.6;
  color:var(--dim);
}
/* The column headings are h2s so the footer is navigable by heading, and they are
   sized as labels rather than as headings — a footer whose column titles compete
   with the page's own h2s is the five-competing-titles defect in the basement. */
.msa-foot__h{
  margin:0 0 .9rem;
  font-family:var(--font-mono);
  font-size:.6875rem;
  font-weight:400;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--dim);
}
/* The gap is small because the LINKS carry the spacing: each one is padded to a 24px
   hit box (WCAG 2.5.8), which a 22px line of 15px type is 2px short of on its own.
   Measured across 657 renders, the footer was the only thing on this site with a
   control under 24px, and it was every control in it. */
.msa-foot ul{list-style:none;margin:0;padding:0;display:grid;gap:.15rem}
.msa-foot a{
  display:inline-block;
  padding-block:.2rem;
  color:var(--paper);
  text-decoration-line:underline;
  text-decoration-color:transparent;
  text-decoration-thickness:2px;
  text-underline-offset:.18em;
  transition:text-decoration-color .12s ease;
}
.msa-foot a:hover,
.msa-foot a:focus-visible{text-decoration-color:var(--red)}
.msa-foot a:focus-visible{outline:3px solid var(--paper);outline-offset:3px}
.msa-foot__note{color:var(--dim);font-size:.875rem}
.msa-foot__address{
  margin-block-start:1rem;
  font-style:normal;
  font-size:.9375rem;
  line-height:1.6;
  color:var(--dim);
}

/* The legal bar. Separated by a rule rather than by a colour change: two dark bands
   with a 1px line between them reads as one surface with a closing statement, which
   is what it is. */
.msa-foot__legal{border-block-start:1px solid rgba(var(--paper-rgb),.14)}
.msa-foot__inner--legal{
  padding-block:1.5rem;
  grid-template-columns:minmax(0,1fr);
  gap:.75rem;
  align-items:baseline;
}
@media (min-width:700px){
  .msa-foot__inner--legal{grid-template-columns:auto 1fr;gap:.5rem 2rem}
  .msa-foot__tm{grid-column:1 / -1}
}
.msa-foot__copy,
.msa-foot__tm{margin:0;font-size:.8125rem;line-height:1.6;color:var(--dim)}
/* `.msa-foot ul` above is a class PLUS a type selector and outranks a bare class, so a
   plain `.msa-foot__legal-links` rule lost its display:flex to the grid and the two links
   stacked. Qualified rather than made !important. */
.msa-foot ul.msa-foot__legal-links{display:flex;flex-wrap:wrap;gap:0 1.25rem;font-size:.8125rem}
.msa-foot__legal-links a{padding-block:.32rem}
@media (prefers-reduced-motion:reduce){.msa-foot a{transition:none}}

/* ------------------------------------------------------------------ the eyebrow
   craic-forms emits `<p class="eyebrow">` above the enquiry form's heading and its
   own stylesheet says, in as many words, "the plugin reuses the theme's
   .btn/.eyebrow". This theme never defined one, so on every product page the label
   above the form rendered as an ordinary paragraph of body copy — the same shape as
   the sentence above it, doing none of the work an eyebrow does.

   Same treatment as the hero's own eyebrow and the footer's column headings: mono,
   small, letterspaced, --dim-ink. Three places using one voice for a label. */
.eyebrow{
  margin:0 0 .4rem;
  font-family:var(--font-mono);
  font-size:.6875rem;
  font-weight:400;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--ink-dim);
}
.msa-stage{--ink-dim:var(--dim)}

/* ============================================ the second dark surface, as a reusable class
   --stage is the page's dark plane. This is a sub-area WITHIN a page, and it exists because
   the site kept painting one on the other: the navigation panels opened --stage over a
   --stage masthead, and the Techniques band was --stage between two white sections, so a
   block whose job was to be its own area was the same colour as everything around it.

   TWO CLASSES, ONE SURFACE. .msa-panel is the surface itself and works on any element — a
   card, a callout, an aside. .msa-sec--panel is the page-section variant, which additionally
   breaks out of the shell to the viewport edges the way .msa-sec--stage does, by shadow
   spread rather than 100vw (which includes the scrollbar, a bug this project has paid for).

   A SURFACE NAMES ITS OWN INK. Both set --ink-dim, so secondary type reads the right grey
   without knowing where it is — the mechanism an earlier release added after --dim-ink was
   found rendering 2.84:1 inside a component that was dark by colour and light by selector. */
.msa-panel{
  background:var(--stage-panel);
  color:var(--paper);
  --ink-dim:var(--dim-panel);
}
.msa-sec--panel{
  background:var(--stage-panel);
  color:var(--paper);
  --ink-dim:var(--dim-panel);
  box-shadow:0 0 0 100vmax var(--stage-panel);
  clip-path:inset(0 -100vmax);
  padding-block:var(--gap-section);
  margin-block:0;
}
.msa-sec--panel h2,
.msa-sec--panel .msa-figname,
.msa-sec--panel .msa-figlink{ color:var(--white); }
/* Two of these in a row are one area, not two — the same rule .msa-sec--stage states. */
.msa-sec--panel + .msa-sec--panel{ margin-block-start:0; padding-block-start:0; }

/* ============================================================== the content rail
   At 1440px an Article or Product page rendered a 68ch column against the left
   gutter of a 1360px shell and left half the viewport empty — and these are the
   site's DEEP pages, 139 of which are reached only from a card grid, with no route
   onward and no route to the enquiry form once a reader arrives. The shape is the
   sister site's, which the client has already approved: content column, then a
   sticky rail carrying an enquiry card and the page's siblings.

   .msa-body is the grid and it is emitted whether or not there is a rail, so the
   railed and unrailed pages share one shell. With one child it is one column, which
   is exactly what a page with no siblings should render.

   The rail appears at 1100px — below that the content column would be under 40ch
   beside a 17rem rail, which is narrower than the copy needs and is the width at
   which the sister site drops its own rail too. The rail follows the copy in SOURCE
   order, so a narrow viewport and a screen reader both get the page before its
   navigation, and no `order` property is used to fake it. */
.msa-body{
  max-width:var(--wrap);
  margin-inline:auto;
  padding-inline:var(--gutter);
  display:grid;
  grid-template-columns:minmax(0,1fr);
  /* Below 1100px the rail sits UNDER the copy, and the row gap was --gap-section on top of
     .msa-prose's own --gap-section bottom padding — measured 152-186px above the rail and
     0px below it, its light nav list butting straight onto the black footer. On every one
     of the 197 railed pages, which is every phone. The prose drops its bottom padding when
     it is stacked above a rail (below), so the single --gap-section here is the whole gap,
     and the rail's own bottom is the section's bottom padding, restored below. */
  gap:var(--gap-section) 0;
  align-items:start;
}
.msa-body--railed > .msa-prose{padding-block-end:0}
.msa-body--railed{padding-block-end:var(--gap-section)}
@media (min-width:1100px){
  /* Side by side again: the prose owns its bottom padding and the wrapper adds none. */
  .msa-body--railed > .msa-prose{padding-block-end:var(--gap-section)}
  .msa-body--railed{padding-block-end:0}
}
/* The prose inside the grid has already been given the page gutter by the grid, and
   the shell rule above would add it a second time. Same for the width cap: the grid
   track IS the width now. */
.msa-body > .msa-prose{max-width:none;margin-inline:0;padding-inline:0}
@media (min-width:1100px){
  /* THE CONTENT TRACK IS THE READING MEASURE, not "whatever is left".
     It was `minmax(0,1fr)` — 904px — while the copy inside it caps at --measure (680px), so
     the rail was positioned against a track edge the text never reached and stood 296px away
     from the words it belongs to, on 86 of 90 news pages. Extending the measure to more
     wrappers made that WORSE, not better: every wrapper newly capped is another page whose
     copy stops short of the track. The two repairs were pulling against each other, and this
     is the one that resolves both — size the track to the measure and the gap is the gap.

     `fit-content` rather than a hard width: a table, a .cols grid or a figure wider than the
     measure still takes the space it needs up to the maximum, so nothing that was full-width
     is squeezed. */
  .msa-body--railed{
    /* --measure, not --measure-block. The block value (42.5rem) is 68ch expressed as a
       LENGTH for headings whose own font-size would otherwise change what `ch` means; the
       actual body measure is what the copy uses. And NO gutter is added: .msa-body already
       carries the page gutter, and .msa-prose inside it is explicitly padding-inline:0, so a
       track of measure+gutters left 96px of empty track between the last word and the rail.
       The track is the reading measure, exactly. */
    grid-template-columns:minmax(0, var(--measure)) minmax(15rem,18rem);
    gap:clamp(2rem,1rem + 4vw,4.5rem);
    /* NOT space-between. With both tracks sized, the leftover width of the wrap has to go
       somewhere, and space-between puts it in the GAP — measured, that pushed every one of
       the 263 railed pages over a 150px channel, which is the defect this rule is fixing.
       Left to itself the grid puts the leftover after the last track, so the gap stays the
       gap and the spare width sits at the page edge where it reads as margin. */
  }
}
/* THREE BLOCKS, AND THE HEAVY ONE IN THE MIDDLE NEEDS MORE ROOM BENEATH IT.
   The rail reads photograph, ask, neighbours. The ask is a solid --stage rectangle; the
   neighbours are hairline-ruled text. With one flat gap the black card sat 44px below the
   photograph and 24px above the list, so the loudest element was closest to the quietest —
   the list read as attached to the card rather than as its own block. The photograph's own
   silhouette leaves optical space at its foot, so it needs less measured gap, not more. */
.msa-rail{
  display:grid;
  /* The prose ends var(--gap-section) above the footer; the rail ended flush against it.
     Sticky clamps the rail to the article's bottom edge, so the last item in the neighbour
     list stopped 12px above the black footer while the copy beside it had 83px. Same bottom
     room as the column it sits next to. */
  padding-block-end:var(--gap-section);
  gap:1.75rem;
  align-content:start;
  font-size:.9375rem;
}
.msa-rail__cta + .msa-rail__nav{margin-block-start:1rem}
@media (min-width:1100px){
  .msa-prose{font-size:1.125rem}
  /* The rail sits beside that copy, so it moves with it. 15px against 18px in adjacent
     columns reads as a mismatch rather than a hierarchy; 16px against 18px still says
     "secondary" and keeps the enquiry card's sentence comfortable. */
  .msa-rail{font-size:1rem}

  /* THE PHOTOGRAPH RISES TO THE TITLE'S LAST LINE.
     The title is held to --measure-block so it wraps in its own column and leaves the rail's
     width clear; the rail still began where the title's box ended, so on a two-line title the
     instrument started 47px below the last word. Lifted by exactly one title line, which puts
     the top of the photograph on the top of the title's last line — the second line on every
     product page here, and the only line on a one-line title. Measured on Apollo M: line two
     starts at y=215, the rail now starts at y=217.
     The lift is safe because .msa-h1's padding-inline-end already keeps its ink out of this
     column: the title's text ends at x=768 and the rail begins at x=840. */
  .msa-rail{margin-block-start:calc(-1 * var(--h1-size) * var(--h1-leading))}

  /* Clears the STICKY masthead. This said "the header, which is not sticky on this site" and
     the header is sticky now; the rail cleared it by 2.06px, which was two unrelated numbers
     happening not to collide. Pinned to the masthead's own height plus the section break, so
     the rail stops clear of it rather than under it. */
  .msa-rail{position:sticky;top:calc(var(--msa-head-h) + var(--gap-section))}
}
/* THE PRODUCT, AT THE HEAD OF THE RAIL. Before this, a product page's photograph sat
   somewhere in the middle of the body — measured on the 508PV page, below the fold on a
   1440x950 screen — so a reader met four headings and a wall of grey before they met the
   instrument. It leads now, and it pushes the enquiry card down, which is the second thing
   it is for: the title beside it stops running into that card's top-left corner.

   NO PLATE, NO FRAME. It carried a white plate and a hairline, on the argument that a white
   photograph on a white page has no edge. The page is not white — it is --paper — and the
   image already multiplies its own white ground away into it, so the hairline was drawing a
   box around nothing and putting a second rectangle beside the instrument's red octagon.
   The silhouette is the shape. Every microscope in this range stands on a plinth and reads
   as an object without help. */
.msa-rail__shot{
  margin:0;
  padding:0;
  background:none;
  border:0;
}
.msa-rail__shot-img{
  display:block;
  inline-size:100%;
  block-size:auto;
  /* white on white: multiply leaves the plate's white untouched and darkens only the
     instrument, so no product shot carries a visible box. The same device the home page's
     instrument row uses. */
  mix-blend-mode:multiply;
}

.msa-rail__cta{
  padding:1.5rem 1.2rem;
  background:var(--stage);
  color:var(--paper);
  --ink-dim:var(--dim);   /* a dark surface names its own secondary ink — see :root */
  /* The aperture, used small, on the leading edge — the same mark the cards carry, so
     a reader meets one shape in both places the site asks them to go somewhere. */
  border-inline-start:3px solid var(--red);
}
.msa-rail__title{
  margin:0 0 .6rem;
  font-family:var(--font-display);
  font-weight:600;
  font-size:1.0625rem;
  line-height:1.3;
}
.msa-rail__text{margin:0 0 1.1rem;line-height:1.55;color:var(--dim)}
/* The button fills the card, and its own horizontal padding is trimmed to let the
   label sit on ONE line inside a rail 18rem wide. Measured: .btn ships 28.5px each
   side, which left 185px for a label that needs about 200 at the 19px/700 the
   white-on-red contrast contract requires — so it wrapped to "Request /
   information" and stood 71px tall. The padding is what gives; the type is not. */
.msa-rail__cta .btn{width:100%;justify-content:center;padding-inline:1rem;text-align:center}
.msa-rail__nav ul{list-style:none;margin:0;padding:0;display:grid;gap:.1rem}
.msa-rail__nav li{border-block-start:1px solid #E1E2E5}
.msa-rail__nav li:last-child{border-block-end:1px solid #E1E2E5}
.msa-rail__nav a{
  display:block;
  padding:.7rem .2rem;
  color:var(--stage);
  line-height:1.35;
  text-decoration-line:underline;
  text-decoration-color:transparent;
  text-decoration-thickness:2px;
  text-underline-offset:.16em;
  transition:text-decoration-color .12s ease;
}
.msa-rail__nav a:hover,
.msa-rail__nav a:focus-visible{text-decoration-color:var(--red)}
.msa-rail__nav a:focus-visible{outline:3px solid var(--stage);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.msa-rail__nav a{transition:none}}

/* The Index template's grid, used INSIDE a page body — the home page's five link sets are
   card grids (import/home-page.php). Three things the top-level shell rule does have to be
   undone here: the page gutter (the prose already has it, so it would be applied twice), the
   --wrap cap (the prose column is already narrower than that), and the square list marker,
   which `.msa-prose ul` sets at a higher specificity than `.msa-cards`'s own list-style:none
   and would therefore draw a red square beside every card. */
.msa-prose ul.msa-cards{
  list-style:none;
  max-width:none;
  padding-inline:0;
  padding-block:0;
  /* THE SAME DISTANCE UNDER A SECTION HEAD AS EVERY OTHER BODY BLOCK. This was 1.25rem —
     20px, against 64px under the paired figures, the micrograph grid, the instruments and
     the science library. It is set HERE rather than on .msa-cards because `.msa-prose
     ul.msa-cards` is an element plus two classes and outranks it; a second declaration
     lower down would have been a rule that could never win. */
  margin-block:var(--gap-head) 2.5rem;
}

/* The prose list-item rhythm is not the card grid's — the grid's own gap is. */
.msa-prose ul.msa-cards > li{margin-block:0}

/* ------------------------------------------------- standalone navigation links
   An anchor that is the only element in its own block is a navigation control, not a
   run of words in a sentence, so WCAG 2.5.8's inline exception does not cover it — and
   it measured 21-22px against a 24px minimum.

   TWO CORRECTIONS to the first version of this rule, both from measurement.

   It matched `p > a:only-child` alone, and a re-verification counted 133 standalone
   controls still under 24px in the visible DOM plus 235 more inside collapsed tab
   panels: 176 of them `li > a`, the rest in `td`, `span`, `h3`-`h5` and `strong`. The
   shape was fixed and the class was not. Every container that holds a bare link is
   listed now, including inside craic-tabs' own panels.

   And the rationale was wrong: `:only-child` ignores TEXT nodes, so it also matches
   `<p>Email: <a>x@y</a></p>`. Measured, 83 of 383 matches are genuinely inline. That
   over-applies rather than under-applies — a slightly taller line box on an inline
   link, no overflow (checked: 0 documents scroll) — so it is kept and stated honestly
   rather than replaced with something CSS cannot express.

   THE .btn EXCLUSION IS NOT OPTIONAL. `.msa-prose :is(...) > a:only-child` outranks the
   bare `.btn` class, so without it this rule REPLACES the button's `display:inline-flex`
   and its `.72em 1.5em` padding with `inline-block` and 2.4px — and a call to action
   renders 26.6px tall instead of 49.2px. Measured: 86 buttons before this rule was
   broadened, 128 after, on 122 pages that also carry a correctly-sized 49.2px button in
   the rail. Both on screen at once. The same trap as the prose link colour twenty lines
   up, and it caught this file a second time. */
/* `:not(.msa-card__link)` IS LOAD-BEARING, exactly as `:not(.btn)` is. A card link is
   `li.msa-card > a.msa-card__link` and therefore an only-child anchor inside .msa-prose, so
   this rule matched it and its `display` outranked `.msa-card__link{display:flex;
   flex-direction:column}`. The card stopped being a column and its title and blurb ran
   together on one line — "2030PV PRO(tm)UV-visible-NIR microspectrophotometer" — on every card
   of every Index page and the home page. The stylesheet already excludes .msa-card__link from
   the prose-link underline rule above, with the reason written there: a card is not prose. The
   same exclusion belongs here, and its absence is why this rule has now broken two different
   components (the primary CTA at 26.6px, and this) after being widened. */
/* AND THE THIRD COMPONENT IT BROKE, for the same reason as the first two. The kit and the
   index that craic-tabs builds on /solutions/applications/ are rows of `li > a` — every one of
   them an only-child anchor inside a craic-tabs body — so this rule matched all eleven and its
   `display:inline-block` outranked their `display:inline-flex`. Width and height do not apply
   to a non-replaced INLINE box, and the `::before` of an inline-block is inline, so the 8px red
   aperture square each chip and each index entry carries computed as 8x8 in red and painted
   nothing: measured, the chip's text began 15px from its border where the square plus its gap
   should have put it at 32px. `gap` and `align-items` went inert with it. The mark is the one
   this site uses wherever it says "this is the subject" and it was missing from both rows.
   This rule's own job — a 24px hit box on a standalone control — is not lost by excluding them:
   the chip pads to 37px on the plugin's own padding, and the index entry is given padding there
   to clear the floor, since this rule is what had been supplying it (26px, measured). */
/* AND THE PAPERS CHIPS, for the fourth time and the same reason. `.craic-papers__chip` is an
   `li > a:only-child` inside .msa-prose, so this rule's `display:inline-block` replaces its
   `inline-flex` and the count beside each field name stops being laid out as a flex item.
   Excluded here rather than out-specified in the plugin, which is where the three exclusions
   above already live. */
/* ENUMERATING THE EXCEPTIONS WAS THE BUG. This rule gives a standalone link a 24px hit box,
   and it had grown a chain of seven `:not()`s — one added each time it reached into a component
   and broke it: the primary CTA at 26.6px, the index cards' layout, the tabs kit, the tabs
   index, the papers chips. An independent audit then found three MORE it still captured
   (.msa-figlink, .msa-topic__link, and the anchors inside .msa-lib__list), which is what a
   list of exceptions does — it is always one component behind.

   MEASURED, across six pages: of 226 anchors this rule captured, 35 carry a component class of
   their own and 176 sit inside a component list; 15 are the bare prose links it exists for.
   It was reaching into components 93% of the time.

   The test is now what the rule was always trying to express: an anchor with NO class of its
   own, not inside a classed list, has nothing else styling it and needs the box. Anything a
   component owns, the component styles — and S-CONTROL-68 holds the 24px floor across all 284
   pages, so releasing them is checked rather than assumed. */
/* SELECTED BY A MARK, NOT BY :only-child — which cannot see text, and so also caught links in
   the middle of sentences (83 of 383, stated above as a compromise; after v1.46.0 removed the
   junk spans that sat beside many of them, the compromise fused "Microcolorimetry" to "is" and
   raised every line holding a link). inc/sanitise.php marks an anchor that is its parent's
   only content with data-msa-solo — the same test S-CONTROL-74 asserts — and only those get
   the box. A link inside a sentence is inline text again, as WCAG's own exception expects. */
.msa-prose :is(p,li,td,th,h3,h4,h5,h6,span,strong,em,div) > a[data-msa-solo]:not([class]):not(ul[class] a):not(ol[class] a):not(.tablinks--chips a),
.craic-tabs__body :is(p,li,td,th,h3,h4,h5,h6,span,strong) > a[data-msa-solo]:not([class]):not(ul[class] a):not(ol[class] a):not(.tablinks--chips a){
  display:inline-block;
  /* ENOUGH TO CLEAR 24px IN BOTH AXES, which .15rem did not. WCAG 2.5.8 asks for 24x24 CSS px
     and this rule only ever padded the block axis: measured by S-CONTROL-68, a standalone link
     reading "Ink" came out 22px WIDE — three letters at 15px — and a long one came out 23.97px
     tall, three hundredths under, because 2.4px either side of a 19.17px line box lands just
     short. Horizontal padding is safe here and nowhere else: this selector only matches a link
     that is its parent's ONLY child, so it is already on a line of its own and widening it
     cannot push a sentence around. */
  padding-block:.25rem;
  min-inline-size:24px;
  /* `inline-block` makes this a shrink-to-fit box, and shrink-to-fit will not go below
     MIN-CONTENT. The shell's inherited `overflow-wrap:break-word` lets a long word break
     across lines but does NOT reduce min-content — that is the documented difference
     between `break-word` and `anywhere` — so a citation whose link text is a bare URL
     sized this box to the whole URL: measured at 390px, four news pages carried a 430px
     anchor in a 310px list item and scrolled the document sideways by up to 100px.
     `anywhere` is the one value that shrinks min-content, so the box fits and the URL
     wraps inside it. It is scoped to this rule because this rule is what made these
     anchors shrink-to-fit in the first place. */
  overflow-wrap:anywhere;
}

/* THE TABS' LINK CHIPS ARE THE PLUGIN'S, NOT PROSE. Their list carries no class, so the prose
   list marker, the prose link underline and the standalone-link box above all reached into
   them: a red square hung outside some chips at the height of their last line, each label was
   underlined inside its border, and chips in one row came out different heights ("Instrument
   Relocation Services" on three lines beside one-line neighbours; the v1.46.0 visual review of
   /products/2030xl/ and /products/flex/). The plugin's own chip — a bordered block, no
   underline, an arrow — is what renders now, and every chip in a row is the row's height. */
.msa-prose .tablinks--chips .tabpanel-text li{list-style:none}
.msa-prose .tablinks--chips .tabpanel-text li::marker{content:none}
.msa-prose .tablinks--chips .tabpanel-text li > a{display:block;block-size:100%}   /* underline: excluded at the prose link rule */

/* The two 11px label styles. Both are uppercase mono at 1.32px tracking, which is
   legible at 11px in a way 11px prose would not be — but 11px is under every floor
   worth holding, and neither needs to be that small. */
.msa-foot__h,
.eyebrow{font-size:.75rem}

/* The collapsed accordion's own rhythm. `.craic-tabs__heading` carries margin:40px 0 8px
   from the plugin, which is right for a tab PANEL and wrong for a closed disclosure row:
   measured, the buttons are 53px tall and sit 93px apart, so an 11-row accordion spends
   ~440px on gaps, and the divider ends up nearer the label below it than the one above.
   Scoped to the disclosure mode the plugin sets on the container. */
@media (max-width:759px){
  .craic-tabs[data-mode] .craic-tabs__panel + .craic-tabs__panel > .craic-tabs__heading{
    margin-block-start:.5rem;
  }
}

/* --------------------------------------------------- a page shorter than the screen
   Nothing gave the document a minimum height, so on a short page the footer stopped
   partway down and left bare white beneath it — measured 282px at 1920x1080 on
   /contact-us/. The footer is the page's closing band; it should reach the bottom. */
html{min-height:100%}
body{min-height:100vh;display:flex;flex-direction:column}
body > main{flex:1 0 auto}
body > .msa-foot{flex-shrink:0}

/* ------------------------------------------------------- the news lead banner
   template-news.php renders the article's featured image above its heading, which
   is where the live site puts it. These are 1000x250 banners and the column is
   904px, so the width is the column's and the height follows the ratio — never the
   file's own numbers, which would let a wide image set the column. */
.msa-news__banner{
  max-width:var(--wrap);
  margin:0 auto;
  padding-inline:var(--gutter);
  padding-block-start:var(--gap-section);
}
.msa-news__banner img{
  display:block;
  inline-size:100%;
  block-size:auto;
  aspect-ratio:4 / 1;
  object-fit:cover;
}
/* The heading follows the banner, so it must not add the section break a second time. */
.msa-news__banner + .msa-h1{padding-block-start:1.5rem}

/* ------------------------------------------------ a standalone image in a wide column
   121 images render far narrower than the column they sit in — a 166px micrograph in a
   1264px column is 13% of it, with 1,098px of white beside it, and it reads as dropped
   onto the page rather than placed on it. This library is 2008 web JPEGs, 209-300px
   wide, so making them fill the column would upscale them into mush; centring is the
   treatment that respects the pixels they actually have.

   Scoped to an image that is ALONE in its block — a figure, or a paragraph whose only
   child is an image. An image inside a run of prose is an illustration of that prose and
   keeps its place in the flow. `.cols` is excluded: a column is already a placement
   decision, and its figures are meant to sit at its leading edge. */
.msa-prose > figure:not(:has(figcaption)),
.msa-prose > p:has(> img:only-child),
/* `div.craic-tabs__body` is NOT in these two, for the reason `.cols` is not: a tab panel is
   already a placement decision. Its copy is capped at the reading measure and starts at the
   panel's padding, so centring a 576px photograph in a 1198px panel put its middle at x=720
   while the paragraph above it ran 121→869 — consistent with the other panels and inconsistent
   with the words beside it. It stays in the measure lines above, which are about width. */
.msa-prose :is(div:not([class]), div.row, div.page-content, div.ft-text, div.tpzone, div.prodtabs, div.scipg2, td, td.copy) figure:not(:has(figcaption)):not(.tabpanel-media),
.msa-prose :is(div:not([class]), div.row, div.page-content, div.ft-text, div.tpzone, div.prodtabs, div.scipg2, td, td.copy) p:has(> img:only-child){
  width:fit-content;
  max-width:100%;
  margin-inline:auto;
}
/* Captioned figures keep their caption tied to the picture's own width rather than to the
   column's, so a 30-word caption under a 209px image does not run four times its width.
   A FIGURE THAT CARRIES A CLASS PLACES ITSELF, which is the same judgement `ul[class] figure`
   already makes below and for the same reason: a classed figure is a component, not a picture
   dropped into prose. Measured before this exclusion: .msa-coverage — a <figure> because it is
   a table with a caption, not because it is a picture — came out 1043px wide and centred, 111px
   inside every other block on the page, on the one element the applications page is built
   around. Of the 36 captioned figures in this site's stored content, 0 carry a class; the four
   classed ones are all theme components. So the exclusion costs nothing and covers the next
   component built here without a fourth visit. */
.msa-prose > figure:has(figcaption):not([class]){width:fit-content;max-width:100%;margin-inline:auto}
.msa-prose figure:has(figcaption):not([class]) figcaption{max-width:34rem}

/* ==================================================== the real logo and the utility bar
   THE CLIENT'S LOGO. `.msa-brand__mark` was a CSS square standing in for a logo nobody had
   imported: extract.py takes each page's itemprop="articleBody" region and the logo lives in
   the header, outside it, so /images/logo.png — on 462 captured pages, the most repeated asset
   on the site — was never in the inventory, never fetched and never in the library. The theme
   drew its own mark and 50 green gates said nothing, because no gate asks whether the client's
   logo is present. The file now ships with the theme rather than as an attachment: it is brand
   chrome, not content, and a theme asset cannot go missing from a database restore. */
/* WordPress's own wrapper around the logo, which nothing in this stylesheet had ever named. It
   is an <a> outside .msa-prose, so the rule that gives a link its colour never reached it and it
   computed rgb(0,0,238) on every page of the site — invisible, because its only child is an
   image, and a browser-default link all the same. Found by the same sweep that found it on the
   coverage cells and the fleet plates; this one had been there since the logo shipped. */
.custom-logo-link{ color:var(--white); text-decoration:none; }
.msa-foot .custom-logo-link{ color:var(--paper); }

.msa-brand__logo{
  display:block;
  /* Height-locked, width auto: the mark is 754x166 and must not be scaled by its width into a
     different height on a narrow header. */
  block-size:1.75rem;
  inline-size:auto;
  max-inline-size:100%;
}
@media (max-width:759px){ .msa-brand__logo{ block-size:1.5rem; } }
.msa-foot .msa-brand__logo{ block-size:1.5rem; }

/* The utility bar above the masthead: the client's trademark line, their certification and the
   toll-free number. Mono at 11px is the same label treatment the hero captions use — small, but
   these are reference facts a reader scans for, not prose. */
.msa-util{
  /* ITS OWN BACKGROUND. This lived inside <header class="msa-head msa-stage"> and was legible
     only because that ancestor painted --stage behind it. Moving it out for the sticky masthead
     took the paint away and left white text on white: the toll-free number measured 1.00:1 and
     the band read as empty on all 284 pages. An element that carries text carries its own
     ground; inheriting one from a parent is a dependency nobody records. */
  background:var(--stage);
  color:var(--dim);
  border-block-end:1px solid rgba(var(--paper-rgb),.10);
  font-family:var(--font-mono);
  font-size:.6875rem;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--dim);
}
.msa-util__inner{
  /* --wrap, NOT --shell. `--shell` is defined nowhere in this stylesheet, so this line was
     invalid and the utility bar ignored the page shell entirely: it ran the full viewport with
     only its own gutter, putting the tagline at x=48 and the phone at x=1392 while the logo,
     the hero and every section sat at 88 and 1352. One undefined token, and the top line of the
     page lined up with nothing beneath it. */
  max-inline-size:var(--wrap);
  margin-inline:auto;
  padding:.5rem var(--gutter);
  display:flex;
  flex-wrap:wrap;
  gap:.35rem 1.5rem;
  align-items:center;
  justify-content:space-between;
}
.msa-util__right{ display:flex; flex-wrap:wrap; gap:.35rem 1.5rem; align-items:center; }
/* THE SAME 24px FLOOR, at every width and not only the narrow one. The rule inside the
   max-width block below gives this link a 24px box on a handset and nothing gave it one on a
   desktop: measured on the deployed site, the toll-free number renders 14px tall at 768px and
   at 1440px. It is the site's most-dialled control and it was the smallest target on the page. */
.msa-util__tel a{ display:inline-block; padding-block:.3125rem; color:var(--white); text-decoration:none; font-weight:600; }
.msa-util__tel a:hover{ text-decoration:underline; }
/* WHAT SURVIVES THE NARROW ROW IS THE ACTIONABLE THING, NOT THE MARKETING ONE.
   The rule here used to hide .msa-util__right entirely below 559px, on the reasoning that
   three uppercase mono runs stacked is a wall rather than a bar. The reasoning was right; the
   choice of what to drop was backwards. It took the toll-free number — the header's only
   tel: link, and a tap-to-call — off every handset, on the one class of device where tapping
   a number IS the action, and left the tagline, which sells nothing at 12px. A phone user
   then had to scroll 14,451px to the footer to find it.
   So the row keeps ONE run on a handset and it is the number. The tagline and the
   certification both remain in the footer. */
@media (max-width:559px){
  .msa-util__tagline,
  .msa-util__cert{ display:none; }
}
/* Between a handset and a laptop all three runs do not fit on one line, and wrapping them
   destroys the left/right split the bar is built on — measured at 768px: two rows, 51px
   instead of 31px, both sides left-aligned. The certification is the one of the three with
   no action attached, so it is the one that goes. */
@media (max-width:899px){
  .msa-util__cert{ display:none; }
}
/* On a handset the number is the row's purpose, so it gets a real target rather than a 14px
   line of inline text. WCAG 2.5.8 exempts a link inline in a sentence, which this is — the
   exemption is not the point; being tappable at arm's length is. */
@media (max-width:559px){
  .msa-util__tel a{ display:inline-block; padding-block:.4rem; }
}

/* ==================================================== the hero lede and its two actions */
.msa-hero__lede{
  margin-block:1.25rem 0;
  max-inline-size:34rem;
  font-size:1.0625rem;
  line-height:1.55;
  color:rgba(var(--paper-rgb),.82);
}
.msa-hero__actions{
  margin-block:1.75rem 0;
  display:flex;
  flex-wrap:wrap;
  gap:.75rem;
}
/* The hero sits on --stage, so the ghost button's default ink would be the body colour on a
   dark ground. White border and white label, filling to the stage colour on hover. */
.msa-hero__actions .btn--ghost{
  border-color:rgba(var(--paper-rgb),.55);
  color:var(--white);
}
.msa-hero__actions .btn--ghost:hover{
  background:var(--white);
  color:var(--stage);
  border-color:var(--white);
}

/* ============================================ the section pattern, PORTED from the mockup
   Every value here is read out of the approved mockup's own stylesheet rather than re-derived:
   .sec-head.split{grid-template-columns:1.3fr 1fr;gap:80px;align-items:end}, h2{font-size:42px;
   line-height:1.1}, .sec-head h2{max-width:20ch}, .sec-head .stand{font-size:18px;
   line-height:1.6;color:var(--dim)}, .eyebrow{display:flex;gap:11px;color:var(--red)},
   .ap{width:8px;height:8px;background:var(--red)}, .disc .c .n{font-size:14.5px;
   margin-top:14px}, --gap:120px.

   Re-implementing a design from a written description is what lets a page drift: the palette
   can match exactly while every metric around it moves, because nothing compares the two.
   These are the approved design's own numbers, ported rather than re-derived. */
.msa-sec{ --sec-gap:120px; }
.msa-sec + .msa-sec{ margin-block-start:var(--sec-gap); }

.msa-sechead{
  display:grid;
  /* THE SAME TRACKS THE CONTENT BELOW IT USES. At 1.3fr/1fr with an 80px gutter the
     standfirst began at x=837 on a 1440 screen, and nothing else on the page begins there:
     the paired figures are two 619px columns starting at 88 and 733, and the micrograph and
     instrument grids are four columns starting at 88, 410, 733 and 1055. The one paragraph
     introducing all of them was the only block on the page aligned to nothing.

     Two equal tracks with the content grid's own 26px gutter put it at 733 — the second
     figure's left edge, and the third grid column's. It also gives the paragraph a 619px
     measure instead of 515, which is what stops the longest of them towering over the
     heading beside it: the instruments standfirst set 7.5 lines deep against a 2-line
     heading, and the section below it started 88px lower than the heading it belongs to.

     The heading does not crowd the gutter, because it is capped at 20ch (~420px) by the rule
     below and never reaches the track's edge. */
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:26px;
  /* The mockup baseline-aligns this pair (`align-items:end`) because its standfirst runs to
     two or three lines. The client's own section copy runs to six, so `end` aligned a short
     heading to the bottom of a tall paragraph and left a hole above the heading. Same
     intention, honest about the content it actually has. */
  align-items:start;
}
/* NOT `> h2`. The markup wraps the eyebrow and the heading in a div, so a direct-child
   selector matched nothing and every section heading on the site fell back to the base h2
   size — 26.32px where the approved design sets 42px. That one combinator is why the page
   read as an undifferentiated column of text: nothing below the hero was ever heading-sized.
   Measured before: 4 of 4 section headings at 26.32px, directChild=false. */
.msa-sechead h2{
  margin:22px 0 0;
  max-inline-size:20ch;
  font-size:clamp(1.875rem,1.1rem + 1.6vw,2.625rem);   /* 30 -> 42px */
  line-height:1.08;
  letter-spacing:-.015em;
  text-wrap:balance;
}
.msa-sechead__stand{
  margin:0;
  /* ALIGNED TO THE HEADING'S FIRST LINE. The left column is eyebrow + heading and the right is
     the standfirst alone, so `align-items:start` levelled the standfirst with the EYEBROW and
     it floated a line and a half above the heading it belongs to. Offset by exactly the
     eyebrow's own height plus the heading's top margin. */
  padding-block-start:calc(.75rem * 1.5 + 22px);
  padding-block-end:5px;
  font-size:1.125rem;      /* 18px */
  line-height:1.6;
  color:var(--ink-dim);
  text-wrap:pretty;
}
.msa-eyebrow-ap{
  display:flex;
  align-items:center;
  gap:11px;
  margin:0;
  font-family:var(--font-mono);
  font-size:.6875rem;
  letter-spacing:.1em;
  text-transform:uppercase;
  /* THE MARK IS RED; THE LABEL IS NOT. --red on white at 12px measures 4.16:1, under the
     4.5:1 AA floor, and this rule put the brand red on all nine home-page section eyebrows.
     The red cannot move — #EE2E24 is the logo exactly, and this rebuild exists partly to
     end the four competing reds the live site had, so a darker "text red" would be a fifth.
     The colour is therefore kept where it carries meaning (the aperture square below, and
     the card edges) and dropped where it was only carrying letters. --ink-dim measures
     6.78:1 on white and 5.83:1 on the stage, so the label is legible on either surface. */
  color:var(--ink-dim);
}
/* The aperture, used at label size — the same mark the hero and the card edges carry.
   This stays --red: it is the motif, not text, and a solid 8px square has no contrast
   requirement to meet as type. */
.msa-ap{
  inline-size:8px; block-size:8px;
  flex:0 0 8px;
  background:var(--red);
  display:inline-block;
}
/* ON THE LINE'S CENTRE, NOT ITS TOP. In a caption the mark sits in a flex row set to
   `align-items:flex-start`, which puts an 8px square at the top of an 18px line box — 5px
   above the centre of the letters beside it, which at this size is a visible step. The
   eyebrows never had this: .msa-eyebrow-ap centres its row. The row cannot simply be centred
   here, because a caption wraps to two lines on a narrow screen and centring would then float
   the mark between them; the margin centres it on the FIRST line and leaves it there.
   1lh resolves against the caption's own line-height, so the mark follows the type rather
   than a number copied out of it. */
.msa-meas figcaption .msa-ap{ margin-block-start:5px; margin-block-start:calc((1lh - 8px) / 2); }
/* Below the split's breakpoint the mockup stacks it and drops the baseline alignment. */
/* THE HEAD GAP TIGHTENS ON A HANDSET, ONCE. Two grids restated it as 40px in their own
   breakpoints and two other blocks did not, so at 390 the same relationship measured 40px
   under three section heads and 64px under the other two. The value is responsive; the
   components are not. */
@media (max-width:559px){ :root{ --gap-head:2.5rem; } }

@media (max-width:899px){
  .msa-sec{ --sec-gap:72px; }
  .msa-sechead{ grid-template-columns:1fr; gap:20px; align-items:start; }
  .msa-sechead__stand{ padding-block-start:0; padding-block-end:0; }
  .msa-sechead h2{ font-size:2rem; }
}

/* The discovery grid: cells that share hairline edges rather than floating cards, each opening
   with the aperture mark, then the name, then what it measures. */
.msa-disc{
  display:grid;
  grid-template-columns:repeat(5,minmax(0,1fr));
  margin-block-start:70px;
  border-block-start:1px solid var(--line-ink);
  border-inline-start:1px solid var(--line-ink);
  list-style:none;
  padding:0;
}
.msa-disc > li{ margin:0; }
.msa-disc__cell{
  display:flex;
  flex-direction:column;
  block-size:100%;
  padding:26px 24px 30px;
  border-block-end:1px solid var(--line-ink);
  border-inline-end:1px solid var(--line-ink);
  color:inherit;
  text-decoration:none;
}
.msa-disc__cell h3{
  margin:16px 0 0;
  flex:1;
  font-size:1.0625rem;
  line-height:1.25;
}
.msa-disc__note{
  margin-block-start:14px;
  font-size:.90625rem;     /* 14.5px */
  color:var(--ink-dim);
}
a.msa-disc__cell:hover h3{ text-decoration:underline; text-decoration-color:var(--red);
  text-decoration-thickness:2px; text-underline-offset:.16em; }
@media (max-width:1099px){ .msa-disc{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width:559px){ .msa-disc{ grid-template-columns:1fr; } }

/* --- the discovery grid, corrected -------------------------------------------------------
   THREE DEFECTS, all mine, all found by looking at the rendered page rather than at a
   measurement that agreed with me:
   1. `repeat(5,...)` was copied from the mockup's ten-cell section and left an empty fifth
      column on a four-cell grid. The column count now comes from the cell count.
   2. The aperture mark was a <span> beside an <h3> inside the anchor. wpautop saw an inline
      next to a block, inserted <br /> and an unclosed <p>, and the browser re-parented the
      descriptor out of the cell — so every descriptor was in the DOM and none was in a card.
      The mark is a ::before now and the cell holds block children only.
   3. 252px columns broke the titles mid-word. Four columns give 316px, and these titles are
      short enough that breaking inside a word is never the right answer here. */
.msa-disc{ grid-template-columns:repeat(4,minmax(0,1fr)); }
.msa-disc[data-cells="3"]{ grid-template-columns:repeat(3,minmax(0,1fr)); }
.msa-disc[data-cells="5"],
.msa-disc[data-cells="10"]{ grid-template-columns:repeat(5,minmax(0,1fr)); }
.msa-disc__cell::before{
  content:"";
  inline-size:8px; block-size:8px;
  background:var(--red);
  display:block;
}
/* The prose list bullet is the aperture too, and inside this grid it landed a second red
   square opposite the cell's own. One mark per cell. */
.msa-prose .msa-disc > li::before,
.msa-prose .msa-disc > li::marker{ content:none; }
.msa-prose .msa-disc > li{ padding-inline-start:0; }
.msa-disc__cell h3{ overflow-wrap:normal; hyphens:none; }
@media (max-width:1099px){
  .msa-disc,
  .msa-disc[data-cells="3"],
  .msa-disc[data-cells="5"],
  .msa-disc[data-cells="10"]{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
@media (max-width:559px){
  .msa-disc,
  .msa-disc[data-cells="3"],
  .msa-disc[data-cells="5"],
  .msa-disc[data-cells="10"]{ grid-template-columns:1fr; }
}

/* INVERTED, as the note here promised it would be. This rule had grown a not-list one entry at
   a time — .btn, then .msa-card__link, then .msa-disc__cell and .msa-disc__link, then figure
   links, and the science library would have made it six. A list that grows every time a
   component is added is a rule pointed the wrong way.
   The property that actually separates them is structural: a link inside a CLASSED list is a
   component, and a link inside a bare <ul> or a paragraph is prose. `:not(ul[class] a)` says
   exactly that in one token and covers every component built after it, so nothing has to
   remember to come back here. */
.msa-disc__cell,
.msa-disc__cell h3,
.msa-disc__note{ text-decoration:none; }
.msa-disc__cell:hover h3{
  text-decoration:underline; text-decoration-color:var(--red);
  text-decoration-thickness:2px; text-underline-offset:.16em;
}

/* --- the cell is the <li>, not an anchor wrapping blocks --------------------------------
   wpautop inserts <br /> and an unclosed <p> when it finds block elements inside an inline
   <a>, and that stray <p> inherited the cell's border and ::before — four empty phantom cells
   under the grid. Blocks are direct children of the list item now; the link holds text and is
   stretched over the cell so the whole cell is still the target. */
li.msa-disc__cell{
  position:relative;
  display:flex;
  flex-direction:column;
  padding:26px 24px 30px;
  border-block-end:1px solid var(--line-ink);
  border-inline-end:1px solid var(--line-ink);
}
li.msa-disc__cell h3{ margin:16px 0 0; flex:0 0 auto; font-size:1.0625rem; line-height:1.25;
  overflow-wrap:normal; hyphens:none; }
li.msa-disc__cell .msa-disc__note{ margin-block-start:10px; }
.msa-disc__link{ color:inherit; text-decoration:none; }
.msa-disc__link::after{ content:""; position:absolute; inset:0; }
li.msa-disc__cell:hover .msa-disc__link{
  text-decoration:underline; text-decoration-color:var(--red);
  text-decoration-thickness:2px; text-underline-offset:.16em;
}

/* --- specificity, stated once ------------------------------------------------------------
   Three rules I added earlier outrank the ones above because they carry the .msa-prose
   ancestor: the prose bullet suppression (0,2,2) beat my marker (0,1,1) so no cell had one,
   and the prose list padding reset beat the cell's own padding so every cell started at x=0.
   Re-asserted at the same depth rather than by piling !important on top. */
.msa-prose .msa-disc > li.msa-disc__cell{
  padding:26px 24px 30px;
}
.msa-prose .msa-disc > li.msa-disc__cell::before{
  content:"";
  display:block;
  inline-size:8px;
  block-size:8px;
  background:var(--red);
}

/* Every figure caption on the site centres under its image, for the same reason as the hero's.
   `.msa-prose > figure:has(figcaption)` is already width:fit-content and margin-inline:auto, so
   centring the text centres it on the picture rather than on the column. */
.msa-prose figcaption{ text-align:center; }
.msa-hero__caption{ text-align:center; }

/* A CAPTIONED FIGURE HUGS ITS IMAGE, wherever it sits. `.msa-prose > figure:has(figcaption)`
   matched DIRECT children only, and the imported bodies nest most figures inside an unclassed
   <div>. Measured: a 263px image in a 680px figure with a 544px caption put the caption 141px
   off the picture's centre on /support/learn/protein-absorption/, and seven more like it. The
   figure now shrinks to its content at any depth, so it is as wide as the wider of the image
   and the caption and both centre on the same axis. The caption keeps its 34rem reading cap;
   where the caption is the wider of the two it sets the figure's width and the image centres
   inside it. */
/* NOT inside a figure grid — and stated structurally, because naming one grid class was not
   enough. Shrinking a captioned figure to its content is right for a figure sitting in a column
   of prose (it is what centres the caption on the picture) and wrong for a figure that IS a grid
   cell, where the plate must fill its track or the row goes ragged. Measured: an eight-cell
   instrument row rendered plates of 293, 270, 293, 288 / 278, 218, 293 and 192px, and after
   excluding only .msa-figgrid the two paired figures came out 393 and 471. `ul[class] figure`
   is the same rule the prose-link exclusion uses — a figure inside a CLASSED list is a
   component — so the next grid built here is covered without a third visit. */
.msa-prose figure:has(figcaption):not(ul[class] figure):not([class]){
  inline-size:fit-content;
  max-inline-size:100%;
  margin-inline:auto;
}
.msa-prose figure:has(figcaption):not([class]) figcaption{
  max-inline-size:34rem;
  margin-inline:auto;
}
.msa-prose figure:has(figcaption):not([class]) img{ margin-inline:auto; display:block; }
/* EXCEPT IN THE PAIRED FIGURES, where the picture is left-aligned in its column and the
   caption must be too. The generic rule above is `.msa-prose figure:has(figcaption)
   figcaption` — a class, a pseudo-class and an element — so it outranks `.msa-meas
   figcaption` and kept the caption 544px wide and auto-centred however the caption rule was
   written. Measured: plate 88..528, caption 125..669. Matched here at a specificity that can
   actually win, with the plate's own width expression so the two cannot drift apart. */
.msa-prose .msa-meas figure:has(figcaption) figcaption{
  max-inline-size:min(100%, 440px);
  margin-inline:0;
}

/* get_custom_logo() emits `.custom-logo-link > img.custom-logo`. Give it the same geometry as
   the shipped fallback so the header does not shift when an editor sets a logo in wp-admin. */
.custom-logo-link{ display:inline-flex; align-items:center; }
.custom-logo{ display:block; block-size:1.75rem; inline-size:auto; max-inline-size:100%; }
@media (max-width:759px){ .custom-logo{ block-size:1.5rem; } }

/* ================================================ the image-led sections, PORTED from the mockup
   Values read out of the approved mockup's stylesheet, not re-derived:
     .gallery .grid{grid-template-columns:repeat(4,1fr);gap:26px;margin-top:70px}
     .gallery .plate{background:#000;height:224px;display:flex;align-items:center;
                     justify-content:center;overflow:hidden}
     .gallery .plate img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain}
     .gallery figcaption{margin-top:14px}
     .gallery .s{font-family:Lexend;font-size:16px;font-weight:500;letter-spacing:-.02em}
     .gallery .d{margin-top:5px;color:var(--dim)}
     .inst .grid{gap:30px}  .inst .plate{height:240px}  .plate.lit{background:var(--white)}
     .inst .nm{font-size:18px;font-weight:500;letter-spacing:-.025em}

   THE PLATE IS THE POINT. Every image is a measurement of a real sample at a real wavelength,
   and they arrive at wildly different sizes and aspect ratios. A fixed-height plate with
   `object-fit:contain` gives the grid one rhythm without cropping anybody's micrograph — a
   crop would cut the aperture out of the frame, which is the one thing every one of these
   pictures is about. Letterboxing on black is what the mockup does and what a microscopist
   expects; the alternative, `cover`, silently throws away part of the measurement.

   DEVIATION, stated: the mockup runs these bands full-bleed on --stage. Here the plates carry
   the dark and the band does not, because a 100vw break-out inside this shell risks the
   horizontal overflow S-STRUCT-07 fails a page for, and the texture comes from the plates. */
.msa-figgrid{
  display:grid;
  grid-template-columns:repeat(4,minmax(0,1fr));
  /* ROWS APART, COLUMNS ALIGNED. The column gutter is 26px because every grid on this page
     shares one set of column edges. The ROW gap is a different question and was answered by
     accident: a cell is a picture and the two lines that name it, and the eye groups by
     proximity, so the space between two cells has to beat the space inside one. Measured
     before: 14px from a plate to its own caption against 26px to the next row — 1.9:1, and
     on the instruments grid, where the caption carries a rule and sits 27px down, 26px
     between rows made it 1.0:1 at desktop and 0.7:1 on a handset, where the row gap dropped
     to 20px. Below 1 the caption is closer to the NEXT product than to its own. */
  gap:36px 26px;
  margin-block-start:var(--gap-head);
  padding:0;
  list-style:none;
}
.msa-figgrid > li{ margin:0; }
.msa-figgrid figure{ margin:0; }
.msa-plate{
  background:#000;
  block-size:224px;
  display:flex;
  align-items:center;
  justify-content:center;
  overflow:hidden;
}
/* FOUR BY THREE, WHICH IS WHAT A MICROGRAPH IS. A fixed 224px height gives the plate whatever
   aspect the column happens to have, and `contain` then letterboxes a 4:3 picture inside it:
   measured at 390px, a 350x200 plate painted a 268x200 image and left 82px of black — 23% of
   the cell — and at 768 it left 42px. Every camera on these instruments returns 4:3, so the
   plate takes that ratio and each cell is filled exactly at every width. Height is derived
   rather than declared, so no breakpoint has to restate it. */
.msa-figgrid:not(.msa-figgrid--inst) .msa-plate{ block-size:auto; aspect-ratio:4/3; }
.msa-plate img{
  max-inline-size:100%;
  max-block-size:100%;
  inline-size:auto;
  block-size:auto;
  object-fit:contain;
  margin:0;
}
.msa-figgrid figcaption{ margin-block-start:14px; text-align:start; }
.msa-figname{
  display:block;
  font-family:var(--font-display);
  font-size:1rem;
  font-weight:500;
  letter-spacing:-.02em;
  color:var(--ink,var(--stage));
}
.msa-figfield{
  display:block;
  margin-block-start:5px;
  font-family:var(--font-mono);
  font-size:.75rem;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--ink-dim);
}
/* The instruments sit on a white plate, which is the plane change the approved design makes
   between the specimens and the hardware. A hairline keeps a white-background product
   photograph from floating on a white page. */
/* 26px BETWEEN COLUMNS — the gutter every other grid on this page uses, so the instrument
   plates sit on the same column edges as the micrographs above them and as the standfirst
   above those. (It was 30px, which put the third plate at x=735 against everything else's
   733: two pixels of nothing, in exchange for a column edge that did not line up.)

   60px BETWEEN ROWS, AND THAT IS THE POINT. A product cell is a picture and the two lines
   that name it, and the eye groups by proximity: measured before, a caption sat 31px below
   its own plate (16px margin, a 1px rule, 14px padding) while the next row's plate began
   26px below that caption. Five pixels apart — so a name was as close to the instrument
   BELOW it as to the one it belongs to, and eight products read as a wall of parts. The
   caption also moves 4px closer to its own picture. 27px inside a group against 60px
   between them is the ratio that makes the grouping automatic. */
.msa-figgrid--inst{ gap:60px 26px; }
/* 392px, not 240px. The instruments are portrait objects — the client's own product
   photography runs from 0.37 to 0.99 wide-over-tall — and `object-fit:contain` in a plate
   wider than it is tall makes every one of them HEIGHT-limited. Measured on the 240px plate:
   the eight photographs rendered between 103px and 196px wide inside 293px plates, so the
   subject occupied a third of the cell it was given and no two cells agreed. The plate now
   matches the aspect the photographs are supplied at (see the note on the canvas below), and
   each instrument fills its cell edge to edge. */
.msa-figgrid--inst .msa-plate{
  block-size:392px;
  padding:20px;
  background:var(--white);
  border:1px solid var(--line-ink);
}
/* The product shots are composited on white. `multiply` leaves white untouched and darkens
   everything else, so the photograph's own white and the plate's white are the same white at
   any zoom, in any colour profile — no seam, no halo along an antialiased edge. It is also
   what the approved mockup does with these same pictures. */
.msa-figgrid--inst .msa-plate img{ mix-blend-mode:multiply; }
/* A rule under the picture, not around it: it gives the row a common baseline and states
   where one instrument ends and the next begins without drawing a box around each. */
.msa-figgrid--inst figcaption{
  margin-block-start:12px;
  border-block-start:1px solid var(--line-ink);
  padding-block-start:14px;
}
.msa-figgrid--inst .msa-figname{ font-size:1.125rem; letter-spacing:-.025em; }
.msa-figgrid a{ color:inherit; text-decoration:none; display:block; }
.msa-figgrid a:hover .msa-figname{
  text-decoration:underline; text-decoration-color:var(--red);
  text-decoration-thickness:2px; text-underline-offset:.16em;
}
/* The prose list bullet has no business inside a figure grid. */
.msa-prose .msa-figgrid > li::before,
.msa-prose .msa-figgrid > li::marker{ content:none; }
.msa-prose .msa-figgrid > li{ padding-inline-start:0; }
@media (max-width:1099px){ .msa-figgrid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width:559px){
  /* One column, so only the ROW gap is doing anything — and it has to stay bigger than
     the distance from a plate to its own caption, which does not shrink here. */
  .msa-figgrid{ grid-template-columns:1fr; gap:36px; }
  .msa-figgrid--inst{ gap:60px; }
  .msa-plate{ block-size:200px; }   /* still the floor for any plate outside the two grids */
  /* One column, so the plate is the full page width and a 200px one would show a 144px
     instrument in a 342px cell. Two classes and later in the file, so it wins on both counts
     over the .msa-plate rule immediately above. */
  .msa-figgrid--inst .msa-plate{ block-size:400px; }
}

/* A link around a figure carries no underline; the figure is the affordance and the name
   underlines on hover. Structural, so any future figure-wrapping link is covered without
   adding a class to the prose-link exclusion list for a fifth time. */
.msa-figgrid a, .msa-figgrid a .msa-figname, .msa-figgrid a .msa-figfield{ text-decoration:none; }

.msa-figgrid figure{ inline-size:100%; margin-inline:0; }

/* ============================================== the science library, PORTED from the mockup
     .lib .cols{grid-template-columns:1fr 1fr;gap:0 80px;margin-top:64px}
     .lib .grp+.grp{border-left:1px solid var(--line-ink);padding-left:80px}
     .lib ul{margin:22px 0 0}  .lib li{border-top:1px solid var(--line-ink)}
     .lib li:last-child{border-bottom:1px solid var(--line-ink)}
     .lib li a{display:flex;align-items:baseline;gap:16px;padding:17px 0}
     .lib .t{font-size:17.5px;font-weight:500;letter-spacing:-.022em;flex:1}

   This is the section the client's own material earns. The educational library exists in this
   form on almost no other site; the approved design gives it a place on the home page and
   keeps every article at the address it already has. The rule between the
   two columns is the only divider on the page that carries meaning: these are two different
   branches of the library, not one list in two columns. */
.msa-lib__cols{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:0 80px;
  margin-block-start:var(--gap-head);
}
.msa-lib__grp + .msa-lib__grp{
  border-inline-start:1px solid var(--line-ink);
  padding-inline-start:80px;
}
.msa-lib__label{
  display:flex;
  align-items:center;
  gap:10px;
  margin:0;
  font-family:var(--font-mono);
  font-size:.6875rem;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--ink-dim);
}
.msa-prose .msa-lib__list{ list-style:none; margin:22px 0 0; padding:0; }
.msa-prose .msa-lib__list > li{
  margin:0;
  padding-inline-start:0;
  border-block-start:1px solid var(--line-ink);
}
.msa-prose .msa-lib__list > li::before,
.msa-prose .msa-lib__list > li::marker{ content:none; }
.msa-prose .msa-lib__list > li:last-child{
  border-block-end:1px solid var(--line-ink);
}
.msa-lib__list a{
  display:flex;
  align-items:baseline;
  gap:16px;
  padding-block:17px;
  color:inherit;
  text-decoration:none;
  font-family:var(--font-display);
  font-size:1.09375rem;      /* 17.5px */
  font-weight:500;
  letter-spacing:-.022em;
}
.msa-lib__list a:hover{
  text-decoration:underline; text-decoration-color:var(--red);
  text-decoration-thickness:2px; text-underline-offset:.16em;
}
@media (max-width:899px){
  /* One column below the split, and the SAME head gap as every other section — this
     restated 40px where the token is 64, so the library alone tightened on a handset. */
  .msa-lib__cols{ grid-template-columns:1fr; gap:44px; }
  .msa-lib__grp + .msa-lib__grp{ border-inline-start:0; padding-inline-start:0; }
}

/* ===================================================== the closing band, PORTED from the mockup
     .cta-band{background:var(--red);color:var(--white);padding:96px 0}
     .cta-band .wrap{grid-template-columns:1fr auto;gap:80px;align-items:center}
     .cta-band h2{font-size:46px;line-height:1.08;max-width:22ch}
     .cta-band .acts{gap:14px;justify-content:flex-end}
     .btn-white{background:var(--white);color:var(--red);border-color:var(--white)}

   The one place the brand red is used as a FIELD rather than as an accent, and the last thing
   on the page: the site's whole argument is "tell us what you need to measure", and this is
   where it asks. Full-bleed, because a red band that stops at the gutter reads as a box. */
.msa-ctaband{
  background:var(--red);
  color:var(--white);
  padding-block:96px;
  /* NO VIEWPORT BREAK-OUT. This was `inline-size:100vw` with a negative start margin, which is
     the standard full-bleed trick and is wrong here: 100vw is the viewport INCLUDING the
     scrollbar while the content area is clientWidth, so on a browser with classic scrollbars
     the band sat ~15px wider than the page and produced a horizontal scrollbar. Headless
     Chromium uses overlay scrollbars, where the two are equal, so every local measurement
     reported zero overflow while the deployed site showed the bar. The band is rendered
     outside .msa-prose now (template-feature.php), where full width is simply what a block
     does, and nothing has to be tricked. */

  /* PADDING, NEVER MARGIN, on an outermost block — the rule this stylesheet already states for
     .msa-foot and which I broke here. `margin-block-start:120px` gave the section above the
     band a 0px gap on top and a 120px gap below, and S-BALANCE-08 fails a page for exactly that
     asymmetry. The band's own 96px padding is the mockup's value and the separation is carried
     by the colour change, which is what a full-bleed band is for. */
}
.msa-ctaband__inner{
  max-inline-size:var(--wrap);
  margin-inline:auto;
  padding-inline:var(--gutter);
  display:grid;
  grid-template-columns:1fr auto;
  gap:80px;
  align-items:center;
}
.msa-ctaband h2{
  margin:0;
  font-size:2.875rem;        /* 46px */
  line-height:1.08;
  max-inline-size:22ch;
  color:var(--white);
  text-wrap:balance;
}
.msa-ctaband p{
  margin-block:18px 0;
  max-inline-size:40ch;
  /* ON THE RED, THE FLOOR IS THE FLOOR. S-ONRED-10 failed this at 19px/400 in 92% white: on
     --red, text must be PURE white and either >=24px at any weight or >=18.66px at weight
     >=700. 92% white is not white — it is a tint of red showing through, and the gate measures
     the actual composite. 24px at 400 clears it without turning a paragraph bold, and a
     closing band is the one place on the site where copy this size reads as confidence rather
     than shouting. The measure drops to 40ch so the line length stays right at the new size. */
  font-size:1.5rem;         /* 24px — the large-text threshold, any weight */
  line-height:1.45;
  color:var(--white);
}
/* SIDE BY SIDE at desktop. `flex-wrap:wrap` in an `auto` grid track let the track size to one
   button and stacked the pair, which reads as two separate offers rather than one choice. The
   track is given the room the two buttons need; below the split they stack deliberately. */
.msa-ctaband__acts{ display:flex; gap:14px; justify-content:flex-end; flex-wrap:wrap; }
/* nowrap ONLY where there is room for it. Applied unconditionally it forced two 250px buttons
   to sit side by side inside a 390px viewport and pushed the document 197px sideways — the
   exact failure S-STRUCT-07 exists to catch, introduced by a fix for a cosmetic wrap. */
@media (min-width:900px){ .msa-ctaband__acts{ flex-wrap:nowrap; } }
.btn--white{ background:var(--white); color:var(--red); border-color:var(--white); }
.btn--white:hover,.btn--white:focus-visible{ background:var(--stage); color:var(--white); border-color:var(--stage); }
.msa-ctaband .btn--ghost{ color:var(--white); border-color:rgba(255,255,255,.62); }
.msa-ctaband .btn--ghost:hover,.msa-ctaband .btn--ghost:focus-visible{
  background:var(--white); color:var(--red); border-color:var(--white);
}
@media (max-width:899px){
  .msa-ctaband{ padding-block:64px; }
  .msa-ctaband__inner{ grid-template-columns:1fr; gap:36px; }
  .msa-ctaband h2{ font-size:2.125rem; }
  .msa-ctaband__acts{ justify-content:flex-start; }
}

/* ================================================= the paired figures, PORTED from the mockup
     .meas .plate{background:#000;height:300px;display:flex;align-items:center;justify-content:center}
     .meas .plate.lit{background:var(--white)}
     .meas figcaption{display:flex;align-items:flex-start;gap:10px;margin-top:18px}

   The page's whole claim in two pictures: the aperture on one OLED sub-pixel, and the spectrum
   that sub-pixel returns. It is the only place on the site where a micrograph and its own
   measurement sit side by side, which is what a microspectrophotometer actually does.
   The mockup runs this on the stage and colours the caption's lead word white; here the section
   is on paper, so the lead word takes the ink and the trailing detail takes --dim-ink. */
.msa-meas{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:26px;
  margin-block-start:var(--gap-head);
  padding:0;
  list-style:none;
}
.msa-meas > li{ margin:0; padding-inline-start:0; }
.msa-prose .msa-meas > li::before,
.msa-prose .msa-meas > li::marker{ content:none; }
.msa-meas figure{ margin:0; }   /* size: see the hug rule below */
/* THE PLATE HUGS ITS PICTURE. It was a full-column 619x300 box holding, at 1440, a 277x208
   micrograph — 45% of the width, 55% of it empty black — and a 400x231 chart beside it. The
   client's micrographs are 200-400px; any plate larger than that letterboxes, and a plate this
   much larger reads as a picture lost in a box rather than as a frame.
   This is the rule the stylesheet already applies to captioned figures in prose ("A CAPTIONED
   FIGURE HUGS ITS IMAGE, wherever it sits"), which these are; they were simply not covered by
   it because they live in a classed list. The two plates end up different widths, each centred
   in its own half — which is what two differently-sized photographs honestly look like. */
/* THE PLATE CARRIES THE PICTURE, it does not shrink to it. Hugging the image fixed a 45%-fill
   plate by making the plate small — a 277px photograph adrift in a 619px column, which reads as
   an afterthought rather than as the subject. The approved design gives these figures a plate of
   real size and lets the picture fill it. Capped so the upscale stays around 1.5x. */
.msa-meas figure{ inline-size:100%; margin-inline:0; }
.msa-meas .msa-plate{
  block-size:auto;
  inline-size:min(100%, 440px);
  aspect-ratio:4 / 3;
  margin-inline:0;
}
.msa-meas .msa-plate img{ inline-size:100%; block-size:100%; object-fit:contain; }
.msa-meas .msa-plate--lit{ background:var(--white); border:1px solid var(--line-ink); }
.msa-meas figcaption{
  display:flex;
  align-items:flex-start;
  /* CENTRE THE WHOLE GROUP, not the text inside it. This is a flex row — the aperture mark
     and the caption text — and a flex row with no justify-content packs to flex-start. The
     `text-align:center` inherited from .msa-prose figcaption centres the words INSIDE the
     text span, which is already shrink-to-fit, so it moves nothing. Measured at 1440: the
     caption box was x=126 w=544, the group occupied 126..519, and its optical centre sat
     66px left of the image it names. On mobile the mark was left stranded at the far edge of
     a two-line caption. text-align cannot centre a flex line; justify-content is the property
     that does. */
  /* AND THE GROUP ALIGNS WITH THE PICTURE. Centring the caption was the fix for a caption
     that sat 66px off its own optical centre, and it was the wrong axis: the plate is
     `inline-size:min(100%,440px)` inside a 619px column, so it is LEFT-aligned, and a centred
     caption under a left-aligned picture starts 37px to its right and ends 141px past it.
     Measured at 1440: plate 88..528, caption 125..669, the aperture mark at x=201 against a
     picture edge at x=88. The micrograph grid two sections down already does this correctly —
     plate 88..384.5, caption 88..384.5 — so this is the outlier, not the pattern. The caption
     takes the plate's own width and its left edge. */
  justify-content:flex-start;
  text-align:start;
  max-inline-size:min(100%, 440px);
  gap:10px;
  margin-block-start:18px;
  font-family:var(--font-mono);
  font-size:.75rem;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--ink-dim);
}
.msa-meas figcaption b{ color:var(--ink,var(--stage)); font-weight:400; }
@media (max-width:759px){
  .msa-meas{ grid-template-columns:1fr; }
  .msa-meas .msa-plate img{ max-block-size:220px; }
}


/* --- the mockup's OWN mobile button rule, which I failed to port with the desktop one -------
     @media: .btn{padding:14px 18px;white-space:normal;text-align:center}
   `white-space:nowrap` came across from the desktop rule and nothing came with it, so at 320px
   "See all twenty-two explainers" rendered 323px wide inside a 320px viewport and pushed the
   document 23px sideways. A long label is exactly what nowrap cannot survive on a handset. */
@media (max-width:559px){
  .btn,.btn--ghost{
    padding:14px 18px;
    white-space:normal;
    text-align:center;
  }
}

/* --- 12px is the floor, and these were at 11 -----------------------------------------------
   The stylesheet already says it, next to .msa-foot__h and .eyebrow: "11px is under every floor
   worth holding, and neither needs to be that small." The components added since — the utility
   bar, the section eyebrows, the library labels — were set at .6875rem (11px) and quietly broke
   the rule the theme states about itself. Eleven of them, on the page a client opens first. */
.msa-util,
.msa-eyebrow-ap,
.msa-lib__label{ font-size:.75rem; }

/* The "see all" line belongs to its section: inside it, and set off from the grid above by the
   grid's own rhythm rather than by a paragraph margin nobody chose. */
.msa-sec__more{ margin-block:1.75rem 0; }
.msa-sec__more a{ font-family:var(--font-display); font-weight:500; }

/* ===================================================================== the sticky masthead
   The masthead follows the page. It carries the navigation, the wordmark and the phone number,
   and this site's pages are long — 284 of them, several running past 6,000px — so a visitor who
   has scrolled to a product they like should not have to scroll back to act on it.

   THE UTILITY BAR DOES NOT COME WITH IT. It carries the tagline, the certification and the
   number: reference facts a visitor reads once, not controls they reach for. Sticking the whole
   header would spend 44 more pixels of every screen on something already read, and on a handset
   that is most of a line of copy. It scrolls away; the masthead stays.

   `position:sticky` on .msa-head alone would do nothing while its ancestor is the flex column
   body — sticky resolves against the nearest scrollport, which is the document here, so the
   element sticks as long as no ancestor clips. Nothing on this page sets overflow, which is
   also why the full-bleed band was fixed by moving it rather than by clipping an ancestor. */
.msa-head{
  position:sticky;
  top:0;
}
/* The drawer's height cap is stated once, at .msa-nav__list above, and the desktop bar clears
   it deliberately with `max-height:none`. A second unconditional declaration here restated the
   first in a different property and outranked that clearance, capping the desktop navigation
   nobody had capped. Removed rather than duplicated: one statement of a fact. */

/* THE HEADER'S EDGE, AT REST AND IN MOTION. This hairline used to be opacity:0 until the
   masthead pinned, which left a dark bar sitting on a dark hero with no bottom edge at all —
   the header's foot was findable only where the hero photograph happened to start. Sampled on
   the approved mockup at rest, at x=1400: srgb(44,45,47), which is this exact value composited
   on the stage. So it is drawn from the first frame, as the mockup draws it.

   Sticking is then a change of depth rather than the appearance of a line: the rule doubles and
   a soft shadow falls onto the content passing underneath. Animation-free; it is a border. */
.msa-head::after{
  content:"";
  position:absolute;
  inset-inline:0;
  bottom:0;
  block-size:1px;
  background:rgba(var(--paper-rgb),.14);
}
.msa-head[data-stuck]::after{ background:rgba(var(--paper-rgb),.28); }
.msa-head[data-stuck]{ box-shadow:0 10px 24px -18px rgba(var(--stage-rgb),.9); }

/* An anchor target must clear the sticky masthead. Without this the skip link — the WCAG 2.4.1
   bypass block — lands 70px under the header on desktop and 64px on mobile, and so does every
   in-page fragment, including the tab panels on 117 product pages. The value tracks the
   masthead's own height rather than repeating it. */
html{ scroll-padding-block-start:calc(var(--msa-head-h) + 8px); }
/* 899, because that is where .msa-brand__logo and the masthead's padding change and the
   header measures 64px instead of 69.9px — not 759, which is a different breakpoint
   this file uses for other things. Keep this number with the header's, not with them. */
@media (max-width:899px){ :root{ --msa-head-h:64px; } }

/* The logged-in client sees their own site under the admin bar, which is fixed above ours. */
body.admin-bar .msa-head{ top:32px; }
@media (max-width:782px){ body.admin-bar .msa-head{ top:46px; } }

/* The figure grid's link holds TEXT ONLY, for the same reason the discovery grid's does:
   wpautop injects <br /> when block elements sit inside an inline <a>, and eight of them were
   reaching the served instruments grid. The cell is the list item; the link is stretched over
   it so the whole card stays clickable. */
.msa-figgrid > li{ position:relative; }
.msa-figlink{ color:inherit; text-decoration:none; }
.msa-figlink::after{ content:""; position:absolute; inset:0; }
.msa-figgrid > li:hover .msa-figlink{
  text-decoration:underline; text-decoration-color:var(--red);
  text-decoration-thickness:2px; text-underline-offset:.16em;
}

/* ===================================================== the plane change, restored
   THE SPECIMENS SIT ON THE STAGE; THE PAGE TURNS WHITE WHERE THE HARDWARE IS.

   Sampled down both renders every 50px: the approved design is dark for its first 3,750px —
   the hero, the paired aperture-and-spectrum figures and the whole micrograph gallery — and
   turns white only at the instruments. This build turned white 1,000px in and then ran 6,150px
   of unbroken white to the footer. That single missing plane change is most of why the page
   read as one endless column of text: eight sections in a row on one surface, with nothing to
   say where one idea ended and the next began.

   Full-bleed by box-shadow spread rather than 100vw, because 100vw includes the scrollbar and
   this project has already paid for that once; clip-path keeps the spread from becoming
   horizontal overflow, which S-STRUCT-07 fails a page for. */
.msa-sec--stage{
  background:var(--stage);
  color:var(--paper);
  --ink-dim:var(--dim);          /* a dark surface names its own secondary ink — see :root */
  box-shadow:0 0 0 100vmax var(--stage);
  clip-path:inset(0 -100vmax);
  padding-block:var(--gap-section);
  margin-block:0;
}
/* ADJACENT STAGE SECTIONS ARE ONE PLANE, NOT TWO STACKED BANDS. `margin-block:0` on the
   single-class rule above loses to `.msa-sec + .msa-sec`, which is two classes and therefore
   more specific — so the section gap survived and split the dark plane with a 120px white
   band across the middle of it. Matched at the same specificity here, and later in the file. */
.msa-sec--stage + .msa-sec--stage{
  margin-block-start:0;
  padding-block-start:0;
}
/* AND NO SEAM WHERE THEY MEET. Each dark section paints past the shell by shadow spread and
   is cut back to its own height by clip-path — and a clip-path edge is not snapped to device
   pixels the way a background is. Where two dark sections meet at a fractional y, Firefox
   anti-aliases BOTH clipped edges, a sub-pixel strip is painted by neither, and the white
   <body> shows through as a full-width hairline that flashes as the scroll offset changes.
   Measured in Firefox at device-pixel-ratio 1.25 (Windows at 125%): a light row on 61 of 80
   scroll offsets across the home page's two dark-to-dark seams; none at 1x or 2x.

   The second section's clip reaches 1px above its own top, so its paint overlaps the edge
   instead of meeting it. Only where both sides are the SAME surface — stage on stage, panel on
   panel — is the overlap invisible; a stage/panel pair is two colours, where a 1px overlap would
   draw a line of its own, so it is not matched here (none is adjacent today; S-SEAM-76 measures
   every dark-to-dark seam, mixed or not, and would report one). */
.msa-sec--stage + .msa-sec--stage,
.msa-sec--panel + .msa-sec--panel{
  clip-path:inset(-1px -100vmax 0);
}
.msa-sec--stage h2{ color:var(--white); }

/* ============================================================== the standing band
   Twenty-five years, five hundred clients and an ISO certification, set as a claim.

   WHAT IT REPLACES, MEASURED. `.msa-hero__counters` was a 1360x99 strip at the bottom of
   the hero on all eight pages using template-feature.php: 36px figures at weight 500 over
   12px Martian Mono labels at --dim, three equal 405px columns at x=88, 517 and 946, no
   rule between them, no difference in weight or scale. Three facts of three different
   kinds — a duration, a client count, and a count of articles on this website — set as one
   series, in a strip a reader scrolls past on the way to the content. A status readout for
   something that should be a statement, and published above a comparison page whose job is
   to answer "which of these three instruments do I need".

   THE FIGURES ARE THE HEADING. clamp(4rem, 8.5vw, 7.5rem) is 120px at 1440 and nothing else
   on this site is set within 60px of it — the hero's own h1 is 52px. That is the whole
   celebration, and it is the reason there is no animation in this block: a number that has
   to spin to be noticed is a number set too small.

   THE CERTIFICATION IS NOT A FIGURE. "ISO 9001" is eight glyphs, still not a quantity; at the figure size
   it would run wider than the two real figures and no third of a 1360 wrap holds it, so forcing the series to
   accommodate it would mean setting the two real figures smaller for the sake of one that is
   not a quantity at all. It takes the size of a claim instead — 44px at 1440 — and the
   aperture marks it as one, the same square the eyebrows and the measurement captions carry.

   SUBGRID, SO THE LABELS SHARE A LINE. The three claims are 120px, 120px and 44px tall; in
   three independent flex columns their labels would start on three different lines, which is
   the ragged edge this band exists to stop being. `grid-template-rows: subgrid` puts every
   claim in one row and every label in the next, and `align-self:end` sits the short one on
   the row's baseline beside the tall ones. Where subgrid is unsupported the flex fallback
   above still stacks correctly — it just does not align. */
.msa-sec--stage.msa-standing{
  /* The adjacency rule above collapses the gap between two stage sections to nothing, which
     is right when they are one plane of the same kind. This is a change of subject inside
     that plane, so it takes its own air back — two classes, matching that rule's specificity
     and later in the file, which is the trap the note above it records. */
  padding-block-start:calc(var(--gap-section) * 1.4);
  padding-block-end:calc(var(--gap-section) * 1.3);
}
/* AIR BETWEEN THE SENTENCE AND THE FIGURES. --gap-head is the distance from a section
   heading to the content it introduces, and it is right when that content is a grid of cards.
   Here the content is three claims at display size, and 64px put them close enough to the
   heading to read as its continuation rather than as the thing it introduces. */
.msa-standing .msa-sechead{ margin-block-end:calc(var(--gap-head) * 1.75); }

.msa-standing__figures{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:3rem var(--gutter);
  margin:0;
  padding:0;
  list-style:none;
}
@media (min-width:760px){
  .msa-standing__figures{ grid-template-columns:repeat(3,minmax(0,1fr)); }
}
.msa-standing__figure{
  display:flex;
  flex-direction:column;
  gap:.85rem;
  min-width:0;
}
@supports (grid-template-rows:subgrid){
  @media (min-width:760px){
    .msa-standing__figures{ grid-template-rows:auto auto; }
    .msa-standing__figure{
      display:grid;
      grid-row:span 2;
      grid-template-rows:subgrid;
      row-gap:.85rem;
    }
    .msa-standing__figure > :first-child{ align-self:end; }
  }
}
.msa-standing__n{
  /* 120px WAS TOO BIG, measured against the sentence above it: the h2 sets at 41px and the
     figures at 120 were very nearly three times it, so the heading read as a caption to the
     numbers rather than as the claim they support. 80px is a shade under twice the heading —
     still by a distance the largest type on the site, and now in scale with the sentence it
     belongs to. */
  font-size:clamp(3.25rem,5.6vw,5rem);
  line-height:.85;
  font-weight:600;
  letter-spacing:-.035em;
  color:var(--white);
  /* Lining, tabular figures: 25 and 500 are read against each other, and a proportional 5
     under a proportional 2 puts the two claims on different optical columns. */
  font-variant-numeric:tabular-nums lining-nums;
}
.msa-standing__cert{
  display:flex;
  align-items:baseline;
  gap:.7rem;
  /* At 36px beside 80px figures — 45% of their size — it read as a caption to them rather than
     the third claim (the client's note after v1.46.0). 60px at 1440 is three quarters of the
     figures: still not set as a quantity, plainly one of the three, and "ISO 9001" with its mark
     fits the column at every width from 760px up (measured: 1440, 1024, 768). */
  font-size:clamp(2rem,4.2vw,3.75rem);
  line-height:1.1;
  font-weight:600;
  letter-spacing:-.015em;
  color:var(--white);
}
/* The mark on the claim's own baseline, not the top of its line box — the same correction
   the measurement captions make, against this element's line-height rather than a number. */
.msa-standing__cert .msa-ap{ align-self:baseline; transform:translateY(-0.28em); }
.msa-standing__l{
  font-size:1.0625rem;
  line-height:1.45;
  font-weight:400;
  color:var(--dim);
  /* NO MEASURE CAP. 24ch wrapped "Years in the micro-analysis field" onto a second line while
     its two neighbours stayed on one, and a second line hangs below the shared baseline the
     subgrid above exists to hold. The column is already a third of the wrap, which is a
     narrower measure than any cap would give it. */
  max-width:none;
}
@media (max-width:759px){
  /* On a handset the three claims are read one after another rather than compared, so the
     figure comes down to a size that leaves room for its label beside the thumb. */
  .msa-standing__n{ font-size:clamp(3rem,14vw,4rem); }
  /* The same three-quarter step on a handset: 39px under 55px figures at 390. */
  .msa-standing__cert{ font-size:clamp(2.25rem,10vw,3rem); }
  .msa-standing__l{ max-width:none; }
}
.msa-sec--stage .msa-figname,
.msa-sec--stage .msa-figlink{ color:var(--white); }
/* The caption's lead-in is set in --stage, which is the BODY ink: near-black, and invisible the
   moment the caption moves onto the stage. It is the half of the caption that names the
   specimen, so losing it costs the caption its subject. */
.msa-sec--stage .msa-meas figcaption b{ color:var(--white); }
/* The gallery plates are already black, so on the stage the pictures float free of any frame —
   which is what the approved design does with them. */
.msa-sec--stage .msa-plate{ background:transparent; }

/* ONE UNBROKEN STAGE FROM THE HERO INTO THE SPECIMENS. .msa-prose opens with 72px of top
   padding, which is right when the page turns white immediately below the hero and wrong when
   it does not: with the specimen sections on the stage it painted a 72px white strip across
   the page between the hero and the first thing under it, cutting the dark plane in two before
   it had started. The section below already carries the same 72px, on the stage, so the space
   is kept and only the surface changes. */
.msa-prose:has(> .msa-sec--stage:first-child){ padding-block-start:0; }

/* ============================================ the supporting band: four topics, one surface
   Techniques, Software, Applications and Service each had a full section head — eyebrow, 42px
   heading, standfirst — for a paragraph and three links. Five of those in a row is what made
   the lower half of the page read as an undifferentiated column, and Software was the clearest
   case: 454 characters of content under a heading the size of the page's own.

   They are peers, so they stay h2 and keep every word; only the frame around them changed. Two
   columns rather than four, because these paragraphs are the client's own and run long — four
   would set six-line columns beside one-line ones. On the stage, so the band breaks the white
   run between the instruments and the library rather than extending it. */
.msa-sec--topics{ padding-block:var(--gap-section); }
.msa-topics{
  list-style:none;
  margin:0;
  padding:0;
  display:grid;
  grid-template-columns:1fr;
  gap:56px;
}
@media (min-width:900px){
  .msa-topics{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:72px 80px; }
}
.msa-topic{ margin:0; padding:0; }
.msa-topic > h2{
  margin:14px 0 0;
  font-size:1.75rem;        /* 28px — a peer inside the band, not a page-level section head */
  line-height:1.15;
  letter-spacing:-.01em;
  color:var(--white);
}
.msa-topic__stand{
  margin:14px 0 0;
  max-inline-size:46ch;
  font-size:1.0625rem;
  line-height:1.6;
  color:var(--ink-dim);
  text-wrap:pretty;
}
.msa-topic__links{
  list-style:none;
  margin:26px 0 0;
  padding:0;
  display:grid;
  gap:0;
}
.msa-topic__links > li{
  margin:0;
  padding:14px 0;
  border-block-start:1px solid rgba(var(--paper-rgb),.14);
}
.msa-topic__links > li:last-child{ border-block-end:1px solid rgba(var(--paper-rgb),.14); }
.msa-topic__link{
  /* ITS OWN 24px HIT BOX (WCAG 2.5.8). This used to get one from the standalone-link rule, which
     reached into every component until it was scoped to bare prose links — and the moment it
     stopped, S-CONTROL-68 measured this at 467x21px on the home page. That is the blast radius
     of that change, caught by the gate that exists for it: a component that relied on a shared
     rule has to state what it needs itself. The <li> already pads 14px above and below, so the
     row is comfortably tappable; this is about the LINK being the target, not the row. */
  display:inline-block;
  padding-block:.125rem;
  min-block-size:24px;
  color:var(--white);
  text-decoration:none;
  font-family:var(--font-display);
  font-weight:500;
  font-size:1.0625rem;
}
.msa-topic__link:hover{
  text-decoration:underline;
  text-decoration-color:var(--red);
  text-decoration-thickness:2px;
  text-underline-offset:.18em;
}
.msa-topic__note{
  display:block;
  margin-block-start:4px;
  font-family:var(--font-mono);
  font-size:.75rem;
  letter-spacing:.04em;
  color:var(--ink-dim);
}
/* The prose list styling must not reach these — they are navigation, not a bulleted list.
   ::MARKER, NOT ::BEFORE. This theme draws its list bullets with ::marker in the brand red, so
   suppressing ::before removed nothing: every topic showed a red square before its eyebrow (the
   topic is itself an <li>) and every link showed another, so each eyebrow read as two markers
   in a row and the link lists grew bullets they were never meant to have. */
.msa-prose .msa-topics > li,
.msa-prose .msa-topic__links > li{ list-style:none; }
.msa-prose .msa-topics > li::marker,
.msa-prose .msa-topic__links > li::marker{ content:""; }
.msa-prose .msa-topics > li::before,
.msa-prose .msa-topic__links > li::before{ content:none; }

/* ==========================================================================================
   INSTRUMENT APPLICATIONS — the coverage grid and the field spreads
   ==========================================================================================
   THE PAGE THIS IS FOR, AND WHAT IT USED TO SHOW. /solutions/applications/ carries 100
   applications, 82 of them distinct, across five techniques and seven fields, with twenty
   photomicrographs of real samples — a library almost no instrument maker publishes. Measured
   on the deployed page before this: 193 visible words and ONE photograph, a stock shot of a man
   at a microscope. Everything else sat inside six collapsed tabs, and the first tab was an
   introduction. A buyer landing on the applications funnel could scroll the whole page without
   meeting a single application.

   WHY IT IS ORDERED BY FIELD. The tabs were ordered by what CRAIC makes. A visitor arrives
   knowing their sample: somebody in forensics had to open three separate tabs to find the three
   answers that exist for them, and could not see them together to choose between them. Ordered
   by field, the two questions they came with — is my work here, which instrument does it — are
   answered in one screen with the photograph beside each answer.

   WHERE THE COLOUR IS. Nowhere here. Every rule below is white, near-black and one red mark;
   the twenty photomicrographs are wildly coloured — fluorescence greens, a banknote under UV,
   petrographic minerals — and they are the argument. A page that competed with them would be
   decorating over its own evidence. */

/* ---- the opening: the client's own photograph beside the client's own paragraphs ---------- */
.msa-applede{
  display:grid;
  gap:var(--gutter);
  grid-template-columns:minmax(0,1fr);
  align-items:start;
  margin-block-end:var(--gap-section);
}
@media (min-width:900px){
  .msa-applede{ grid-template-columns:minmax(0,1.35fr) minmax(0,1fr); }
}
.msa-applede__text > p{ max-width:var(--measure); }
.msa-applede__text > p:first-child{ margin-block-start:0; }
.msa-applede__plate img{ display:block; inline-size:100%; block-size:auto; }
.msa-applede__plate figure{ margin:0; }

/* ---- the coverage grid ---------------------------------------------------------------------
   THE ONE BOLD THING ON THE PAGE, and it is bold by scale and emptiness rather than by
   ornament: a wide, airy grid on white with a single red square as its only colour. The square
   is the measuring aperture — the mark this site already spends on "the exact microscopic area
   an instrument measures" — so a filled cell reads as "this instrument measures this field"
   rather than as a tick somebody chose. Every row, column and mark is read out of the page's
   own content at render time; see inc/applications.php. */
.msa-coverage{
  /* The room the hover mark lives in, held once so the table and its first column agree. */
  --msa-coverage-inset:.65rem;
  margin:0 0 var(--gap-section);
  border-block-start:2px solid var(--stage);
}
/* THE FIELD NAMES STILL START ON THE PAGE'S OWN EDGE. The inset above would otherwise push the
   whole first column 10px in from every heading and paragraph on the page, which is the kind of
   near-miss this client notices. The scroll box is pulled out by exactly the inset and pads it
   back, so the text lands at the wrap edge and the mark occupies the space to its left. */
.msa-coverage__scroll{
  margin-inline-start:calc(var(--msa-coverage-inset) * -1);
  padding-inline-start:var(--msa-coverage-inset);
  /* THE GRID IS WIDER THAN A TABLET AND HAS TO SCROLL INSIDE ITS OWN BOX. `overflow-x:auto`
     alone does not do it here: an AUTO-LAYOUT table's intrinsic width propagates into the root's
     scrollable overflow past an overflow ancestor, so the page itself gained a horizontal
     scrollbar — S-STRUCT-07 measured /solutions/applications/ overflowing by 277px at 768px and
     150px at 900px, on a page that is clean at 390px and at 1440px. Every other containment was
     tried and measured: overflow:clip, overflow-y:hidden, display:flow-root, max-inline-size and
     removing the negative margin all left it at 1045. Two things fix it — `table-layout:fixed`,
     which sizes the columns from the table instead of from their content and wraps every
     technique name to three lines, and this. Paint containment costs nothing here because
     nothing in the grid paints outside its own cell: the focus ring uses a NEGATIVE outline
     offset and the hover mark scales within its box. */
  contain:paint;
}
/* A figcaption on this site is a centred monospace note under a photograph, which is right
   there and wrong here: this one is a section heading with a sentence under it. Both are said
   back explicitly rather than left to inherit. */
.msa-coverage figcaption{
  /* A figcaption on this site is a centred monospace note under a photograph. This one is a
     section heading with a sentence under it, so every part of that inheritance is said back. */
  padding-block:1.75rem 1.5rem;
  inline-size:auto;
  /* .msa-prose figcaption is 13px Martian Mono at 34ch, which is right for a line naming a
     sample under a photomicrograph and wrong for a section heading with a sentence under it.
     Every part of that inheritance is said back rather than left to chance. */
  max-inline-size:none;
  margin-block-start:0;
  margin-inline:0;
  text-align:start;
  font-family:var(--font-body);
  font-size:1rem;
  line-height:1.55;
  color:var(--stage);
}
.msa-coverage figcaption h2{
  margin:0;
  font-size:clamp(1.375rem,1.1rem + 1.1vw,1.875rem);
  line-height:1.15;
  letter-spacing:-0.015em;
}
.msa-coverage figcaption p{
  margin:.5rem 0 0;
  max-width:var(--measure);
  font-size:1rem;
  color:var(--dim-ink);
}
/* The grid is wider than a phone and narrower than a desk. It scrolls inside its own box rather
   than making the page scroll, which is the rule every wide thing on this site follows. */
.msa-coverage__scroll{ overflow-x:auto; }
/* THE WRAPPER SCROLLS, NOT THE TABLE, and that distinction is the whole bug.
   `.msa-prose table` is `display:block; overflow-x:auto; inline-size:max-content; max-width:100%`
   — the theme's responsive-table treatment, right for a wide data table dropped into prose.
   Putting min-inline-size:46rem on top of it made the table its own scroll container AND gave it
   a floor wider than its parent (min-width beats max-width), and the result escaped the page:
   S-STRUCT-07 measured /solutions/applications/ overflowing by 277px at 768px and 150px at
   900px, on a page that is clean at 390 and at 1440. The tablet band is exactly the gap that
   gate exists to cover, and exactly the band I had not looked at.
   So the table goes back to being a table, the scrolling belongs to .msa-coverage__scroll, and
   the 46rem floor stays — inside a box that can actually scroll it. */
.msa-coverage table{
  display:table;
  overflow:visible;
  inline-size:100%;
  max-inline-size:none;
  border-collapse:collapse;
  /* Wide enough that the technique names never wrap to two lines mid-grid, which is what made
     the first version look ragged. Below this the wrapper scrolls. */
  min-inline-size:46rem;
}
.msa-coverage th,
.msa-coverage td{ padding:0; text-align:center; vertical-align:middle; }
.msa-coverage thead th{
  padding:0 .5rem .75rem;
  font-family:var(--font-body);
  font-size:.9375rem;
  font-weight:600;
  line-height:1.25;
  color:var(--dim-ink);
  border-block-end:1px solid var(--stage);
  vertical-align:bottom;
}
.msa-coverage thead th:first-child{
  text-align:start;
  padding-inline-start:var(--msa-coverage-inset);
}
.msa-coverage tbody th{
  inline-size:28%;
  padding:0;
  text-align:start;
  font-family:var(--font-display);
  font-size:1.0625rem;
  font-weight:600;
  line-height:1.3;
}
.msa-coverage tbody tr{ border-block-end:1px solid var(--line-ink); }
/* Beats .msa-prose's link rules, which would otherwise draw every field name in red and
   underline it — a row of seven red headings above a grid whose only colour is meant to be the
   marks inside it. */
.msa-prose .msa-coverage__field{
  display:block;
  /* Said here, not inherited: 13px is under every floor this stylesheet holds, and that is what
     the field names measured before this line. */
  font-family:var(--font-display);
  font-size:1.0625rem;
  font-weight:600;
  line-height:1.3;
  /* THE INDENT IS PERMANENT AND THE COLOUR IS WHAT MOVES. It used to be the other way round:
     hovering a row added .65rem of leading padding, so the field name jumped right, the first
     column re-measured, and every OTHER column shifted with it — a whole-table twitch on a grid
     you read by moving across it. The space for the mark is always there now; hover only fills
     it. Nothing about the layout changes on hover, so nothing can jump. */
  padding:1.2rem .75rem 1.2rem var(--msa-coverage-inset);
  color:var(--stage);
  /* The aperture again, on the leading edge — the same device that marks the section you are in
     elsewhere in this stylesheet. It appears on hover and focus, so the row you are reading says
     so without the grid carrying a permanent stripe down its side. */
  box-shadow:inset 3px 0 0 0 transparent;
  /* SLOW OUT, QUICK IN. The base state carries the longer duration, so the highlight arrives at
     once and fades back gently — moving along a column of seven rows no longer flickers. */
  transition:box-shadow 300ms ease, color 300ms ease;
}
.msa-coverage tbody tr:hover .msa-coverage__field,
.msa-coverage__field:focus-visible{
  box-shadow:inset 3px 0 0 0 var(--red);
  color:var(--red);
  transition-duration:110ms;
}
.msa-prose .msa-coverage__cell{
  display:flex;
  align-items:center;
  justify-content:center;
  min-block-size:2.75rem;
  text-decoration:none;
  /* A COLOUR, BECAUSE THE EXCLUSION ABOVE TOOK IT OUT OF THE ONE THAT WAS GIVING IT ONE. This
     class is in the `:not()` list on the prose-link rule so the cell does not get an underline
     and a 24px inline box, and nothing put it back — so every cell link computed
     rgb(0,0,238), the browser's default. It never LOOKED wrong because the cell's only visible
     child is the aperture mark and its text is the screen-reader name, but a link that kept
     the browser default is the defect S-CONTROL-74 checks for and it was right to find 20 of
     them. Setting the colour is the fix; hiding behind "you cannot see it" is not. */
  color:var(--stage);
}
/* display:block, and it is not a detail. A <span> is a non-replaced INLINE box, and width and
   height do not apply to one — so the first version of this grid set .75rem on both and painted
   a zero-sized nothing in all twenty-four cells. This stylesheet has the same note against the
   aperture mark two thousand lines up; it is the trap this codebase pays for most often. */
.msa-coverage__mark{
  display:block;
  inline-size:.875rem;
  block-size:.875rem;
  background:var(--red);
  transition:transform 120ms ease;
}
/* Covered, but the list lives under another field: an outline rather than a fill, and the link
   goes where the words actually are. Nothing is duplicated and nothing is hidden. */
.msa-coverage__yes--elsewhere .msa-coverage__mark{
  background:none;
  box-shadow:inset 0 0 0 2px var(--red);
}
.msa-coverage__cell:hover .msa-coverage__mark{ transform:scale(1.3); }
.msa-coverage__cell:focus-visible{ outline:3px solid var(--stage); outline-offset:-3px; }

/* A phone cannot hold seven columns, and a grid scrolled sideways on a phone is a grid nobody
   reads. Each row becomes its own block: the field name, then the techniques that measure it as
   a wrapped row of named marks. Same information, same order, no horizontal scroll. */
@media (max-width:759px){
  .msa-coverage table,
  .msa-coverage tbody,
  .msa-coverage tbody tr,
  .msa-coverage tbody th,
  .msa-coverage tbody td{ display:block; inline-size:auto; min-inline-size:0; }
  .msa-coverage thead{ display:none; }
  .msa-coverage tbody tr{ padding-block:1rem; }
  .msa-coverage{ --msa-coverage-inset:0px; }
  .msa-coverage__scroll{ margin-inline-start:0; padding-inline-start:0; }
  .msa-coverage__field{ padding:0 0 .5rem; }
  .msa-coverage tbody tr:hover .msa-coverage__field,
  .msa-coverage__field:focus-visible{ padding-inline-start:0; box-shadow:none; color:var(--red); }
  .msa-coverage__no{ display:none !important; }
  /* THESE HAVE TO OUT-SPECIFY THE DESKTOP RULES ABOVE. `.msa-prose .msa-coverage__cell` is
     (0,2,1) and a bare class is (0,1,0), so the first version of this media query lost every
     declaration in it: the grid came out as one mark per line, full width, with no technique
     names at all. A media query changes nothing about specificity. */
  .msa-prose .msa-coverage tbody td{ display:inline-block; inline-size:auto; }
  .msa-prose .msa-coverage__cell{
    display:inline-flex;
    gap:.5rem;
    justify-content:flex-start;
    min-block-size:2.75rem;
    padding-inline-end:1.25rem;
    font-size:.9375rem;
    color:var(--stage);
  }
  /* The column heading has nowhere to live once the head row is gone, so each mark says what it
     is. data-tech is on the ANCHOR, written there by the shortcode: attr() resolves against the
     element the pseudo belongs to and not against its parent, so reading it off the <td> —
     which is where it started — printed an empty string in every cell. */
  .msa-prose .msa-coverage__cell::after{ content:attr(data-tech); }
}

/* ---- a field spread ------------------------------------------------------------------------ */
.msa-appfield{
  margin-block:0 var(--gap-section);
  padding-block-start:var(--gap-head);
  border-block-start:1px solid var(--line-ink);
}
.msa-appfield > h2{
  margin:0;
  font-size:clamp(1.5rem,1.15rem + 1.4vw,2.125rem);
  line-height:1.12;
  letter-spacing:-0.02em;
}
.msa-appfield__stand{
  margin:.6rem 0 0;
  max-width:var(--measure);
  font-size:1.0625rem;
  line-height:1.55;
  color:var(--dim-ink);
}

/* THE COLUMNS ARE THE COMPARISON. Tabs would show one technique at a time, which is the thing
   this page was already doing and the reason it does not work: a reader choosing between
   instruments has to see them together. Equal columns, and every plate and every list starts on
   the same line across the row — subgrid does that, and the min-heights below do it well enough
   where subgrid is not available. */
.msa-appfield__techs{
  display:grid;
  /* ROW GAP 0, AND THAT IS THE SUBGRID TALKING. With subgrid the parent's row gap is applied
     between EVERY row a column spans — head, kit, plate, list — so a --gutter of 48px opened
     four 48px holes inside each column and the heading floated 76px above its own instrument
     list. The columns space themselves with their own margins; the gap between wrapped rows of
     columns is carried by .msa-apptech's bottom padding below. */
  gap:0 calc(var(--gutter) * 0.9);
  grid-template-columns:minmax(0,1fr);
  margin-block-start:2rem;
}
@media (min-width:640px){
  .msa-appfield__techs[data-n="2"],
  .msa-appfield__techs[data-n="4"]{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .msa-appfield__techs[data-n="3"]{ grid-template-columns:repeat(3,minmax(0,1fr)); }
}
@media (min-width:1000px){
  .msa-appfield__techs[data-n="4"]{ grid-template-columns:repeat(4,minmax(0,1fr)); }
  .msa-appfield__techs[data-n="5"]{ grid-template-columns:repeat(5,minmax(0,1fr)); }
}
/* One technique is not a row of columns; it is a spread. The plate takes the right, the list
   the left, which is the arrangement the rest of this site already uses for a figure beside
   prose. */
@media (min-width:760px){
  .msa-appfield__techs[data-n="1"]{ grid-template-columns:minmax(0,1fr); }
  .msa-appfield__techs[data-n="1"] .msa-apptech{
    display:grid;
    grid-template-columns:minmax(0,1fr) minmax(0,22rem);
    grid-template-areas:"head plate" "kit plate" "list plate";
    column-gap:var(--gutter);
    align-items:start;
  }
  .msa-appfield__techs[data-n="1"] .msa-apptech > h3{ grid-area:head; }
  .msa-appfield__techs[data-n="1"] .msa-apptech__kit{ grid-area:kit; }
  .msa-appfield__techs[data-n="1"] .msa-apptech__plate{ grid-area:plate; }
  .msa-appfield__techs[data-n="1"] .msa-apptech__body{ grid-area:list; }
  /* A SINGLE-TECHNIQUE FIELD IS A LONG LIST BESIDE A SHORT PLATE. Displays runs to twelve
     applications and Materials Science to nine, so one column of them left the picture stranded
     at the top of a tall empty gutter — the imbalance is the only thing these two sections had
     that the four-column ones did not. Two columns bring the list back to the plate's own
     height. Applied only here: a list inside one of several columns is already narrow. */
  .msa-appfield__techs[data-n="1"] .msa-apptech__body > ul{
    columns:2;
    column-gap:var(--gutter);
  }
  .msa-appfield__techs[data-n="1"] .msa-apptech__body > ul li{
    break-inside:avoid;
    /* A list item that starts a column must not inherit the previous column's top margin. */
    margin-block-start:0;
  }
}
.msa-apptech{
  display:grid;
  grid-template-rows:auto auto auto 1fr;
  align-content:start;
  row-gap:0;
  /* The space between one row of columns and the next when they wrap, which the parent's row
     gap can no longer carry. Removed from the last row by the negative margin below, so the
     section foot sits where it would have. */
  padding-block-end:2.75rem;
}
.msa-appfield__techs{ margin-block-end:-2.75rem; }
/* A TECHNIQUE WITH NO PICTURE keeps its list on the list row. The four rows are heading,
   instruments, picture, list, shared across a field's columns by subgrid; without a plate the
   list would rise into the picture row and start ~200px above its neighbours' lists. Two NIR
   blocks have no picture because the client has no near-infrared photograph of a mineral or a
   canvas, and a visible-light one under an "NIR microscopy" heading would misstate the method. */
.msa-apptech:not(:has(> .msa-apptech__plate)) > .msa-apptech__body{ grid-row:4; }
@supports (grid-template-rows:subgrid){
  .msa-appfield__techs{ grid-template-rows:auto auto auto 1fr; }
  @media (min-width:640px){
    .msa-appfield__techs:not([data-n="1"]) .msa-apptech{ grid-row:span 4; grid-template-rows:subgrid; }
  }
}
.msa-apptech > h3{
  margin:0;
  font-size:1.0625rem;
  font-weight:700;
  line-height:1.3;
  letter-spacing:-0.005em;
}
/* The instruments that do the work, as a row rather than a sentence: four names run together
   with nothing between them read as one long name. Underlined on hover only — four permanently
   underlined links directly under a heading compete with the heading. */
.msa-apptech__kit{
  display:flex;
  flex-wrap:wrap;
  gap:.15rem 1rem;
  margin:.45rem 0 0;
  padding:0;
  font-size:.9375rem;
  line-height:1.5;
  color:var(--dim-ink);
}
.msa-prose .msa-apptech__kit a{
  /* 28px TALL AND 24px WIDE. The height was set here and the width was not, so "rIQ" — three
     characters, the shortest instrument name in the range — rendered 20x28px at both 390 and
     1440, under WCAG 2.5.8's 24x24 floor. Measured by S-CONTROL-74; it is the only name short
     enough to fall through, which is exactly the kind of thing a person checking by eye does
     not find. justify-content keeps the short ones centred in the wider box rather than
     leaving the underline running past the word. */
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-inline-size:1.5rem;
  min-block-size:1.75rem;
  color:var(--dim-ink);
  text-decoration:none;
  box-shadow:inset 0 -1px 0 0 var(--line-ink);
}
.msa-prose .msa-apptech__kit a:hover{ color:var(--red); box-shadow:inset 0 -1px 0 0 var(--red); }
/* ONE HEIGHT FOR EVERY PLATE IN A ROW, and the picture is never cropped and never enlarged past
   its own pixels: these are measurements, and a photomicrograph stretched or cut is a different
   measurement. object-fit:contain on a fixed box against --paper does both, and it is what
   makes a row of four samples read as four samples of one kind rather than four sizes. */
.msa-apptech__plate{
  margin-block-start:1.1rem;
  display:flex;
  gap:.5rem;
  align-items:center;
  justify-content:center;
  block-size:11.5rem;
  padding:.5rem;
  background:var(--paper);
}
.msa-apptech__plate figure{ margin:0; display:contents; }
.msa-apptech__plate img{
  inline-size:auto;
  max-inline-size:100%;
  block-size:100%;
  max-block-size:100%;
  object-fit:contain;
}
.msa-apptech__plate[data-n="2"] img{ max-inline-size:calc(50% - .25rem); }
.msa-apptech__body{ margin-block-start:1.1rem; }
.msa-apptech__body > ul{
  margin:0;
  padding-inline-start:1.15rem;
  font-size:1rem;
  line-height:1.5;
}
.msa-apptech__body > ul li{ margin-block-end:.45rem; }
/* Whatever a panel says after its list — usually "And more…" with a link behind it. A note,
   set as one: it is not a heading, and the client's own wording is kept. */
.msa-apptech__more{
  margin-block-start:.85rem;
  font-size:.9375rem;
  line-height:1.5;
  color:var(--dim-ink);
}
.msa-apptech__more p{ margin:0 0 .35rem; max-width:none; font-size:inherit; font-weight:inherit; }
.msa-apptech__more p:last-child{ margin-block-end:0; }

/* An application more than one of this field's instruments can measure. It is a choice of
   instrument rather than a repetition, which is worth a mark and is invisible without one — but
   only here, where both columns are on screen together and the reader can see what the choice
   is. The outline is the same mark the coverage grid uses for the same meaning. */
.msa-apptech__shared{
  display:inline-block;
  inline-size:.5rem;
  block-size:.5rem;
  margin-inline-start:.45rem;
  vertical-align:.05em;
  box-shadow:inset 0 0 0 1.5px var(--red);
}

.msa-appfield__legend{
  margin:1.5rem 0 0;
  max-width:var(--measure);
  font-size:.9375rem;
  line-height:1.55;
  color:var(--dim-ink);
}
.msa-appfield__legend .msa-apptech__shared{ margin-inline:0 .15rem; }
.msa-appfield__also{
  margin:1.25rem 0 0;
  max-width:var(--measure);
  font-size:.9375rem;
  color:var(--dim-ink);
}
.msa-appfield__next{
  display:flex;
  flex-wrap:wrap;
  gap:.5rem 1.75rem;
  margin:1.5rem 0 0;
  max-width:none;
}
/* THE TWO NEXT STEPS, AND THEY ARE THE POINT OF THE PAGE. Each carries the aperture square the
   rest of this site puts in front of "here is a thing" — a sales funnel that ends in two grey
   underlines ends in nothing. They are links rather than buttons because there are seven fields
   and fourteen buttons would shout over the work they sit under. */
.msa-appfield__next a{
  display:inline-flex;
  align-items:center;
  gap:.55rem;
  min-block-size:2.75rem;
  font-size:1.0625rem;
  font-weight:600;
  color:var(--stage);
  text-decoration:none;
}
.msa-appfield__next a::before{
  content:"";
  inline-size:.5rem;
  block-size:.5rem;
  background:var(--red);
  flex:0 0 auto;
}
.msa-appfield__next a span,
.msa-appfield__next a{ text-underline-offset:.28em; }
.msa-appfield__next a:hover{ color:var(--red); text-decoration:underline; text-decoration-color:var(--red); }

/* ---- the techniques, in detail: the one place a tab strip earns its keep -------------------
   Four write-ups a reader dips into one at a time, which is what tabs are for. The comparison
   above is what they are not for. */
.msa-apptechs{
  margin-block:var(--gap-section);
  padding-block-start:var(--gap-head);
  border-block-start:2px solid var(--stage);
}
.msa-apptechs > h2{
  margin:0 0 1.5rem;
  font-size:clamp(1.375rem,1.1rem + 1.1vw,1.875rem);
  line-height:1.15;
  letter-spacing:-0.015em;
}
/* THE TAB STRIP STARTS WHERE THE HEADING ENDS. craic-tabs carries 80px of its own margin for the
   product pages, where it is the whole page; here it follows a heading that already owns the
   space above it, so the two stacked to 104px of nothing between "The techniques, in detail" and
   the first tab. The panel's 52px of top padding is the same story one level down. */
.msa-apptechs .craic-tabs{ margin-block:0; }
.msa-apptechs .craic-tabs__panel{ padding-block-start:2rem; }
/* THE PAGE DOES NOT SHORTEN WHEN YOU CHANGE TAB. The four write-ups measure 410, 516, 409 and
   320px, so moving from Raman to Specialty pulled 200px out from under the reader and
   everything below jumped up to meet them — the same defect the coverage grid's hover had,
   one section further down. The container holds the tallest, so switching only ever changes
   what is inside it. The floor is a measurement of today's content and it fails soft: a longer
   write-up simply exceeds it. Desktop only — on a phone every panel is far taller than this. */
@media (min-width:760px){
  .msa-apptechs .craic-tabs__panels{ min-block-size:33rem; }
}
/* The instruments are the way out of this block and on to a product page. At the kit's shared
   15px they read as metadata under a paragraph set at 18px — a payload smaller than the prose
   explaining it, which is a defect class this project has a name for. */
.msa-apptechs .msa-techkit{
  display:flex;
  flex-wrap:wrap;
  gap:.4rem 1.75rem;
  margin-block-start:1.25rem;
}
.msa-prose .msa-apptechs .msa-techkit a{
  font-size:1.0625rem;
  font-weight:600;
}
.msa-appfield--papers{ border-block-start-width:2px; border-block-start-color:var(--stage); }
/* AT THE READING MEASURE. Everything else in a field section is a column or a grid and sets its
   own width; this block is plain prose, and a <p> that is not a direct child of .msa-prose gets
   no measure at all — it ran the full 1264px, about 150 characters a line, directly under a
   page built on 68ch columns. */
.msa-appfield__papersbody{ margin-block-start:1.25rem; }
.msa-appfield__papersbody > p{ max-width:var(--measure); }
.msa-appfield--papers > h2{ margin-block-end:0; }

/* ==========================================================================================
   THE APPLICATION SPREAD — /solutions/raman-for-forensic-science/ and its three siblings
   ==========================================================================================

   WHAT THESE PAGES ARE. Each one answers a buyer's two questions: can this instrument measure
   my sample, and which instrument is it. Four pages, one shape, so the answer looks the same
   wherever they land.

   WHAT WAS THERE, measured at 1280px on /solutions/raman-for-forensic-science/ before this:

     - an h3 under the h1 paraphrasing it, so the page announced itself twice at two sizes;
     - a first paragraph repeating the masthead standfirst almost word for word;
     - a left column holding a 260x188 photograph, a button and a 217x149 captioned figure with
       910px of empty white beneath them — the button floating level with nothing;
     - six images across three pages at six sizes: 260x188, 217x149, 255x208, 218x136, 212x170,
       267x140. Six sizes for pictures doing two jobs;
     - the product link — the only link on the page that sells anything — as the SEVENTH item of
       a seven-item list, drawn identically to "Units Used in Spectroscopy";
     - and on /solutions/microscope-photometer-applications/, four paragraphs that were links on
       the client's own page and arrived as dead text, above 900px of nothing.

   WHAT THIS IS INSTEAD, in the order a buyer needs it:

     1. .msa-spread        prose at the reading measure, with the evidence beside it
     2. .msa-spread__ev    THE PAIR: the sample, and the spectrum it returns. Two plates, one
                           width, one height. This pairing is the instrument's whole promise and
                           it is the one place this page spends any boldness.
     3. .msa-spread__kit   the instrument that does this work, named once, with its call to
                           action. The page stops explaining here and starts selling.
     4. .msa-spread__more  the background reading, demoted to what it is.

   ONE PLATE SIZE, NOT SIX. The plate is the answer .msa-apptech__plate already gave on the
   applications page: a fixed box on --paper with object-fit:contain, so a micrograph is never
   cropped, never enlarged past its own pixels, and never a different size from the one beside
   it. These are measurements. A photomicrograph stretched to fill a box is a different
   measurement, and a spectrum stretched is a false one. */

.msa-prose .msa-spread{
  display:grid;
  gap:2.5rem;
  margin-block:2.5rem;
}
@media (min-width:900px){
  /* PROSE FIRST, EVIDENCE SECOND. The imported pages put the pictures on the left and the
     argument on the right, which asks a reader to look at a photograph before being told what
     it is of. minmax(0,…) on both tracks because a grid track sized by content is what let an
     image's intrinsic width push a column wider than its share. */
  .msa-prose .msa-spread{
    /* THE PROSE TRACK IS CAPPED AT THE READING MEASURE, not at a fraction. Capping the
       PARAGRAPHS instead left the track full width with 280px of nothing between the last word
       and the evidence — the text ended in the middle of its own column. With the cap on the
       track and space-between, both columns reach the page's edges and the slack becomes the
       gutter it was always meant to be. */
    grid-template-columns:minmax(0,var(--measure)) minmax(0,21rem);
    justify-content:space-between;
    gap:3.5rem;
    align-items:start;
  }
}
.msa-spread__main > :first-child{ margin-block-start:0; }
.msa-spread__main > :last-child{ margin-block-end:0; }

/* The section heading. h2 rather than the h3 these pages carried: the page's own title is the
   h1 above it, and a document that goes h1 -> h3 has skipped a level for no reason a reader
   can see. */
.msa-prose .msa-spread__main > h2{
  margin-block:0 1.1rem;
  font-size:clamp(1.375rem,1.15rem + .85vw,1.75rem);
  line-height:1.2;
  letter-spacing:-0.015em;
}
/* THE BOLD LEAD-IN WAS A HEADING WEARING A PARAGRAPH'S CLOTHES. "Drugs:", "Explosives:",
   "Trace Evidence:", "Silicon Crystal Form:", "Strain in Silicon Structures:" each opened a
   paragraph in <strong>, which is how a heading looks to a person and is invisible to everything
   else — a screen reader's heading list, a search engine's outline, this site's own in-page
   navigation. They are h3s now and they are set like h3s. */
.msa-prose .msa-spread__main > h3{
  margin-block:1.85rem .4rem;
  font-size:1.0625rem;
  font-weight:700;
  line-height:1.35;
  letter-spacing:-0.005em;
  padding-inline-start:1.05rem;
  position:relative;
}
.msa-prose .msa-spread__main > h3::before{
  /* The aperture, used small, the same square this site draws wherever it says "this is the
     subject" — on an eyebrow, on a card link, beside a list item. */
  content:"";
  position:absolute;
  inset-inline-start:0;
  inset-block-start:.48em;
  inline-size:8px;
  block-size:8px;
  background:var(--red);
}
/* A HEADING AND THE PARAGRAPH IT HEADS ARE ONE UNIT. The h3's bottom margin and the p's top
   margin both applied, so "Drugs" floated 20px above the sentence it introduces and read as
   closer to the paragraph above it than to its own. */
.msa-prose .msa-spread__main > h3 + p{ margin-block-start:0; }

/* ---- the evidence pair ------------------------------------------------------------------ */

.msa-spread__evidence{
  display:grid;
  gap:2rem;
  min-width:0;
}
.msa-prose .msa-spread__ev{
  margin:0;
  display:grid;
  gap:.55rem;
}
/* WHICH OF THE TWO THIS IS. The captions say what the particular sample or spectrum shows;
   this says which half of the pair it is, which is the part that makes the two read as one
   statement rather than as two loose pictures. Martian Mono because that is the face this site
   sets every label in. */
.msa-spread__evlabel{
  margin:0;
  font-family:"Martian Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.6875rem;
  font-weight:600;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--dim-ink);
}
.msa-spread__plate{
  display:flex;
  align-items:center;
  justify-content:center;
  block-size:12rem;
  padding:.6rem;
  background:var(--paper);
}
.msa-spread__plate img{
  inline-size:auto;
  max-inline-size:100%;
  block-size:100%;
  max-block-size:100%;
  object-fit:contain;
}
/* LEFT, UNDER THE PLATE. `.msa-prose figcaption{text-align:center}` is right for the figures
   it was written for — a captioned figure that hugs its own image, where the caption centres
   under the picture. These plates are full-column, so a centred caption is a ragged block in a
   left-aligned column beside left-aligned prose. */
.msa-prose .msa-spread__ev figcaption{
  margin:0;
  font-size:.8125rem;
  line-height:1.5;
  color:var(--dim-ink);
  max-inline-size:none;
  text-align:start;
}

/* ---- the instrument ---------------------------------------------------------------------- */

/* THE PAGE TURNS HERE. Everything above explains; this block answers "so which one do I buy".
   --paper rather than a colour: the page already ends in the full-width red band, and a second
   strong field 400px above it would make the reader choose between two shouts.

   THE PRODUCT LINK IS NOT BACKGROUND READING. On all three Raman pages it was the last item of
   a seven-item bullet list, set in the same size and the same underline as "Units Used in
   Spectroscopy". It is the only link on the page that leads to something for sale. */
.msa-spread__kit{
  margin-block:var(--gap-section) 0;
  padding:2rem clamp(1.25rem,3vw,2.25rem);
  background:var(--paper);
}
.msa-prose .msa-spread__kit > h2{
  margin:0;
  font-family:"Martian Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.6875rem;
  font-weight:600;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--dim-ink);
}
.msa-spread__kitname{
  margin:.75rem 0 0;
  font-family:"Lexend",system-ui,sans-serif;
  font-size:clamp(1.25rem,1.05rem + .8vw,1.625rem);
  font-weight:600;
  line-height:1.2;
  letter-spacing:-0.015em;
}
.msa-prose .msa-spread__kitname a{
  /* Its own link treatment, which also takes it out of the shared 24px-hit-box rule the theme
     applies to a standalone link in a paragraph — that rule would make this one an inline-block
     with a 24px minimum, which is smaller than the line it already occupies. */
  color:var(--stage);
  /* text-decoration, NOT a box-shadow. An inline box-shadow paints once per element, so on a
     phone where "CRAIC Apollo(tm) Raman Spectrometer" wraps to two lines the rule appeared
     under the second line only and the first read as unlinked. A text decoration follows every
     line box there is. */
  text-decoration:underline;
  text-decoration-color:var(--red);
  text-decoration-thickness:2px;
  text-underline-offset:5px;
}
.msa-prose .msa-spread__kitname a:hover{ color:var(--red); }
.msa-prose .msa-spread__kitname a:focus-visible{
  outline:3px solid var(--stage);
  outline-offset:3px;
}
.msa-prose .msa-spread__kitline{
  margin:.7rem 0 0;
  max-width:var(--measure);
  color:var(--dim-ink);
}
/* Where a page's work is done by a family rather than by one instrument. Same row of names the
   applications page uses for the same job, so a reader who has seen one has seen both. */
.msa-spread__kitrun{
  display:flex;
  flex-wrap:wrap;
  gap:.15rem 1.5rem;
  margin:.9rem 0 0;
  padding:0;
  list-style:none;
}
.msa-spread__kitrun > li::marker{ content:""; }
.msa-prose .msa-spread__kitrun a{
  display:inline-flex;
  align-items:center;
  min-block-size:1.75rem;
  font-size:1.0625rem;
  font-weight:600;
  color:var(--stage);
  text-decoration:none;
  box-shadow:inset 0 -1px 0 0 var(--line-ink);
}
.msa-prose .msa-spread__kitrun a:hover{ color:var(--red); box-shadow:inset 0 -1px 0 0 var(--red); }
.msa-prose .msa-spread__kit .msa-cta{ margin-block-start:1.5rem; }

/* ---- the background reading -------------------------------------------------------------- */

/* Six links that explain the science, which is what they are: worth keeping, worth finding, not
   worth the same weight as the instrument. Two columns so six items are two short runs rather
   than one long ladder, and at one size — the imported list drew them at the prose size, which
   made a page of six equal-looking links with the product hidden among them. */
.msa-spread__more{ margin-block:var(--gap-section) 0; }
.msa-prose .msa-spread__more > h2{
  margin:0 0 1rem;
  font-family:"Martian Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.6875rem;
  font-weight:600;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--dim-ink);
}
.msa-prose ul.msa-spread__reading{
  margin:0;
  padding:0;
  list-style:none;
  display:grid;
  gap:.15rem 2.5rem;
}
@media (min-width:640px){
  .msa-prose ul.msa-spread__reading{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
.msa-prose ul.msa-spread__reading > li::marker{ content:""; }
.msa-prose ul.msa-spread__reading a{
  display:flex;
  align-items:center;
  min-block-size:2.25rem;
  font-size:1rem;
  line-height:1.4;
  color:var(--dim-ink);
  text-decoration:none;
  border-block-end:1px solid var(--line-ink);
}
.msa-prose ul.msa-spread__reading a:hover{ color:var(--red); border-block-end-color:var(--red); }
.msa-prose ul.msa-spread__reading a:focus-visible{
  outline:3px solid var(--stage);
  outline-offset:3px;
}

/* ---- the list variant: /solutions/microscope-photometer-applications/ --------------------- */

/* TWENTY-FIVE MEASUREMENTS IN FOUR FIELDS, and they arrived crammed into the right-hand half of
   a two-column grid while the left half held four dead sentences and 900px of white. The
   applications ARE this page; they get the full width and one card each field. Two columns, not
   four: the fields hold 11, 8, 3 and 3 items, and four equal columns beside an 11-item list is
   three columns of air. */
.msa-spread__groups{
  display:grid;
  gap:2.5rem;
  margin-block-start:2.5rem;
}
@media (min-width:760px){
  .msa-spread__groups{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:3rem 3rem; }
}
.msa-spread__group{ min-width:0; }
.msa-prose .msa-spread__group > h3{
  margin:1rem 0 .75rem;
  font-size:1.0625rem;
  font-weight:700;
  line-height:1.3;
  letter-spacing:-0.005em;
}
/* The same plate as the evidence pair, shorter: here the picture is one sample standing for a
   field, not half of a paired statement. */
/* THE PLATE HUGS THE PICTURE. At full card width a 4:3 micrograph 152px tall sat in a 366px
   band with 200px of grey beside it — the ground read as the subject. fit-content makes the
   plate the picture's own size, and because all four of these micrographs are 4:3 a single
   height makes four identical plates. */
.msa-prose .msa-spread__group figure{
  margin:0;
  display:flex;
  align-items:center;
  justify-content:center;
  inline-size:fit-content;
  max-inline-size:100%;
  block-size:11.5rem;
  padding:.6rem;
  background:var(--paper);
}
.msa-prose .msa-spread__group figure img{
  inline-size:auto;
  max-inline-size:100%;
  block-size:100%;
  max-block-size:100%;
  object-fit:contain;
}
/* Eleven items in a 640px card is a ribbon. Two text columns make it a block the eye can take
   in, and column-fill:balance keeps the two halves level rather than filling the first. */
.msa-prose .msa-spread__group > ul{
  margin:0;
  padding-inline-start:1.15rem;
  font-size:1rem;
  line-height:1.5;
}
.msa-prose .msa-spread__group > ul li{ margin-block-end:.4rem; break-inside:avoid; }
@media (min-width:520px){
  .msa-prose .msa-spread__group[data-n="long"] > ul{
    columns:2;
    column-gap:2rem;
    column-fill:balance;
  }
}

/* ==========================================================================================
   THE FLEET — /products/
   ==========================================================================================

   WHAT WAS THERE. template-index.php renders a page's children as cards, and a card's line of
   description is the child page's own post_excerpt. On /products/ that excerpt was empty on 13
   of 13, so the page a buyer lands on to choose an instrument was THIRTEEN TITLE-ONLY TILES —
   no photograph, though eleven of the thirteen pages carry a featured image and the client
   supplied studio photography of every instrument; no sentence; and no order, with "Hardware
   Accessories" the same size and weight as the 2030PV PRO.

   WHAT THIS IS. Eight instruments as a formation — one plate size, one photograph each, in the
   client's own current photography where the octagonal head's colour IS the product line — and
   a chooser above them that answers the only question a first-time visitor has: which of these
   measures what I measure.

   THE CHOOSER IS NOT A SCRIPT. It is eight radio inputs and `:has()`. The browser owns the
   state, the keyboard behaviour and the focus ring; there is nothing to initialise, nothing to
   lose on a slow connection, and nothing that breaks with scripts off. Selecting a field dims
   the instruments that do not serve it — dims, never hides, because the fleet is the point of
   the page and a grid that reflows under the pointer is a grid nobody trusts.

   AND THE FILTER IS NEVER THE ONLY COPY OF THE ANSWER. Every card names its fields in words,
   whatever the chooser is set to, so a reader with no CSS, or one using a screen reader — for
   whom a stylesheet dimming things announces nothing — has each instrument's answer in text.

   ONE RULE PER FIELD KEY, and the keys are applications-fields.json's, the same table
   /solutions/applications/ is built from. import/build-fleet.py refuses to write the page if a
   key here has no rule below: a chip that dims nothing looks live and does nothing, and no gate
   can see that — the markup is valid, the CSS is valid, only the pairing is wrong. */

.msa-prose .msa-fleet{ margin-block:2.25rem 0; }

/* ---- the chooser ------------------------------------------------------------------------- */

.msa-fleet__chooser{
  margin:0 0 2.5rem;
  padding:0;
  border:0;
}
.msa-fleet__chooser > legend{
  padding:0;
  margin-block-end:.9rem;
  font-family:"Lexend",system-ui,sans-serif;
  font-size:1.0625rem;
  font-weight:600;
  color:var(--stage);
}
.msa-fleet__chips{ display:flex; flex-wrap:wrap; gap:.5rem; }
/* The input carries the state and the focus; the label carries the paint. Not display:none —
   a hidden input is not focusable, and the keyboard would lose the control entirely. */
.msa-fleet__radio{
  position:absolute;
  inline-size:1px; block-size:1px;
  margin:0;
  opacity:0;
  pointer-events:none;
}
.msa-fleet__chip{
  display:inline-flex;
  align-items:center;
  gap:.55rem;
  padding:.5rem .85rem;
  border:1px solid #D6D8DC;
  font-size:1rem;
  font-weight:600;
  line-height:1.3;
  color:var(--stage);
  cursor:pointer;
  transition:border-color 140ms ease, background-color 140ms ease, color 140ms ease;
}
.msa-fleet__chip::before{
  /* The aperture, the mark this site draws wherever it says "this is the subject". */
  content:"";
  inline-size:8px; block-size:8px;
  flex:0 0 8px;
  background:var(--red);
}
.msa-fleet__chip:hover{ border-color:var(--stage); }
.msa-fleet__count{
  font-family:"Martian Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.75rem;
  font-weight:600;
  color:var(--dim-ink);
}
.msa-fleet__radio:checked + .msa-fleet__chip{
  background:var(--stage);
  border-color:var(--stage);
  color:var(--white);
}
.msa-fleet__radio:checked + .msa-fleet__chip .msa-fleet__count{ color:var(--dim); }
.msa-fleet__radio:focus-visible + .msa-fleet__chip{
  outline:3px solid var(--stage);
  outline-offset:2px;
}

/* ---- the fleet --------------------------------------------------------------------------- */

/* TWO ACROSS EVEN ON A PHONE. One column at 390px gave every instrument a 390x542 photograph
   and made the page 8,400px — twenty-one screens to see eight instruments, on the page whose
   job is showing the range. Two columns is a contact sheet: the whole fleet in about three
   screens, which is what a reader came for. The photographs are still 160px wide, which is
   more than enough to tell a 2030PV from a GeoImage — the head is the shape and the colour. */
.msa-prose ul.msa-fleet__grid{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:2.25rem 1.25rem;
  margin:0;
  padding:0;
  list-style:none;
}
.msa-prose ul.msa-fleet__grid > li::marker{ content:""; }
@media (min-width:620px){
  .msa-prose ul.msa-fleet__grid{ gap:2.25rem 1.5rem; }
}
@media (min-width:1060px){
  /* FOUR ACROSS, so eight instruments are two full rows and not a row of three with a widow.
     The count is the reason for the number. */
  .msa-prose ul.msa-fleet__grid{ grid-template-columns:repeat(4,minmax(0,1fr)); }
  .msa-prose a.msa-fleet__name{ font-size:1.0625rem; }
}
/* Subgrid needs the row structure at every width the grid has more than one column in, which
   is now every width. Without the media query around it the single-column stack on a very
   narrow screen would still work — a card is its own four rows there — so there is no floor. */
.msa-fleet__item{
  display:grid;
  /* minmax(0,1fr), NOT the implicit auto track. A grid container with no
     grid-template-columns gets ONE AUTO column sized to its widest child's min-content, and
     `min-width:0` on the item does nothing about the track inside it: measured at 390px, a
     165px card whose every child rendered 187px wide, because "Microspectrophotometer" is one
     22-character word and an auto track will not break it. minmax(0,…) lets the track shrink,
     and overflow-wrap lets the word break when it has to — the same pair the shell already
     applies to imported bodies for emails and part numbers. */
  grid-template-columns:minmax(0,1fr);
  overflow-wrap:break-word;
  gap:.7rem;
  align-content:start;
  min-width:0;
  transition:opacity 220ms ease, filter 220ms ease;
}
/* ONE BASELINE PER ROW, NOT PER CARD. The blurb is the client's own sentence and they run 9 to
   26 words, so four cards set their names, sentences and field runs at four different heights
   and the row read as four unrelated columns. Subgrid makes the four rows of a card the four
   rows of the grid: every name starts on one line, every field run starts on one line. The
   same technique the applications page's technique columns already use.

   The last track is 1fr so the field run sits at the FOOT of the card, which is where a reader
   goes looking for "is my sample here" once they have read the name. */
@supports (grid-template-rows:subgrid){
  .msa-prose ul.msa-fleet__grid{ grid-template-rows:auto auto auto 1fr; }
  .msa-fleet__item{ grid-row:span 4; grid-template-rows:subgrid; }
}
/* EVERY PHOTOGRAPH IS 540x750, normalised when the client's library was processed, so the
   plate can be that exact ratio and the picture fills it edge to edge. No letterboxing, no
   crop, and eight cards whose pictures are the same size to the pixel — which is what makes a
   grid of instruments read as one range rather than as eight photographs. */
.msa-fleet__plate{
  display:block;
  /* A COLOUR, because this is an <a> now and `ul[class] a` takes it out of the prose-link rule
     that would have given it one. Measured by S-CONTROL-74 the moment the plate became a link:
     16 renders of rgb(0,0,238), the browser's default, on a link whose only child is an image
     — invisible, and exactly the defect the gate names. The same miss as the coverage cells,
     made again in the same week: a class added to that :not() list owes itself a colour. */
  color:var(--stage);
  text-decoration:none;
  /* inline-size:100%, AND align-self:start. Without them the plate is a grid item in a subgrid
     row whose height the parent decides, and `aspect-ratio` then works backwards: the row's
     height sets the plate's WIDTH, which came out 187px in a 165px column — a 2px page overflow
     at 390px from a box 22px wider than the track it sits in. Pinning the width makes the
     aspect ratio do the one job it is here for, which is deciding the height. */
  inline-size:100%;
  align-self:start;
  aspect-ratio:540 / 750;
  background:var(--paper);
}
.msa-fleet__plate img{
  inline-size:100%;
  block-size:100%;
  object-fit:contain;
  display:block;
}
.msa-prose a.msa-fleet__name{
  font-family:"Lexend",system-ui,sans-serif;
  /* Smaller in a 160px column, where "2030PV PRO Microspectrophotometer" at 17px is four lines. */
  font-size:.9375rem;
  font-weight:600;
  line-height:1.3;
  letter-spacing:-0.01em;
  color:var(--stage);
  text-decoration:underline;
  text-decoration-color:transparent;
  text-decoration-thickness:2px;
  text-underline-offset:4px;
  transition:text-decoration-color 140ms ease, color 140ms ease;
}
.msa-prose a.msa-fleet__name:hover{ color:var(--red); text-decoration-color:var(--red); }
.msa-prose a.msa-fleet__name:focus-visible{ outline:3px solid var(--stage); outline-offset:3px; }
.msa-fleet__line{
  font-size:.9375rem;
  line-height:1.5;
  color:var(--dim-ink);
}
/* The fields this instrument serves, as a run rather than a sentence: four of the seven names
   carry a comma of their own, so a comma cannot be the separator. A hairline can. */
.msa-fleet__fields{
  display:flex;
  flex-wrap:wrap;
  align-content:end;
  gap:.15rem .6rem;
  padding-block-start:.6rem;
  border-block-start:1px solid var(--line-ink);
  font-size:.6875rem;
  line-height:1.55;
  color:var(--dim-ink);
}
/* NO SEPARATOR BETWEEN THEM. A hairline between names that wrap lands mid-line as often as it
   lands between two names, which reads as a rule through the text rather than as a divider. The
   row above the run does the dividing, and a wider gap does the rest. */

/* ---- what the chooser does ---------------------------------------------------------------
   THE RULES ARE NOT HERE. The filter keys are the client's own section ids on
   /solutions/applications/ — "applications-forensics", "applications-semis" — so a static
   stylesheet would have to repeat them, and the first version of this did, with the keys of a
   JSON file that turned out not to be those ids at all: seven chips, seven rules, and not one
   match, so every chip looked live and did nothing. inc/fleet.php emits the rules from the same
   loop that emits the chips, through wp_add_inline_style on this stylesheet's own handle, and
   only on a page that carries the shortcode. One loop, so they cannot disagree.

   GREYSCALE IS NOT DECORATION THERE. The octagonal head's colour is the product line — red,
   green, black, blue — so draining it says what the dimming says, in the one visual language
   this range already owns. The card stays readable, focusable and a link: it is still an
   instrument for sale, just not the answer to this question. */
@media (prefers-reduced-motion:reduce){
  .msa-fleet__item,
  .msa-fleet__chip{ transition:none; }
}

/* ---- what you add to one ------------------------------------------------------------------ */

/* A consumable is not a peer of a microspectrophotometer, and neither is a software licence.
   Five of the thirteen pages under /products/ are things you add to an instrument you already
   chose — including Circular Dichroism microspectroscopy, which the client's own page calls a
   capability. They belong after the fleet and at a quieter weight. */
.msa-fleet__addons{
  margin-block-start:var(--gap-section);
  padding-block-start:1.75rem;
  border-block-start:1px solid var(--line-ink);
}
.msa-prose .msa-fleet__addons > h2{
  margin:0 0 1rem;
  font-family:"Martian Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.6875rem;
  font-weight:600;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--dim-ink);
}
.msa-prose ul.msa-fleet__addonlist{
  display:flex;
  flex-wrap:wrap;
  gap:.3rem 2rem;
  margin:0;
  padding:0;
  list-style:none;
}
.msa-prose ul.msa-fleet__addonlist > li::marker{ content:""; }
.msa-prose ul.msa-fleet__addonlist a{
  display:inline-flex;
  align-items:center;
  min-block-size:2.25rem;
  font-size:1rem;
  color:var(--stage);
  text-decoration:none;
  box-shadow:inset 0 -1px 0 0 var(--line-ink);
}
.msa-prose ul.msa-fleet__addonlist a:hover{ color:var(--red); box-shadow:inset 0 -1px 0 0 var(--red); }
.msa-prose ul.msa-fleet__addonlist a:focus-visible{ outline:3px solid var(--stage); outline-offset:3px; }

/* ---- the declaration, read downwards ------------------------------------------------------
   A page that others name as their breadcrumb step closes with the list of them. Set like the
   fleet's add-on row rather than like a card grid: these are four destinations under a page
   that already carries twenty application panels, and a second grid would compete with the
   one above it. */
.msa-declared{
  margin-block-start:var(--gap-section);
  padding-block-start:1.75rem;
  border-block-start:1px solid var(--line-ink);
}
.msa-prose .msa-declared > h2{
  margin:0 0 1rem;
  font-family:"Martian Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.6875rem;
  font-weight:600;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--dim-ink);
}
.msa-prose ul.msa-declared__list{
  display:grid;
  gap:.25rem 2.5rem;
  margin:0;
  padding:0;
  list-style:none;
}
@media (min-width:760px){
  .msa-prose ul.msa-declared__list{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
.msa-prose ul.msa-declared__list > li::marker{ content:""; }
.msa-prose ul.msa-declared__list a{
  display:grid;
  gap:.2rem;
  padding-block:.7rem;
  border-block-end:1px solid var(--line-ink);
  text-decoration:none;
  color:var(--stage);
}
.msa-prose ul.msa-declared__list a:hover{ border-block-end-color:var(--red); }
.msa-prose ul.msa-declared__list a:hover .msa-declared__name{ color:var(--red); }
.msa-prose ul.msa-declared__list a:focus-visible{ outline:3px solid var(--stage); outline-offset:3px; }
.msa-declared__name{
  font-family:"Lexend",system-ui,sans-serif;
  font-size:1rem;
  font-weight:600;
  line-height:1.35;
}
.msa-declared__blurb{ font-size:.875rem; line-height:1.5; color:var(--dim-ink); }

/* ==========================================================================================
   ABOUT CRAIC — /about-craic/
   ==========================================================================================

   WHAT WAS THERE: ONE CHARACTER. The body was empty, so the page rendered a masthead, nothing,
   and six title-only cards — on the page a buyer opens once they have chosen an instrument, to
   answer the question no product page answers: is this company safe to spend six figures with,
   on a machine we will own for a decade.

   THE PAGE SPENDS ITS BOLDNESS ONCE, on one sentence: that the standards a microspectrophotometer
   is checked against are made in the same building as the instrument. That is the strongest
   thing this company can say, it is true, and it was buried on a product page. Everything around
   it is quiet on purpose — left-aligned, at the reading measure, hairlines, no cards, no
   shadows, and no row of big numbers, which is the default About-page device and the one this
   site has already been burned by (S-CLAIM-22 exists because a typed counter went stale on the
   home page). The three figures here are counted at render and each is a link to what it counted.

   NOTHING NEW IN THE PALETTE OR THE TYPE. This is a section of a site, not a standalone page. */

/* A TIGHTER RHYTHM THAN THE SITE'S. --gap-section is 4.5rem and it is right for a page whose
   sections are long; this page's are short, and five of them at 72px apart was 360px of nothing
   holding apart blocks that belong together. Declared as a variable so every rule below reads
   from one number. */
.msa-about{ --about-gap:clamp(2.5rem,4vw,3.25rem); }
.msa-prose .msa-about{ margin-block:0; }
.msa-about__block{ margin-block-start:var(--about-gap); }

/* TWO THINGS A READER IS WEIGHING AGAINST EACH OTHER, SIDE BY SIDE. What this company makes
   against why its numbers can be trusted; what happens after you buy against what you can go
   and check. Stacked, each of those ran the full width for content that did not need it — the
   page was long without being full. The pairing also does work the stacking could not: the
   claim sits beside the capability it is about. */
.msa-about__pair{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:var(--about-gap);
  margin-block-start:var(--about-gap);
}
@media (min-width:860px){
  /* ROW GAP AND COLUMN GAP ARE NOT THE SAME DISTANCE HERE. The columns are two separate
     arguments and want air between them; the band's last row is the twelve techniques, which
     belong to the heading in the column above and were floating a full 3.5rem below both of
     them, reading as a row of their own. The list carries its own 1.25rem margin, so the row
     gap goes to zero and that margin is the whole distance. */
  .msa-about__pair{
    grid-template-columns:repeat(2,minmax(0,1fr));
    column-gap:clamp(2rem,4vw,3.5rem);
    row-gap:0;
  }
  /* The second band's halves are a panel and a plain block, and a panel that starts where the
     block starts reads as the same object split. The panel carries its own padding, so it is
     pulled back to the grid line the copy beside it sits on. */
  .msa-about__pair > .msa-about__block{ margin-block-start:0; }
}
.msa-about__pair > .msa-about__block{ margin-block-start:0; }

/* A label, in the mono face this site sets every label in. Lower case, not tracked-out capitals:
   the four on this page sit above four headings and four tracked-out capital lines down one page
   reads as chrome rather than as structure. */
.msa-about__label{
  margin:0 0 .55rem;
  font-family:"Martian Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.6875rem;
  font-weight:600;
  letter-spacing:.06em;
  color:var(--dim-ink);
}
.msa-prose .msa-about h2{
  margin:0 0 1rem;
  max-width:22ch;
  font-size:clamp(1.5rem,1.15rem + 1.4vw,2.125rem);
  line-height:1.15;
  letter-spacing:-0.02em;
}
.msa-prose .msa-about__body{ margin:0; max-width:var(--measure); }
.msa-about__go{ margin:1.35rem 0 0; }
.msa-prose .msa-about__go a{
  display:inline-flex;
  align-items:center;
  gap:.6rem;
  min-block-size:1.75rem;
  font-size:1.0625rem;
  font-weight:600;
  color:var(--stage);
  text-decoration:none;
  box-shadow:inset 0 -2px 0 0 var(--red);
}
.msa-prose .msa-about__go a::before{
  content:"";
  inline-size:8px; block-size:8px;
  flex:0 0 8px;
  background:var(--red);
}
.msa-prose .msa-about__go a:hover{ color:var(--red); }
.msa-prose .msa-about__go a:focus-visible{ outline:3px solid var(--stage); outline-offset:3px; }

/* ---- the opening --------------------------------------------------------------------------
   THE PHOTOGRAPH IS NOT IN THE MASTHEAD, and that is deliberate. inc/pagehead.php sets out at
   length why a sub-page masthead carries no picture — one hero image cannot be about seven
   subjects, and the same coal photograph sat above "Fiber and Hair Analysis" for months. This
   page has a picture that IS about it, so it opens the body instead of displacing the furniture
   that decision produced.

   It is the client's own photograph: their instrument, their head, a wafer on an Axio stage, and
   the blue cast of the UV source. It is the one picture on this site that shows what these
   instruments are FOR rather than what they look like, which is why the mission sits on it. */
.msa-about__open{
  position:relative;
  margin-block:1.5rem 0;
  background:var(--stage);
  color:var(--white);
  isolation:isolate;
}
/* `.msa-prose .msa-about__photo`, NOT `.msa-about__photo`. The shell sets `.msa-prose img
   {height:auto}` — one class and one element, which outranks a bare class — so the block-size
   below was ignored and the photograph rendered at its natural ratio: measured, 789px where
   this asks for 352, which is the single biggest block on the page and most of why it was
   long. A media query changes nothing about specificity and neither does writing the rule
   later; the selector has to win. */
.msa-prose .msa-about__photo{
  display:block;
  inline-size:100%;
  /* 50% 22%, NOT 50% 38%. The frame is 950x633 and this box is about 16:7, so something is
     cropped whatever happens; at 38% the crop took the top of the optical head and left half
     the frame to the operator's back — it cut the one thing in the picture that identifies
     whose instrument it is. Anchoring near the top keeps the head, the CRAIC mark and the
     eyepiece, and loses floor. */
  block-size:clamp(13rem,30vw,22rem);
  object-fit:cover;
  object-position:50% 24%;
  opacity:.85;
}
.msa-about__open::after{
  content:"";
  position:absolute;
  inset:0;
  /* SOLID BEFORE THE TEXT STARTS, not fading through it. The copy begins 6.5rem above the
     frame's bottom edge, so the gradient reaches full --stage by 70% and the heading sits on
     one colour rather than on a photograph that happens to be dark there. A scrim that is
     still translucent under white type is a contrast failure waiting for a different
     photograph. */
  background:linear-gradient(180deg,
             rgba(13,14,16,0) 0%, rgba(13,14,16,.12) 38%,
             rgba(13,14,16,.72) 58%, var(--stage) 70%, var(--stage) 100%);
  pointer-events:none;
}
.msa-about__opencopy{
  position:relative;
  z-index:1;
  margin-block-start:-6.5rem;
  padding:0 clamp(1.25rem,3vw,2.25rem) 2.25rem;
}
.msa-prose .msa-about__mission{
  margin:0 0 .9rem;
  max-width:18ch;
  font-size:clamp(1.75rem,1.2rem + 2.2vw,2.75rem);
  line-height:1.08;
  letter-spacing:-0.025em;
  color:var(--white);
}
.msa-prose .msa-about__opencopy p{
  margin:0;
  max-width:var(--measure);
  color:var(--dim);
}
@media (max-width:600px){
  /* At this width the copy is four lines and the scrim would have to eat most of the frame to
     hold it. The picture keeps its own space and the words sit below it — which is the same
     statement, made in two blocks instead of one. */
  .msa-about__photo{ opacity:1; }
  .msa-about__open::after{ display:none; }
  .msa-about__opencopy{ margin-block-start:0; padding-block:1.75rem 2rem; }
}

/* ---- the one loud thing --------------------------------------------------------------------
   Set at the mission's scale rather than a heading's, on the page ground rather than in a panel:
   a box around it would make it a callout, and it is not an aside — it is the argument. The red
   rule on the leading edge is the only ornament, and it is the aperture, drawn long. */
/* COMPRESSED, NOT DEMOTED. This had a block of its own at the page's largest type — the loudest
   thing on a page that then had nothing else to say at that volume. It is the same sentence at
   the heading scale now, in the half of the band beside what it is about, and it keeps the red
   rule on its leading edge, which is what marks it as the page's one claim. The page is about
   500px shorter for the change and the sentence reads better for having its subject next to it. */
.msa-about__credential{
  padding-inline-start:clamp(1rem,2vw,1.5rem);
  border-inline-start:3px solid var(--red);
}

/* ---- the twelve techniques -----------------------------------------------------------------
   A RUN, NOT A COLUMN OF BULLETS. Twelve bulleted lines reads as a specification sheet; this is
   a range, and a range reads as a field of names. Each carries the aperture square, which is the
   mark this site draws wherever it says "this is a subject". */
/* TWO COLUMNS IN HALF A BAND. As a wrapping row this was four lines across the full width and
   ten down a 470px column — the same twelve names, 275px instead of 110. Columns keep it a
   field of names rather than a ladder. */
.msa-prose ul.msa-about__run{
  display:flex;
  flex-wrap:wrap;
  gap:.3rem 1.5rem;
  grid-column:1 / -1;
  margin:1.25rem 0 0;
  padding:0;
  list-style:none;
  max-width:none;
}
.msa-prose ul.msa-about__run > li{
  margin:0;
  padding-inline-start:1rem;
  position:relative;
  font-size:1rem;
  line-height:1.6;
  color:var(--stage);
}
.msa-prose ul.msa-about__run > li::marker{ content:""; }
.msa-prose ul.msa-about__run > li::before{
  content:"";
  position:absolute;
  inset-inline-start:0;
  inset-block-start:.62em;
  inline-size:6px; block-size:6px;
  background:var(--red);
}

/* ---- after you buy -------------------------------------------------------------------------
   THE ONLY PANEL ON THE PAGE, because this is the only section that is about a different period
   of time: everything above is what you are buying, this is the ten years after. --paper rather
   than a colour — the page closes on the red band and a second strong field would compete. */
.msa-about__service{
  padding:clamp(1.5rem,2.5vw,2rem) clamp(1.25rem,2.5vw,1.75rem);
  background:var(--paper);
}
.msa-prose ul.msa-about__services{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:.15rem 2.5rem;
  margin:1.5rem 0 0;
  padding:0;
  list-style:none;
  max-width:none;
}
@media (min-width:560px){
  .msa-prose ul.msa-about__services{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
/* NO FOUR-COLUMN STATE ANY MORE: this list lives in half a band now, and four columns of
   "Standards recertification" in 560px is four columns of wrapped words. */
.msa-prose ul.msa-about__services > li{
  margin:0;
  padding-block:.55rem;
  border-block-end:1px solid var(--line-ink);
  font-size:1rem;
  line-height:1.45;
}
.msa-prose ul.msa-about__services > li::marker{ content:""; }

/* ---- counted, never typed ------------------------------------------------------------------
   Three figures, each a LINK to the thing it counted. The number is large because the point is
   that it is checkable, not because a large number is impressive — which is why there is no
   fourth one for "20 years" or "500 clients": this site cannot be counted for either, and a
   figure nothing keeps true is the defect S-CLAIM-22 exists for. */
.msa-prose ul.msa-counts{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:0;
  margin:1.75rem 0 0;
  padding:0;
  list-style:none;
  max-width:none;
}
@media (min-width:700px) and (max-width:859px){
  .msa-prose ul.msa-counts{ grid-template-columns:repeat(3,minmax(0,1fr)); gap:0 2.5rem; }
}
/* Above 860 these sit in half a band beside the service panel, so they stack — three numbers
   across 560px would set each label to two words a line. */
.msa-prose ul.msa-counts > li::marker{ content:""; }
.msa-prose a.msa-count__link{
  display:grid;
  gap:.3rem;
  padding-block:1rem;
  border-block-start:2px solid var(--stage);
  text-decoration:none;
  color:var(--stage);
  transition:border-block-start-color 140ms ease, color 140ms ease;
}
.msa-prose a.msa-count__link:hover{ border-block-start-color:var(--red); color:var(--red); }
.msa-prose a.msa-count__link:focus-visible{ outline:3px solid var(--stage); outline-offset:3px; }
.msa-count__n{
  font-family:"Lexend",system-ui,sans-serif;
  font-size:clamp(2rem,1.4rem + 2.4vw,3rem);
  font-weight:600;
  line-height:1;
  letter-spacing:-0.03em;
  font-variant-numeric:tabular-nums;
}
.msa-count__label{ font-size:.9375rem; line-height:1.4; color:var(--dim-ink); }
@media (prefers-reduced-motion:reduce){
  .msa-prose a.msa-count__link{ transition:none; }
}

/* ---- the record ----------------------------------------------------------------------------
   WHAT A BUYER MEANS BY "HISTORY" is not a founding date and a paragraph about passion: it is
   whether this company is still building things, and has been for a while. The press-release
   index answers that to the day, and inc/about.php reads it rather than restating it.

   GROUPED BY YEAR, AND THE YEARS ARE THE ONLY NUMBERS. A numbered marker on a list is right
   when the content is a sequence; this one is, so it gets a chronological structure — but the
   sequence already has numbers of its own, and 01/02/03 beside 2026/2025/2024 would be a second
   set counting the same thing. The year prints once per group; the repeats are still in the
   markup for a screen reader reading one row at a time, and hidden from sight. */
.msa-prose ol.msa-record{
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  gap:0 1.25rem;
  margin:1.5rem 0 0;
  padding:0;
  list-style:none;
  max-width:none;
}
.msa-record__item{
  display:grid;
  grid-column:1 / -1;
  grid-template-columns:subgrid;
  align-items:baseline;
  padding-block:.6rem;
  border-block-start:1px solid var(--line-ink);
}
.msa-record__item:first-child{ border-block-start:0; padding-block-start:0; }
.msa-prose ol.msa-record > li::marker{ content:""; }
.msa-record__year{
  font-family:"Martian Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.8125rem;
  font-weight:600;
  color:var(--stage);
  font-variant-numeric:tabular-nums;
}
/* The repeat is present for anything reading a row on its own and invisible to the eye, which
   is what makes the column read as four years rather than as sixteen. Not display:none — that
   would take it out of the accessibility tree, which is the half that still needs it. */
.msa-record__year--same{ visibility:hidden; }
.msa-prose a.msa-record__title{
  display:block;
  font-size:.9375rem;
  line-height:1.45;
  color:var(--stage);
  text-decoration:none;
}
.msa-record__month{
  display:inline-block;
  min-inline-size:2.6rem;
  font-family:"Martian Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.6875rem;
  color:var(--dim-ink);
}
.msa-prose a.msa-record__title:hover{ color:var(--red); }
.msa-prose a.msa-record__title:focus-visible{ outline:3px solid var(--stage); outline-offset:3px; }
@supports not (grid-template-columns:subgrid){
  /* Subgrid is what lines the years up against the titles. Without it the row is a flex line,
     which loses the column but keeps every word. */
  .msa-record__item{ display:flex; gap:1.25rem; align-items:baseline; }
  .msa-record__year{ flex:0 0 3rem; }
}

/* ==========================================================================================
   THE "WHAT IS IT?" LIBRARY — /support/learn/ and its eleven articles
   ==========================================================================================

   WHAT WAS MEASURED across the eleven, before this. Eight of them opened at h4 directly under
   the h1 — a three-level skip on the pages a search engine reads first. Five opened on a
   heading that was really the standfirst. Three carried a heading restating their own h1. Six
   wrapped their body in div.scipg, a Joomla layout wrapper this site styles nothing from. And
   23 distinct images at 14 different sizes, from 204x534 to 471x100.

   The index showcasing them rendered ELEVEN TITLE-ONLY BARS: `.msa-cards` has no picture slot,
   and none of the eleven carried a featured image or an excerpt for it to show.

   Site-wide heading skips went 81 -> 71 when the ladder was repaired. */

/* ---- the article ---------------------------------------------------------------------------
   The standfirst these pages were setting as a heading, set as a standfirst. */
.msa-prose .msa-explainer__stand{
  margin:0 0 1.75rem;
  max-width:var(--measure);
  font-family:"Lexend",system-ui,sans-serif;
  font-size:clamp(1.125rem,1rem + .5vw,1.375rem);
  font-weight:500;
  line-height:1.35;
  letter-spacing:-0.01em;
  color:var(--stage);
}
.msa-prose .msa-explainer > h2{
  margin-block:2.5rem 1rem;
  font-size:clamp(1.25rem,1.1rem + .6vw,1.5rem);
  line-height:1.25;
  letter-spacing:-0.015em;
}
.msa-prose .msa-explainer > h2:first-of-type{ margin-block-start:0; }
/* ONE PLATE FOR EVERY FIGURE IN THE LIBRARY. These are optical-path diagrams, spectra and
   micrographs at fourteen different sizes, and a page that shows them at their stored pixel
   dimensions reads as a scrapbook. Contained, never cropped and never enlarged: a diagram
   stretched is a diagram that is wrong, and a spectrum stretched is a false one. */
/* THE PLATE HUGS THE PICTURE. Full width, it put a 263x180 spectrum in the middle of a 680px
   grey band — four hundred pixels of ground either side of the subject, on a page carrying three
   of them in a row. The plate exists to give pictures of different shapes one ground, not to
   give a small picture a large one. fit-content makes it the picture's own size; the caption
   sits under it at the reading measure, aligned with the prose. */
.msa-prose .msa-explainer figure{
  margin-block:1.75rem;
  padding:.75rem;
  background:var(--paper);
  display:grid;
  gap:.6rem;
  inline-size:fit-content;
  max-inline-size:100%;
  justify-items:start;
}
.msa-prose .msa-explainer figure img{
  inline-size:auto;
  max-inline-size:100%;
  block-size:auto;
  max-block-size:22rem;
  object-fit:contain;
  /* WHITE ON GREY IS A VISIBLE BOX. Half these pictures are diagrams and product shots already
     on a white ground, so the plate drew a rectangle round them. multiply leaves the plate's own
     tone untouched wherever the picture is white and darkens only the drawing — the same device
     the rail and the home page's instrument row use. On a dark photograph it is a 4% shift
     against --paper, which is nothing; there is no case here where it costs anything. */
  mix-blend-mode:multiply;
}
.msa-prose .msa-explainer figure figcaption{
  max-inline-size:min(var(--measure), 100%);
  text-align:start;
  justify-self:start;
}
/* TWO PICTURES THAT BELONG TOGETHER, SIDE BY SIDE AND NEITHER ENLARGED. Where a page shows a
   sample and what the instrument returned from it, the pair is the statement; stacked, the
   reader has to hold the first in their head. They share one plate and one height, and each
   keeps its own width — a Jablonski diagram is 204x534 and a micrograph 263x210, and forcing
   either to the other's width is how the right-hand one came out stretched and blurry.
   Never enlarged past its own pixels: that is a diagram made wrong, not a diagram made bigger. */
.msa-prose .msa-explainer figure.msa-explainer__pair{
  display:flex;
  flex-wrap:wrap;
  align-items:flex-end;
  gap:.75rem;
}
.msa-prose .msa-explainer figure.msa-explainer__pair img{
  max-block-size:16rem;
  inline-size:auto;
}
.msa-prose .msa-explainer figure.msa-explainer__pair figcaption{ flex:1 0 100%; }

/* ---- the library ---------------------------------------------------------------------------
   Three groups, because the section's own title is one question and a reader arriving is asking
   one of three versions of it: what is the instrument, what does it measure, what is happening
   to the light. import/learn-explainers.json says that is editorial and says why. */
.msa-explainers__group{ margin-block-start:var(--gap-section); }
.msa-explainers__group:first-child{ margin-block-start:1.5rem; }
.msa-prose .msa-explainers__group > h2{
  margin:0 0 .3rem;
  padding-block-end:.75rem;
  border-block-end:2px solid var(--stage);
  font-size:clamp(1.25rem,1.1rem + .6vw,1.5rem);
  line-height:1.25;
  letter-spacing:-0.015em;
}
.msa-prose .msa-explainers__stand{
  margin:.9rem 0 0;
  max-width:var(--measure);
  font-size:1rem;
  color:var(--dim-ink);
}
.msa-prose ul.msa-explainers__grid{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:2rem 1.25rem;
  margin:1.75rem 0 0;
  padding:0;
  list-style:none;
  max-width:none;
}
@media (min-width:760px){
  .msa-prose ul.msa-explainers__grid{ grid-template-columns:repeat(4,minmax(0,1fr)); gap:2.25rem 1.5rem; }
}
.msa-prose ul.msa-explainers__grid > li::marker{ content:""; }
.msa-explainers__item{
  display:grid;
  grid-template-columns:minmax(0,1fr);
  overflow-wrap:break-word;
  gap:.6rem;
  align-content:start;
  min-width:0;
}
@supports (grid-template-rows:subgrid){
  .msa-prose ul.msa-explainers__grid{ grid-template-rows:auto auto 1fr; }
  .msa-explainers__item{ grid-row:span 3; grid-template-rows:subgrid; }
}
/* 4:3, and every picture contained inside it. The library's images have no common shape — a
   Jablonski diagram is tall, an optical path is wide — so the PLATE is what they have in
   common, not the pictures. inline-size and align-self:start for the reason the fleet needed
   them: an aspect-ratio box in a subgrid row otherwise takes its width from the row's height. */
.msa-explainers__plate{
  display:flex;
  align-items:center;
  justify-content:center;
  inline-size:100%;
  align-self:start;
  aspect-ratio:4 / 3;
  padding:.5rem;
  background:var(--paper);
  color:var(--stage);
  text-decoration:none;
}
.msa-explainers__plate img{
  inline-size:auto;
  max-inline-size:100%;
  block-size:auto;
  max-block-size:100%;
  object-fit:contain;
}
.msa-prose a.msa-explainers__name{
  font-family:"Lexend",system-ui,sans-serif;
  font-size:.9375rem;
  font-weight:600;
  line-height:1.3;
  letter-spacing:-0.01em;
  color:var(--stage);
  text-decoration:underline;
  text-decoration-color:transparent;
  text-decoration-thickness:2px;
  text-underline-offset:4px;
  transition:text-decoration-color 140ms ease, color 140ms ease;
}
.msa-prose a.msa-explainers__name:hover{ color:var(--red); text-decoration-color:var(--red); }
.msa-prose a.msa-explainers__name:focus-visible{ outline:3px solid var(--stage); outline-offset:3px; }
.msa-explainers__line{ font-size:.875rem; line-height:1.45; color:var(--dim-ink); }
@media (prefers-reduced-motion:reduce){
  .msa-prose a.msa-explainers__name{ transition:none; }
}

/* The run of further reading, at the end of the page where it belongs. It was wherever the
   Joomla layout left it — on Film Thickness, between the first figure and the second, before a
   single sentence of the article had been read. It is navigation away from the page, so it goes
   after the page. */
.msa-prose ul.msa-explainer__more{
  margin-block-start:var(--gap-section);
  padding-block-start:1.5rem;
  padding-inline-start:0;
  border-block-start:1px solid var(--line-ink);
  list-style:none;
  display:grid;
  gap:.15rem 2.5rem;
}
@media (min-width:640px){
  .msa-prose ul.msa-explainer__more{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
.msa-prose ul.msa-explainer__more > li::marker{ content:""; }
.msa-prose ul.msa-explainer__more a{
  display:flex;
  align-items:center;
  min-block-size:2.25rem;
  font-size:1rem;
  line-height:1.4;
  color:var(--dim-ink);
  text-decoration:none;
  border-block-end:1px solid var(--line-ink);
}
.msa-prose ul.msa-explainer__more a:hover{ color:var(--red); border-block-end-color:var(--red); }
.msa-prose ul.msa-explainer__more a:focus-visible{ outline:3px solid var(--stage); outline-offset:3px; }

/* ---- the reading-list rhythm — /support/the-science/ ---------------------------------------
   NOT A COPY OF THE LIBRARY GRID. /support/learn/ holds eleven short answers to short questions
   and a gallery of four across suits browsing them. These eleven are technical reads of 3,000
   to 6,600 characters: a small square plate beside the title, two rows across, which is a
   reading list. Same card markup, one class more — a second set of markup for one object is how
   two things that should agree stop agreeing. */
.msa-prose .msa-explainers--rows ul.msa-explainers__grid{
  grid-template-columns:minmax(0,1fr);
  gap:0;
  margin-block-start:1.25rem;
}
@media (min-width:820px){
  .msa-prose .msa-explainers--rows ul.msa-explainers__grid{
    grid-template-columns:repeat(2,minmax(0,1fr));
    column-gap:2.5rem;
  }
}
.msa-explainers--rows .msa-explainers__item{
  grid-template-columns:auto minmax(0,1fr);
  grid-template-rows:auto auto;
  align-content:start;
  align-items:start;
  gap:.25rem 1rem;
  padding-block:1rem;
  border-block-start:1px solid var(--line-ink);
}
/* Subgrid is what lines the gallery's names up across a row; a list has no such row, so it is
   turned off here rather than left to span three tracks that no longer exist. */
@supports (grid-template-rows:subgrid){
  .msa-prose .msa-explainers--rows ul.msa-explainers__grid{ grid-template-rows:none; }
  .msa-explainers--rows .msa-explainers__item{ grid-row:auto; grid-template-rows:auto auto; }
}
.msa-explainers--rows .msa-explainers__plate{
  grid-row:span 2;
  inline-size:4.5rem;
  aspect-ratio:1;
  padding:.3rem;
}
.msa-explainers--rows .msa-explainers__plate img{ mix-blend-mode:multiply; }
.msa-prose .msa-explainers--rows a.msa-explainers__name{ font-size:1rem; }
.msa-explainers--rows .msa-explainers__line{ font-size:.875rem; }
/* A PAGE WITH NO PICTURE KEEPS THE COLUMN ANYWAY. Collapsing the row to one column put its
   title hard against the left edge while every row around it started 5.5rem in — one page
   without a picture broke the alignment of the whole list. The track stays; the space is simply
   empty, which reads as a row that has no picture rather than as a row that is out of line. */
.msa-explainers--rows .msa-explainers__item:not(:has(.msa-explainers__plate))::before{
  content:"";
  grid-row:span 2;
  inline-size:4.5rem;
}

/* ==================================================== pictures placed, not dropped (v1.50.0)
   A SMALL PICTURE THAT OPENS A BLOCK OF WORDS SITS BESIDE THEM. inc/sanitise.php marks it
   (.msa-aside) when it is 420px or narrower and words follow it; see the reasoning there. The
   block contains its float so nothing after it — a tab strip, the next section — wraps round
   the picture. Below 560px there is no room for two columns, and the picture is centred above
   its words at its own size. */
.msa-prose :has(> img.msa-aside){display:flow-root}
@media (min-width:560px){
  .msa-prose img.msa-aside{
    float:right;
    inline-size:auto;
    max-inline-size:min(42%, 20rem);
    block-size:auto;
    margin:.35rem 0 1rem 2rem;
  }
  /* The first words start level with the picture's top, not a heading's margin below it. */
  .msa-prose img.msa-aside + :is(h2,h3,h4,p){margin-block-start:0}
}
@media (max-width:559px){
  .msa-prose img.msa-aside{display:block;max-inline-size:100%;block-size:auto;margin:0 auto 1.25rem}
}

/* A PICTURE ALONE IN ITS PARAGRAPH, IN AN EXPLAINER. The centring rule above lists the wrappers
   imported pages use and was written before the explainers were rebuilt in .msa-explainer, so
   their lone diagrams kept the left edge — the Raman optical path, the coal reflectance plot —
   with half the column empty beside them. Same treatment, and the same for a picture wrapped
   in its own link. */
.msa-prose .msa-explainer :is(p:has(> img:only-child), p:has(> a:only-child > img:only-child)),
.msa-prose :is(div:not([class]), div.row, div.page-content, div.ft-text, div.prodtabs, div.scipg2, td, td.copy) p:has(> a:only-child > img:only-child),
.msa-prose > p:has(> a:only-child > img:only-child){
  width:fit-content;
  max-width:100%;
  margin-inline:auto;
}

/* THE INSTRUMENT BEFORE THE TABS WHERE THE RAIL IS NOT BESIDE THE WORDS. inc/rail.php prints
   the rail's photograph a second time, before the tab block; exactly one of the two shows at
   any width. 1100px is where .msa-body--railed becomes two columns. */
.msa-shot-inline{margin:0 auto 1.75rem;max-inline-size:min(100%, 22rem)}
@media (min-width:1100px){.msa-shot-inline{display:none}}
@media (max-width:1099px){.msa-rail__shot{display:none}}

/* THE PLATE HOLDS ITS PICTURE. `.msa-prose figure img{height:auto}` (0,1,2) outranks
   `.msa-apptech__plate img` (0,1,1), so the plate's `block-size:100%` never applied: a portrait
   picture took its own height and ran out of the 11.5rem plate. Measured on /solutions/applications/
   at 390px: the vitrinite report stood 432px tall in a 184px plate, across the Geology card's
   heading and its first three applications. Qualified to outrank it. */
.msa-prose .msa-apptech__plate figure img{block-size:100%;height:100%}

/* AN EXPLAINER'S LONE FIGURE IS CENTRED, like a lone figure everywhere else in the prose. The
   rule above names the imported pages' wrappers; the rebuilt explainers wrap their copy in
   .msa-explainer, so a 176px objectives diagram or a 195px equation sat at the column's left edge
   with two-thirds of it empty. A classed figure places itself and is left alone. */
.msa-prose .msa-explainer > figure:not([class]){
  width:fit-content;
  max-width:100%;
  margin-inline:auto;
}

/* ONE ROW HEIGHT IN A PICTURE-AND-CAPTION LIST. .msa-explainer__more sets each picture beside
   its caption, two to a row; the rows are as tall as their picture. On /support/the-science/
   raman-science/ the Apollo M photograph stood 547px tall among 92-161px diagrams, so one row was
   a tower with a one-line caption at its top and 500px of nothing beside it. Capped at the height
   of the tallest diagram in these lists; narrower pictures are untouched, nothing is enlarged. */
.msa-prose ul.msa-explainer__more img{
  inline-size:auto;
  max-inline-size:100%;
  block-size:auto;
  max-block-size:10rem;
}
