/*
 * miguelbarroso.com — consulting surface, 2026
 *
 * Scoped entirely under .mb26 so nothing here can reach the existing pages.
 * The world is a drawing sheet: warm paper, hairline rules, ink, and one blue --
 * ruri-iro, the Japanese lapis, rather than the theme's stock #0067FF.
 * Monospace is reserved for measurements, versions and materials -- never as
 * decoration.
 */

/* ------------------------------------------------------------ root scale */

/* Astra's Customizer emits `@media (max-width:921px){html{font-size:91.2%}}`,
   which drops the root from 16px to 14.592px on every viewport under 921px --
   tablets and small laptops, not only phones. Every size in this file is
   authored against a 16px root, so under that breakpoint the whole micro-scale
   silently shrank by 8.8%: rail annotations, figure captions and the contact
   form's own field labels all landed between 10.2px and 10.8px, measured on the
   live page. Pinning the root is the fix that keeps the rem values in this file
   honest; raising each size individually would only paper over the lie.
   `:has()` gives this (0,1,2) against Astra's (0,0,1), so it wins on
   specificity rather than on load order, and it reaches nothing but this
   surface. */
html:has(body.mb26) { font-size: 100%; }

/* ---------------------------------------------------------------- tokens */

.mb26 {
  --ink:        #15181b;
  /* PRODUCT.md sets AAA for body text as the floor, and these two missed it
     everywhere: at #565e66 and #62696f the body ran 6.31:1 on paper and the
     annotation grey 4.81:1 on the panel -- the lowest reading on the page, and
     it was on the contact form's own field labels. Both are set from the panel
     rather than from paper, because the panel is the darkest surface either one
     sits on, so clearing 7:1 there clears it everywhere.
     The ramp got wider, not flatter: ink -> ink-2 -> ink-3 now steps x1.70 and
     x1.26, against x1.11 and x1.18 before. */
  --ink-2:      #3b4046;   /* 10.02:1 on paper, 9.03:1 on the panel */
  --ink-3:      #4a4f53;   /*  7.94:1 on paper, 7.16:1 on the panel */
  --paper:      #fbfaf7;
  --panel:      #f1eee8;
  --rule:       #ddd8ce;
  --rule-soft:  #e9e5dc;
  /* ruri-iro. The darker and lighter steps hold its hue and saturation
     exactly (217.3 deg, S 0.69) and move only lightness, so the ramp stays
     one blue instead of three. */
  --accent:     #1e50a2;   /* 7.38:1 on paper, 6.66:1 on the panel */
  --accent-ink: #173d7c;   /* 10.08:1 either way against paper */
  --accent-lit: #7fa7e8;   /* the accent, legible on ink -- 7.29:1 */
  --wash:       #e8eef7;

  --sans: 'IBM Plex Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;

  /* Declared 68ch, rendered 81 characters per line: `ch` is the advance of "0",
     which in Plex Sans is far wider than the average lowercase letter, so a ch
     measure always sets a line about 19% longer than it claims. Counted the
     real thing instead -- characters divided by rendered line boxes, off
     Range.getClientRects -- across all four pages, and 576px puts every text
     role between 51 and 75. A length rather than a ch also gives the column one
     edge: the lead and the body now break on the same vertical, which 54ch and
     68ch never did, because ch resolves against each element's own size.
     --measure-tight is for text set below body size, where the same 576px would
     run back over 75.

     A reviewer will notice that 576px sits inside a column whose hairlines run
     to about x=1262, and that on a prose-only section this leaves the right 40%
     empty for as much as 1,600px at a stretch. That is true, it is measured, and
     it is not fixed here on purpose. Widening the measure would break the count
     above -- 576 is the number that keeps every text role between 51 and 75
     characters, and there is no wider value that does. Pulling the hairlines in
     to the text would break the other half of the system: they are sheet rules,
     they run the sheet, and shortening them only on sections that happen to be
     prose would read as arbitrary rather than as a decision.

     What actually fills that margin is the rail, and the rail is a content
     question, not a CSS one -- the sections it looks empty beside are the ones
     that have nothing in it. So the answer belongs to whatever goes on which
     page, and it was decided there rather than patched here. It is decided now:
     every prose section on /, /work/ and /about/ carries a rail note, and the
     three pages give it different work. / annotates with counts, because the
     home page argues and a count is the shortest form of evidence. /work/
     annotates with dates and with what the document at hand is, because that
     page is a record in the order it happened. /about/ does both, being a CV.
     If a new section looks empty on the right, it is missing its note. */
  --measure:       36rem;
  --measure-tight: 33rem;
  --page:    1180px;
  --rail:    172px;        /* margin annotations sit here from 1100px up */

  --step:  clamp(3.5rem, 7vw, 6.5rem);   /* space between major sections */
  --gap:   clamp(1.5rem, 3vw, 2.5rem);
  --bleed: clamp(1.25rem, 5vw, 3rem);    /* page gutter */

  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.0625rem;
  line-height: 1.65;
  font-feature-settings: 'kern' 1, 'liga' 1;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ------------------------------------------------- browser surfaces */

.mb26 ::selection      { background: var(--accent); color: #fff; }
.mb26 :focus-visible   { outline: 2px solid var(--accent);
                         outline-offset: 3px; border-radius: 2px; }
/* The ring is a non-text contrast surface, so it owes 3:1 against whatever it is
   drawn on. --accent measures 2.31:1 on the ink plate and fails there; --accent-lit
   is the same hue at 7.29:1 and is already what links use on that background. */
.mb26 .mb-ink :focus-visible { outline-color: var(--accent-lit); }
.mb26 { caret-color: var(--accent); scrollbar-color: var(--rule) transparent; }
.mb26 ::-webkit-scrollbar        { width: 11px; height: 11px; }
.mb26 ::-webkit-scrollbar-track  { background: var(--panel); }
.mb26 ::-webkit-scrollbar-thumb  { background: var(--rule); border-radius: 6px;
                                   border: 3px solid var(--panel); }
.mb26 ::-webkit-scrollbar-thumb:hover { background: var(--ink-3); }

/* ------------------------------------------------------------ scaffold */

.mb26 .mb-shell   { max-width: var(--page); margin-inline: auto;
                    padding-inline: var(--bleed); }

/* EWWW wraps every <img> on the site in a <picture> to offer its webp source.
   That wrapper, not the image, then becomes the flex item and the containing
   block a percentage height resolves against -- measured: the hero's 70px
   byline plate came out 38.3px, because Astra's `img { max-width: 100% }` was
   resolving against a <picture> that had itself shrunk to fit. Taking the
   wrapper out of layout puts every image back where the markup says it is.
   Source selection is unaffected: it happens before layout.

   The <source> has to be hidden explicitly. `display: contents` promotes it to
   a sibling of the image, where it is a zero-width flex item that still claims
   its share of the gap -- measured as a 13.6px indent on the byline plate, one
   `gap` exactly. */
.mb26 picture     { display: contents; }
.mb26 picture > source { display: none; }
.mb26 .mb-prose   { max-width: var(--measure); }
.mb26 section     { padding-block: var(--step); }
.mb26 section + section { border-top: 1px solid var(--rule-soft); }

/* A section can carry a real datum in the left margin -- a count, a date
   range, a material. Never an ornament, and never a sequence number. */
.mb26 .mb-note {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: 500;
}

/* The label aligns to the heading's first baseline, not to a fixed nudge: the
   headings are clamp()ed, so any px offset is only ever correct at one viewport
   width. Measured before this, the label floated a clear 24px above the cap
   line and read as unattached to the heading it annotates. */
@media (min-width: 1100px) {
  .mb26 .mb-railed { display: grid; grid-template-columns: var(--rail) minmax(0, 1fr);
                     column-gap: var(--gap); align-items: baseline; }
  .mb26 .mb-railed > .mb-note { text-align: right; }
  /* A figure has no baseline of its own, so it opts back out. */
  .mb26 .mb-railed:has(> .mb-portrait) > .mb-portrait { align-self: start; }
}
@media (max-width: 1099px) {
  .mb26 .mb-railed > .mb-note { display: block; margin-bottom: 1.1rem; }
}

/* ---------------------------------------------------------------- type */

.mb26 h1, .mb26 h2, .mb26 h3 {
  font-family: var(--sans);
  color: var(--ink);
  text-wrap: balance;
  margin: 0 0 0.65em;
}

/* 2026-10-09: the headline was clamp(2.6rem, 6.2vw, 4.6rem) at 16ch, which set
   the commissioning page's H1 across seven lines at 1280x720 and pushed every
   first call to action under the fold. The headlines were shortened first (the
   longest is now 61 characters); the type then comes down one step and the
   measure opens to 20ch so a headline of that length sets in two or three lines
   on a desktop and three on a phone. Still the loudest thing on the page. */
.mb26 h1 {
  font-size: clamp(2.35rem, 5.2vw, 3.9rem);
  line-height: 1.05;
  letter-spacing: -0.034em;
  font-weight: 600;
  max-width: 20ch;
}
.mb26 h2 {
  font-size: clamp(1.75rem, 3.2vw, 2.6rem);
  line-height: 1.12;
  letter-spacing: -0.026em;
  font-weight: 600;
  max-width: 22ch;
}
.mb26 h3 {
  font-size: clamp(1.2rem, 1.9vw, 1.45rem);
  line-height: 1.25;
  letter-spacing: -0.014em;
  font-weight: 600;
  max-width: 34ch;
}
/* More air above a heading than below it. */
.mb26 * + h2 { margin-top: 1.6em; }
.mb26 * + h3 { margin-top: 2.1em; }

.mb26 p { margin: 0 0 1.15em; max-width: var(--measure); }
.mb26 p:last-child { margin-bottom: 0; }
.mb26 .mb-lead {
  font-size: clamp(1.15rem, 1.7vw, 1.35rem);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: var(--measure);
}
.mb26 strong { font-weight: 600; color: var(--ink); }
.mb26 em     { font-style: italic; }

.mb26 a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: color-mix(in srgb, var(--accent) 38%, transparent);
  transition: text-decoration-color 140ms ease, color 140ms ease;
}
.mb26 a:hover { color: var(--accent-ink); text-decoration-color: currentColor; }

/* Measurements, versions, materials, part counts. */
.mb26 .mb-spec {
  font-family: var(--mono);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  max-width: var(--measure-tight);
}

/* A caveat in the reading voice. .mb-spec is the measurement face -- mono,
   tabular, tracked -- and the note under the hero band is a 437-character
   sentence about a public register, which is prose. It was set as a spec, which
   made the one piece of text a sceptical reader most wants to check the
   smallest and hardest-read prose on the page. Still subordinate to the band by
   size and colour, but in the face the page is read in. */
.mb26 .mb-footnote {
  font-size: 0.94rem;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: var(--measure-tight);
}

/* ---------------------------------------------------------------- hero */

/* Top padding trimmed 2026-10-09 (was clamp(3.5rem, 7vw, 5.5rem)): with the
   header at 64px and the hero copy cut to a lead, one proof sentence and the
   actions, the first call to action now lands inside a 390x844 viewport. */
.mb26 .mb-hero { padding-block: clamp(2.25rem, 4.5vw, 3.75rem) clamp(2rem, 3.5vw, 2.75rem); }
/* The hero's rail note names the discipline rather than measuring something, so
   it is the one .mb-note that sits above the heading in the accent. It is also
   the first line on the site: it has to say what this is before the headline
   gets a chance to be read as being about turnaround alone. */
.mb26 .mb-hero .mb-note {
  color: var(--accent); letter-spacing: 0.12em; margin: 0 0 1.1rem;
}
/* The domain is his name and the logo is his initials, but the first person on
   the page was an unnamed "I" inside the headline: the visitor learned what the
   work is long before learning whose it is. Name and role now sit above the
   headline, and on a wide screen the photograph holds the right of the hero as
   a contained black field. That column was empty -- the headline measures 553px
   and the lead 700px inside an 1084px shell -- so the identity costs the
   proposition no vertical at all. The black is the negative's own and is left
   alone: against this much paper it reads as a plate, which is the same move
   the ink panels make further down. The photograph carries an empty alt on
   purpose -- the only thing it could announce is the name a screen reader
   reads out of the very next element. */
.mb26 .mb-hero {
  /* The photograph is the highest-contrast object on a page that is otherwise
     all paper, so it wins any fixation it is allowed to compete for. At 312px
     it took the first one and the read became "portrait, therefore consultant".
     Cut to 208 -- 55% of the area -- it is still unmistakably a person at arm's
     length and no longer the subject of the hero.

     The gap grew as the face shrank, and deliberately: it is the reserved
     column minus the photograph, so holding it near its old total keeps the
     text measure, and with it the hairline, exactly where it was. What the
     photograph gives up becomes air between the proposition and the person
     rather than a wider line of type. */
  --face:     clamp(176px, 14.5vw, 208px);
  --face-gap: clamp(4.5rem, 11.5vw, 10.5rem);
  --ident-mb: 1.35rem;
}
.mb26 .mb-hero .mb-ident {
  display: flex; align-items: center; gap: 1.05rem;
  margin: 0 0 1.6rem;
}
/* Three widths, and the img's `sizes` in home.blocks.html has to name all three
   or the browser picks the wrong file. It used to say
   `(min-width: 1100px) 208px, 88px`, which is wrong twice: 88 where this element
   is 70 below 600px, and a flat 208 where --face is a clamp that only reaches
   208 at a 1435px viewport. The 88 is the one that cost something. A 390px phone
   at 3x needs 70 x 3 = 210 device pixels and the srcset offers 225w, but the
   over-declared 88 asked for 264, which no candidate under 768w satisfies -- so
   every 3x phone downloaded the 768x1024 file for a 70px slot. Measured 3.66x
   oversized, and it is the largest image on the page.

   The candidate list still has a real gap between 225w and 768w, so a 2x display
   in the 176-208px band pulls 768w for a 416px slot. Closing that needs a new
   intermediate thumbnail, not an attribute. If any width below changes, change
   `sizes` with it -- nothing enforces the pair. */
.mb26 .mb-hero .mb-ident-face {
  flex: none; width: 88px; height: auto; display: block;
  border-radius: 2px; background: #0c0d0e;
  /* Squarer than the negative, so cover trims the studio's headroom and the
     small plate is a face rather than a figure at a distance. */
  aspect-ratio: 1 / 1.12; object-fit: cover; object-position: 50% 24%;
}
.mb26 .mb-hero .mb-ident-txt { min-width: 0; }
.mb26 .mb-hero .mb-ident-name {
  font-family: var(--sans); font-size: 1.1rem; font-weight: 600;
  letter-spacing: -0.014em; line-height: 1.2; color: var(--ink);
  margin: 0 0 0.3rem;
}
/* The kicker is now the role line under the name, so it stops paying for its
   own clearance and the block pays it once. */
.mb26 .mb-hero .mb-ident .mb-note { margin: 0; }
/* On a phone the role line wraps to three tracked-out lines and overtakes the
   plate, so the plate comes down with it and the block stays a byline. */
@media (max-width: 600px) {
  .mb26 .mb-hero .mb-ident      { gap: 0.85rem; margin-bottom: 1.35rem; }
  .mb26 .mb-hero .mb-ident-face { width: 70px; }
}
/* Above 1100px the shell reserves a right gutter and the photograph is lifted
   into it, out of flow. Absolute rather than a grid column on purpose: the
   hero's vertical rhythm is built out of collapsing margins between the
   heading, the hairline, the lead and the button, and making the shell a grid
   would have made every one of those margins additive. The reserved padding
   also pulls the hairline in off the far edge, so it now stops where the lead
   stops rather than running the full width of the shell.

   It used to hang off the byline, so that its top edge was the headline's top
   edge. That alignment was real but it bought the wrong thing: the column is
   771px tall and the photograph is 277 of them, so topping the two out left
   the whole lower half of the gutter empty in one 429px block, beside the
   second paragraph and the button. A void with text alongside it reads as a
   column that failed to fill, not as air.

   Centered in the shell instead, the same 429px is spent as 215 above and 215
   below, and neither half is large enough to read as a hole. It costs the
   headline nothing -- the eye still enters on text, and now meets the
   photograph on the way down into the lead, which is the order the page argues
   in. Nothing here touches --face or --face-gap, so the reserved padding is
   unchanged and the hairline cannot move.

   Positioned against the shell, whose padding box already ends where the
   photograph should: right: var(--bleed) is the same gutter every other
   element on the page is held off the edge by, rather than a second number
   that has to be kept in step with the first. */
@media (min-width: 1100px) {
  .mb26 .mb-hero .mb-shell {
    position: relative;
    padding-right: calc(var(--bleed) + var(--face) + var(--face-gap));
  }
  .mb26 .mb-hero .mb-ident {
    display: block; margin-bottom: var(--ident-mb);
  }
  .mb26 .mb-hero .mb-ident-face {
    position: absolute;
    top: 50%; transform: translateY(-50%);
    right: var(--bleed);
    width: var(--face); aspect-ratio: 896 / 1195; object-position: 50% 50%;
  }
}
.mb26 .mb-hero h1 { margin-bottom: 0.5em; }
.mb26 .mb-hero .mb-lead { margin-bottom: 2rem; }
/* The button is inline-flex, so without this the band's top rule lands within a
   few pixels of the button edge and reads as an underline that ran on. */
.mb26 .mb-hero .mb-cta { margin-bottom: clamp(2rem, 4vw, 2.75rem); }
/* The proof strip carries its own top margin for use inside a section; in the
   hero it sits under the button, which has already paid that gap. */
.mb26 .mb-hero .mb-layers { margin-top: 0; }
/* The proof strip moved out of the hero and under the photograph, so the button
   is now the last thing in the section and its clearance is the section's own. */
.mb26 .mb-hero .mb-cta:last-child { margin-bottom: 0; }

/* On a laptop-height viewport the hero's own generosity puts the only call to
   action below the fold -- measured at 1280x720, where it landed at y=749.
   Tighten the vertical rather than the authority of the type.

   The ceiling is 920 rather than 820 because a 900px viewport had the same
   problem one step further down: the photograph began at y=1001, so the first
   screen of a page about instruments held no instrument. Tightened, its top
   edge crosses the fold. Below ~800px of viewport the headline, the lead and a
   photograph genuinely do not fit together, and the photograph is what gives:
   it is the first thing under the hero, not a second screenful away. */
@media (min-width: 1100px) and (max-height: 920px) {
  /* --ident-mb is only the byline's own margin now; the photograph is centered
     in the shell and follows whatever height these rules leave it. Still set on
     the hero rather than on the byline so it stays one declaration away from
     the padding it is tightened alongside. */
  .mb26 .mb-hero            { padding-block: 3.5rem 2rem; --ident-mb: 1.25rem; }
  .mb26 .mb-hero h1         { font-size: clamp(2.4rem, 4.2vw, 3.6rem); }
  .mb26 .mb-hero .mb-rule   { margin-bottom: 1.5rem; }
  .mb26 .mb-hero .mb-lead   { margin-bottom: 1.5rem; }
  .mb26 .mb-panelband         { padding-block: 2rem clamp(2.25rem, 4vw, 3rem); }
}

/* That block was not enough: measured after it, the button's bottom edge still
   fell at y=730 in a 720 viewport and y=777 in a 768 one -- ten and nine pixels
   under, which is the worst possible amount. A button half-cut by the fold does
   not read as "scroll for more", it reads as the end of the page.

   Two blocks rather than one, on different guards:

   - The trims below start at 1000px, because 1024x768 is the other viewport
     that misses, and it never entered the block above.
   - The headline step above stays at 1100px, because 4.2vw only becomes
     smaller than the base clamp somewhere above that. Applied at 1024 it would
     shrink the headline to 43px for nothing: the spacing alone clears the fold
     there with 60px to spare.
   - 820 rather than 920 as the height ceiling, because at 900 there is no fold
     problem to solve and the block above is already shaping that case for the
     photograph. Tightening 1440x900 further would only cost it air.

   Budget at 1280x720: 16px off the hero's top padding, 8 off the headline, 6
   each off the rule, the lead and the button's approach. 42px, landing the
   button at ~688 with the fold a comfortable distance below it. */
@media (min-width: 1000px) and (max-height: 820px) {
  .mb26 .mb-hero            { padding-block: 2rem 2rem; --ident-mb: 1rem; }
  .mb26 .mb-hero h1         { margin-bottom: 0.3em; }
  .mb26 .mb-hero .mb-rule   { margin-bottom: 1rem; }
  .mb26 .mb-hero .mb-lead   { margin-bottom: 0.9rem; }
  .mb26 .mb-hero p          { margin-bottom: 0.9em; }
  .mb26 .mb-hero .mb-cta    { margin-top: 1rem; }
  /* 2026-10-09: measured at 1280x720 with the shorter hero, the primary action
     still ended at y=736; the trims above land it inside the fold. */
}

/* The one authored moment: the rule under the headline draws itself, and the
   headline settles from a hair of blur. Both start from a visible state, so
   nothing is stranded if the animation never runs.

   This rule is also the accent's one structural job. Scattered across links it
   was three marks nobody reads as a color; here it is a single blue line drawn
   under the page's own claim, once per page, and it is the only place the
   palette announces itself rather than merely labels something. */
.mb26 .mb-hero h1 { animation: mb-settle 900ms cubic-bezier(.16,1,.3,1) both; }
.mb26 .mb-rule {
  height: 1px; background: var(--accent); border: 0; margin: 0 0 2rem;
  transform-origin: left center;
  animation: mb-draw 1100ms cubic-bezier(.16,1,.3,1) 120ms both;
}
/* The entrance used to start from blur(6px)/opacity .55, which measured 2.8:1
   on its first frames: a blur averages ink into paper, so no opacity value
   rescues it. The headline is the whole argument of the page and has to stay
   legible through its own entrance, so it rises and fades instead -- .85 on
   paper is 10.5:1, above AA from the first frame to the last. */
@keyframes mb-settle {
  from { opacity: .85; transform: translateY(12px); }
  to   { opacity: 1;   transform: none; }
}
@keyframes mb-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* Reduced motion means less movement, not a page with no feedback at all. The
   entrance and every transform are dropped; color, shadow and underline still
   transition, so a hover or a focus ring still reads as a change of state. */
@media (prefers-reduced-motion: reduce) {
  .mb26 *, .mb26 *::before, .mb26 *::after {
    animation-duration: 1ms !important; animation-iteration-count: 1 !important;
    transition-property: color, background-color, border-color, box-shadow,
                         opacity, text-decoration-color !important;
    transition-duration: 120ms !important;
  }
}

/* ------------------------------------------------------- section index */

/* Both long pages run past twenty thousand pixels and the header scrolls away
   with everything else, so a reader who arrived for one section had no way to
   reach it but to scroll through all the others. A contents block answers that
   without a sticky bar's standing charge: it costs its own height once, below
   the fold, and nothing on any screen after.

   Drawn as a sheet index rather than a menu -- numbered, ruled, two columns of
   small type -- because that is the register the rest of the page is drawn in,
   and because it has to read as reference furniture rather than as the first
   piece of content. The numbers come from a counter, so the list stays an <ol>
   and a screen reader does not read every number twice. */
/* The closing rule needs its own clearance: with zero bottom padding it landed
   exactly on the panel band's edge and read as that panel's top border rather
   than as the end of the list. */
.mb26 .mb-indexband {
  padding-block: clamp(1.75rem, 3vw, 2.5rem) clamp(1.25rem, 2vw, 1.75rem);
}
.mb26 .mb-index .mb-note { color: var(--ink-3); margin: 0 0 0.85rem; }
.mb26 .mb-index-list {
  list-style: none; margin: 0; padding: 0;
  counter-reset: mb-ix;
  border-bottom: 1px solid var(--rule);
}
.mb26 .mb-index-list li {
  counter-increment: mb-ix;
  border-top: 1px solid var(--rule);
  break-inside: avoid;
}
.mb26 .mb-index-list a {
  display: grid; grid-template-columns: 2.5rem 1fr; align-items: baseline;
  padding: 0.55rem 0;
  font-family: var(--sans); font-size: 0.92rem; line-height: 1.35;
  color: var(--ink-2); text-decoration: none;
}

/* A single-line entry is 37px tall, which is a drawn density that works under a
   cursor and is seven pixels short under a thumb. Rather than loosen the list
   for everyone, the rows grow only where something coarse can point at them.
   any-pointer rather than pointer: a laptop with a touchscreen reports its
   trackpad as the primary pointer and would otherwise keep the tight rows. */
@media (any-pointer: coarse) {
  .mb26 .mb-index-list a { min-height: 44px; align-content: center; }
}
.mb26 .mb-index-list a::before {
  content: counter(mb-ix, decimal-leading-zero);
  font-size: 0.76rem; font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em; color: var(--accent);
}
@media (hover: hover) {
  .mb26 .mb-index-list a:hover        { color: var(--ink); }
  .mb26 .mb-index-list a:hover::before { color: var(--accent-ink); }
}

/* The home page indexes one heading level, so every entry there is a section
   and this class never appears. The work page indexes two, and it used to do
   that as one flat <ol> separated by weight alone -- which reads as a run of
   equal things rather than as sections with their parts under them. "Building
   a laboratory" and "The systems chart" sat as peers when the second is inside
   the first. The list is now nested for real; weight stays as the second
   signal, reinforcing the structure instead of standing in for it. The page
   has since grown to ten sections and twenty-seven rows, which is why the
   counts that used to be written into these comments are gone: they were
   already wrong twice. */
.mb26 .mb-index-list .mb-ix-sec > a { color: var(--ink); font-weight: 600; }

/* The hierarchy is carried in the number column, not by an indent, and that is
   the original constraint rather than a new preference: indenting the
   subordinate entries would cost the number column its alignment, which is
   what makes the list read as an index rather than as a menu. So a nested <ol>
   is the one place this list needs its own reset. list-style is an inherited
   property, so the markers stay off by themselves -- but padding is not, and
   the UA's 40px padding-inline-start is precisely the indent that is ruled
   out. Nothing in this file or in Astra resets a bare <ol>, so it is done
   here. */
.mb26 .mb-index-list ol {
  list-style: none; margin: 0; padding: 0;
  counter-reset: mb-ixsub;
}
/* Outranks `.mb-index-list li` on specificity (0,2,2 against 0,2,1), so a
   child increments a counter of its own and leaves mb-ix alone -- which is
   what lets the child below read its parent's number straight back out of
   mb-ix. Without it the children would go on incrementing the section counter
   and the seven sections would number 01, 05, 09, 12, 16, 19, 22: nested on
   the page and flat in the margin, which is worse than either. */
.mb26 .mb-index-list ol > li { counter-increment: mb-ixsub; }
/* 01 / 01.1 / 01.2, the way a specification numbers its subdivisions, and the
   register the rest of the sheet is drawn in. Every row keeps a locator -- the
   alternative, unnumbered children, leaves a blank cell in the number column
   and reads as missing data on a drawing. Four glyphs at 0.76rem tabular come
   to about 28px against the 2.5rem column, so the measure is unchanged. */
.mb26 .mb-index-list ol > li > a::before {
  content: counter(mb-ix, decimal-leading-zero) "." counter(mb-ixsub);
}

/* Two columns, and columns rather than a grid so the run reads down the first
   before the second. What the browser is balancing on the work page is not
   loose rows but whole sections: a section li carries its children and the
   break-inside: avoid above holds it together, so the split lands on a section
   boundary and never orphans a child from its parent. The columns come out
   close rather than exactly level, and that is the trade -- an uneven closing
   rule is a smaller fault than a subsection standing at the top of column two
   with nothing above it to belong to. */
@media (min-width: 760px) {
  .mb26 .mb-index-list { columns: 2; column-gap: var(--gap); }
}

/* ------------------------------------------------------------- buttons */

.mb26 .mb-cta {
  display: inline-flex; align-items: center; gap: 0.6rem;
  font-weight: 500; font-size: 1rem; line-height: 1;
  padding: 0.95rem 1.5rem;
  color: var(--paper); background: var(--ink);
  border: 1px solid var(--ink); border-radius: 2px;
  text-decoration: none;
  transition: background 160ms ease, transform 160ms cubic-bezier(.16,1,.3,1),
              box-shadow 160ms ease;
}
.mb26 .mb-cta::after { content: '\2192'; transition: transform 220ms cubic-bezier(.16,1,.3,1); }
/* Gated: on touch this fill would latch after the tap and sit there until the
   next tap somewhere else, on a button whose whole job is to be pressed once. */
@media (hover: hover) and (pointer: fine) {
  .mb26 .mb-cta:hover  { background: var(--accent-ink); border-color: var(--accent-ink);
                         color: var(--paper);
                         box-shadow: 0 6px 18px -8px rgba(21, 24, 27, .5); }
  .mb26 .mb-cta:hover::after { transform: translateX(4px); }
}
.mb26 .mb-cta:active { transform: translateY(1px); }

/* ------------------------------------------------- credibility band */

/* Separated by space, not by rules. A drawn divider belongs to one item or the
   other, and either way it strands itself at the start or the end of a wrapped
   line -- which this list does at every width between the two breakpoints.

   Columns rather than a wrapped row, for the same reason the figures share a
   date column: reflowed inline, the second item landed at three different
   x-positions down the same band, 32px apart, and a strip of measured numbers
   is exactly where that reads as a misprint. auto-fit reproduces the wrap the
   flex row already had -- two up above 600px of column, one below -- and lands
   them on a shared edge. padding-inline is the browser's own list indent: at
   390px it was pushing the numbers 40px right of the heading above them. */
.mb26 .mb-band {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  column-gap: clamp(1.75rem, 4vw, 3.25rem);
  row-gap: 0.55rem;
  padding-block: 1.15rem;
  padding-inline: 0;
  border-block: 1px solid var(--rule);
  list-style: none; margin: 0;
}
.mb26 .mb-band li {
  font-family: var(--mono); font-size: 0.775rem; font-weight: 500;
  letter-spacing: 0.045em; text-transform: uppercase; color: var(--ink-2);
}

/* --------------------------------------------------- lifecycle scale */

/* The layers a laboratory runs on, set as a measured scale rather than as a
   process diagram: a rule across the top and a tick down onto each item. No
   arrows, no chevrons, no boxes -- the reading order already carries the
   direction, and a drawn arrow between ten items is what makes this kind of
   band look like a slide.

   These were numbered 01-10 in mono, which read as a sequence and put a third
   step count on a page that already disagreed with itself: the drawing further
   down describes eleven steps, and the prose next to it names its own set. Four
   of the ten are not steps anyway -- LIMS, workflow, method and quality control
   are layers the sample is inside the whole time, not places it stops. Dropped
   to an unnumbered list, it makes no count to be wrong about, and the mono
   numerals took the last --accent text on the panel (6.66:1) out with them.

   Five columns, not four or six: ten divides into 5 and 2 without leaving an
   orphan on the last row at either width.

   The rule is drawn per item rather than across the list, because the list
   wraps to a second row: a single rule on the <ul> leaves the second row's
   ticks hanging off nothing, which is what it did when first measured. Per
   item, the column gaps segment the rule -- which is what a scale looks like
   anyway -- and every item gets the tick it belongs to. */
.mb26 .mb-lifecycle {
  list-style: none; margin: 1.75rem 0 1.5rem; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--gap); row-gap: 1.4rem;
}
@media (min-width: 720px) {
  .mb26 .mb-lifecycle { grid-template-columns: repeat(5, minmax(0, 1fr));
                        column-gap: clamp(0.75rem, 1.6vw, 1.25rem); }
}
.mb26 .mb-lifecycle li {
  position: relative; padding-top: 0.85rem;
  border-top: 1px solid var(--rule);
}
/* The tick hangs from that rule, so it has to start on it: -1px, not 0. */
.mb26 .mb-lifecycle li::before {
  content: ''; position: absolute; top: -1px; left: 0;
  width: 1px; height: 0.55rem; background: var(--rule);
}
.mb26 .mb-lifecycle span { display: block; font-size: 0.95rem; line-height: 1.35;
                           color: var(--ink); text-wrap: balance; }

