/* ==========================================================================
   components.css
   Every reusable block on the site. Pages compose these; pages never define
   their own. A <style> tag under src/pages fails the build.

   Source is marked per block:
     [both]  byte-identical in homepage-v1.html and managed-services-page-v7.html
     [home]  homepage-v1.html only
     [ms]    managed-services-page-v7.html only
     [merged] present in both but divergent, reconciled here
   ========================================================================== */


/* ==========================================================================
   Small label                                                        [both]
   Open Sans Bold, uppercase, navy. Sits above an h2.
   ========================================================================== */
.eyebrow{font-family:var(--text);font-weight:700;font-size:13px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--navy);margin:0 0 var(--sp-2)}


/* ==========================================================================
   Site header and navigation                                         [both]

   Rebuilt from the mockups' JavaScript-generated markup. Three changes, all
   required by BUILD-REFERENCE.md section 3:

   1. Top-level items are <a href> anchors, not <button>. The mockups emitted
      buttons, so the homepage had no crawlable link to any hub page.
   2. Dropdowns open on :hover and :focus-within. The .open class the mockup
      JavaScript toggled is gone.
   3. The mobile drawer is <details>/<summary>, so it works with no script.

   JavaScript is optional enhancement only: Escape-to-close and click-outside.
   ========================================================================== */
/* --hdr-h is the header's own height, measured rather than guessed, and it is
   here because .fd-drawer needs it to cap itself against the viewport. Above
   900px the tallest child is .fd-cta, 11px padding either side of a 22.4px
   line box, so 12 + 44.4 + 12 + 1px border = 69.4, rounded to 70. Below 900px
   the CTA is hidden and the tallest child is the 38px burger, the logo being
   37.5px at its 146px width: 12 + 38 + 12 + 1 = 63. The override is in the
   900px block beside the rule that hides the CTA. */
.fd-header{--hdr-h:70px;position:sticky;top:0;z-index:60;background:var(--white);border-bottom:1px solid var(--line)}
.fd-header .wrap{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-2) var(--gutter)}
.fd-logo{flex:0 0 auto;width:160px;line-height:0}
.fd-logo-svg{display:block;width:100%;height:auto}

.fd-nav{display:flex;align-items:center;gap:2px;flex:1 1 auto}
.fd-item{position:relative;flex:0 0 auto}
/* NOWRAP IS THE POINT OF THIS RULE, 9 October 2026. The header row is a flex
   line and .fd-item was shrinkable, so when the row ran long the only label
   with a space in it took the whole of the squeeze: Managed Services set
   itself over two lines while the six single-word items stayed put, which
   reads as a rendering fault rather than as a compact nav. Every label is now
   one line, .fd-item does not shrink, and the row is kept inside the measure
   by the two compact tiers at the foot of this section instead. Padding and
   the chevron gap came in with it, 11 to 8 and 5 to 4, which is 50px across
   the seven items and is most of what the one-line Managed Services costs. */
.fd-link{display:flex;align-items:center;gap:4px;font-family:var(--text);
  font-size:14px;font-weight:600;color:var(--navy);background:none;border:0;
  cursor:pointer;padding:10px 8px;border-bottom:2px solid transparent;
  text-decoration:none;line-height:1.4;white-space:nowrap}
.fd-link:hover,
.fd-item:hover>.fd-link,
.fd-item:focus-within>.fd-link{border-bottom-color:var(--blue);color:var(--navy-deep)}
.fd-link[aria-current]{border-bottom-color:var(--blue)}
.fd-link .fd-chev{font-size:9px;color:var(--blue)}

.fd-panel{display:none;position:absolute;top:100%;left:0;z-index:70;min-width:252px;
  background:var(--white);border:1px solid var(--line);border-top:2px solid var(--blue);
  border-radius:0 0 6px 6px;padding:var(--sp-1) 0}
.fd-item:focus-within>.fd-panel{display:block}
/* Hover opens a panel only where hover is real. A touch device emulates hover
   on tap, which opened a panel nothing could then close: nav.js clears its
   data-closed on mouseleave, and mouseleave never fires on touch, so a tapped
   item stayed dead until the page reloaded. Keyboard and focus are unaffected,
   and the rule below still outranks both because it comes after them. */
@media (hover:hover){
  .fd-item:hover>.fd-panel{display:block}
}
/* Script sets this to close a panel the pointer is still resting on, after
   Escape or a click outside. Without script it never appears. */
.fd-item[data-closed]>.fd-panel{display:none}

.fd-panel a{display:block;padding:8px var(--sp-4);font-size:14px;line-height:1.5;
  color:var(--body);text-decoration:none}
.fd-panel a:hover{background:var(--wash-blue);color:var(--navy)}
.fd-panel a.fd-hub{font-weight:600;color:var(--navy)}
.fd-panel a[aria-current]{background:var(--wash-blue);color:var(--navy);font-weight:600}
.fd-panel .fd-div{height:1px;background:var(--line);margin:var(--sp-1) 0}
.fd-panel .fd-ext{font-size:11px;color:var(--muted);margin-left:5px}

.fd-actions{flex:0 0 auto;display:flex;align-items:center;gap:var(--sp-2)}
.fd-search{width:34px;height:34px;border:1px solid var(--line);border-radius:4px;
  background:var(--white);color:var(--navy);cursor:pointer;font-size:15px;line-height:1}
.fd-search:hover{border-color:var(--navy)}
.fd-cta{display:inline-block;background:var(--navy);color:var(--white);text-decoration:none;
  font-family:var(--text);font-size:14px;font-weight:600;padding:11px 20px;
  border-radius:4px;white-space:nowrap}
.fd-cta:hover{background:var(--navy-hover);color:var(--white)}

/* .fd-emerg [new, 8 October 2026] — the persistent emergency entry point in
   the header, right of the CTA, linking to the Emergency Support page.

   IT IS TWO LINES BECAUSE THE HEADER MUST NOT GET TALLER. Set on one line the
   label and the number run about 290px, which the row has nowhere near; set
   stacked it is 10.5 + 14 at 1.2 line height plus a 1px gap, 30px, well inside
   the 44.4px .fd-cta that --hdr-h is measured from, so the header is the same
   height it was.

   IT IS NOT RED. --alert is the form validation red and its own comment says
   nothing but validation may use it; the brand palette has no other red. The
   separation here is the hairline rule and the weight on the number, not a
   color the brand does not own. */
.fd-emerg{flex:0 0 auto;display:flex;flex-direction:column;justify-content:center;
  gap:1px;padding-left:var(--sp-2);border-left:1px solid var(--line);
  font-family:var(--text);line-height:1.2;text-decoration:none;white-space:nowrap}
.fd-emerg .ee{font-size:10.5px;font-weight:600;letter-spacing:.02em;color:var(--muted)}
.fd-emerg .en{font-size:14px;font-weight:700;color:var(--navy)}
.fd-emerg:hover .ee{color:var(--navy)}
.fd-emerg:hover .en{color:var(--navy-deep)}

/* ---------- mobile drawer ----------
   <details open> on the wrapper replaces the .drawer-open class the burger
   script used to toggle. The burger is the <summary>. */
.fd-burger{display:none;width:38px;height:38px;border:1px solid var(--line);
  border-radius:4px;background:var(--white);color:var(--navy);font-size:16px;
  cursor:pointer;align-items:center;justify-content:center;list-style:none}
.fd-burger::-webkit-details-marker{display:none}
/* The drawer is POSITIONED, and that is the whole of the off-screen bug it was
   reported with. <details class="fd-menu"> sits inside .fd-actions, a
   flex:0 0 auto item in the header's flex row, and this rule used to set only
   display, border, background and padding. So the drawer opened INSIDE that
   right-aligned flex item, roughly as wide as the burger, and 28 links ran off
   the right of the viewport. It also re-measured the flex row on every
   accordion tap, which is what read as flicker.

   .fd-header is position:sticky, so it is already the containing block and
   nothing needs position:relative adding. top:100% lands on the header's own
   bottom border, which is why the drawer's border-top sits exactly on it
   rather than doubling it.

   max-height and overflow-y are the second half of the same bug: a sticky
   header taller than the screen cannot be scrolled to the bottom of, so the
   drawer's own call to action was unreachable with every accordion open. dvh
   rather than vh because of mobile browser chrome, with vh first for anything
   that does not know dvh. */
.fd-drawer{display:none;position:absolute;top:100%;left:0;right:0;z-index:1;
  max-height:calc(100vh - var(--hdr-h));
  max-height:calc(100dvh - var(--hdr-h));
  overflow-y:auto;overscroll-behavior:contain;
  border-top:1px solid var(--line);background:var(--white);
  box-shadow:0 12px 24px rgba(35,43,95,.13);padding:4px 0 var(--sp-3)}
.fd-menu[open] .fd-drawer{display:block}

.fd-acc{border-bottom:1px solid var(--line-soft)}
.fd-acc-top{width:100%;display:flex;align-items:center;justify-content:space-between;
  font-family:var(--text);font-weight:600;font-size:15px;line-height:1.4;color:var(--navy);
  background:none;border:0;padding:var(--sp-3) var(--gutter);cursor:pointer;text-align:left;
  list-style:none}
.fd-acc-top::-webkit-details-marker{display:none}
.fd-acc-top .fd-chev{color:var(--blue);font-size:11px}
.fd-acc[open]>.fd-acc-top .fd-chev{transform:rotate(180deg)}
.fd-acc-sub{background:var(--wash-drawer);padding:0 0 8px}
.fd-acc-sub a{display:block;padding:9px var(--gutter) 9px 38px;font-size:14px;
  color:var(--body);text-decoration:none}
.fd-acc-sub a.fd-hub{font-weight:600;color:var(--navy)}
.fd-acc-sub a[aria-current]{color:var(--navy);font-weight:600}
.fd-acc-sub .fd-div{height:1px;background:var(--line);margin:var(--sp-1) var(--gutter) var(--sp-1) 38px}
.fd-drawer-cta{padding:var(--sp-3) var(--gutter) 0}
.fd-drawer-cta a{display:block;text-align:center;background:var(--navy);color:var(--white);
  text-decoration:none;font-weight:600;padding:13px;border-radius:4px;font-size:15px}


/* ==========================================================================
   Buttons                                                            [both]
   .btn-a and .btn-b are the on-navy pair: white fill with navy type is the
   brand-approved pairing and the only one clearing WCAG AA at this size.
   .btn-navy and .btn-outline are the on-light pair.                  [home]
   ========================================================================== */
.btns{display:flex;flex-wrap:wrap;gap:var(--sp-2);margin:var(--sp-6) 0 0}
.btn{display:inline-block;padding:13px 26px;border-radius:3px;text-decoration:none;
  font-family:var(--text);font-weight:700;font-size:1rem;border:1.5px solid transparent}
.btn-a{background:var(--white);color:var(--navy)}
.btn-a:hover{background:var(--wash-navy);color:var(--navy)}
.btn-b{border-color:rgba(255,255,255,.55);color:var(--white)}
.btn-b:hover{border-color:var(--white);background:rgba(255,255,255,.08);color:var(--white)}
.btn-navy{background:var(--navy);color:var(--white)}
.btn-navy:hover{background:var(--navy-hover);color:var(--white)}
.btn-outline{border-color:var(--navy);color:var(--navy)}
.btn-outline:hover{background:var(--navy);color:var(--white)}


/* ==========================================================================
   Hero                                                             [merged]
   Full-bleed navy panel. The two mockups differ only in h1 clamp, paragraph
   measure and the optional two-column claims list, so the claims list is a
   modifier rather than a second hero.

   The tiled brand super graphic behind the hero was a mockup toggle
   (body.pattern-on). It renders on every hero. A .hero.flat modifier held it
   to the homepage until 17 September 2026; both the modifier and the rule
   that switched it off are gone.
   ========================================================================== */
.hero{background:var(--navy);color:var(--white);padding:64px 0 60px;position:relative;overflow:hidden}
.hero .wrap{position:relative}
/* The crumb inside .eyebrow is a real <a>, so it needs a color: base.css
   sets a{color:var(--navy)} and navy type on the navy hero is invisible. The
   footer solves the same problem at .foot a. --on-navy rather than white with
   opacity, because opacity on the parent caps the child and a hover state
   then has nothing brighter to reach. */
.hero .eyebrow{color:var(--on-navy)}
.hero .eyebrow a{color:inherit}
.hero .eyebrow a:hover{color:var(--white)}
.hero h1{font-size:clamp(2.1rem,5.4vw,3.15rem);line-height:1.06;margin:0;max-width:24ch;}
.hero .rule{width:96px;height:1px;background:var(--blue);margin:22px 0 20px}
.hero p{color:var(--on-navy);font-size:1.15rem;max-width:58ch}
/* A link in the lead is the same problem the crumb above has and it was
   missed: base.css sets a{color:var(--navy)}, so the /webinars/ link in the
   /events/ lead was navy type on the navy hero, legible only because it is
   underlined. White rather than --on-navy, so the link is the brightest thing
   in the paragraph, and --on-navy on hover, which is the pair the eyebrow
   uses in the other direction. */
.hero p a{color:var(--white);text-decoration:underline;text-underline-offset:2px}
.hero p a:hover{color:var(--on-navy)}
.hero-micro{margin:var(--sp-3) 0 0;font-size:14px;font-style:italic;
  color:rgba(255,255,255,.7);max-width:46ch}

/* Two-column hero: the pitch left, discrete quotable claims right. Tightens
   the display size, because the headline no longer owns the full measure. */
.hero-grid{display:grid;grid-template-columns:1.15fr .85fr;gap:40px var(--sp-9);align-items:start}
.hero-grid h1{font-size:clamp(2rem,4.6vw,2.85rem);line-height:1.08;max-width:26ch}
.hero-grid p{font-size:1.12rem;max-width:46ch}
.hero-claims{list-style:none;margin:0;padding:0}
.hero-claims li{border-top:1px solid var(--rule-navy);padding:var(--sp-3) 0;font-size:15.5px;
  line-height:1.55;color:var(--on-navy)}
.hero-claims li:first-child{border-top:0;padding-top:0}

/* The hero's left column, under the rule: a vision and a mission. [new, §4.1]
   Added 21 September 2026, when /about-us/ moved both into the hero.

   A <dl>, so the label and its sentence are related in the document rather
   than only on screen. The label is solid white and the statement is
   --on-navy, which is the same pairing .hero-claims uses for its <b> and its
   body: the label is a signpost and wants weight, the sentence is read.

   Note the accent-band rules further down this file. Every white in the hero
   was mixed for navy, and on --blue and --green a 17px statement in --on-navy
   would fail AA, so both go solid white and up to 1.2rem there, exactly as
   the lead paragraph does and for the same 18.66px large-text threshold. No
   page uses statements on an accent band today; the rule is there so the
   first one that does is not a contrast defect. */
.hero-creed{margin:24px 0 0}
.hero-creed dt{font-family:var(--text);font-weight:700;font-size:12.5px;
  letter-spacing:.09em;text-transform:uppercase;color:var(--white)}
.hero-creed dd{margin:5px 0 0;font-size:1.08rem;line-height:1.5;
  color:var(--on-navy);max-width:46ch}
.hero-creed dd + dt{margin-top:var(--sp-4)}

/* The hero's other right-hand column: a self-hosted video.        [new, §4.1]
   Added 21 September 2026, when /about-us/ moved its video out of a band two
   thirds of the way down the page and into the hero.

   .has-media squares the grid off. The 1.15fr/.85fr split above is drawn for
   a claims list, which is type and wants to be the narrower column; a 16:9
   frame at .85fr of the wrap renders about 520px wide and reads as an
   afterthought beside a 3rem heading. At 1fr each it is 584px, and the text
   column centers against it rather than hanging from the top, because the
   two columns are now close enough in height for the ragged top to show. */
.hero-grid.has-media{grid-template-columns:1fr 1fr;align-items:center}
.hero-media{min-width:0}
/* The frame's own border is --line, a light gray drawn for a white band, and
   it disappears against navy. On a hero it is the white hairline the rest of
   the band uses. No new token: --rule-navy is already the hairline on navy. */
.hero-media .vid-frame{border-color:var(--rule-navy)}
/* .vid figcaption is --body on white. In a hero it is on navy. */
.hero-media .vid figcaption{color:var(--on-navy);max-width:none;font-size:14.5px}
.hero-claims li b{color:var(--white);font-weight:700}

/* Section hero bands.
   Navy is the default and what Managed Services, Platforms, Resources,
   Company and the homepage use. Consulting, Assessments and Industries carry
   a `band` key in nav.json, which components/hero.njk turns into the class
   below, so the color belongs to the section rather than to fifteen pages.

   Consulting and Industries take the two brand accents at the client's
   direction on 17 September 2026. They are light, and the rest of this block
   is what that costs. Read the contrast note under the light bands before
   changing anything in it, and see the CLAUDE.md hero band section for what
   still does not meet AA on them.

   Purple in web output is open decision 7 and is Marketing's to close. The
   brand guidelines name dark purple alongside navy as a panel the white logo
   sits on, which is the evidence Assessments was chosen from, but the
   decision itself is still open. The value lives in tokens.css, so a "print
   only" answer is one deletion here and nothing else. */
.hero.band-purple-dark{background:var(--purple-dark)}
.hero.band-blue{background:var(--blue)}
.hero.band-green{background:var(--green)}

/* The light bands.
   Every white in the hero was mixed for navy, where 84% body copy still
   clears 9.8:1. On --blue full white is 3.66:1 and on --green 3.38:1, so
   there is nothing left to give away and each one goes to full strength.

   What that buys, against the 3:1 large-text and graphical-object floor: the
   h1 passes on both, so do the rule, the outline button and the white fill
   of the solid one. The lead paragraph is the reason for the font-size line:
   at 1.15rem it is 18.4px, a quarter of a pixel under the 18.66px where WCAG
   starts calling text large, so it needed 4.5:1 and had 3.66. At 1.2rem it
   is large text and passes. Nothing else in the hero is resized, which is
   why the rule excludes .hero-micro: that line is 14px and stays 14px.

   .hero-micro does not pass on either band and cannot be made to. No value
   in the palette clears 4.5:1 on an accent at 14px, white and navy included.
   It is recorded in CLAUDE.md rather than papered over here.

   The tiled mark is white at 15%, which reads on navy and disappears on an
   accent. 22% is the same graphic at the same weight relative to its band. */
.hero.band-blue .eyebrow,.hero.band-green .eyebrow,
.hero.band-blue p,.hero.band-green p,
.hero.band-blue .hero-creed dd,.hero.band-green .hero-creed dd{color:var(--white)}
.hero.band-blue p:not(.hero-micro),
.hero.band-green p:not(.hero-micro),
.hero.band-blue .hero-creed dd,
.hero.band-green .hero-creed dd{font-size:1.2rem}
.hero.band-blue .rule,.hero.band-green .rule{background:var(--white)}
.hero.band-blue .btn-b,.hero.band-green .btn-b{border-color:var(--white)}
.hero.band-blue::after,.hero.band-green::after{opacity:.22}

/* Tiled brand mark, masked so it fades in from the left and never sits under
   the headline. One rule, both hero variants.

   The sizing was written for the homepage's empty second column and carries
   over to the one-column hero unchanged, because the copy ends at roughly the
   same measure either way: .hero h1 caps at 24ch and .hero p at 58ch, which
   lands inside 720px of a 1232px wrap, and the mask does not reach full
   strength until 467px from the right edge.

   The mask is the part that does the work. At a stop of 85% the pattern only
   reached full strength a few pixels short of the right edge and read as a
   sliver rather than a field. It ramps in over the first 55% instead, and it
   stays masked, so it is still transparent where it passes behind the
   headline at narrow widths. */
.hero::after{
  content:"";position:absolute;top:0;right:0;bottom:0;width:min(64%,850px);
  background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01LjgyMjggLTUuODIyOCA4Ni45MDg4IDk4LjY3OTkiIGZpbGw9IiNGRkZGRkYiPjxwb2x5Z29uIHBvaW50cz0iMCAwIDAgMjMuMDQ3MyAwIDI4Ljg3MDkgMCA0MC4zOTQ1IDUuODIyOCA0MC4zOTQ1IDUuODIyOCAyOC44NzA5IDIzLjEwODcgMjguODcwOSAyMy4xMDg3IDIzLjA0NzMgNS44MjI4IDIzLjA0NzMgNS44MjI4IDUuODIzNiAzNC42MzIzIDUuODIzNiAzNC42MzIzIDAgMCAwIi8+PHBvbHlnb24gcG9pbnRzPSIxMS41MjM2IDE3LjM0NzMgMjguODA5NSAxNy4zNDczIDI4LjgwOTUgMzQuNTcwOSAxMS41MjM2IDM0LjU3MDkgMTEuNTIzNiA0MC4zOTQ1IDM0LjYzMjMgNDAuMzk0NSAzNC42MzIzIDExLjUyMzYgMTEuNTIzNiAxMS41MjM2IDExLjUyMzYgMTcuMzQ3MyIvPjxwb2x5Z29uIHBvaW50cz0iMzQuNjMyMyA4Ny4wMzQzIDM0LjYzMjMgODEuMjEwNiA1LjgyMjggODEuMjEwNiA1LjgyMjggNjMuOTg3IDIzLjEwODcgNjMuOTg3IDIzLjEwODcgNTguMTYzNCA1LjgyMjggNTguMTYzNCA1LjgyMjggNDYuNjM5NyAwIDQ2LjYzOTcgMCA1OC4xNjM0IDAgNjMuOTg3IDAgODcuMDM0MyAzNC42MzIzIDg3LjAzNDMiLz48cG9seWdvbiBwb2ludHM9IjExLjUyMzYgNzUuNTEwNiAzNC42MzIzIDc1LjUxMDYgMzQuNjMyMyA0Ni42Mzk3IDExLjUyMzYgNDYuNjM5NyAxMS41MjM2IDUyLjQ2MzQgMjguODA5NSA1Mi40NjM0IDI4LjgwOTUgNjkuNjg3IDExLjUyMzYgNjkuNjg3IDExLjUyMzYgNzUuNTEwNiIvPjxwb2x5Z29uIHBvaW50cz0iNzUuMjYzMiA4Ny4wMzQzIDc1LjI2MzIgNjMuOTg3IDc1LjI2MzIgNTguMTYzNCA3NS4yNjMyIDQ2LjYzOTcgNjkuNDQwNCA0Ni42Mzk3IDY5LjQ0MDQgNTguMTYzNCA1Mi4xNTQ2IDU4LjE2MzQgNTIuMTU0NiA2My45ODcgNjkuNDQwNCA2My45ODcgNjkuNDQwNCA4MS4yMTA2IDQwLjYzMDkgODEuMjEwNiA0MC42MzA5IDg3LjAzNDMgNzUuMjYzMiA4Ny4wMzQzIi8+PHBvbHlnb24gcG9pbnRzPSI2My43Mzk2IDY5LjY4NyA0Ni40NTM3IDY5LjY4NyA0Ni40NTM3IDUyLjQ2MzQgNjMuNzM5NiA1Mi40NjM0IDYzLjczOTYgNDYuNjM5NyA0MC42MzA5IDQ2LjYzOTcgNDAuNjMwOSA3NS41MTA2IDYzLjczOTYgNzUuNTEwNiA2My43Mzk2IDY5LjY4NyIvPjxwb2x5Z29uIHBvaW50cz0iNDAuNjMwOSAwIDQwLjYzMDkgNS44MjM2IDY5LjQ0MDQgNS44MjM2IDY5LjQ0MDQgMjMuMDQ3MyA1Mi4xNTQ2IDIzLjA0NzMgNTIuMTU0NiAyOC44NzA5IDY5LjQ0MDQgMjguODcwOSA2OS40NDA0IDQwLjM5NDUgNzUuMjYzMiA0MC4zOTQ1IDc1LjI2MzIgMjguODcwOSA3NS4yNjMyIDIzLjA0NzMgNzUuMjYzMiAwIDQwLjYzMDkgMCIvPjxwb2x5Z29uIHBvaW50cz0iNjMuNzM5NiAxMS41MjM2IDQwLjYzMDkgMTEuNTIzNiA0MC42MzA5IDQwLjM5NDUgNjMuNzM5NiA0MC4zOTQ1IDYzLjczOTYgMzQuNTcwOSA0Ni40NTM3IDM0LjU3MDkgNDYuNDUzNyAxNy4zNDczIDYzLjczOTYgMTcuMzQ3MyA2My43Mzk2IDExLjUyMzYiLz48L3N2Zz4=");
  background-repeat:repeat;background-size:126px auto;background-position:right top;
  opacity:.15;pointer-events:none;
  -webkit-mask-image:linear-gradient(to right,transparent,#000 55%);
  mask-image:linear-gradient(to right,transparent,#000 55%);
}


/* ==========================================================================
   Proof band                                                         [both]
   White band directly beneath the hero. Four figures.

   Numerals are 32px Asap Bold, which is WCAG "large text", so all three
   accents clear 3:1 on the paper tint. Labels stay gray at body size.
   ========================================================================== */
.proofband{background:var(--white);border-bottom:1px solid var(--line);padding:0}
.proof{display:grid;grid-template-columns:repeat(4,1fr)}
/* The band is drawn for four. A page supplying its own figures may have fewer,
   and three in a four-column grid leaves a dead cell on the right with the
   last divider already suppressed, which reads as a missing item rather than
   as a deliberate three. Added 21 September 2026 with the per-page form of
   components/proofband.njk, which emits the class. */
.proof.n3{grid-template-columns:repeat(3,1fr)}
.proof.n2{grid-template-columns:repeat(2,1fr)}
.proof div{padding:26px 22px 28px;border-right:1px solid var(--line);
  transition:background-color var(--ease),box-shadow var(--ease)}
.proof div:last-child{border-right:0}
.proof b{display:block;font-family:var(--disp);font-weight:700;font-size:2rem;line-height:1;
  color:var(--navy);transition:color var(--ease)}
.proof span{display:block;font-size:14px;color:var(--muted);margin-top:var(--sp-1);max-width:22ch}
.proof div:hover{background:var(--paper);box-shadow:inset 0 3px 0 var(--blue)}
.proof div:hover b{color:var(--blue)}
.proof div:nth-child(2):hover{box-shadow:inset 0 3px 0 var(--green)}
.proof div:nth-child(2):hover b{color:var(--green)}
.proof div:nth-child(3):hover{box-shadow:inset 0 3px 0 var(--purple)}
.proof div:nth-child(3):hover b{color:var(--purple)}


/* ==========================================================================
   Answer-first block                                                 [home]
   The definitional paragraph written to be lifted whole by an answer engine.
   Wider measure than body prose, because it is one extractable unit.
   ========================================================================== */
.answer{max-width:82ch;font-size:1.08rem;line-height:1.6;color:var(--body);margin:0 0 8px}
.answer b{color:var(--navy)}

/* Every paragraph in this band shares the lead's right edge unless it declares
   its own measure. cb.definition emits its `body` paragraphs as bare <p>, which
   fell through to base.css: 66ch at 1rem against .answer's 82ch at 1.08rem. ch
   is proportional to font-size within one family, so that was a right edge 34%
   short of the one above it, on 29 built pages.

   82 x 1.08 = 88.56, so 88.56ch at 1rem is the SAME PHYSICAL WIDTH as 82ch at
   1.08rem, in any font. The number is exact rather than round and that is why
   it is not tidied to 88ch. Change it only with .answer's own two values.

   :where() holds the selector at one class, and the rule sits ABOVE the classed
   paragraphs below it, so anything that sets its own max-width later wins on
   source order: .answer-meta at 74ch, .case-note at 80ch, and every .measure-*
   utility. That is the rule rather than a growing :not() list, and it means a
   page can add a spacing utility to one of these paragraphs without silently
   dropping it back to the 66ch default.

   The margin is the same defect vertically. .answer closes on 8px, for the meta
   line that sits tight under it, while the paragraphs below each close on 1em,
   so the first gap was half the others. Adjacent margins collapse to the larger
   of the two, so 1em here produces 1em rather than 8px + 1em. */
.answer ~ :where(p){max-width:88.56ch}
.answer + :where(p){margin-top:1em}

.answer-meta{font-size:15px;color:var(--muted);max-width:74ch;margin:0 0 34px}
/* Case study summary bands carry no meta line, so the figure row would sit on
   .answer's own 8px. Restores the 34px the meta line used to supply. */
.answer + .stats{margin-top:26px}



/* ==========================================================================
   Service card grid                                                  [home]
   Three across, separate cards, each white on a hairline. Accent rotation:
   blue, green, purple.

   IT WAS ONE BORDERED BOX WITH HAIRLINE DIVIDERS AND NO GAP UNTIL 8 OCTOBER
   2026, so that it read as one table of contents. That only works when the
   card count is a multiple of three, and it is not: the consulting and
   assessment hubs carry five children, resources four, about-us two, and
   cb.grid takes whatever a leaf supplies. The leftover cells inherited the
   box's white fill and its border, so an incomplete row rendered as one or
   two empty white cards. Chad reported it on the consulting hub as the
   section looking like it was missing pieces, and he is reading it exactly
   as drawn.

   Separating the cards is what makes the count stop mattering: an unused
   cell is now the section's own band and there is nothing there to look
   unfinished. It is also .posts' idiom, which this build already uses for
   every other card grid, so it is one less idiom rather than one more.

   The nth-child border math went with it, in all three breakpoints. That
   arithmetic was wrong for a ragged count anyway: five cards two across
   stripped the bottom rule from the fourth card, which sits in the middle
   row. Nothing replaces it, because a gap needs no bookkeeping.
   ========================================================================== */
.svc{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-5)}
.svc article{padding:26px var(--sp-5) 28px;border:1px solid var(--line);
  background:var(--white);display:flex;flex-direction:column;
  transition:background-color var(--ease)}
.svc article:hover{background:var(--paper)}
.svc-rule{width:40px;height:3px;background:var(--blue);margin:0 0 var(--sp-3)}
.svc article:nth-child(3n+2) .svc-rule{background:var(--green)}
.svc article:nth-child(3n+3) .svc-rule{background:var(--purple)}
.svc h3{margin:0 0 8px;font-size:1.08rem}
.svc h3 a{text-decoration:none;color:var(--title)}
.svc h3 a:hover{color:var(--blue)}
.svc p{font-size:15.5px;color:var(--muted);margin:0 0 var(--sp-4);max-width:none}
.svc .more{margin:auto 0 0;font-size:14.5px;font-weight:700;max-width:none}
.svc .more a{text-decoration:none;color:var(--navy);border-bottom:2px solid var(--blue);padding-bottom:2px}
.svc .more a:hover{color:var(--blue)}

/* Two cards rather than three.                                   [new, §4]
   crossSell drops the card for the page it is on, so the two leaves that
   ARE engagement models render two cards. Separate cards made the border
   half of this rule unnecessary on 8 October 2026, but not the grid half:
   without it the two cards take two thirds of the row and the third column
   is a gap wide enough to read as a dropped card. Scoped to min-width 561px
   so the 560px single-column rule, which is less specific, still wins on a
   phone. */
@media (min-width:561px){
  .svc.svc-2{grid-template-columns:repeat(2,1fr)}
}


/* ==========================================================================
   Stat cards                                                           [ms]
   Three figures with a source line. Accent top rule and figure share a
   color: navy, green, light purple.
   ========================================================================== */
.case-sub{font-family:var(--text);font-style:italic;color:var(--blue);
  font-size:1.05rem;line-height:1.5;margin:0 0 30px;max-width:70ch}
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
.stat{background:var(--white);border:1px solid var(--line);border-top:5px solid var(--navy);
  padding:28px var(--sp-5) 20px;display:flex;flex-direction:column;
  transition:background-color .2s ease}
.stat:nth-child(2){border-top-color:var(--green)}
.stat:nth-child(3){border-top-color:var(--purple)}
.stat .fig{font-family:var(--disp);font-weight:700;font-size:clamp(2.5rem,4.4vw,3.15rem);
  line-height:1;color:var(--navy);margin:0 0 var(--sp-4);max-width:none}
.stat:nth-child(2) .fig{color:var(--green)}
.stat:nth-child(3) .fig{color:var(--purple)}
.stat .fig .pct{font-size:.56em}
.stat .claim{font-size:16px;line-height:1.5;color:var(--body);margin:0 0 26px;max-width:none}
.stat .src{margin:auto 0 0;padding-top:var(--sp-3);border-top:1px solid var(--line);
  font-size:13px;line-height:1.4;color:var(--muted);max-width:none}
.stat:hover{background:var(--paper)}
.case-note{margin:28px 0 0;padding-top:22px;border-top:1px solid var(--line);
  font-style:italic;font-size:1.02rem;line-height:1.5;color:var(--body);max-width:80ch}


/* ==========================================================================
   Service tiers                                                        [ms]
   Essential / Select / Signature. Tier names run 25.6px bold, which is WCAG
   "large text", so the green and blue accents are safe here. Not at body size.
   One shared call to action beneath the grid, not three inside it.
   ========================================================================== */
.tiers{display:grid;grid-template-columns:repeat(3,1fr);gap:0;
  border:1px solid var(--line);background:var(--white)}
.tier{padding:28px var(--sp-5) 30px;border-right:1px solid var(--line);display:flex;flex-direction:column;
  position:relative}
.tier:last-child{border-right:0}

/* [new, §4] .tier-badge — the "Recommended" flag over one plan name. Added 5
   October 2026 for the president's Managed Services review v1.5, D-9, which
   makes Select the plan to steer a buyer to. MANAGED-SERVICES-REBUILD.md
   section 11.

   IT IS POSITIONED RATHER THAN IN THE FLOW, and that is the whole of the
   decision. In the flow it pushes one .tier-name down by its own height and
   the three plan names stop aligning across a three-column grid with no gap
   between the cards, which reads as a rendering fault rather than as
   emphasis. The alternative is a reserved empty strip on all three, which is
   a placeholder by another name. Absolute placement sits it inside .tier's
   existing 28px top padding and costs the other two cards nothing, which is
   why `position:relative` went on .tier above.

   One card per grid should carry it. A grid where every card is recommended
   recommends nothing. */
.tier-badge{position:absolute;top:9px;right:var(--sp-5);margin:0;max-width:none;
  font-family:var(--text);font-size:12px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--blue)}

.tier-name{font-family:var(--disp);font-size:1.6rem;font-weight:700;line-height:1.1}
.tier-e .tier-name{color:var(--green)}
.tier-s .tier-name{color:var(--blue)}
.tier-g .tier-name{color:var(--purple)}
.tier-rule{height:3px;margin:var(--sp-2) 0 var(--sp-4);width:44px}
.tier-e .tier-rule{background:var(--green)}
.tier-s .tier-rule{background:var(--blue)}
.tier-g .tier-rule{background:var(--purple)}
.tier .lead{font-size:1.05rem;font-weight:700;color:var(--title);margin-bottom:10px;max-width:none}
.tier .who{color:var(--muted);font-size:15.5px;max-width:none;margin-bottom:var(--sp-4)}
.tier .cadence{font-size:14.5px;color:var(--muted);max-width:none;
  border-top:1px solid var(--line);padding-top:var(--sp-3);margin:auto 0 0}
.tier .cadence b{font-family:var(--text);font-weight:700;color:var(--navy)}
.tiers-cta{text-align:center;margin:26px 0 0;max-width:none}
.tiers-cta a{display:inline-block;font-weight:700;text-decoration:none;color:var(--navy);
  border-bottom:2px solid var(--blue);padding-bottom:2px}
.tiers-cta a:hover{color:var(--blue)}


/* ==========================================================================
   Comparison matrix                                                    [ms]
   One table per service group. Table chrome itself lives in base.css.
   ========================================================================== */
.mx-intro{color:var(--muted);margin:0 0 30px}
.mx-group{margin-bottom:44px}
.mx-group:last-of-type{margin-bottom:0}
.mx-label{font-family:var(--text);font-weight:700;font-size:13.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--navy);margin:0 0 var(--sp-3)}
.mx-label.tight{margin-bottom:var(--sp-1)}
.mx-sub{font-size:14.5px;line-height:1.5;color:var(--muted);margin:0 0 var(--sp-4);max-width:88ch}
.tbl-scroll{overflow-x:auto}

/* Status markers. Both are graphical objects, so green at 3.2:1 and blue at
   3.45:1 clear the 3:1 non-text requirement; .sr text carries the meaning. */
.yes::before{content:"\2713";color:var(--green);font-size:1.2rem;font-weight:700;line-height:1}
.no::before{content:"\2013";color:var(--off-mark);font-size:1rem;line-height:1}
.scoped::before{content:"$";color:var(--blue);font-size:1.05rem;font-weight:700;line-height:1}
.key{display:flex;gap:var(--sp-5);flex-wrap:wrap;font-size:14.5px;color:var(--muted);margin-top:8px}
.key .yes::before,.key .no::before,.key .scoped::before{margin-right:7px;vertical-align:-1px}

/* Fine print beneath a matrix. */
.fine{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-4) var(--sp-8);margin-top:34px;
  padding-top:26px;border-top:1px solid var(--line)}
.fine p{margin:0;font-size:14.5px;line-height:1.55;color:var(--muted);max-width:54ch}
.fine b{color:var(--navy);font-weight:700}


/* ==========================================================================
   Callout                                                              [ms]
   Navy panel with a large numeral, dropped inside a light section.
   Blue on navy is 3.5:1, fine here because the numeral is large text.
   ========================================================================== */
.callout{background:var(--navy);color:var(--white);display:grid;grid-template-columns:180px 1fr;
  align-items:center;margin-top:38px}
.callout .num{font-family:var(--disp);font-weight:700;font-size:2.2rem;line-height:1;
  color:var(--blue);text-align:center;padding:26px var(--sp-2)}
.callout .txt{padding:26px 32px 26px 0;font-size:16px;line-height:1.55;color:var(--on-navy)}
.callout .txt b{color:var(--white);font-weight:700}

/* .callout.tel is the variant whose figure is a dialable phone number rather
   than a numeral.                                               [new, §4]
   Added 5 October 2026 for /database-managed-services/emergency-dba-support-
   services/, the one page in the build whose callout has to print a number
   somebody acts on rather than reads. The base block's first column is a
   fixed 180px, sized for "11" and "27-32%"; "(844) 282-3282" is fourteen
   characters and overflows it, so the column goes to auto and the figure is
   held on one line. The size does not drop: the whole point of putting the
   number here is that it is the largest thing in the band.

   THE FIGURE IS A LINK AND IT CARRIES AN UNDERLINE. A tel: number that dials
   and one that does not look identical without it, and this page is read by
   somebody mid-incident who should not have to find out by tapping. Blue on
   navy is the 3.5:1 the base block's comment relies on, which holds here
   because 2.2rem is large text.

   SKIP-INK IS OFF AND THAT IS WHY THE RULE HAS A SECOND LINE. The default
   skips an underline around a descending glyph. "(844)" opens on a
   parenthesis, which descends, so the line was cut as soon as it started and
   left a two-pixel stub standing in the parenthesis's left side bearing. At
   this size that stub reads as a period in front of the number, which on a
   page whose one job is getting a number dialed is the worst place on the
   site for a stray character. Drawing the line through the parentheses is the
   lesser of the two. */