/* --------------------------------------------------- capability grid */

/* Hairlines between rows, nothing around them. The sub grid is the same
   component one step quieter -- smaller heading, gray body -- because the
   hierarchy is the message: the six above are the consultancy, and automation
   is one of the ways it gets implemented. Anything that made this item equal
   in weight would put the site back to being an automation site. */
.mb26 .mb-grid {
  list-style: none; margin: 1.75rem 0 0; padding: 0;
  display: grid; gap: 0 var(--gap);
}
@media (min-width: 720px) {
  .mb26 .mb-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.mb26 .mb-grid > li {
  padding-block: clamp(1.1rem, 2.2vw, 1.5rem);
  border-bottom: 1px solid var(--rule-soft);
}
/* Two columns means the top row needs its own rule to close the block. */
@media (min-width: 720px) {
  .mb26 .mb-grid > li:nth-child(-n+2) { border-top: 1px solid var(--rule-soft); }
}
@media (max-width: 719px) {
  .mb26 .mb-grid > li:first-child { border-top: 1px solid var(--rule-soft); }
}
.mb26 .mb-grid h3 {
  font-size: 1rem; letter-spacing: 0.005em; margin-bottom: 0.4em;
}
.mb26 .mb-grid p { margin: 0; color: var(--ink-2); font-size: 0.94rem;
                   line-height: 1.55; max-width: 46ch; }
.mb26 .mb-grid + p { margin-top: 1.75rem; }

.mb26 .mb-grid-sub { margin-top: 0; }
.mb26 .mb-grid-sub > li { border-top: 0; }
@media (min-width: 720px) {
  .mb26 .mb-grid-sub { grid-template-columns: minmax(0, 1fr); }
  .mb26 .mb-grid-sub > li:nth-child(-n+2) { border-top: 0; }
}
.mb26 .mb-grid-sub h3 { font-size: 0.9rem; color: var(--ink-2); }
.mb26 .mb-grid-sub p  { color: var(--ink-3); font-size: 0.9rem; max-width: 68ch; }

/* ------------------------------------------------------ boundary pairs */

/* Two roles and the gap between them. The arrow is the content -- it is what
   the section is about -- so it is set in the accent and given room, and the
   pair reads as one line rather than as a definition list. */
.mb26 .mb-pairs {
  list-style: none; margin: 1.5rem 0; padding: 0;
  display: grid; gap: 0.5rem;
}
@media (min-width: 620px) {
  .mb26 .mb-pairs { grid-template-columns: repeat(2, minmax(0, 1fr));
                    column-gap: var(--gap); }
}
.mb26 .mb-pairs li {
  font-family: var(--mono); font-size: 0.8rem; letter-spacing: 0.02em;
  color: var(--ink-2); line-height: 1.5;
}
.mb26 .mb-pairs li > span + span::before {
  content: '\2194'; color: var(--accent); margin: 0 0.5em; font-weight: 600;
}

/* ------------------------------------------------------ measurements */

/* Figures set as measurements rather than as a statistics band: mono, tabular,
   in the page's ink, with no accent and no tile around them. Every value keeps
   the sentence that qualifies it -- "~½" on its own is a boast, "~½, on the
   laboratory's own figures" is a citation, and the qualifier is the reason a
   laboratory director reads past the number. Ruled top and bottom like
   .mb-band, and never between the columns: the row reflows, and a divider
   drawn between two items always strands itself when it does. */
.mb26 .mb-proof,
.mb26 .mb-result {
  list-style: none; padding: 0;
  display: grid;
  gap: clamp(1.25rem, 3vw, 2.25rem);
}
.mb26 .mb-proof b,
.mb26 .mb-result b {
  display: block;
  font-family: var(--mono); font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.mb26 .mb-proof span,
.mb26 .mb-result span {
  display: block; margin-top: 0.45rem;
  line-height: 1.4; color: var(--ink-2);
}

/* The strip under the hero photograph: four values, read at a glance. */
.mb26 .mb-proof {
  margin: clamp(2rem, 4vw, 2.75rem) 0 0;
  padding-top: 1.6rem;
  border-top: 1px solid var(--rule);
  grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr));
}
.mb26 .mb-proof b    { font-size: clamp(1.35rem, 2.6vw, 1.75rem);
                       line-height: 1.1; letter-spacing: -0.015em; }
.mb26 .mb-proof span { font-size: 0.85rem; max-width: 25ch; }

/* The result of one case study, set large enough to be read before the
   paragraph that explains it -- which is the point: the outcome first, the
   method second. */
.mb26 .mb-result {
  margin: 2rem 0 0;
  padding-block: 1.75rem;
  border-block: 1px solid var(--rule);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
.mb26 .mb-result b    { font-size: clamp(2.4rem, 5.6vw, 3.6rem);
                        line-height: 0.95; letter-spacing: -0.045em; }
.mb26 .mb-result span { font-size: 0.88rem; max-width: 27ch; }

/* Three of the four strips on the site hold bare numerals -- 26, 1,199, 9 --
   and a numeral fits any column. The fourth holds values that carry their unit,
   because "11" on its own beside "22" and "3" is not a measurement of anything.
   A monospace cell is a fixed width, so that strip is one character away from
   breaking: measured on / at 1440 the column is 267px, the ceiling puts a
   character at 32px, and eight fit. "22 months" is nine. It wrapped to two lines
   while "11 years" and "3 papers" stayed on one, which dropped its caption 54px
   out of line with theirs -- a row of measurements that no longer reads as a row.

   nowrap is the assertion, and it is global: a value that outgrows its column
   should overflow where it can be seen rather than quietly fall out of line
   again. The size cap is not global, because it costs something -- 19cqw holds
   nine characters inside the column at any width, but it binds on label length
   the CSS cannot see, so on a strip of numerals it would shrink type that was
   never in trouble. It goes on the strip that needs it, by name. */
.mb26 .mb-result b { white-space: nowrap; }
.mb26 .mb-result-unit li { container-type: inline-size; }
.mb26 .mb-result-unit b  { font-size: min(clamp(2.4rem, 5.6vw, 3.6rem), 19cqw); }

/* Plex Mono draws ½ as a stacked fraction inside one monospace cell, so at
   display size the halved-turnaround figure carried a third of the ink of the
   numerals beside it -- the weakest mark on the page was the strongest claim.
   The sans cut is a diagonal fraction that fills its box; a hair of extra size
   brings its cap height back to the digits'. The tilde stays mono. */
.mb26 .mb-frac {
  font-family: var(--sans); font-style: normal; font-weight: 600;
  font-size: 1.12em; letter-spacing: -0.02em;
}

/* ---------------------------------------------- problems (no cards) */

/* Opened and closed with the stronger rule, and ruled between its rows with the
   softer one -- which is what .mb-layers, .mb-roles, .mb-chrono and .mb-pubs all
   do, and what this list alone did not. It ruled its rows and then simply
   stopped, so nine items 259-322px tall read as a run with no visible end: 2,603
   measured pixels in which a reader scanning for their own laboratory could not
   see how much of it was left. Bracketing it costs two pixels of rule and the
   clearance either side, and turns the run into a set of nine.

   The margins are the ones the other four lists take, for the same reasons:
   1.75rem above so the opening rule reads as the top of a block rather than as
   an underline beneath the line before it, and the same below so the paragraph
   that follows is not sitting on the closing rule. */
.mb26 .mb-problems { list-style: none; margin: 1.75rem 0 0; padding: 0;
                     border-block: 1px solid var(--rule); }
.mb26 .mb-problems + p { margin-top: 1.75rem; }
.mb26 .mb-problems > li { padding-block: clamp(1.75rem, 3.5vw, 2.75rem); }
.mb26 .mb-problems > li + li { border-top: 1px solid var(--rule-soft); }
.mb26 .mb-problems h3 {
  margin: 0 0 0.6em;
  font-weight: 500;
  font-size: clamp(1.25rem, 2.4vw, 1.7rem);
  line-height: 1.28;
  letter-spacing: -0.02em;
  max-width: 30ch;
  color: var(--ink);
}
/* These nine headings carried generated curly quotes, hung outside the column so
   the letterforms rather than the punctuation set the left edge. The hanging was
   right and the quotes were wrong.

   Curly quotes with no attribution is the shape of a testimonial, and these are
   not testimonials -- they are hypotheticals, and the section says so in its own
   second line: "Said the way a laboratory director would say it, rather than the
   way a consultancy would." The page also carries a real quotation elsewhere,
   with a real <cite>, so the two were competing for the same signal and the one
   with nobody behind it was winning by volume: nine of them, in the largest type
   in the section. On a site whose whole discipline is that a claim is stated as
   what it is, this was the one place the visual language claimed more than the
   words did.

   Nothing replaces them, because nothing has to. Every heading is already in the
   client's grammar -- "We need", "Our LIMS", "We are bringing in" -- which no
   reader mistakes for the consultant's own voice, and the intro line names the
   device outright. The quotes were saying a second time, less honestly, what the
   sentence structure says for free. */
/* Was 60ch, which resolved to 612px and rendered at 78 characters -- the only
   body paragraph on the site the shared measure did not reach, because it set
   its own. */
.mb26 .mb-problems p { color: var(--ink-2); max-width: var(--measure); }

/* --------------------------------------------------------- outcomes */

.mb26 .mb-outcome { padding-block: clamp(1.5rem, 3vw, 2.25rem); }
.mb26 .mb-outcome + .mb-outcome { border-top: 1px solid var(--rule-soft); }
.mb26 .mb-outcome h3 { margin-bottom: 0.45em; }
.mb26 .mb-outcome p  { color: var(--ink-2); }
.mb26 .mb-outcome .mb-spec { display: block; margin-top: 0.7rem; }

.mb26 blockquote {
  margin: 1.5rem 0 0;
  padding: 0 0 0 1.5rem;
  border-left: 1px solid var(--accent);
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 62ch;
}
.mb26 blockquote cite {
  display: block; margin-top: 0.85rem; font-style: normal;
  font-family: var(--mono); font-size: 0.78rem; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-3);
}
/* The quote pays no bottom margin, and the cite and the fact line are the same
   small mono note in the same gray: run together they read as one caption and
   the attribution stops being an attribution.

   Paragraphs need the same rule for a different reason. Spacing on this page
   runs on margin-bottom, so a paragraph asks its predecessor for the gap above
   it -- and a quote that pays none leaves the next paragraph sitting flush
   against the cite, reading as a continuation of the attribution. Every quote
   on the site was followed by a .mb-spec until / gained one that is followed by
   prose. */
.mb26 blockquote + .mb-spec,
.mb26 blockquote + p { display: block; margin-top: 2rem; }

/* ------------------------------------------------------ layer stack */

.mb26 .mb-layers { list-style: none; margin: 1.75rem 0 0; padding: 0;
                   border-top: 1px solid var(--rule); }
.mb26 .mb-layers li {
  display: grid; gap: 0.15rem 1.5rem; align-items: baseline;
  padding-block: 1.05rem; border-bottom: 1px solid var(--rule-soft);
}
@media (min-width: 720px) {
  .mb26 .mb-layers li { grid-template-columns: 11.5rem minmax(0, 1fr); }
}
.mb26 .mb-layers dfn,
.mb26 .mb-layers > li > span:first-child {
  font-family: var(--mono); font-style: normal; font-size: 0.78rem;
  font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--accent);
}
.mb26 .mb-layers p { margin: 0; color: var(--ink-2); }
.mb26 .mb-layers + p { margin-top: 1.75rem; }

/* ------------------------------------------------------------- roles */

/* The career, as a dated list rather than a timeline: no spine, no dots, no
   alternating sides. A drawn timeline implies even spacing between jobs that
   ran 6 months and 11 years, which is exactly the thing the dates already say
   accurately. Same date column as .mb-layers so the two read as one system.
   <ol> because the order is the claim -- reverse chronological. */
.mb26 .mb-roles { list-style: none; margin: 1.75rem 0 0; padding: 0;
                  border-top: 1px solid var(--rule); }
.mb26 .mb-roles > li {
  display: grid; gap: 0.35rem 1.5rem;
  padding-block: 1.6rem; border-bottom: 1px solid var(--rule-soft);
}
@media (min-width: 720px) {
  .mb26 .mb-roles > li { grid-template-columns: 11.5rem minmax(0, 1fr); }
  /* The date sits on the role title's baseline, not the row's top edge. */
  .mb26 .mb-roles > li > span:first-child { padding-top: 0.28rem; }
}
.mb26 .mb-roles > li > span:first-child {
  font-family: var(--mono); font-size: 0.78rem; font-weight: 500;
  letter-spacing: 0.06em; color: var(--accent); white-space: nowrap;
}
.mb26 .mb-roles h3 {
  font-size: 1.06rem; font-weight: 600; line-height: 1.35;
  margin: 0 0 0.15rem; max-width: var(--measure);
}
/* Employer is the quieter half of the pair -- the role is what was done, the
   employer only says where. Not mono: it is a proper noun, not a measurement. */
.mb26 .mb-roles .mb-where {
  margin: 0 0 0.5rem; color: var(--ink-3); font-size: 0.9rem;
}
.mb26 .mb-roles p { margin: 0 0 0.6rem; color: var(--ink-2); }
.mb26 .mb-roles p:last-child { margin-bottom: 0; }

/* -------------------------------------------------------- chronology */

/* One project's sequence, in the same date column as .mb-roles and .mb-layers
   so all three read as one system. Only the two ends carry a date, because
   those are the two dates that can be checked; the steps between them are
   ordered and undated, and show an arrow instead -- they overlapped, and a
   date invented for each would be the one part of this section a reader could
   catch out. No spine and no dots, for the reason .mb-roles gives: a drawn
   line between rows claims the intervals are equal, and here they are not. */
/* The closing rule of the last row is a border, not a margin, so the paragraph
   that follows the chronology sits on top of it without this. */
.mb26 .mb-chrono { list-style: none; margin: 2rem 0 2.25rem; padding: 0;
                   border-top: 1px solid var(--rule); }
.mb26 .mb-chrono > li {
  display: grid; gap: 0.2rem 1.5rem;
  padding-block: 0.9rem; border-bottom: 1px solid var(--rule-soft);
}
@media (min-width: 720px) {
  .mb26 .mb-chrono > li     { grid-template-columns: 9.5rem minmax(0, 1fr); }
  /* Placed rather than flowed: a row with no date would otherwise put its text
     in the date column and break the only alignment this list has. */
  .mb26 .mb-chrono > li > b    { grid-column: 1; }
  .mb26 .mb-chrono > li > span { grid-column: 2; }
}
.mb26 .mb-chrono > li > b {
  font-family: var(--mono); font-weight: 500; font-size: 0.78rem;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--accent); white-space: nowrap;
}
/* The arrow is a continuation mark, not a date, so it is set in the annotation
   gray the rail uses rather than in the accent the real dates get. */
.mb26 .mb-chrono > li > b[aria-hidden] {
  color: var(--ink-3); font-size: 0.95rem; letter-spacing: 0;
}
.mb26 .mb-chrono > li > span { color: var(--ink-2); }
/* The Swedish page names its three steps as verbs with objects -- "Genomföra
   och lämna över" is 24 uppercase mono characters, about 198px, in a label
   column of 9.5rem (152px). With the list's nowrap it would run across the gap
   and under the first words of the step. The modifier lets a label wrap inside
   its column; the English lists keep nowrap because their labels are dates and
   single words that must never break at a dash. Below 720px the label has the
   whole row and nothing changes. */
.mb26 .mb-chrono-wrap > li > b { white-space: normal; }

/* ------------------------------------------------------- ink section */

.mb26 .mb-ink {
  background: var(--ink); color: var(--paper);
  border-top: 0;
}
/* `section + section` rules every join with a --rule-soft hairline, which is
   right on paper and a seam on ink: a pale line hard under a black band reads
   as the band's bottom edge failing to meet the page rather than as a divider.
   .mb-contact and .mb-panelband each zeroed their own top border for exactly
   this reason, one case at a time, because for a long while the only thing that
   ever followed an ink band was one of those two. The rule belongs to whatever
   comes after an ink band, so it is stated once here instead. It also reaches
   /about/, where a plain section follows the ink one and has been drawing that
   seam since the page was built. */