.callout.tel{grid-template-columns:auto 1fr}
.callout.tel .num{white-space:nowrap;padding:26px 32px}
.callout.tel .num a{color:inherit;text-decoration:underline;text-underline-offset:5px;
  text-decoration-skip-ink:none}
.callout.tel .num a:hover,.callout.tel .num a:focus-visible{color:var(--white)}


/* ==========================================================================
   Reasons grid                                                         [ms]
   Why choose Fortified Data. Three across, accent rotation on the top rule.
   ========================================================================== */
.why{display:grid;grid-template-columns:repeat(3,1fr);gap:28px var(--sp-7)}
/* Two items, from 21 September 2026. Without this a two-item block fills two
   cells of three and the empty third reads as a missing item. Emitted by the
   grid macro from the item count, never written by a page. */
.why.two{grid-template-columns:repeat(2,1fr)}
.why div{border-top:3px solid var(--blue);padding-top:var(--sp-3)}
.why div:nth-child(3n+2){border-top-color:var(--green)}
.why div:nth-child(3n+3){border-top-color:var(--purple)}
.why h3{font-size:1.05rem;margin-bottom:4px}
.why p{font-size:15.5px;color:var(--muted);margin:0}


/* ==========================================================================
   Two-column split and platform cards                                  [ms]
   ========================================================================== */
.split{display:grid;grid-template-columns:1fr 1fr;gap:40px var(--sp-9);align-items:center}
.split h2{margin-bottom:.45em}
.split p{color:var(--muted);margin:0;max-width:52ch}

.plat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-3)}
.plat-grid span{background:var(--white);border:1px solid var(--line);border-radius:3px;
  padding:var(--sp-5) 20px;display:flex;align-items:center;justify-content:center;
  min-height:132px;overflow:hidden}
.plat-grid img{display:block;width:auto;height:auto;max-width:100%;max-height:76px}
/* Wide wordmarks need a lower cap than the square-canvas files. */
.plat-grid img.wide{max-height:42px}

.plat-note{display:flex;flex-wrap:wrap;gap:var(--sp-2);margin-top:34px;align-items:center}
.plat-note b{font-family:var(--text);font-size:13px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--navy)}
.plat-note span{background:var(--white);border:1px solid var(--line);border-radius:3px;
  padding:8px var(--sp-4);font-size:14.5px;color:var(--body)}


/* ==========================================================================
   Conviction band                                                     [both]
   Full-bleed navy statement. Display title, so Asap Bold and all caps.
   Blue on navy is only 3.5:1, so labels on this band are white.
   ========================================================================== */
.conviction{background:var(--navy);color:var(--white)}
.conviction h2{font-family:var(--disp);font-weight:700;color:var(--white);
  font-size:clamp(1.6rem,3.4vw,2.15rem);line-height:1.12;text-transform:uppercase;
  letter-spacing:.01em;max-width:26ch;margin:0 0 26px}
.conviction .eyebrow{color:var(--white)}
.conviction .rule{width:96px;height:1px;background:var(--blue);margin:0 0 30px}
.conviction-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:var(--sp-7) 60px;align-items:start}
.conviction p{color:var(--on-navy);font-size:1.05rem;margin:0 0 1em;max-width:60ch}
.conviction p:last-child{margin-bottom:0}

.principles{list-style:none;margin:0;padding:0}
.principles li{border-top:1px solid var(--rule-navy);padding:var(--sp-4) 0}
.principles li:first-child{border-top:0;padding-top:0}
.principles li:last-child{padding-bottom:0}
.principles b{display:block;font-family:var(--text);font-weight:700;font-size:1rem;
  color:var(--white);margin-bottom:5px}
.principles span{display:block;font-size:15.5px;color:var(--on-navy);line-height:1.55}


/* ==========================================================================
   Video facade                                                       [home]
   Click to load. No third-party iframe until the visitor asks for it, which
   keeps the player off the critical path. Genuine performance win, kept.
   ========================================================================== */
.testi-grid{display:grid;grid-template-columns:1.05fr .95fr;gap:var(--sp-7) var(--sp-8);align-items:stretch}

.vid{margin:0}
.vid-frame{position:relative;aspect-ratio:16/9;background:var(--navy);border:1px solid var(--line);
  overflow:hidden;max-width:520px}
.vid-frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
/* Self-hosted <video>, /about-us/. Same box as the injected iframe. .vid-wide
   drops .vid-frame's 520px cap, which exists for the homepage's two-column
   testimonial grid and is wrong for a video that owns its own band. */
.vid-frame video{position:absolute;inset:0;width:100%;height:100%;display:block;background:var(--navy)}
.vid-wide .vid-frame{max-width:860px}
.vid-intro{color:var(--muted);margin:0 0 var(--sp-5);max-width:66ch}
.vid-facade{position:absolute;inset:0;width:100%;height:100%;padding:0;border:0;cursor:pointer;
  background:var(--navy) center/cover no-repeat;display:flex;align-items:center;justify-content:center}
.vid-facade::before{content:"";position:absolute;inset:0;background:rgba(35,43,95,.45)}
.vid-play{position:relative;width:64px;height:64px;border-radius:50%;background:var(--white);
  display:flex;align-items:center;justify-content:center;transition:transform var(--ease)}
.vid-play::after{content:"";width:0;height:0;margin-left:5px;
  border-left:19px solid var(--navy);border-top:12px solid transparent;border-bottom:12px solid transparent}
.vid-facade:hover .vid-play{transform:scale(1.06)}
/* The webinar variant, 28 September 2026. The homepage facade is a <button>
   carrying its still as a background-image set from script; this one is an
   <a href> to the YouTube watch page carrying a real <img>, so that the
   control still works with script blocked. The z-index ladder is the whole
   difference: an <img> is a positioned child that would otherwise paint OVER
   .vid-facade::before and bury the play button. Scoped to the modifier
   rather than added to .vid-facade, because giving ::before a z-index
   sitewide would paint the homepage's play button underneath it. */
.vid-facade-img{text-decoration:none}
.vid-facade-img img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0}
.vid-facade-img::before{z-index:1}
.vid-facade-img .vid-play{z-index:2}
.vid figcaption{margin:var(--sp-4) 0 0;font-size:15.5px;line-height:1.5;color:var(--body);max-width:44ch}
.vid figcaption b{display:block;font-family:var(--text);font-weight:700;color:var(--navy);
  font-size:13px;letter-spacing:.06em;text-transform:uppercase;margin-top:8px}
.vid .cs{display:inline-block;margin-top:var(--sp-3);font-size:14.5px;font-weight:700;
  text-decoration:none;color:var(--navy);border-bottom:2px solid var(--blue);padding-bottom:2px}
.vid .cs:hover{color:var(--blue)}


/* ==========================================================================
   Review rotator                                                     [home]
   Every slide sits in the same grid cell, so the track is always as tall as
   the longest review and never resizes when the slide changes. That is what
   stops the section below from jumping.
   ========================================================================== */
.rev{background:var(--white);border:1px solid var(--line);border-top:4px solid var(--navy);
  padding:0;display:flex;flex-direction:column;overflow:hidden}
.rev-inner{padding:var(--sp-5) 26px 20px;display:flex;flex-direction:column;flex:1 1 auto}

/* Autoplay progress: the clearest signal that a rotation is about to happen. */
.rev-bar{height:2px;background:var(--bar-track);flex:0 0 auto}
.rev-bar i{display:block;height:100%;width:0;background:var(--blue)}
.rev.run .rev-bar i{animation:revfill 7000ms linear forwards}
@keyframes revfill{from{width:0}to{width:100%}}

.rev-scores{display:flex;gap:var(--sp-3) 30px;flex-wrap:wrap;
  padding-bottom:var(--sp-4);border-bottom:1px solid var(--line);margin-bottom:20px}
.rev-scores>div{display:flex;flex-direction:column;gap:var(--sp-1)}
.rev-src{font-size:12px;letter-spacing:.07em;text-transform:uppercase;font-weight:700;color:var(--navy)}
.rev-line{display:flex;align-items:center;gap:9px}
.rev-score{font-family:var(--disp);font-weight:700;font-size:1.6rem;line-height:1;color:var(--navy)}
.rev-of{font-size:13px;color:var(--muted)}
.stars{display:inline-flex;gap:2px;line-height:1}
.stars i{width:13px;height:13px;background:var(--blue);
  clip-path:polygon(50% 0,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%)}
.stars i.off{background:var(--off)}

.rev-track{flex:1 1 auto;display:grid;align-items:start;min-height:200px}
.rev-item{grid-area:1/1;opacity:0;visibility:hidden;transform:translateY(10px);
  transition:opacity var(--ease-slow),transform var(--ease-slow),visibility .38s step-end}
.rev-item.on{opacity:1;visibility:visible;transform:none;
  transition:opacity .38s ease .06s,transform .38s ease .06s,visibility 0s}
.rev-item blockquote{margin:10px 0 0;font-size:1.08rem;line-height:1.55;color:var(--title);font-weight:700}
.rev-item .rev-body{margin:var(--sp-2) 0 0;font-size:15.5px;line-height:1.6;color:var(--body);max-width:52ch}
.rev-item .rev-who{margin:var(--sp-4) 0 0;font-size:13.5px;line-height:1.5;color:var(--muted);max-width:none}
.rev-tag{display:inline-block;font-size:11px;font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;color:var(--navy);border:1px solid var(--line);
  padding:3px 8px;border-radius:2px;margin-left:10px;vertical-align:2px}
.rev-top{display:flex;align-items:center;flex-wrap:wrap}
.rev-foot{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);
  border-top:1px solid var(--line);padding-top:var(--sp-3);margin-top:var(--sp-4)}
.rev-dots{display:flex;gap:8px}
.rev-dots button{width:9px;height:9px;padding:0;border-radius:50%;border:1px solid var(--muted);
  background:none;cursor:pointer}
.rev-dots button[aria-current="true"]{background:var(--navy);border-color:var(--navy)}
.rev-nav{display:flex;gap:var(--sp-1)}
.rev-nav button{width:32px;height:32px;border:1px solid var(--line);background:var(--white);
  color:var(--navy);cursor:pointer;font-size:13px;line-height:1;border-radius:3px}
.rev-nav button:hover{border-color:var(--navy)}
.rev-all{margin:var(--sp-3) 0 0;font-size:13.5px;color:var(--muted);max-width:none}
.rev-all a{color:var(--navy);font-weight:700}

@media (prefers-reduced-motion:reduce){
  .rev-item{transition:none}
  .rev-bar{display:none}
}


/* ==========================================================================
   Post cards                                                         [home]
   ========================================================================== */
.posts{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-5)}
.posts article{background:var(--white);border:1px solid var(--line);border-top:3px solid var(--blue);
  padding:var(--sp-5) 22px;display:flex;flex-direction:column}
/* 3n rather than a flat 2 and 3: the homepage shows three posts, a blog
   index shows sixteen, and the rotation has to survive the second row. */
.posts article:nth-child(3n+2){border-top-color:var(--green)}
.posts article:nth-child(3n+3){border-top-color:var(--purple)}
.posts time{display:block;font-size:13px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted);margin-bottom:10px}
.posts h3{font-size:1.05rem;margin:0 0 10px}
.posts h3 a{text-decoration:none;color:var(--title)}
.posts h3 a:hover{color:var(--blue)}
.posts p{font-size:15px;color:var(--muted);margin:0 0 var(--sp-4);max-width:none}
.posts .more{margin:auto 0 0;max-width:none;font-size:14.5px;font-weight:700}
.posts .more a{text-decoration:none;color:var(--navy);border-bottom:2px solid var(--blue);padding-bottom:2px}

.blog-head{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--sp-5);flex-wrap:wrap}
.blog-head h2{margin-bottom:.35em}
.blog-head .all{font-size:14.5px;font-weight:700;text-decoration:none;color:var(--navy);
  border-bottom:2px solid var(--blue);padding-bottom:2px;margin-bottom:var(--sp-1)}


/* ==========================================================================
   Score cards                                                    [new, §4]
   /case-studies/ only, with components/scorecard.njk. The figure treatment
   is comp A's, design/case-study-templates/parts/a-headline.js, at the size
   a side panel carries rather than the size a full-width hero does.

   REBUILT 21 September 2026, the same day it landed. The first version
   stacked the callout above the title and the cards were too tall. The score
   now sits beside the prose behind a hairline, which turns about 110px of
   stacked callout into width and takes roughly a fifth off every scored card.

   TWO ACROSS IS THE GEOMETRY OF THAT SPLIT, NOT A PREFERENCE. A card cannot
   be both narrow and side by side. At three across the card carries about
   350px of content: give 140px to the panel and the summary runs in a 190px
   measure, sixteen lines instead of nine, and the card ends up taller than
   the stacked one it replaced. At two across the prose keeps a ~320px measure
   beside a 190px panel. --sc-col is the panel width and is the one dial.

   THE PANEL IS FIRST IN THE SOURCE AND PAINTS ON THE RIGHT. Explicit
   grid-column, the same mechanism and the same reason as .railed and .team:
   the figure is the claim, so it leads for a screen reader and a crawler, and
   when the card collapses it belongs above the summary rather than under the
   closing link. Never reach for `order`, `direction:rtl` or `row-reverse`.

   THE TRACK IS `auto` AND THE PANEL CARRIES ITS OWN WIDTH, MARGIN AND RULE.
   That is what lets the three cards with no score be one column with no
   divider and no dead strip, without a modifier class and without :has().
   A fixed second track would reserve 190px of nothing on those three.

   IT CANNOT REUSE .posts, WHICH IS DIRECTLY ABOVE. That card has no callout
   region, no divider and no before/after row, and it is a single column. It
   cannot reuse .stat either: that is a standalone figure card with a source
   line and no title, summary or link. What is shared is the idiom, white
   fill on a hairline border with a 3px accent top rule, which is deliberate
   and is why the two sit next to each other in this file.
   ========================================================================== */
.scorecards{--sc-col:190px;display:grid;grid-template-columns:repeat(2,1fr);gap:var(--sp-5)}
.scorecards article{background:var(--white);border:1px solid var(--line);border-top:3px solid var(--blue);
  padding:22px;display:grid;grid-template-columns:1fr auto}
.scorecards article:nth-child(3n+2){border-top-color:var(--green)}
.scorecards article:nth-child(3n+3){border-top-color:var(--purple)}

/* Both columns stretch, which is the grid default and is load-bearing twice:
   it is what lets .more's `margin:auto 0 0` pin the closing link to the
   bottom of every card regardless of summary length, and it is what runs the
   divider the full height of the card rather than stopping it under the
   panel's own content. Do not add align-items:start here.

   min-width:0 so a long unbroken string in the summary cannot push the grid
   track wider than its share and squeeze the panel. */
.scorecards .sc-body{grid-column:1;grid-row:1;min-width:0;display:flex;flex-direction:column}
.scorecards .sc-panel{grid-column:2;grid-row:1;width:var(--sc-col);
  margin-left:var(--sp-5);padding-left:var(--sp-5);border-left:1px solid var(--line)}

/* Smaller than .stat .fig's clamp(2.5rem,4.4vw,3.15rem), and smaller again
   than the stacked version this replaces, because the figure now lives in a
   190px column rather than across the card. The widest authored string is
   "$65,000" and the widest figure-plus-unit is "90.48%"; both hold one line
   at the top of this range. */
.scorecards .sc-fig{font-family:var(--disp);font-weight:700;
  font-size:clamp(1.9rem,2.9vw,2.35rem);line-height:1.02;color:var(--blue);
  margin:0 0 var(--sp-1);max-width:none}
.scorecards article:nth-child(3n+2) .sc-fig{color:var(--green)}
.scorecards article:nth-child(3n+3) .sc-fig{color:var(--purple)}
/* .56em is .stat .fig .pct's ratio, so a unit here reads at the same weight
   against its figure as a percent sign does on a stat card. */
.scorecards .sc-unit{font-size:.56em}
.scorecards .sc-label{font-size:13.5px;line-height:1.4;color:var(--muted);
  margin:0;max-width:none}

/* The panel carried a before/after row under the label until 21 September
   2026, four cards having a pair: two values either side of an arrow. It
   came out on the client's call because it did not work at this width. In a
   190px column a pair like "SQL Server 2012" and "SQL Server 2019" wraps
   across three lines however it is set, flex row or flowing text, and spends
   most of the panel on the context rather than on the claim. Anything that
   goes back in this slot has to answer that first. */

.scorecards h3{font-size:1.05rem;margin:0 0 10px}
.scorecards h3 a{text-decoration:none;color:var(--title)}
.scorecards h3 a:hover{color:var(--blue)}
.scorecards .sc-sum{font-size:14.5px;line-height:1.55;color:var(--muted);
  margin:0 0 var(--sp-4);max-width:none}
.scorecards .more{margin:auto 0 0;max-width:none;font-size:14.5px;font-weight:700}
.scorecards .more a{text-decoration:none;color:var(--navy);border-bottom:2px solid var(--blue);
  padding-bottom:2px}


/* ==========================================================================
   Ticked list                                                    [new, §4]
   NEW COMPONENT. The qualification list, which the two harvested content
   patterns ask for three times: the hub's core inclusions, the hub's "when to
   choose X" with its six qualifying scenarios, and the leaf's "who needs
   this". One block rather than three, because they are one shape.

   Two columns, so six scenarios read as a block rather than as a long column
   the reader scrolls past. .ticks.one is the single-column variant, for a
   list sitting inside a .split column.

   The mark is the same green check as .yes in the comparison matrix, drawn
   here as a positioned ::before so a wrapped line indents under the text
   rather than under the mark.
   ========================================================================== */
.ticks{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:1fr 1fr;
  gap:var(--sp-4) var(--sp-7)}
.ticks.one{grid-template-columns:1fr}
.ticks li{position:relative;padding-left:30px;font-size:15.5px;line-height:1.55;color:var(--body)}
.ticks li::before{content:"\2713";position:absolute;left:0;top:0;color:var(--green);
  font-weight:700;font-size:1.15rem;line-height:1.35}
.ticks b{display:block;font-weight:700;color:var(--title)}


/* ==========================================================================
   Outcome cards                                                   [new, §4]
   NEW COMPONENT. Leaf pattern step 5, real results, three named-industry
   outcomes. Content comes from shared.case_outcomes, which is a headline plus
   a list of results, and no existing block holds that shape: .stat is one
   figure with one claim, .svc is a paragraph and a link.

   auto-fit rather than a fixed three, because an industry page shows one
   outcome and a service leaf shows three, and both have to lay out correctly.

   The headline is Asap: it is a figure, like .stat .fig and .tier-name, not
   body copy. Asap stays off body copy at any size.
   ========================================================================== */
.outcomes{display:grid;grid-template-columns:repeat(auto-fit,minmax(258px,1fr));gap:var(--sp-5)}
.outcomes article{background:var(--white);border:1px solid var(--line);
  border-top:3px solid var(--blue);padding:var(--sp-5) 22px;display:flex;flex-direction:column}
.outcomes article:nth-child(3n+2){border-top-color:var(--green)}
.outcomes article:nth-child(3n+3){border-top-color:var(--purple)}
.outcomes .sector{font-size:12.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);margin:0 0 8px;max-width:none}
.outcomes h3{font-family:var(--disp);font-weight:700;font-size:1.35rem;line-height:1.15;
  color:var(--navy);margin:0 0 var(--sp-3)}
.outcomes article:nth-child(3n+2) h3{color:var(--green)}
.outcomes article:nth-child(3n+3) h3{color:var(--purple)}
.outcomes ul{list-style:none;margin:0;padding:0}
.outcomes li{position:relative;padding-left:26px;font-size:15px;line-height:1.5;
  color:var(--body);margin-bottom:10px}