.mb26 .mb-ink + section { border-top: 0; }
.mb26 .mb-ink h2, .mb26 .mb-ink h3 { color: var(--paper); }
.mb26 .mb-ink p  { color: #a8b0b8; }
/* Scoped off .mb-cta, for the same reason .mb-contact is at the bottom of this
   file: this rule matches `.mb26 .mb-cta` on specificity and outranks it on
   source order, so it was painting --accent-lit onto a button whose fill and
   border are both --ink, inside a section whose background is also --ink. A
   black button on black ground, measured: fill and border each identical to
   the section behind them, so the only thing marking it as a control was the
   arrow. That was the CTA closing #tooling -- the strongest section on the
   home page, right after the printed-rack evidence -- reading as body copy.
   Contrast was never the problem (7.29:1), which is why the contrast pass
   found nothing; affordance was.

   The exclusion alone would leave a paper-on-ink button invisible on an ink
   ground, so the button inverts on the dark plate: paper fill, ink text. The
   sweep this bug asked for found no third instance -- the only other
   context-scoped link colours are the index list, the footer hover and the
   global hover, and none of them can match a .mb-cta. */
.mb26 .mb-ink a:not(.mb-cta)  { color: var(--accent-lit); }
.mb26 .mb-ink a:not(.mb-cta):hover { color: #a9cbf0; }
.mb26 .mb-ink .mb-cta {
  color: var(--ink); background: var(--paper); border-color: var(--paper);
}
@media (hover: hover) and (pointer: fine) {
  .mb26 .mb-ink .mb-cta:hover {
    color: var(--ink); background: var(--accent-lit); border-color: var(--accent-lit);
    box-shadow: 0 6px 18px -8px rgba(0, 0, 0, .6);
  }
}
.mb26 .mb-ink .mb-note,
.mb26 .mb-ink .mb-spec { color: #9ea5ac; }
.mb26 .mb-ink .mb-layers      { border-top-color: #2b3036; }
.mb26 .mb-ink .mb-layers li   { border-bottom-color: #23282d; }
/* Both label forms, not just <dfn>: the about page writes the layer label as a
   <span> and it was inheriting the paper accent onto ink at 2.8:1. */
.mb26 .mb-ink .mb-layers dfn,
.mb26 .mb-ink .mb-layers > li > span:first-child { color: var(--accent-lit); }
/* The boundary pairs came onto ink with #why-one-person, and both halves of the
   row needed their own value measured against it rather than inherited from
   paper: the label gray is --ink-2, which is 1.40:1 on ink, and the connector is
   --accent at 2.31:1. The connector takes the text floor rather than the 3:1
   non-text one, because it is content -- the comment on .mb-pairs says so, and
   the section is about the gap the arrow draws. Same two substitutions every
   other reversed role in this file makes. */
.mb26 .mb-ink .mb-pairs li { color: #a8b0b8; }
.mb26 .mb-ink .mb-pairs li > span + span::before { color: var(--accent-lit); }
.mb26 .mb-ink ::selection     { background: var(--accent-lit); color: var(--ink); }
/* The qualifier under each figure is a <span>, so it does not inherit the
   paragraph color the rest of this band is corrected with. */
.mb26 .mb-ink .mb-result      { border-block-color: #2b3036; }
.mb26 .mb-ink .mb-result b    { color: var(--paper); }
.mb26 .mb-ink .mb-result span { color: #a8b0b8; }

/* Platform roll -- nine names, set as data, not as logos we do not own. */
.mb26 .mb-platforms {
  list-style: none; margin: 2rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.5rem;
}
.mb26 .mb-platforms li {
  font-family: var(--mono); font-size: 0.78rem; font-weight: 500;
  letter-spacing: 0.03em;
  padding: 0.4rem 0.7rem;
  border: 1px solid #2f353b; border-radius: 2px; color: #c3cad1;
}

/* ------------------------------------------------------------ figures */

.mb26 figure { margin: 0; }
.mb26 .mb-figure img {
  display: block; width: 100%; height: auto;
  border-radius: 2px; background: var(--panel);
}
/* Captions are read in the reading voice since 2026-10-09. They were set in the
   mono annotation face at 0.76rem, which is right for a date or a dimension and
   wrong for a sentence: the captions are explanations of what the picture
   proves, and small monospace prose is the hardest text on the page to read.
   Mono stays for .mb-spec, the rail notes, the band and the drawing legend --
   the short data labels. */
.mb26 .mb-figure figcaption {
  margin-top: 0.85rem;
  font-family: var(--sans); font-size: 0.88rem; line-height: 1.55;
  color: var(--ink-2); max-width: 52ch;
}
.mb26 .mb-ink .mb-figure figcaption { color: #9ea5ac; }
.mb26 .mb-figure { margin-top: 2.5rem; }
.mb26 .mb-ink .mb-figure { margin-top: 2.75rem; }
/* Figures carry no bottom margin, on the same principle as the paragraphs: what
   follows one asks for its own clearance. On /work/ that is always a heading or
   a .mb-spec, which have theirs; the lone photograph on the homepage is the one
   place a bare paragraph comes next. */
.mb26 .mb-figure + p { margin-top: 2.5rem; }
/* Was an adjacent-sibling rule until the Alinity figure's caption gave up its
   argument to a paragraph, which then sat between the two. One .mb-spec follows
   the figure in this section, so the general sibling combinator is exact. */
.mb26 .mb-ink .mb-figure ~ .mb-spec { display: block; margin-top: 1.75rem; }
/* One treatment for a photograph standing on its own, ink or paper. It was
   scoped to .mb-ink while every lone photograph happened to sit on a plate, and
   the one that did not -- the Eppendorf deck on /work/ -- fell back to running
   the full 872px column with its caption stranded under the left third of it,
   296px wider than the text it illustrates and the only image on the site at
   that size. The grid is what makes it consistent: the picture takes the column
   less the rail, and the caption goes in the rail, set to the foot of the
   picture the way a print is annotated in its margin. */
@media (min-width: 900px) {
  .mb26 .mb-figure {
    display: grid; grid-template-columns: minmax(0, 1fr) 17rem;
    gap: 0 var(--gap); align-items: end;
  }
  .mb26 .mb-figure figcaption { margin-top: 0; padding-bottom: 0.35rem; }
}

/* The bench photograph and the drawings under it are the same setup twice, so
   they share one caption rather than repeating each other -- which makes that
   caption about four times the length of any other on the site. In the rail it
   sets 21 lines, and that is taller than the picture at every width where the
   shell is squeezed: the 900-975 band before the rail column appears, and again
   from 1100 until the viewport has grown enough to pay for it. Taller than the
   picture means the picture bottom-aligns against dead space above it (measured
   62px at 1100, 24px at 940). So this figure alone keeps the margin annotation
   only where the picture can hold it, and takes the .mb-figwide treatment --
   full column, caption underneath, set to the measure -- below that. */
@media (min-width: 900px) and (max-width: 1365px) {
  .mb26 .mb-figbench            { grid-template-columns: minmax(0, 1fr); }
  .mb26 .mb-figbench figcaption { margin-top: 0.9rem; padding-bottom: 0;
                                  max-width: var(--measure); }
}

/* Two ways of setting a photograph next to what it is evidence for. Neither is
   a card, and neither is a gallery: each pairing exists because the two things
   in it are the same claim seen twice. They share a caption voice -- mono,
   small, the annotation gray -- so the page reads as one hand. */
.mb26 .mb-two figcaption,
.mb26 .mb-compare figcaption {
  margin-top: 0.75rem;
  font-family: var(--sans); font-size: 0.88rem; line-height: 1.55;
  color: var(--ink-2);
}
.mb26 .mb-two img,
.mb26 .mb-compare img {
  display: block; width: 100%; height: auto;
  border-radius: 2px; background: var(--panel);
}
.mb26 .mb-ink .mb-two figcaption { color: #9ea5ac; }

/* .mb-aside is gone: photograph in one column, prose in the other, at roughly
   equal width. It was used once, on the whole-blood section of /work/, and the
   two columns could not both be right. The prose column ran about 500px, which
   is 45 characters -- well under the 68ch measure the rest of the page is set
   to -- so two paragraphs and a spec line ran tall, while the square photograph
   beside them finished 250px earlier and left the column empty from the caption
   down. Widening the picture would have squeezed the text further; narrowing it
   would have made the evidence smaller than the claim.

   That block now takes the .mb-figure treatment instead: prose at the full
   measure across the column, then the photograph across the column under it
   with its caption in the rail. Same pattern as the Eppendorf deck and the
   barcode read further down, so the page has one way of showing a photograph
   rather than two. .mb-two, the equal-weight pair, is the only side-by-side
   figure layout left. */

/* Two parts, one row. The pair is the argument -- one machined for heat, one
   keyed into an analyzer's own rack -- so they are set at equal weight rather
   than one leading and one following. */
.mb26 .mb-two { display: grid; gap: var(--gap); margin: 2.5rem 0; }
@media (min-width: 700px) {
  .mb26 .mb-two { grid-template-columns: 1fr 1fr; align-items: start; }
}

/* The bench as it stands, beside the bench as it was drawn. Neither one is the
   illustration of the other -- they are the same object at two moments -- so
   they are set at equal weight, and equal weight here means equal height, not
   equal width. Equal columns gave them equal width and 90px of raggedness,
   because the photograph is 1600x1191 and the elevation's viewBox is 2460x2420:
   1.343 against 1.017. Centering hid the ragged edge by splitting it, which put
   45px of air under the photograph and left the shared caption reading as the
   drawing's alone.

   So the columns carry the ratio of the two aspect ratios, 1.343/1.017 = 1.322,
   and the two render the same height at every width the band is used at. That
   is also why the photograph is the wider column: it is the shallower picture,
   not the more important one. The caption then runs under both, level with the
   foot of each, because it describes the pair and not either half. The
   photograph comes first here and on mobile alike: it is the thing that
   exists. */
.mb26 .mb-compare { display: grid; gap: var(--gap); margin: 2.5rem 0; }
@media (min-width: 820px) {
  .mb26 .mb-compare { grid-template-columns: 1.322fr 1fr; align-items: start; }
  .mb26 .mb-compare figcaption { grid-column: 1 / -1; margin-top: 0.35rem; }
}
.mb26 .mb-compare figcaption { max-width: 62ch; }

/* The elevation is a print, and a print reads as one reversed out of ink --
   that is how it was drawn and how /work/ still carries it. Rather than move a
   whole section to ink for one figure, the pair sits on a plate of it: the
   photograph and the drawing keep equal weight, the implementation section
   keeps its paper, and the plate gives that section the one dark beat it had
   nothing else to make. */
.mb26 .mb-plate {
  margin: 2.75rem 0;
  padding: clamp(1.5rem, 3.5vw, 2.5rem);
  border-radius: 3px;
}
.mb26 .mb-plate .mb-compare { margin: 0; }
.mb26 .mb-ink .mb-compare figcaption { color: #9ea5ac; }
/* The paper placeholder behind a loading photograph would flash on the plate. */
.mb26 .mb-ink .mb-compare img { background: #23282d; }

/* -------------------------------------------------------- the drawing */

/* Two views of one bench, held at a single scale: both viewBoxes are the same
   width, so equal columns render them equal without any arithmetic here. They
   stack below 760px, where a half-width view would set its dimensions at eight
   pixels and stop being a drawing. */
.mb26 .mb-dwgfig {
  margin: 2.75rem 0;
  padding-top: clamp(1.25rem, 3vw, 2rem);
  border-top: 1px solid #2b3036;
  display: grid;
  gap: clamp(1.5rem, 4vw, 2.5rem);
}
/* The caption ran in the right column, under the plan view, on the theory that
   the elevation was twice the plan's height and the caption could fill what the
   plan left -- a title block, bottom right. The elevation is not twice: measured,
   409px against 298px. The caption is eleven lines and 207px, so it overran the
   112px it was given and hung 95px below the elevation, a column of small type
   with nothing beside it and nothing under it. That is the "free flowing to the
   right" of it.

   A drawing sheet does carry a title block, but a title block is four lines, not
   eleven. So the caption goes where the one on .mb-compare goes: across the foot
   of both views, at its own 60ch measure, describing the sheet rather than one
   view on it. The views stay in equal columns -- both viewBoxes are 2460 wide,
   so equal columns are what holds the two of them at a single scale, and any
   arithmetic here would break that. They hang from a shared top edge, which is
   the projection: same origin, same scale, plan beside elevation. */
@media (min-width: 760px) {
  .mb26 .mb-dwgfig {
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }
  .mb26 .mb-dwgfig figcaption { grid-column: 1 / -1; }
}
.mb26 .mb-dwg { display: block; width: 100%; height: auto; }
.mb26 .mb-dwg text {
  font-family: var(--mono);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

/* Annotation scale. Text inside a viewBox is drawn in user units, so its size on
   screen is whatever the plot happens to be scaled to -- and this plot is small
   almost everywhere. Measured against the live pages, the elevation renders 261px
   wide on a 390px phone and only 376px on a 1440px desktop, which put the
   dimension figures at 8.3px and 11.9px: a drawing nobody can read the dimensions
   off is not a drawing. The measurements also showed the widest render is the odd
   one out -- around 700px viewport, single column and no rail, the same figure
   gets 569px -- so a single larger size would be shouting there.

   One knob, then, set per band, with the groups keeping the ratios the markup
   already established (72/78 and 68/78). The bands are not arbitrary widths: each
   one is a layout event that changes the plot scale. 560px is where the phone
   column gets wide enough to carry the figure at its drawn size; 760px is where
   the two views on /work/ go side by side; 820px is where the homepage photograph
   and elevation do the same. The 1100px rail narrows the column again, but not
   enough to need its own step.

   Set once, then measured across eleven widths on both pages, which found the
   view label -- the smallest of the three groups, at 0.87 -- sitting between
   9.6px and 10.2px in three of the bands. So the numbers below are the second
   set, sized off that measurement rather than off the ratio: floor 11px on
   every group at every width tested, and unchanged at 1280 and above, where
   the first set was already right and any more would shout.

   The knob has a ceiling as well as a floor, and the ceiling is 88. Above it
   the part labels stop fitting the parts they name: at 108 "Xevo TQ-XS" runs
   out of its box and "Workstation" is cut off by the edge of the viewBox. That
   is a property of the knob alone, not of the viewport -- the labels and the
   parts are both in user units, so the fit is the same at every render size.

   The ceiling was 96, set by eye off "fits or does not fit". Measured with
   getBBox against the rect each label names, 96 was not a ceiling so much as
   the last value before the wall: "Workstation" spanned 604.7 units inside a
   610-unit box, which is 2.6 units of clearance a side. Every other label had
   between 31.7 and 380.1. It did not overflow, it rested on both borders at
   once, and it was the only label on the sheet doing so -- one part in a
   drawing whose argument is that it was drawn properly. 88 gives it 26.9,
   which is the same order as the next tightest ("Sample organizer", 31.7).
   "Workstation" is the binding label: it is the longest word on the smallest
   box, so it sets this number for the whole sheet.

   Buying that fit costs rendered size, and it costs more than it first looked
   like. The bands below were drawn against 96 and had to be redrawn after this
   change: at 88 the bench labels fall under the 11px floor until 880, not 800,
   and the compare pair never clears it at all once it goes side by side. The
   ceiling is a fit constraint and the floor is a legibility one; they are
   measured separately, in user units and in rendered px, and the bands below
   are where the two are reconciled per figure. */
/* Two knobs, not one. The part labels are the only group whose size is bounded
   from above as well as below -- they have to fit inside parts drawn in user
   units -- so tying them to the same knob as the dimensions and the view label
   made a conflict out of two requirements that do not actually collide. --dwg-l
   follows --dwg-t unless a band pins it, which is what lets a tight plot raise
   the annotation for legibility without pushing the labels out of their parts. */
.mb26 .mb-dwg       { --dwg-t: 78; --dwg-l: var(--dwg-t); }
.mb26 .mb-dwg-txt   { font-size: calc(var(--dwg-t) * 1px); }
.mb26 .mb-dwg-lbl   { font-size: calc(var(--dwg-l) * 0.92px); }
.mb26 .mb-dwg-note  { font-size: calc(var(--dwg-t) * 0.92px); }
.mb26 .mb-dwg-view  { font-size: calc(var(--dwg-t) * 0.87px); }
/* The numbered key, which is the thing the three label-dropping bands below were
   dropping the labels FOR. The either/or in the paragraph above -- at a 293px
   plot the labels are either too small to read or too big to fit -- is a
   property of label LENGTH, not of glyph size. "Sample organizer" is sixteen
   characters set down a 250-unit face; "1952" is four and clears the floor in
   every band at the same knob. A digit is one, so it fits any part at any
   setting the dimensions are already legible at.

   Which is why the key is sized off --dwg-t and not off --dwg-l: it is not a
   part label, it is an annotation, and it reads at exactly the size the
   dimension figures read at -- the group already measured to an 11px floor
   across eleven widths. The ceiling that binds --dwg-l does not apply to it.

   Dropping the labels was right. Dropping them and leaving both captions saying
   "the sample organizer and the LC stack on their own table" about a drawing
   with no names in it was not: on a phone the caption named parts the reader
   had no way to pick out, and the figure quietly stopped being the thing the
   caption claimed it was. The numerals put the names back in the only form
   that fits, and the legend carries the words at body scale where there is
   room for them. */
.mb26 .mb-dwg-key    { font-size: calc(var(--dwg-t) * 1px); }
.mb26 .mb-dwg-key,
.mb26 .mb-dwg-legend { display: none; }
@media (max-width: 559px) {
  .mb26 .mb-compare .mb-dwg { --dwg-t: 124; }
  .mb26 .mb-dwgfig  .mb-dwg { --dwg-t: 104; }
  /* Seven part labels, and the longest is set down the 250-unit face of a cabinet
     943 units tall. Sized to stay legible it no longer fits inside the part it
     names; sized to fit it lands near 10px. At this plot scale the labels are the
     thing to drop -- the outline, the dimensions and the caption still carry the
     figure, and the <desc> names every part for anyone reading it aloud.

     The leader stays, because the key replaces what it was pointing at. It
     points at "Solvent bottles", the one label that had to sit outside the part
     it names; with the label gone it pointed at nothing -- a 39-unit stub off
     the bottle caps, a 4px dash on a phone, a stray line in a drawing whose
     whole claim is that it was drawn properly. With numeral 7 sitting where the
     label was, it terminates in something again, which is what a leader is for. */
  .mb26 .mb-dwg-lbl    { display: none; }
  .mb26 .mb-dwg-key    { display: inline; }
  .mb26 .mb-dwg-legend { display: grid; }
}
/* Where the two figures go side by side, and the plot gets tighter than
   anywhere else on the site. Both bands were first drawn to end at 1023, on the
   assumption that past that width the readable size and the fitting size are
   the same number. Measured on the live pages at the 88 ceiling, that is true
   of one figure and false of the other, and the difference is the rendered box,
   not the viewport:

     /work/  .mb-dwgfig   800 -> 310px box -> 10.2px   860 -> 336px -> 11.1px
                         1024 -> 393px      -> 12.9px  1440 -> 416px -> 13.7px
     /       .mb-compare  820 -> 253px box  (key)      1024 -> 312px -> 10.3px
                         1280 -> 324px      -> 10.7px  1920 -> 324px -> 10.7px

   The bench on /work/ gets a column to itself, so it grows out of the problem:
   from 880 its labels clear the 11px floor and keep clearing it. The compare
   pair never does. Once it goes side by side at 820 each drawing is pinned near
   320px however wide the window gets -- 1920 buys it nothing over 1280 -- and
   at the ceiling that fits "Workstation" its labels sit at 10.7px forever. So
   the compare figure is a permanent key-and-legend case, not a tablet one:
   below 820 it is stacked and full width and the labels are 12.4-17.9px, which
   is why they stay there. Above 820 there is no width at which they come back.

   The knobs are still two. Dimensions and view label hold 108 through the
   narrow bands and drop to 96 once there is room, and stay legible on their own
   terms throughout (11.1px at the worst point, 820 on the compare pair). Only
   the part labels are bounded above by what fits. */
@media (min-width: 760px) and (max-width: 1023px) {
  .mb26 .mb-dwgfig .mb-dwg        { --dwg-t: 108; --dwg-l: 88; }
}
@media (min-width: 760px) and (max-width: 879px) {
  .mb26 .mb-dwgfig .mb-dwg-lbl    { display: none; }
  .mb26 .mb-dwgfig .mb-dwg-key    { display: inline; }
  .mb26 .mb-dwgfig .mb-dwg-legend { display: grid; }
}
@media (min-width: 820px) and (max-width: 1023px) {
  .mb26 .mb-compare .mb-dwg        { --dwg-t: 108; }
}
@media (min-width: 820px) {
  .mb26 .mb-compare .mb-dwg-lbl    { display: none; }
  .mb26 .mb-compare .mb-dwg-key    { display: inline; }
  .mb26 .mb-compare .mb-dwg-legend { display: grid; }
}
@media (min-width: 1024px) {
  /* No --dwg-l for .mb-compare: its part labels are hidden at every width from
     820 up, so the label knob has nothing left to set. */
  .mb26 .mb-compare .mb-dwg { --dwg-t: 96; }
  .mb26 .mb-dwgfig  .mb-dwg { --dwg-t: 96; --dwg-l: 88; }
}
/* The drawing is inked for whichever surface it lands on. On paper it is a
   print: solid lines in the page's ink, dimensions and view label in the same
   annotation gray every other margin note uses. On ink it is the negative of
   itself, which is where it started and where /work/ still uses it -- those
   values are unchanged, only moved behind .mb-ink so the paper case can exist
   at all. */
.mb26 .mb-dwg-solid { stroke: var(--ink); }
.mb26 .mb-dwg-dim,
.mb26 .mb-dwg-arrow { stroke: var(--ink-3); }
.mb26 .mb-dwg-arrow { fill: var(--ink-3); stroke: none; }
.mb26 .mb-dwg-clear { color: var(--accent); }
.mb26 .mb-dwg-txt   { fill: var(--ink); }
.mb26 .mb-dwg-lbl   { fill: var(--ink-2); }
.mb26 .mb-dwg-key   { fill: var(--ink-2); }
.mb26 .mb-dwg-note  { fill: var(--accent-ink); }
.mb26 .mb-dwg-view  { fill: var(--ink-3); }

.mb26 .mb-ink .mb-dwg-solid { stroke: #c9d0d6; }
.mb26 .mb-ink .mb-dwg-dim,
.mb26 .mb-ink .mb-dwg-arrow { stroke: #7d858d; }
.mb26 .mb-ink .mb-dwg-arrow { fill: #7d858d; stroke: none; }
.mb26 .mb-ink .mb-dwg-clear { color: var(--accent-lit); }
.mb26 .mb-ink .mb-dwg-txt   { fill: #c9d0d6; }
.mb26 .mb-ink .mb-dwg-lbl   { fill: #a8b0b8; }
.mb26 .mb-ink .mb-dwg-key   { fill: #a8b0b8; }
.mb26 .mb-ink .mb-dwg-note  { fill: var(--accent-lit); }
.mb26 .mb-ink .mb-dwg-view  { fill: #9ea5ac; }   /* 7.2:1 on ink; #6b7278 was 3.7 */
/* The caption follows the drawing onto whichever surface it lands on, which it
   did not do before: it was written when .mb-dwgfig existed only inside .mb-ink,
   so the ink gray was set unconditionally. On paper that is about 2:1. Same two
   values every other figcaption uses -- --ink-3 on paper, #9ea5ac on ink. */
.mb26 .mb-dwgfig figcaption {
  font-family: var(--sans); font-size: 0.88rem; line-height: 1.55;
  color: var(--ink-2); max-width: 56ch;
}
.mb26 .mb-ink .mb-dwgfig figcaption { color: #9ea5ac; }

/* The legend for the key above. It sits between the views and the caption, and
   it spans the foot for the same reason the caption does: it belongs to the
   sheet, not to one view on it. In the two side-by-side bands the figure is a
   two-column grid, so without the span it would sit under the elevation and
   leave the plan with a hole beside it.

   Set in the mono face at the caption's size, because that is what it is -- the
   part of the caption that had to be drawn rather than written. Two columns of
   auto-fit at 11rem: seven items in one column is a list, seven in two is a
   title block, and the phone gets whichever its width allows. The numerals are
   real text in an <ol>, not generated content, so they are read out with the
   names; the ones in the drawing sit inside an SVG with role="img" and are not
   announced twice. */
.mb26 .mb-dwg-legend {
  grid-column: 1 / -1;
  margin: 0;
  padding: 0;
  list-style: none;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.3rem 1.5rem;
  font-family: var(--mono); font-size: 0.72rem; line-height: 1.5;
  color: var(--ink-2);
}
.mb26 .mb-dwg-legend li { display: grid; grid-template-columns: 1.6em 1fr; }
.mb26 .mb-dwg-legend b {
  font-weight: 400; font-variant-numeric: tabular-nums; color: var(--accent-ink);
}
.mb26 .mb-ink .mb-dwg-legend   { color: #a8b0b8; }
.mb26 .mb-ink .mb-dwg-legend b { color: var(--accent-lit); }

/* One full-width band lifted onto the panel, so that whatever sits in it reads
   as an inset plate rather than another block of page. It carried a photograph
   until the lifecycle scale took the slot; the figure rules below still apply
   to anything placed here later, which is why they stayed. */
.mb26 .mb-panelband {
  background: var(--panel);
  border-top: 0;
  padding-block: clamp(2.25rem, 4vw, 3rem);
}
.mb26 .mb-panelband .mb-figure { display: grid; gap: 0 var(--gap); }
@media (min-width: 900px) {
  .mb26 .mb-panelband .mb-figure {
    grid-template-columns: minmax(0, 1fr) 20rem;
    align-items: end;
  }
  .mb26 .mb-panelband figcaption { margin-top: 0; padding-bottom: 0.35rem; }
  /* A 2.2:1 panorama of a whole instrument earns the full column. Setting its
     caption in a side rail would spend a third of that width on one line of
     type, so this figure keeps the width and drops the caption underneath. */
  .mb26 .mb-panelband .mb-figwide            { grid-template-columns: minmax(0, 1fr); }
  .mb26 .mb-panelband .mb-figwide figcaption { margin-top: 0.9rem; padding-bottom: 0;
                                             max-width: var(--measure); }
}

/* The sample flow is the one portrait plot on the site -- eleven steps down the
   page rather than a bench across it -- so it wants a narrow column with the
   caption beside it, not a full-width band with the caption underneath. Placed
   after .mb-panelband on purpose: both selectors carry the same specificity, and
   this figure needs its own caption column rather than the band's 20rem rail.

   22rem is set from the annotation rather than from the picture. At that width
   the plot scales to 0.4, which puts the sub-notes near 12px -- and every one of
   them is written to 26 characters or fewer so that the longest still clears the
   bracket at x=800 when the phone band opens the knob up to 38. */
.mb26 .mb-flowfig { display: grid; gap: clamp(1.5rem, 4vw, 2.5rem); margin: 0; }
.mb26 .mb-flowfig .mb-dwg-flow { max-width: 22rem; }
@media (min-width: 900px) {
  .mb26 .mb-flowfig { grid-template-columns: 22rem minmax(0, 1fr); align-items: center; }
}
.mb26 .mb-flowfig figcaption {
  font-family: var(--sans); font-size: 0.88rem; line-height: 1.55;
  color: var(--ink-2); max-width: 44ch; margin-top: 0;
}
/* This drawing's one rotated label reads downward, rotate(90). The bench
   elevation's rotated labels and its 1952 dimension read upward, rotate(-90).
   Two conventions in one drawing system, and it is deliberate rather than
   drift, so it is written here before someone reconciles them.

   The bench is a dimensioned engineering drawing and upward is the convention
   for one: vertical dimension text reads from the right-hand side of the sheet.
   Its 1952 has to be set that way, and the part labels follow it because they
   share the sheet. This is not that drawing -- it is a flow chart with no
   dimensions in it, so there is nothing for the record label to be consistent
   with here, and the choice is free.

   It was spent on the reader. At rotate(-90) the label ran bottom-to-top
   against a page that scrolls top-to-bottom, and the bracket line struck
   through it at every one of the three annotation knobs. Flipping it and
   moving the bracket 20 units left clears the text by 6.2-8.5 user units at
   34, 38 and 44 with the label still inside the viewBox. Reverting it to match
   the bench brings the strike-through back. */
.mb26 .mb-dwg-flow { --dwg-t: 34; }
/* Measured across seven widths on the live page. The plot is the column less
   91px of shell padding, capped at the 22rem above, so it runs 229px at a 320
   viewport, 299px at 390, and 352px from 443 up -- and the annotation sizes
   below are set off those numbers rather than off the ratios.

   384 is not an arbitrary boundary. Holding the view label at the file's 11px
   floor needs a plot of 293px, which is a 384px viewport: above it the sub-notes
   fit and stay legible, below it they do not. */
@media (max-width: 559px) {
  .mb26 .mb-dwg-flow { --dwg-t: 38; }
  /* The band further up hides .mb-dwg-lbl on phones, because on the bench
     elevation the labels are captions on parts too small to hold them. Here the
     sub-notes are half of what the drawing says -- a step name without them is
     just a word -- and down to 384 they are short enough to fit. Kept. */
  .mb26 .mb-dwg-flow .mb-dwg-lbl { display: inline; }
}
/* Under 384 the sub-notes go, and the eleven step names take the size they
   free up: 11.4px at a 320 viewport rising to 14.6px at 383, against 9.9px if
   the notes had stayed. The record label and the view label sit at 10.5 and 9.9
   at the very bottom of that band, under the floor the rest of the file keeps.
   There is no value that clears it -- at a 229px plot the annotation is either
   too small to read or too wide for the viewBox -- so the size is spent on the
   step names, which are the chain the drawing exists to show. */
@media (max-width: 383px) {
  .mb26 .mb-dwg-flow { --dwg-t: 44; }
  .mb26 .mb-dwg-flow .mb-dwg-lbl { display: none; }
}

/* The portrait is small on purpose: this is a person, not a hero image. It is
   also a margin annotation, so from 1100px it goes where every other annotation
   on this page goes -- the rail, at the rail's own width, under the credentials
   it belongs to. Floating it into the text column only made it look stranded. */
/* This used to zoom and offset the image inside the frame, because the file the
   markup pointed at was 2560x3648 with only 470,600 -> 2460,3148 opaque: 62%
   transparent canvas, which is why the portrait read as a small picture
   stranded in a large empty box. That file was a pre-crop orphan. The
   attachment itself had been recropped to the plate long before and the markup
   was never moved onto it, so the CSS was reconstructing a crop that already
   existed. Both pages now point at the cropped file and the image simply fills
   its frame. */
.mb26 .mb-portrait {
  width: 172px; margin: 0 0 2rem;
  aspect-ratio: 896 / 1195;
  overflow: hidden;
  border: 1px solid var(--rule); border-radius: 2px;
  /* The negative is a dark field now, so the placeholder behind a loading
     photograph is dark too: paper would flash a white rectangle in its place. */
  background: #0c0d0e;
}
.mb26 .mb-portrait img {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
/* The text column is taller than note + portrait, and with only two auto rows
   the grid shared its surplus between them -- which pushed the portrait 150px
   clear of the credentials it annotates. A third row takes the slack instead,
   so rows 1 and 2 stay content-sized and the portrait sits directly under the
   note, as the comment above always claimed it did. */
@media (min-width: 1100px) {
  .mb26 .mb-railed:has(> .mb-portrait)                   { grid-template-rows: auto auto 1fr; }
  .mb26 .mb-railed:has(> .mb-portrait) > .mb-note        { grid-area: 1 / 1; }
  .mb26 .mb-railed:has(> .mb-portrait) > .mb-portrait    { grid-area: 2 / 1;
                                                           width: 100%;
                                                           margin: 1.6rem 0 0; }
  .mb26 .mb-railed:has(> .mb-portrait) > .wp-block-group { grid-area: 1 / 2 / 4 / 3; }
}

/* ------------------------------------------------------- publications */

.mb26 .mb-pubs { list-style: none; margin: 1.5rem 0 0; padding: 0; }
/* The measure belongs to the text, not to the row: putting it on the <li> made
   the rule stop 160px short of the right edge every other hairline on the page
   lands on. Same shape as .mb-problems, which constrains its <p> and lets the
   border run the full column. */
.mb26 .mb-pubs li {
  padding-block: 1.1rem; border-bottom: 1px solid var(--rule-soft);
}
/* Only the title needs it: <b> is already display:block, and the journal/year
   line under it is inline and never approaches the measure. */
.mb26 .mb-pubs li:first-child { border-top: 1px solid var(--rule); }
.mb26 .mb-pubs b  { font-weight: 500; display: block; margin-bottom: 0.3rem;
                    max-width: 72ch; }
.mb26 .mb-pubs i  { font-style: italic; color: var(--ink-2); }
.mb26 .mb-pubs .mb-spec { margin-left: 0.5rem; }

/* The citations link out now. Four accent-blue titles stacked in a row would
   turn a quiet credential list into a link dump, so the titles keep the page's
   ink and carry only a hairline underneath -- the same rule color the list is
   already ruled with. It warms to the accent on hover, which is where the
   affordance belongs on a list nobody scans for links. */
.mb26 .mb-pubs b a {
  color: inherit;
  text-decoration-color: var(--rule);
}
.mb26 .mb-pubs b a:hover {
  color: var(--accent-ink);
  text-decoration-color: currentColor;
}

/* ------------------------------------------------------------ contact */

.mb26 .mb-contact { background: var(--panel); border-top: 0; }
.mb26 .mb-contact ::selection { background: var(--accent); color: #fff; }
/* Every link in this section sits on the panel, where --accent measures 6.66:1
   -- under the floor the rest of the page now holds, on the surface carrying the
   email address and the privacy policy link. --accent-ink is the same hue one
   step down the ramp and clears it at 9.09, so the section keeps the identity
   blue rather than dropping to ink. Hover goes to full ink, because a second
   blue at this darkness would not read as a state change.

   Scoped off .mb-cta, which is a filled button and not a text link. This rule
   outranks `.mb26 .mb-cta` on both specificity and source order, so it was
   painting --accent-ink onto the ink fill at 1.69:1 -- the primary action on
   /work/ and /about/, unreadable since those pages were built, and invisible
   here because the homepage keeps its CTAs outside this section. The hover pair
   needs the same exclusion, or the button goes ink on accent-ink instead. */
.mb26 .mb-contact a:not(.mb-cta)       { color: var(--accent-ink); }
.mb26 .mb-contact a:not(.mb-cta):hover { color: var(--ink); }

/* Contact Form 7, wearing the page rather than the plugin default.
 *
 * The site's Additional CSS already dresses this form under `#contact .wpcf7
 * .mb-form`, in the old blue. That is an ID selector, so every rule here has to
 * carry #contact too or it silently loses. Nothing outside .mb26 is touched. */
.mb26 #contact .wpcf7 { max-width: 34rem; margin-top: 2rem; }
/* The field names are real <label for> elements now. They used to be a styled
   div, which looked identical and told a screen reader nothing -- three required
   fields with no accessible name, on the one page that exists to be filled in.
   Both selectors are kept: the site-wide Additional CSS still dresses the old
   `.mb-label > p` shape in the previous palette, so if the form template is ever
   reverted the styling degrades rather than disappears. */
.mb26 #contact .wpcf7 .mb-field  { margin-bottom: 1.35rem; }
.mb26 #contact .wpcf7 .mb-form p { margin: 0; }
.mb26 #contact .wpcf7 .mb-label,
.mb26 #contact .wpcf7 .mb-label p {
  display: block;
  margin: 0 0 0.45rem;
  font-family: var(--mono); font-size: 0.72rem; font-weight: 500;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-3);
}
.mb26 #contact .wpcf7 .mb-form input[type=text],
.mb26 #contact .wpcf7 .mb-form input[type=email],
.mb26 #contact .wpcf7 .mb-form input[type=tel],
.mb26 #contact .wpcf7 .mb-form textarea {
  display: block; width: 100%; margin-top: 0.4rem;
  font: inherit; font-size: 1rem; color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule); border-radius: 2px;
  padding: 0.7rem 0.8rem;
  transition: border-color 140ms ease, box-shadow 140ms ease;
}
.mb26 #contact .wpcf7 .mb-form textarea { min-height: 9rem; resize: vertical; }
.mb26 #contact .wpcf7 :is(input, textarea)::placeholder { color: var(--ink-3); }
.mb26 #contact .wpcf7 .mb-form :is(input, textarea):focus {
  border-color: var(--accent); outline: none;
  box-shadow: 0 0 0 3px var(--wash);
}
/* The halo above is --wash on the contact panel: 1.01:1, which is no ring at
   all. It stays, because on a wide field the tinted border is a pleasant
   pointer-focus cue, but it cannot be the only thing a keyboard visitor gets.
   Same shape as the submit button's ring, and the same specificity (1,4,1) --
   :is() contributes the highest of its arguments -- so this has to sit after
   the rule above to win. */
.mb26 #contact .wpcf7 .mb-form :is(input, textarea):focus-visible {
  outline: 3px solid var(--accent); outline-offset: 3px;
}
/* Sized against the hero CTA, which is 284px. This is the button that actually
   does something and it was 97px: the decorative twin outweighed the real one.
   Full width of the field column on a phone, where it is the last thing under
   the thumb. */
.mb26 #contact .wpcf7 .mb-submit input[type=submit] {
  font: inherit; font-weight: 500; line-height: 1; cursor: pointer;
  min-width: 15rem;
  color: var(--paper); background: var(--ink);
  border: 1px solid var(--ink); border-radius: 2px;
  padding: 0.95rem 1.5rem;
  transition: background 160ms ease, box-shadow 160ms ease,
              transform 160ms cubic-bezier(.16,1,.3,1);
}
@media (max-width: 520px) {
  .mb26 #contact .wpcf7 .mb-submit input[type=submit] { width: 100%; }
}
/* Astra ships `input[type=submit]:focus { outline: 0 }`, which outranks the
   scoped :focus-visible, and this element's own `border` shorthand above beats
   the field rule's border-color at equal specificity. Between them the button
   focused to a 1.00:1 wash ring on the panel -- i.e. to nothing, on the one
   control the whole page exists to reach. Stated here explicitly instead. */
.mb26 #contact .wpcf7 .mb-submit input[type=submit]:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 3px;
}
/* The hero CTA only jumps to this form; this button is the actual action, so it
   gets the same press. It had the hover and not the :active, which left the
   one click that matters as the only one with no tactile answer. */
.mb26 #contact .wpcf7 .mb-submit input[type=submit]:active:not(:disabled) {
  transform: translateY(1px);
}
@media (hover: hover) and (pointer: fine) {
  .mb26 #contact .wpcf7 .mb-submit input[type=submit]:hover:not(:disabled) {
    background: var(--accent-ink); border-color: var(--accent-ink);
    color: var(--paper);
    box-shadow: 0 6px 18px -8px rgba(21, 24, 27, .5);
  }
}
/* There used to be a :disabled rule here, on the belief that Turnstile disables
   the button until the challenge clears. It does not, and never did: the plugin
   injects `.wpcf7-submit { pointer-events: none; opacity: .5 }` and releases it
   in script -- `disable-submit.js` does not contain the word `disabled`. So the
   rule could not match, and the waiting state was a washed-out button with no
   explanation. There is no class or attribute to hook, so the explanation is
   written next to the widget instead, where it is true at every moment.

   The hint is still the honest place for the explanation, but "true at every
   moment" was measured and is not: sampled five times across 25 seconds on the
   live page, the button sat at opacity .5 with pointer-events none the whole
   time while the hint above it read "there is nothing to solve". Composited at
   half opacity its label falls to about 3.4:1 -- under AA, on the only control
   the page has. `pointer-events: none` already stops the click on its own, so
   the opacity is redundant belt-and-braces bought at the cost of legibility:
   drop it and let the widget above carry the pending state, which it already
   does. (A headless browser never gets a Turnstile token by design, so the
   25-second wait is that client's ceiling, not a real visitor's -- but the
   washed-out state is what every visitor sees until the challenge clears.) */
.mb26 #contact .wpcf7 .mb-submit input[type=submit] { opacity: 1 !important; }
/* Dropping the opacity left the pending state with no form of its own, so it is
   stated here instead: outlined while the token is missing, filled once it
   exists. Both are --ink on the panel, 15.4:1 either way, so the waiting button
   is exactly as legible as the ready one -- it just does not read as pressable
   yet. mb26.php drives the attribute off the token, so a visitor whose check
   clears before they reach the button never sees this state at all. The :hover
   twin is here because the fill rule above is one class heavier; the plugin's
   `pointer-events: none` ought to make hover unreachable while pending, but
   that is the plugin's promise, not ours. */
.mb26 #contact .wpcf7 form[data-mb-verify=pending] .mb-submit input[type=submit],
.mb26 #contact .wpcf7 form[data-mb-verify=pending] .mb-submit input[type=submit]:hover {
  color: var(--ink); background: transparent;
  border-color: var(--ink); box-shadow: none;
  /* Measured 2026-10-15 in the app's browser pane: with the button's 0.16s
     background transition in force, the pending state computed the filled
     ink background under the pending rule's ink text for the whole time the
     page was open -- the transition from the filled state never completed --
     and only `transition: none` returned the transparent fill. A state the
     visitor has to read must not depend on a transition finishing. */
  transition: none;
}
/* Stays empty until the wrapper catches a click the button could not take. */
.mb26 #contact .wpcf7 .mb-verify-msg {
  margin: 0.5rem 0 0; font-size: 0.82rem; color: var(--ink-2);
  max-width: 34rem;
}
.mb26 #contact .wpcf7 .mb-verify-msg:empty { display: none; }
.mb26 #contact .wpcf7 .mb-hint {
  margin: 0.5rem 0 0; font-size: 0.82rem; color: var(--ink-3);
  max-width: 34rem;
}
.mb26 .wpcf7-not-valid-tip { color: #912929; font-size: 0.82rem; margin-top: 0.35rem; }
/* Server-side (CF7) and client-side (the browser, once the field has been left
   in a bad state) mark the same failure the same way. :user-invalid rather than
   :invalid so an untouched empty field is not red before it has been tried. */
.mb26 #contact .wpcf7 .mb-form :is(input, textarea).wpcf7-not-valid,
.mb26 #contact .wpcf7 .mb-form :is(input, textarea):user-invalid {
  border-color: #912929;
}
.mb26 .wpcf7-response-output {
  margin: 1.25rem 0 0 !important; padding: 0.8rem 1rem !important;
  border: 1px solid var(--rule) !important; border-radius: 2px;
  background: var(--paper); font-size: 0.9rem;
}
/* The Turnstile plugin adds its own <br> and a -15px bottom margin around the
   widget. Its field already carries the form's rhythm, so both are removed
   rather than compounded into a hole above the button. */
.mb26 #contact .cf-turnstile     { margin-bottom: 0; }
.mb26 #contact .cf-turnstile-br  { display: none; }
.mb26 #contact .cf7-cf-turnstile { margin-bottom: 0 !important; }
/* Turnstile pins its widget content to a fixed 300px inside a shadow root, so a
   rect scan cannot see it and below ~341px the whole page scrolls sideways --
   measured at 341 against a 320 viewport. Scaling keeps the widget entirely
   visible and clickable rather than clipping it, and the negative margin
   reclaims the height the transform no longer occupies. */
@media (max-width: 340px) {
  .mb26 #contact .cf-turnstile {
    transform: scale(0.86);
    transform-origin: left top;
    margin-bottom: -10px;
  }
}

/* ------------------------------------------------ theme overrides */

/* The site header is shared with every other page, so it is not rebuilt here --
   only recoloured, inside the scope, so the stock blue and the white bar do not
   sit on top of the paper. */
.mb26 .ast-primary-header-bar,
.mb26 .ast-primary-header-bar .site-primary-header-wrap,
.mb26 #ast-mobile-header .ast-primary-header-bar {
  background-color: var(--paper);
  border-bottom: 1px solid var(--rule-soft);
}
.mb26 .main-header-menu .menu-link,
.mb26 .main-header-menu > li > a {
  color: var(--ink-2);
  font-family: var(--sans);
  font-size: 0.95rem;
  letter-spacing: -0.005em;
}
.mb26 .main-header-menu .menu-item:hover > .menu-link,
.mb26 .main-header-menu .current-menu-item > .menu-link { color: var(--ink); }

/* The Services dropdown (2026-10). Astra's submenu arrives as a white box with
   a drop shadow and a 2px accent top border -- a card, which this sheet forbids.
   Restated for paper: a hairline box, no radius, no shadow, rows ruled with the
   soft hairline, links in the bar's own colours. The :before hover bridge that
   Astra draws between the parent and its submenu is left alone -- it is what
   keeps the menu open while the pointer travels down to it. Specificity is one
   class over Astra's own rules on purpose, so neither load order nor its inline
   dynamic CSS decides. */
.mb26 .main-header-menu .sub-menu,
.mb26.ast-desktop .main-header-menu .sub-menu {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 0;
  box-shadow: none;
}
.mb26 .main-header-menu .sub-menu .menu-link {
  color: var(--ink-2);
  font-size: 0.92rem;
  border-bottom: 1px solid var(--rule-soft);
}
.mb26 .main-header-menu .sub-menu .menu-item:last-child > .menu-link { border-bottom: 0; }
.mb26 .main-header-menu .sub-menu .menu-item:hover > .menu-link,
.mb26 .main-header-menu .sub-menu .current-menu-item > .menu-link {
  color: var(--ink);
  background: var(--panel);
}
.mb26 .main-header-menu .menu-item > .ast-menu-toggle { color: var(--ink-2); }
.mb26 .main-header-menu .menu-item:hover > .ast-menu-toggle,
.mb26 .main-header-menu .current-menu-ancestor > .ast-menu-toggle { color: var(--ink); }
.mb26 .main-header-menu .current-menu-ancestor > .menu-link { color: var(--ink); }

/* Under the mobile dropdown the submenu is an accordion inside the bar, not a
   floating box: no border of its own, the paper ground, and the same hairline
   between rows that the desktop box uses. */
.mb26 .ast-mobile-header-content .main-header-menu .sub-menu,
.mb26 .ast-header-break-point .main-header-menu .sub-menu {
  border: 0;
  background: var(--paper);
}

/* Astra's mobile menu carries its own colour rules at four classes deep
   (.ast-builder-menu-mobile .main-navigation .menu-item.current-menu-item >
   .menu-link, and the same for the ancestor and the toggle), which outrank the
   bar rules above and paint the open parent and its chevron in Astra's blue.
   Restated one class deeper: the bar's own inks, the current page and the open
   parent in full ink, everything else in the reading grey. Measured 2026-10-09
   on /iso-15189-quality/ at 375px: rgb(0, 94, 233) before, var(--ink) after. */
.mb26 .ast-builder-menu-mobile .main-navigation .main-header-menu .menu-item > .menu-link,
.mb26 .ast-builder-menu-mobile .main-navigation .main-header-menu .menu-item > .ast-menu-toggle {
  color: var(--ink-2);
}
.mb26 .ast-builder-menu-mobile .main-navigation .main-header-menu .menu-item.current-menu-item > .menu-link,
.mb26 .ast-builder-menu-mobile .main-navigation .main-header-menu .current-menu-ancestor > .menu-link,
.mb26 .ast-builder-menu-mobile .main-navigation .main-header-menu .current-menu-ancestor > .ast-menu-toggle,
.mb26 .ast-builder-menu-mobile .main-navigation .main-header-menu .menu-item:hover > .menu-link {
  color: var(--ink);
}

/* Astra also prints an SVG arrow (<span class="ast-icon icon-arrow">) inside
   every submenu link on the mobile accordion. The sheet draws no arrows,
   chevrons or dots between the items of a list -- the indent already says
   these are children -- so the span is hidden and its indent kept. Astra
   turns the arrow back on at the break point with
   `.ast-header-break-point .main-navigation ul .sub-menu
   .menu-item:not(.menu-item-has-children) .menu-link .icon-arrow:first-of-type`
   (eight classes, a pseudo-class and an element), so this selector is written
   to outrank it rather than to read well; measured 2026-10-09. */
.mb26.ast-header-break-point .ast-builder-menu-mobile .main-navigation ul .sub-menu .menu-item .menu-link .ast-icon.icon-arrow:first-of-type,
.mb26 .ast-builder-menu-mobile .main-navigation .sub-menu .menu-link .ast-icon.icon-arrow {
  display: none;
}

/* The header button is gone, and so are its rules. Three of them styled
   .ast-header-button-1 .ast-custom-button -- fill, hover, and a :focus-visible
   override written because Astra's own `outline: thin dotted` outranked the
   global ring at mb-2026.css:76 for "the first tab stop after the logo".
   Measured on all four live pages: ast-header-button occurs 0 times, and the
   single ast-custom-button hit is a font-family selector inside Astra's own
   inline <style>, not an element. There is no such tab stop and there has not
   been one for as long as these pages have been deployed.

   Deleted rather than kept against a future restore: rules for an element that
   does not exist read as a working feature, and the next reader trusts them
   over the page. DESIGN.md's Navigation section carried the same claim and is
   corrected in this change. If the button is ever wanted back, it is a header
   builder item plus these four rules again -- both cheaper to rewrite than a
   stale block is to keep honest. */
.mb26 .ast-mobile-menu-trigger-fill { background: var(--ink); color: var(--paper); }

/* The header is not sticky, and there is nothing here to make it so. There was:
   `position: sticky` on .ast-main-header-wrap with #ast-desktop-header and
   #ast-mobile-header forced static under it, written to keep a control on screen
   down a very long page. It never pinned. #ast-desktop-header stays the
   containing block and is exactly the header's own 156px, so the bar's bottom
   edge measured -44, -444, -1,844, -7,844 and -19,844 at scrollY 200, 600,
   2,000, 8,000 and 20,000 -- one pixel of travel per pixel scrolled, which is
   what a header that is not sticky at all does.

   The long page is answered by the section index under the hero and by the
   in-flow .mb-cta blocks, and a persistent bar was then ruled out on its merits.
   So the rules are deleted rather than repaired: left in place they read as a
   working feature and invite the argument to be had again. mb26.php:134 has
   always described the behavior correctly.

   That answer was only half true when it was written, and the half that was
   false is the half this note leans on. Measured: the four homepage CTAs sat at
   y 995, 10,202, 14,346 and 20,836 on a 23,452px page, so the first gap was
   9,207px -- and it covered #sample-and-record, #systems-and-lims, #how-i-work
   and every one of the nine problem statements, which is the stretch where a
   visitor recognises their own laboratory. The index does not cover it either:
   it sits at y 1,149, above the gap, so a reader eight thousand pixels in has
   neither an action nor a way back to one.

   The fix is the one this note already claimed: an in-flow block inside the
   gap, at the end of #sample-and-record where the section closes on a cost.
   That halves it rather than adding a bar, which stays ruled out. The second
   half is one continuous section that ends in its own CTA, which is a reader
   finishing what they started, not a desert. */

/* The hero's CTA is a 19,000px jump to the form, and the section index below it
   plus the three in-flow CTAs make jumps of that order routine. Under
   `scroll-behavior: auto` any of them reads as a page change rather than as
   movement within one document. Chrome caps the duration rather than scaling it
   with distance, so the long ones do not become proportionally longer -- they
   stay a beat, which is all that is wanted.

   On the root, not on .mb26: the property only propagates to the viewport from
   the root element, so on <body> it would silently do nothing. The
   prefers-reduced-motion block further up handles animations and transitions and
   does not touch this one, so the opt-out is spelled out here. */
html:has(body.mb26) { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html:has(body.mb26) { scroll-behavior: auto; }
}

/* The footer is shared the same way, and the stock pale blue reads as a
   different website bolted to the bottom of this one. Recoloured, not rebuilt:
   the same panel the contact section already sits on, closed with a hairline. */
.mb26 .site-primary-footer-wrap {
  background-color: var(--panel);
  border-top: 1px solid var(--rule);
  padding-block: clamp(2.25rem, 4vw, 3.25rem);
}
/* Astra's own 1200px container is 58px wider on each side than .mb-shell, so
   the page's closing element hung outside the margins everything above it
   keeps. The width is set by astra-theme-css-inline-css on
   .site-primary-footer-wrap[data-section] .ast-builder-grid-row, which outranks
   a plain two-class override -- hence the attribute selector here, which beats
   it on specificity rather than with !important. */
.mb26 .site-primary-footer-wrap[data-section] .ast-builder-grid-row {
  max-width: var(--page);
}
.mb26 .site-primary-footer-inner-wrap { padding-inline: var(--bleed); }
.mb26 .site-footer p { margin: 0; color: var(--ink-3); font-size: 0.86rem; }
.mb26 .site-footer a {
  color: var(--ink-2); text-decoration: none;
  border-bottom: 1px solid var(--rule);
  transition: color 140ms ease, border-color 140ms ease;
}
.mb26 .site-footer a:hover { color: var(--ink); border-bottom-color: var(--ink); }
.mb26 .site-footer .ast-builder-social-element { color: var(--ink-2); border-bottom: 0; }
.mb26 .site-footer .ast-builder-social-element:hover { color: var(--accent-ink); }

/* The footer nav sets its links at 29px and the two social icons at 19px square.
   Both are under the thumb minimum, and the footer is the one place on the page
   where every target is a small one. Grown on coarse pointers only, so the
   drawn footer is unchanged under a cursor. The icons keep their 19px glyph and
   gain the box around it, which is the part a thumb is aiming at. */
@media (any-pointer: coarse) {
  .mb26 .site-footer .menu-link {
    display: inline-flex; align-items: center; min-height: 44px;
  }
  .mb26 .site-footer .ast-builder-social-element {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px;
  }
  /* The header's one button is drawn at 122.7x40 and appears from 1023 up,
     which on a tablet in landscape is a thumb target 4px short. The anchor is
     an inline box with no padding of its own -- the drawn button is the span
     inside it -- so min-height alone does nothing to it. Made a flex box to
     take the height, with the painted button centred and unchanged. */
  .mb26 .ast-custom-button-link {
    display: inline-flex; align-items: center; min-height: 44px;
  }
}

/* Astra's scroll-to-top is a 31.5px opaque square fixed 30px off the right
   edge, and .mb-shell is centred in a wider column than that gap: measured, it
   lands inside the reading column at every width up to about 1340 -- 36px into
   it on a phone, 10.5px at 1024, 1100, 1200 and 1280 -- where it sits on the
   text as an opaque dark box rather than beside it. There is nowhere to move it
   to on a phone; the margins either side of the column are 25.5px and the
   button is 31.5. It is also a 31.5px target, and it is floating chrome of
   exactly the kind this design does without. The page has a contents index for
   the job it was doing. Astra writes display:block inline from its own script,
   so this needs the important to reach it. */
.mb26 #ast-scroll-top { display: none !important; }

/* Astra wraps the content in a max-width container with its own padding, which
   would stop the dark and panel bands from reaching the edge. Unwrap it here
   and let .mb-shell do all the measuring. */
.mb26 .site-content > .ast-container { max-width: none; padding: 0; }
.mb26 #primary            { margin: 0; padding: 0; }
.mb26 .ast-article-single { margin: 0; padding: 0; border: 0; }
.mb26 .entry-content > *  { margin-block: 0; max-width: none; }
/* Astra sets padding-left/right on `.entry-content > .wp-block-group` from its
   own block-padding variable, and the rule above does not reach it: both
   selectors are (0,2,0) and Astra's is written later. Measured, that padding
   landed 25.5px a side at 390 and 51px above 1100, on top of --bleed, so every
   section ran a doubled gutter. The drawings are where it showed: at 390 the
   SVG lost 51px of width and its dimension text came out at exactly 11.00px,
   sitting on the floor with nothing in hand, and by 375 the view labels were
   under it. The shell does all the measuring, so the group does none. */
.mb26 .entry-content > .wp-block-group { padding-inline: 0; }
.mb26 .entry-header,
.mb26 .ast-single-post-order,
.mb26 .post-navigation { display: none; }

/* ------------------------------------------- 2026-10-09 editorial pass */

/* The header bar was 80px tall by Astra's default (the inline dynamic CSS sets
   min-height on both the desktop wrap and the mobile bar). Nothing in it needs
   that height, and on a phone it was 80 of the 844 pixels the first screen has.
   One class heavier than Astra's selector so it wins on specificity. */
.mb26 .ast-primary-header-bar .site-primary-header-wrap,
.mb26 .ast-mobile-header-wrap .ast-primary-header-bar { min-height: 64px; }

/* Two actions under a hero: the filled call to action and a quiet second one
   (the services, or the prices). Same box, outlined instead of filled, so the
   pair reads as primary and secondary rather than as two equal buttons. */
.mb26 .mb-actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.75rem 1rem;
  margin: 0 0 clamp(1.5rem, 3vw, 2rem);
}
.mb26 .mb-hero .mb-actions .mb-cta { margin: 0; }
.mb26 .mb-hero .mb-actions:last-child { margin-bottom: 0; }
.mb26 .mb-cta-2 { color: var(--ink); background: transparent; border-color: var(--ink); }
@media (hover: hover) and (pointer: fine) {
  .mb26 .mb-cta-2:hover { color: var(--paper); background: var(--ink); border-color: var(--ink);
                          box-shadow: 0 6px 18px -8px rgba(21, 24, 27, .5); }
}
.mb26 .mb-ink .mb-cta-2 { color: var(--paper); background: transparent; border-color: #c9d0d6; }
@media (hover: hover) and (pointer: fine) {
  .mb26 .mb-ink .mb-cta-2:hover { color: var(--ink); background: var(--paper); border-color: var(--paper); }
}
/* The data strip is the last thing in a service hero now, under the actions. */
.mb26 .mb-hero .mb-band { margin-top: 0; }
.mb26 .mb-hero .mb-actions + .mb-band { margin-top: 0.25rem; }
/* On a phone the hero pays for its height in the lead and the proof sentence,
   so both come down a step there and the rule takes less air. Measured against
   a 390x844 viewport: the first action has to land inside it. */
@media (max-width: 600px) {
  .mb26 .mb-hero .mb-lead  { font-size: 1.08rem; margin-bottom: 1.25rem; }
  .mb26 .mb-hero p         { font-size: 1rem; }
  .mb26 .mb-hero h1        { margin-bottom: 0.4em; }
  .mb26 .mb-hero .mb-rule  { margin-bottom: 1.25rem; }
  .mb26 .mb-hero .mb-ident { margin-bottom: 1.1rem; }
}

/* The service grid is a set of six choices now, and each heading is the link.
   Ink, with the list's own hairline under the words rather than the accent, so
   six links do not read as a link dump; the accent arrives on hover. */
.mb26 .mb-grid h3 a {
  color: inherit; text-decoration: none;
  border-bottom: 1px solid var(--rule);
  transition: color 140ms ease, border-color 140ms ease;
}
.mb26 .mb-grid h3 a:hover { color: var(--accent-ink); border-bottom-color: currentColor; }
.mb26 .mb-grid h3 { font-size: 1.05rem; }
.mb26 .mb-grid p a { color: var(--accent-ink); }

/* A short case on the home page: heading, one paragraph, a spec line naming the
   role and the context of the result, and a link to the full account. */
.mb26 .mb-case { padding-block: clamp(1.75rem, 3.5vw, 2.5rem); }
.mb26 .mb-case + .mb-case { border-top: 1px solid var(--rule-soft); }
/* The cases sit on the panel band, where the base accent measures 6.66:1,
   under the house floor; links there take the deep step (DESIGN.md: never
   accent text on the inset panel). Measured 2026-10-15. */
.mb26 .mb-panelband .mb-case a { color: var(--accent-ink); }
.mb26 .mb-case h3 { margin-bottom: 0.5em; max-width: var(--measure); }
.mb26 .mb-case .mb-spec { display: block; margin: 1rem 0 0; }
.mb26 .mb-case .mb-figure { margin-top: 1.75rem; }

/* The desktop disclosure button for a parent menu item (mb-nav.js). Hidden by
   default and at the break point, where Astra's own accordion toggle takes
   over; shown as a 36px square beside the link on the desktop layout. The
   chevron turns when the submenu is open. */
.mb26 .mb-subtoggle { display: none; }
.mb26.ast-desktop .main-header-menu > .menu-item-has-children { display: inline-flex; align-items: center; }
.mb26.ast-desktop .main-header-menu > .menu-item-has-children > .mb-subtoggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.25rem; height: 2.25rem; margin-left: -0.45rem; padding: 0;
  background: transparent; border: 0; border-radius: 2px;
  color: var(--ink-2); cursor: pointer;
}
.mb26.ast-desktop .main-header-menu > .menu-item-has-children > .mb-subtoggle svg {
  display: block; width: 0.75rem; height: 0.5rem;
  transition: transform 160ms ease;
}
.mb26.ast-desktop .main-header-menu > .menu-item-has-children > .mb-subtoggle[aria-expanded="true"] svg {
  transform: rotate(180deg);
}
.mb26.ast-desktop .main-header-menu > .menu-item-has-children > .mb-subtoggle:hover,
.mb26.ast-desktop .main-header-menu > .menu-item-has-children.mb-open > .mb-subtoggle { color: var(--ink); }
.mb26.ast-desktop .main-header-menu > .menu-item-has-children > .mb-subtoggle:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px;
}
/* The open state has to hold while a pointer is away from the item -- Astra's
   hover rule alone would close it -- and Astra's .toggled-on rule gives it
   visibility. Opacity is stated too, in case a menu animation class ever lands
   on the desktop menu. */
.mb26.ast-desktop .main-header-menu > .menu-item-has-children.mb-open > .sub-menu {
  visibility: visible; opacity: 1;
  /* Astra hides a submenu by parking it at left:-999em as well as by
     visibility, and only its :hover and .focus rules bring it back to 0.
     Measured 2026-10-15: Enter on the chevron with the pointer elsewhere set
     every open state and the list stayed 16,983px off-screen, so the next Tab
     went into an invisible list. The open state is self-sufficient now. */
  left: 0;
}

/* The language switch at the end of the main menu: set a step smaller and
   divided from the pages by a hairline on desktop, so it reads as a utility
   rather than as a seventh page. */
.mb26 .main-header-menu .mb-lang .menu-link { font-size: 0.86rem; letter-spacing: 0.02em; }
.mb26.ast-desktop .main-header-menu > .mb-lang {
  margin-left: 0.5rem; padding-left: 0.85rem;
  border-left: 1px solid var(--rule);
}

/* An illustrative excerpt -- a few rows of a requirements table or a gap
   register with synthetic content, labelled as such in the note under it.
   Ruled like every other list on the sheet; the first column is the reference,
   set in the mono face because it is an identifier. */
.mb26 .mb-tablewrap { overflow-x: auto; margin-top: 1.75rem; }
.mb26 .mb-table {
  width: 100%; border-collapse: collapse;
  border-top: 1px solid var(--rule);
  font-size: 0.92rem; line-height: 1.5;
}
.mb26 .mb-table th {
  text-align: left; vertical-align: bottom;
  padding: 0.7rem 0.75rem 0.5rem 0;
  font-family: var(--mono); font-size: 0.7rem; font-weight: 500;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3);
  border-bottom: 1px solid var(--rule);
}
.mb26 .mb-table td {
  vertical-align: top; padding: 0.7rem 0.75rem 0.7rem 0;
  border-bottom: 1px solid var(--rule-soft); color: var(--ink-2);
}
.mb26 .mb-table td:first-child {
  font-family: var(--mono); font-size: 0.78rem; font-weight: 500;
  color: var(--ink); white-space: nowrap;
}
.mb26 .mb-table-note {
  margin: 0.65rem 0 0; font-size: 0.82rem; color: var(--ink-3);
  max-width: var(--measure-tight);
}

/* The logo. Astra's Customizer sets the mark at 120px square, which made the
   header bar 155px tall at every width -- the bar's own min-height never came
   into play. On this surface the monogram is a mark, not a billboard: 56px
   keeps it legible and brings the bar down to its 64px floor. One class over
   Astra's `.site-logo-img .custom-logo-link img` so it wins on specificity. */
.mb26 .site-logo-img .custom-logo-link img,
.mb26 .site-logo-img img { max-width: 56px; width: 56px; height: auto; }

/* Astra lays each top-level menu item out as a column (the link above its
   submenu). The disclosure button has to sit beside the link, not under it,
   so the parent item alone goes back to a row; the submenu is absolutely
   positioned off the item and is unaffected. */
.mb26.ast-desktop .main-header-menu > li.menu-item-has-children {
  flex-direction: row; flex-wrap: nowrap; align-items: center;
}
.mb26.ast-desktop .main-header-menu > li.menu-item-has-children > .menu-link { flex: 0 0 auto; }

/* ---------------------------------------------- 2026-10-15 critique fixes */

/* The quotation on the ink band. `cite` takes --ink-3 from the paper rule and
   measured 2.15:1 on ink -- the attribution of the one external voice on the
   home page was the dimmest text on it. Restated with the band's own
   annotation grey (7.16:1) and the light accent for the drawn rule, as every
   other reversed role in this file is. */
.mb26 .mb-ink blockquote      { color: #a8b0b8; }
.mb26 .mb-ink blockquote cite { color: #9ea5ac; }
/* The drawn rule beside a quotation. Measured in the preview, the 1px accent
   rule declared at `.mb26 blockquote` was losing to a later rule and the
   quote showed a 5% black line; stated at entry-content depth so it holds,
   accent on paper and the light step on ink. */
.mb26 .entry-content blockquote         { border-left: 1px solid var(--accent); }
.mb26 .entry-content .mb-ink blockquote { border-left: 1px solid var(--accent-lit); }

/* A lazy photograph on an ink band loaded over a panel-coloured placeholder,
   which flashes a light rectangle on black during a fast scroll. */
.mb26 .mb-ink .mb-figure img  { background: #23282d; }

/* The illustrative tables stack below 600px: one record per row, each cell
   led by its column name as a mono label, instead of five columns scrolling
   sideways inside a 350px wrapper with no cue. The wrapper stops scrolling
   there, so the page never gets a second horizontal axis. */
@media (max-width: 599px) {
  .mb26 .mb-tablewrap { overflow-x: visible; }
  .mb26 .mb-table thead { display: none; }
  .mb26 .mb-table, .mb26 .mb-table tbody, .mb26 .mb-table tr, .mb26 .mb-table td { display: block; width: 100%; }
  .mb26 .mb-table tr { padding: 0.9rem 0; border-bottom: 1px solid var(--rule-soft); }
  .mb26 .mb-table td { padding: 0.2rem 0 0.45rem; border-bottom: 0; white-space: normal; }
  .mb26 .mb-table td:first-child { font-size: 0.8rem; padding-top: 0; }
  .mb26 .mb-table td::before {
    content: attr(data-label); display: block;
    font-family: var(--mono); font-size: 0.66rem; font-weight: 500;
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3);
    margin-bottom: 0.15rem;
  }
  .mb26 .mb-table td:first-child::before { display: none; }
}

/* On a phone the byline wrapped to three lines of accent blue, uppercase and
   tracked, above a 37px headline: the loudest colour on the page on the least
   important line. Below 600px it takes the annotation grey and less tracking;
   the string itself is pinned and unchanged. */
@media (max-width: 600px) {
  .mb26 .mb-hero .mb-ident .mb-note { color: var(--ink-3); letter-spacing: 0.08em; }
  .mb26 .mb-hero .mb-lead { margin-bottom: 1rem; }
  .mb26 .mb-hero p { margin-bottom: 0.9em; }
}

/* Contact Form 7 validates the whole form when any field is left, so typing a
   bad email and tabbing out also painted the never-visited name field red.
   mb26.php marks a field data-mb-touched on blur (and every field on submit);
   until then the field keeps its resting border and its tip stays hidden.
   Specificity matches the invalid rules above and sits after them. */
.mb26 #contact .wpcf7 .mb-form :is(input, textarea).wpcf7-not-valid:not([data-mb-touched]),
.mb26 #contact .wpcf7 .mb-form :is(input, textarea):user-invalid:not([data-mb-touched]) {
  border-color: var(--rule);
}
/* The wrap holds the control and its tip; the test has to name the control,
   or the tip (which never carries the attribute) would hide itself for good. */
.mb26 #contact .wpcf7 .mb-form .wpcf7-form-control-wrap:has(> input:not([data-mb-touched]), > textarea:not([data-mb-touched])) .wpcf7-not-valid-tip {
  display: none;
}

/* ------------------------------------------ 2026-10-15, round three */
/* ---- 2026-10-15, round three (the second critique). */

/* The phone byline. The <=600px hero step above sets every hero paragraph to
   1rem, and that selector (0,2,1) outranks .mb-note (0,2,0), so the byline
   rendered at 16px over three lines, 79px tall, above the H1, while every other
   note on the site is 0.7rem. The earlier fix changed its colour and tracking
   and left the size, which was the cause of the three lines. */
@media (max-width: 600px) {
  .mb26 .mb-hero .mb-ident .mb-note { font-size: 0.7rem; }
}

/* Attribution in sentence case. 104 uppercase characters at 12.5px was the
   least readable line on the home page; the capitals carried nothing the mono
   face and the grey were not already saying. Still mono, still annotation. */
.mb26 blockquote cite { text-transform: none; letter-spacing: 0.02em; }

/* One focus ring. The header's links took Astra's ring, 3px in the link's own
   colour, while every other control on the site takes the 2px accent ring at
   2px offset; keyboard users saw two systems in one bar. */
.mb26 .main-header-menu .menu-item > .menu-link:focus-visible,
.mb26 .main-header-menu .sub-menu .menu-item > .menu-link:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px;
}
/* Astra gives every menu link `transition: all .2s linear`, and "all" includes
   the outline: on focus the ring eased in over 200ms from the initial values
   (3px, the link's own colour, offset 0), which is what a reading taken at the
   moment of focus reported as a different ring. Only the colours are worth
   easing; the ring has to be there the instant the key is pressed. */
.mb26 .main-header-menu .menu-link { transition: color 140ms ease, background-color 140ms ease; }

/* Footer menu links. Astra's inline rule, with an id in it, set them in the
   theme's heading colour at 17px: the one near-black run of text in a footer
   drawn in greys. The id here matches that rule's weight and the two classes
   beat it; the size is the footer's own. */
.mb26 #astra-footer-menu .menu-item > a { color: var(--ink-2); font-size: 0.92rem; }
.mb26 #astra-footer-menu .menu-item:hover > a { color: var(--ink); }

/* The six services, named in place at the foot of every service page. From
   inside a service page the other five existed only behind the chevron or on
   the home grid; now they are the last thing before the form, in the same
   labels the menu uses, the current page unlinked and in full ink. The section
   hairlines frame the row, so it draws none of its own, and it takes a little
   less air than a reading section because it is a list, not an argument. */
.mb26 section.mb-siblings-sec { padding-block: calc(var(--step) * 0.6); }
.mb26 .mb-siblings ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.55rem 1.6rem;
}
.mb26 .mb-siblings li { margin: 0; font-size: 0.95rem; }
.mb26 .mb-siblings a {
  color: var(--ink-2); text-decoration: none;
  border-bottom: 1px solid var(--rule);
  transition: color 140ms ease, border-color 140ms ease;
}
.mb26 .mb-siblings a:hover { color: var(--ink); border-bottom-color: var(--ink); }
.mb26 .mb-siblings span[aria-current] { color: var(--ink); font-weight: 500; }
@media (any-pointer: coarse) {
  .mb26 .mb-siblings a,
  .mb26 .mb-siblings span[aria-current] { display: inline-flex; align-items: center; min-height: 44px; }
}

/* On a service page the word "Services" is a disclosure as well (mb-nav.js), so
   while the list is open the word takes full ink, as the hovered item does. */
.mb26.ast-desktop .main-header-menu > .menu-item-has-children.mb-open > .menu-link { color: var(--ink); }

/* ------------------------------------------- 2026-10-15, round four */
/* ---- 2026-10-15, round four (the third critique). */

/* The service hero's right column. The home hero reserves the right of the
   shell for the photograph; a service hero reserved nothing, and at 1280 the
   first screen was half paper while the four facts of the data strip sat under
   the fold (823px on the workflow page). From 1100px the strip stands in that
   column as a rail of facts, one per hairline, centred on the text beside it
   the way the portrait is; below 1100px nothing changes and the strip stays
   under the actions. Six children, the strip spanning them all: the row gap is
   zero so the spare row costs nothing. */
@media (min-width: 1100px) {
  .mb26.mb-service .mb-hero .mb-shell {
    display: grid; grid-template-columns: minmax(0, 1fr) 17rem;
    column-gap: clamp(3rem, 6vw, 6rem); row-gap: 0; align-items: start;
    /* The home hero's rule above reserves the right of every hero shell for
       the portrait (bleed + face + gap, about 424px); a service hero has no
       portrait, and measured with that padding still in force the grid's
       first column fell to 402px and the call to action to 916px, under the
       fold. The ordinary gutter, as everywhere else on the page. */
    padding-right: var(--bleed);
  }
  .mb26.mb-service .mb-hero .mb-shell > * { grid-column: 1; }
  .mb26.mb-service .mb-hero .mb-actions { margin-bottom: 0; }
  .mb26.mb-service .mb-hero .mb-shell > .mb-band {
    grid-column: 2; grid-row: 1 / span 6; align-self: center;
    display: block; margin: 0; padding: 0;
    border-block: 0; border-top: 1px solid var(--rule);
  }
  .mb26.mb-service .mb-hero .mb-band li {
    padding: 0.7rem 0; border-bottom: 1px solid var(--rule);
  }
}

/* The footer in two groups. Twelve equal links was the page's close and its
   only route back under the no-sticky ruling, with the six services sitting
   unmarked between Home and Selected work. mb26_footer_groups() reorders the
   items services first and puts a mono label in front of each group; this
   lays them out as two columns from 601px (the Services column is the first
   seven items, Pages the next seven, placed densely so each column fills from
   its own top) and as one column, group under group, on a phone. The id in
   the selector matches the weight of Astra's own footer rule. */
.mb26 #astra-footer-menu {
  display: grid; grid-template-columns: max-content max-content;
  grid-auto-flow: row dense; justify-content: end;
  column-gap: clamp(2rem, 4vw, 3.5rem); row-gap: 0;
}
.mb26 #astra-footer-menu .menu-item { justify-content: flex-start; margin: 0; }
.mb26 #astra-footer-menu .menu-item > a { padding: 0.2rem 0; }
.mb26 #astra-footer-menu > li:nth-child(-n+7) { grid-column: 1; }
.mb26 #astra-footer-menu > li:nth-child(n+8)  { grid-column: 2; }
.mb26 #astra-footer-menu .mb-flabel {
  font-family: var(--mono); font-size: 0.7rem; font-weight: 500;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-3);
  padding: 0 0 0.4rem; margin: 0 0 0.45rem; border-bottom: 1px solid var(--rule);
  display: block; min-height: 0;
}
/* On a phone the two groups stay side by side, half the width each: seven
   rows of 44px instead of fourteen, and a long label wraps inside its half.
   (A one-column rule was tried first and lost to the placement rules above on
   specificity, which left a phantom second track; two columns were the better
   footer anyway.) Astra's vertical mobile menu centres each item along its
   column axis, so the items are set back to the left edge of their column. */
@media (max-width: 600px) {
  .mb26 #astra-footer-menu {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    justify-content: stretch; column-gap: 1.5rem;
  }
  .mb26 #astra-footer-menu .menu-item {
    align-items: flex-start; justify-content: flex-start; text-align: left;
  }
}

/* ------------------------------------------- 2026-10-15, round five */
/* ---- 2026-10-15, round five (the fourth critique). */

/* The prices. The package list borrows the role list, whose first column is
   a date in the annotation register, so EUR 900 / 2,800 / 4,500 sat at 12.5px
   mono on the one page that sells by price, while the evidence figures two
   sections down took the display register. The number the buyer came for
   takes a step of the figure register: mono, tabular, full ink, a size the
   row can hold. The qualifier under the package name keeps its register. */
.mb26 .mb-roles.mb-packages > li > span:first-child {
  font-size: clamp(1.3rem, 2.4vw, 1.6rem); line-height: 1.1;
  letter-spacing: -0.02em; text-transform: none; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
@media (min-width: 720px) {
  .mb26 .mb-roles.mb-packages > li > span:first-child { padding-top: 0; }
}

/* The six service titles on the home grid are links in full ink under a
   hairline that reads as the list's rule, not as an underline: two reviews
   read them as headings. The arrow the call to action carries says where a
   title goes, in the accent, after the words. */
.mb26 .mb-grid h3 a::after {
  content: "\2192"; margin-left: 0.4em;
  color: var(--accent); font-weight: 400;
  transition: color 140ms ease;
}
.mb26 .mb-grid h3 a:hover::after { color: var(--accent-ink); }

/* -------------------------------------------- 2026-10-15, round six */
/* ---- 2026-10-15, round six (the fourth critique, structural items). */

/* The reassurance at the top of the form, not the bottom. "It reaches me as an
   email and I read it myself" had sat under the button, between the pending
   notice and the contracting footnote, in hint type; it is the sentence that
   earns the message, so it is the first thing inside the form, in the reading
   voice. The hint register stays for the human-check hint only. */
.mb26 #contact .wpcf7 .mb-form p.mb-form-note {
  /* Weighted above `.mb-form p { margin: 0 }` further up (1,3,1), which had
     zeroed this margin and left the sentence sitting on the first label.
     Measured 2026-10-16: note bottom 8143, label top 8143, on every browser. */
  margin: 0 0 1.5rem; font-size: 0.95rem; line-height: 1.6;
  color: var(--ink-2); max-width: var(--measure);
}
/* The hints under a field lost their top margin to the same rule; the one
   under the human check never showed it because the widget carries its own
   gap. The hint under the message field has no such neighbour. */