.outcomes li:last-child{margin-bottom:0}
.outcomes li::before{content:"\2713";position:absolute;left:0;top:0;color:var(--green);
  font-weight:700;line-height:1.5}


/* ==========================================================================
   Pull quote                                                      [new, §4]
   NEW COMPONENT. Hub pattern step 7, the customer testimonial. The homepage
   has the review rotator and the video figure; neither is a single attributed
   quote sitting in the flow of a hub page, and borrowing .rev-item for one
   would drag the rotator's grid stacking with it.

   Attribution is required by the template, not by this file: an anonymous
   quote on a B2B services page reads as invented.
   ========================================================================== */
.quote{background:var(--white);border:1px solid var(--line);border-left:4px solid var(--blue);
  padding:var(--sp-6) var(--sp-7);margin:0;max-width:76ch}
.quote blockquote{margin:0;font-size:1.15rem;line-height:1.55;color:var(--title);font-weight:700}
.quote figcaption{margin:var(--sp-4) 0 0;font-size:14.5px;line-height:1.5;color:var(--muted)}
.quote figcaption b{display:block;font-family:var(--text);font-weight:700;font-size:13px;
  letter-spacing:.06em;text-transform:uppercase;color:var(--navy);margin-bottom:3px}

/* ==========================================================================
   Signed statement                                                 [new, §4.2]

   NEW COMPONENT, added 21 September 2026 for /about-us/.

   One portrait beside one quoted narrative with an attribution under it. The
   header of components/welcome.njk carries why this is not .team and not
   .quote; in CSS terms the distinction is that .team is a roster row sized for
   scanning at 15.5px, and this is one argument sized for reading at 1.05rem.

   The portrait column is 220px rather than the roster's 440px. This block is
   one person and the photograph is identification, not the subject; at 440px
   a single headshot beside three paragraphs reads as a portrait with a caption.

   `align-items:start` and not center. The attribution sits at the bottom of a
   variable-length narrative, and centering the photograph against it leaves the
   face floating beside the middle of the second paragraph.
   ========================================================================== */
.welcome{display:grid;grid-template-columns:220px 1fr;gap:var(--sp-5) var(--sp-8);
  align-items:start;margin:0}
.welcome-photo{display:block;width:220px;height:220px;object-fit:cover;
  border:1px solid var(--line);background:var(--navy)}
.welcome-body{min-width:0}
/* Not bold, unlike .quote's blockquote. That block is one sentence and carries
   it on weight; three paragraphs at 700 is a wall. */
.welcome blockquote{margin:0;quotes:none}
.welcome blockquote p{font-size:1.05rem;line-height:1.62;color:var(--body);
  margin:0 0 1em;max-width:66ch}
.welcome blockquote p:last-child{margin-bottom:0}
.welcome figcaption{margin:var(--sp-5) 0 0;padding-top:var(--sp-4);
  border-top:3px solid var(--blue);max-width:66ch}
.welcome figcaption b{display:block;font-family:var(--text);font-weight:700;
  font-size:15px;letter-spacing:.04em;color:var(--navy)}
.welcome figcaption span{display:block;font-size:13px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--blue);font-weight:700;margin-top:3px}

/* ==========================================================================
   Review scores                                                    [new, §4.5]

   NEW COMPONENT, added 21 September 2026 for the social proof band.

   A static row of platform ratings from shared.review_scores. The homepage
   renders the same data inside a JavaScript rotator; this does not, because
   nothing here needs to move and a score that only exists after script runs
   is a score no crawler sees.

   auto-fit rather than a fixed column count. There are two sources today and
   a third would otherwise need a number changed here.

   .sp-sub is the subheading between the parts of the social proof band. It is
   an h3 under an h2 and needs to read as a label rather than as a third level
   of heading, which is what the rest of this file does with .eyebrow.
   ========================================================================== */
.sp-sub{font-family:var(--text);font-weight:700;font-size:13px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted);margin:var(--sp-8) 0 var(--sp-5)}

.scores{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:var(--sp-3)}
.scores li{background:var(--white);border:1px solid var(--line);
  border-top:3px solid var(--blue);border-radius:3px;padding:var(--sp-5)}
.scores li:nth-child(3n+2){border-top-color:var(--green)}
.scores li:nth-child(3n+3){border-top-color:var(--purple)}
.scores-src{display:block;font-family:var(--text);font-weight:700;font-size:13px;
  letter-spacing:.06em;text-transform:uppercase;color:var(--navy)}
.scores-fig{display:block;margin-top:var(--sp-2);font-size:15px;color:var(--muted)}
.scores-fig b{font-family:var(--disp);font-size:2rem;line-height:1;color:var(--navy);
  margin-right:6px}
/* --blue on white is 3.9:1, which is short of AA for 14px text but is the
   right side of 3:1 for a graphic. These are decoration and the rating is
   stated as a number in .scores-fig directly above, so nothing here is the
   only copy of anything. The spent glyph is --line, the same hairline the
   card is drawn with. */
.scores-stars{display:block;margin-top:var(--sp-2);font-size:15px;
  letter-spacing:2px;color:var(--blue)}
.scores-stars .off{color:var(--line)}
.scores-count{display:block;margin-top:var(--sp-2);font-size:14px;color:var(--muted)}
.scores li a{display:inline-block;margin-top:var(--sp-3);font-size:14px;font-weight:700}

.sp-quote{margin-top:var(--sp-8)}

/* ==========================================================================
   Leadership                                                       [new, §4]
   NEW COMPONENT, added 21 September 2026 for /about-us/.

   A portrait beside a name, a role, a LinkedIn link and a multi-paragraph
   bio. Nothing in the build did this. .why is a three-across text grid with
   no room for an image, .outcomes is a results card, and .split puts one
   image column beside one prose column for a whole section rather than once
   per person. Borrowing any of the three would have meant a modifier that
   changed the grid, the type scale and the image handling at once, which is
   a new component wearing another component's name.

   The photo column is fixed rather than fractional, so portraits line up down
   the page whatever the bio length is, and the rule under each member does the
   separating. Portraits are square and cropped with object-fit, because the
   live headshots are square, and a source image that is not will letterbox
   rather than distort.

   THE SIDES ALTERNATE. Rebuilt 21 September 2026, concept 2 of the redesign.
   Odd rows put the portrait left, even rows put it right, and the whole of
   that is the :nth-child(even) rule below.

   It is done with explicit grid-column placement. `direction: rtl` and
   `flex-direction: row-reverse` both draw the same picture in fewer
   declarations, and both do it by reversing the source order, which hands a
   screen reader and a crawler a different reading order on every second
   person. The markup emits portrait, name, role, link, bio on every row
   without exception; only the painting changes. The header of
   components/team.njk states the same contract from the other side.

   --team-col IS THE 50/50 DIAL. The concept asked for a true half-and-half
   grid. Half of the 1280px wrap is about 584px, and the source photographs
   will not carry it: four of the five are 450px square and the fifth is
   1030px, and no larger version exists on the live site, checked on 21
   September 2026 with -scaled, -1024x1024 and -768x768 all returning 404. A
   450px file in a 584px box upscales at 1x and is visibly soft at 2x. So the
   column is capped at 440px, just under native, which reads as the concept
   and stays crisp. The day photography at 1200px square arrives, this becomes
   a true 50/50 by setting --team-col to 1fr. One value, one line.

   The LinkedIn action is .li-link, not .li-btn. Same button shape as the
   footer's, inverted fill for a light band. The note above the rule itself,
   further down this block, carries the reasoning.
   ========================================================================== */
.team-intro{color:var(--muted);margin:0 0 var(--sp-6);max-width:66ch}

.team{--team-col:440px;display:grid;gap:var(--sp-8)}
.team-member{display:grid;grid-template-columns:var(--team-col) 1fr;
  gap:var(--sp-5) var(--sp-8);align-items:start;
  padding-top:var(--sp-8);border-top:1px solid var(--line)}
.team-member:first-child{padding-top:0;border-top:0}

/* The alternation. Both children are placed explicitly and both are pinned to
   row 1, because a grid item given only a column would otherwise be pushed to
   a new row by the sibling that precedes it in the source. */
.team-member:nth-child(even){grid-template-columns:1fr var(--team-col)}
.team-member:nth-child(even) .team-photo{grid-column:2;grid-row:1}
.team-member:nth-child(even) .team-body{grid-column:1;grid-row:1}

/* width:100% and not a pixel value: the column is the cap, so the one source
   that is larger than 440px fills the same box as the four that are not, and
   changing --team-col changes the rendered image with it. aspect-ratio keeps
   the square that object-fit crops to. */
.team-photo{display:block;width:100%;height:auto;aspect-ratio:1;object-fit:cover;
  border:1px solid var(--line);background:var(--navy)}
.team-body h3{font-size:1.25rem;margin:0 0 3px}
.team-role{font-family:var(--text);font-weight:700;font-size:13px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--blue);margin:0 0 var(--sp-3);max-width:none}
.team-body p{font-size:15.5px;line-height:1.6;color:var(--body);margin:0 0 1em;max-width:66ch}
.team-body p:last-child{margin-bottom:0}

/* The LinkedIn action. It began as an underlined inline link and was too quiet
   next to 300 words of bio, so from 21 September 2026 it is a button built on
   the same shape as the footer's .foot a.li-btn: inline-flex, a 10px gap, the
   glyph at 18px, 4px radius. The fill is inverted because the band is: the
   footer sits on navy and paints the button white, a bio sits on white or the
   navy tint and paints it navy. Same component, opposite ground.

   Not scoped under a parent the way .li-btn has to be. That one loses to
   `.foot a` on specificity and paints its own label white on white; there is
   no equivalent rule over a bio, so `a.li-link` is enough to beat base.css. */
.team-social{margin:0 0 var(--sp-5)!important}
a.li-link{display:inline-flex;align-items:center;gap:10px;background:var(--navy);
  color:var(--white);font-weight:700;font-size:14.5px;text-decoration:none;
  padding:10px var(--sp-4);border-radius:4px;transition:background var(--ease)}
a.li-link svg{width:18px;height:18px;display:block;fill:currentColor;flex:0 0 auto}
a.li-link:hover{background:var(--navy-hover);color:var(--white);text-decoration:none}

/* ==========================================================================
   Closing panel                                                    [merged]

   The two mockups disagreed. managed-services-page-v7.html closed on a navy
   panel; homepage-v1.html closed on a light paper band with the two actions
   lifted into a bordered white card, on the argument that the navy bands
   further up the page were already doing the heavy lifting.

   Both are kept. .close is the navy default; .close.light is the paper
   variant. One page cannot use both, and a page picks based on what came
   before it, not by preference.
   ========================================================================== */
.close{background:var(--navy)}
.close h2{color:var(--white);max-width:24ch}
.close h2::after{background:var(--blue)}
.close p{color:var(--on-navy)}
.close .tagline{font-family:var(--disp);font-weight:700;font-size:1.05rem;
  text-transform:uppercase;letter-spacing:.06em;color:var(--blue);margin:26px 0 0}

.close.light{background:var(--paper);border-top:1px solid var(--line)}
.close.light .wrap{display:grid;grid-template-columns:1.1fr .9fr;gap:var(--sp-7) var(--sp-9);align-items:center}
.close.light h2{color:var(--title);max-width:22ch}
.close.light p{color:var(--body);font-size:1.08rem;max-width:52ch;margin:0}
.close.light .tagline{font-size:1rem;color:var(--navy);margin:var(--sp-5) 0 0}

.close-card{background:var(--white);border:1px solid var(--line);border-left:4px solid var(--blue);
  padding:26px 26px var(--sp-5)}
.close-card .kicker{font-family:var(--text);font-weight:700;font-size:12.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--navy);margin:0 0 var(--sp-4);max-width:none}
.close-card .btns{margin:0;flex-direction:column;gap:10px}
.close-card .btn{text-align:center}
.close-micro{margin:var(--sp-4) 0 0;font-size:14px;font-style:italic;color:var(--muted);max-width:none}


/* ==========================================================================
   Legal document                                                  [new, §4]
   NEW COMPONENT, added for rows 35 and 36. The ported privacy policy and
   terms of use are 1,379 and 3,911 words of unclassed prose, and they are
   the only long-form text on the site. Everything else that looks like a
   list is .ticks, which is a qualification list with a green check and the
   wrong semantics entirely for "you must not introduce viruses".

   So what a bare <ul> needs here is a measure. base.css caps p at 66ch and
   leaves li alone, which on the then 1080px wrap set a clause at twice the line
   length of the paragraph above it. The li cap matches the p cap, and the
   container sits a little wider so a nested list still has somewhere to go.

   The heading rhythm is the second half. Terms of use runs 22 numbered
   clauses, and h2's default margin is written for one heading per section
   rather than twenty-two in a row.

   Used by layouts/legal.njk's two pages and nothing else.
   ========================================================================== */
.legal{max-width:78ch}
.legal h2{margin-top:var(--sp-8)}
.legal h2:first-child{margin-top:0}
.legal h3{margin-top:var(--sp-6)}
.legal ul,.legal ol{margin:0 0 1em;padding-left:24px}
.legal li{margin-bottom:.55em;line-height:1.6}
.legal li:last-child{margin-bottom:0}
.legal li,.legal li p{max-width:66ch}


/* ==========================================================================
   Footer                                                             [home]
   Recognition row lives here, not in the closing call to action.
   ========================================================================== */
.foot{background:var(--navy);color:var(--on-navy);font-size:15px}
.foot a{color:var(--on-navy);text-decoration:none}
.foot a:hover{color:var(--white);text-decoration:underline}
.foot-top{display:grid;grid-template-columns:1.2fr 2.8fr;gap:40px var(--sp-8);padding:var(--sp-9) 0 40px}
.foot-brand .fl{width:190px;display:block;margin-bottom:var(--sp-4)}
.foot-brand .fl svg{width:100%;height:auto;display:block}
.foot-tag{font-family:var(--disp);font-weight:700;color:var(--white);font-size:1.05rem;
  line-height:1.3;margin:0 0 22px;max-width:34ch}
/* 34ch, raised from 24ch on 9 October 2026. The tagline is 32 characters, so
   a 24ch cap broke it after "what’s" and left "possible." alone on line two
   in the footer of all 205 pages. The brand column is 1.2fr of 4fr, roughly
   370px at the 1280px wrap, which the full line clears. It still wraps at
   the 1080px breakpoint where the footer goes to one column. */

/* Scoped as .foot a.li-btn: a bare .li-btn loses to .foot a on specificity,
   which paints the label white on a white fill and hides it. */
.foot a.li-btn{display:inline-flex;align-items:center;gap:10px;background:var(--white);
  color:var(--navy);font-weight:700;font-size:14.5px;text-decoration:none;
  padding:11px var(--sp-4);border-radius:4px}
.foot a.li-btn:hover{background:var(--wash-navy);color:var(--navy);text-decoration:none}
.foot a.li-btn svg{width:18px;height:18px;display:block;fill:currentColor;flex:0 0 auto}

.foot-nav{display:grid;grid-template-columns:repeat(4,1fr);gap:32px var(--sp-5)}
.foot-nav h3{font-family:var(--text);font-size:13px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--white);margin:0 0 var(--sp-3)}
.foot-nav ul{list-style:none;margin:0;padding:0}
.foot-nav li{margin-bottom:9px;font-size:14.5px;line-height:1.5}

.recog{border-top:1px solid var(--rule-navy);padding:26px 0;display:flex;align-items:center;
  gap:var(--sp-4) 32px;flex-wrap:wrap}
.recog-label{font-size:12.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--white)}
.badge{display:flex;align-items:center;gap:10px;border:1px solid var(--rule-navy);
  padding:10px var(--sp-3);border-radius:3px;font-size:13.5px;line-height:1.35;color:var(--on-navy)}
.badge b{display:block;font-family:var(--disp);font-weight:700;color:var(--white);font-size:14.5px}
.badge img{display:block;max-height:44px;width:auto}
/* .badge-ph, the dashed placeholder badge, removed 5 October 2026 with its
   only caller: the CRN MSP 500 box in the footer. [removed, was §4] */

.foot-legal{border-top:1px solid var(--rule-navy);padding:20px 0 32px;display:flex;
  justify-content:space-between;gap:var(--sp-3) 28px;flex-wrap:wrap;font-size:13.5px}
.foot-legal ul{list-style:none;display:flex;gap:22px;margin:0;padding:0;flex-wrap:wrap}


/* ==========================================================================
   Responsive
   Both mockups shipped near-identical nav breakpoints at 1040 and 900. The
   content breakpoints diverged: the homepage broke at 960, the managed
   services page at 860. Both are kept, because they describe different grids.
   ========================================================================== */

/* The nav compacts at 1180 rather than at 1040, from 8 October 2026. The
   emergency block added about 125px to the right of the row and the full-size
   nav no longer clears it in the band below the 1280px measure. Nothing else
   in the row can give: .fd-logo is flex:0 0 auto, the CTA is nowrap and so is
   the emergency block.

   It became two tiers on 9 October 2026, when the labels stopped wrapping.
   The row cannot shed a line any more, so it sheds type size instead: 13px
   from 1280, which is where .wrap stops growing and the row stops gaining
   room, and 12.5px from 1040, where the full seven items plus the CTA and the
   emergency block no longer clear 13px. Below 900 the nav is a drawer and
   none of this applies. */
@media (max-width:1280px){
  .fd-link{padding:10px 7px;font-size:13px}
  .fd-logo{width:146px}
}

@media (max-width:1040px){
  .fd-link{padding:10px 5px;font-size:12.5px}
  .fd-logo{width:134px}
  .fd-cta{padding:11px 14px}
}

@media (max-width:960px){
  /* One across. The card is already two columns internally and holds that
     split down to 560px, so the grid has nothing left to give here. */
  .scorecards{grid-template-columns:1fr}
  .hero-grid{grid-template-columns:1fr;gap:28px}
  .hero-grid h1{max-width:22ch}
  .hero-grid p{max-width:60ch}
  .svc{grid-template-columns:1fr 1fr}
  .testi-grid{grid-template-columns:1fr;gap:32px}
  .vid-frame{max-width:none}
  .close.light .wrap{grid-template-columns:1fr;gap:28px}
  .foot-top{grid-template-columns:1fr;padding-bottom:32px}
  .foot-nav{grid-template-columns:1fr 1fr}
}

@media (max-width:900px){
  .fd-nav,.fd-search{display:none}
  .fd-burger{display:flex}
  /* The CTA comes out here and the drawer carries it instead, in .fd-drawer-cta.
     It is white-space:nowrap and never shrank, so at 360px the row wanted
     146 + 12 + ~205 + 12 + 38 = 413px against 312px of content box and pushed
     the burger, last in the row, off the right edge. Nothing else in the row
     could give: .fd-logo is flex:0 0 auto and there is no overflow-x guard
     anywhere in the build. */
  .fd-cta{display:none}
  /* The CTA is gone here and the emergency link stays, number only: it is the
     one thing on the header somebody on a phone in an incident is looking for.
     At 360px the row is 146 logo + 12 + ~105 number + 12 + 38 burger = 313
     against 312px of content box, which is why the eyebrow and the rule come
     off rather than the block. */
  .fd-emerg{border-left:0;padding-left:0}
  .fd-emerg .ee{display:none}
  .fd-header{--hdr-h:63px}
  .fd-header .wrap{justify-content:space-between;gap:var(--sp-2)}
  .proof{grid-template-columns:1fr 1fr}
  .proof div:nth-child(2){border-right:0}
  .proof div:nth-child(-n+2){border-bottom:1px solid var(--line)}
  .posts{grid-template-columns:1fr}
}

@media (max-width:860px){
  .tiers,.why{grid-template-columns:1fr 1fr}
  .ticks{grid-template-columns:1fr}
  .stats{grid-template-columns:1fr}
  .tier{border-right:0;border-bottom:1px solid var(--line)}
  .callout,.callout.tel,.fine{grid-template-columns:1fr}
  .callout.tel .num{white-space:normal}
  .callout .num{padding:20px var(--sp-2) 0}
  .callout .txt{padding:var(--sp-4) var(--sp-5) var(--sp-5)}
  .split,.conviction-grid{grid-template-columns:1fr;gap:28px}
  .split p{max-width:66ch}
}

/* The alternation stops here and every row reverts to portrait left. Two
   columns at this width are narrow enough that a photograph changing sides
   reads as an inconsistency rather than as a rhythm; alternation needs room
   for the eye to register the pattern before it registers the exception. The
   nth-child placement is unset rather than overridden, so `auto` puts both
   children back in source order. */
@media (max-width:860px){
  .team{--team-col:300px;gap:var(--sp-6)}
  .team-member{gap:var(--sp-4) var(--sp-5);padding-top:var(--sp-6)}
  .team-member:nth-child(even){grid-template-columns:var(--team-col) 1fr}
  .team-member:nth-child(even) .team-photo,
  .team-member:nth-child(even) .team-body{grid-column:auto;grid-row:auto}
  .welcome{grid-template-columns:180px 1fr;gap:var(--sp-5) var(--sp-6)}
  .welcome-photo{width:180px;height:180px}
  .hero-grid.has-media{grid-template-columns:1fr}
}

@media (max-width:560px){
  /* The card's own split collapses here, and the panel lands ABOVE the
     summary because it is already first in the source. Nothing reorders.
     The divider turns from a left rule into a bottom one. */
  .scorecards article{grid-template-columns:1fr}
  .scorecards .sc-panel{grid-column:1;grid-row:1;width:auto;margin:0 0 var(--sp-4);
    padding:0 0 var(--sp-4);border-left:0;border-bottom:1px solid var(--line)}
  .scorecards .sc-body{grid-column:1;grid-row:2}
  .team-member{grid-template-columns:1fr;gap:var(--sp-4)}
  .welcome{grid-template-columns:1fr;gap:var(--sp-4)}
  .welcome-photo{width:160px;height:160px}
  .hero{padding:44px 0 var(--section-y-sm)}
  .quote{padding:var(--sp-5) var(--sp-5)}
  .tiers,.why{grid-template-columns:1fr}
  .svc{grid-template-columns:1fr}
  .proof{grid-template-columns:1fr}
  .proof div{border-right:0;border-bottom:1px solid var(--line)}
  .proof div:last-child{border-bottom:0}
  .plat-grid span{padding:var(--sp-5) var(--sp-3)}
  .foot-nav{grid-template-columns:1fr}
  .btn{width:100%;text-align:center}
}

@media (prefers-reduced-motion:reduce){.fd-header *{transition:none!important}}

/* .pager — numbered pagination under a paginated listing.        [new, §4]
   Added 16 September 2026 with the blog migration. The first component that
   exists because a page has more entries than fit on a screen rather than
   because a content pattern asked for it: /blog/ carries 135 posts across 12
   pages and the listing had nowhere to put a next link.
   Rewritten 17 September 2026 from a full-measure row reading "Newer posts /
   Page 1 of 12 / Older posts" to a centered row of numbers. Quieter, and every
   page is one click away rather than eleven.
   The numbers are a fixed 34px square so the row does not shift as the digit
   count changes at page 10, and they wrap rather than scroll on a phone. */
.pager{margin:var(--sp-6) auto 0;max-width:var(--wrap);padding:0 var(--gutter)}
.pager ol{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;
  justify-content:center;gap:var(--sp-1)}
.pager a,.pager span{display:flex;align-items:center;justify-content:center;
  min-width:34px;height:34px;padding:0 8px;border-radius:3px;font-size:14.5px;
  text-decoration:none;color:var(--body)}
.pager a:hover{background:var(--wash-blue);color:var(--navy)}
.pager span[aria-current]{background:var(--navy);color:var(--white);font-weight:700}

/* .blog-layout, .blog-aside — the blog's category sidebar and the two-column
   grid it sits in.                                            [new, §4]
   Added 17 September 2026. The sixth new component, and the second after
   .legal that is not derived from the two content patterns in
   BUILD-REFERENCE.md section 4: every listing in the build until now was full
   width and a post was one column of prose, so nothing here had a sidebar.
   Three callers, /blog/, the nine archives and all 135 posts, one grid.
   The card grid is three columns. The rail is 200px and the gap 36px rather
   than the 240 and 48 they started at, because three cards need the room: at
   the old pair they came out at 232px on the 1080px measure. The rail lost
   nothing for it, since "Database managed services" wrapped to two lines at
   either width. On the 1280px measure the site moved to on 17 September 2026
   the cards are 316px, against the 328px a full-width three-card row uses,
   so the rail now costs the grid almost nothing.
   Three columns hold to 1040px and drop to two below it, where the viewport
   rather than the measure sets the width and three cards beside the rail
   would be narrower than the card's own padding makes sensible.
   The rail reuses the .posts article idiom, white fill on a blue top rule,
   because it has to read on both bands the listing alternates between: navy
   tint on /blog/, white on the ten archives and on a post.
   The 900px reset is not redundant. .blog-layout .posts outranks the .posts
   rule in the 900px block above on specificity, whatever the source order, so
   without it the multi-column card grid would survive onto a phone. */
.blog-layout{display:grid;grid-template-columns:minmax(0,1fr) 200px;
  gap:var(--sp-7);align-items:start}
.blog-layout .posts{grid-template-columns:repeat(3,1fr)}
.blog-aside{background:var(--white);border:1px solid var(--line);
  border-top:3px solid var(--blue);padding:var(--sp-5) 22px}
.blog-aside h2{font-family:var(--text);font-size:13px;font-weight:700;
  letter-spacing:.09em;text-transform:uppercase;color:var(--muted);
  margin:0 0 var(--sp-3)}
.blog-aside ul{list-style:none;margin:0;padding:0}
.blog-aside li{border-top:1px solid var(--line-soft)}
.blog-aside li:first-child{border-top:0}
.blog-aside li a{display:flex;align-items:baseline;justify-content:space-between;
  gap:var(--sp-2);padding:9px 0;text-decoration:none;color:var(--body);
  font-size:15px;line-height:1.35}
.blog-aside li a:hover{color:var(--blue)}
.blog-aside li a[aria-current]{color:var(--navy);font-weight:700}
.blog-aside b{font-weight:400;font-size:13px;color:var(--muted)}
.blog-aside-all{margin:var(--sp-3) 0 0;max-width:none;font-size:14px;
  font-weight:700;border-top:1px solid var(--line);padding-top:var(--sp-3)}
.blog-aside-all a{text-decoration:none;color:var(--navy);
  border-bottom:2px solid var(--blue);padding-bottom:2px}

@media (max-width:1040px){
  .blog-layout .posts{grid-template-columns:repeat(2,1fr)}
}

@media (max-width:900px){
  .blog-layout{grid-template-columns:1fr;gap:var(--sp-7)}
  .blog-layout .posts{grid-template-columns:1fr}
}


/* ==========================================================================
   Filmstrip                                                       [new, §4]

   NEW COMPONENT, added 21 September 2026 for /about-us/careers/ against
   docs/superpowers/specs/2026-09-21-careers-page-redesign-design.md.

   A full-bleed band of team photographs, edge to edge with no .wrap. Nothing
   in the build was full-bleed before: every band on the site is a section
   with a 1280px measure inside it, and this one is deliberately not, because
   a row of photographs that stops 24px short of the viewport reads as a
   widget rather than as a band.

   IT IS A NATIVE SCROLLER AND THAT IS THE WHOLE BAND WITH SCRIPT BLOCKED.
   overflow-x:auto plus scroll-snap gives drag, trackpad, shift-wheel and
   keyboard, and all nineteen photographs are in the HTML. src/js/filmstrip.js
   adds the drift and adds the class below that turns snapping off, because
   snapping and a continuous drift fight: every pause would jerk the nearest
   photograph to center under the cursor.

   FIXED HEIGHT, NATIVE ASPECT. height on the image and width:auto, so the
   nine portrait shots stay portrait and nothing is cropped through somebody's
   head. That is the reason this is a filmstrip and not a one-at-a-time
   slideshow: one frame shape would have had to crop two thirds of the set.

   THE BAND IS WHITE WITH A BOTTOM HAIRLINE, ON THE CLIENT'S CALL OF 21
   SEPTEMBER 2026: put the images below the hero. It was navy when it landed
   that morning, on the argument that it read as the lower half of the
   masthead, and navy under navy is exactly why it did not read as a separate
   band at all. This is .proofband's treatment, which is the build's existing
   answer to a tight band sitting directly under the hero, and it puts a hard
   edge between the navy and the photographs. The first content block below
   is still white, which is the sitewide rule.

   THE SCROLLBAR IS HIDDEN, same call, same day. Note what it costs, because
   it is not nothing: with script blocked the bar was the only visible thing
   saying the band scrolls. The band still scrolls by drag, trackpad,
   shift-wheel and keyboard, and with script running it drifts, which is its
   own affordance. Do not remove the overflow to "simplify" this: hiding the
   bar and removing the scrolling are one character apart in intent and
   nothing in the build would catch it.
   ========================================================================== */
.strip{--strip-h:300px;padding:0;background:var(--white);
  border-bottom:1px solid var(--line)}
.strip-view{overflow-x:auto;overflow-y:hidden;scroll-snap-type:x proximity;
  scrollbar-width:none;-ms-overflow-style:none}
.strip-view::-webkit-scrollbar{display:none}
.strip-view.strip-js{scroll-snap-type:none}
.strip-track{list-style:none;margin:0;padding:0;display:flex;gap:4px}
/* height on the item as well as on the image, so one photograph failing to
   load cannot collapse the band to a hairline. A broken <img> keeps its
   width from the width/height attributes in modern browsers; this is what
   holds the height in the ones that do not. */
.strip-track li{flex:0 0 auto;height:var(--strip-h);scroll-snap-align:center}
.strip-track img{display:block;height:var(--strip-h);width:auto}


/* ==========================================================================
   Railed region                                                    [new, §4]

   NEW COMPONENT, added 21 September 2026 for /about-us/careers/. The third
   block in the build not derived from the two content patterns in
   BUILD-REFERENCE.md section 4, after .legal and .blog-layout.

   Two columns for the whole body of the page: a sticky openings rail on the
   left and every prose block on the right. .blog-layout is the nearest thing
   and is not this. That rail is a category list beside one grid, it does not
   stick, and its sections are the page's bands. Here the outer section IS
   the band and four blocks sit inside one column of it.

   .railed CARRIES NO PADDING. Every block in the right column is a <section>
   with its own var(--section-y), so the outer section adding its own would
   double the gap at the top and the bottom. The rail's margin-top does the
   same job on the left, which is what lines its top edge up with the first
   paragraph beside it.

   THE HAIRLINE IS THE BAND RHYTHM ON THIS PAGE. The header of
   layouts/careers.njk carries the argument in full: four alternating fills
   inside a grid column paint two thirds of the viewport each and read as a
   rendering fault. So the blocks are separated by a rule and by spacing, and
   nothing on this page alternates.

   THE RAIL IS ON THE RIGHT AND THE MARKUP DOES NOT SAY SO. Changed on the
   client's call of 21 September 2026, from the left where the design spec
   put it. It is placed there with explicit grid-column, exactly as .team
   alternates its portraits, and for the same reason: the rail is FIRST in
   the source and stays first, because at 960px the grid collapses to one
   column and the openings are what the candidate came for, so they belong
   above the prose on a phone. Reordering the source would have painted the
   same picture and handed a screen reader and a crawler the openings after
   1,700 words of culture copy. Never reach for `direction: rtl` or
   `order` here. Both children are pinned to row 1, because a grid item given
   only a column is otherwise pushed to a new row by the sibling preceding it
   in the source.

   --rail-col IS THE DIAL, the way --team-col is on the leadership grid.

   --rail-top CLEARS THE STICKY HEADER and is measured rather than guessed.
   .fd-header .wrap is var(--sp-2) top and bottom, 24px, its tallest child is
   .fd-cta at 11px padding either side of a 22.4px line box, 44.4px, and the
   header has a 1px bottom border: 69.4px, rounded to 70. The same measurement
   is --hdr-h on .fd-header. It held at every width until 25 September 2026,
   when the CTA was hidden below 900px and the header dropped to 63px; that
   does not reach this value, because the rail stops sticking at 960px. One spacing step
   clear of it is var(--sp-5). The same value is scroll-margin-top, because
   the hero's "See open roles" button lands on this element and would
   otherwise land behind the navigation.

   THE RAIL IS SHORT AND THE COLUMN BELOW IT IS EMPTY FOR MOST OF THE SCROLL.
   That is the accepted cost of the decision to put the openings in it and
   nothing else: no jump links, no speculative-application card. It is
   mitigated by keeping the column narrow enough that the space reads as
   margin. If the openings ever drop to zero, cb.ticks renders nothing, the
   rail is an empty card and the page is a single column with no error.
   ========================================================================== */
.railed{--rail-col:300px;--rail-top:calc(70px + var(--sp-5));padding:0}
.railed>.wrap{display:grid;grid-template-columns:minmax(0,1fr) var(--rail-col);
  gap:var(--sp-9);align-items:start}
.rail{grid-column:2;grid-row:1}
.railed-main{grid-column:1;grid-row:1;min-width:0}
.railed-main>section+section{border-top:1px solid var(--line)}

/* The nested wrap. Every macro in the right column emits its own
   <section><div class="wrap">, and inside a grid column that must not
   re-apply the 1280px cap and the 24px gutter. Two declarations, and the
   rail needs them too because cb.ticks emits the same pair. */
.railed-main .wrap,
.rail .wrap{max-width:none;padding:0}

.rail{position:sticky;top:var(--rail-top);scroll-margin-top:var(--rail-top);
  margin-top:var(--section-y);background:var(--white);border:1px solid var(--line);
  border-top:3px solid var(--blue);padding:var(--sp-5) 22px}
.rail section{padding:0;background:transparent}
/* The card's own 3px rule already separates the heading, so the hairline
   base.css puts under every h2 is one line too many in 300px. */
.rail h2{font-size:1.15rem;margin:0 0 var(--sp-3)}
.rail h2::after{display:none}
.rail .mx-intro{font-size:14px;line-height:1.55;color:var(--muted);
  margin:0 0 var(--sp-5);max-width:none}
.rail .ticks{grid-template-columns:1fr;gap:var(--sp-5)}
.rail .ticks li{font-size:14.5px}


/* ==========================================================================
   Interspersed photograph                                          [new, §4]

   NEW COMPONENT, added 21 September 2026 for /about-us/careers/. The fourth
   line of the brief: outside the carousel, intersperse the images through
   the page as part of the text.

   One photograph renders at its native ratio and is not cropped. A pair is
   cropped to 4:3, because two photographs side by side at two different
   ratios leave one of them floating above a gap, and the pair is chosen at
   the page rather than fixed here.
   ========================================================================== */
.photo-inset{margin:0;display:grid;grid-template-columns:1fr;gap:var(--sp-4)}
.photo-inset.two{grid-template-columns:1fr 1fr}
.photo-inset img{display:block;width:100%;height:auto;border:1px solid var(--line);
  background:var(--navy-tint)}
.photo-inset.two img{aspect-ratio:4/3;object-fit:cover}
.photo-inset figcaption{grid-column:1/-1;font-size:14px;line-height:1.5;
  color:var(--muted)}


/* THE ONE PLACE THE DESIGN SPEC WAS WRONG, and it is worth stating rather
   than quietly fixing. Section 4 argued that cb.grid and cb.cards are both
   repeat(3,1fr) and that "in a right column of roughly 900px that is about
   290px per card ... neither needs a new breakpoint". The right column is
   900px at about 1400px of viewport. Between the rail collapsing at 960 and
   roughly 1150, the column is the viewport minus 300px of rail and 56px of
   gap, so at 1000px it is 596px and a three-across card is 198px wide with
   150px of text inside it, which is eighteen characters a line. The sitewide
   960px breakpoint never rescues it, because it fires below the band.

   Two across for that band only. min-width is what keeps this off the
   narrower widths: .railed-main .svc outranks the plain .svc rules that
   already handle 960 and 560, so without the lower bound this would pin a
   phone to two columns. */
@media (min-width:961px) and (max-width:1150px){
  .railed-main .why,
  .railed-main .svc{grid-template-columns:1fr 1fr}
}

@media (max-width:960px){
  /* One column, and the rail stops sticking. It renders above the prose,
     directly under the filmstrip, which is the right reading order on a
     phone: the openings are what the candidate came for. The source order
     already puts it first, so the placement is unset rather than overridden
     and `auto` puts both children back where the markup has them. Same
     mechanism .team uses to stop alternating at 860px. */
  .railed>.wrap{grid-template-columns:1fr;gap:0}
  .rail,
  .railed-main{grid-column:auto;grid-row:auto}
  .rail{position:static;margin-bottom:0}
}

@media (max-width:860px){
  .strip{--strip-h:230px}
}

@media (max-width:560px){
  .strip{--strip-h:180px}
  .photo-inset.two{grid-template-columns:1fr}
  .photo-inset.two img{aspect-ratio:auto}
}


/* ==========================================================================
   HubSpot form                                                    [new, §4]
   Ported from the live /contact-sales/ page on 21 September 2026, where the
   same embed (portal 7815435, form b53f3304) is styled by a stylesheet held
   in an Elementor HTML widget. Without it the embed renders with HubSpot's
   own defaults: a 500px fieldset cap, 95%-width inputs, and the two checkbox
   groups as a plain bulleted list.

   SCOPED TO .hsform, a wrapper the page puts around the embed target - NOT
   to HubSpot's .hbspt-form, which is only created when forms.create() runs
   WITHOUT a `target`. Both pages pass a target, so .hbspt-form never exists
   and scoping to it would silently kill every rule here.

   TWO PAGES LOAD THIS, and they need different amounts of it.
   /contact-us/ gets all of it, including the step cards that
   src/js/hs-form.js builds. /request-assessment/ gets the wrapper and
   nothing else, because its form (88d57e98) has different fields and the
   live site never styled it. Sections 5, 6 and 9 are keyed to contact-form
   field names through the stable hs_<fieldname> classes HubSpot emits, so on
   the assessment form they simply never match. That is why one sheet serves
   both and there is no second component.

   WHAT WAS RE-POINTED IN THE PORT. The live sheet declares its own palette
   as bare hex on the wrapper. Every one of those is a build token here, so
   the form cannot drift from the rest of the site:
     #232b5f -> --navy        exact match, no visual change
     #1b8ccc -> --blue        this is the Elementor blue defect. The form
                              now renders #1B8DCD like everything else
     #dde1e8 -> --line        }
     #b9c2d0 -> --off-mark    } near matches. The form reads very slightly
     #eaf4fb -> --wash-blue   } cooler and lighter than the live one
     #6b7280 -> --muted       placeholder and help text. Darker than the
                              live value, so contrast improves
     "Static"/"Asap SemiExpanded" -> --disp
     "Open Sans"                  -> --text
   Two values had no token and are now in tokens.css: --alert and
   --blue-hover. Read the notes there before reusing either.

   WHAT IS DELIBERATELY NOT A TOKEN. The three tints below are an existing
   token at low alpha, which CSS cannot express as a token reference without
   color-mix, and each is used only by this component. The card geometry is
   local because the submit row and the returning-visitor notice reproduce
   the card's inset through calc() and it has to change in one place.

   SPACING IS IN PIXELS HERE AND THAT IS ON PURPOSE. The values below are
   HubSpot's internal form rhythm, not the page's section rhythm, and 16, 20
   and 32 are not on the --sp-* scale. Rounding them onto it would change the
   geometry of a form that is already correct, and three of them are operands
   inside calc(). Do not "fix" them.
   ========================================================================== */