.mb26 #contact .wpcf7 .mb-form p.mb-hint { margin: 0.5rem 0 0; }

/* The contracting facts as a title block: a mono label and two ruled rows in
   the reading voice, instead of six lines of 12.8px mono closing every page
   on "no professional indemnity insurance". Same facts, from the contact
   page; set so they read as stated, which is the site's whole principle. */
.mb26 .mb-contracting { margin: 2rem 0 0; max-width: var(--measure); }
.mb26 .mb-contracting > .mb-note { margin: 0 0 0.5rem; }
.mb26 .mb-contracting ul {
  list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule);
}
.mb26 .mb-contracting li {
  margin: 0; padding: 0.7rem 0; border-bottom: 1px solid var(--rule);
  font-size: 0.9rem; line-height: 1.55; color: var(--ink-2);
}

/* The current section in the header bar. The parent of the current page
   already took full ink, one shade off its neighbours; a reader scanning the
   bar needs a mark, so the current item and the current section carry a 2px
   accent line along the bottom of the bar, where a tab marker sits. */
.mb26.ast-desktop .main-header-menu > .current-menu-item > .menu-link,
.mb26.ast-desktop .main-header-menu > .current-menu-ancestor > .menu-link {
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* ------------------------------------------ 2026-10-15, round seven */
/* The long runs in groups. Four "What you get" / "What I work on" lists ran
   seven or eight identical ruled rows; past four the reader stops holding
   the set. Each now stands as two or three groups under a mono label in the
   rail-note voice, the items unchanged, the section's count still honest.
   The label takes the air a group needs above it and sits close to its own
   rule; the list's own top rule follows. */
.mb26 .mb-note.mb-group { display: block; margin: 1.75rem 0 0.45rem; }
.mb26 .mb-note.mb-group + .mb-layers { margin-top: 0; }

/* ------------------------------------------- 2026-10-16, round nine */
/* The situation before the deliverable. Each home-grid cell opens with one
   line in the director's grammar ("Turnaround is slipping and nobody can say
   at which bench"), in full ink and the reading voice, and the deliverable
   paragraph follows in second ink as before. Two reviews asked whether the
   grid should lead with the situation; the owner said draft it, then yes. */
.mb26 .mb-grid .mb-situation {
  margin: 0.4rem 0 0.45rem; color: var(--ink);
  font-size: 0.95rem; line-height: 1.5; max-width: 40ch;
}