.hsform {
  /* an existing token at low alpha, see the note above */
  --tint-blue:rgba(27,141,205,.08);   /* --blue  at 8%, focus ring + checked tile */
  --tint-navy:rgba(35,43,95,.08);     /* --navy  at 8%, tile hover shadow */
  --tint-alert:rgba(209,55,63,.12);   /* --alert at 12%, invalid focus ring */

  /* card geometry, read by the submit row and the returning-visitor notice */
  --card-pad-x:32px;
  --card-bd:1px;                      /* top, right, bottom */
  --card-bd-l:3px;                    /* accent edge */

  --radius:3px;                       /* matches .btn, not the live sheet's 4px */
}

/* Body font for the whole widget. Form controls do not inherit font by
   default, which is why the input rules below say font-family: inherit. */
.hsform,
.hsform form.hs-form {
  font-family: var(--text);
}

/* ---- 1. UNLOCK THE WIDTH -----------------------------------
   HubSpot injects `.hs-form-<id> fieldset { max-width:500px }`
   into <head> at runtime. The <id> carries a random per-load
   instance suffix, so it cannot be targeted directly - we beat
   it on specificity + !important instead. This single rule is
   what pins the whole form into a 500px column. */
.hsform form.hs-form fieldset,
.hsform form.hs-form fieldset.form-columns-1,
.hsform form.hs-form fieldset.form-columns-2,
.hsform form.hs-form fieldset.form-columns-3 {
  max-width: none !important;
  width: 100%;
}

/* HubSpot also sets .hs-input{width:95%} and .input{margin-right:8px},
   which is why every text field stops short of the column edge. */
.hsform form.hs-form fieldset .input {
  margin-right: 0 !important;
}
.hsform form.hs-form fieldset .hs-input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]) {
  width: 100% !important;
  max-width: 100%;
}

/* ---- 2. FIELD RHYTHM + 2-UP ROWS ---------------------------
   Replaces HubSpot's float columns with grid so gutters are even.
   Applies to any row set to 2 columns in the HubSpot form editor. */
.hsform form.hs-form fieldset { margin: 0 0 20px; }

.hsform form.hs-form fieldset.form-columns-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}
.hsform form.hs-form fieldset.form-columns-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
.hsform form.hs-form fieldset.form-columns-2 .hs-form-field,
.hsform form.hs-form fieldset.form-columns-3 .hs-form-field {
  width: 100% !important;
  float: none !important;
  padding: 0 !important;
}
@media (max-width: 620px) {
  .hsform form.hs-form fieldset.form-columns-2,
  .hsform form.hs-form fieldset.form-columns-3 {
    grid-template-columns: 1fr;
  }
}

/* ---- 3. LABELS + HELP TEXT --------------------------------- */
.hsform .hs-form-field > label {
  display: block;
  font-weight: 600;
  color: var(--navy);
  font-size: 14px;
  margin-bottom: 8px;
}
.hsform .hs-form-required { color: var(--alert); margin-left: 2px; }
.hsform .hs-field-desc {
  color: var(--muted);
  font-size: 13px;
  margin: -2px 0 10px;
}

/* ---- 4. TEXT / SELECT / TEXTAREA ---------------------------- */
.hsform input.hs-input[type="text"],
.hsform input.hs-input[type="email"],
.hsform input.hs-input[type="tel"],
.hsform input.hs-input[type="number"],
.hsform textarea.hs-input,
.hsform select.hs-input {
  box-sizing: border-box;
  font-family: inherit;
  font-size: 14px;
  line-height: 1.4;
  color: var(--body);
  background-color: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px 14px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.hsform textarea.hs-input { resize: vertical; min-height: 110px; }

/* HubSpot forces textarea padding via `.hs-custom-style fieldset textarea` */
.hsform form.hs-form fieldset textarea.hs-input { padding: 12px 14px; }

.hsform select.hs-input {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath fill='%235B6180' d='M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z'%3E%3C/path%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 11px 11px;
  cursor: pointer;
}
.hsform select.hs-input.is-placeholder { color: var(--muted); }

/* Specificity note: the base field rule above is (0,3,1) because of the
   [type=...] selectors. Every state rule below must therefore be at least
   (0,4,1) or it silently loses and the border never changes color. */
.hsform .hs-form-field input.hs-input:focus,
.hsform .hs-form-field textarea.hs-input:focus,
.hsform .hs-form-field select.hs-input:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px var(--tint-blue);
}
.hsform .hs-input::placeholder { color: var(--muted); }

/* ---- 5. ICON TILE GRID (Current Database Problem) -----------
   Scoped to `.multi-container` (the option list) so the grid can
   never land on HubSpot's `ul.hs-error-msgs.inputs-list`, which
   would otherwise render validation errors as 2-col tiles. */
.hsform .hs_current_database_problem .inputs-list.multi-container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
}
@media (max-width: 620px) {
  .hsform .hs_current_database_problem .inputs-list.multi-container {
    grid-template-columns: 1fr;
  }
}

.hsform .hs_current_database_problem .hs-form-checkbox {
  margin: 0;
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--white);
  transition: border-color .15s ease, background .15s ease, box-shadow .15s ease;
}
.hsform .hs_current_database_problem .hs-form-checkbox:hover {
  border-color: var(--off-mark);
  box-shadow: 0 1px 4px var(--tint-navy);
}
.hsform .hs_current_database_problem .hs-form-checkbox:has(input:checked) {
  border-color: var(--blue);
  background: var(--tint-blue);
}

/* rounded icon plate - drawn on the <li> so the <label> keeps both
   of its pseudo-elements free (glyph + check badge) */
.hsform .hs_current_database_problem .hs-form-checkbox::before {
  content: "";
  position: absolute;
  left: 16px; top: 50%;
  transform: translateY(-50%);
  width: 34px; height: 34px;
  border-radius: 6px;
  background: var(--wash-blue);
  transition: background .15s ease;
  pointer-events: none;
}
.hsform .hs_current_database_problem .hs-form-checkbox:has(input:checked)::before {
  background: var(--blue);
}

.hsform .hs_current_database_problem .hs-form-checkbox-display {
  display: flex;
  align-items: center;
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 16px 52px 16px 62px;
  cursor: pointer;
  font-size: 14px;
  color: var(--body);
  box-sizing: border-box;
}
.hsform .hs_current_database_problem .hs-form-checkbox-display input.hs-input {
  position: absolute; opacity: 0; width: 0; height: 0; margin: 0; pointer-events: none;
}
.hsform .hs_current_database_problem .hs-form-checkbox-display > span {
  line-height: 1.35;
}

/* glyph - painted with mask-image so one background-color flips it
   blue -> white on check, with no duplicated icon URLs */
.hsform .hs_current_database_problem .hs-form-checkbox-display::before {
  content: "";
  position: absolute;
  left: 25px; top: 50%;
  transform: translateY(-50%);
  width: 16px; height: 16px;
  background-color: var(--blue);
  transition: background-color .15s ease;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
  pointer-events: none;
}
.hsform .hs_current_database_problem .hs-form-checkbox-display:has(input:checked)::before {
  background-color: var(--white);
}

/* check badge */
.hsform .hs_current_database_problem .hs-form-checkbox-display::after {
  content: "";
  position: absolute;
  right: 16px; top: 50%;
  transform: translateY(-50%);
  width: 20px; height: 20px;
  border-radius: 50%;
  border: 1.5px solid var(--off-mark);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 10px 10px;
  transition: background-color .15s ease, border-color .15s ease;
  pointer-events: none;
}
.hsform .hs_current_database_problem .hs-form-checkbox-display:has(input:checked)::after {
  background-color: var(--blue);
  border-color: var(--blue);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath fill='%23ffffff' d='M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z'%3E%3C/path%3E%3C/svg%3E");
}

/* per-option glyphs, keyed by the exact HubSpot option value */
.hsform .hs_current_database_problem .hs-form-checkbox-display:has(input[value="Moving or migrating data"])::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M32 96l320 0 0-64c0-12.9 7.8-24.6 19.8-29.6s25.7-2.2 34.9 6.9l96 96c6 6 9.4 14.1 9.4 22.6s-3.4 16.6-9.4 22.6l-96 96c-9.2 9.2-22.9 11.9-34.9 6.9s-19.8-16.6-19.8-29.6l0-64L32 160c-17.7 0-32-14.3-32-32s14.3-32 32-32zM480 352c17.7 0 32 14.3 32 32s-14.3 32-32 32l-320 0 0 64c0 12.9-7.8 24.6-19.8 29.6s-25.7 2.2-34.9-6.9l-96-96c-6-6-9.4-14.1-9.4-22.6s3.4-16.6 9.4-22.6l96-96c9.2-9.2 22.9-11.9 34.9-6.9s19.8 16.6 19.8 29.6l0 64 320 0z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M32 96l320 0 0-64c0-12.9 7.8-24.6 19.8-29.6s25.7-2.2 34.9 6.9l96 96c6 6 9.4 14.1 9.4 22.6s-3.4 16.6-9.4 22.6l-96 96c-9.2 9.2-22.9 11.9-34.9 6.9s-19.8-16.6-19.8-29.6l0-64L32 160c-17.7 0-32-14.3-32-32s14.3-32 32-32zM480 352c17.7 0 32 14.3 32 32s-14.3 32-32 32l-320 0 0 64c0 12.9-7.8 24.6-19.8 29.6s-25.7 2.2-34.9-6.9l-96-96c-6-6-9.4-14.1-9.4-22.6s3.4-16.6 9.4-22.6l96-96c9.2-9.2 22.9-11.9 34.9-6.9s19.8 16.6 19.8 29.6l0 64 320 0z'/%3E%3C/svg%3E");
}
.hsform .hs_current_database_problem .hs-form-checkbox-display:has(input[value="Need someone to manage it"])::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 512'%3E%3Cpath d='M224 0a128 128 0 1 1 0 256A128 128 0 1 1 224 0zM178.3 304l91.4 0c11.8 0 23.4 1.2 34.5 3.3c-2.1 18.5 7.4 35.6 21.8 44.8c-16.6 10.6-26.7 31.6-20 53.3c4 12.9 9.4 25.5 16.4 37.6s15.2 23.1 24.4 33c15.7 16.9 39.6 18.4 57.2 8.7l0 .9c0 9.2 2.7 18.5 7.9 26.3L29.7 512C13.3 512 0 498.7 0 482.3C0 383.8 79.8 304 178.3 304zM436 218.2c0-7 4.5-13.3 11.3-14.8c10.5-2.4 21.5-3.7 32.7-3.7s22.2 1.3 32.7 3.7c6.8 1.5 11.3 7.8 11.3 14.8l0 30.6c7.9 3.4 15.4 7.7 22.3 12.8l24.9-14.3c6.1-3.5 13.7-2.7 18.5 2.4c7.6 8.1 14.3 17.2 20.1 27.2s10.3 20.4 13.5 31c2.1 6.7-1.1 13.7-7.2 17.2l-25 14.4c.4 4 .7 8.1 .7 12.3s-.2 8.2-.7 12.3l25 14.4c6.1 3.5 9.2 10.5 7.2 17.2c-3.3 10.6-7.8 21-13.5 31s-12.5 19.1-20.1 27.2c-4.8 5.1-12.5 5.9-18.5 2.4l-24.9-14.3c-6.9 5.1-14.3 9.4-22.3 12.8l0 30.6c0 7-4.5 13.3-11.3 14.8c-10.5 2.4-21.5 3.7-32.7 3.7s-22.2-1.3-32.7-3.7c-6.8-1.5-11.3-7.8-11.3-14.8l0-30.5c-8-3.4-15.6-7.7-22.5-12.9l-24.7 14.3c-6.1 3.5-13.7 2.7-18.5-2.4c-7.6-8.1-14.3-17.2-20.1-27.2s-10.3-20.4-13.5-31c-2.1-6.7 1.1-13.7 7.2-17.2l24.8-14.3c-.4-4.1-.7-8.2-.7-12.4s.2-8.3 .7-12.4L343.8 325c-6.1-3.5-9.2-10.5-7.2-17.2c3.3-10.6 7.7-21 13.5-31s12.5-19.1 20.1-27.2c4.8-5.1 12.4-5.9 18.5-2.4l24.8 14.3c6.9-5.1 14.5-9.4 22.5-12.9l0-30.5zm92.1 133.5a48.1 48.1 0 1 0 -96.1 0 48.1 48.1 0 1 0 96.1 0z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 512'%3E%3Cpath d='M224 0a128 128 0 1 1 0 256A128 128 0 1 1 224 0zM178.3 304l91.4 0c11.8 0 23.4 1.2 34.5 3.3c-2.1 18.5 7.4 35.6 21.8 44.8c-16.6 10.6-26.7 31.6-20 53.3c4 12.9 9.4 25.5 16.4 37.6s15.2 23.1 24.4 33c15.7 16.9 39.6 18.4 57.2 8.7l0 .9c0 9.2 2.7 18.5 7.9 26.3L29.7 512C13.3 512 0 498.7 0 482.3C0 383.8 79.8 304 178.3 304zM436 218.2c0-7 4.5-13.3 11.3-14.8c10.5-2.4 21.5-3.7 32.7-3.7s22.2 1.3 32.7 3.7c6.8 1.5 11.3 7.8 11.3 14.8l0 30.6c7.9 3.4 15.4 7.7 22.3 12.8l24.9-14.3c6.1-3.5 13.7-2.7 18.5 2.4c7.6 8.1 14.3 17.2 20.1 27.2s10.3 20.4 13.5 31c2.1 6.7-1.1 13.7-7.2 17.2l-25 14.4c.4 4 .7 8.1 .7 12.3s-.2 8.2-.7 12.3l25 14.4c6.1 3.5 9.2 10.5 7.2 17.2c-3.3 10.6-7.8 21-13.5 31s-12.5 19.1-20.1 27.2c-4.8 5.1-12.5 5.9-18.5 2.4l-24.9-14.3c-6.9 5.1-14.3 9.4-22.3 12.8l0 30.6c0 7-4.5 13.3-11.3 14.8c-10.5 2.4-21.5 3.7-32.7 3.7s-22.2-1.3-32.7-3.7c-6.8-1.5-11.3-7.8-11.3-14.8l0-30.5c-8-3.4-15.6-7.7-22.5-12.9l-24.7 14.3c-6.1 3.5-13.7 2.7-18.5-2.4c-7.6-8.1-14.3-17.2-20.1-27.2s-10.3-20.4-13.5-31c-2.1-6.7 1.1-13.7 7.2-17.2l24.8-14.3c-.4-4.1-.7-8.2-.7-12.4s.2-8.3 .7-12.4L343.8 325c-6.1-3.5-9.2-10.5-7.2-17.2c3.3-10.6 7.7-21 13.5-31s12.5-19.1 20.1-27.2c4.8-5.1 12.4-5.9 18.5-2.4l24.8 14.3c6.9-5.1 14.5-9.4 22.5-12.9l0-30.5zm92.1 133.5a48.1 48.1 0 1 0 -96.1 0 48.1 48.1 0 1 0 96.1 0z'/%3E%3C/svg%3E");
}
.hsform .hs_current_database_problem .hs-form-checkbox-display:has(input[value="Security, compliance, an audit"])::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M144 144l0 48 160 0 0-48c0-44.2-35.8-80-80-80s-80 35.8-80 80zM80 192l0-48C80 64.5 144.5 0 224 0s144 64.5 144 144l0 48 16 0c35.3 0 64 28.7 64 64l0 192c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 256c0-35.3 28.7-64 64-64l16 0z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M144 144l0 48 160 0 0-48c0-44.2-35.8-80-80-80s-80 35.8-80 80zM80 192l0-48C80 64.5 144.5 0 224 0s144 64.5 144 144l0 48 16 0c35.3 0 64 28.7 64 64l0 192c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 256c0-35.3 28.7-64 64-64l16 0z'/%3E%3C/svg%3E");
}
.hsform .hs_current_database_problem .hs-form-checkbox-display:has(input[value="Starting a new project"])::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M156.6 384.9L125.7 354c-8.5-8.5-11.5-20.8-7.7-32.2c3-8.9 7-20.5 11.8-33.8L24 288c-8.6 0-16.6-4.6-20.9-12.1s-4.2-16.7 .2-24.1l52.5-88.5c13-21.9 36.5-35.3 61.9-35.3l82.3 0c2.4-4 4.8-7.7 7.2-11.3C289.1-4.1 411.1-8.1 483.9 5.3c11.6 2.1 20.6 11.2 22.8 22.8c13.4 72.9 9.3 194.8-111.4 276.7c-3.5 2.4-7.3 4.8-11.3 7.2l0 82.3c0 25.4-13.4 49-35.3 61.9l-88.5 52.5c-7.4 4.4-16.6 4.5-24.1 .2s-12.1-12.2-12.1-20.9l0-107.2c-14.1 4.9-26.4 8.9-35.7 11.9c-11.2 3.6-23.4 .5-31.8-7.8zM384 168a40 40 0 1 0 0-80 40 40 0 1 0 0 80z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M156.6 384.9L125.7 354c-8.5-8.5-11.5-20.8-7.7-32.2c3-8.9 7-20.5 11.8-33.8L24 288c-8.6 0-16.6-4.6-20.9-12.1s-4.2-16.7 .2-24.1l52.5-88.5c13-21.9 36.5-35.3 61.9-35.3l82.3 0c2.4-4 4.8-7.7 7.2-11.3C289.1-4.1 411.1-8.1 483.9 5.3c11.6 2.1 20.6 11.2 22.8 22.8c13.4 72.9 9.3 194.8-111.4 276.7c-3.5 2.4-7.3 4.8-11.3 7.2l0 82.3c0 25.4-13.4 49-35.3 61.9l-88.5 52.5c-7.4 4.4-16.6 4.5-24.1 .2s-12.1-12.2-12.1-20.9l0-107.2c-14.1 4.9-26.4 8.9-35.7 11.9c-11.2 3.6-23.4 .5-31.8-7.8zM384 168a40 40 0 1 0 0-80 40 40 0 1 0 0 80z'/%3E%3C/svg%3E");
}
.hsform .hs_current_database_problem .hs-form-checkbox-display:has(input[value="Performance is slow"])::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zM288 96a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zM256 416c35.3 0 64-28.7 64-64c0-17.4-6.9-33.1-18.1-44.6L366 161.7c5.3-12.1-.2-26.3-12.3-31.6s-26.3 .2-31.6 12.3L257.9 288c-.6 0-1.3 0-1.9 0c-35.3 0-64 28.7-64 64s28.7 64 64 64zM176 144a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zM96 288a32 32 0 1 0 0-64 32 32 0 1 0 0 64zm352-32a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zM288 96a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zM256 416c35.3 0 64-28.7 64-64c0-17.4-6.9-33.1-18.1-44.6L366 161.7c5.3-12.1-.2-26.3-12.3-31.6s-26.3 .2-31.6 12.3L257.9 288c-.6 0-1.3 0-1.9 0c-35.3 0-64 28.7-64 64s28.7 64 64 64zM176 144a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zM96 288a32 32 0 1 0 0-64 32 32 0 1 0 0 64zm352-32a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z'/%3E%3C/svg%3E");
}
.hsform .hs_current_database_problem .hs-form-checkbox-display:has(input[value="Uptime & disaster recovery"])::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 0c4.6 0 9.2 1 13.4 2.9L457.7 82.8c22 9.3 38.4 31 38.3 57.2c-.5 99.2-41.3 280.7-213.6 363.2c-16.7 8-36.1 8-52.8 0C57.3 420.7 16.5 239.2 16 140c-.1-26.2 16.3-47.9 38.3-57.2L242.7 2.9C246.8 1 251.4 0 256 0zm0 66.8l0 378.1C394 378 431.1 230.1 432 141.4L256 66.8s0 0 0 0z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 0c4.6 0 9.2 1 13.4 2.9L457.7 82.8c22 9.3 38.4 31 38.3 57.2c-.5 99.2-41.3 280.7-213.6 363.2c-16.7 8-36.1 8-52.8 0C57.3 420.7 16.5 239.2 16 140c-.1-26.2 16.3-47.9 38.3-57.2L242.7 2.9C246.8 1 251.4 0 256 0zm0 66.8l0 378.1C394 378 431.1 230.1 432 141.4L256 66.8s0 0 0 0z'/%3E%3C/svg%3E");
}
.hsform .hs_current_database_problem .hs-form-checkbox-display:has(input[value="Upgrade or end-of-life version"])::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160 352 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l111.5 0c0 0 0 0 0 0l.4 0c17.7 0 32-14.3 32-32l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 35.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1L16 432c0 17.7 14.3 32 32 32s32-14.3 32-32l0-35.1 17.6 17.5c0 0 0 0 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.8c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352l34.4 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L48.4 288c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160 352 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l111.5 0c0 0 0 0 0 0l.4 0c17.7 0 32-14.3 32-32l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 35.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1L16 432c0 17.7 14.3 32 32 32s32-14.3 32-32l0-35.1 17.6 17.5c0 0 0 0 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.8c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352l34.4 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L48.4 288c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z'/%3E%3C/svg%3E");
}
.hsform .hs_current_database_problem .hs-form-checkbox-display:has(input[value="Not sure yet, let's talk"])::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM169.8 165.3c7.9-22.3 29.1-37.3 52.8-37.3l58.3 0c34.9 0 63.1 28.3 63.1 63.1c0 22.6-12.1 43.5-31.7 54.8L280 264.4c-.2 13-10.9 23.6-24 23.6c-13.3 0-24-10.7-24-24l0-13.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1c0-8.4-6.8-15.1-15.1-15.1l-58.3 0c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM169.8 165.3c7.9-22.3 29.1-37.3 52.8-37.3l58.3 0c34.9 0 63.1 28.3 63.1 63.1c0 22.6-12.1 43.5-31.7 54.8L280 264.4c-.2 13-10.9 23.6-24 23.6c-13.3 0-24-10.7-24-24l0-13.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1c0-8.4-6.8-15.1-15.1-15.1l-58.3 0c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z'/%3E%3C/svg%3E");
}

/* ---- 6. PILL ROW (Which databases are you running) ---------- */
.hsform .hs_which_databases_are_you_running .inputs-list.multi-container {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
}
.hsform .hs_which_databases_are_you_running .hs-form-checkbox {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--white);
  transition: border-color .15s ease, background .15s ease;
}
.hsform .hs_which_databases_are_you_running .hs-form-checkbox:hover {
  border-color: var(--off-mark);
}
.hsform .hs_which_databases_are_you_running .hs-form-checkbox-display {
  display: flex;
  align-items: center;
  position: relative;
  margin: 0;
  padding: 8px 18px;
  font-size: 13px;
  cursor: pointer;
  color: var(--body);
}
.hsform .hs_which_databases_are_you_running .hs-form-checkbox-display input.hs-input {
  position: absolute; opacity: 0; width: 0; height: 0; margin: 0; pointer-events: none;
}
.hsform .hs_which_databases_are_you_running .hs-form-checkbox:has(input:checked) {
  border-color: var(--blue);
  background: var(--tint-blue);
}
.hsform .hs_which_databases_are_you_running .hs-form-checkbox:has(input:checked) .hs-form-checkbox-display {
  color: var(--blue);
  font-weight: 600;
}

/* ---- 7. SUBMIT BUTTON --------------------------------------- */
.hsform .hs_submit .actions { padding: 0; margin: 0; }
.hsform input.hs-button.primary {
  -webkit-appearance: none;
  appearance: none;
  background-color: var(--blue);
  color: var(--white);
  font-family: inherit;
  font-weight: 700;
  font-size: 15px;
  line-height: 1;
  border: none;
  border-radius: var(--radius);
  padding: 15px 30px;
  width: auto;
  cursor: pointer;
  transition: background-color .15s ease;
}
.hsform input.hs-button.primary:hover,
.hsform input.hs-button.primary:focus {
  background-color: var(--blue-hover);
}
.hsform input.hs-button.primary:focus {
  outline: none;
  box-shadow: 0 0 0 3px var(--tint-blue);
}

/* ---- 8. VALIDATION ------------------------------------------
   HubSpot renders errors as ul.no-list.hs-error-msgs.inputs-list.
   Reset it so it can never inherit the tile grid / pill flex. */
.hsform .hs-error-msgs {
  display: block !important;
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
}
.hsform .hs-error-msgs li {
  margin: 0;
  border: none;
  background: none;
  border-radius: 0;
}
.hsform .hs-error-msgs li::before,
.hsform .hs-error-msgs li::after { content: none; }
.hsform .hs-error-msgs label,
.hsform .hs-error-msg {
  display: block;
  color: var(--alert);
  font-size: 12px;
  font-weight: 400;
  margin: 0;
  padding: 0;
}
.hsform .hs-form-field input.hs-input.invalid,
.hsform .hs-form-field input.hs-input.error,
.hsform .hs-form-field textarea.hs-input.invalid,
.hsform .hs-form-field textarea.hs-input.error,
.hsform .hs-form-field select.hs-input.invalid,
.hsform .hs-form-field select.hs-input.error {
  border-color: var(--alert);
}
.hsform .hs-form-field input.hs-input.invalid:focus,
.hsform .hs-form-field textarea.hs-input.invalid:focus,
.hsform .hs-form-field select.hs-input.invalid:focus {
  border-color: var(--alert);
  box-shadow: 0 0 0 3px var(--tint-alert);
}
.hsform .hs_error_rollup .hs-error-msgs label { font-size: 14px; }

/* ---- 9. SIDE-BY-SIDE ROWS --------------------------------
   HubSpot emits one <fieldset> per row, and First Name / Last
   Name / Company are each in their own 1-column row on this
   form. Rather than depend on row position (which breaks the
   moment a field is added), we make the row container a 2-col
   grid and place fields by their stable hs_<fieldname> class.

   Cleanest long-term fix is to drag First + Last into a single
   2-column row in the HubSpot form editor - then this rule is
   redundant and harmless. */
.hsform .hsform-step {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 20px;
}
/* The form's 2nd column exists only to seat the reCAPTCHA badge beside
   the submit/footer block. `auto` sizes it to the badge, so the tail
   column keeps everything else. */
.hsform form.hs-form {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: 28px;
}
/* everything spans the full width by default ... */
.hsform form.hs-form > *,
.hsform .hsform-step > * {
  grid-column: 1 / -1;
  min-width: 0;
}

/* Visual order only - the reCAPTCHA's DOM position is never changed.
   It holds live iframes, and reparenting an iframe reloads it, which
   can invalidate the token. `order` moves it visually to the bottom
   without touching the node. */
/* .hsform-tail holds the submit row, which is safe to move. The badge
   is placed into column 2 of the same row WITHOUT being moved in the
   DOM - grid placement, not reparenting. */
.hsform form.hs-form > .hsform-tail {
  grid-column: 1;
  order: 3;
  min-width: 0;
}
.hsform form.hs-form > .hs_recaptcha {
  grid-column: 2;
  order: 4;
  align-self: center;
}
/* ... except these, which pair up */
.hsform fieldset:has(.hs_firstname) { grid-column: 1; }
.hsform fieldset:has(.hs_lastname)  { grid-column: 2; }

@media (max-width: 620px) {
  .hsform fieldset:has(.hs_firstname),
  .hsform fieldset:has(.hs_lastname) { grid-column: 1 / -1; }
}

/* ---- 10. STEP CARDS --------------------------------------
   The prototype's bordered "01 / 02" sections have no HubSpot
   equivalent - HubSpot produces a flat list of fieldsets with
   nothing wrapping them, so there is no element for CSS to put
   a border on. The onFormReady callback in src/js/hs-form.js
   wraps the fieldsets into .hsform-step containers; these rules
   style them. Remove the callback and the form still renders
   fine, just without the cards. */
.hsform .hsform-step {
  border: var(--card-bd) solid var(--line);
  border-left: var(--card-bd-l) solid var(--blue);
  border-radius: var(--radius);
  padding: 28px var(--card-pad-x);
  margin-bottom: 24px;
}
.hsform .hsform-step > .hsform-step-head {
  grid-column: 1 / -1;
  margin: 0 0 18px;
}
.hsform .hsform-step-head h3 {
  font-family: var(--disp);
  font-size: 19px;
  font-weight: 600;          /* maps to asap_semiexpanded-semibold */
  letter-spacing: .01em;
  color: var(--navy);
  margin: 0 0 4px;
  line-height: 1.3;
}
.hsform .hsform-step-head p {
  color: var(--muted);
  font-size: 13px;
  margin: 0;
}
/* last field in a card should not add trailing space */
.hsform .hsform-step > fieldset:last-child { margin-bottom: 0; }

@media (max-width: 620px) {
  .hsform { --card-pad-x: 18px; }
  .hsform .hsform-step { padding-top: 20px; padding-bottom: 20px; }
}

/* ---- 11. SUBMIT ROW --------------------------------------
   Helper text left, button right. The note is injected into
   .hs_submit by the onFormReady callback (HubSpot has no field
   type for it). */
.hsform .hs_submit {
  display: flex;
  align-items: center;
  /* Note + button sit left-aligned as a pair, on the same left edge as
     the field labels above. Alternatives: `center` to center the pair,
     or `space-between` + `flex: 1 1 260px` on .hsform-note for the
     old edge-to-edge look. */
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 28px;
  margin-top: 4px;
  padding-left: calc(var(--card-pad-x) + var(--card-bd-l));
  padding-right: calc(var(--card-pad-x) + var(--card-bd));
}
.hsform .hs_submit .hsform-note {
  /* must NOT grow, or it fills the row and shoves the button back to the
     right edge, which is what `flex: 1 1 260px` was doing */
  flex: 0 1 auto;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
  margin: 0;
}
.hsform .hs_submit .actions { flex: 0 0 auto; }

/* reCAPTCHA is an iframe - reparenting it forces a reload and can
   invalidate the token, so it stays exactly where HubSpot puts it
   (above the submit row). Spacing only. */
.hsform .hs_recaptcha {
  display: flex;
  justify-content: center;
  margin: 0;
  padding: 0;
}
.hsform .hs_recaptcha .grecaptcha-badge {
  box-shadow: none !important;
}

/* ---- 13. HUBSPOT RETURNING-VISITOR NOTICE ------------------
   Rendered by HubSpot, not by this snippet. When the visitor's
   hubspotutk cookie maps to a known contact and the form has
   "pre-populate fields with known values" enabled, HubSpot fills
   the fields and prepends:

     <div class="cookie-reset-container">
       Not you? <a>Click here to reset</a>
     </div>

   HubSpot's own rule is {font-size:14px;margin-bottom:10px;
   text-align:right}. The <a> carries an onClick but NO href, so
   it gets no link color and no pointer cursor from the browser -
   both have to be set explicitly. */
.hsform .cookie-reset-container {
  margin: 0 0 14px;
  padding-left: calc(var(--card-pad-x) + var(--card-bd-l));
  padding-right: calc(var(--card-pad-x) + var(--card-bd));
  font-size: 13px;
  color: var(--muted);
  text-align: right;
}
.hsform .cookie-reset-container a {
  color: var(--blue);
  cursor: pointer;
  text-decoration: none;
}
.hsform .cookie-reset-container a:hover {
  text-decoration: underline;
}

/* ---- 14. FORM TAIL (submit row, badge alongside) ----------
   Built by the onFormReady callback. Only .hs_submit is moved into
   it; .hs_recaptcha stays put and is positioned by grid, because
   reparenting its iframes would reload them.

   The live page also moves a footer link block in here, carrying
   "Prefer to pick a time?" and a typed meeting URL. It is not ported:
   hard rule 5 says that link comes from shared.json, and the page
   already renders it through layouts/form.njk's `meeting` flag in the
   card directly below the form. Section 12 of the live sheet styled
   that footer and is gone with it. */
.hsform .hsform-tail {
  display: block;
}

@media (max-width: 620px) {
  /* single column: badge drops back underneath, centered */
  .hsform form.hs-form { grid-template-columns: 1fr; }
  .hsform form.hs-form > .hsform-tail,
  .hsform form.hs-form > .hs_recaptcha { grid-column: 1 / -1; }
  .hsform form.hs-form > .hs_recaptcha { margin-top: 18px; }
}


/* ==========================================================================
   Engagement track                                                 [new, §4]

   NEW COMPONENT, added 21 September 2026 for the case study pages. Lifted
   from comp D, design/case-study-templates/parts/d-timeline.js, with its
   comments.

   An ordered list down a vertical rail. The rail is a pseudo element on the
   <ol>, the markers are pseudo elements on each <li>, so the markup stays a
   plain ordered list and reads correctly in a reader that ignores all of it.

   ONE DELIBERATE DEVIATION FROM THE COMP. Comp D fills the rail on scroll,
   which it does with a second pseudo element sized by a --fill custom
   property and a block of rules scoped to .anim. None of that is taken: the
   case study pages ship with no JavaScript, per section 6 of the design
   document, so the second pseudo element would paint the first at 100% on
   every load and the .anim rules would never match anything. The rail is
   drawn once, in blue, which is exactly what comp D renders with script
   blocked. If the fill is wanted later it is one file and one class, and the
   comp has both written already.
   ========================================================================== */
/* One measure for the track and the payoff beneath it. The site wrap is
   1280px and a step's prose caps at 64ch, so an unconstrained track leaves a
   third of the band empty beside a full-width payoff panel, which reads as a
   broken grid rather than as a column. */
.track{list-style:none;margin:0;padding:0;position:relative;max-width:900px}
.track::before{content:"";position:absolute;left:19px;top:14px;bottom:14px;width:2px;
  background:var(--blue)}
.track li{position:relative;padding:0 0 var(--sp-8) 62px;min-height:52px}
.track li:last-child{padding-bottom:0}
.track li::before{content:attr(data-step);position:absolute;left:0;top:0;width:40px;height:40px;
  border-radius:50%;background:var(--white);border:2px solid var(--blue);color:var(--navy);
  font-family:var(--disp);font-weight:700;font-size:15px;display:flex;align-items:center;
  justify-content:center;z-index:1}
.track li:nth-child(3n+2)::before{border-color:var(--green)}
.track li:nth-child(3n+3)::before{border-color:var(--purple)}
.track h3{font-size:1.15rem;margin:8px 0 6px}
.track p{color:var(--body);margin:0;max-width:64ch}
.track .when{font-family:var(--text);font-size:12.5px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--muted);margin:0 0 2px}
.track .won{display:inline-block;margin-top:var(--sp-3);background:var(--white);
  border:1px solid var(--line);border-left:3px solid var(--green);padding:9px var(--sp-4);
  font-size:14.5px;line-height:1.45;color:var(--body);max-width:56ch}
.track .won b{font-family:var(--disp);font-weight:700;color:var(--navy)}

/* The payoff. The track ends in the figure the sequence produced, and this is
   the largest thing on the page by a wide margin. */
.payoff{max-width:900px;background:var(--navy);color:var(--white);border-top:5px solid var(--green);
  padding:var(--sp-8) var(--sp-7);display:grid;grid-template-columns:auto 1fr;
  gap:var(--sp-5) var(--sp-8);align-items:center;margin-top:var(--sp-7)}
.payoff .po-fig{font-family:var(--disp);font-weight:700;font-size:clamp(3.4rem,9vw,6rem);
  line-height:.9;color:var(--white);margin:0;max-width:none}
.payoff .po-label{font-family:var(--text);font-weight:700;font-size:12.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--on-navy);margin:0 0 10px}
.payoff p.po-text{color:var(--on-navy);font-size:1.08rem;line-height:1.55;margin:0;max-width:52ch}
.payoff p.po-text b{color:var(--white)}

@media (max-width:700px){
  .payoff{grid-template-columns:1fr;padding:var(--sp-7) var(--sp-5)}
  .track li{padding-left:52px}
}


/* ==========================================================================
   Hero value panel                                                 [new, §4]

   NEW COMPONENT, added 21 September 2026 for the case study pages. Lifted
   from comp A, design/case-study-templates/parts/a-headline.js.

   The third thing that can fill the hero's right-hand column, after
   .hero-claims and .hero-media. Comp A's argument is that a case study is
   read by somebody building a business case for somebody else, so the number
   is the page: one figure at display size beside the h1, and the supporting
   figures as a definition list under a hairline.

   It sits inside .hero-grid, so it inherits the 1.15fr / .85fr split and the
   navy band with it. Every color here is a navy-panel token for that
   reason: --on-navy for the label and the terms, --white for the figure and
   the values, --rule-navy for the hairlines.

   TWO THINGS IN COMP A ARE NOT TAKEN, both per section 6 of the design
   document. The figure wipes in on scroll, scoped to an .anim class only
   script adds, and there is a .valuebar fixed to the bottom of the viewport
   once the hero scrolls away. The pages ship with no JavaScript, so the
   first would never fire, and the second is neither the hero nor the first
   section and is outside what was asked for.
   ========================================================================== */
.valuepanel{border:1px solid var(--rule-navy);background:rgba(255,255,255,.05);
  padding:var(--sp-6) var(--sp-5) var(--sp-5)}
.valuepanel .vp-label{font-family:var(--text);font-weight:700;font-size:12.5px;
  letter-spacing:.09em;text-transform:uppercase;color:var(--on-navy);margin:0}
.valuepanel .vp-fig{font-family:var(--disp);font-weight:700;line-height:.95;
  font-size:clamp(3.6rem,8vw,5.4rem);color:var(--white);margin:10px 0 6px;max-width:none}
.valuepanel .vp-sub{font-size:15.5px;line-height:1.45;color:var(--on-navy);
  margin:0 0 var(--sp-5);max-width:30ch}
.valuepanel dl{margin:0;border-top:1px solid var(--rule-navy)}
.valuepanel dl div{display:flex;justify-content:space-between;gap:var(--sp-4);
  align-items:baseline;padding:11px 0;border-bottom:1px solid var(--rule-navy)}
.valuepanel dt{font-size:14.5px;color:var(--on-navy)}
.valuepanel dd{margin:0;font-family:var(--disp);font-weight:700;font-size:1.25rem;
  color:var(--white);white-space:nowrap}

@media (max-width:700px){
  .valuepanel .vp-fig{font-size:3.4rem}
}

/* ==========================================================================
   AI READINESS ASSESSMENT                                       [new, §4]
   /database-assessment/ai-readiness-assessment/ and nowhere else.
   components/assessment.njk is the only markup that uses any of it.

   WHY A NEW BLOCK RATHER THAN A COMPOSITION. Nothing in this build styles a
   native form control. The .hsform block above is scoped to HubSpot's own
   injected markup, class for class, and matches none of ours. .ticks is the
   only existing list component and it is a qualification list with a green
   check, which is the wrong semantics for "here is what needs fixing", the
   same objection that produced .legal for the two ported legal documents.
   There is no progress indicator, no meter and no radio card anywhere in the
   build.

   THE NO-SCRIPT STATE IS THE DEFAULT STATE. Every rule that hides something
   is scoped to .assess[data-js="on"], which a three line inline script sets
   during parse. With script blocked, all ten steps, the scoring reference and
   both calls to action are on screen and nothing below applies. Written the
   other way round, a visitor with no JavaScript would see question one and no
   way to reach the other nine. Do not tidy these selectors by hiding the
   steps unconditionally.

   ONE WIDTH IS SET FROM SCRIPT, the progress fill, through a --fill custom
   property rather than a width, so the rule that paints the bar still lives
   in this file. It is the only thing on this page a script styles. The four
   pillar bars were the other until 22 September 2026, when the breakdown came
   off the results screen.

   MOBILE FIRST. Everything below is the 375px layout; the two media queries
   are additive. The inputs are 16px because anything smaller makes iOS zoom
   the viewport on focus, which on a one-question-per-screen form throws away
   the visitor's place.
   ========================================================================== */
.assess{max-width:840px;margin:0 auto}
.assess [hidden]{display:none!important}

/* The three enhancement-only regions, and the step machine. */
.assess-prog,.assess-nav{display:none}
.assess[data-js="on"] .assess-prog{display:block}
.assess[data-js="on"] .assess-nav{display:flex}
.assess[data-js="on"] .assess-ref{display:none}
.assess[data-js="on"] .assess-step{display:none}
.assess[data-js="on"] .assess-step.is-on{display:block}
.assess[data-js="on"].is-done .assess-form,
.assess[data-js="on"].is-done .assess-prog{display:none}

/* ---- progress ---------------------------------------------------------- */
.assess-progtext{display:flex;justify-content:space-between;align-items:baseline;
  gap:var(--sp-3);margin:0 0 10px;font-family:var(--text);font-size:12.5px;
  font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
.assess-progtrack{height:6px;background:var(--bar-track);border-radius:3px;overflow:hidden}
.assess-progfill{height:100%;width:var(--fill,10%);background:var(--blue);
  border-radius:3px;transition:width var(--ease-slow)}
/* Says that the assessment advances on its own. It is inside .assess-prog, so
   it is gated on data-js with everything else: with script blocked nothing
   advances and the sentence would be a lie. */
.assess-prognote{font-size:13px;line-height:1.5;color:var(--muted);margin:10px 0 0}

/* ---- a question -------------------------------------------------------- */
.assess-form{margin:0}
.assess-q{border:0;padding:0;margin:0;min-width:0}
.assess-q legend{padding:0;width:100%;float:left}
.assess-q legend + *{clear:both}
.assess-q-tight{margin-top:var(--sp-7)}
.assess-eyebrow{display:block;font-family:var(--text);font-size:12px;font-weight:700;
  letter-spacing:.09em;text-transform:uppercase;color:var(--blue);margin:0 0 10px}
.assess-qtext{display:block;font-family:var(--disp);font-weight:700;
  font-size:clamp(1.3rem,5.2vw,1.85rem);line-height:1.25;color:var(--title);max-width:30ch}
.assess-qsub{display:block;font-family:var(--text);font-weight:700;font-size:15.5px;
  color:var(--title);margin:0 0 12px}
h3.assess-qtext{margin:0}
.assess-head{margin:0 0 var(--sp-5)}
.assess-head .assess-intro{margin-top:10px}
.assess-intro{font-size:15.5px;line-height:1.6;color:var(--body);margin:0;max-width:62ch}
.assess-subhead{font-family:var(--disp);font-weight:700;font-size:1.2rem;color:var(--title);
  margin:var(--sp-7) 0 var(--sp-3)}

/* ---- the options ------------------------------------------------------- */
.assess-opts{display:grid;gap:10px;margin:var(--sp-5) 0 0}
.assess-opt{display:flex;gap:14px;align-items:flex-start;padding:15px 17px;
  border:1px solid var(--line);background:var(--white);border-radius:3px;cursor:pointer;
  transition:border-color var(--ease),background var(--ease)}
.assess-opt:hover{border-color:var(--navy)}
.assess-opt input{flex:0 0 auto;width:18px;height:18px;margin:2px 0 0;accent-color:var(--navy)}
.assess-optlabel{font-size:15.5px;line-height:1.5;color:var(--body)}

/* The checked and focused states are painted twice on purpose. The :has()
   rules light the whole card, which is the intended treatment; the plain
   input rules are what a browser without :has() still gets, and they are the
   ones that make the control legible rather than decorative. */
.assess-opt input:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
.assess-opt:has(input:checked){border-color:var(--navy);background:var(--wash-blue)}
.assess-opt:has(input:checked) .assess-optlabel{color:var(--title)}
.assess-opt:has(input:focus-visible){outline:2px solid var(--blue);outline-offset:2px}

/* ---- the contact step -------------------------------------------------- */
.assess-fields{display:grid;gap:var(--sp-4);margin:var(--sp-5) 0 0}
.assess-field{margin:0;display:grid;gap:6px}
.assess-field label{font-family:var(--text);font-weight:700;font-size:14px;color:var(--title)}
.assess-opt-tag{font-weight:400;color:var(--muted)}
.assess-field input{width:100%;font-family:inherit;font-size:16px;line-height:1.4;
  padding:12px 14px;color:var(--body);background:var(--white);
  border:1px solid var(--line);border-radius:3px}
.assess-field input:focus-visible{outline:2px solid var(--blue);outline-offset:1px;
  border-color:var(--navy)}
.assess-consent{font-size:13.5px;line-height:1.6;color:var(--muted);margin:var(--sp-5) 0 0}

.assess-err{display:block;font-size:14px;line-height:1.45;color:var(--alert);margin:4px 0 0}
/* The free-mail warning. It is a note, not a failure, so it must not paint
   in the validation color: the whole point is that it does not block. */
.assess-err-soft{color:var(--muted)}

/* ---- navigation -------------------------------------------------------- */
.assess-nav{gap:var(--sp-2);flex-wrap:wrap;margin:var(--sp-7) 0 0;
  padding-top:var(--sp-5);border-top:1px solid var(--line)}
.assess-nav [data-next],.assess-nav [data-submit]{margin-left:auto}
.assess-nav button{font-family:var(--text);cursor:pointer}
.assess-nav button[disabled]{opacity:.4;cursor:not-allowed}
.assess-nav button:focus-visible{outline:2px solid var(--blue);outline-offset:3px}

/* ---- the results ------------------------------------------------------- */
.assess-scorebox{text-align:center;background:var(--white);border:1px solid var(--line);
  border-top:5px solid var(--blue);padding:var(--sp-7) var(--sp-5)}
.assess-scorelabel{font-family:var(--text);font-size:12px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted);margin:0}
.assess-scorelabel:focus-visible{outline:2px solid var(--blue);outline-offset:5px}
.assess-score{display:flex;align-items:baseline;justify-content:center;gap:10px;
  font-family:var(--disp);font-weight:700;line-height:.9;
  font-size:clamp(3.8rem,15vw,6rem);color:var(--navy);margin:12px 0 0;max-width:none}
.assess-scoreof{font-family:var(--text);font-size:13px;font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;color:var(--muted)}
.assess-bandname{font-family:var(--disp);font-weight:700;font-size:1.45rem;color:var(--title);
  margin:var(--sp-5) 0 8px}
.assess-banddesc{font-size:16px;line-height:1.6;color:var(--body);margin:0 auto;max-width:52ch}

/* The four pillar bars were here until 22 September 2026 and came off with
   the breakdown. .assess-focuslist below stays: the reference block still
   publishes the recommendation sets, which is what a no-script visitor and a
   crawler read. */
.assess-focuslist{list-style:none;margin:var(--sp-4) 0 0;padding:0;display:grid;
  gap:var(--sp-4);counter-reset:fx}
.assess-focuslist li{counter-increment:fx;position:relative;padding-left:44px;
  font-size:15.5px;line-height:1.6;color:var(--body)}
.assess-focuslist li::before{content:counter(fx);position:absolute;left:0;top:1px;
  display:flex;align-items:center;justify-content:center;width:30px;height:30px;
  border-radius:50%;background:var(--navy);color:var(--white);
  font-family:var(--disp);font-weight:700;font-size:15px}

/* Small print, and it sits under the call to action rather than above it,
   which is where the results screen puts it. */
.assess-disc{font-size:13.5px;line-height:1.6;color:var(--muted);
  margin:var(--sp-6) 0 0;padding-top:var(--sp-4);border-top:1px solid var(--line)}

.assess-cta{margin:var(--sp-6) 0 0;background:var(--navy);padding:var(--sp-6) var(--sp-5)}
.assess-cta h3{color:var(--white);margin:0 0 10px}
.assess-cta p{color:var(--on-navy);margin:0;max-width:56ch}
.assess-ctabtn{margin:var(--sp-5) 0 0}
.assess-sendstate{margin:var(--sp-4) 0 0;font-size:14px;line-height:1.55;color:var(--on-navy)}

/* ---- the scoring reference, which only a no-script visitor sees --------- */
.assess-ref{margin:var(--sp-8) 0 0;padding-top:var(--sp-6);border-top:1px solid var(--line)}
.assess-refbands{margin:var(--sp-4) 0 0}
.assess-refbands div{padding:14px 0;border-bottom:1px solid var(--line)}
.assess-refbands dt{display:flex;flex-wrap:wrap;align-items:baseline;gap:10px;
  font-family:var(--disp);font-weight:700;font-size:1.05rem;color:var(--title)}
.assess-refrange{font-family:var(--text);font-size:12px;font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;color:var(--muted)}
.assess-refbands dd{margin:6px 0 0;font-size:15px;line-height:1.55;color:var(--body)}
.assess-refrec{margin:var(--sp-5) 0 0}
.assess-refrec h4{margin:0}
.assess-refcta{display:flex;flex-wrap:wrap;gap:var(--sp-2);margin:var(--sp-6) 0 0}

@media (min-width:620px){
  .assess-fields{grid-template-columns:1fr 1fr}
  .assess-field-req{grid-column:1 / -1}
  .assess-opts-inline{grid-template-columns:repeat(auto-fit,minmax(170px,1fr))}
}

@media (min-width:860px){
  .assess-scorebox{padding:var(--sp-9) var(--sp-7)}
  .assess-cta{padding:var(--sp-7)}
}

/* The progress fill is the only animated width left on the page, and a
   readiness score is not worth a motion complaint. */
@media (prefers-reduced-motion:reduce){
  .assess-progfill{transition:none}
}


/* ==========================================================================
   Webinar cards                                                  [new, §4]
   /webinars/ only, with components/webinar-cards.njk. Added 28 September
   2026 with the webinars section.

   IT CANNOT REUSE .posts, WHICH IS THE CARD IT IS CLOSEST TO. That card is
   text from its first pixel: a date, a title, a summary, a link. This one
   leads on a 16:9 still with a play control over it, and the still is the
   reason somebody stops on the card at all. Putting an image region on
   .posts to get it would put an empty one on all 135 blog post cards and on
   the 22 other listing pages that use it, which is the same argument that
   produced .scorecards directly above.

   THREE ACROSS, unlike .scorecards' two. The card has no internal split to
   defend here: the thumbnail is full-bleed across the top and the prose runs
   the whole card width under it, so a narrower column costs measure and
   nothing else. Seven cards land as three, three and one.

   What it does share with .posts is the idiom, and deliberately: white on a
   hairline with a 3px accent top rule, and the nth-child(3n) blue, green,
   purple rotation. A reader should not be able to tell that these two grids
   are different components.

   The overlay lifts rather than deepens on hover. The still is the content;
   darkening it further to signal interactivity would hide the thing being
   pointed at.
   ========================================================================== */
.wcards{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-5)}
.wcards article{background:var(--white);border:1px solid var(--line);border-top:3px solid var(--blue);
  display:flex;flex-direction:column}
.wcards article:nth-child(3n+2){border-top-color:var(--green)}
.wcards article:nth-child(3n+3){border-top-color:var(--purple)}

.wc-thumb{position:relative;display:block;aspect-ratio:16/9;background:var(--navy);overflow:hidden}
.wc-thumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.wc-thumb::before{content:"";position:absolute;inset:0;z-index:1;
  background:rgba(35,43,95,.30);transition:background var(--ease)}
.wcards article:hover .wc-thumb::before{background:rgba(35,43,95,.12)}
.wc-play{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:2;
  width:48px;height:48px;border-radius:50%;background:var(--white);
  display:flex;align-items:center;justify-content:center;transition:transform var(--ease)}
.wc-play::after{content:"";width:0;height:0;margin-left:4px;
  border-left:14px solid var(--navy);border-top:9px solid transparent;border-bottom:9px solid transparent}
.wcards article:hover .wc-play{transform:translate(-50%,-50%) scale(1.06)}

.wc-body{padding:var(--sp-5) 22px;display:flex;flex-direction:column;flex:1}
.wcards time{display:block;font-size:13px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted);margin-bottom:8px}
/* The presenter, not a byline on an article: these are sessions people chose
   to attend because of who was giving them, which is why it sits above the
   title rather than under it. */
.wc-by{font-size:13px;letter-spacing:.05em;text-transform:uppercase;font-weight:700;
  color:var(--navy);margin:0 0 8px;max-width:none}
.wcards h3{font-size:1.05rem;margin:0 0 10px}
.wcards h3 a{text-decoration:none;color:var(--title)}
.wcards h3 a:hover{color:var(--blue)}
.wcards p{font-size:15px;color:var(--muted);margin:0 0 var(--sp-4);max-width:none}
.wcards .more{margin:auto 0 0;max-width:none;font-size:14.5px;font-weight:700}
.wcards .more a{text-decoration:none;color:var(--navy);border-bottom:2px solid var(--blue);
  padding-bottom:2px}

@media (max-width:960px){
  .wcards{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:620px){
  .wcards{grid-template-columns:1fr}
}

@media (prefers-reduced-motion:reduce){
  .wc-thumb::before,.wc-play{transition:none}
}


/* ==========================================================================
   Consent banner  [new, §4]
   The CookieConsent dialogs, restyled onto this site's tokens, plus the
   footer control that reopens them.

   THIS BLOCK WRITES ALMOST NO LAYOUT AND THAT IS THE POINT. The library
   vendored at src/assets/vendor/cookieconsent/ ships its own 32 KB
   stylesheet, loaded BEFORE the four in base.njk, and everything it paints
   reads from 46 --cc-* custom properties declared on its own :root. This
   file redeclares the ones that carry a color, a font or a radius, at equal
   specificity and later in the cascade, so ours win. Nothing here reaches
   into the library's internal class names except the two rules at the foot,
   and both say why.

   So: to change how the banner looks, change a token. Editing the vendored
   CSS works until somebody upgrades it and then silently stops working,
   which is the failure this arrangement exists to prevent.

   The library's own defaults are a near-black #30363c on white with system
   fonts. Nothing below is a tweak of that; it is a different palette.
   ========================================================================== */
:root{
  /* Type and shape. 3px is the radius every button on this site already
     uses, and the library's .4rem would have been the only 6.4px corner in
     the build. */
  --cc-font-family:var(--text);
  --cc-modal-border-radius:3px;
  --cc-btn-border-radius:3px;
  --cc-pm-toggle-border-radius:999px;

  /* Above the sticky header, which is the highest thing on the site at 70.
     The library's own default is 2147483647; this leaves room to put
     something over the banner later without editing a vendored file. */
  --cc-z-index:1000;

  /* Surfaces and text. --paper rather than white for the nested blocks,
     because the page behind the dialog is already white. */
  --cc-bg:var(--white);
  --cc-primary-color:var(--body);
  --cc-secondary-color:var(--muted);
  --cc-link-color:var(--blue);
  --cc-separator-border-color:var(--line);

  /* Accept all: .btn-navy, exactly. */
  --cc-btn-primary-bg:var(--navy);
  --cc-btn-primary-color:var(--white);
  --cc-btn-primary-border-color:var(--navy);
  --cc-btn-primary-hover-bg:var(--navy-hover);
  --cc-btn-primary-hover-color:var(--white);
  --cc-btn-primary-hover-border-color:var(--navy-hover);

  /* Reject all and Manage preferences: .btn-outline, exactly. See the note
     on equal weight at the foot of this block, which is why this is an
     outlined navy button and not the library's light gray fill. */
  --cc-btn-secondary-bg:var(--white);
  --cc-btn-secondary-color:var(--navy);
  --cc-btn-secondary-border-color:var(--navy);
  --cc-btn-secondary-hover-bg:var(--navy);
  --cc-btn-secondary-hover-color:var(--white);
  --cc-btn-secondary-hover-border-color:var(--navy);

  /* The category switches. --off is already documented in tokens.css as the
     switch track, so the off state is not a new value. Green for on matches
     .ticks, where green means included. */
  --cc-toggle-on-bg:var(--green);
  --cc-toggle-off-bg:var(--off);
  --cc-toggle-on-knob-bg:var(--white);
  --cc-toggle-off-knob-bg:var(--white);
  --cc-toggle-readonly-bg:var(--line);
  --cc-toggle-readonly-knob-bg:var(--white);
  --cc-toggle-readonly-knob-icon-color:var(--line);
  --cc-toggle-enabled-icon-color:var(--white);
  --cc-toggle-disabled-icon-color:var(--white);

  /* The expandable category rows. */
  --cc-cookie-category-block-bg:var(--paper);
  --cc-cookie-category-block-border:var(--line);
  --cc-cookie-category-block-hover-bg:var(--wash-navy);
  --cc-cookie-category-block-hover-border:var(--line);
  --cc-cookie-category-expanded-block-hover-bg:var(--wash-navy);
  --cc-section-category-border:var(--line);

  /* The scrim behind the preferences dialog. Navy at 55% rather than black,
     the same navy-as-rgba the .wc-thumb overlay already uses, because a
     black scrim over a navy site reads as a different product. */
  --cc-overlay-bg:rgba(35,43,95,.55);

  --cc-footer-bg:var(--paper);
  --cc-footer-color:var(--muted);
  --cc-footer-border-color:var(--line);

  --cc-webkit-scrollbar-bg:var(--line);
  --cc-webkit-scrollbar-hover-bg:var(--muted);
}

/* The dialog titles. The library sets these in its body font; every heading
   on this site is Asap, and a dialog whose heading is Open Sans is the one
   place a reader would notice the banner is not ours. */
#cc-main .cm__title,#cc-main .pm__title{font-family:var(--disp);color:var(--title)}

/* EQUAL WEIGHT IS A COMPLIANCE REQUIREMENT, NOT A LAYOUT PREFERENCE.
   Consent that is easier to give than to refuse is not freely given, so
   "Reject all" has to be as reachable and as prominent as "Accept all".
   equalWeightButtons in consent.js equalises their widths; this equalises
   their weight, by giving the secondary button a real navy border rather
   than the library's light gray fill, which at 1.4:1 against white reads as
   a disabled control sitting beside a live one.
   DO NOT make reject quieter than accept to tidy up the banner. */
#cc-main .cm__btn,#cc-main .pm__btn{font-weight:700;border-width:1.5px;border-style:solid}

/* The footer control that reopens the dialog, in the utility row beside the
   privacy policy link. It is a <button> because it opens a dialog rather
   than going anywhere, so hard rule 6 does not apply: there is no URL for it
   to be an <a href> to. It ships with the hidden attribute and consent.js
   removes it, the same bargain every enhancement here makes, because with
   script blocked it would open nothing.

   Everything below exists to make a <button> look like the <a> beside it:
   .foot a sets color and hover, and a button inherits none of it. */
.consent-open{appearance:none;-webkit-appearance:none;background:none;border:0;padding:0;
  margin:0;font:inherit;color:var(--on-navy);cursor:pointer;text-decoration:none}
.consent-open:hover{color:var(--white);text-decoration:underline}
